all(); $token = $request->header('d-token', ''); if (!$token) { $token = getProp($params, 'd_token'); if (!$token) Utils::throwError(ErrorConst::NOT_LOGIN); } $config = config('param_ownership', []); if (empty($config['enabled'])) { return $next($request); } // 平台角色需要跨公司操作,跳过归属校验 if (in_array(Site::getRole(), getProp($config, 'skip_roles', []), true)) { return $next($request); } $cpid = Site::getCpid(); if (!$cpid) { Utils::throwError(ErrorConst::NOT_ACCESS); } // 内部公司白名单:灰度期间先放过自己人,避免误拦 if (in_array($cpid, getProp($config, 'skip_cpids', []), true)) { return $next($request); } $ownerColumn = getProp($config, 'owner_column', 'cpid'); $path = $request->path(); $routeMap = getProp($config, 'routes', []); // 汇总全部校验项:普通 / 批量 / 链式 / 链式批量 / 按路由覆盖 $paramMap = getProp($config, 'params', []); foreach (getProp($config, 'array_params', []) as $key => $define) { $define['batch'] = true; $paramMap[$key] = $define; } foreach (getProp($config, 'linked_params', []) as $key => $define) { $paramMap[$key] = $define; } foreach (getProp($config, 'linked_array_params', []) as $key => $define) { $define['batch'] = true; $paramMap[$key] = $define; } if (isset($routeMap[$path]) && is_array($routeMap[$path])) { foreach ($routeMap[$path] as $key => $define) { $paramMap[$key] = $define; } } foreach ($paramMap as $key => $define) { $this->checkOwnership($params, $key, $define, $cpid, $ownerColumn); } return $next($request); } /** * 校验参数所指向资源的归属公司 * * 支持两种模式: * - 直接校验:目标表自带归属列(cpid); * - 链式校验:目标表无归属列,先取记录再用外键到父表校验。 * * @param array $params 请求参数 * @param string $key 参数名 * @param array $define 配置(table / key / type / batch / via) * @param int $cpid 当前公司ID * @param string $ownerColumn 归属列名 * @throws ApiException */ private function checkOwnership(array $params, string $key, array $define, int $cpid, string $ownerColumn): void { $table = getProp($define, 'table'); if (!$table || !array_key_exists($key, $params)) { return; } $keyColumn = getProp($define, 'key', 'id'); $type = getProp($define, 'type', 'int'); $batch = (bool)getProp($define, 'batch', false); $via = getProp($define, 'via', null); foreach ($this->toIdList($params[$key], $batch, $type) as $id) { $owned = $via ? $this->ownedVia($table, $keyColumn, $id, $via, $cpid, $ownerColumn) : DB::table($table)->where($keyColumn, $id)->where($ownerColumn, $cpid)->exists(); if (!$owned) { dLog('checkCompany')->info('资源归属校验未通过', [ 'path' => request()->path(), 'param' => $key, 'id' => $id, 'table' => $table, 'cpid' => $cpid, 'uid' => Site::getUid(), ]); Utils::throwError(ErrorConst::NOT_ACCESS); } } } /** * 链式归属校验:按主键取记录,再用记录中的外键去父表校验归属 * * @param string $table 目标表 * @param string $keyColumn 目标表主键列 * @param mixed $id 主键值 * @param array $via ['local' => 外键列, 'table' => 父表, 'key' => 父表主键列] * @param int $cpid 当前公司ID * @param string $ownerColumn 归属列名 * @return bool */ private function ownedVia(string $table, string $keyColumn, $id, array $via, int $cpid, string $ownerColumn): bool { $local = getProp($via, 'local'); $refTable = getProp($via, 'table'); $refKey = getProp($via, 'key', 'id'); if (!$local || !$refTable) { return false; } // 资源不存在时按无权限处理(fail-closed) $row = DB::table($table)->where($keyColumn, $id)->first([$local]); if (!$row) { return false; } $refValue = getProp($row, $local); if ($refValue === null || $refValue === '') { return false; } return DB::table($refTable)->where($refKey, $refValue)->where($ownerColumn, $cpid)->exists(); } /** * 统一转换为 ID 列表(兼容单值、数组、逗号分隔、JSON 数组) * * @param mixed $value * @param bool $batch 是否允许逗号分隔 / JSON 数组 * @param string $type 主键类型:int(默认)/ string * @return array */ private function toIdList($value, bool $batch, string $type = 'int'): array { if (is_array($value)) { $items = $value; } elseif ($batch && is_string($value)) { $decoded = json_decode($value, true); $items = is_array($decoded) ? $decoded : explode(',', $value); } else { $items = [$value]; } $ids = []; foreach ($items as $item) { if (is_array($item)) { $ids = array_merge($ids, $this->toIdList($item, $batch, $type)); continue; } if ($type === 'string') { // 字符串主键(如 mp_episode_segments.segment_id)需保留原值 $val = trim((string)$item); if ($val !== '') { $ids[] = $val; } continue; } $id = (int)$item; if ($id > 0) { $ids[] = $id; } } return array_values(array_unique($ids)); } }