Phase1 后端核心:
- 新增 fsgx_v1.sql 迁移脚本(is_queue_goods/frozen_points/available_points/no_assess)
- SystemConfigServices 返佣设置扩展(周期人数/分档比例/范围/时机)
- StoreOrderCreateServices 周期循环佣金计算
- StoreOrderTakeServices 佣金发放后同步冻结积分
- StoreProductServices/StoreProduct 保存 is_queue_goods
Phase2 后端接口:
- GET /api/hjf/brokerage/progress 佣金周期进度
- GET /api/hjf/assets/overview 资产总览
- HjfPointsServices 每日 frozen_points 0.4‰ 释放定时任务
- PUT /adminapi/hjf/member/{uid}/no_assess 不考核接口
- GET /adminapi/hjf/points/release_log 积分日志接口
Phase3 前端清理:
- hjfCustom.js 路由精简(仅保留 points/log)
- hjfQueue.js/hjfMember.js API 清理/重定向至 CRMEB 原生接口
- pages.json 公排→推荐佣金/佣金记录/佣金规则
Phase4-5 前端改造:
- queue/status.vue 推荐佣金进度页整体重写
- 商品详情/订单确认/支付结果页文案与逻辑改造
- 个人中心/资产页/引导页/规则页文案改造
- HjfQueueProgress/HjfRefundNotice/HjfAssetCard 组件改造
- 推广中心嵌入佣金进度摘要
- hjfMockData.js 全量更新(公排字段→佣金字段)
Phase6 Admin 增强:
- 用户列表新增 frozen_points/available_points 列及不考核操作按钮
- hjfPoints.js USE_MOCK=false 对接真实积分日志接口
Phase7 配置文档:
- docs/fsgx-phase7-config-checklist.md 后台配置与全链路验收清单
Made-with: Cursor
146 lines
5.6 KiB
PHP
146 lines
5.6 KiB
PHP
<?php
|
|
|
|
/*
|
|
* This file is part of the Symfony package.
|
|
*
|
|
* (c) Fabien Potencier <fabien@symfony.com>
|
|
*
|
|
* For the full copyright and license information, please view the LICENSE
|
|
* file that was distributed with this source code.
|
|
*/
|
|
|
|
namespace Symfony\Component\HttpClient\Internal;
|
|
|
|
use Psr\Log\LoggerInterface;
|
|
use Symfony\Component\HttpClient\Response\CurlResponse;
|
|
|
|
/**
|
|
* Internal representation of the cURL client's state.
|
|
*
|
|
* @author Alexander M. Turek <me@derrabus.de>
|
|
*
|
|
* @internal
|
|
*/
|
|
final class CurlClientState extends ClientState
|
|
{
|
|
public ?\CurlMultiHandle $handle;
|
|
public ?\CurlShareHandle $share;
|
|
|
|
/** @var PushedResponse[] */
|
|
public array $pushedResponses = [];
|
|
public DnsCache $dnsCache;
|
|
/** @var float[] */
|
|
public array $pauseExpiries = [];
|
|
public int $execCounter = \PHP_INT_MIN;
|
|
public ?LoggerInterface $logger = null;
|
|
|
|
public static array $curlVersion;
|
|
|
|
public function __construct(int $maxHostConnections, int $maxPendingPushes)
|
|
{
|
|
self::$curlVersion = self::$curlVersion ?? curl_version();
|
|
|
|
$this->handle = curl_multi_init();
|
|
$this->dnsCache = new DnsCache();
|
|
$this->reset();
|
|
|
|
// Don't enable HTTP/1.1 pipelining: it forces responses to be sent in order
|
|
if (\defined('CURLPIPE_MULTIPLEX')) {
|
|
curl_multi_setopt($this->handle, \CURLMOPT_PIPELINING, \CURLPIPE_MULTIPLEX);
|
|
}
|
|
if (\defined('CURLMOPT_MAX_HOST_CONNECTIONS')) {
|
|
$maxHostConnections = curl_multi_setopt($this->handle, \CURLMOPT_MAX_HOST_CONNECTIONS, 0 < $maxHostConnections ? $maxHostConnections : \PHP_INT_MAX) ? 0 : $maxHostConnections;
|
|
}
|
|
if (\defined('CURLMOPT_MAXCONNECTS') && 0 < $maxHostConnections) {
|
|
curl_multi_setopt($this->handle, \CURLMOPT_MAXCONNECTS, $maxHostConnections);
|
|
}
|
|
|
|
// Skip configuring HTTP/2 push when it's unsupported or buggy, see https://bugs.php.net/77535
|
|
if (0 >= $maxPendingPushes) {
|
|
return;
|
|
}
|
|
|
|
// HTTP/2 push crashes before curl 7.61
|
|
if (!\defined('CURLMOPT_PUSHFUNCTION') || 0x073D00 > self::$curlVersion['version_number'] || !(\CURL_VERSION_HTTP2 & self::$curlVersion['features'])) {
|
|
return;
|
|
}
|
|
|
|
// Clone to prevent a circular reference
|
|
$multi = clone $this;
|
|
$multi->handle = null;
|
|
$multi->share = null;
|
|
$multi->pushedResponses = &$this->pushedResponses;
|
|
$multi->logger = &$this->logger;
|
|
$multi->handlesActivity = &$this->handlesActivity;
|
|
$multi->openHandles = &$this->openHandles;
|
|
|
|
curl_multi_setopt($this->handle, \CURLMOPT_PUSHFUNCTION, static function ($parent, $pushed, array $requestHeaders) use ($multi, $maxPendingPushes) {
|
|
return $multi->handlePush($parent, $pushed, $requestHeaders, $maxPendingPushes);
|
|
});
|
|
}
|
|
|
|
public function reset()
|
|
{
|
|
foreach ($this->pushedResponses as $url => $response) {
|
|
$this->logger && $this->logger->debug(sprintf('Unused pushed response: "%s"', $url));
|
|
curl_multi_remove_handle($this->handle, $response->handle);
|
|
curl_close($response->handle);
|
|
}
|
|
|
|
$this->pushedResponses = [];
|
|
$this->dnsCache->evictions = $this->dnsCache->evictions ?: $this->dnsCache->removals;
|
|
$this->dnsCache->removals = $this->dnsCache->hostnames = [];
|
|
|
|
$this->share = curl_share_init();
|
|
|
|
curl_share_setopt($this->share, \CURLSHOPT_SHARE, \CURL_LOCK_DATA_DNS);
|
|
curl_share_setopt($this->share, \CURLSHOPT_SHARE, \CURL_LOCK_DATA_SSL_SESSION);
|
|
|
|
if (\defined('CURL_LOCK_DATA_CONNECT') && \PHP_VERSION_ID >= 80000) {
|
|
curl_share_setopt($this->share, \CURLSHOPT_SHARE, \CURL_LOCK_DATA_CONNECT);
|
|
}
|
|
}
|
|
|
|
private function handlePush($parent, $pushed, array $requestHeaders, int $maxPendingPushes): int
|
|
{
|
|
$headers = [];
|
|
$origin = curl_getinfo($parent, \CURLINFO_EFFECTIVE_URL);
|
|
|
|
foreach ($requestHeaders as $h) {
|
|
if (false !== $i = strpos($h, ':', 1)) {
|
|
$headers[substr($h, 0, $i)][] = substr($h, 1 + $i);
|
|
}
|
|
}
|
|
|
|
if (!isset($headers[':method']) || !isset($headers[':scheme']) || !isset($headers[':authority']) || !isset($headers[':path'])) {
|
|
$this->logger && $this->logger->debug(sprintf('Rejecting pushed response from "%s": pushed headers are invalid', $origin));
|
|
|
|
return \CURL_PUSH_DENY;
|
|
}
|
|
|
|
$url = $headers[':scheme'][0].'://'.$headers[':authority'][0];
|
|
|
|
// curl before 7.65 doesn't validate the pushed ":authority" header,
|
|
// but this is a MUST in the HTTP/2 RFC; let's restrict pushes to the original host,
|
|
// ignoring domains mentioned as alt-name in the certificate for now (same as curl).
|
|
if (!str_starts_with($origin, $url.'/')) {
|
|
$this->logger && $this->logger->debug(sprintf('Rejecting pushed response from "%s": server is not authoritative for "%s"', $origin, $url));
|
|
|
|
return \CURL_PUSH_DENY;
|
|
}
|
|
|
|
if ($maxPendingPushes <= \count($this->pushedResponses)) {
|
|
$fifoUrl = key($this->pushedResponses);
|
|
unset($this->pushedResponses[$fifoUrl]);
|
|
$this->logger && $this->logger->debug(sprintf('Evicting oldest pushed response: "%s"', $fifoUrl));
|
|
}
|
|
|
|
$url .= $headers[':path'][0];
|
|
$this->logger && $this->logger->debug(sprintf('Queueing pushed response: "%s"', $url));
|
|
|
|
$this->pushedResponses[$url] = new PushedResponse(new CurlResponse($this, $pushed), $headers, $this->openHandles[(int) $parent][1] ?? [], $pushed);
|
|
|
|
return \CURL_PUSH_OK;
|
|
}
|
|
}
|