header('x-forwarded-for'); var_dump($x_forwarded_for); $x_real_ip = $request->header('x-real-ip'); var_dump(__METHOD__ . 'x-real-ip: ' . $x_real_ip); $remote_addr = $request->server('remote_addr'); var_dump(__METHOD__ . 'remote_addr: ' . $remote_addr); return $x_forwarded_for ?? $x_real_ip ?? $remote_addr ?? 'unknown'; } /** * 在响应头中添加 Link 字段 * * @param ResponseInterface $response * @param string|null $urlReference * @param string $rel * @param array $params * @param bool $trimQuote * * @return ResponseInterface */ public function extendLinkToHeader( ResponseInterface $response, ?string $urlReference, string $rel = '', array $params = [], bool $trimQuote = true ): ResponseInterface { if (is_null($urlReference)) { return $response; } $params['rel'] = $rel; if (!$trimQuote) { foreach ($params as &$value) { $value = sprintf("\"%s\"", trim($value, '"')); } } $link_list = []; if ($response->hasHeader('Link')) { $link = $response->getHeaderLine('Link'); $link_list = explode(',', $link); $link_list = array_map('trim', $link_list); } $link_list[] = "<$urlReference>; " . http_build_query($params, '', '; '); return $response->withHeader( 'Link', join(',', $link_list) ); } /** * 构建 URL * * @param string $url * @param array $moreQueries 要附加的 query 参数 * @param bool $anchorQuery 开启后,query 参数将被尽量组合到锚点中,以支持 spa 路由 * @return string */ #[Pure] public function buildUrl(string $url, array $moreQueries = [], bool $anchorQuery = false): string { if (count($moreQueries) === 0) { return $url; } $url_info = parse_url($url); $result = ''; // 协议 if (isset($url_info['scheme'])) { $result .= $url_info['scheme']; } // 域名 if (isset($url_info['host'])) { if (!empty($result)) { $result .= ':'; } $result .= '//'; // 用户名 if (isset($url_info['user'])) { $result .= $url_info['user']; // 密码 if (isset($url_info['pass'])) { $result .= ':' . $url_info['pass']; } $result .= '@'; } $result .= $url_info['host']; } // 端口 if (isset($url_info['port'])) { $result .= ':' . $url_info['port']; } // 路径 $result .= $url_info['path']; // 查询参数 if (!empty($url_info['query'])) { $query_params = $url_info['query']; parse_str($query_params, $origin_params); if (!$anchorQuery) { $origin_params += $moreQueries; } $result .= '?' . http_build_query($origin_params); } else { if (!$anchorQuery) { $result .= '?' . http_build_query($moreQueries); } } // 锚点 if (!empty($url_info['fragment'])) { $fragment = $url_info['fragment']; if ($anchorQuery) { $fragment_data = parse_url($url_info['fragment']); parse_str($fragment_data['query'] ?? '', $hash_queries); $hash_queries += $moreQueries; $fragment = $fragment_data['path'] . '?' . http_build_query($hash_queries); } $result .= '#' . $fragment; } return $result; } /** * 以迭代器的形式响应 * * @param string $fullName * * @return Generator * @throws Exception */ public function csvReaderGenerator(string $fullName): Generator { if (!is_file($fullName)) { throw new Exception('指定的 csv 文件不存在'); } $file = fopen($fullName, 'r'); while ($data = fgetcsv($file)) { //每次读取CSV里面的一行内容 yield $data; } fclose($file); } /** * 以数组形式响应 * * @param string $fullName * @param array $headers * @param int|null $batchNumber * @return array * @throws Exception */ public function csvReader(string $fullName, array $headers = [], ?int $batchNumber = null): array { if (!is_file($fullName)) { throw new Exception($fullName . ' 不存在'); } $file = fopen($fullName, 'r'); $result = []; $row_number = 0; while ($data = fgetcsv($file)) { //每次读取CSV里面的一行内容 $row_number++; $data = empty($headers) ? $data : array_combine($headers, $data); $data = array_map(function ($row) { return is_string($row) ? trim($row) : $row; }, $data); if (!is_null($batchNumber)) { $result[intval($row_number / $batchNumber)][] = $data; } else { $result[] = $data; } } fclose($file); return $result; } /** * (level倒序)快速无极分类(时间复杂度 O(n),空间复杂度 O(1)) * 条件:数组索引是数据parent_id对应的id,只支持level倒序 * * @param array $list * @param int $level * @param string $parentIdName * @param string $childrenName * @param Closure|null $filterCallback * * @return void */ #[NoReturn] public function unlimitedSubCategoriesQuicklyWithLevel( array &$list, int $level = 1, string $parentIdName = 'parent_id', string $childrenName = 'children', ?Closure $filterCallback = null ): void { foreach ($list as $i => &$item) { if (!isset($item[$parentIdName])) { break; } unset($list[$i]); $item = isset($filterCallback) ? $filterCallback($item) : $item; //判定非顶级的pid是否存在,如果存在,则再pid所在的数组下面加入一个字段items,来将本身存进去 if (isset($list[$item[$parentIdName]])) { $list[$item[$parentIdName]][$childrenName][] = $item; continue; } //取出顶级 if ($item['level'] === $level) { $list[$childrenName][] = $item; } } $list = $list[$childrenName]; } /** * 将内存/硬盘大小转化为带单位大小 * * @param float|int|string $size 空间大小(单位:Byte) * @param bool $unitToUpper 单位转换为大写(默认 false) * @param int $precision 小数点后保留的位数(默认 2) * * @return array{'size': float, 'unit': string} */ #[Pure] public function convertStorageSize( float|int|string $size, bool $unitToUpper = false, int $precision = 2 ): array { $unit = ['b', 'kb', 'mb', 'gb', 'tb', 'pb']; if ($size > 0) { $i = floor(log($size, 1024)); $size = round($size / pow(1024, $i), $precision); } else { $i = 0; $size = 0; } return [ 'size' => $size, 'unit' => $unitToUpper ? strtoupper($unit[$i]) : $unit[$i], ]; } /** * 将时长转换为带单位时间 * * @param int|float $duration 时长(单位:ms) * @param bool $unitToUpper * @param bool $format * @param integer $precision * * @return array{'duration': float, 'unit': string} */ #[Pure] public function convertDuration( int|float $duration, bool $unitToUpper = false, bool $format = true, int $precision = 3 ): array { $unit = ['ms', 's']; if ($duration > 1) { $i = floor(log($duration, 1000)); $duration = round($duration / pow(1000, $i), $precision); } elseif ($duration < 1) { $i = 0; } else { $i = 0; $duration = 0; } return [ 'duration' => $format ? number_format($duration, $precision) : $duration, 'unit' => $unitToUpper ? strtoupper($unit[$i]) : $unit[$i], ]; } }