edit inquiry
This commit is contained in:
@@ -0,0 +1,425 @@
|
||||
<?php
|
||||
namespace app\common\traits;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use support\Db;
|
||||
use support\exception\BusinessException;
|
||||
|
||||
class DataPermission
|
||||
{
|
||||
|
||||
/*
|
||||
ALTER TABLE `user` ADD COLUMN `report_to` bigint(20) unsigned DEFAULT '0' COMMENT '上级用户ID';
|
||||
|
||||
ALTER TABLE `roles`
|
||||
ADD COLUMN `parent_id` bigint(20) unsigned DEFAULT '0' COMMENT '父角色ID,0表示顶级角色',
|
||||
ADD INDEX `idx_parent_id` (`parent_id`) USING BTREE;
|
||||
*/
|
||||
|
||||
/**
|
||||
* 应用数据权限过滤到查询
|
||||
*
|
||||
* @param Builder $query 查询构造器
|
||||
* @param string $type 权限类型:superadmin, personal, same_role, role_tree, relation
|
||||
* @param int $currentUserId 当前用户ID
|
||||
* @param int $currentRoleId 当前角色ID
|
||||
* @param string $userField 用户ID字段名
|
||||
* @param string $roleField 角色ID字段名
|
||||
*/
|
||||
public static function applyQueryPermission(
|
||||
Builder $query,
|
||||
string $type,
|
||||
int $currentUserId,
|
||||
int $currentRoleId,
|
||||
string $userField = 'user_id',
|
||||
string $roleField = 'role_id'
|
||||
): void {
|
||||
// 超管不需要过滤
|
||||
if (self::isSuperAdmin($currentUserId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch ($type) {
|
||||
case 'personal':
|
||||
// 个人权限:只能访问自己的数据
|
||||
$query->where($userField, $currentUserId);
|
||||
break;
|
||||
|
||||
case 'same_role':
|
||||
// 同角色权限:访问同角色的数据
|
||||
$userIds = self::getSameRoleUserIds($currentRoleId);
|
||||
$query->whereIn($userField, $userIds);
|
||||
break;
|
||||
|
||||
case 'role_tree':
|
||||
// 角色树权限:访问自己角色及子角色的数据
|
||||
$userIds = self::getRoleTreeUserIds($currentRoleId);
|
||||
$query->whereIn($userField, $userIds);
|
||||
break;
|
||||
|
||||
case 'relation':
|
||||
// 从属关系权限:访问自己及下属的数据
|
||||
$userIds = self::getRelationUserIds($currentUserId);
|
||||
$query->whereIn($userField, $userIds);
|
||||
break;
|
||||
|
||||
default:
|
||||
// 默认个人权限
|
||||
$query->where($userField, $currentUserId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用数据权限到创建/更新操作
|
||||
*
|
||||
* @param mixed $target 创建时为数组,更新时为Model对象
|
||||
* @param string $type 权限类型
|
||||
* @param int $currentUserId 当前用户ID
|
||||
* @param int $currentRoleId 当前角色ID
|
||||
* @param array $newData 更新时的数据
|
||||
* @param string $userField 用户ID字段名
|
||||
* @param string $roleField 角色ID字段名
|
||||
*/
|
||||
public static function applyMutationPermission(
|
||||
&$target,
|
||||
string $type,
|
||||
int $currentUserId,
|
||||
int $currentRoleId,
|
||||
array $newData = [],
|
||||
string $userField = 'admin_id',
|
||||
string $roleField = 'role_id'
|
||||
): void {
|
||||
// 超管不需要检查
|
||||
if (self::isSuperAdmin($currentUserId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch ($type) {
|
||||
case 'personal':
|
||||
self::handlePersonalPermission($target, $newData, $userField, $currentUserId);
|
||||
break;
|
||||
|
||||
case 'same_role':
|
||||
self::handleSameRolePermission($target, $newData, $userField, $roleField, $currentRoleId);
|
||||
break;
|
||||
|
||||
case 'role_tree':
|
||||
self::handleRoleTreePermission($target, $newData, $userField, $roleField, $currentRoleId);
|
||||
break;
|
||||
|
||||
case 'relation':
|
||||
self::handleRelationPermission($target, $newData, $userField, $currentUserId);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new BusinessException("未定义的数据权限类型: $type", 403);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取同角色的用户ID列表
|
||||
*
|
||||
* @param int $roleId 角色ID
|
||||
* @return array 用户ID列表
|
||||
*/
|
||||
public static function getSameRoleUserIds(int $roleId): array
|
||||
{
|
||||
return Db::table('user_roles')
|
||||
->where('role_id', $roleId)
|
||||
->pluck('user_id')
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取角色树下的所有用户ID列表
|
||||
*
|
||||
* @param int $roleId 当前角色ID
|
||||
* @return array 用户ID列表
|
||||
*/
|
||||
public static function getRoleTreeUserIds(int $roleId): array
|
||||
{
|
||||
// 获取角色树中的所有角色ID
|
||||
$roleIds = self::getRoleTreeIds($roleId);
|
||||
|
||||
// 获取这些角色下的所有用户ID
|
||||
return Db::table('user_roles')
|
||||
->whereIn('role_id', $roleIds)
|
||||
->pluck('user_id')
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取从属关系下的用户ID列表(包括自己和下属)
|
||||
*
|
||||
* @param int $userId 当前用户ID
|
||||
* @return array 用户ID列表
|
||||
*/
|
||||
public static function getRelationUserIds(int $userId): array
|
||||
{
|
||||
// 获取所有下属用户ID(递归)
|
||||
$subordinateIds = self::getSubordinateIds($userId);
|
||||
|
||||
// 返回自己和所有下属的ID
|
||||
return array_merge([$userId], $subordinateIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取直接下属的用户ID列表
|
||||
*
|
||||
* @param int $userId 用户ID
|
||||
* @return array 直接下属用户ID列表
|
||||
*/
|
||||
public static function getDirectSubordinateIds(int $userId): array
|
||||
{
|
||||
return Db::table('user')
|
||||
->where('report_to', $userId)
|
||||
->pluck('id')
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归获取所有下属用户ID列表
|
||||
*
|
||||
* @param int $userId 用户ID
|
||||
* @return array 所有下属用户ID列表
|
||||
*/
|
||||
private static function getSubordinateIds(int $userId): array
|
||||
{
|
||||
$subordinates = self::getDirectSubordinateIds($userId);
|
||||
$allSubordinates = $subordinates;
|
||||
|
||||
foreach ($subordinates as $subordinateId) {
|
||||
$allSubordinates = array_merge($allSubordinates, self::getSubordinateIds($subordinateId));
|
||||
}
|
||||
|
||||
return $allSubordinates;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理个人权限
|
||||
*/
|
||||
private static function handlePersonalPermission(
|
||||
&$target,
|
||||
array $newData,
|
||||
string $userField,
|
||||
int $currentUserId
|
||||
): void {
|
||||
if (is_array($target)) {
|
||||
// 创建时强制设置用户ID
|
||||
$target[$userField] = $currentUserId;
|
||||
} else {
|
||||
// 更新时校验原始记录
|
||||
if ($target->{$userField} !== $currentUserId) {
|
||||
throw new BusinessException("无权操作该记录", 403);
|
||||
}
|
||||
|
||||
// 禁止变更归属字段
|
||||
if (isset($newData[$userField]) && $newData[$userField] !== $currentUserId) {
|
||||
throw new BusinessException("无权变更归属字段", 403);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理同角色权限
|
||||
*/
|
||||
private static function handleSameRolePermission(
|
||||
&$target,
|
||||
array $newData,
|
||||
string $userField,
|
||||
string $roleField,
|
||||
int $currentRoleId
|
||||
): void {
|
||||
// 获取同角色用户ID列表
|
||||
$sameRoleUserIds = self::getSameRoleUserIds($currentRoleId);
|
||||
|
||||
if (is_array($target)) {
|
||||
// 创建时设置归属用户并校验
|
||||
if (isset($target[$userField])) {
|
||||
if (!in_array($target[$userField], $sameRoleUserIds)) {
|
||||
throw new BusinessException("无权为指定用户创建数据", 403);
|
||||
}
|
||||
} else {
|
||||
$target[$userField] = $currentRoleId;
|
||||
}
|
||||
} else {
|
||||
// 更新时校验原始记录
|
||||
if (!in_array($target->{$userField}, $sameRoleUserIds)) {
|
||||
throw new BusinessException("无权操作该记录", 403);
|
||||
}
|
||||
|
||||
// 禁止变更归属字段到无权限的用户
|
||||
if (isset($newData[$userField]) && !in_array($newData[$userField], $sameRoleUserIds)) {
|
||||
throw new BusinessException("无权变更归属字段", 403);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理角色树权限
|
||||
*/
|
||||
private static function handleRoleTreePermission(
|
||||
&$target,
|
||||
array $newData,
|
||||
string $userField,
|
||||
string $roleField,
|
||||
int $currentRoleId
|
||||
): void {
|
||||
// 获取角色树用户ID列表
|
||||
$roleTreeUserIds = self::getRoleTreeUserIds($currentRoleId);
|
||||
|
||||
if (is_array($target)) {
|
||||
// 创建时校验用户是否在允许范围内
|
||||
if (isset($target[$userField])) {
|
||||
if (!in_array($target[$userField], $roleTreeUserIds)) {
|
||||
throw new BusinessException("无权为指定用户创建数据", 403);
|
||||
}
|
||||
} else {
|
||||
$target[$userField] = $currentRoleId;
|
||||
}
|
||||
} else {
|
||||
// 更新时校验原始记录用户
|
||||
if (!in_array($target->{$userField}, $roleTreeUserIds)) {
|
||||
throw new BusinessException("无权操作该记录", 403);
|
||||
}
|
||||
|
||||
// 校验新用户是否在允许范围内
|
||||
if (isset($newData[$userField])) {
|
||||
if (!in_array($newData[$userField], $roleTreeUserIds)) {
|
||||
throw new BusinessException("无权变更用户字段", 403);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理从属关系权限
|
||||
*/
|
||||
private static function handleRelationPermission(
|
||||
&$target,
|
||||
array $newData,
|
||||
string $userField,
|
||||
int $currentUserId
|
||||
): void {
|
||||
// 获取从属关系用户ID列表
|
||||
$relationUserIds = self::getRelationUserIds($currentUserId);
|
||||
|
||||
if (is_array($target)) {
|
||||
// 创建时校验用户是否在允许范围内
|
||||
if (isset($target[$userField])) {
|
||||
if (!in_array($target[$userField], $relationUserIds)) {
|
||||
throw new BusinessException("无权为指定用户创建数据", 403);
|
||||
}
|
||||
} else {
|
||||
$target[$userField] = $currentUserId;
|
||||
}
|
||||
} else {
|
||||
// 更新时校验原始记录用户
|
||||
if (!in_array($target->{$userField}, $relationUserIds)) {
|
||||
throw new BusinessException("无权操作该记录", 403);
|
||||
}
|
||||
|
||||
// 校验新用户是否在允许范围内
|
||||
if (isset($newData[$userField])) {
|
||||
if (!in_array($newData[$userField], $relationUserIds)) {
|
||||
throw new BusinessException("无权变更用户字段", 403);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取角色树ID(当前角色及其所有子角色)
|
||||
* 从数据库中动态获取角色树关系
|
||||
*/
|
||||
private static function getRoleTreeIds(int $roleId): array
|
||||
{
|
||||
static $roleTreeCache = [];
|
||||
|
||||
if (isset($roleTreeCache[$roleId])) {
|
||||
return $roleTreeCache[$roleId];
|
||||
}
|
||||
|
||||
// 从数据库中递归获取角色树
|
||||
$ids = [$roleId];
|
||||
$queue = [$roleId];
|
||||
|
||||
while (!empty($queue)) {
|
||||
$current = array_shift($queue);
|
||||
// 查找当前角色的所有子角色
|
||||
$childRoles = Db::table('roles')
|
||||
->where('parent_id', $current)
|
||||
->where('status', 1) // 只获取启用的角色
|
||||
->pluck('id')
|
||||
->toArray();
|
||||
|
||||
foreach ($childRoles as $childId) {
|
||||
if (!in_array($childId, $ids)) {
|
||||
$ids[] = $childId;
|
||||
$queue[] = $childId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$roleTreeCache[$roleId] = $ids;
|
||||
return $ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否为超管
|
||||
*
|
||||
* @param int $userId 用户ID
|
||||
* @return bool 是否为超管
|
||||
*/
|
||||
public static function isSuperAdmin(int $userId): bool
|
||||
{
|
||||
// 检查用户是否关联了超级管理员角色
|
||||
// 假设角色ID为1的是超级管理员角色
|
||||
$superAdminRoleId = 1;
|
||||
|
||||
return Db::table('user_roles')
|
||||
->where('user_id', $userId)
|
||||
->where('role_id', $superAdminRoleId)
|
||||
->exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户的角色ID
|
||||
*
|
||||
* @param int $userId 用户ID
|
||||
* @return int 角色ID
|
||||
*/
|
||||
public static function getUserIdRoleId(int $userId): int
|
||||
{
|
||||
// 从 user_roles 表中获取当前用户的角色ID
|
||||
$roleId = Db::table('user_roles')
|
||||
->where('user_id', $userId)
|
||||
->value('role_id');
|
||||
|
||||
return $roleId ?: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户的所有角色ID(包括继承的角色)
|
||||
*
|
||||
* @param int $userId 用户ID
|
||||
* @return array 角色ID列表
|
||||
*/
|
||||
public static function getUserAllRoleIds(int $userId): array
|
||||
{
|
||||
// 获取用户直接关联的角色
|
||||
$directRoleIds = Db::table('user_roles')
|
||||
->where('user_id', $userId)
|
||||
->pluck('role_id')
|
||||
->toArray();
|
||||
|
||||
// 获取所有继承的角色
|
||||
$allRoleIds = [];
|
||||
foreach ($directRoleIds as $roleId) {
|
||||
$allRoleIds = array_merge($allRoleIds, self::getRoleTreeIds($roleId));
|
||||
}
|
||||
|
||||
// 去重并返回
|
||||
return array_unique($allRoleIds);
|
||||
}
|
||||
}
|
||||
@@ -74,7 +74,7 @@ trait CrudServiceTrait
|
||||
$data = $this->preProcess('Update', $data, $request);
|
||||
$config = $this->buildQueryConfig($request, 'update');
|
||||
$updated = $this->logicUpdateBy($config, $data);
|
||||
return $this->postProcess('Update', $updated);
|
||||
return $this->postProcess('Update', $data);
|
||||
}
|
||||
|
||||
/** 删除 */
|
||||
|
||||
+412
-39
@@ -4,8 +4,8 @@ namespace app\common\utils;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use plugin\admin\app\common\Auth;
|
||||
use support\exception\BusinessException;
|
||||
use support\Db;
|
||||
|
||||
/**
|
||||
* 构建查询工具类
|
||||
@@ -49,6 +49,13 @@ class CrudUtil
|
||||
* 'roles' => ['id','name'], // 关联表字段选择
|
||||
* 'posts' => ['id','title','user_id'],
|
||||
* ],
|
||||
* 'dataLimit' => [
|
||||
* 'type' => 'personal|same_role|role_tree|relation', // personal:个人数据,same_role:同角色,role_tree:角色树,relation:从属关联数据
|
||||
* 'userId' => 1,
|
||||
* 'roleId' => 2,
|
||||
* 'userField' => 'user_id',
|
||||
* 'roleField' => 'role_id'
|
||||
* ]
|
||||
* ];
|
||||
*/
|
||||
public static function buildQuery(array $config): Builder
|
||||
@@ -354,78 +361,444 @@ class CrudUtil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ================== Data Limit ==================
|
||||
|
||||
/**
|
||||
* 应用数据权限过滤到查询(buildQuery 场景)
|
||||
*/
|
||||
protected static function applyDataLimit(Builder $q, array $dataLimit): void
|
||||
{
|
||||
if (Auth::isSuperAdmin()) {
|
||||
return;
|
||||
}
|
||||
$type = $dataLimit['type'] ?? null;
|
||||
$field = $dataLimit['field'] ?? 'admin_id';
|
||||
$type = $dataLimit['type'] ?? 'personal';
|
||||
$userId = $dataLimit['userId'] ?? 0;
|
||||
$roleId = $dataLimit['roleId'] ?? 0;
|
||||
$userField = $dataLimit['userField'] ?? 'user_id';
|
||||
$roleField = $dataLimit['roleField'] ?? 'role_id';
|
||||
|
||||
if ($type === 'personal') {
|
||||
$q->where($field, admin_id());
|
||||
} elseif ($type === 'auth') {
|
||||
$q->whereIn($field, Auth::getScopeAdminIds(true));
|
||||
}
|
||||
self::applyQueryPermission($q, $type, $userId, $roleId, $userField, $roleField);
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用数据权限到“创建/更新”操作
|
||||
* 应用数据权限到"创建/更新"操作
|
||||
*
|
||||
* - $target 可能是 array|Model 对象
|
||||
* - $newData 仅在 updateByPk 场景时传入,用以校验变更归属
|
||||
*/
|
||||
protected static function applyDataLimitOnMutate(&$target, array $dataLimit, array $newData = []): void
|
||||
{
|
||||
if (Auth::isSuperAdmin()) {
|
||||
$type = $dataLimit['type'] ?? 'personal';
|
||||
$userId = $dataLimit['userId'] ?? 0;
|
||||
$roleId = $dataLimit['roleId'] ?? 0;
|
||||
$userField = $dataLimit['userField'] ?? 'user_id';
|
||||
$roleField = $dataLimit['roleField'] ?? 'role_id';
|
||||
|
||||
self::applyMutationPermission($target, $type, $userId, $roleId, $newData, $userField, $roleField);
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用数据权限过滤到查询
|
||||
*
|
||||
* @param Builder $query 查询构造器
|
||||
* @param string $type 权限类型:superadmin, personal, same_role, role_tree, relation
|
||||
* @param int $currentUserId 当前用户ID
|
||||
* @param int $currentRoleId 当前角色ID
|
||||
* @param string $userField 用户ID字段名
|
||||
* @param string $roleField 角色ID字段名
|
||||
*/
|
||||
public static function applyQueryPermission(
|
||||
Builder $query,
|
||||
string $type,
|
||||
int $currentUserId = 0,
|
||||
int $currentRoleId = 0,
|
||||
string $userField = 'user_id',
|
||||
string $roleField = 'role_id'
|
||||
): void {
|
||||
// 超管不需要过滤
|
||||
if (self::isSuperAdmin($currentUserId)) {
|
||||
return;
|
||||
}
|
||||
$type = $dataLimit['type'] ?? null;
|
||||
$field = $dataLimit['field'] ?? 'admin_id';
|
||||
|
||||
// 读取当前归属:create 时 $target 是 array,update 时是 Model
|
||||
$current = is_array($target) ? null : $target->{$field};
|
||||
switch ($type) {
|
||||
case 'personal':
|
||||
// 个人权限:只能访问自己的数据
|
||||
$query->where($userField, $currentUserId);
|
||||
break;
|
||||
|
||||
// 1. personal → 只能自己
|
||||
if ($type === 'personal') {
|
||||
// create: 强制设置
|
||||
if (is_array($target)) {
|
||||
$target[$field] = admin_id();
|
||||
case 'same_role':
|
||||
// 同角色权限:访问同角色的数据
|
||||
$userIds = self::getSameRoleUserIds($currentRoleId);
|
||||
$query->whereIn($userField, $userIds);
|
||||
break;
|
||||
|
||||
case 'role_tree':
|
||||
// 角色树权限:访问自己角色及子角色的数据
|
||||
$userIds = self::getRoleTreeUserIds($currentRoleId);
|
||||
$query->whereIn($userField, $userIds);
|
||||
break;
|
||||
|
||||
case 'relation':
|
||||
// 从属关系权限:访问自己及下属的数据
|
||||
$userIds = self::getRelationUserIds($currentUserId);
|
||||
$query->whereIn($userField, $userIds);
|
||||
break;
|
||||
|
||||
default:
|
||||
// 默认个人权限
|
||||
$query->where($userField, $currentUserId);
|
||||
}
|
||||
// update: 校验当前归属
|
||||
elseif ($current !== admin_id()) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用数据权限到创建/更新操作
|
||||
*
|
||||
* @param mixed $target 创建时为数组,更新时为Model对象
|
||||
* @param string $type 权限类型
|
||||
* @param int $currentUserId 当前用户ID
|
||||
* @param int $currentRoleId 当前角色ID
|
||||
* @param array $newData 更新时的数据
|
||||
* @param string $userField 用户ID字段名
|
||||
* @param string $roleField 角色ID字段名
|
||||
*/
|
||||
public static function applyMutationPermission(
|
||||
&$target,
|
||||
string $type,
|
||||
int $currentUserId,
|
||||
int $currentRoleId,
|
||||
array $newData = [],
|
||||
string $userField = 'user_id',
|
||||
string $roleField = 'role_id'
|
||||
): void {
|
||||
// 超管不需要检查
|
||||
if (self::isSuperAdmin($currentUserId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch ($type) {
|
||||
case 'personal':
|
||||
self::handlePersonalPermission($target, $newData, $userField, $currentUserId);
|
||||
break;
|
||||
|
||||
case 'same_role':
|
||||
self::handleSameRolePermission($target, $newData, $userField, $roleField, $currentRoleId);
|
||||
break;
|
||||
|
||||
case 'role_tree':
|
||||
self::handleRoleTreePermission($target, $newData, $userField, $roleField, $currentRoleId);
|
||||
break;
|
||||
|
||||
case 'relation':
|
||||
self::handleRelationPermission($target, $newData, $userField, $currentUserId);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new BusinessException("未定义的数据权限类型: $type", 403);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取同角色的用户ID列表
|
||||
*
|
||||
* @param int $roleId 角色ID
|
||||
* @return array 用户ID列表
|
||||
*/
|
||||
public static function getSameRoleUserIds(int $roleId): array
|
||||
{
|
||||
return Db::table('user_roles')
|
||||
->where('role_id', $roleId)
|
||||
->pluck('user_id')
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取角色树下的所有用户ID列表
|
||||
*
|
||||
* @param int $roleId 当前角色ID
|
||||
* @return array 用户ID列表
|
||||
*/
|
||||
public static function getRoleTreeUserIds(int $roleId): array
|
||||
{
|
||||
// 获取角色树中的所有角色ID
|
||||
$roleIds = self::getRoleTreeIds($roleId);
|
||||
|
||||
// 获取这些角色下的所有用户ID
|
||||
return Db::table('user_roles')
|
||||
->whereIn('role_id', $roleIds)
|
||||
->pluck('user_id')
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取从属关系下的用户ID列表(包括自己和下属)
|
||||
*
|
||||
* @param int $userId 当前用户ID
|
||||
* @return array 用户ID列表
|
||||
*/
|
||||
public static function getRelationUserIds(int $userId): array
|
||||
{
|
||||
// 获取所有下属用户ID(递归)
|
||||
$subordinateIds = self::getSubordinateIds($userId);
|
||||
|
||||
// 返回自己和所有下属的ID
|
||||
return array_merge([$userId], $subordinateIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取直接下属的用户ID列表
|
||||
*
|
||||
* @param int $userId 用户ID
|
||||
* @return array 直接下属用户ID列表
|
||||
*/
|
||||
public static function getDirectSubordinateIds(int $userId): array
|
||||
{
|
||||
return Db::table('user')
|
||||
->where('report_to', $userId)
|
||||
->pluck('id')
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归获取所有下属用户ID列表
|
||||
*
|
||||
* @param int $userId 用户ID
|
||||
* @return array 所有下属用户ID列表
|
||||
*/
|
||||
private static function getSubordinateIds(int $userId): array
|
||||
{
|
||||
$subordinates = self::getDirectSubordinateIds($userId);
|
||||
$allSubordinates = $subordinates;
|
||||
|
||||
foreach ($subordinates as $subordinateId) {
|
||||
$allSubordinates = array_merge($allSubordinates, self::getSubordinateIds($subordinateId));
|
||||
}
|
||||
|
||||
return $allSubordinates;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理个人权限
|
||||
*/
|
||||
private static function handlePersonalPermission(
|
||||
&$target,
|
||||
array $newData,
|
||||
string $userField,
|
||||
int $currentUserId
|
||||
): void {
|
||||
if (is_array($target)) {
|
||||
// 创建时强制设置用户ID
|
||||
$target[$userField] = $currentUserId;
|
||||
} else {
|
||||
// 更新时校验原始记录
|
||||
if ($target->{$userField} !== $currentUserId) {
|
||||
throw new BusinessException("无权操作该记录", 403);
|
||||
}
|
||||
// 若要修改归属,也必须和自己一致
|
||||
if (isset($newData[$field]) && $newData[$field] !== admin_id()) {
|
||||
|
||||
// 禁止变更归属字段
|
||||
if (isset($newData[$userField]) && $newData[$userField] !== $currentUserId) {
|
||||
throw new BusinessException("无权变更归属字段", 403);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理同角色权限
|
||||
*/
|
||||
private static function handleSameRolePermission(
|
||||
&$target,
|
||||
array $newData,
|
||||
string $userField,
|
||||
string $roleField,
|
||||
int $currentRoleId
|
||||
): void {
|
||||
// 获取同角色用户ID列表
|
||||
$sameRoleUserIds = self::getSameRoleUserIds($currentRoleId);
|
||||
|
||||
// 2. auth → 自己及下属
|
||||
if ($type === 'auth') {
|
||||
$scope = Auth::getScopeAdminIds(true);
|
||||
// create: 若前端指定归属,则校验范围;否则默认自己
|
||||
if (is_array($target)) {
|
||||
if (isset($target[$field])) {
|
||||
if (!in_array($target[$field], $scope)) {
|
||||
throw new BusinessException("无权为指定用户操作", 403);
|
||||
// 创建时设置归属用户并校验
|
||||
if (isset($target[$userField])) {
|
||||
if (!in_array($target[$userField], $sameRoleUserIds)) {
|
||||
throw new BusinessException("无权为指定用户创建数据", 403);
|
||||
}
|
||||
} else {
|
||||
$target[$field] = admin_id();
|
||||
$target[$userField] = $currentRoleId;
|
||||
}
|
||||
}
|
||||
// update: 校验当前归属
|
||||
elseif (!in_array($current, $scope)) {
|
||||
} else {
|
||||
// 更新时校验原始记录
|
||||
if (!in_array($target->{$userField}, $sameRoleUserIds)) {
|
||||
throw new BusinessException("无权操作该记录", 403);
|
||||
}
|
||||
// 校验更新中指定的新归属
|
||||
if (isset($newData[$field]) && !in_array($newData[$field], $scope)) {
|
||||
|
||||
// 禁止变更归属字段到无权限的用户
|
||||
if (isset($newData[$userField]) && !in_array($newData[$userField], $sameRoleUserIds)) {
|
||||
throw new BusinessException("无权变更归属字段", 403);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理角色树权限
|
||||
*/
|
||||
private static function handleRoleTreePermission(
|
||||
&$target,
|
||||
array $newData,
|
||||
string $userField,
|
||||
string $roleField,
|
||||
int $currentRoleId
|
||||
): void {
|
||||
// 获取角色树用户ID列表
|
||||
$roleTreeUserIds = self::getRoleTreeUserIds($currentRoleId);
|
||||
|
||||
if (is_array($target)) {
|
||||
// 创建时校验用户是否在允许范围内
|
||||
if (isset($target[$userField])) {
|
||||
if (!in_array($target[$userField], $roleTreeUserIds)) {
|
||||
throw new BusinessException("无权为指定用户创建数据", 403);
|
||||
}
|
||||
} else {
|
||||
$target[$userField] = $currentRoleId;
|
||||
}
|
||||
} else {
|
||||
// 更新时校验原始记录用户
|
||||
if (!in_array($target->{$userField}, $roleTreeUserIds)) {
|
||||
throw new BusinessException("无权操作该记录", 403);
|
||||
}
|
||||
|
||||
// 校验新用户是否在允许范围内
|
||||
if (isset($newData[$userField])) {
|
||||
if (!in_array($newData[$userField], $roleTreeUserIds)) {
|
||||
throw new BusinessException("无权变更用户字段", 403);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理从属关系权限
|
||||
*/
|
||||
private static function handleRelationPermission(
|
||||
&$target,
|
||||
array $newData,
|
||||
string $userField,
|
||||
int $currentUserId
|
||||
): void {
|
||||
// 获取从属关系用户ID列表
|
||||
$relationUserIds = self::getRelationUserIds($currentUserId);
|
||||
|
||||
if (is_array($target)) {
|
||||
// 创建时校验用户是否在允许范围内
|
||||
if (isset($target[$userField])) {
|
||||
if (!in_array($target[$userField], $relationUserIds)) {
|
||||
throw new BusinessException("无权为指定用户创建数据", 403);
|
||||
}
|
||||
} else {
|
||||
$target[$userField] = $currentUserId;
|
||||
}
|
||||
} else {
|
||||
// 更新时校验原始记录用户
|
||||
if (!in_array($target->{$userField}, $relationUserIds)) {
|
||||
throw new BusinessException("无权操作该记录", 403);
|
||||
}
|
||||
|
||||
// 校验新用户是否在允许范围内
|
||||
if (isset($newData[$userField])) {
|
||||
if (!in_array($newData[$userField], $relationUserIds)) {
|
||||
throw new BusinessException("无权变更用户字段", 403);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取角色树ID(当前角色及其所有子角色)
|
||||
* 从数据库中动态获取角色树关系
|
||||
*/
|
||||
private static function getRoleTreeIds(int $roleId): array
|
||||
{
|
||||
static $roleTreeCache = [];
|
||||
|
||||
if (isset($roleTreeCache[$roleId])) {
|
||||
return $roleTreeCache[$roleId];
|
||||
}
|
||||
|
||||
// 从数据库中递归获取角色树
|
||||
$ids = [$roleId];
|
||||
$queue = [$roleId];
|
||||
|
||||
while (!empty($queue)) {
|
||||
$current = array_shift($queue);
|
||||
// 查找当前角色的所有子角色
|
||||
$childRoles = Db::table('roles')
|
||||
->where('parent_id', $current)
|
||||
->where('status', 1) // 只获取启用的角色
|
||||
->pluck('id')
|
||||
->toArray();
|
||||
|
||||
foreach ($childRoles as $childId) {
|
||||
if (!in_array($childId, $ids)) {
|
||||
$ids[] = $childId;
|
||||
$queue[] = $childId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$roleTreeCache[$roleId] = $ids;
|
||||
return $ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否为超管
|
||||
*
|
||||
* @param int $userId 用户ID
|
||||
* @return bool 是否为超管
|
||||
*/
|
||||
public static function isSuperAdmin(int $userId): bool
|
||||
{
|
||||
// 检查用户是否关联了超级管理员角色
|
||||
// 设定角色ID为1的是超级管理员角色
|
||||
$superAdminRoleId = 1;
|
||||
|
||||
return Db::table('user_roles')
|
||||
->where('user_id', $userId)
|
||||
->where('role_id', $superAdminRoleId)
|
||||
->exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户的角色ID
|
||||
*
|
||||
* @param int $userId 用户ID
|
||||
* @return int 角色ID
|
||||
*/
|
||||
public static function getUserIdRoleId(int $userId): int
|
||||
{
|
||||
// 从 user_roles 表中获取当前用户的角色ID
|
||||
$roleId = Db::table('user_roles')
|
||||
->where('user_id', $userId)
|
||||
->value('role_id');
|
||||
|
||||
return $roleId ?: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户的所有角色ID(包括继承的角色)
|
||||
*
|
||||
* @param int $userId 用户ID
|
||||
* @return array 角色ID列表
|
||||
*/
|
||||
public static function getUserAllRoleIds(int $userId): array
|
||||
{
|
||||
// 获取用户直接关联的角色
|
||||
$directRoleIds = Db::table('user_roles')
|
||||
->where('user_id', $userId)
|
||||
->pluck('role_id')
|
||||
->toArray();
|
||||
|
||||
// 获取所有继承的角色
|
||||
$allRoleIds = [];
|
||||
foreach ($directRoleIds as $roleId) {
|
||||
$allRoleIds = array_merge($allRoleIds, self::getRoleTreeIds($roleId));
|
||||
}
|
||||
|
||||
// 去重并返回
|
||||
return array_unique($allRoleIds);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,843 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\utils;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use support\exception\BusinessException;
|
||||
use support\Db;
|
||||
|
||||
/**
|
||||
* 构建查询工具类
|
||||
* | 方法 | 说明 |
|
||||
* | ------------------------------- | --------------------- |
|
||||
* | `buildQuery($config)` | 构建动态查询(关联加载/字段/过滤/排序) |
|
||||
* | `paginate($q,$per,$page)` | 分页返回 Paginator |
|
||||
* | `getAll($q)` | 不分页直接 `get()` |
|
||||
* | `findByPk($model,$id)` | 根据主键查询单条,找不到抛 404 |
|
||||
* | `findOneBy($model,$where)` | 根据任意条件查询单条,找不到抛 404 |
|
||||
* | `deleteByPk($model,$ids)` | 根据主键删除(可批量),返回删除条数 |
|
||||
* | `deleteBy($model,$where)` | 根据任意条件删除,返回删除条数 |
|
||||
* | `updateByPk($model,$id,$data)` | 根据主键更新,找不到抛 404 |
|
||||
* | `updateBy($model,$where,$data)` | 根据任意条件更新,返回影响行数 |
|
||||
*/
|
||||
|
||||
class CrudUtil_bak
|
||||
{
|
||||
|
||||
/**
|
||||
* 构建查询,支持:
|
||||
* - 本表字段过滤 where
|
||||
* - 关联加载 with
|
||||
* - 关联表字段选择 withFields
|
||||
* - 关联表字段过滤 where with dot-notation
|
||||
* - 排序 order
|
||||
* - 本表字段选择 fields
|
||||
*
|
||||
* $config = [
|
||||
* 'model' => User::class,
|
||||
* 'where' => [
|
||||
* 'status' => 1,
|
||||
* 'name' => ['like','%张%'],
|
||||
* 'roles.name' => '管理员', // 关联表字段过滤
|
||||
* 'posts.created_at'=> ['>','2025-01-01'],
|
||||
* ],
|
||||
* 'order' => ['id'=>'desc'],
|
||||
* 'fields' => ['id','name','email'],
|
||||
* 'with' => ['roles','posts'],
|
||||
* 'withFields' => [
|
||||
* 'roles' => ['id','name'], // 关联表字段选择
|
||||
* 'posts' => ['id','title','user_id'],
|
||||
* ],
|
||||
* ];
|
||||
*/
|
||||
public static function buildQuery(array $config): Builder
|
||||
{
|
||||
/** @var Model $model */
|
||||
$model = new $config['model'];
|
||||
$q = $model->newQuery();
|
||||
|
||||
// eager-load:支持 ['rel','rel2'] 和 ['rel'=>fields[], ...]
|
||||
if (! empty($config['with'])) {
|
||||
foreach ($config['with'] as $rel => $fields) {
|
||||
if (is_int($rel)) {
|
||||
// 普通关联
|
||||
$q->with($fields);
|
||||
} else {
|
||||
// 带字段筛选的关联
|
||||
$q->with([$rel => function ($qr) use ($fields) {
|
||||
$key = $qr->getModel()->getKeyName();
|
||||
if (! in_array($key, $fields, true)) {
|
||||
$fields[] = $key;
|
||||
}
|
||||
$qr->select($fields);
|
||||
}]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 表字段
|
||||
if (!empty($config['fields'])) {
|
||||
$q->select($config['fields']);
|
||||
}
|
||||
|
||||
// where
|
||||
if (!empty($config['where'])) {
|
||||
foreach ($config['where'] as $col => $val) {
|
||||
if (strpos($col, '.') !== false) {
|
||||
// 多层支持
|
||||
$segments = explode('.', $col);
|
||||
$leaf = array_pop($segments);
|
||||
self::applyNestedWhere($q, $segments, $leaf, $val);
|
||||
} else {
|
||||
self::applyWhere($q, $col, $val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// or where
|
||||
if (!empty($config['orWhere'])) {
|
||||
$q->where(function($q) use($config) {
|
||||
foreach ($config['orWhere'] as $cond) {
|
||||
[$col,$op,$val] = $cond;
|
||||
$q->orWhere($col,$op,$val);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 排序
|
||||
if (!empty($config['order'])) {
|
||||
foreach ($config['order'] as $col => $dir) {
|
||||
$q->orderBy($col, strtolower($dir) === 'asc' ? 'asc' : 'desc');
|
||||
}
|
||||
}
|
||||
|
||||
// 数据权限过滤
|
||||
if (!empty($config['dataLimit'])) {
|
||||
self::applyDataLimit($q, $config['dataLimit']);
|
||||
}
|
||||
|
||||
return $q;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询
|
||||
*
|
||||
* @param Builder $query
|
||||
* @param int $perPage
|
||||
* @param int $page
|
||||
* @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
|
||||
*/
|
||||
public static function paginate(Builder $q, int $perPage, int $page)
|
||||
{
|
||||
return $q->paginate($perPage, ['*'], 'page', $page);
|
||||
}
|
||||
|
||||
/**
|
||||
* 非分页查询
|
||||
*
|
||||
* @param Builder $query
|
||||
* @return \Illuminate\Database\Eloquent\Collection
|
||||
*/
|
||||
public static function getAll(Builder $q)
|
||||
{
|
||||
return $q->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行复杂查询——仅在显式调用时生效
|
||||
*
|
||||
* @param string $modelClass 模型类
|
||||
* @param callable $callback function(Builder $q) { ... }
|
||||
* @return mixed 由 callback 返回的结果
|
||||
*/
|
||||
public static function executeComplex(string $modelClass, callable $cb)
|
||||
{
|
||||
/** @var Model $model */
|
||||
$model = new $modelClass;
|
||||
return $cb($model->newQuery());
|
||||
}
|
||||
|
||||
/**
|
||||
* 主键单条查询
|
||||
*
|
||||
* @param string $modelClass
|
||||
* @param mixed $id
|
||||
* @param array $with
|
||||
* @return Model
|
||||
* @throws BusinessException
|
||||
*/
|
||||
public static function findByPk(string $modelClass, $id, array $with = []): Model
|
||||
{
|
||||
$m = $modelClass::with($with)->find($id);
|
||||
if (!$m) {
|
||||
throw new BusinessException("记录 ID={$id} 不存在", 404);
|
||||
}
|
||||
return $m;
|
||||
}
|
||||
|
||||
/**
|
||||
* 任意条件单条查询,支持 dataLimit
|
||||
*
|
||||
* @param Builder $query
|
||||
* @return Model
|
||||
* @throws BusinessException
|
||||
*/
|
||||
public static function findOneBy(Builder $q): Model
|
||||
{
|
||||
return $q->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建,支持 dataLimit
|
||||
*
|
||||
* @param string $modelClass
|
||||
* @param array $data
|
||||
* @param array $dataLimit
|
||||
* @return Model
|
||||
* @throws BusinessException
|
||||
*/
|
||||
public static function create(
|
||||
string $modelClass,
|
||||
array $data,
|
||||
array $dataLimit = []
|
||||
): Model {
|
||||
// dataLimit 检查/注入
|
||||
if (!empty($dataLimit)) {
|
||||
self::applyDataLimitOnMutate($data, $dataLimit);
|
||||
}
|
||||
return $modelClass::create($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 主键更新,支持 dataLimit
|
||||
*
|
||||
* @param string $modelClass
|
||||
* @param mixed $id
|
||||
* @param array $data
|
||||
* @param array $dataLimit
|
||||
* @return Model
|
||||
* @throws BusinessException
|
||||
*/
|
||||
public static function updateByPk(
|
||||
string $modelClass,
|
||||
$id,
|
||||
array $data,
|
||||
array $dataLimit = []
|
||||
): Model {
|
||||
/** @var Model $m */
|
||||
$m = $modelClass::find($id);
|
||||
if (!$m) {
|
||||
throw new BusinessException("记录 ID={$id} 不存在", 404);
|
||||
}
|
||||
// dataLimit 校验
|
||||
if (!empty($dataLimit)) {
|
||||
self::applyDataLimitOnMutate($m, $dataLimit, $data);
|
||||
}
|
||||
$m->fill($data)->save();
|
||||
return $m;
|
||||
}
|
||||
|
||||
/**
|
||||
* 非主键条件更新(批量),支持 dataLimit
|
||||
*
|
||||
* @param Builder $query
|
||||
* @param array $data
|
||||
* @return int 受影响行数
|
||||
*/
|
||||
public static function updateBy(Builder $q, array $data): int
|
||||
{
|
||||
return $q->update($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 主键删除(支持批量)
|
||||
*
|
||||
* @param string $modelClass
|
||||
* @param array $ids
|
||||
* @return int 删除条数
|
||||
*/
|
||||
public static function deleteByPk(string $modelClass, array $ids): int
|
||||
{
|
||||
return $modelClass::destroy($ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* 非主键条件删除,支持 dataLimit
|
||||
*
|
||||
* @param Builder $query
|
||||
* @return int 删除条数
|
||||
*/
|
||||
public static function deleteBy(Builder $q): int
|
||||
{
|
||||
return $q->delete();
|
||||
}
|
||||
|
||||
|
||||
//=================== Internal Methods ===================//
|
||||
|
||||
|
||||
/**
|
||||
* 支持多层的 whereHas:$relations=['roles','permissions'],$leaf='name'
|
||||
*/
|
||||
protected static function applyNestedWhere(Builder $q, array $relations, string $leaf, $val): void
|
||||
{
|
||||
$rel = array_shift($relations);
|
||||
$q->whereHas($rel, function (Builder $qr) use ($relations, $leaf, $val) {
|
||||
if ($relations) {
|
||||
// 还有更深层,继续递归
|
||||
self::applyNestedWhere($qr, $relations, $leaf, $val);
|
||||
} else {
|
||||
// 最后一层,使用普通 applyWhere
|
||||
self::applyWhere($qr, $leaf, $val);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 单字段 where 支持:
|
||||
* - 多条件数组 [ [op1,val1], [op2,val2], … ]
|
||||
* - 单条件 ['op', val]
|
||||
* - 直接值
|
||||
*
|
||||
* @param Builder $q
|
||||
* @param string $col
|
||||
* @param mixed $val
|
||||
* @return void
|
||||
*/
|
||||
protected static function applyWhere(Builder $q, string $col, $val): void
|
||||
{
|
||||
// 多条件数组
|
||||
if (is_array($val) && isset($val[0]) && is_array($val[0])) {
|
||||
foreach ($val as $pair) {
|
||||
if (is_array($pair) && count($pair) === 2) {
|
||||
self::applySingleWhere($q, $col, $pair[0], $pair[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 单条件
|
||||
elseif (is_array($val) && count($val) === 2) {
|
||||
self::applySingleWhere($q, $col, $val[0], $val[1]);
|
||||
}
|
||||
// 直接等值
|
||||
else {
|
||||
$q->where($col, $val);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行条件 where
|
||||
*/
|
||||
protected static function applySingleWhere(Builder $q, string $col, string $op, $v): void
|
||||
{
|
||||
switch (strtolower($op)) {
|
||||
case 'like':
|
||||
$q->where($col, 'like', $v);
|
||||
break;
|
||||
case 'in':
|
||||
$q->whereIn($col, (array)$v);
|
||||
break;
|
||||
case 'not in':
|
||||
$q->whereNotIn($col, (array)$v);
|
||||
break;
|
||||
case 'null':
|
||||
$q->whereNull($col);
|
||||
break;
|
||||
case 'not null':
|
||||
$q->whereNotNull($col);
|
||||
break;
|
||||
case 'between':
|
||||
$q->whereBetween($col, (array)$v);
|
||||
break;
|
||||
default:
|
||||
$q->where($col, $op, $v);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ================== Data Limit ==================
|
||||
|
||||
/**
|
||||
* 应用数据权限过滤到查询
|
||||
*
|
||||
* @param Builder $query 查询构造器
|
||||
* @param string $type 权限类型:superadmin, personal, same_role, role_tree, relation
|
||||
* @param int $currentUserId 当前用户ID
|
||||
* @param int $currentRoleId 当前角色ID
|
||||
* @param string $userField 用户ID字段名
|
||||
* @param string $roleField 角色ID字段名
|
||||
*/
|
||||
public static function applyQueryPermission(
|
||||
Builder $query,
|
||||
string $type,
|
||||
int $currentUserId = 0,
|
||||
int $currentRoleId = 0,
|
||||
string $userField = 'user_id',
|
||||
string $roleField = 'role_id'
|
||||
): void {
|
||||
// 超管不需要过滤
|
||||
if (self::isSuperAdmin($currentUserId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch ($type) {
|
||||
case 'personal':
|
||||
// 个人权限:只能访问自己的数据
|
||||
$query->where($userField, $currentUserId);
|
||||
break;
|
||||
|
||||
case 'same_role':
|
||||
// 同角色权限:访问同角色的数据
|
||||
$userIds = self::getSameRoleUserIds($currentRoleId);
|
||||
$query->whereIn($userField, $userIds);
|
||||
break;
|
||||
|
||||
case 'role_tree':
|
||||
// 角色树权限:访问自己角色及子角色的数据
|
||||
$userIds = self::getRoleTreeUserIds($currentRoleId);
|
||||
$query->whereIn($userField, $userIds);
|
||||
break;
|
||||
|
||||
case 'relation':
|
||||
// 从属关系权限:访问自己及下属的数据
|
||||
$userIds = self::getRelationUserIds($currentUserId);
|
||||
$query->whereIn($userField, $userIds);
|
||||
break;
|
||||
|
||||
default:
|
||||
// 默认个人权限
|
||||
$query->where($userField, $currentUserId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用数据权限到创建/更新操作
|
||||
*
|
||||
* @param mixed $target 创建时为数组,更新时为Model对象
|
||||
* @param string $type 权限类型
|
||||
* @param int $currentUserId 当前用户ID
|
||||
* @param int $currentRoleId 当前角色ID
|
||||
* @param array $newData 更新时的数据
|
||||
* @param string $userField 用户ID字段名
|
||||
* @param string $roleField 角色ID字段名
|
||||
*/
|
||||
public static function applyMutationPermission(
|
||||
&$target,
|
||||
string $type,
|
||||
int $currentUserId,
|
||||
int $currentRoleId,
|
||||
array $newData = [],
|
||||
string $userField = 'admin_id',
|
||||
string $roleField = 'role_id'
|
||||
): void {
|
||||
// 超管不需要检查
|
||||
if (self::isSuperAdmin($currentUserId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch ($type) {
|
||||
case 'personal':
|
||||
self::handlePersonalPermission($target, $newData, $userField, $currentUserId);
|
||||
break;
|
||||
|
||||
case 'same_role':
|
||||
self::handleSameRolePermission($target, $newData, $userField, $roleField, $currentRoleId);
|
||||
break;
|
||||
|
||||
case 'role_tree':
|
||||
self::handleRoleTreePermission($target, $newData, $userField, $roleField, $currentRoleId);
|
||||
break;
|
||||
|
||||
case 'relation':
|
||||
self::handleRelationPermission($target, $newData, $userField, $currentUserId);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new BusinessException("未定义的数据权限类型: $type", 403);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取同角色的用户ID列表
|
||||
*
|
||||
* @param int $roleId 角色ID
|
||||
* @return array 用户ID列表
|
||||
*/
|
||||
public static function getSameRoleUserIds(int $roleId): array
|
||||
{
|
||||
return Db::table('user_roles')
|
||||
->where('role_id', $roleId)
|
||||
->pluck('user_id')
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取角色树下的所有用户ID列表
|
||||
*
|
||||
* @param int $roleId 当前角色ID
|
||||
* @return array 用户ID列表
|
||||
*/
|
||||
public static function getRoleTreeUserIds(int $roleId): array
|
||||
{
|
||||
// 获取角色树中的所有角色ID
|
||||
$roleIds = self::getRoleTreeIds($roleId);
|
||||
|
||||
// 获取这些角色下的所有用户ID
|
||||
return Db::table('user_roles')
|
||||
->whereIn('role_id', $roleIds)
|
||||
->pluck('user_id')
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取从属关系下的用户ID列表(包括自己和下属)
|
||||
*
|
||||
* @param int $userId 当前用户ID
|
||||
* @return array 用户ID列表
|
||||
*/
|
||||
public static function getRelationUserIds(int $userId): array
|
||||
{
|
||||
// 获取所有下属用户ID(递归)
|
||||
$subordinateIds = self::getSubordinateIds($userId);
|
||||
|
||||
// 返回自己和所有下属的ID
|
||||
return array_merge([$userId], $subordinateIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取直接下属的用户ID列表
|
||||
*
|
||||
* @param int $userId 用户ID
|
||||
* @return array 直接下属用户ID列表
|
||||
*/
|
||||
public static function getDirectSubordinateIds(int $userId): array
|
||||
{
|
||||
return Db::table('user')
|
||||
->where('report_to', $userId)
|
||||
->pluck('id')
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归获取所有下属用户ID列表
|
||||
*
|
||||
* @param int $userId 用户ID
|
||||
* @return array 所有下属用户ID列表
|
||||
*/
|
||||
private static function getSubordinateIds(int $userId): array
|
||||
{
|
||||
$subordinates = self::getDirectSubordinateIds($userId);
|
||||
$allSubordinates = $subordinates;
|
||||
|
||||
foreach ($subordinates as $subordinateId) {
|
||||
$allSubordinates = array_merge($allSubordinates, self::getSubordinateIds($subordinateId));
|
||||
}
|
||||
|
||||
return $allSubordinates;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理个人权限
|
||||
*/
|
||||
private static function handlePersonalPermission(
|
||||
&$target,
|
||||
array $newData,
|
||||
string $userField,
|
||||
int $currentUserId
|
||||
): void {
|
||||
if (is_array($target)) {
|
||||
// 创建时强制设置用户ID
|
||||
$target[$userField] = $currentUserId;
|
||||
} else {
|
||||
// 更新时校验原始记录
|
||||
if ($target->{$userField} !== $currentUserId) {
|
||||
throw new BusinessException("无权操作该记录", 403);
|
||||
}
|
||||
|
||||
// 禁止变更归属字段
|
||||
if (isset($newData[$userField]) && $newData[$userField] !== $currentUserId) {
|
||||
throw new BusinessException("无权变更归属字段", 403);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理同角色权限
|
||||
*/
|
||||
private static function handleSameRolePermission(
|
||||
&$target,
|
||||
array $newData,
|
||||
string $userField,
|
||||
string $roleField,
|
||||
int $currentRoleId
|
||||
): void {
|
||||
// 获取同角色用户ID列表
|
||||
$sameRoleUserIds = self::getSameRoleUserIds($currentRoleId);
|
||||
|
||||
if (is_array($target)) {
|
||||
// 创建时设置归属用户并校验
|
||||
if (isset($target[$userField])) {
|
||||
if (!in_array($target[$userField], $sameRoleUserIds)) {
|
||||
throw new BusinessException("无权为指定用户创建数据", 403);
|
||||
}
|
||||
} else {
|
||||
$target[$userField] = $currentRoleId;
|
||||
}
|
||||
} else {
|
||||
// 更新时校验原始记录
|
||||
if (!in_array($target->{$userField}, $sameRoleUserIds)) {
|
||||
throw new BusinessException("无权操作该记录", 403);
|
||||
}
|
||||
|
||||
// 禁止变更归属字段到无权限的用户
|
||||
if (isset($newData[$userField]) && !in_array($newData[$userField], $sameRoleUserIds)) {
|
||||
throw new BusinessException("无权变更归属字段", 403);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理角色树权限
|
||||
*/
|
||||
private static function handleRoleTreePermission(
|
||||
&$target,
|
||||
array $newData,
|
||||
string $userField,
|
||||
string $roleField,
|
||||
int $currentRoleId
|
||||
): void {
|
||||
// 获取角色树用户ID列表
|
||||
$roleTreeUserIds = self::getRoleTreeUserIds($currentRoleId);
|
||||
|
||||
if (is_array($target)) {
|
||||
// 创建时校验用户是否在允许范围内
|
||||
if (isset($target[$userField])) {
|
||||
if (!in_array($target[$userField], $roleTreeUserIds)) {
|
||||
throw new BusinessException("无权为指定用户创建数据", 403);
|
||||
}
|
||||
} else {
|
||||
$target[$userField] = $currentRoleId;
|
||||
}
|
||||
} else {
|
||||
// 更新时校验原始记录用户
|
||||
if (!in_array($target->{$userField}, $roleTreeUserIds)) {
|
||||
throw new BusinessException("无权操作该记录", 403);
|
||||
}
|
||||
|
||||
// 校验新用户是否在允许范围内
|
||||
if (isset($newData[$userField])) {
|
||||
if (!in_array($newData[$userField], $roleTreeUserIds)) {
|
||||
throw new BusinessException("无权变更用户字段", 403);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理从属关系权限
|
||||
*/
|
||||
private static function handleRelationPermission(
|
||||
&$target,
|
||||
array $newData,
|
||||
string $userField,
|
||||
int $currentUserId
|
||||
): void {
|
||||
// 获取从属关系用户ID列表
|
||||
$relationUserIds = self::getRelationUserIds($currentUserId);
|
||||
|
||||
if (is_array($target)) {
|
||||
// 创建时校验用户是否在允许范围内
|
||||
if (isset($target[$userField])) {
|
||||
if (!in_array($target[$userField], $relationUserIds)) {
|
||||
throw new BusinessException("无权为指定用户创建数据", 403);
|
||||
}
|
||||
} else {
|
||||
$target[$userField] = $currentUserId;
|
||||
}
|
||||
} else {
|
||||
// 更新时校验原始记录用户
|
||||
if (!in_array($target->{$userField}, $relationUserIds)) {
|
||||
throw new BusinessException("无权操作该记录", 403);
|
||||
}
|
||||
|
||||
// 校验新用户是否在允许范围内
|
||||
if (isset($newData[$userField])) {
|
||||
if (!in_array($newData[$userField], $relationUserIds)) {
|
||||
throw new BusinessException("无权变更用户字段", 403);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取角色树ID(当前角色及其所有子角色)
|
||||
* 从数据库中动态获取角色树关系
|
||||
*/
|
||||
private static function getRoleTreeIds(int $roleId): array
|
||||
{
|
||||
static $roleTreeCache = [];
|
||||
|
||||
if (isset($roleTreeCache[$roleId])) {
|
||||
return $roleTreeCache[$roleId];
|
||||
}
|
||||
|
||||
// 从数据库中递归获取角色树
|
||||
$ids = [$roleId];
|
||||
$queue = [$roleId];
|
||||
|
||||
while (!empty($queue)) {
|
||||
$current = array_shift($queue);
|
||||
// 查找当前角色的所有子角色
|
||||
$childRoles = Db::table('roles')
|
||||
->where('parent_id', $current)
|
||||
->where('status', 1) // 只获取启用的角色
|
||||
->pluck('id')
|
||||
->toArray();
|
||||
|
||||
foreach ($childRoles as $childId) {
|
||||
if (!in_array($childId, $ids)) {
|
||||
$ids[] = $childId;
|
||||
$queue[] = $childId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$roleTreeCache[$roleId] = $ids;
|
||||
return $ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否为超管
|
||||
*
|
||||
* @param int $userId 用户ID
|
||||
* @return bool 是否为超管
|
||||
*/
|
||||
public static function isSuperAdmin(int $userId): bool
|
||||
{
|
||||
// 检查用户是否关联了超级管理员角色
|
||||
// 假设角色ID为1的是超级管理员角色
|
||||
$superAdminRoleId = 1;
|
||||
|
||||
return Db::table('user_roles')
|
||||
->where('user_id', $userId)
|
||||
->where('role_id', $superAdminRoleId)
|
||||
->exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户的角色ID
|
||||
*
|
||||
* @param int $userId 用户ID
|
||||
* @return int 角色ID
|
||||
*/
|
||||
public static function getUserIdRoleId(int $userId): int
|
||||
{
|
||||
// 从 user_roles 表中获取当前用户的角色ID
|
||||
$roleId = Db::table('user_roles')
|
||||
->where('user_id', $userId)
|
||||
->value('role_id');
|
||||
|
||||
return $roleId ?: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户的所有角色ID(包括继承的角色)
|
||||
*
|
||||
* @param int $userId 用户ID
|
||||
* @return array 角色ID列表
|
||||
*/
|
||||
public static function getUserAllRoleIds(int $userId): array
|
||||
{
|
||||
// 获取用户直接关联的角色
|
||||
$directRoleIds = Db::table('user_roles')
|
||||
->where('user_id', $userId)
|
||||
->pluck('role_id')
|
||||
->toArray();
|
||||
|
||||
// 获取所有继承的角色
|
||||
$allRoleIds = [];
|
||||
foreach ($directRoleIds as $roleId) {
|
||||
$allRoleIds = array_merge($allRoleIds, self::getRoleTreeIds($roleId));
|
||||
}
|
||||
|
||||
// 去重并返回
|
||||
return array_unique($allRoleIds);
|
||||
}
|
||||
|
||||
// ================== Data Limit ==================
|
||||
|
||||
/**
|
||||
* 应用数据权限过滤到查询(buildQuery 场景)
|
||||
*/
|
||||
protected static function applyDataLimit(Builder $q, array $dataLimit): void
|
||||
{
|
||||
if (Auth::isSuperAdmin()) {
|
||||
return;
|
||||
}
|
||||
$type = $dataLimit['type'] ?? null;
|
||||
$field = $dataLimit['field'] ?? 'admin_id';
|
||||
|
||||
if ($type === 'personal') {
|
||||
$q->where($field, admin_id());
|
||||
} elseif ($type === 'auth') {
|
||||
$q->whereIn($field, Auth::getScopeAdminIds(true));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用数据权限到“创建/更新”操作
|
||||
*
|
||||
* - $target 可能是 array|Model 对象
|
||||
* - $newData 仅在 updateByPk 场景时传入,用以校验变更归属
|
||||
*/
|
||||
protected static function applyDataLimitOnMutate(&$target, array $dataLimit, array $newData = []): void
|
||||
{
|
||||
if (Auth::isSuperAdmin()) {
|
||||
return;
|
||||
}
|
||||
$type = $dataLimit['type'] ?? null;
|
||||
$field = $dataLimit['field'] ?? 'admin_id';
|
||||
|
||||
// 读取当前归属:create 时 $target 是 array,update 时是 Model
|
||||
$current = is_array($target) ? null : $target->{$field};
|
||||
|
||||
// 1. personal → 只能自己
|
||||
if ($type === 'personal') {
|
||||
// create: 强制设置
|
||||
if (is_array($target)) {
|
||||
$target[$field] = admin_id();
|
||||
}
|
||||
// update: 校验当前归属
|
||||
elseif ($current !== admin_id()) {
|
||||
throw new BusinessException("无权操作该记录", 403);
|
||||
}
|
||||
// 若要修改归属,也必须和自己一致
|
||||
if (isset($newData[$field]) && $newData[$field] !== admin_id()) {
|
||||
throw new BusinessException("无权变更归属字段", 403);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. auth → 自己及下属
|
||||
if ($type === 'auth') {
|
||||
$scope = Auth::getScopeAdminIds(true);
|
||||
// create: 若前端指定归属,则校验范围;否则默认自己
|
||||
if (is_array($target)) {
|
||||
if (isset($target[$field])) {
|
||||
if (!in_array($target[$field], $scope)) {
|
||||
throw new BusinessException("无权为指定用户操作", 403);
|
||||
}
|
||||
} else {
|
||||
$target[$field] = admin_id();
|
||||
}
|
||||
}
|
||||
// update: 校验当前归属
|
||||
elseif (!in_array($current, $scope)) {
|
||||
throw new BusinessException("无权操作该记录", 403);
|
||||
}
|
||||
// 校验更新中指定的新归属
|
||||
if (isset($newData[$field]) && !in_array($newData[$field], $scope)) {
|
||||
throw new BusinessException("无权变更归属字段", 403);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -67,10 +67,10 @@ class InquiryLogValidate extends Validate
|
||||
// 场景
|
||||
protected $scene = [
|
||||
'store' => [
|
||||
'enter_user_id', 'quality', 'channel', 'email', 'tel', 'whatsapp', 'country', 'contacts', 'content'
|
||||
'enter_user_id', 'quality', 'channel', 'email', 'tel', 'whatsapp', 'country', 'contacts', 'content', 'recipient'
|
||||
],
|
||||
'update' => [
|
||||
'quality', 'channel', 'email', 'tel', 'whatsapp', 'country', 'contacts', 'content'
|
||||
'quality', 'channel', 'email', 'tel', 'whatsapp', 'country', 'contacts', 'content', 'recipient'
|
||||
],
|
||||
'release' => [
|
||||
'status', 'r_time'
|
||||
|
||||
@@ -6,6 +6,7 @@ use app\common\model\InquiryLog;
|
||||
use app\common\model\User;
|
||||
use app\common\model\UserRoles;
|
||||
use app\common\model\UserSystems;
|
||||
use app\common\traits\DataPermission;
|
||||
use app\common\utils\email\MailSend;
|
||||
use app\common\utils\scaffold\Generate;
|
||||
use support\Request;
|
||||
@@ -105,9 +106,8 @@ class IndexController
|
||||
public function test()
|
||||
{
|
||||
|
||||
$data = MailSend::getContent(22);
|
||||
MailSend::send('mzl@hnxinyouhe.com', '【XYH】新的客户线索', replaceTpl($data));
|
||||
|
||||
$data = DataPermission::getRoleTreeUserIds(1);
|
||||
var_dump($data);
|
||||
return 22;
|
||||
}
|
||||
}
|
||||
@@ -221,3 +221,51 @@ if (!function_exists('removeParentheses')) {
|
||||
return trim(preg_replace($pattern, '', $str, 1));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 规范hmtl富文本
|
||||
*/
|
||||
if (!function_exists('normalizeRichText')) {
|
||||
function normalizeRichText(string $html): string
|
||||
{
|
||||
$dom = new DOMDocument();
|
||||
|
||||
// 处理UTF-8编码和HTML5标签
|
||||
@$dom->loadHTML('<?xml encoding="UTF-8">' . $html,
|
||||
LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
|
||||
|
||||
// 设置编码并规范化文档
|
||||
$dom->encoding = 'UTF-8';
|
||||
$dom->normalizeDocument();
|
||||
|
||||
// 统一处理空白字符
|
||||
$xpath = new DOMXPath($dom);
|
||||
foreach ($xpath->query('//text()') as $node) {
|
||||
$normalized = preg_replace('/\s+/u', ' ', trim($node->nodeValue));
|
||||
$node->nodeValue = $normalized;
|
||||
}
|
||||
|
||||
// 标准化属性顺序(可选)
|
||||
foreach ($xpath->query('//*[@*]') as $element) {
|
||||
$attributes = $element->attributes;
|
||||
$sorted = [];
|
||||
foreach ($attributes as $attr) {
|
||||
$sorted[$attr->nodeName] = $attr->nodeValue;
|
||||
}
|
||||
ksort($sorted);
|
||||
|
||||
// 移除所有属性后按排序后顺序重新添加
|
||||
while ($attributes->length) {
|
||||
$element->removeAttributeNode($attributes->item(0));
|
||||
}
|
||||
foreach ($sorted as $name => $value) {
|
||||
$element->setAttribute($name, $value);
|
||||
}
|
||||
}
|
||||
|
||||
// 返回规范化后的HTML
|
||||
$result = trim($dom->saveHTML());
|
||||
// 替换所有空白实体为普通空格
|
||||
return preg_replace('/\s+/u', ' ', $result);
|
||||
}
|
||||
}
|
||||
@@ -25,11 +25,11 @@ class InquiryLogController extends BaseController
|
||||
*/
|
||||
protected function indexFormat($data)
|
||||
{
|
||||
$data['countdown'] = time()-1;
|
||||
if (isset($data['r_time'])) {
|
||||
$countdown = 60 * 60;
|
||||
$data['countdown'] = (time() - $data['r_time']) < $countdown ? $data['r_time'] + (60*60) : time()-1;
|
||||
}
|
||||
// $data['countdown'] = time()-1;
|
||||
// if (isset($data['r_time'])) {
|
||||
// $countdown = 60 * 60;
|
||||
// $data['countdown'] = (time() - $data['r_time']) < $countdown ? $data['r_time'] + (60*60) : time()-1;
|
||||
// }
|
||||
|
||||
if (isset($data['created_at'])) {
|
||||
$data['created_at'] = date('Y-m-d H:i:s', $data['created_at']);
|
||||
@@ -46,7 +46,7 @@ class InquiryLogController extends BaseController
|
||||
if (isset($data['t_time'])) {
|
||||
$data['t_time'] = $data['t_time'] > 0 ? date('Y-m-d H:i:s', $data['t_time']) : 0;
|
||||
}
|
||||
// var_dump($data);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
@@ -84,6 +84,23 @@ class InquiryLogController extends BaseController
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新分配
|
||||
*
|
||||
* @param Request $request
|
||||
* @return mixed
|
||||
*/
|
||||
public function reAllocation(Request $request)
|
||||
{
|
||||
try {
|
||||
$data = $this->validate($this->service->getValidator(), $request->post(), 'allocation');
|
||||
$res = $this->service->serviceReAllocation($request, $data);
|
||||
return $this->success([], '重新分配成功');
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// 导出
|
||||
// public function export(Request $request)
|
||||
// {
|
||||
|
||||
@@ -32,27 +32,6 @@ class InquiryLogService
|
||||
return new $this->validator;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发布
|
||||
*/
|
||||
public function serviceRelease(Request $request, array $data = [])
|
||||
{
|
||||
$data = $this->preProcess('Release', $data, $request);
|
||||
$config = $this->buildQueryConfig($request, 'release');
|
||||
$updated = $this->logicUpdateBy($config, $data);
|
||||
return $this->postProcess('Release', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 分配
|
||||
*/
|
||||
public function serviceAllocation(Request $request, array $data = [])
|
||||
{
|
||||
$data = $this->preProcess('Allocation', $data, $request);
|
||||
$config = $this->buildQueryConfig($request, 'allocation');
|
||||
$updated = $this->logicUpdateBy($config, $data);
|
||||
return $this->postProcess('Allocation', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建查询条件
|
||||
@@ -108,6 +87,7 @@ class InquiryLogService
|
||||
case 'destroy':
|
||||
case 'release':
|
||||
case 'allocation':
|
||||
case 'reAllocation':
|
||||
$config['where']['id'] = $p['id'];
|
||||
case 'store':
|
||||
break;
|
||||
@@ -116,6 +96,40 @@ class InquiryLogService
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发布
|
||||
*/
|
||||
public function serviceRelease(Request $request, array $data = [])
|
||||
{
|
||||
$data = $this->preProcess('Release', $data, $request);
|
||||
$config = $this->buildQueryConfig($request, 'release');
|
||||
$updated = $this->logicUpdateBy($config, $data);
|
||||
return $this->postProcess('Release', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 分配
|
||||
*/
|
||||
public function serviceAllocation(Request $request, array $data = [])
|
||||
{
|
||||
$data = $this->preProcess('Allocation', $data, $request);
|
||||
$config = $this->buildQueryConfig($request, 'allocation');
|
||||
$updated = $this->logicUpdateBy($config, $data);
|
||||
return $this->postProcess('Allocation', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新分配
|
||||
*/
|
||||
public function serviceReAllocation(Request $request, array $data = [])
|
||||
{
|
||||
$data = $this->preProcess('ReAllocation', $data, $request);
|
||||
$config = $this->buildQueryConfig($request, 'reAllocation');
|
||||
$updated = $this->logicUpdateBy($config, $data);
|
||||
return $this->postProcess('ReAllocation', $data);
|
||||
}
|
||||
|
||||
// ====== 前置处理 ======
|
||||
/**
|
||||
* 添加前置处理
|
||||
*/
|
||||
@@ -123,7 +137,7 @@ class InquiryLogService
|
||||
{
|
||||
$data['order_no'] = orderNum();
|
||||
$data['enter_user_id'] = $request->user->uid;
|
||||
$data['recipient'] = 0;
|
||||
// $data['recipient'] = 0;
|
||||
$data['email'] = trim($data["email"]);
|
||||
$data['tel'] = trim($data['tel']);
|
||||
$data['whatsapp'] = trim($data['whatsapp']);
|
||||
@@ -143,6 +157,50 @@ class InquiryLogService
|
||||
$data['content']
|
||||
);
|
||||
|
||||
// 如果添加时填写了接单人
|
||||
if (isset($data['recipient']) && $data['recipient']) {
|
||||
$data['r_time'] = time();
|
||||
$data['t_time'] = time();
|
||||
$data['status'] = 2;
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新前置处理
|
||||
*/
|
||||
public function beforeServiceUpdate($data, Request $request)
|
||||
{
|
||||
$info = $this->findByPk($request->post('id'));
|
||||
if (!$info) {
|
||||
throw new BusinessException('数据不存在');
|
||||
}
|
||||
|
||||
// 处理富文本
|
||||
if (md5(normalizeRichText($data['content'])) !== md5(normalizeRichText($info->content))) {
|
||||
$ossDomain = 'https://vehicle-album.oss-cn-beijing.aliyuncs.com/';
|
||||
$proxyPath = getenv('MAIN_DOMAIN').'/proxy/ossurl?file=';
|
||||
|
||||
$data['content'] = str_replace(
|
||||
'?Expires=',
|
||||
'&Expires=',
|
||||
$data['content']
|
||||
);
|
||||
$data['content'] = str_replace(
|
||||
$ossDomain,
|
||||
$proxyPath,
|
||||
$data['content']
|
||||
);
|
||||
}
|
||||
|
||||
// 如果更新时操作了跟单人
|
||||
if ($info->recipient) {
|
||||
$info->recipient == $data['recipient'] ?: $data['t_time'] = time();
|
||||
} else {
|
||||
$data['r_time'] = time();
|
||||
$data['t_time'] = time();
|
||||
$data['status'] = 2;
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
@@ -183,6 +241,20 @@ class InquiryLogService
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新分配前置处理
|
||||
*/
|
||||
public function beforeServiceReAllocation($data, Request $request)
|
||||
{
|
||||
$info = $this->findByPk($request->post('id'));
|
||||
if (!$info) {
|
||||
throw new BusinessException('数据不存在');
|
||||
}
|
||||
$data['t_time'] = time();
|
||||
return $data;
|
||||
}
|
||||
|
||||
// ====== 后置处理 ======
|
||||
/**
|
||||
* 发布后置处理
|
||||
*/
|
||||
@@ -201,6 +273,34 @@ class InquiryLogService
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加后置处理
|
||||
*/
|
||||
protected function afterServiceStore($res)
|
||||
{
|
||||
RedisQueueRedis::send('inquiry-to-crm', $res);
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新后置处理
|
||||
*/
|
||||
protected function afterServiceUpdate($res)
|
||||
{
|
||||
if ($res['recipient']) {
|
||||
RedisQueueRedis::send('inquiry-to-crm-update', $res);
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新分配后置处理
|
||||
*/
|
||||
protected function afterServiceReAllocation($res)
|
||||
{
|
||||
RedisQueueRedis::send('inquiry-to-crm-update', $res);
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取询单ById
|
||||
|
||||
+20
-20
@@ -28,29 +28,29 @@ class Emails
|
||||
// $email->process();
|
||||
// });
|
||||
|
||||
// 拉取新邮件 - 每3小时的第0分钟0秒执行
|
||||
new Crontab('0 0 */3 * * *', function(){
|
||||
$email = new EmailsUtils();
|
||||
$email->uids();
|
||||
});
|
||||
// // 拉取新邮件 - 每3小时的第0分钟0秒执行
|
||||
// new Crontab('0 0 */3 * * *', function(){
|
||||
// $email = new EmailsUtils();
|
||||
// $email->uids();
|
||||
// });
|
||||
|
||||
// 邮件详情 - 每3小时的第10分钟0秒执行
|
||||
new Crontab('0 10 */3 * * *', function(){
|
||||
$email = new EmailsUtils();
|
||||
$email->details();
|
||||
});
|
||||
// // 邮件详情 - 每3小时的第10分钟0秒执行
|
||||
// new Crontab('0 10 */3 * * *', function(){
|
||||
// $email = new EmailsUtils();
|
||||
// $email->details();
|
||||
// });
|
||||
|
||||
// 邮件处理 - 每3小时的第20分钟0秒执行
|
||||
new Crontab('0 20 */3 * * *', function(){
|
||||
$email = new EmailsUtils();
|
||||
$email->process();
|
||||
});
|
||||
// // 邮件处理 - 每3小时的第20分钟0秒执行
|
||||
// new Crontab('0 20 */3 * * *', function(){
|
||||
// $email = new EmailsUtils();
|
||||
// $email->process();
|
||||
// });
|
||||
|
||||
// 邮件分派 - 每3小时的第30分钟0秒执行
|
||||
new Crontab('0 30 */3 * * *', function(){
|
||||
$email = new EmailsUtils();
|
||||
$email->fenpai();
|
||||
});
|
||||
// // 邮件分派 - 每3小时的第30分钟0秒执行
|
||||
// new Crontab('0 30 */3 * * *', function(){
|
||||
// $email = new EmailsUtils();
|
||||
// $email->fenpai();
|
||||
// });
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
namespace app\queue\redis;
|
||||
|
||||
use app\inquiry\service\InquiryLogService;
|
||||
use app\common\model\CrmCustomer;
|
||||
use app\common\model\InquiryLog;
|
||||
use Webman\RedisQueue\Consumer;
|
||||
use app\common\model\User;
|
||||
use app\common\utils\FeishuMessage;
|
||||
use support\exception\BusinessException;
|
||||
use support\Log;
|
||||
|
||||
class InquiryToCrmUpdate implements Consumer
|
||||
{
|
||||
// 要消费的队列名
|
||||
public $queue = 'inquiry-to-crm-update';
|
||||
|
||||
// 连接名,对应 plugin/webman/redis-queue/redis.php 里的连接`
|
||||
public $connection = 'default';
|
||||
|
||||
// 消费
|
||||
public function consume($data)
|
||||
{
|
||||
try {
|
||||
// 获取询盘信息
|
||||
$info = InquiryLog::with(['channelInfo', 'countryInfo', 'enter'])->where('id', $data['id'])->first();
|
||||
|
||||
$corpId = 0;
|
||||
if ($info->recipient) {
|
||||
$uinfo = User::where('id', $info->recipient)->where('status', 1)->first();
|
||||
if ($uinfo) {
|
||||
$corpId = $uinfo->corp_id;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取客户信息
|
||||
$crmCustomerInfo = CrmCustomer::where('sn', $info->order_no)->first();
|
||||
if ($crmCustomerInfo) {
|
||||
$udata = [
|
||||
'corp_abbr' => $corpId,
|
||||
'user_id' => $info->recipient,
|
||||
'channel_id' => $info->channel,
|
||||
'country_id' => $info->country,
|
||||
'magnitude' => $info->quality,
|
||||
'email' => $info->email,
|
||||
'phone' => $info->tel,
|
||||
'WhatsApp' => $info->whatsapp,
|
||||
'contacts' => $info->contacts,
|
||||
'desc_collect' => $info->content,
|
||||
];
|
||||
$crmCustomerInfo->update($udata);
|
||||
} else {
|
||||
// 同步客户数据
|
||||
$crmCustomer = new CrmCustomer();
|
||||
$crmCustomer->sn = $info->order_no;
|
||||
$crmCustomer->corp_abbr = $corpId;
|
||||
$crmCustomer->user_id = $info->recipient;
|
||||
$crmCustomer->type = 1;
|
||||
$crmCustomer->channel_id = $info->channel;
|
||||
$crmCustomer->country_id = $info->country;
|
||||
$crmCustomer->magnitude = $info->quality;
|
||||
$crmCustomer->email = $info->email;
|
||||
$crmCustomer->phone = $info->tel;
|
||||
$crmCustomer->WhatsApp = $info->whatsapp;
|
||||
$crmCustomer->contacts = $info->contacts;
|
||||
$crmCustomer->desc_collect = $info->content;
|
||||
$crmCustomer->enter_user_id = $info->enter_user_id;
|
||||
// 修改同步CRM状态
|
||||
$info->sync_crm = 1;
|
||||
// 保存
|
||||
if (!$crmCustomer->save() || !$info->save()) {
|
||||
throw new BusinessException('同步失败~');
|
||||
}
|
||||
|
||||
// 发送邮件消息
|
||||
if ($uinfo && $uinfo->email) {
|
||||
$options = [
|
||||
'id' => $info->id,
|
||||
'email' => $uinfo->email
|
||||
];
|
||||
\Webman\RedisQueue\Redis::send('email-message', $options);
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $th) {
|
||||
throw $th;
|
||||
}
|
||||
}
|
||||
|
||||
// 消费失败回调
|
||||
/*
|
||||
$package = [
|
||||
'id' => 1357277951, // 消息ID
|
||||
'time' => 1709170510, // 消息时间
|
||||
'delay' => 0, // 延迟时间
|
||||
'attempts' => 2, // 消费次数
|
||||
'queue' => 'send-mail', // 队列名
|
||||
'data' => ['to' => 'tom@gmail.com', 'content' => 'hello'], // 消息内容
|
||||
'max_attempts' => 5, // 最大重试次数
|
||||
'error' => '错误信息' // 错误信息
|
||||
]
|
||||
*/
|
||||
public function onConsumeFailure(\Throwable $e, $package)
|
||||
{
|
||||
echo "consume failure\n";
|
||||
echo $e->getMessage() . "\n";
|
||||
// 无需反序列化
|
||||
var_export($package);
|
||||
}
|
||||
}
|
||||
@@ -97,6 +97,7 @@ Route::group('/admin', function () {
|
||||
Route::any('/destroy', [app\inquiry\controller\InquiryLogController::class, 'destroy']);
|
||||
Route::any('/release', [app\inquiry\controller\InquiryLogController::class, 'release']);
|
||||
Route::any('/allocation', [app\inquiry\controller\InquiryLogController::class, 'allocation']);
|
||||
Route::any('/reallocation', [app\inquiry\controller\InquiryLogController::class, 'reAllocation']);
|
||||
// 统计
|
||||
Route::group('/statistics', function () {
|
||||
Route::any('/whole', [app\inquiry\controller\StatisticsController::class, 'whole']);
|
||||
|
||||
Reference in New Issue
Block a user