- 移除聊天室右下角浮动游戏图标(占卜、百家乐、赛马、老虎机) - 用户名片按钮区:修复已婚/已好友时按钮换行问题,统一单行显示 - 婚礼红包弹窗:重设计为喜庆鲜红背景,领取按钮改为圆形米黄样式 - 新增婚礼红包恢复接口(/wedding/pending-envelopes),刷新后自动恢复领取按钮 - 修复 Alpine :style 字符串覆盖静态 style 导致圆形按钮失效的问题 - 撤职后用户等级改为根据经验值重新计算,不再无条件重置为1 - 管理员修改用户经验值后自动重算等级,有职务用户等级锁定 - 娱乐大厅钓鱼游戏按钮直接调用 startFishing() 简化操作流程 - 新增赛马、占卜、百家乐游戏及相关后端逻辑
303 lines
11 KiB
PHP
303 lines
11 KiB
PHP
<?php
|
||
|
||
/**
|
||
* 文件功能:职务任命服务
|
||
* 处理职务系统的核心业务逻辑:任命、撤销、权限校验
|
||
* 所有权限操作均写入 position_authority_logs 留存审计
|
||
*
|
||
* @author ChatRoom Laravel
|
||
*
|
||
* @version 1.0.0
|
||
*/
|
||
|
||
namespace App\Services;
|
||
|
||
use App\Models\Position;
|
||
use App\Models\PositionAuthorityLog;
|
||
use App\Models\User;
|
||
use App\Models\UserPosition;
|
||
use Illuminate\Support\Facades\DB;
|
||
|
||
class AppointmentService
|
||
{
|
||
/**
|
||
* 获取用户当前在职记录(无则返回 null)
|
||
*/
|
||
public function getActivePosition(User $user): ?UserPosition
|
||
{
|
||
return UserPosition::query()
|
||
->where('user_id', $user->id)
|
||
->where('is_active', true)
|
||
->with(['position.department', 'position.appointablePositions'])
|
||
->first();
|
||
}
|
||
|
||
/**
|
||
* 校验操作人是否有权将目标用户任命到指定职务
|
||
*
|
||
* id=1 超级管理员绕过所有要目校验,可直接任命任意职务。
|
||
*
|
||
* @return array{ok: bool, message: string}
|
||
*/
|
||
public function validateAppoint(User $operator, User $target, Position $targetPosition): array
|
||
{
|
||
// 超级管理员(id=1)特权:跳过职务和白名单校验,只检查被任命人是否已有职务
|
||
if ($operator->id === 1) {
|
||
$existingPosition = $this->getActivePosition($target);
|
||
if ($existingPosition) {
|
||
$currentName = $existingPosition->position->name;
|
||
|
||
return ['ok' => false, 'message' => "【{$target->username}】当前已担任【{$currentName}】,请先撤销其职务再重新任命。"];
|
||
}
|
||
|
||
return ['ok' => true, 'message' => '超级管理员直接授权'];
|
||
}
|
||
|
||
// 操作人必须有在职职务
|
||
$operatorPosition = $this->getActivePosition($operator);
|
||
if (! $operatorPosition) {
|
||
return ['ok' => false, 'message' => '您当前无在职职务,无法进行任命操作。'];
|
||
}
|
||
|
||
// 校验任命白名单:目标职务是否在操作人职务的可任命列表内
|
||
$isAllowed = $operatorPosition->position
|
||
->appointablePositions()
|
||
->where('positions.id', $targetPosition->id)
|
||
->exists();
|
||
|
||
if (! $isAllowed) {
|
||
return ['ok' => false, 'message' => "您的职务无权任命【{$targetPosition->name}】职位。"];
|
||
}
|
||
|
||
// 检查目标职务是否已满员
|
||
if ($targetPosition->isFull()) {
|
||
return ['ok' => false, 'message' => "【{$targetPosition->name}】职位人数已满,无法继续任命。"];
|
||
}
|
||
|
||
// 检查被任命人是否已有在职职务
|
||
$existingPosition = $this->getActivePosition($target);
|
||
if ($existingPosition) {
|
||
$currentName = $existingPosition->position->name;
|
||
|
||
return ['ok' => false, 'message' => "【{$target->username}】当前已担任【{$currentName}】,请先撤销其职务再重新任命。"];
|
||
}
|
||
|
||
return ['ok' => true, 'message' => '校验通过'];
|
||
}
|
||
|
||
/**
|
||
* 执行任命操作
|
||
* 任命成功后自动同步 user_level 并写入权限日志
|
||
*
|
||
* @return array{ok: bool, message: string, userPosition?: UserPosition}
|
||
*/
|
||
public function appoint(User $operator, User $target, Position $targetPosition, ?string $remark = null): array
|
||
{
|
||
// 权限校验
|
||
$validation = $this->validateAppoint($operator, $target, $targetPosition);
|
||
if (! $validation['ok']) {
|
||
return $validation;
|
||
}
|
||
|
||
// id=1 超级管理员无需在职职务,直接任命
|
||
$operatorPosition = $operator->id === 1 ? null : $this->getActivePosition($operator);
|
||
|
||
DB::transaction(function () use ($operator, $target, $targetPosition, $remark, $operatorPosition, &$userPosition) {
|
||
// 创建任职记录
|
||
$userPosition = UserPosition::create([
|
||
'user_id' => $target->id,
|
||
'position_id' => $targetPosition->id,
|
||
'appointed_by_user_id' => $operator->id,
|
||
'appointed_at' => now(),
|
||
'remark' => $remark,
|
||
'is_active' => true,
|
||
]);
|
||
|
||
// 同步 user_level
|
||
$target->update(['user_level' => $targetPosition->level]);
|
||
|
||
// 写入权限操作日志
|
||
$this->logAuthority(
|
||
operator: $operator,
|
||
operatorPosition: $operatorPosition,
|
||
actionType: 'appoint',
|
||
target: $target,
|
||
targetPosition: $targetPosition,
|
||
remark: $remark
|
||
);
|
||
});
|
||
|
||
return [
|
||
'ok' => true,
|
||
'message' => "已成功将【{$target->username}】任命为【{$targetPosition->name}】。",
|
||
'userPosition' => $userPosition,
|
||
];
|
||
}
|
||
|
||
/**
|
||
* 校验操作人是否有权撤销目标用户的职务
|
||
*
|
||
* id=1 超级管理员可直接撤销任意职务。
|
||
*
|
||
* @return array{ok: bool, message: string}
|
||
*/
|
||
public function validateRevoke(User $operator, User $target): array
|
||
{
|
||
// 超级管理员(id=1)特权:跳过白名单校验,直接撤销任意职务
|
||
if ($operator->id === 1) {
|
||
$targetPosition = $this->getActivePosition($target);
|
||
if (! $targetPosition) {
|
||
return ['ok' => false, 'message' => "【{$target->username}】当前没有在职职务。"];
|
||
}
|
||
|
||
return ['ok' => true, 'message' => '超级管理员直接授权'];
|
||
}
|
||
|
||
// 操作人必须有在职职务
|
||
$operatorPosition = $this->getActivePosition($operator);
|
||
if (! $operatorPosition) {
|
||
return ['ok' => false, 'message' => '您当前无在职职务,无法进行撤职操作。'];
|
||
}
|
||
|
||
// 被撤销人必须有在职职务
|
||
$targetPosition = $this->getActivePosition($target);
|
||
if (! $targetPosition) {
|
||
return ['ok' => false, 'message' => "【{$target->username}】当前没有在职职务。"];
|
||
}
|
||
|
||
// 操作人不能撤销自己
|
||
if ($operator->id === $target->id) {
|
||
return ['ok' => false, 'message' => '不能撤销自己的职务。'];
|
||
}
|
||
|
||
// 操作人的任命白名单中必须包含目标职务(即有权任命该职务,也就有权撤销)
|
||
$isAllowed = $operatorPosition->position
|
||
->appointablePositions()
|
||
->where('positions.id', $targetPosition->position_id)
|
||
->exists();
|
||
|
||
if (! $isAllowed) {
|
||
return ['ok' => false, 'message' => "您的职务无权撤销【{$targetPosition->position->name}】职位的人员。"];
|
||
}
|
||
|
||
return ['ok' => true, 'message' => '校验通过'];
|
||
}
|
||
|
||
/**
|
||
* 执行撤销职务操作
|
||
* 撤销后 user_level 归 1,并写入权限日志
|
||
*
|
||
* @return array{ok: bool, message: string}
|
||
*/
|
||
public function revoke(User $operator, User $target, ?string $remark = null): array
|
||
{
|
||
// 权限校验
|
||
$validation = $this->validateRevoke($operator, $target);
|
||
if (! $validation['ok']) {
|
||
return $validation;
|
||
}
|
||
|
||
$operatorPosition = $this->getActivePosition($operator);
|
||
$targetUP = $this->getActivePosition($target);
|
||
|
||
DB::transaction(function () use ($operator, $target, $remark, $operatorPosition, $targetUP) {
|
||
// 撤销在职记录
|
||
$targetUP->update([
|
||
'is_active' => false,
|
||
'revoked_at' => now(),
|
||
'revoked_by_user_id' => $operator->id,
|
||
]);
|
||
|
||
// 关闭尚未结束的 duty_log(只结算今日,历史遗留置0)
|
||
$target->activePosition?->dutyLogs()
|
||
->whereNull('logout_at')
|
||
->whereDate('login_at', today())
|
||
->update([
|
||
'logout_at' => now(),
|
||
'duration_seconds' => DB::raw('TIMESTAMPDIFF(SECOND, login_at, NOW())'),
|
||
]);
|
||
|
||
// 历史遗留未关闭日志直接清零
|
||
$target->activePosition?->dutyLogs()
|
||
->whereNull('logout_at')
|
||
->whereDate('login_at', '<', today())
|
||
->update([
|
||
'logout_at' => DB::raw('login_at'),
|
||
'duration_seconds' => 0,
|
||
]);
|
||
|
||
// 撤职后:按当前经验值重新计算等级(不再无条件归 1)
|
||
// 这样用户撤职后能保留正常的经验升级成果
|
||
$recalcLevel = \App\Models\Sysparam::calculateLevel($target->exp_num ?? 0);
|
||
$superLevel = (int) \App\Models\Sysparam::getValue('superlevel', '100');
|
||
// 不超过满级阈值
|
||
$safeLevel = max(1, min($recalcLevel, $superLevel - 1));
|
||
$target->update(['user_level' => $safeLevel]);
|
||
|
||
// 写入权限操作日志
|
||
$this->logAuthority(
|
||
operator: $operator,
|
||
operatorPosition: $operatorPosition,
|
||
actionType: 'revoke',
|
||
target: $target,
|
||
targetPosition: $targetUP->position,
|
||
remark: $remark
|
||
);
|
||
});
|
||
|
||
return [
|
||
'ok' => true,
|
||
'message' => "已成功撤销【{$target->username}】的【{$targetUP->position->name}】职务,其等级已归 1。",
|
||
];
|
||
}
|
||
|
||
/**
|
||
* 记录权限操作日志(各类管理操作公共调用)
|
||
*
|
||
* @param string $actionType 操作类型(appoint/revoke/reward/warn/kick/mute/banip/other)
|
||
*/
|
||
public function logAuthority(
|
||
User $operator,
|
||
?UserPosition $operatorPosition,
|
||
string $actionType,
|
||
User $target,
|
||
?Position $targetPosition = null,
|
||
?int $amount = null,
|
||
?string $remark = null,
|
||
): void {
|
||
// 无在职职务的操作不记录(普通管理员通过 user_level 操作不进此表)
|
||
if (! $operatorPosition) {
|
||
return;
|
||
}
|
||
|
||
PositionAuthorityLog::create([
|
||
'user_id' => $operator->id,
|
||
'user_position_id' => $operatorPosition->id,
|
||
'action_type' => $actionType,
|
||
'target_user_id' => $target->id,
|
||
'target_position_id' => $targetPosition?->id,
|
||
'amount' => $amount,
|
||
'remark' => $remark,
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* 获取视图用:操作人有权任命的职务列表(用于后台/弹窗任命下拉选择)
|
||
*
|
||
* @return \Illuminate\Database\Eloquent\Collection<int, Position>
|
||
*/
|
||
public function getAppointablePositions(User $operator)
|
||
{
|
||
$operatorPosition = $this->getActivePosition($operator);
|
||
if (! $operatorPosition) {
|
||
return collect();
|
||
}
|
||
|
||
return $operatorPosition->position
|
||
->appointablePositions()
|
||
->with('department')
|
||
->orderByDesc('rank')
|
||
->get();
|
||
}
|
||
}
|