功能:职务奖励金币发放系统
数据库: - positions 新增 daily_reward_limit(单日累计上限) - positions 新增 recipient_daily_limit(同一接收者每日次数上限) 后端: - CurrencySource::POSITION_REWARD 新枚举值 - AdminCommandController::reward() 三层限额校验 ① 单次上限 ② 单日累计上限 ③ 同一接收者每日次数 写履职记录(PositionAuthorityLog)+ UserCurrencyService 聊天室悄悄话通知接收者 - POST /command/reward 路由注册 前端(user-actions.blade.php): - 名片按钮行 2+1 布局(加好友/送礼物/送金币) - 送金币仅在 myMaxReward>0 时显示(职务持有者) - 内联奖励金币面板:金额输入 + 确认发放 + 说明文字 - sendReward() 前端校验 + API 调用 + chatDialog 反馈 后台(positions/index): - 编辑表单新增两个奖励限额字段 - PositionController 验证规则同步更新
This commit is contained in:
@@ -50,6 +50,8 @@ class PositionController extends Controller
|
||||
'level' => 'required|integer|min:1|max:100',
|
||||
'max_persons' => 'nullable|integer|min:1',
|
||||
'max_reward' => 'nullable|integer|min:0',
|
||||
'daily_reward_limit' => 'nullable|integer|min:0',
|
||||
'recipient_daily_limit' => 'nullable|integer|min:0',
|
||||
'sort_order' => 'required|integer|min:0',
|
||||
'appointable_ids' => 'nullable|array',
|
||||
'appointable_ids.*' => 'exists:positions,id',
|
||||
@@ -79,6 +81,8 @@ class PositionController extends Controller
|
||||
'level' => 'required|integer|min:1|max:100',
|
||||
'max_persons' => 'nullable|integer|min:1',
|
||||
'max_reward' => 'nullable|integer|min:0',
|
||||
'daily_reward_limit' => 'nullable|integer|min:0',
|
||||
'recipient_daily_limit' => 'nullable|integer|min:0',
|
||||
'sort_order' => 'required|integer|min:0',
|
||||
'appointable_ids' => 'nullable|array',
|
||||
'appointable_ids.*' => 'exists:positions,id',
|
||||
|
||||
@@ -15,12 +15,15 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Enums\CurrencySource;
|
||||
use App\Events\MessageSent;
|
||||
use App\Jobs\SaveMessageJob;
|
||||
use App\Models\Message;
|
||||
use App\Models\PositionAuthorityLog;
|
||||
use App\Models\Sysparam;
|
||||
use App\Models\User;
|
||||
use App\Services\ChatStateService;
|
||||
use App\Services\UserCurrencyService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
@@ -33,6 +36,7 @@ class AdminCommandController extends Controller
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly ChatStateService $chatState,
|
||||
private readonly UserCurrencyService $currencyService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -386,12 +390,12 @@ class AdminCommandController extends Controller
|
||||
{
|
||||
$request->validate([
|
||||
'room_id' => 'required|integer',
|
||||
'type' => 'required|in:fireworks,rain,lightning,snow',
|
||||
'type' => 'required|in:fireworks,rain,lightning,snow',
|
||||
]);
|
||||
|
||||
$admin = Auth::user();
|
||||
$roomId = $request->input('room_id');
|
||||
$type = $request->input('type');
|
||||
$admin = Auth::user();
|
||||
$roomId = $request->input('room_id');
|
||||
$type = $request->input('type');
|
||||
$superLevel = (int) Sysparam::getValue('superlevel', '100');
|
||||
|
||||
// 仅 superlevel 等级可触发特效
|
||||
@@ -405,6 +409,139 @@ class AdminCommandController extends Controller
|
||||
return response()->json(['status' => 'success', 'message' => "已触发特效:{$type}"]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 职务奖励金币(凭空发放,无需扣操作者余额)
|
||||
*
|
||||
* 三层限额校验:
|
||||
* 1. amount ≤ position.max_reward (单次上限)
|
||||
* 2. 今日累计发放 + amount ≤ position.daily_reward_limit (操作人单日累计上限)
|
||||
* 3. 今日对同一接收者发放次数 < position.recipient_daily_limit(同一接收者每日次数限)
|
||||
*
|
||||
* 成功后:
|
||||
* - 通过 UserCurrencyService 给接收者增加金币
|
||||
* - 写入 PositionAuthorityLog(action_type=reward,记录到履职记录)
|
||||
* - 向房间发送悄悄话通知接收者
|
||||
*
|
||||
* @param Request $request 需包含 username, room_id, amount
|
||||
*/
|
||||
public function reward(Request $request): JsonResponse
|
||||
{
|
||||
$request->validate([
|
||||
'username' => 'required|string',
|
||||
'room_id' => 'required|integer',
|
||||
'amount' => 'required|integer|min:1|max:99999',
|
||||
]);
|
||||
|
||||
$admin = Auth::user();
|
||||
$roomId = (int) $request->input('room_id');
|
||||
$amount = (int) $request->input('amount');
|
||||
$targetUsername = $request->input('username');
|
||||
|
||||
// 不能给自己发放
|
||||
if ($admin->username === $targetUsername) {
|
||||
return response()->json(['status' => 'error', 'message' => '不能给自己发放奖励'], 422);
|
||||
}
|
||||
|
||||
// 必须有在职职务且职务配置了 max_reward
|
||||
$userPosition = $admin->activePosition;
|
||||
if (! $userPosition) {
|
||||
return response()->json(['status' => 'error', 'message' => '你当前没有在职职务,无权发放奖励'], 403);
|
||||
}
|
||||
|
||||
$position = $userPosition->position;
|
||||
if (! $position?->max_reward) {
|
||||
return response()->json(['status' => 'error', 'message' => '你的职务未配置奖励权限'], 403);
|
||||
}
|
||||
|
||||
// 目标用户必须存在
|
||||
$target = User::where('username', $targetUsername)->first();
|
||||
if (! $target) {
|
||||
return response()->json(['status' => 'error', 'message' => '用户不存在'], 404);
|
||||
}
|
||||
|
||||
// ① 单次上限校验
|
||||
if ($amount > $position->max_reward) {
|
||||
return response()->json([
|
||||
'status' => 'error',
|
||||
'message' => "单次奖励上限为 {$position->max_reward} 金币,请调整金额",
|
||||
], 422);
|
||||
}
|
||||
|
||||
// ② 操作人单日累计上限校验
|
||||
if ($position->daily_reward_limit) {
|
||||
$todayTotal = PositionAuthorityLog::where('user_id', $admin->id)
|
||||
->where('action_type', 'reward')
|
||||
->whereDate('created_at', today())
|
||||
->sum('amount');
|
||||
|
||||
if ($todayTotal + $amount > $position->daily_reward_limit) {
|
||||
$remaining = max(0, $position->daily_reward_limit - $todayTotal);
|
||||
|
||||
return response()->json([
|
||||
'status' => 'error',
|
||||
'message' => "今日剩余可发放额度为 {$remaining} 金币,超出单日上限({$position->daily_reward_limit})",
|
||||
], 422);
|
||||
}
|
||||
}
|
||||
|
||||
// ③ 同一接收者每日次数上限校验
|
||||
if ($position->recipient_daily_limit) {
|
||||
$recipientCount = PositionAuthorityLog::where('user_id', $admin->id)
|
||||
->where('target_user_id', $target->id)
|
||||
->where('action_type', 'reward')
|
||||
->whereDate('created_at', today())
|
||||
->count();
|
||||
|
||||
if ($recipientCount >= $position->recipient_daily_limit) {
|
||||
return response()->json([
|
||||
'status' => 'error',
|
||||
'message' => "今日已对 {$targetUsername} 发放过 {$position->recipient_daily_limit} 次奖励,已达上限",
|
||||
], 422);
|
||||
}
|
||||
}
|
||||
|
||||
// 发放金币(通过 UserCurrencyService 原子性更新 + 写流水)
|
||||
$this->currencyService->change(
|
||||
$target,
|
||||
'gold',
|
||||
$amount,
|
||||
CurrencySource::POSITION_REWARD,
|
||||
"{$admin->username}({$position->name})职务奖励",
|
||||
$roomId,
|
||||
);
|
||||
|
||||
// 写履职记录(PositionAuthorityLog)
|
||||
PositionAuthorityLog::create([
|
||||
'user_id' => $admin->id,
|
||||
'user_position_id' => $userPosition->id,
|
||||
'action_type' => 'reward',
|
||||
'target_user_id' => $target->id,
|
||||
'amount' => $amount,
|
||||
'remark' => "发放奖励金币 {$amount} 枚给 {$targetUsername}",
|
||||
]);
|
||||
|
||||
// 向聊天室发送悄悄话通知接收者
|
||||
$msg = [
|
||||
'id' => $this->chatState->nextMessageId($roomId),
|
||||
'room_id' => $roomId,
|
||||
'from_user' => '系统',
|
||||
'to_user' => $targetUsername,
|
||||
'content' => "🎁 <b>{$admin->username}</b>({$position->name})向你发放了 <b>{$amount}</b> 枚金币奖励!当前金币:{$target->fresh()->jjb} 枚。",
|
||||
'is_secret' => true,
|
||||
'font_color' => '#d97706',
|
||||
'action' => '',
|
||||
'sent_at' => now()->toDateTimeString(),
|
||||
];
|
||||
$this->chatState->pushMessage($roomId, $msg);
|
||||
broadcast(new MessageSent($roomId, $msg));
|
||||
SaveMessageJob::dispatch($msg);
|
||||
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
'message' => "已向 {$targetUsername} 发放 {$amount} 金币奖励 🎉",
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 权限检查:管理员是否可对目标用户执行指定操作
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user