基于企鹅号的视频文件分片上传的实现流程,包含队列、文件切片、while 循环等

1、数据库结构的设计,一张资源表,一张企鹅号的视频文件分片上传表,一张企鹅号的事务表,结构如下:

28、asset:资源  Asset
id 			           主键
channel_id 			   渠道ID
channel_code	       渠道代码,qq:企鹅号;wx:微信公众帐号
channel_type_id 	   渠道的类型ID
channel_type_code      渠道的类型代码,qq_cw:企鹅号的内容网站应用;qq_tp:企鹅号的第三方服务平台应用;wx:微信公众帐号应用
source                 来源,xContent:内容库;vms:视频管理系统;cms:内容管理系统;spider:自媒体
type                   资源文件的类型,image:图片;video:视频
absolute_url           来源的资源文件的绝对URL
relative_path          渠道发布的资源文件的相对路径
size                   文件大小,单位(字节)
task_id                任务ID
channel_article_id     渠道的文章ID
status                 状态,0:禁用;1:启用
is_deleted             是否被删除,0:否;1:是
created_at             创建时间
updated_at 	           更新时间
deleted_at             删除时间
29、qq_video_multipart_upload:企鹅号的视频文件分片上传  QqCwVideoMultipartUpload
id 			                 主键
asset_id                     资源ID
qq_app_task_id 		         企鹅号的应用的任务ID
qq_app_id                    企鹅号的应用ID
qq_app_type                  企鹅号的应用类型,cw:内容网站应用;tp:第三方服务平台应用
size                         视频文件大小,单位(字节)
md5                          视频文件MD5值
sha                          视频文件SHA-1值
transaction_id               上传的唯一事务ID
mediatrunk                   视频 mediatrunk 文件
start_offset                 分片的起始位置(从0开始计数)
end_offset                   分片的结束位置
vid                          视频文件唯一标示ID
status                       状态,0:禁用;1:待上传;2:上传中;3:上传中(已失败);4:已上传
is_deleted                   是否被删除,0:否;1:是
created_at                   创建时间
updated_at 	                 更新时间
deleted_at                   删除时间
30、qq_transaction:企鹅号的事务  QqTransaction
id 			                        主键
group_id                            租户ID
qq_app_task_id 		                企鹅号的应用的任务ID
qq_app_id                           企鹅号的应用ID
qq_app_type                         企鹅号的应用类型,cw:内容网站应用;tp:第三方服务平台应用
qq_article_id                       企鹅号的文章ID
qq_video_multipart_upload_id        企鹅号的应用的视频文件分片上传ID
transaction_id                      事务ID
type                                类型,1:文章;2:视频
transaction_ctime                   事务创建时间
ext_err                             扩展的错误
transaction_err_msg                 事务的错误信息
article_abstract                    文章摘要
article_type                        文章类型,取值:普通文章,图文文章,视频文章,直播文章,RTMP直播文章
article_type_code                   文章类型代码,normal:文章;multivideos:视频;images:组图;live:直播
article_url                         文章快报链接
article_imgurl                      文章封面图
article_title                       文章标题
article_pub_flag                    文章发布状态,取值:未发布,发布成功,审核中
article_pub_time                    文章发布时间
article_video_title                 视频文章标题
article_video_desc                  视频文章描述
article_video_type                  视频文章类型,视频
article_video_vid                   视频文章的视频唯一ID
task_id                             任务ID
status                              状态,0:禁用;1:成功;2:失败;3:处理中
is_deleted                          是否被删除,0:否;1:是
created_at                          创建时间
updated_at 	                        更新时间
deleted_at                          删除时间

2、资源的上传是基于队列实现的,因此会先将资源数据存储至资源表,进而入上传资源的队列,再执行上传资源的作业

3、执行 1 次接口请求,此时资源表已经存在资源的相应数据,且入复制资源的队列

PS E:/wwwroot/channel-pub-api> ./yii copy-asset-queue/info --color=0
Jobs
- waiting: 1
- delayed: 0
- reserved: 0
- done: 0

