其中包括用户授权,获取accessToken,获取用户信息,自动上传视频,发布视频,视频列表,删除视频等
<?php
namespace app\controller;
use app\BaseController;
use Psr\SimpleCache\InvalidArgumentException;
use think\facade\App;
use think\facade\Cache;
class KuaiShou extends BaseController
{
public $appId = "";
public $appSecret = "";
public $uploadToken; // 上传令牌
public $endpoint;//上传网关的域名
public $redirectUri = "";
public $accessToken;
public $accessTokenTTl = 3600 * 12 * 2; // 2天
/**
* @var mixed
*/
private $refreshToken;
public $refreshTokenTTl = 3600 * 24 * 180;//180天
private $limitShardSize = 1024 * 1024 * 10;//大于10m分片上传
public $userId = 1;
public $successResult = [
'code' => 200,
'msg' => 'success',
'data' => []
];
public $errorResult = [
'code' => 400,
'msg' => 'error',
'data' => []
];
/**
* 用户授权
* */
public function authorize($return = 0)
{
// 要请求的 URL
$url = 'https://open.kuaishou.com/oauth2/authorize?';
$params = [
'app_id' => $this->appId, // 应用唯一标识
'scope' => 'user_info,user_base,user_video_publish,user_video_delete,user_video_info', // code
'response_type' => 'code',
'redirect_uri' => $this->redirectUri,// 应用授权作用域,多个授权作用域以英文逗号(,)分隔
'state' => '123456789', //安全参数,标识和用户或者设备相关的授权请求。建议开发者实现。回调的时候会带回。
'ua' => 'pc', // 运行环境,普通网页传pc, H5网页不传此参数即可
];
// 调用 CURL GET 请求方法
$result = $this->montageUrl($url,$params);
if ($return) {
return $result;
}
header('Location: '.$result);
exit;
}
/**
* 获取access_token
*
* @throws InvalidArgumentException
*/
public function getAccessToken()
{
$res = $this->getAccessTokenServices();
$data = [
'access_token' => $res
];
return json($data);
}
/**
* 获取access_token服务
* */
public function getAccessTokenServices()
{
$redis = Cache::store('redis');
$accessTokenKey = "{$this->userId}:access_token";
$refreshTokenKey = "{$this->userId}:refresh_token";
// 检查用户access_token是否存在
if ($redis->has($accessTokenKey)) {
// 获取key的剩余过期时间
$ttl = $redis->ttl($accessTokenKey);
if ($ttl < 1800) {
//半小时内过期重新获取一个token
$this->refreshToken();
}
return $redis->get($accessTokenKey, '');
} else {
// key不存在或已过期
$url = "https://open.kuaishou.com/oauth2/access_token?";
$data = [
'app_id' => $this->appId,
'app_secret' => $this->appSecret,
'code' => $this->getCode(),
'grant_type' => 'authorization_code',
];
$result = $this->performCurlGetRequestWithParameters($url,$data);
if ($result) {
$result = json_decode($result,true);
if ($result['result'] != 1 ) {
return '';
}
if ($result['access_token']) {
$this->accessToken = $result['access_token'];
$this->refreshToken = $result['refresh_token'];
$this->accessTokenTTl = $result['expires_in'];
$this->refreshTokenTTl = $result['refresh_token_expires_in'];
Cache::set($accessTokenKey,$this->accessToken,$this->accessTokenTTl);
Cache::set($refreshTokenKey,$this->refreshToken,$this->refreshTokenTTl);
return $result['access_token'];
}
return '';
} else{
//返回错误信息
return $result;
}
}
}
private function getCode()
{
return "ad3dac16a93aa4434335c4d3e9a1773dcc8cd759c65b965a727a6ec18b8569981ec3e415";
}
/**
* 刷新token
* */
public function refreshToken()
{
$accessTokenKey = "{$this->userId}:access_token";
$refreshTokenKey = "{$this->userId}:refresh_token";
$url = "https://open.kuaishou.com/oauth2/refresh_token?";
$data = [
'app_id' => $this->appId,
'app_secret' => $this->appSecret,
"refresh_token" => $this->getRefreshTokenServices(),
'grant_type' => 'refresh_token',
];
$result = $this->performCurlGetRequestWithParameters($url,$data);
if ($result) {
$result = json_decode($result,true);
if ($result['access_token']) {
$this->accessToken = $result['access_token'];
$this->refreshToken = $result['refresh_token'];
$this->accessTokenTTl = $result['expires_in'];
$this->refreshTokenTTl = $result['refresh_token_expires_in'];
Cache::set($accessTokenKey,$this->accessToken,$this->accessTokenTTl);
Cache::set($refreshTokenKey,$this->refreshToken,$this->refreshTokenTTl);
return $result['access_token'];
}
return '';
}else{
//返回错误信息
return $result;
}
}
/**
* 刷新token服务
* */
public function getRefreshTokenServices()
{
$refreshTokenKey = "{$this->userId}:refresh_token";
$this->refreshToken = Cache::get($refreshTokenKey, '');
if ($this->refreshToken) {
return $this->refreshToken;
} else {
$this->getAccessTokenServices();
$this->refreshToken = Cache::get($refreshTokenKey, '');
}
return $this->refreshToken;
}
/**
* 获取用户信息
* */
public function getUserInfo()
{
$accessToken = $this->getAccessTokenServices();
if (!$accessToken) {
return json(['code' => 10001,'msg' => '请先授权', 'data' => $this->authorize(1)], 200);
}
$url = "https://open.kuaishou.com/openapi/user_info?";
$data = [
'app_id' => $this->appId,
'access_token' => $accessToken
];
$result = $this->performCurlGetRequestWithParameters($url,$data);
if ($result) {
$result = json_decode($result,true);
if ($result['result'] == 1) {
$this->successResult['data'] = $result['user_info'];
return json($this->successResult, 200);
}else{
$this->errorResult['msg'] = $result['error_msg'];
return json($this->errorResult, 200);
}
}
return $result;
}
/**
* 获取用户手机号码
* */
public function getUserPhone()
{
$accessToken = $this->getAccessTokenServices();
if (!$accessToken) {
return json(['code' => 10001,'msg' => '请先授权', 'data' => $this->authorize(1)], 200);
}
$url = "https://open.kuaishou.com/openapi/user_phone?";
$data = [
'app_id' => $this->appId,
'access_token' => $accessToken
];
$result = $this->performCurlGetRequestWithParameters($url,$data);
if ($result) {
$result = json_decode($result,true);
if ($result['result'] == 1) {
$this->successResult['data'] = $result['encrypted_phone'];
return json($this->successResult, 200);
}else{
$this->errorResult['msg'] = $result['error_msg'];
return json($this->errorResult, 200);
}
}
return $result;
}
/**
* 上传视频,用户调用
* */
public function uploadVideo()
{
$this->accessToken = $this->getAccessTokenServices();
if (!$this->accessToken) {
return json(['code' => 10001,'msg' => '请先授权', 'data' => $this->authorize(1)], 200);
}
$basePath = App::getBasePath();
// 构造runtime文件夹的完整路径
$runtimePath = $basePath . '../runtime/storage/';;
// 获取表单上传文件 例如上传了001.jpg
$videoFile = request()->file('videoFile');//视频文件
$picFile = request()->file('picFile');//封面文件
$uploadType = $this->request->post('uploadType', 'formData');
$title = $this->request->post('title', date('Y-m-d H i:s'));
$videoFileSize = $videoFile->getSize();
// 上传到本地服务器
$videoFilePath = \think\facade\Filesystem::putFile( 'topic', $videoFile);
$picFilePath = \think\facade\Filesystem::putFile( 'topic', $picFile);
$videoFilePath = $runtimePath.$videoFilePath;
$picFilePath = $runtimePath.$picFilePath;
$preData = $this->startUpload();
if ($preData['result'] != 1) {
return json($this->errorResult, 200);
}
$this->uploadToken = $preData['upload_token'] ?? '';
$this->endpoint = $preData['endpoint'] ?? '';
if ($this->uploadToken && $this->endpoint) {
//视频大于5M分片上传
if ($videoFileSize > $this->limitShardSize) {
$fragmentTotal = $this->uploadFragment($videoFilePath);
$comData = $this->uploadComplete($fragmentTotal);
if ($comData['result'] != 1) {
return json($this->errorResult, 200);
}
} else {
if ($uploadType == 'formData') {
$doingData = $this->uploadForFormData($videoFilePath);
if ($doingData['result'] != 1) {
return json($this->errorResult, 200);
}
} else {
$doingData = $this->uploadForBinaryData($videoFilePath);
if ($doingData['result'] != 1) {
return json($this->errorResult, 200);
}
}
}
}
$res = $this->publish($picFilePath, $title);
if ($res['result'] == 1) {
$this->successResult['data'] = $res['video_info'];
return json($this->successResult, 200);
}else{
$this->errorResult['msg'] = $res['error_msg'];
return json($this->errorResult, 200);
}
}
/**
* 发起上传
* */
public function startUpload()
{
$url = "https://open.kuaishou.com/openapi/photo/start_upload?";
$data = [
'app_id' => $this->appId,
'access_token' => $this->accessToken
];
$params = '';
foreach ($data as $key => $val) {
$params .= "$key=$val&";
}
$url .= rtrim($params, '&');
$res = $this->sendPostRequest($url);
return json_decode($res, true);
}
/**
* 视频上传 FormData
* */
public function uploadForFormData($file_path)
{
$url = "http://{$this->endpoint}/api/upload/multipart?upload_token={$this->uploadToken}";
$data = [
'file' => new \CURLFile($file_path),
];
$headers = ['Content-Type: multipart/form-data'];
$result = $this->sendPostRequest($url, $headers, $data);
return json_decode($result, true);
}
/**
* body二进制 视频上传
* */
public function uploadForBinaryData($file_path)
{
$url = "http://{$this->endpoint}/api/upload?upload_token={$this->uploadToken}";
$headers = array('Content-Type: video/mp4');
// 准备二进制数据
$binaryData = file_get_contents($file_path);
$result = $this->sendPostRequest($url,$headers, $binaryData);
return json_decode($result, true);
}
/**
* 分片上传
* */
public function uploadFragment($file_path)
{
$chunks = $this->splitFileIntoChunks($file_path, $this->limitShardSize);
$headers = array('Content-Type: video/mp4');
foreach ($chunks as $k => $chunk) {
// 对每个分片进行处理,例如上传到服务器或写入到另一个文件
$url = "http://{$this->endpoint}/api/upload/fragment?fragment_id={$k}&upload_token={$this->uploadToken}";
$this->sendPostRequest($url,$headers, $chunk);
}
return count($chunks);
}
/**
* 断点续传
* */
public function uploadResume()
{
$endpoint = "upload.kuaishouzt.com";
$url = "http://{$endpoint}/api/upload/resume?upload_token={$this->uploadToken}";
$this->performCurlGetRequestWithParameters($url);
}
/**
* 完成分片上传
* */
public function uploadComplete($fragment_count)
{
$url = "http://{$this->endpoint}/api/upload/complete?upload_token={$this->uploadToken}&fragment_count={$fragment_count}";
$res = $this->sendPostRequest($url);
return json_decode($res, true);
}
/**
* 发布视频
* */
public function publish($picFilePath, $title)
{
$url = "https://open.kuaishou.com/openapi/photo/publish?";
$data = [
'app_id' => $this->appId,
'access_token' => $this->accessToken,
'upload_token' => $this->uploadToken,
];
$url = $this->montageUrl($url, $data);
$header = [
'Content-Type: multipart/form-data'
];
$params = [
'cover' => new \CURLFile($picFilePath),
'caption' => $title
];
$res = $this->sendPostRequest($url,$header, $params);
return json_decode($res, true);
}
/**
* 查询用户视频列表
* */
public function userVideoList()
{
$accessToken = $this->getAccessTokenServices();
if (!$accessToken) {
return json(['code' => 10001,'msg' => '请先授权', 'data' => $this->authorize(1)], 200);
}
$url = "https://open.kuaishou.com/openapi/photo/list?";
$data = [
'access_token' => $accessToken,
'app_id' => $this->appId
];
$result = $this->performCurlGetRequestWithParameters($url, $data);
if ($result) {
$result = json_decode($result,true);
if ($result['result'] == 1) {
$this->successResult['data'] = $result['video_list'];
return json($this->successResult, 200);
}else{
$this->errorResult['msg'] = $result['error_msg'];
return json($this->errorResult, 200);
}
}
return $result;
}
/**
* 删除视频
* */
public function deleteVideo()
{
$accessToken = $this->getAccessTokenServices();
if (!$accessToken) {
return json(['code' => 10001,'msg' => '请先授权', 'data' => $this->authorize(1)], 200);
}
$url = "https://open.kuaishou.com/openapi/photo/delete?";
$data = [
'access_token' => $accessToken,
'app_id' => $this->appId,
'photo_id' => '3xdr3fw9f5i9ajw'
];
$url = $this->montageUrl($url, $data);
$res = $this->sendPostRequest($url);
if ($res) {
$res = json_decode($res,true);
$errorMap = [
1 => '成功',
120001 => '视频不存在', // 用户请求的视频不存在
120002 => '视频未上传', // 视频尚未完成上传
120003 => '视频发布失败', // 发布视频过程中遇到错误
100400 => '异常参数', // 请求中包含异常或缺失的参数
100200500 => '服务器内部错误', // 请求中包含异常或缺失的参数
];
if ($res['result'] == 1) {
return json($this->successResult, 200);
}else{
$this->errorResult['msg'] = $errorMap[$res['result']] ?? '';
return json($this->errorResult, 200);
}
}
return $res;
}
/**
* 获取二进制内容,并按指定大小分割
* */
function splitFileIntoChunks($filePath, $chunkSize) {
$fileHandle = fopen($filePath, 'rb'); // 以二进制读取模式打开文件
if ($fileHandle === false) {
return false;// 如果文件打开失败,返回false
}
$chunks = [];
while (!feof($fileHandle)) {
$buffer = fread($fileHandle, $chunkSize);
// 读取文件的一个片段
if ($buffer === false) {
break;// 如果读取失败,跳出循环
}
$chunks[] = $buffer; // 将片段添加到数组中
}
fclose($fileHandle); // 关闭文件句柄
return $chunks;// 返回包含所有片段的数组
}
function performCurlGetRequestWithParameters($url, $parameters = []) {
// 构建带有参数的 URL
$url = $this->montageUrl($url, $parameters);
// 执行 CURL GET 请求
$headers = [
'Content-Type: application/json'
];
return $this->sendGetRequest($url, $headers);
}
/**
* url拼接
* */
public function montageUrl($url, $data)
{
$params = http_build_query($data);
$url .= rtrim($params, '&');
return $url;
}
/**
* 执行 CURL GET 请求的方法
*
* @param string $url 要请求的 URL
* @return string 请求的结果
*/
function sendGetRequest(string $url, $headers = [], $data = []) {
// 初始化 CURL
$ch = curl_init();
// 设置请求方式为 GET
curl_setopt($ch, CURLOPT_HTTPGET, true);
// 设置要请求的 URL
curl_setopt($ch, CURLOPT_URL, $url);
// 禁用 SSL 证书验证
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
// 设置默认的头部信息
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
// 将数据以 JSON 格式发送
if ($data) {
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
}
// 使cURL将响应结果作为字符串返回而不是输出
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
// 执行请求
$result = curl_exec($ch);
// 检查请求是否成功
if (curl_errno($ch)) {
// 处理请求失败的情况
echo "CURL 请求失败: ". curl_error($ch);
return false;
}
// 关闭 CURL 连接
curl_close($ch);
// 返回请求结果
return $result;
}
/**
* curl post请求
* params string $url 请求的url
* */
public function sendPostRequest($url, $header = [], $data = [])
{
// 初始化cURL会话
$ch = curl_init();
// 设置目标URL
curl_setopt($ch, CURLOPT_URL, $url);
// 为cURL会话设置适当的请求头
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
// 启用POST方法
curl_setopt($ch, CURLOPT_POST, 1);
if ($data) {
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
}
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
// 设置cURL选项,以便将响应结果直接作为字符串返回,而不是输出到浏览器
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
// 执行cURL请求并获取结果
$response = curl_exec($ch);
// 检查是否有错误发生
if (curl_errno($ch)) {
echo 'cURL error: ' . curl_error($ch);
return false;
}
// 关闭cURL会话
curl_close($ch);// 输出响应结果echo $response;
return $response;
}
}