This commit is contained in:
Your Name
2026-04-27 15:30:28 +08:00
parent 23bd86e056
commit fe14f67965
21 changed files with 2335 additions and 449 deletions
@@ -0,0 +1,122 @@
<?php
declare(strict_types=1);
namespace app\common\lists\Traits;
use app\common\service\DataScope\DataScopeService;
use think\db\Query;
/**
* 列表按「数据范围」过滤。
*
* 使用前置条件:宿主类须通过 BaseAdminDataLists 获得 $this->adminId 与 $this->adminInfo。
*
* 三种用法:
* - applyDataScopeByOwner($q, 'creator_id') 直接按某列 IN
* - applyDataScopeByOwnerColumns($q, ['creator_id', 'doctor_id']) 多列 OR
* - applyDataScopeByExists($q, $sqlTemplate, 'owner_expr') 通过 exists 子查询(跨表)
*/
trait HasDataScopeFilter
{
/**
* 单列过滤。null 表示豁免(ALL)。
*/
protected function applyDataScopeByOwner($query, string $ownerField): bool
{
if (!$this->dataScopeShouldApply()) {
return false;
}
$ids = $this->getDataScopeVisibleAdminIds();
if ($ids === null) {
return false;
}
if ($ids === []) {
$query->whereRaw('0 = 1');
return true;
}
$query->whereIn($ownerField, $ids);
return true;
}
/**
* 多列 OR 过滤(任意属主列命中即可)。
*
* @param string[] $ownerFields
*/
protected function applyDataScopeByOwnerColumns($query, array $ownerFields): bool
{
if (!$this->dataScopeShouldApply()) {
return false;
}
$ids = $this->getDataScopeVisibleAdminIds();
if ($ids === null) {
return false;
}
if ($ids === []) {
$query->whereRaw('0 = 1');
return true;
}
$query->where(function ($q) use ($ownerFields, $ids) {
$first = true;
foreach ($ownerFields as $f) {
if ($first) {
$q->whereIn($f, $ids);
$first = false;
} else {
$q->whereOr(function ($qq) use ($f, $ids) {
$qq->whereIn($f, $ids);
});
}
}
});
return true;
}
/**
* exists 子查询方式。
* $subSqlTemplate 内部可使用占位符 `__OWNER_IDS__`,将被替换成逗号分隔的整数列表。
*/
protected function applyDataScopeByExistsSql($query, string $subSqlTemplate): bool
{
if (!$this->dataScopeShouldApply()) {
return false;
}
$ids = $this->getDataScopeVisibleAdminIds();
if ($ids === null) {
return false;
}
if ($ids === []) {
$query->whereRaw('0 = 1');
return true;
}
$inList = implode(',', $ids);
$sql = str_replace('__OWNER_IDS__', $inList, $subSqlTemplate);
$query->whereExists($sql);
return true;
}
/**
* 可见 admin idnull = 全部。
*
* @return array<int>|null
*/
protected function getDataScopeVisibleAdminIds(): ?array
{
$adminId = property_exists($this, 'adminId') ? (int) $this->adminId : 0;
$adminInfo = property_exists($this, 'adminInfo') && is_array($this->adminInfo) ? $this->adminInfo : [];
return DataScopeService::getVisibleAdminIds($adminId, $adminInfo);
}
protected function dataScopeShouldApply(): bool
{
return DataScopeService::isEnabled();
}
}
@@ -0,0 +1,148 @@
<?php
declare(strict_types=1);
namespace app\common\service\DataScope;
use app\adminapi\logic\dept\DeptLogic;
use app\common\model\auth\AdminDept;
use app\common\model\auth\AdminRole;
use app\common\model\auth\SystemRole;
use think\facade\Config;
/**
* 数据范围(数据隔离)工具服务。
*
* 设计约定:
* - ALL (1) = 全部数据,不附加过滤
* - DEPT_AND_CHILD (2) = 本部门及所有子部门(取 admin 全部部门的并集)
* - DEPT (3) = 仅本部门(取 admin 全部部门的并集,不含子孙)
* - SELF (4) = 仅本人
*
* 多角色时取「范围最大」= data_scope 最小值。
* root 管理员固定为 ALL。未挂任何部门时,范围退化为 SELF(可由 config 关闭)。
*
* 关键返回:`getVisibleAdminIds` 返回 int[](可见 admin_id 集合)或 nullALL = 不过滤)。
*/
class DataScopeService
{
public const SCOPE_ALL = 1;
public const SCOPE_DEPT_AND_CHILD = 2;
public const SCOPE_DEPT = 3;
public const SCOPE_SELF = 4;
/**
* 计算当前 admin 的有效数据范围。
*/
public static function getEffectiveScope(array $adminInfo): int
{
if (!self::isEnabled()) {
return self::SCOPE_ALL;
}
if ((int) ($adminInfo['root'] ?? 0) === 1) {
return self::SCOPE_ALL;
}
$roleIds = array_values(array_filter(array_map(
'intval',
is_array($adminInfo['role_id'] ?? null) ? $adminInfo['role_id'] : []
)));
$exempt = array_map('intval', Config::get('project.data_scope.exempt_roles', []) ?: []);
if ($roleIds !== [] && array_intersect($roleIds, $exempt) !== []) {
return self::SCOPE_ALL;
}
if ($roleIds === []) {
return self::SCOPE_SELF;
}
$scopes = SystemRole::whereIn('id', $roleIds)
->whereNull('delete_time')
->column('data_scope');
$scopes = array_values(array_filter(array_map('intval', $scopes), static function (int $v): bool {
return $v >= self::SCOPE_ALL && $v <= self::SCOPE_SELF;
}));
if ($scopes === []) {
return self::SCOPE_ALL;
}
return (int) min($scopes);
}
/**
* 可见 admin id 集合;null = 不过滤(ALL
*
* @return array<int>|null
*/
public static function getVisibleAdminIds(int $adminId, array $adminInfo): ?array
{
$scope = self::getEffectiveScope($adminInfo);
if ($scope === self::SCOPE_ALL) {
return null;
}
if ($scope === self::SCOPE_SELF) {
return $adminId > 0 ? [$adminId] : [];
}
$myDeptIds = AdminDept::where('admin_id', $adminId)->column('dept_id');
$myDeptIds = array_values(array_filter(array_map('intval', $myDeptIds), static function (int $v): bool {
return $v > 0;
}));
if ($myDeptIds === []) {
$fallback = (bool) Config::get('project.data_scope.no_dept_fallback_self', true);
return $fallback ? [$adminId] : [];
}
$targetDeptIds = [];
if ($scope === self::SCOPE_DEPT) {
$targetDeptIds = $myDeptIds;
} else {
foreach ($myDeptIds as $did) {
foreach (DeptLogic::getSelfAndDescendantIds($did) as $id) {
$id = (int) $id;
if ($id > 0) {
$targetDeptIds[$id] = true;
}
}
}
$targetDeptIds = array_keys($targetDeptIds);
}
if ($targetDeptIds === []) {
return $adminId > 0 ? [$adminId] : [];
}
$ids = AdminDept::whereIn('dept_id', $targetDeptIds)->column('admin_id');
$ids = array_values(array_unique(array_filter(array_map('intval', $ids), static function (int $v): bool {
return $v > 0;
})));
if ($adminId > 0 && !in_array($adminId, $ids, true)) {
$ids[] = $adminId;
}
return $ids;
}
public static function isEnabled(): bool
{
return (bool) Config::get('project.data_scope.enabled', true);
}
public static function isAll(array $adminInfo): bool
{
return self::getEffectiveScope($adminInfo) === self::SCOPE_ALL;
}
/**
* 文字描述(日志 / 接口返回可选使用)
*/
public static function scopeLabel(int $scope): string
{
return [
self::SCOPE_ALL => '全部',
self::SCOPE_DEPT_AND_CHILD => '本部门及下级',
self::SCOPE_DEPT => '仅本部门',
self::SCOPE_SELF => '仅本人',
][$scope] ?? '全部';
}
}