4、执行复制资源队列中的任务命令,会复制相应的资源,且将资源的相对路径存储至资源表中

PS E:/wwwroot/channel-pub-api> ./yii copy-asset-queue/run --verbose=1 --isolate=1 --color=0
2018-11-15 10:05:56 [pid: 23112] - Worker is started
2018-11-15 10:05:57 [1] common/jobs/CopyAssetJob (attempt: 1, pid: 23112) - Started
2018-11-15 10:06:08 [1] common/jobs/CopyAssetJob (attempt: 1, pid: 23112) - Done (11.394 s)
2018-11-15 10:06:08 [pid: 23112] - Worker is stopped (0:00:12)

5、复制资源队列中的任务执行成功后,会入上传资源的队列

PS E:/wwwroot/channel-pub-api> ./yii upload-asset-queue/info --color=0
Jobs
- waiting: 1
- delayed: 0
- reserved: 0
- done: 0

6、上传资源的代码分为 2 个部分,一为文件切片,需要将视频资源文件切片为100M大小的小文件,/channel-pub-api/common/services/AssetService.php

    /**
     * 文件切片
     * @param string $fileAbsolutePath 需要切片的文件的绝对路径
     * 格式如下:E:/wwwroot/channel-pub-api/storage/spider/videos/切片文件.mp4
     *
     * @param int $size 104857600,单位为字节
     *
     * 生成文件列表如下:
     * E:/wwwroot/channel-pub-api/storage/spider/videos/切片文件_0.mp4
     * E:/wwwroot/channel-pub-api/storage/spider/videos/切片文件_1.mp4
     */
    public static function cut($fileAbsolutePath, $size)
    {
        // 获取需要切片的文件的路径信息
        $pathInfo = pathinfo($fileAbsolutePath);
        $i = 0;
        $handle = fopen($fileAbsolutePath, "rb");
        while (!feof($handle)) {
            $cutHandle = fopen($pathInfo['dirname'] . '/' . $pathInfo['filename'] . '_' . $i . '.' . $pathInfo['extension'], "wb");
            fwrite($cutHandle, fread($handle, $size));
            fclose($cutHandle);
            unset($cutHandle);
            $i++;
        }
        fclose($handle);
    }

7、HTTP请求,企鹅号的内容网站应用的视频文件分片上传,/channel-pub-api/common/logics/http/qq_api/Video.php

<?php
/**
 * Created by PhpStorm.
 * User: Qiang Wang
 * Date: 2018/10/26
 * Time: 15:33
 */

namespace common/logics/http/qq_api;

use Yii;
use yii/httpclient/Client;
use yii/web/ServerErrorHttpException;

/**
 * 企鹅号接口的视频
 *
 * @author Qiang Wang <shuijingwanwq@163.com>
 * @since 1.0
 */
class Video extends Model
{
    const CUT_SIZE = 104857600; //视频分片上传的切片大小:100M

