diff --git a/app/common/traits/DataPermission.php b/app/common/traits/DataPermission.php new file mode 100644 index 0000000..fa5e917 --- /dev/null +++ b/app/common/traits/DataPermission.php @@ -0,0 +1,425 @@ +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); + } +} \ No newline at end of file diff --git a/app/common/traits/crud/CrudServiceTrait.php b/app/common/traits/crud/CrudServiceTrait.php index 5ad6dbc..344756d 100644 --- a/app/common/traits/crud/CrudServiceTrait.php +++ b/app/common/traits/crud/CrudServiceTrait.php @@ -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); } /** 删除 */ diff --git a/app/common/utils/CrudUtil.php b/app/common/utils/CrudUtil.php index dec779b..836e39e 100644 --- a/app/common/utils/CrudUtil.php +++ b/app/common/utils/CrudUtil.php @@ -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}; - - // 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); - } + + 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); } - - // 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)) { + } + + /** + * 应用数据权限到创建/更新操作 + * + * @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]) && !in_array($newData[$field], $scope)) { + + // 禁止变更归属字段 + 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); + } } \ No newline at end of file diff --git a/app/common/utils/CrudUtil_bak.php b/app/common/utils/CrudUtil_bak.php new file mode 100644 index 0000000..10617e2 --- /dev/null +++ b/app/common/utils/CrudUtil_bak.php @@ -0,0 +1,843 @@ + 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); + } + } + } +} \ No newline at end of file diff --git a/app/common/validate/InquiryLogValidate.php b/app/common/validate/InquiryLogValidate.php index 2a869bb..e4cc026 100644 --- a/app/common/validate/InquiryLogValidate.php +++ b/app/common/validate/InquiryLogValidate.php @@ -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' diff --git a/app/controller/IndexController.php b/app/controller/IndexController.php index 6dc874b..77c5622 100644 --- a/app/controller/IndexController.php +++ b/app/controller/IndexController.php @@ -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; } } \ No newline at end of file diff --git a/app/functions.php b/app/functions.php index f69f082..59fdd24 100644 --- a/app/functions.php +++ b/app/functions.php @@ -220,4 +220,52 @@ 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('' . $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); + } } \ No newline at end of file diff --git a/app/inquiry/controller/InquiryLogController.php b/app/inquiry/controller/InquiryLogController.php index 89af6b0..833be58 100644 --- a/app/inquiry/controller/InquiryLogController.php +++ b/app/inquiry/controller/InquiryLogController.php @@ -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) // { diff --git a/app/inquiry/service/InquiryLogService.php b/app/inquiry/service/InquiryLogService.php index 667e41d..10367db 100644 --- a/app/inquiry/service/InquiryLogService.php +++ b/app/inquiry/service/InquiryLogService.php @@ -32,28 +32,7 @@ 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 diff --git a/app/process/Emails.php b/app/process/Emails.php index f9641ea..6ff6374 100644 --- a/app/process/Emails.php +++ b/app/process/Emails.php @@ -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(); + // }); } } \ No newline at end of file diff --git a/app/queue/redis/InquiryToCrmUpdate.php b/app/queue/redis/InquiryToCrmUpdate.php new file mode 100644 index 0000000..c4e6d02 --- /dev/null +++ b/app/queue/redis/InquiryToCrmUpdate.php @@ -0,0 +1,110 @@ +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); + } +} \ No newline at end of file diff --git a/config/route.php b/config/route.php index 2ae131d..7ad0f19 100644 --- a/config/route.php +++ b/config/route.php @@ -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']);