Files
lotteryLaravel/app/Models/AdminUser.php
kang e3ffffad9c feat: 增强代理和玩家管理功能
- 在 SyncAdminAuthorizationCommand 中新增对代理线路和结算菜单操作的同步功能,确保缺失的菜单操作行能够被创建。
- 更新多个控制器中的权限检查逻辑,使用 hasPermissionCode 替代原有的权限验证方式,提升权限管理的灵活性。
- 在 AdminPlayerStoreController 中引入对玩家创建能力的验证,确保只有具备相应权限的管理员能够创建玩家。
- 更新请求验证逻辑,新增 credit_limit、rebate_rate 和 extra_rebate_rate 字段,以支持更细粒度的玩家管理。
- 在 AdminUser 和 AgentNode 模型中增强角色与用户的权限管理功能,支持更细粒度的权限控制。
2026-06-04 09:17:47 +08:00

462 lines
13 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace App\Models;
use Laravel\Sanctum\HasApiTokens;
use Illuminate\Support\Facades\DB;
use App\Support\AdminPermissionBridge;
use App\Models\AgentNode;
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,
]);
}
}
});
}
/**
* @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
{
$siteId = self::defaultAdminSiteId();
$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;
}
}
return array_keys($merged);
}
/** 是否具备指定权限:`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();
}
}