    /**
     * HTTP请求,申请企鹅号的内容网站应用的视频文件分片上传的唯一事务ID
     *
     * @param array $data 数据
     * 格式如下:
     * [
     *     'accessToken' => 'KCK0TIV8NDY44ONAEFI2QW', // 企鹅平台企鹅号应用授权调用凭据
     *     'size' => 9135849, // 视频文件大小,单位(字节)
     *     'md5' => 'd081c619ef7dc62eb2aa29c7c83c6f26', // 视频文件MD5值
     *     'sha' => '28a1076b867bed8202df5b3f656b798148e58ced', // 视频文件SHA-1值
     * ]
     *
     * @return array|false
     * 格式如下:
     * 企鹅号的内容网站应用的视频文件分片上传的唯一事务ID
     * [
     *     'message' => '', // 说明
     *     'data' => [ // 数据
     *         'transaction_id' => '780930255958621794', // 上传的唯一事务ID
     *     ],
     * ]
     *
     * 失败(将错误保存在 [[yii/base/Model::errors]] 属性中)
     * false
     *
     * @throws ServerErrorHttpException 如果响应状态码不等于20x
     */
    public function clientUploadReady($data)
    {
        $response = Yii::$app->qqApiHttps->createRequest()
            ->setMethod('post')
            ->setUrl('video/clientuploadready')
            ->setData([
                'access_token' => $data['accessToken'],
                'size' => $data['size'],
                'md5' => $data['md5'],
                'sha' => $data['sha'],
            ])
            ->send();
        // file_put_contents('E:/wwwroot/channel-pub-api/qq/runtime/client-upload-ready_' . $data['size'] . '_' . time() . '.txt', $response->data['data']['transaction_id']);
        // 检查响应状态码是否等于20x
        if ($response->isOk) {
            // 检查业务逻辑是否成功
            if ($response->data['code'] === 0) {
                $responseData = ['message' => '', 'data' => $response->data['data']];
                return $responseData;
            } else {
                $this->addError('id', $response->data['msg']);
                return false;
            }
        } else {
            throw new ServerErrorHttpException(Yii::t('common/error', Yii::t('common/error', Yii::t('common/error', '35041'), ['statusCode' => $response->getStatusCode()])), 35041);
        }
    }

    /**
     * HTTP请求,企鹅号的内容网站应用的视频文件分片上传
     *
     * @param array $data 数据
     * 格式如下:
     * [
     *     'accessToken' => 'LQVHLUQDNEKIDBFARJ1OOA', // 企鹅平台企鹅号应用授权调用凭据
     *     'transactionId' => '780930287703152921', // 上传的唯一事务ID
     *     'mediatrunk' => 'E:/wwwroot/channel-pub-api/storage/channel-pub-api/videos/2018/10/27/1540632234.9842.66697370.mp4', // 视频 mediatrunk 文件
     *     'startOffset' => 0, // 分片的起始位置(从0开始计数)
     * ]
     *
     * @return array|false
     * 格式如下:
     * 企鹅号的内容网站应用的视频文件分片上传
     * [
     *     'message' => '', // 说明
     *     'data' => [ // 数据
     *         'end_offset' => 2198151, // 分片的结束位置
     *         'start_offset' => 2198151, // 分片的起始位置
     *         'transaction_id' => 780930255958621794, // 上传的唯一事务ID
     *     ],
     * ]
     *
     * 失败(将错误保存在 [[yii/base/Model::errors]] 属性中)
     * false
     *
     * @throws ServerErrorHttpException 如果响应状态码不等于20x
     */
    public function clientUploadTrunk($data)
    {
        $response = Yii::$app->qqApiHttp->createRequest()
            ->setMethod('post')
            ->setUrl('video/clientuploadtrunk?access_token=' . $data['accessToken'] . '&transaction_id=' . $data['transactionId'] . '&start_offset=' . $data['startOffset'])
            ->addFile('mediatrunk', $data['mediatrunk'])
            ->send();
        // file_put_contents('E:/wwwroot/channel-pub-api/qq/runtime/client-upload-trunk_' . $data['transactionId'] . '_' . $data['startOffset'] . '_' . time() . '.txt', $response->data['code']);
        // 检查响应状态码是否等于20x
        if ($response->isOk) {
            // 检查业务逻辑是否成功
            if ($response->data['code'] === 0) {
                $responseData = ['message' => '', 'data' => $response->data['data']];
                return $responseData;
            } elseif ($response->data['code'] === 40027) { // 无效的事务ID
                $this->addError('id', $response->data['code']);
                return false;
            } else {
                $this->addError('id', $response->data['msg']);
                return false;
            }
        } else {
            throw new ServerErrorHttpException(Yii::t('common/error', Yii::t('common/error', Yii::t('common/error', '35041'), ['statusCode' => $response->getStatusCode()])), 35041);
        }

    }
}

8、上传视频文件的代码,/channel-pub-api/common/services/QqCwVideoMultipartUploadService.php

