- 在多个控制器中引入 agent_node_id,以支持基于代理节点的权限和数据过滤。 - 更新 AdminRole 和 AdminUser 模型,新增角色范围和代理节点相关功能,提升角色管理的灵活性。 - 在请求验证中添加 agent_node_id 字段,确保 API 接口支持代理节点的相关操作。 - 优化 LotterySettings 服务,支持批量写入设置,提升配置管理的效率。 - 更新仪表板和报告服务,增强数据统计功能,确保管理员能够获取更全面的统计信息。
355 lines
10 KiB
PHP
355 lines
10 KiB
PHP
<?php
|
||
|
||
namespace App\Models;
|
||
|
||
use Laravel\Sanctum\HasApiTokens;
|
||
use Illuminate\Support\Facades\DB;
|
||
use App\Support\AdminPermissionBridge;
|
||
use Illuminate\Notifications\Notifiable;
|
||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||
|
||
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 = 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();
|
||
|
||
if ($fromAgent !== []) {
|
||
return $fromAgent;
|
||
}
|
||
}
|
||
|
||
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 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,
|
||
]);
|
||
}
|
||
});
|
||
}
|
||
|
||
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);
|
||
}
|
||
|
||
/**
|
||
* 可访问的 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;
|
||
}
|
||
|
||
/**
|
||
* @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();
|
||
}
|
||
}
|