Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
173 changes: 173 additions & 0 deletions app/Model/DataScope.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
<?php

declare(strict_types=1);
/**
* This file is part of MineAdmin.
*
* @link https://www.mineadmin.com
* @document https://doc.mineadmin.com
* @contact root@imoi.cn
* @license https://github.com/mineadmin/MineAdmin/blob/master/LICENSE
*/

namespace App\Model;

use App\Http\CurrentUser;
use App\Model\DataPermission\Policy;
use App\Model\Enums\DataPermission\PolicyType;
use App\Model\Permission\Department;
use Hyperf\Database\Model\Builder;
use Hyperf\Database\Schema\Schema;

class DataScope
{
public static function query(Builder $builder, string $table)
{
// 不存在creatd_by字段,则不进行数据过滤
if (!Schema::hasColumn($table, 'created_by')) {
return $builder;
}

$currentUser = make(CurrentUser::class);
try {
$userId = $currentUser->id();
} catch (\Throwable $e) {
$userId = 0;
}

// 只有用户已登录时才应用条件
if ($userId) {
// 超级管理员拥有所有权限,不进行数据过滤
if ($currentUser->isSuperAdmin()) {
return $builder;
}

// 获取当前用户
$user = $currentUser->user();

// 首先获取用户自身的权限策略
$userPolicies = Policy::where('user_id', $userId)->get();

// 如果用户有自己的权限策略,则只使用用户的策略
// 如果用户没有自己的权限策略,才查看角色权限
$policies = null;
if (!$userPolicies->isEmpty()) {
$policies = $userPolicies->sortByDesc('is_default');
} else {
// 获取用户所有角色ID
$roleIds = $user->roles()->pluck('role.id')->toArray();
$rolePolicies = empty($roleIds) ? collect() : Policy::whereIn('role_id', $roleIds)->get();
$policies = $rolePolicies->sortByDesc('is_default');
}

// 如果没有任何策略,默认使用"仅自己"权限
if ($policies->isEmpty()) {
return $builder->where("{$table}.created_by", $userId);
}

// 处理策略:如果任一策略是ALL,则不限制访问范围
if ($policies->contains('policy_type', PolicyType::All)) {
return $builder;
}

// 收集所有可访问的部门ID和用户ID
$deptIds = collect();
$createdByIds = collect([$userId]); // 默认自己的数据总是可访问的

// 用户所有的部门ID(处理多部门情况)
$userDeptIds = $user->department()->pluck('id')->toArray();

// 处理每个策略
foreach ($policies as $policy) {
switch ($policy->policy_type) {
case PolicyType::Self:
// 仅自己 - 已包含在默认的createdByIds中
break;

case PolicyType::DeptSelf:
// 本部门数据 - 将用户所有部门添加到可访问部门列表
$deptIds = $deptIds->merge($userDeptIds);
break;

case PolicyType::DeptTree:
// 本部门及以下数据
if (!empty($userDeptIds)) {
$allDeptIds = [];
// 获取每个部门的子部门
foreach ($userDeptIds as $deptId) {
$allDeptIds[] = $deptId;
self::getChildDeptIds($deptId, $allDeptIds);
}
$deptIds = $deptIds->merge($allDeptIds);
}
break;

case PolicyType::CustomDept:
// 自定义部门
$customDeptIds = $policy->value['dept_ids'] ?? [];
if (!empty($customDeptIds)) {
$deptIds = $deptIds->merge($customDeptIds);
}
break;

case PolicyType::CustomFunc:
// 自定义函数
$funcName = $policy->value['func_name'] ?? '';
if (!empty($funcName) && function_exists($funcName)) {
// 这里需要特殊处理,因为自定义函数可能返回复杂的查询条件
// 临时保存在一个独立的变量里,后面再合并
$customQuery = (clone $builder);
$customQuery = call_user_func($funcName, $customQuery, $table, $userId, $user);

// TODO: 这里需要一个额外的机制来合并自定义查询的结果
// 由于无法直接合并查询条件,需要通过其他方式实现

// 这里是一个简单的处理方案,如果有自定义函数,我们暂时将其作为一个单独的Or条件
// 真实场景中可能需要更复杂的合并逻辑
return $builder->where(function ($query) use ($table, $createdByIds, $deptIds, $customQuery) {
// 应用其他范围条件
if (!$deptIds->isEmpty()) {
$query->orWhereIn("{$table}.dept_id", $deptIds->unique()->values()->toArray());
}
if (!$createdByIds->isEmpty()) {
$query->orWhereIn("{$table}.created_by", $createdByIds->unique()->values()->toArray());
}

// 添加自定义查询的条件(简化处理,实际可能需要更复杂的逻辑)
$query->orWhereRaw('EXISTS (' . $customQuery->toSql() . ')', $customQuery->getBindings());
});
}
Comment on lines +125 to +139
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

自定义查询合并方式过于简化,可能影响数据范围准确性
在这里通过 orWhereRaw('EXISTS (...)') 与自定义查询合并,实际场景或复杂度更高时,可能导致数据范围不一致或性能问题。建议采用更完善的处理方案并进行验证,以确保不同策略能合理组合。

break;
}
}

// 根据收集到的部门ID和用户ID构建最终的查询条件
return $builder->where(function ($query) use ($table, $createdByIds, $deptIds) {
// 如果有部门ID,添加部门条件
if (!$deptIds->isEmpty()) {
$query->orWhereIn("{$table}.dept_id", $deptIds->unique()->values()->toArray());
}

// 添加创建者条件
if (!$createdByIds->isEmpty()) {
$query->orWhereIn("{$table}.created_by", $createdByIds->unique()->values()->toArray());
}
});
}

return $builder;
}

/**
* 递归获取子部门ID
*/
private static function getChildDeptIds(int $deptId, array &$deptIds): void
{
$childDepts = Department::where('parent_id', $deptId)->get();

foreach ($childDepts as $dept) {
$deptIds[] = $dept->id;
self::getChildDeptIds($dept->id, $deptIds);
}
}
}
38 changes: 38 additions & 0 deletions app/Model/Scopes/DataScopeScope.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

