环境:debian 13\php8.3\thinkphp8.1; 把上传后的视频及封面数据保存到mysql8.4中;
表:tp_video结构
sql
CREATE TABLE `tp_video` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`file_id` varchar(32) NOT NULL COMMENT '腾讯云VOD FileId',
`video_url` varchar(1000) NOT NULL DEFAULT '' COMMENT '视频地址',
`video_verify_content` varchar(1000) NOT NULL DEFAULT '' COMMENT '视频校验内容',
`cover_url` varchar(1000) NOT NULL DEFAULT '' COMMENT '封面地址',
`cover_verify_content` varchar(1000) NOT NULL DEFAULT '' COMMENT '封面校验内容',
`status` tinyint unsigned NOT NULL DEFAULT '1' COMMENT '状态:0禁用 1正常',
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_file_id` (`file_id`)
) ENGINE=InnoDB
DEFAULT CHARSET=utf8mb4
COLLATE=utf8mb4_0900_ai_ci
COMMENT='腾讯云点播视频表';
客户端代码:
html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>QCloud VIDEO UGC UPLOAD SDK</title>
<link href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
<style type="text/css">
.text-danger {
color: red;
}
.control-label {
text-align: left !important;
}
#resultBox {
width: 100%;
height: 300px;
border: 1px solid #888;
padding: 5px;
overflow: auto;
margin-bottom: 20px;
}
.uploaderMsgBox {
width: 100%;
border-bottom: 1px solid #888;
}
.cancel-upload {
text-decoration: none;
cursor: pointer;
}
</style>
</head>
<body>
<div id="content">
<div class="container">
<h1>UGC-Uploader</h1>
</div>
</div>
<div class="container" id="main-area">
<div class="row" style="padding:10px;">
<p>
1.示例中的签名是直接从demo后台获取签名。该 demo 对应的点播账号已开启防盗链功能,此视频分发 URL 仅限 2 个独立 IP 进行访问,有效期为 10 分钟。<br>
2.示例1点击"直接上传视频"按钮即可上传视频。<br>
3.示例2点击"添加视频"添加视频文件,点击"添加封面"添加封面文件,然后点击"上传视频和封面"按钮即可上传视频和封面。<br>
4.取消上传为取消上传中的视频,上传成功的视频不能取消上传。
</p>
</div>
<form ref="vExample">
<input type="file" style="display:none;" ref="vExampleFile" @change="vExampleUpload" />
</form>
<div class="row" style="padding:10px;">
<h4>示例1:直接上传视频</h4>
<a href="javascript:void(0);" class="btn btn-default" @click="vExampleAdd">直接上传视频</a>
</div>
<form ref="vcExample">
<input type="file" style="display:none;" ref="vcExampleVideo" @change="setVcExampleVideoName()" />
<input type="file" style="display:none;" ref="vcExampleCover" @change="setVcExampleCoverName()" />
</form>
<div class="row" style="padding:10px;">
<h4>示例2:上传视频和封面</h4>
<a href="javascript:void(0);" class="btn btn-default" @click="vcExampleAddVideo">{{vcExampleVideoName || '添加视频'}}</a>
<a href="javascript:void(0);" class="btn btn-default" @click="vcExampleAddCover">{{vcExampleCoverName || '添加封面'}}</a>
<a href="javascript:void(0);" class="btn btn-default" @click="vcExampleAddUpload">上传视频和封面</a>
</div>
<form ref="cExample">
<input type="file" style="display:none;" ref="cExampleCover" @change="cExampleUpload" />
</form>
<div class="row" id="resultBox">
<!-- 上传信息组件 -->
<div class="uploaderMsgBox" v-for="uploaderInfo in uploaderInfos">
<div v-if="uploaderInfo.videoInfo">
视频名称:{{uploaderInfo.videoInfo.name + '.' + uploaderInfo.videoInfo.type}};
上传进度:{{Math.floor(uploaderInfo.progress * 100) + '%'}};
fileId:{{uploaderInfo.fileId}};
上传结果:{{uploaderInfo.isVideoUploadCancel ? '已取消' : uploaderInfo.isVideoUploadSuccess ? '上传成功' : '上传中'}};
<br>
地址:{{uploaderInfo.videoUrl}};
<a href="javascript:void(0);" class="cancel-upload" v-if="!uploaderInfo.isVideoUploadSuccess && !uploaderInfo.isVideoUploadCancel" @click="uploaderInfo.cancel()">取消上传</a><br>
</div>
<div v-if="uploaderInfo.coverInfo">
封面名称:{{uploaderInfo.coverInfo.name}};
上传进度:{{Math.floor(uploaderInfo.coverProgress * 100) + '%'}};
上传结果:{{uploaderInfo.isCoverUploadSuccess ? '上传成功' : '上传中'}};
<br>
地址:{{uploaderInfo.coverUrl}};
<br>
</div>
</div>
</div>
</div>
<script src="//cdn.jsdelivr.net/npm/es6-promise@4/dist/es6-promise.auto.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.5.21/vue.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.js"></script>
<script src="//cdn-go.cn/cdn/vod-js-sdk-v6/latest/vod-js-sdk-v6.js"></script>
<script type="text/javascript">
;(function () {
/**
* 计算签名
*/
function getSignature() {
return axios.post('/api/tencent/index/').then(function (response) {
return response.data.signature;
})
}
/**
* 保存腾讯云上传结果到自己的数据库
*/
function saveUploadResult(doneResult) {
return axios.post('/api/tencent/saveVideo/', doneResult)
}
var app = new Vue({
el: '#main-area',
data: {
uploaderInfos: [],
vcExampleVideoName: '',
vcExampleCoverName: '',
cExampleFileId: '',
},
created: function () {
this.tcVod = new TcVod.default({
getSignature: getSignature
})
},
methods: {
/**
* 示例1
* 添加视频
*/
vExampleAdd: function () {
this.$refs.vExampleFile.click()
},
/**
* 示例1
* 直接上传视频
*/
vExampleUpload: function () {
var self = this;
var mediaFile = this.$refs.vExampleFile.files[0];
if (!mediaFile) {
return;
}
var uploader = this.tcVod.upload({
mediaFile: mediaFile,
});
var uploaderInfo = {
videoInfo: uploader.videoInfo,
isVideoUploadSuccess: false,
isVideoUploadCancel: false,
progress: 0,
fileId: '',
videoUrl: '',
cancel: function () {
uploaderInfo.isVideoUploadCancel = true;
uploader.cancel();
},
};
/**
* 上传进度
*/
uploader.on('media_progress', function (info) {
uploaderInfo.progress = info.percent;
});
/**
* 视频文件上传完成
*/
uploader.on('media_upload', function (info) {
uploaderInfo.isVideoUploadSuccess = true;
});
console.log(uploader, 'uploader');
this.uploaderInfos.push(uploaderInfo);
/**
* 腾讯云整个上传流程完成
*/
uploader.done().then(function (doneResult) {
console.log('doneResult', doneResult);
/**
* 原有功能保留
*/
uploaderInfo.fileId = doneResult.fileId;
uploaderInfo.videoUrl = doneResult.video.url;
/**
* 保存到自己的 MySQL
*/
return saveUploadResult(doneResult);
}).then(function (response) {
console.log('数据库保存结果:', response.data);
if (response.data.code !== 0) {
console.error('数据库保存失败:', response.data.msg);
}
/**
* 原有重置功能保留
*/
self.$refs.vExample.reset();
}).catch(function (error) {
console.error('视频上传或数据库保存失败:', error);
});
},
/**
* 示例2
* 选择视频
*/
setVcExampleVideoName: function () {
var file = this.$refs.vcExampleVideo.files[0];
if (file) {
this.vcExampleVideoName = file.name;
}
},
/**
* 示例2
* 选择封面
*/
setVcExampleCoverName: function () {
var file = this.$refs.vcExampleCover.files[0];
if (file) {
this.vcExampleCoverName = file.name;
}
},
/**
* 示例2
* 添加视频
*/
vcExampleAddVideo: function () {
this.$refs.vcExampleVideo.click()
},
/**
* 示例2
* 添加封面
*/
vcExampleAddCover: function () {
this.$refs.vcExampleCover.click()
},
/**
* 示例2
* 上传视频 + 封面
*/
vcExampleAddUpload: function () {
var self = this;
var mediaFile = this.$refs.vcExampleVideo.files[0];
var coverFile = this.$refs.vcExampleCover.files[0];
if (!mediaFile) {
console.error('请选择视频文件');
return;
}
if (!coverFile) {
console.error('请选择封面文件');
return;
}
var uploader = this.tcVod.upload({
mediaFile: mediaFile,
coverFile: coverFile,
});
var uploaderInfo = {
videoInfo: uploader.videoInfo,
coverInfo: uploader.coverInfo,
isVideoUploadSuccess: false,
isVideoUploadCancel: false,
isCoverUploadSuccess: false,
progress: 0,
coverProgress: 0,
fileId: '',
videoUrl: '',
coverUrl: '',
cancel: function () {
uploaderInfo.isVideoUploadCancel = true;
uploader.cancel();
},
};
/**
* 视频上传进度
*/
uploader.on('media_progress', function (info) {
uploaderInfo.progress = info.percent;
});
/**
* 视频文件上传完成
*/
uploader.on('media_upload', function (info) {
uploaderInfo.isVideoUploadSuccess = true;
});
/**
* 封面上传进度
*/
uploader.on('cover_progress', function (info) {
uploaderInfo.coverProgress = info.percent;
});
/**
* 封面文件上传完成
*/
uploader.on('cover_upload', function (info) {
uploaderInfo.isCoverUploadSuccess = true;
});
console.log(uploader, 'uploader');
this.uploaderInfos.push(uploaderInfo);
/**
* 腾讯云上传完成
*/
uploader.done().then(function (doneResult) {
console.log('doneResult', doneResult);
/**
* 原有功能全部保留
*/
uploaderInfo.fileId = doneResult.fileId;
if (doneResult.video) {
uploaderInfo.videoUrl = doneResult.video.url;
}
if (doneResult.cover) {
uploaderInfo.coverUrl = doneResult.cover.url;
}
/**
* 把腾讯云返回结果保存数据库
*/
return saveUploadResult(doneResult);
}).then(function (response) {
console.log('数据库保存结果:', response.data);
if (response.data.code !== 0) {
console.error('数据库保存失败:', response.data.msg);
}
/**
* 原有重置功能保留
*/
self.$refs.vcExample.reset();
self.vcExampleVideoName = '';
self.vcExampleCoverName = '';
}).catch(function (error) {
console.error('上传或数据库保存失败:', error);
});
},
/**
* 示例3
* 添加封面
*/
cExampleAddCover: function () {
this.$refs.cExampleCover.click()
},
/**
* 示例3
* 为已经存在的 fileId 上传封面
*/
cExampleUpload: function () {
var self = this;
var coverFile = this.$refs.cExampleCover.files[0];
if (!coverFile) {
console.error('请选择封面');
return;
}
if (!this.cExampleFileId) {
console.error('请输入 fileId');
return;
}
var uploader = this.tcVod.upload({fileId: this.cExampleFileId, coverFile: coverFile});
var uploaderInfo = {
coverInfo: uploader.coverInfo,
isCoverUploadSuccess: false,
coverProgress: 0,
coverUrl: '',
cancel: function () {
uploader.cancel();
},
};
/**
* 封面上传进度
*/
uploader.on(
'cover_progress',
function (info) {
uploaderInfo.coverProgress = info.percent;
}
);
/**
* 封面上传成功
*/
uploader.on(
'cover_upload',
function (info) {
uploaderInfo.isCoverUploadSuccess = true;
}
);
console.log(uploader, 'uploader');
this.uploaderInfos.push(uploaderInfo);
uploader.done().then(function (doneResult) {
console.log('doneResult',doneResult);
if (doneResult.cover) {
uploaderInfo.coverUrl = doneResult.cover.url;
}
/**
* 注意:
* 单独上传封面返回的数据结构
* 可能没有 video。
*
* 我们补上 fileId,
* 方便 TP8 更新数据库。
*/
doneResult.fileId = self.cExampleFileId;
/**
* 保存/更新数据库
*/
return saveUploadResult(doneResult);
}).then(function (response) {
console.log('封面数据库更新结果:', response.data);
self.$refs.cExample.reset();
}).catch(function (error) {
console.error('封面上传或保存失败:', error);
});
},
},
});
})();
</script>
</body>
</html>
后端上传签名:
php
<?php
declare (strict_types = 1);
namespace app\api\controller;
use think\response\Json;
use think\response\View;
class Tencent
{
/**
* 显示资源列表
*
* @return \think\Response
*/
public function index(): Json
{
// 确定 App 的云 API 密钥
$secret_id = "你的ID";
$secret_key = "你的key";
// 确定签名的当前时间和失效时间
$current = time();
$expired = $current + 86400; // 签名有效期:1天
// 向参数列表填入参数
$arg_list = array(
"secretId" => $secret_id,
"currentTimeStamp" => $current,
"expireTime" => $expired,
"random" => rand());
// 计算签名
$original = http_build_query($arg_list);
$signature = base64_encode(hash_hmac('SHA1', $original, $secret_key, true).$original);
return json(['signature' => $signature]);
}
public function uploadVideo(): View
{
return view('/tencent-upload');
}
public function saveVideo(): Json
{
if (!request()->isPost()) {
return json(['code' => 1, 'msg' => '非法请求']);
}
try {
$data = request()->post();
if (empty($data)) {
$data = json_decode(request()->getContent(), true);
}
if (!is_array($data)) {
return json(['code' => 1, 'msg' => 'JSON数据格式错误']);
}
$fileId = trim((string) ($data['fileId'] ?? ''));
if ($fileId === '') {
return json(['code' => 1, 'msg' => 'fileId不能为空']);
}
$saveData = [
'video_url' => (string)($data['video']['url'] ?? ''),
'video_verify_content' => (string)($data['video']['verify_content'] ?? ''),
'cover_url' => (string)($data['cover']['url'] ?? ''),
'cover_verify_content' => (string)($data['cover']['verify_content'] ?? ''),
'status' => 1,
'updated_at' => date('Y-m-d H:i:s'),
];
/*
* 查询 fileId
*/
$id = Db::name('video')->where('file_id', $fileId)->value('id');
if ($id) {
/*
* 已存在 → 更新
*/
Db::name('video')->where('id', $id)->update($saveData);
} else {
/*
* 不存在 → 新增
*/
$saveData['file_id'] = $fileId;
$saveData['created_at'] = date('Y-m-d H:i:s');
$id = Db::name('video')->insertGetId($saveData);
}
return json([
'code' => 0,
'msg' => '保存成功',
'data' => [
'id' => $id,
'file_id' => $fileId,
'video_url' => $saveData['video_url'],
'cover_url' => $saveData['cover_url'],
]
]);
} catch (\Throwable $e) {
return json(['code' => 1, 'msg' => '保存失败:' . $e->getMessage()]);
}
}
}