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
6.0 KiB
PHP
146 lines
6.0 KiB
PHP
<?php
|
|
|
|
namespace Qcloud\Cos;
|
|
|
|
use GuzzleHttp\Pool;
|
|
|
|
class RangeDownload {
|
|
const DEFAULT_PART_SIZE = 52428800;
|
|
|
|
private $client;
|
|
private $options;
|
|
private $partSize;
|
|
private $parts;
|
|
private $progress;
|
|
private $totalSize;
|
|
private $resumableJson;
|
|
|
|
public function __construct( $client, $contentLength, $saveAs, $options = array() ) {
|
|
$this->client = $client;
|
|
$this->options = $options;
|
|
$this->partSize = isset( $options['PartSize'] ) ? $options['PartSize'] : self::DEFAULT_PART_SIZE;
|
|
$this->concurrency = isset( $options['Concurrency'] ) ? $options['Concurrency'] : 10;
|
|
$this->progress = isset( $options['Progress'] ) ? $options['Progress'] : function( $totalSize, $downloadedSize ) {
|
|
}
|
|
;
|
|
$this->parts = [];
|
|
$this->partNumberList = [];
|
|
$this->downloadedSize = 0;
|
|
$this->totalSize = $contentLength;
|
|
$this->saveAs = $saveAs;
|
|
$this->resumableJson = [];
|
|
$this->resumableJson = isset( $options['ResumableJson'] ) ? $options['ResumableJson'] : [];
|
|
unset( $options['ResumableJson'] );
|
|
$this->resumableTaskFile = isset( $options['ResumableTaskFile'] ) ? $options['ResumableTaskFile'] : $saveAs . '.cosresumabletask';
|
|
$this->resumableDownload = isset( $options['ResumableDownload'] ) ? $options['ResumableDownload'] : false;
|
|
}
|
|
|
|
public function performdownloading() {
|
|
if ( $this->resumableDownload ) {
|
|
try {
|
|
if ( file_exists( $this->resumableTaskFile ) ) {
|
|
$origin_content = file_get_contents( $this->resumableTaskFile );
|
|
$this->resumableJsonLocal = json_decode( $origin_content, true );
|
|
if ( $this->resumableJsonLocal == null ) {
|
|
$this->resumableJsonLocal = [];
|
|
} else if ( $this->resumableJsonLocal['LastModified'] != $this->resumableJson['LastModified'] ||
|
|
$this->resumableJsonLocal['ContentLength'] != $this->resumableJson['ContentLength'] ||
|
|
$this->resumableJsonLocal['ETag'] != $this->resumableJson['ETag'] ||
|
|
$this->resumableJsonLocal['Crc64ecma'] != $this->resumableJson['Crc64ecma'] ) {
|
|
$this->resumableDownload = false;
|
|
}
|
|
}
|
|
} catch ( \Exception $e ) {
|
|
$this->resumableDownload = false;
|
|
}
|
|
}
|
|
try {
|
|
if ($this->resumableDownload) {
|
|
$this->fp = fopen( $this->saveAs, 'r+' );
|
|
} else {
|
|
$this->fp = fopen( $this->saveAs, 'wb' );
|
|
}
|
|
$rt = $this->donwloadParts();
|
|
$this->resumableJson['DownloadedBlocks'] = [];
|
|
if (file_exists( $this->resumableTaskFile )) {
|
|
unlink($this->resumableTaskFile);
|
|
}
|
|
} catch ( \Exception $e ) {
|
|
$this->fp_resume = fopen( $this->resumableTaskFile, 'wb' );
|
|
fwrite( $this->fp_resume, json_encode( $this->resumableJson ) );
|
|
fclose( $this->fp_resume );
|
|
throw ( $e );
|
|
}
|
|
finally {
|
|
fclose( $this->fp );
|
|
}
|
|
return $rt;
|
|
}
|
|
|
|
public function donwloadParts() {
|
|
$uploadRequests = function () {
|
|
$index = 1;
|
|
$partSize = 0;
|
|
for ( $offset = 0; $offset < $this->totalSize; ) {
|
|
$partSize = $this->partSize;
|
|
if ( $offset + $this->partSize >= $this->totalSize ) {
|
|
$partSize = $this->totalSize - $offset;
|
|
}
|
|
$this->parts[$index]['PartSize'] = $partSize;
|
|
$this->parts[$index]['Offset'] = $offset;
|
|
$begin = $offset;
|
|
$end = $offset + $partSize - 1;
|
|
if ( !( $this->resumableDownload &&
|
|
isset( $this->resumableJsonLocal['DownloadedBlocks'] ) &&
|
|
in_array( ['from' => $begin, 'to' => $end], $this->resumableJsonLocal['DownloadedBlocks'] ) ) ) {
|
|
$params = array(
|
|
'Bucket' => $this->options['Bucket'],
|
|
'Key' => $this->options['Key'],
|
|
'Range' => sprintf( 'bytes=%d-%d', $begin, $end )
|
|
);
|
|
$command = $this->client->getCommand( 'getObject', $params );
|
|
$request = $this->client->commandToRequestTransformer( $command );
|
|
$index += 1;
|
|
yield $request;
|
|
} else {
|
|
$this->resumableJson['DownloadedBlocks'][] = ['from' => $begin, 'to' => $end];
|
|
$this->downloadedSize += $partSize;
|
|
call_user_func_array( $this->progress, [$this->totalSize, $this->downloadedSize] );
|
|
}
|
|
$offset += $partSize;
|
|
}
|
|
|
|
}
|
|
;
|
|
|
|
$pool = new Pool( $this->client->httpClient, $uploadRequests(), [
|
|
'concurrency' => $this->concurrency,
|
|
'fulfilled' => function ( $response, $index ) {
|
|
$index = $index + 1;
|
|
$stream = $response->getBody();
|
|
$offset = $this->parts[$index]['Offset'];
|
|
$partsize = 8192;
|
|
$begin = $offset;
|
|
fseek( $this->fp, $offset );
|
|
while ( !$stream->eof() ) {
|
|
$output = $stream->read( $partsize );
|
|
$writeLen = fwrite( $this->fp, $output );
|
|
$offset += $writeLen;
|
|
}
|
|
$end = $offset - 1;
|
|
$this->resumableJson['DownloadedBlocks'][] = ['from' => $begin, 'to' => $end];
|
|
$partSize = $this->parts[$index]['PartSize'];
|
|
$this->downloadedSize += $partSize;
|
|
call_user_func_array( $this->progress, [$this->totalSize, $this->downloadedSize] );
|
|
}
|
|
,
|
|
'rejected' => function ( $reason, $index ) {
|
|
throw( $reason );
|
|
}
|
|
] );
|
|
$promise = $pool->promise();
|
|
$promise->wait();
|
|
}
|
|
|
|
}
|