string, 'params' => array] * @throws \Exception */ public static function buildIsolationClause($permission, $endpoint, $customerName = null) { // 1. 检查接口权限 if ($endpoint === 'chuku' && !$permission->canAccess('chuku')) { throw new \Exception('该 API Key 未被授权访问销售数据接口'); } if ($endpoint === 'ruku' && !$permission->canAccess('ruku')) { throw new \Exception('该 API Key 未被授权访问入库数据接口'); } // 2. 全量数据权限:检查是否传入供方名称过滤 if ($permission->isFullData()) { Log::debug("API Key {$permission->api_key_id} 拥有全量数据权限"); if (!empty($customerName)) { $filterField = 'k.[供方全称]'; $sqlFragment = " AND {$filterField} LIKE :supplier_name"; $params = ['supplier_name' => '%' . $customerName . '%']; Log::debug("全量数据模式下按供方名称过滤: {$filterField} LIKE %{$customerName}%"); return ['sql' => $sqlFragment, 'params' => $params]; } return ['sql' => '', 'params' => []]; } // 3. 根据数据模式确定过滤方式 $dataMode = $permission->data_mode; if ($dataMode === Permission::MODE_FULL) { return ['sql' => '', 'params' => []]; } // 4. 使用数据库中已配置的过滤规则 if (!empty($permission->filter_field) && !empty($permission->filter_values)) { return self::buildCustomFilter($permission, $endpoint); } // 5. 自动过滤:根据接口类型自动选择过滤字段和值 $apiKey = $permission->apiKey; $client = $apiKey->client; if (!$client) { throw new \Exception('API Key 未关联客户'); } return self::buildAutoFilter($permission, $endpoint, $client); } /** * 构建自定义过滤规则 */ private static function buildCustomFilter($permission, $endpoint) { $filterField = $permission->filter_field; $filterValues = $permission->filter_values; if (empty($filterField) || empty($filterValues)) { Log::warning("API Key {$permission->api_key_id} 使用自定义模式,但未配置过滤规则,拒绝访问"); throw new \Exception('权限配置不完整:需要手动配置过滤字段和过滤值'); } if (!is_array($filterValues)) { $filterValues = json_decode($filterValues, true); } if ($filterValues === null || !is_array($filterValues) || empty($filterValues)) { Log::warning("API Key {$permission->api_key_id} 过滤值为空或无效"); throw new \Exception('过滤值列表为空或无效,请联系管理员'); } if (!self::validateFilterField($filterField, $endpoint)) { throw new \Exception('非法的过滤字段'); } $prefixedField = self::prefixFilterField($filterField, $endpoint); $placeholders = []; $params = []; foreach ($filterValues as $index => $value) { $placeholder = ':iso_' . $index; $placeholders[] = $placeholder; $params['iso_' . $index] = $value; } $sqlFragment = " AND {$prefixedField} IN (" . implode(',', $placeholders) . ')'; Log::debug("数据隔离: {$prefixedField} IN " . json_encode($filterValues)); return ['sql' => $sqlFragment, 'params' => $params]; } /** * 为过滤字段添加表别名前缀 */ private static function prefixFilterField($field, $endpoint) { if (strpos($field, '[') === 0) { $field = substr($field, 1, -1); } $prefixMap = [ '供方全称' => 'k.[供方全称]', '供方序号' => 'k.[供方序号]', '产品名称' => 'p.[产品名称]', '产品序号' => 'p.[产品序号]', ]; if ($endpoint === 'chuku') { $prefixMap['销方全称'] = 'f.[销方全称]'; $prefixMap['销方序号'] = 'f.[销方序号]'; } return $prefixMap[$field] ?? "[{$field}]"; } /** * 构建自动过滤规则 */ private static function buildAutoFilter($permission, $endpoint, $client) { $filterField = 'k.[供方全称]'; $filterValue = $client->name; $sqlFragment = " AND {$filterField} = :iso_0"; $params = ['iso_0' => $filterValue]; Log::debug("数据隔离: {$filterField} = {$filterValue}"); return ['sql' => $sqlFragment, 'params' => $params]; } /** * 验证过滤字段是否在白名单内(防注入) */ private static function validateFilterField($field, $endpoint) { $allowedFields = [ '供方全称', '供方序号', '产品名称', '产品序号', 'customer_name', 'supplier_name', 'customer_code', 'supplier_code', ]; if ($endpoint === 'chuku') { $allowedFields = array_merge($allowedFields, ['销方全称', '销方序号']); } return in_array($field, $allowedFields); } }