- 在多个控制器中更新权限检查逻辑,确保管理员能够更灵活地管理代理和玩家。 - 在 AdminPlayerStoreController 中引入对玩家创建能力的验证,确保只有具备相应权限的管理员能够创建玩家。 - 更新请求验证逻辑,新增 credit_limit、rebate_rate 和 extra_rebate_rate 字段,以支持更细粒度的玩家管理。 - 在 AgentNodeProfileController 中添加对父代理能力授予的验证,确保子代理的权限在父代理范围内。 - 引入 AgentProfileFieldRules 以简化代理资料更新请求的规则定义,提升代码复用性。
492 lines
14 KiB
PHP
492 lines
14 KiB
PHP
<?php
|
||
|
||
namespace App\Models;
|
||
|
||
use Laravel\Sanctum\HasApiTokens;
|
||
use Illuminate\Support\Facades\DB;
|
||
use App\Support\AdminPermissionBridge;
|
||
use App\Support\AgentProfileCapabilityFilter;
|
||
use App\Models\AdminRole;
|
||
use App\Models\AgentNode;
|
||
use App\Models\AgentProfile;
|
||
use Illuminate\Notifications\Notifiable;
|
||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||
use Illuminate\Validation\ValidationException;
|
||
|
||
final class AdminUser extends Authenticatable
|
||
{
|
||
use HasApiTokens;
|
||
use Notifiable;
|
||
|
||
public const ROLE_SUPER_ADMIN = 'super_admin';
|
||
|
||
protected $table = 'admin_users';
|
||
|
||
protected $fillable = [
|
||
'username',
|
||
'name',
|
||
'email',
|
||
'password',
|
||
'status',
|
||
];
|
||
|
||
protected $hidden = [
|
||
'password',
|
||
'remember_token',
|
||
];
|
||
|
||
protected function casts(): array
|
||
{
|
||
return [
|
||
'email_verified_at' => 'datetime',
|
||
'last_login_at' => 'datetime',
|
||
'password' => 'hashed',
|
||
];
|
||
}
|
||
|
||
public static function defaultAdminSiteId(): int
|
||
{
|
||
static $cached = null;
|
||
if ($cached !== null) {
|
||
return $cached;
|
||
}
|
||
$id = DB::table('admin_sites')->where('is_default', true)->value('id');
|
||
if ($id === null) {
|
||
$id = DB::table('admin_sites')->orderBy('id')->value('id');
|
||
}
|
||
if ($id === null) {
|
||
throw new \RuntimeException('No admin_sites row found.');
|
||
}
|
||
$cached = (int) $id;
|
||
|
||
return $cached;
|
||
}
|
||
|
||
/**
|
||
* 用户在各站点上的角色(多站点 RBAC)。
|
||
*
|
||
* @return BelongsToMany<AdminRole, AdminUser>
|
||
*/
|
||
public function roles(): BelongsToMany
|
||
{
|
||
return $this->belongsToMany(
|
||
AdminRole::class,
|
||
'admin_user_site_roles',
|
||
'admin_user_id',
|
||
'role_id',
|
||
)->withPivot(['site_id', 'granted_at']);
|
||
}
|
||
|
||
/**
|
||
* 将用户在默认站点上的角色设为指定 slug 集合(全量替换该站点 pivot)。
|
||
*
|
||
* @param list<string> $slugs
|
||
*/
|
||
/**
|
||
* @param list<int> $roleIds
|
||
*/
|
||
public function syncAgentRoleIds(int $agentNodeId, array $roleIds): void
|
||
{
|
||
$roleIds = array_values(array_unique(array_map(static fn ($id): int => (int) $id, $roleIds)));
|
||
|
||
DB::transaction(function () use ($agentNodeId, $roleIds): void {
|
||
DB::table('admin_user_agent_roles')
|
||
->where('admin_user_id', $this->id)
|
||
->where('agent_node_id', $agentNodeId)
|
||
->delete();
|
||
|
||
$now = now();
|
||
foreach ($roleIds as $roleId) {
|
||
DB::table('admin_user_agent_roles')->insert([
|
||
'admin_user_id' => $this->id,
|
||
'agent_node_id' => $agentNodeId,
|
||
'role_id' => $roleId,
|
||
'granted_at' => $now,
|
||
]);
|
||
}
|
||
|
||
$siteId = (int) (AgentNode::query()->where('id', $agentNodeId)->value('admin_site_id') ?? 0);
|
||
if ($siteId > 0) {
|
||
DB::table('admin_user_site_roles')
|
||
->where('admin_user_id', $this->id)
|
||
->where('site_id', $siteId)
|
||
->delete();
|
||
|
||
foreach ($roleIds as $roleId) {
|
||
DB::table('admin_user_site_roles')->insert([
|
||
'admin_user_id' => $this->id,
|
||
'site_id' => $siteId,
|
||
'role_id' => $roleId,
|
||
'granted_at' => $now,
|
||
]);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
/** 经营代理主账号:仅平台角色 slug=agent(见 {@see \App\Support\AgentPlatformRole})。 */
|
||
public function syncPrimaryPlatformAgentRole(int $agentNodeId): void
|
||
{
|
||
$this->syncAgentRoleIds($agentNodeId, [\App\Support\AgentPlatformRole::id()]);
|
||
}
|
||
|
||
/**
|
||
* @return list<string>
|
||
*/
|
||
private function roleMenuActionPermissionCodes(): array
|
||
{
|
||
$agentId = $this->primaryAgentNodeId();
|
||
if ($agentId !== null) {
|
||
$fromAgent = $this->agentRoleMenuActionPermissionCodes($agentId);
|
||
if ($fromAgent !== []) {
|
||
return $fromAgent;
|
||
}
|
||
}
|
||
|
||
return $this->siteRoleMenuActionPermissionCodes();
|
||
}
|
||
|
||
/**
|
||
* @return list<string>
|
||
*/
|
||
private function agentRoleMenuActionPermissionCodes(int $agentId): array
|
||
{
|
||
return DB::table('admin_user_agent_roles as uar')
|
||
->join('admin_role_menu_actions as rma', 'rma.role_id', '=', 'uar.role_id')
|
||
->join('admin_menu_actions as ma', 'ma.id', '=', 'rma.menu_action_id')
|
||
->where('uar.admin_user_id', $this->id)
|
||
->where('uar.agent_node_id', $agentId)
|
||
->where('ma.status', 1)
|
||
->pluck('ma.permission_code')
|
||
->all();
|
||
}
|
||
|
||
/**
|
||
* @return list<string>
|
||
*/
|
||
private function siteRoleMenuActionPermissionCodes(): array
|
||
{
|
||
return DB::table('admin_user_site_roles as usr')
|
||
->join('admin_role_menu_actions as rma', 'rma.role_id', '=', 'usr.role_id')
|
||
->join('admin_menu_actions as ma', 'ma.id', '=', 'rma.menu_action_id')
|
||
->where('usr.admin_user_id', $this->id)
|
||
->where('ma.status', 1)
|
||
->pluck('ma.permission_code')
|
||
->all();
|
||
}
|
||
|
||
public function effectiveRoleSource(): string
|
||
{
|
||
$agentId = $this->primaryAgentNodeId();
|
||
if ($agentId !== null) {
|
||
return $this->agentRoleMenuActionPermissionCodes($agentId) !== [] ? 'agent' : 'agent_empty';
|
||
}
|
||
|
||
return $this->siteRoleMenuActionPermissionCodes() !== [] ? 'site' : 'none';
|
||
}
|
||
|
||
public function syncRoleSlugsForDefaultSite(array $slugs): void
|
||
{
|
||
$siteId = self::defaultAdminSiteId();
|
||
$slugs = array_values(array_unique($slugs));
|
||
$roleIds = DB::table('admin_roles')
|
||
->whereIn('slug', $slugs)
|
||
->pluck('id')
|
||
->all();
|
||
|
||
DB::transaction(function () use ($siteId, $roleIds): void {
|
||
DB::table('admin_user_site_roles')
|
||
->where('admin_user_id', $this->id)
|
||
->where('site_id', $siteId)
|
||
->delete();
|
||
|
||
$now = now();
|
||
foreach ($roleIds as $rid) {
|
||
DB::table('admin_user_site_roles')->insert([
|
||
'admin_user_id' => $this->id,
|
||
'site_id' => $siteId,
|
||
'role_id' => (int) $rid,
|
||
'granted_at' => $now,
|
||
]);
|
||
}
|
||
|
||
$agentId = $this->primaryAgentNodeId();
|
||
if ($agentId !== null) {
|
||
$this->syncAgentRoleIds($agentId, array_map('intval', $roleIds));
|
||
}
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 平台账号角色同步:仅允许系统角色,不同步代理角色(默认站点,兼容旧调用)。
|
||
*
|
||
* @param list<string> $slugs
|
||
*/
|
||
public function syncSystemRoleSlugs(array $slugs): void
|
||
{
|
||
$this->syncSystemRoleSlugsForSite(self::defaultAdminSiteId(), $slugs);
|
||
}
|
||
|
||
/**
|
||
* 平台账号在指定站点上的系统角色(全量替换该站点 pivot)。
|
||
*
|
||
* @param list<string> $slugs
|
||
*/
|
||
public function syncSystemRoleSlugsForSite(int $siteId, array $slugs): void
|
||
{
|
||
$slugs = array_values(array_unique($slugs));
|
||
$roleIds = DB::table('admin_roles')
|
||
->where('scope_type', AdminRole::SCOPE_SYSTEM)
|
||
->whereIn('slug', $slugs)
|
||
->pluck('id')
|
||
->map(static fn ($id): int => (int) $id)
|
||
->all();
|
||
|
||
if (count($roleIds) !== count($slugs)) {
|
||
throw ValidationException::withMessages([
|
||
'role_slugs' => [trans('admin.system_roles_only')],
|
||
]);
|
||
}
|
||
|
||
DB::transaction(function () use ($siteId, $roleIds): void {
|
||
DB::table('admin_user_site_roles')
|
||
->where('admin_user_id', $this->id)
|
||
->where('site_id', $siteId)
|
||
->delete();
|
||
|
||
$now = now();
|
||
foreach ($roleIds as $rid) {
|
||
DB::table('admin_user_site_roles')->insert([
|
||
'admin_user_id' => $this->id,
|
||
'site_id' => $siteId,
|
||
'role_id' => $rid,
|
||
'granted_at' => $now,
|
||
]);
|
||
}
|
||
});
|
||
}
|
||
|
||
public function isSuperAdmin(): bool
|
||
{
|
||
if ($this->relationLoaded('roles')) {
|
||
return $this->roles->contains('slug', self::ROLE_SUPER_ADMIN);
|
||
}
|
||
|
||
return $this->roles()->where('admin_roles.slug', self::ROLE_SUPER_ADMIN)->exists();
|
||
}
|
||
|
||
public function primaryAgentNodeId(): ?int
|
||
{
|
||
$id = DB::table('admin_user_agents')
|
||
->where('admin_user_id', $this->id)
|
||
->value('agent_node_id');
|
||
|
||
return $id !== null ? (int) $id : null;
|
||
}
|
||
|
||
public function primaryAgentNode(): ?AgentNode
|
||
{
|
||
$id = $this->primaryAgentNodeId();
|
||
if ($id === null) {
|
||
return null;
|
||
}
|
||
|
||
return AgentNode::query()->find($id);
|
||
}
|
||
|
||
public function hasPrimaryAgentBinding(): bool
|
||
{
|
||
return $this->primaryAgentNodeId() !== null;
|
||
}
|
||
|
||
public function isPlatformAccount(): bool
|
||
{
|
||
if ($this->isSuperAdmin()) {
|
||
return true;
|
||
}
|
||
|
||
return ! $this->hasPrimaryAgentBinding();
|
||
}
|
||
|
||
public function isAgentAccount(): bool
|
||
{
|
||
return ! $this->isPlatformAccount();
|
||
}
|
||
|
||
/**
|
||
* 可访问的 admin_sites.id 列表;`null` 表示不限制(超管)。
|
||
*
|
||
* @return list<int>|null
|
||
*/
|
||
public function accessibleAdminSiteIds(): ?array
|
||
{
|
||
if ($this->isSuperAdmin()) {
|
||
return null;
|
||
}
|
||
|
||
$agent = $this->primaryAgentNode();
|
||
if ($agent !== null) {
|
||
return [(int) $agent->admin_site_id];
|
||
}
|
||
|
||
$ids = DB::table('admin_user_site_roles')
|
||
->where('admin_user_id', $this->id)
|
||
->distinct()
|
||
->pluck('site_id')
|
||
->map(static fn ($id): int => (int) $id)
|
||
->values()
|
||
->all();
|
||
|
||
return $ids;
|
||
}
|
||
|
||
/**
|
||
* 仅来自「直接授权」的 menu_action.permission_code(默认站点,含 site_id 为 null 的历史行)。
|
||
*
|
||
* @return list<string>
|
||
*/
|
||
public function directMenuActionPermissionCodes(): array
|
||
{
|
||
$siteId = self::defaultAdminSiteId();
|
||
$rows = DB::table('admin_user_menu_actions as uma')
|
||
->join('admin_menu_actions as ma', 'ma.id', '=', 'uma.menu_action_id')
|
||
->where('uma.admin_user_id', $this->id)
|
||
->where(function ($q) use ($siteId): void {
|
||
$q->where('uma.site_id', $siteId)->orWhereNull('uma.site_id');
|
||
})
|
||
->where('ma.status', 1)
|
||
->pluck('ma.permission_code')
|
||
->all();
|
||
|
||
$out = [];
|
||
foreach ($rows as $code) {
|
||
if (is_string($code) && $code !== '') {
|
||
$out[$code] = true;
|
||
}
|
||
}
|
||
|
||
return array_keys($out);
|
||
}
|
||
|
||
/**
|
||
* 直接授权对应的 `prd.*` 展示列表(与 {@see self::directMenuActionPermissionCodes()} 桥接)。
|
||
*
|
||
* @return list<string>
|
||
*/
|
||
public function directLegacyPermissionSlugs(): array
|
||
{
|
||
return AdminPermissionBridge::legacySlugsGrantedByMenuActionCodes($this->directMenuActionPermissionCodes());
|
||
}
|
||
|
||
/**
|
||
* 角色 + 直接授权合并后的 menu_action.permission_code。
|
||
*
|
||
* @return list<string>
|
||
*/
|
||
public function effectiveMenuActionPermissionCodes(): array
|
||
{
|
||
if ($this->isSuperAdmin()) {
|
||
$codes = DB::table('admin_menu_actions')->where('status', 1)->pluck('permission_code')->all();
|
||
$out = [];
|
||
foreach ($codes as $c) {
|
||
if (is_string($c) && $c !== '') {
|
||
$out[$c] = true;
|
||
}
|
||
}
|
||
|
||
return array_keys($out);
|
||
}
|
||
|
||
$fromRoles = $this->roleMenuActionPermissionCodes();
|
||
|
||
$merged = [];
|
||
foreach (array_merge($fromRoles, $this->directMenuActionPermissionCodes()) as $c) {
|
||
if (is_string($c) && $c !== '') {
|
||
$merged[$c] = true;
|
||
}
|
||
}
|
||
|
||
$codes = array_keys($merged);
|
||
|
||
return AgentProfileCapabilityFilter::applyToMenuActionCodes($codes, $this->primaryAgentProfile());
|
||
}
|
||
|
||
private function primaryAgentProfile(): ?AgentProfile
|
||
{
|
||
$agentId = $this->primaryAgentNodeId();
|
||
if ($agentId === null) {
|
||
return null;
|
||
}
|
||
|
||
return AgentProfile::query()->where('agent_node_id', $agentId)->first();
|
||
}
|
||
|
||
/** 是否具备指定权限:`prd.*` 走 legacy_map;否则按 permission_code 精确匹配。含 `super_admin` 全放行。 */
|
||
public function hasAdminPermission(string $slug): bool
|
||
{
|
||
if ($this->isSuperAdmin()) {
|
||
return true;
|
||
}
|
||
|
||
$effective = $this->effectiveMenuActionPermissionCodes();
|
||
if ($slug !== '' && in_array($slug, $effective, true)) {
|
||
return true;
|
||
}
|
||
|
||
if (! str_starts_with($slug, 'prd.')) {
|
||
return false;
|
||
}
|
||
|
||
$needed = AdminPermissionBridge::menuActionCodesForLegacy($slug);
|
||
if ($needed === []) {
|
||
return false;
|
||
}
|
||
|
||
return count(array_intersect($needed, $effective)) > 0;
|
||
}
|
||
|
||
/**
|
||
* 仅按 permission_code 判定(不处理 prd.* 映射)。
|
||
*/
|
||
public function hasPermissionCode(string $permissionCode): bool
|
||
{
|
||
if ($permissionCode === '') {
|
||
return false;
|
||
}
|
||
|
||
if ($this->isSuperAdmin()) {
|
||
return true;
|
||
}
|
||
|
||
return in_array($permissionCode, $this->effectiveMenuActionPermissionCodes(), true);
|
||
}
|
||
|
||
/**
|
||
* @return list<string> 与 Next 侧栏兼容的 `prd.*` slug 列表
|
||
*/
|
||
public function adminPermissionSlugs(): array
|
||
{
|
||
if ($this->isSuperAdmin()) {
|
||
return AdminPermissionBridge::allLegacySlugs();
|
||
}
|
||
|
||
return AdminPermissionBridge::legacySlugsGrantedByMenuActionCodes($this->effectiveMenuActionPermissionCodes());
|
||
}
|
||
|
||
/**
|
||
* @return list<string>
|
||
*/
|
||
public function adminRoleSlugs(): array
|
||
{
|
||
$this->loadMissing('roles');
|
||
|
||
return $this->roles
|
||
->pluck('slug')
|
||
->filter(static fn ($slug): bool => is_string($slug) && $slug !== '')
|
||
->unique()
|
||
->values()
|
||
->all();
|
||
}
|
||
}
|