/**
* HTTP请求,企鹅号的内容网站应用的视频文件分片上传
* @param array $data 数据
* 格式如下:
*
* [
*     'accessToken' => 'LQVHLUQDNEKIDBFARJ1OOA', // 企鹅平台企鹅号应用授权调用凭据
*     'transactionId' => '780930287703152921', // 上传的唯一事务ID
*     'mediatrunk' => 'E:/wwwroot/channel-pub-api/storage/channel-pub-api/videos/2018/10/27/1540632234.9842.66697370.mp4', // 视频 mediatrunk 文件
*     'startOffset' => 0, // 分片的起始位置(从0开始计数)
* ]
*
* @return array
* 格式如下:
*
* [
*     'end_offset' => 2198151, // 分片的结束位置
*     'start_offset' => 2198151, // 分片的起始位置
*     'transaction_id' => 780930255958621794, // 上传的唯一事务ID
* ]
*
* @throws ServerErrorHttpException
*/
public function httpUploadTrunk($data)
{
/* HTTP请求,企鹅号的内容网站应用的视频文件分片上传 */
$httpQqApiVideo = new HttpQqApiVideo();
$uploadTrunk = $httpQqApiVideo->clientUploadTrunk($data);
if ($uploadTrunk === false) {
if ($httpQqApiVideo->hasErrors()) {
foreach ($httpQqApiVideo->getFirstErrors() as $message) {
$firstErrors = $message;
break;
}
throw new ServerErrorHttpException(Yii::t('common/error', Yii::t('common/error', Yii::t('common/error', '35042'), ['firstErrors' => $firstErrors])), 35042);
} elseif (!$httpQqApiVideo->hasErrors()) {
throw new ServerErrorHttpException('Penguin/'s content website application api HTTP requests fail for unknown reasons.');
}
}
return $uploadTrunk['data'];
}
/**
* 企鹅号的内容网站应用的视频文件分片上传
*
* @param int $assetId 资源ID
* 格式如下:1
*
* @param int $qqCwAppTaskId 企鹅号的内容网站应用的任务ID
* 格式如下:6
*
* @throws ServerErrorHttpException
* @throws /Throwable
*/
public function upload($assetId, $qqCwAppTaskId)
{
// 基于ID查找状态为启用的单个数据模型(资源)
$assetEnabledItem = AssetService::findModelEnabledById($assetId);
// 基于ID查找状态为启用的单个数据模型(任务)
$taskEnabledItem = TaskService::findModelEnabledById($assetEnabledItem->task_id);
// 基于ID查找状态为发布中的单个数据模型(企鹅号的内容网站应用的任务)
$qqCwAppTaskPublishItem = QqCwAppTaskService::findModelPublishById($qqCwAppTaskId);
// 基于ID查找状态为启用的单个数据模型(企鹅号的内容网站应用)
$qqCwAppEnabledItem = QqCwAppService::findModelEnabledById($qqCwAppTaskPublishItem->qq_cw_app_id);
// 基于企鹅号的内容网站应用ID获取有效的 Access Token
$qqCwAccessTokenService = new QqCwAccessTokenService();
$accessTokenValidity = $qqCwAccessTokenService->getAccessTokenValidityByQqCwAppId($qqCwAppEnabledItem->id);
// 基于资源ID、企鹅号的应用的任务ID获取企鹅号的视频文件分片上传的单个数据模型
$qqVideoMultipartUploadItem = $this->getModelByAssetIdAndQqAppTaskId($assetId, $qqCwAppTaskPublishItem->id);
$data = [
'assetId' => $qqVideoMultipartUploadItem->asset_id,
'qqAppTaskId' => $qqVideoMultipartUploadItem->qq_app_task_id,
'qqAppId' => $qqVideoMultipartUploadItem->qq_app_id,
'qqAppType' => $qqVideoMultipartUploadItem->qq_app_type,
'size' => $qqVideoMultipartUploadItem->size,
'md5' => $qqVideoMultipartUploadItem->md5,
'sha' => $qqVideoMultipartUploadItem->sha,
'transactionId' => (string) $qqVideoMultipartUploadItem->transaction_id,
'mediatrunk' => $qqVideoMultipartUploadItem->mediatrunk,
'startOffset' => $qqVideoMultipartUploadItem->start_offset,
'endOffset' => $qqVideoMultipartUploadItem->end_offset,
'vid' => $qqVideoMultipartUploadItem->vid,
'status' => $qqVideoMultipartUploadItem->status,
];
// 文件切片
AssetService::cut(Yii::$app->params['channelPubApi']['asset'][$assetEnabledItem->type]['basePath'] . $qqVideoMultipartUploadItem->mediatrunk, HttpQqApiVideo::CUT_SIZE);
// 获取需要切片的文件的路径信息
$pathInfo = pathinfo(Yii::$app->params['channelPubApi']['asset'][$assetEnabledItem->type]['basePath'] . $qqVideoMultipartUploadItem->mediatrunk);
// 判断分片的起始位置与分片的结束位置是否等于视频文件大小,如果相等则中断分片上传,否则继续执行分片上传
$i = 0;
while ($qqVideoMultipartUploadItem->start_offset != $qqVideoMultipartUploadItem->size && $qqVideoMultipartUploadItem->end_offset != $qqVideoMultipartUploadItem->size) {
// file_put_contents('E:/wwwroot/channel-pub-api/qq/runtime/while_' . $assetId . '_' . $qqCwAppId . '_' . $qqVideoMultipartUploadItem->start_offset . '_' . $qqVideoMultipartUploadItem->end_offset . '_' . time() . '.txt', $assetId);
// HTTP请求,企鹅号的内容网站应用的视频文件分片上传
$httpUploadTrunkData = [
'accessToken' => $accessTokenValidity->access_token,
'transactionId' => $qqVideoMultipartUploadItem->transaction_id,
'mediatrunk' => $pathInfo['dirname'] . '/' . $pathInfo['filename'] . '_' . $i . '.' . $pathInfo['extension'],
'startOffset' => $qqVideoMultipartUploadItem->start_offset,
];
$uploadTrunkData = $this->httpUploadTrunk($httpUploadTrunkData);
// 基于资源ID、企鹅号的内容网站应用ID插入/更新数据至企鹅号的内容网站应用的视频文件分片上传
$data['startOffset'] = $uploadTrunkData['start_offset'];
$data['endOffset'] = $uploadTrunkData['end_offset'];
if ($uploadTrunkData['start_offset'] != $qqVideoMultipartUploadItem->size && $uploadTrunkData['end_offset'] != $qqVideoMultipartUploadItem->size) {
$data['status'] = QqVideoMultipartUpload::STATUS_UPLOADING;
} else {
// HTTP请求,基于上传的唯一事务ID获取事务信息
$qqTransactionService = new QqTransactionService();
$qqTransactionServiceHttpTransactionInfoData = [
'accessToken' => $accessTokenValidity->access_token,
'transactionId' => $qqVideoMultipartUploadItem->transaction_id,
];
$qqTransactionServiceHttpTransactionInfoResult = $qqTransactionService->httpTransactionInfo($qqTransactionServiceHttpTransactionInfoData);
// 创建企鹅号的事务
$qqTransactionServiceCreateData = [
'groupId' => $taskEnabledItem->group_id,
'qqAppTaskId' => $qqVideoMultipartUploadItem->qq_app_task_id,
'qqAppId' => $qqVideoMultipartUploadItem->qq_app_id,
'qqAppType' => $qqVideoMultipartUploadItem->qq_app_type,
'qqArticleId' => 0,
'qqVideoMultipartUploadId' => $qqVideoMultipartUploadItem->id,
'transactionId' => $qqVideoMultipartUploadItem->transaction_id,
'type' => $qqTransactionService::getTypeByHttpQqApiTransactionType($qqTransactionServiceHttpTransactionInfoResult['transaction_type']),
'transactionCtime' => $qqTransactionServiceHttpTransactionInfoResult['transaction_ctime'],
'extErr' => $qqTransactionServiceHttpTransactionInfoResult['ext_err'],
'transactionErrMsg' => $qqTransactionServiceHttpTransactionInfoResult['transaction_err_msg'],
'articleAbstract' => $qqTransactionServiceHttpTransactionInfoResult['article_info']['article_abstract'],
'articleType' => $qqTransactionServiceHttpTransactionInfoResult['article_info']['article_type'],
'articleTypeCode' => '',
'articleUrl' => $qqTransactionServiceHttpTransactionInfoResult['article_info']['article_url'],
'articleImgurl' => $qqTransactionServiceHttpTransactionInfoResult['article_info']['article_imgurl'],
'articleTitle' => $qqTransactionServiceHttpTransactionInfoResult['article_info']['article_title'],
'articlePubFlag' => $qqTransactionServiceHttpTransactionInfoResult['article_info']['article_pub_flag'],
'articlePubTime' => $qqTransactionServiceHttpTransactionInfoResult['article_info']['article_pub_time'],
'articleVideoTitle' => $qqTransactionServiceHttpTransactionInfoResult['article_info']['article_video_info']['title'],
'articleVideoDesc' => $qqTransactionServiceHttpTransactionInfoResult['article_info']['article_video_info']['desc'],
'articleVideoType' => $qqTransactionServiceHttpTransactionInfoResult['article_info']['article_video_info']['type'],
'articleVideoVid' => $qqTransactionServiceHttpTransactionInfoResult['article_info']['article_video_info']['vid'],
'taskId' => $assetEnabledItem->task_id,
'status' => QqTransaction::STATUS_PROCESSING,
];
$qqTransactionServiceCreateResult = $qqTransactionService->create($qqTransactionServiceCreateData);
if ($qqTransactionServiceCreateResult['status'] === false) {
throw new ServerErrorHttpException($qqTransactionServiceCreateResult['message'], $qqTransactionServiceCreateResult['code']);
}
$data['status'] = QqVideoMultipartUpload::STATUS_UPLOADED;
}
$result = $this->saveModelByData($data);
if ($result['status'] === false) {
throw new ServerErrorHttpException($result['message'], $result['code']);
}
// 基于资源ID、企鹅号的应用的任务ID获取企鹅号的视频文件分片上传的单个数据模型
$qqVideoMultipartUploadItem = $this->getModelByAssetIdAndQqAppTaskId($assetId, $qqCwAppTaskPublishItem->id);
$i++;
}
}

9、执行上传资源队列中的任务命令,会切片文件,上传相应的资源,且更新企鹅号的视频文件分片上传表、新增企鹅号的事务

PS E:/wwwroot/channel-pub-api> ./yii upload-asset-queue/run --verbose=1 --isolate=1 --color=0
2018-11-15 11:02:29 [pid: 39632] - Worker is started
2018-11-15 11:02:30 [1] common/jobs/UploadAssetJob (attempt: 1, pid: 39632) - Started
2018-11-15 11:06:48 [1] common/jobs/UploadAssetJob (attempt: 1, pid: 39632) - Done (257.816 s)
2018-11-15 11:06:48 [pid: 39632] - Worker is stopped (0:04:19)

10、查看生成的切片小文件,由于切片大小为100M,396 MB (415,352,401 字节)的文件切片为4个小的文件,如图1

查看生成的切片小文件,由于切片大小为100M,396 MB (415,352,401 字节)的文件切片为4个小的文件

图1

11、上传成功后,分片的起始位置与分片的结束位置皆等于文件的大小:415352401,如图2

上传成功后,分片的起始位置与分片的结束位置皆等于文件的大小:415352401

图2

12、在企鹅号后台查看我的素材,视频文件已经分片上传成功,如图3

在企鹅号后台查看我的素材,视频文件已经分片上传成功

图3

原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/250440.html

(0)
上一篇 2022年4月29日
下一篇 2022年4月29日

相关推荐

发表回复

登录后才能评论