declare(strict_types=1);
/**
* This file is part of MineAdmin.
*
* @link https://www.mineadmin.com
* @document https://doc.mineadmin.com
* @contact root@imoi.cn
* @license https://github.com/mineadmin/MineAdmin/blob/master/LICENSE
*/

namespace App\Model\Scopes;

use App\Http\CurrentUser;
use App\Model\DataPermission\Policy;
use App\Model\DataScope;
use App\Model\Enums\DataPermission\PolicyType;
use App\Model\Permission\Department;
use Hyperf\Database\Model\Builder;
use Hyperf\Database\Model\Model;
use Hyperf\Database\Model\Scope;
use Hyperf\Database\Schema\Schema;

class DataScopeScope implements Scope
{
/**
* 应用作用域
*/
public function apply(Builder $builder, Model $model): void
{
$table = $model->getTable();

if ($model->getDataScope()) {

Check failure on line 34 in app/Model/Scopes/DataScopeScope.php

View workflow job for this annotation

GitHub Actions / build Code coverage report (ubuntu-latest, 8.1, v5.1.3)

Call to an undefined method Hyperf\Database\Model\Model::getDataScope().
DataScope::query($builder, $table);
}
}
}
36 changes: 36 additions & 0 deletions app/Model/Traits/DataScopeTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);
/**
* This file is part of MineAdmin.
*
* @link https://www.mineadmin.com
* @document https://doc.mineadmin.com
* @contact root@imoi.cn
* @license https://github.com/mineadmin/MineAdmin/blob/master/LICENSE
*/

namespace App\Model\Traits;

trait DataScopeTrait

Check failure on line 15 in app/Model/Traits/DataScopeTrait.php

View workflow job for this annotation

GitHub Actions / build Code coverage report (ubuntu-latest, 8.1, v5.1.3)

Trait App\Model\Traits\DataScopeTrait is used zero times and is not analysed.
{
public $dataScope = false;

protected function boot(): void
{
parent::boot();

static::addGlobalScope(make(\App\Model\Scopes\DataScopeScope::class));
}

public function getDataScope()
{
return $this->dataScope;
}
public function setDataScope($dataScope)
{
$this->dataScope = $dataScope;
return $this;
}

}
Loading