Files
chatroom/app/Services/AppointmentService.php

298 lines
10 KiB
PHP
Raw Normal View History

<?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,
]);
// user_level 归 1由系统经验值自然升级机制重新成长
$target->update(['user_level' => 1]);
// 写入权限操作日志
$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();
}
}