完善职务礼包红包默认配置
This commit is contained in:
@@ -70,6 +70,8 @@ class PositionController extends Controller
|
||||
'max_reward' => 'nullable|integer|min:0',
|
||||
'daily_reward_limit' => 'nullable|integer|min:0',
|
||||
'recipient_daily_limit' => 'nullable|integer|min:0',
|
||||
'red_packet_amount' => 'nullable|integer|min:1|max:999999999|gte:red_packet_count',
|
||||
'red_packet_count' => 'nullable|integer|min:1|max:100',
|
||||
'sort_order' => 'required|integer|min:0',
|
||||
'appointable_ids' => 'nullable|array',
|
||||
'appointable_ids.*' => 'exists:positions,id',
|
||||
@@ -80,6 +82,8 @@ class PositionController extends Controller
|
||||
$appointableIds = $data['appointable_ids'] ?? [];
|
||||
unset($data['appointable_ids']);
|
||||
$data['permissions'] = array_values(array_unique($data['permissions'] ?? []));
|
||||
$data['red_packet_amount'] = (int) ($data['red_packet_amount'] ?? 8888);
|
||||
$data['red_packet_count'] = (int) ($data['red_packet_count'] ?? 10);
|
||||
|
||||
$position = Position::create($data);
|
||||
|
||||
@@ -161,6 +165,8 @@ class PositionController extends Controller
|
||||
'max_reward' => 'nullable|integer|min:0',
|
||||
'daily_reward_limit' => 'nullable|integer|min:0',
|
||||
'recipient_daily_limit' => 'nullable|integer|min:0',
|
||||
'red_packet_amount' => 'nullable|integer|min:1|max:999999999|gte:red_packet_count',
|
||||
'red_packet_count' => 'nullable|integer|min:1|max:100',
|
||||
'sort_order' => 'required|integer|min:0',
|
||||
'appointable_ids' => 'nullable|array',
|
||||
'appointable_ids.*' => 'exists:positions,id',
|
||||
@@ -171,6 +177,8 @@ class PositionController extends Controller
|
||||
$appointableIds = $data['appointable_ids'] ?? [];
|
||||
unset($data['appointable_ids']);
|
||||
$data['permissions'] = array_values(array_unique($data['permissions'] ?? []));
|
||||
$data['red_packet_amount'] = (int) ($data['red_packet_amount'] ?? 8888);
|
||||
$data['red_packet_count'] = (int) ($data['red_packet_count'] ?? 10);
|
||||
|
||||
$position->update($data);
|
||||
$position->appointablePositions()->sync($appointableIds);
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
* 文件功能:聊天室礼包(红包)控制器
|
||||
*
|
||||
* 提供两个核心接口:
|
||||
* - send() :拥有权限的职务用户凭空发出 8888 数量 10 份礼包(金币 or 经验)
|
||||
* - config():读取当前职务的默认礼包数量与份数
|
||||
* - send() :拥有权限的职务用户按职务配置发出礼包(金币 or 经验)
|
||||
* - claim() :在线用户抢礼包(先到先得,每人一份)
|
||||
*
|
||||
* 接入 UserCurrencyService 记录所有货币变动流水。
|
||||
@@ -23,6 +24,7 @@ use App\Events\RedPacketSent;
|
||||
use App\Jobs\SaveMessageJob;
|
||||
use App\Models\RedPacketClaim;
|
||||
use App\Models\RedPacketEnvelope;
|
||||
use App\Models\User;
|
||||
use App\Services\ChatStateService;
|
||||
use App\Services\PositionPermissionService;
|
||||
use App\Services\UserCurrencyService;
|
||||
@@ -40,11 +42,11 @@ use Illuminate\Support\Facades\DB;
|
||||
*/
|
||||
class RedPacketController extends Controller
|
||||
{
|
||||
/** 礼包固定总数量 */
|
||||
private const TOTAL_AMOUNT = 8888;
|
||||
/** 礼包默认总数量 */
|
||||
private const DEFAULT_TOTAL_AMOUNT = 8888;
|
||||
|
||||
/** 礼包固定份数 */
|
||||
private const TOTAL_COUNT = 10;
|
||||
/** 礼包默认份数 */
|
||||
private const DEFAULT_TOTAL_COUNT = 10;
|
||||
|
||||
/** 礼包有效期(秒) */
|
||||
private const EXPIRE_SECONDS = 300;
|
||||
@@ -58,10 +60,34 @@ class RedPacketController extends Controller
|
||||
private readonly PositionPermissionService $positionPermissionService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 获取当前用户可发出的礼包默认配置。
|
||||
*
|
||||
* 聊天室发包弹窗打开时调用,确保页面展示与最终发包数量同源。
|
||||
*/
|
||||
public function config(): JsonResponse
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
// 仅拥有礼包红包权限的在职职务可以读取发包配置。
|
||||
if (! $this->positionPermissionService->hasPermission($user, PositionPermissionRegistry::ROOM_RED_PACKET)) {
|
||||
return response()->json(['status' => 'error', 'message' => '当前职务无权发礼包红包'], 403);
|
||||
}
|
||||
|
||||
$redPacketConfig = $this->redPacketConfigForUser($user);
|
||||
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
'amount' => $redPacketConfig['amount'],
|
||||
'count' => $redPacketConfig['count'],
|
||||
'expire_seconds' => self::EXPIRE_SECONDS,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 拥有权限的职务用户凭空发出礼包。
|
||||
*
|
||||
* 不扣发包人自身货币,888 数量凭空发出分 10 份。
|
||||
* 不扣发包人自身货币,礼包总量和份数读取当前在职职务配置。
|
||||
* type 参数决定本次发出的是金币(gold)还是经验(exp)。
|
||||
*
|
||||
* @param Request $request 需包含 room_id 和 type(gold / exp)
|
||||
@@ -82,6 +108,10 @@ class RedPacketController extends Controller
|
||||
return response()->json(['status' => 'error', 'message' => '当前职务无权发礼包红包'], 403);
|
||||
}
|
||||
|
||||
$redPacketConfig = $this->redPacketConfigForUser($user);
|
||||
$totalAmount = $redPacketConfig['amount'];
|
||||
$totalCount = $redPacketConfig['count'];
|
||||
|
||||
// 检查该用户在此房间是否有进行中的红包(防止刷包)
|
||||
$activeExists = RedPacketEnvelope::query()
|
||||
->where('sender_id', $user->id)
|
||||
@@ -94,8 +124,8 @@ class RedPacketController extends Controller
|
||||
return response()->json(['status' => 'error', 'message' => '您有一个礼包尚未领完,请稍后再发!'], 422);
|
||||
}
|
||||
|
||||
// 随机拆分数量(二倍均值法,保证每份至少 1,总额精确等于 TOTAL_AMOUNT)
|
||||
$amounts = $this->splitAmount(self::TOTAL_AMOUNT, self::TOTAL_COUNT);
|
||||
// 随机拆分数量(二倍均值法,保证每份至少 1,总额精确等于职务配置总量)
|
||||
$amounts = $this->splitAmount($totalAmount, $totalCount);
|
||||
|
||||
// 货币展示文案
|
||||
$typeLabel = $type === 'exp' ? '经验' : '金币';
|
||||
@@ -105,15 +135,15 @@ class RedPacketController extends Controller
|
||||
: 'linear-gradient(135deg,#dc2626,#ea580c)';
|
||||
|
||||
// 事务:创建红包记录 + Redis 写入分额
|
||||
$envelope = DB::transaction(function () use ($user, $roomId, $type, $amounts): RedPacketEnvelope {
|
||||
$envelope = DB::transaction(function () use ($user, $roomId, $type, $amounts, $totalAmount, $totalCount): RedPacketEnvelope {
|
||||
// 创建红包主记录(凭空发出,不扣发包人货币)
|
||||
$envelope = RedPacketEnvelope::create([
|
||||
'sender_id' => $user->id,
|
||||
'sender_username' => $user->username,
|
||||
'room_id' => $roomId,
|
||||
'type' => $type,
|
||||
'total_amount' => self::TOTAL_AMOUNT,
|
||||
'total_count' => self::TOTAL_COUNT,
|
||||
'total_amount' => $totalAmount,
|
||||
'total_count' => $totalCount,
|
||||
'claimed_count' => 0,
|
||||
'claimed_amount' => 0,
|
||||
'status' => 'active',
|
||||
@@ -138,8 +168,8 @@ class RedPacketController extends Controller
|
||||
$btnHtml = '<button data-sent-at="'.time().'" onclick="showRedPacketModal('
|
||||
.$envelope->id
|
||||
.',\''.$user->username.'\','
|
||||
.self::TOTAL_AMOUNT.','
|
||||
.self::TOTAL_COUNT.','
|
||||
.$totalAmount.','
|
||||
.$totalCount.','
|
||||
.self::EXPIRE_SECONDS
|
||||
.',\''.$type.'\''
|
||||
.')" style="margin-left:8px;padding:2px 10px;background:'.$btnBg.';'
|
||||
@@ -151,7 +181,7 @@ class RedPacketController extends Controller
|
||||
'room_id' => $roomId,
|
||||
'from_user' => '系统公告',
|
||||
'to_user' => '',
|
||||
'content' => "🧧 <b>{$user->username}</b> 发出了一个 <b>".self::TOTAL_AMOUNT."</b> {$typeLabel}的礼包!共 ".self::TOTAL_COUNT." 份,先到先得,快去抢!{$btnHtml}",
|
||||
'content' => "🧧 <b>{$user->username}</b> 发出了一个 <b>{$totalAmount}</b> {$typeLabel}的礼包!共 {$totalCount} 份,先到先得,快去抢!{$btnHtml}",
|
||||
'is_secret' => false,
|
||||
'font_color' => $type === 'exp' ? '#6d28d9' : '#b91c1c',
|
||||
'action' => '',
|
||||
@@ -166,15 +196,15 @@ class RedPacketController extends Controller
|
||||
roomId: $roomId,
|
||||
envelopeId: $envelope->id,
|
||||
senderUsername: $user->username,
|
||||
totalAmount: self::TOTAL_AMOUNT,
|
||||
totalCount: self::TOTAL_COUNT,
|
||||
totalAmount: $totalAmount,
|
||||
totalCount: $totalCount,
|
||||
expireSeconds: self::EXPIRE_SECONDS,
|
||||
type: $type,
|
||||
));
|
||||
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
'message' => "🧧 {$typeLabel}礼包已发出!".self::TOTAL_AMOUNT." {$typeLabel} · ".self::TOTAL_COUNT.' 份',
|
||||
'message' => "🧧 {$typeLabel}礼包已发出!{$totalAmount} {$typeLabel} · {$totalCount} 份",
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -389,4 +419,34 @@ class RedPacketController extends Controller
|
||||
|
||||
return $amounts;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按当前在职职务解析礼包红包配置。
|
||||
*
|
||||
* @return array{amount: int, count: int}
|
||||
*/
|
||||
private function redPacketConfigForUser(User $user): array
|
||||
{
|
||||
$position = $user->activePosition?->position;
|
||||
$amount = (int) ($position?->red_packet_amount ?? self::DEFAULT_TOTAL_AMOUNT);
|
||||
$count = (int) ($position?->red_packet_count ?? self::DEFAULT_TOTAL_COUNT);
|
||||
|
||||
if ($amount < 1) {
|
||||
$amount = self::DEFAULT_TOTAL_AMOUNT;
|
||||
}
|
||||
|
||||
if ($count < 1 || $count > 100) {
|
||||
$count = self::DEFAULT_TOTAL_COUNT;
|
||||
}
|
||||
|
||||
if ($amount < $count) {
|
||||
$amount = self::DEFAULT_TOTAL_AMOUNT;
|
||||
$count = self::DEFAULT_TOTAL_COUNT;
|
||||
}
|
||||
|
||||
return [
|
||||
'amount' => $amount,
|
||||
'count' => $count,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user