From 5273b4ee4b26554144347f9e02bdf568db6e5ede Mon Sep 17 00:00:00 2001 From: lkddi Date: Fri, 24 Apr 2026 23:09:32 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=E8=81=8C=E5=8A=A1=E7=A4=BC?= =?UTF-8?q?=E5=8C=85=E7=BA=A2=E5=8C=85=E9=BB=98=E8=AE=A4=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/Admin/PositionController.php | 8 ++ app/Http/Controllers/RedPacketController.php | 94 +++++++++++++--- app/Models/Position.php | 4 + app/Services/PositionPermissionService.php | 8 +- ...red_packet_defaults_to_positions_table.php | 44 ++++++++ .../views/admin/positions/index.blade.php | 39 ++++++- .../partials/games/red-packet-panel.blade.php | 104 ++++++++++++------ routes/web.php | 1 + tests/Feature/ChatControllerTest.php | 25 +++-- .../Feature/AdminCommandControllerTest.php | 21 ++-- .../Feature/AdminPositionPermissionTest.php | 37 +++++++ tests/Feature/RedPacketControllerTest.php | 102 ++++++++++++++--- 12 files changed, 394 insertions(+), 93 deletions(-) create mode 100644 database/migrations/2026_04_24_225521_add_red_packet_defaults_to_positions_table.php diff --git a/app/Http/Controllers/Admin/PositionController.php b/app/Http/Controllers/Admin/PositionController.php index f1b40c6..38259ba 100644 --- a/app/Http/Controllers/Admin/PositionController.php +++ b/app/Http/Controllers/Admin/PositionController.php @@ -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); diff --git a/app/Http/Controllers/RedPacketController.php b/app/Http/Controllers/RedPacketController.php index 3f21f23..861158e 100644 --- a/app/Http/Controllers/RedPacketController.php +++ b/app/Http/Controllers/RedPacketController.php @@ -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 = '