mirror of
https://gitee.com/zoujingli/ThinkAdmin.git
synced 2026-06-07 20:48:09 +08:00
Replace header text "Payment Plugin for ThinkAdmin" with "ThinkAdmin Plugin for ThinkAdmin" across project files (configs, controllers, plugins, php-cs-fixer, etc.) to unify branding. Add a new composer script "rewrite-model" to regenerate models and run php-cs-fixer. Also apply a minor newline fix in .copilot-commit-message-instructions.md.
243 lines
8.4 KiB
PHP
243 lines
8.4 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
/**
|
||
* +----------------------------------------------------------------------
|
||
* | ThinkAdmin Plugin for ThinkAdmin
|
||
* +----------------------------------------------------------------------
|
||
* | 版权所有 2014~2026 ThinkAdmin [ thinkadmin.top ]
|
||
* +----------------------------------------------------------------------
|
||
* | 官方网站: https://thinkadmin.top
|
||
* +----------------------------------------------------------------------
|
||
* | 开源协议 ( https://mit-license.org )
|
||
* | 免责声明 ( https://thinkadmin.top/disclaimer )
|
||
* | 会员特权 ( https://thinkadmin.top/vip-introduce )
|
||
* +----------------------------------------------------------------------
|
||
* | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
|
||
* | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
|
||
* +----------------------------------------------------------------------
|
||
*/
|
||
|
||
namespace think\admin\storage;
|
||
|
||
use think\admin\contract\StorageInterface;
|
||
use think\admin\contract\StorageUsageTrait;
|
||
use think\admin\Exception;
|
||
use think\admin\extend\HttpExtend;
|
||
use think\admin\Storage;
|
||
|
||
/**
|
||
* 七牛云存储支持
|
||
* @class QiniuStorage
|
||
*/
|
||
class QiniuStorage implements StorageInterface
|
||
{
|
||
use StorageUsageTrait;
|
||
|
||
private $bucket;
|
||
|
||
private $accessKey;
|
||
|
||
private $secretKey;
|
||
|
||
/**
|
||
* 上传文件内容.
|
||
* @param string $name 文件名称
|
||
* @param string $file 文件内容
|
||
* @param bool $safe 安全模式
|
||
* @param ?string $attname 下载名称
|
||
* @throws Exception
|
||
*/
|
||
public function set(string $name, string $file, bool $safe = false, ?string $attname = null): array
|
||
{
|
||
$token = $this->token($name, 3600, $attname);
|
||
$data = ['key' => $name, 'token' => $token, 'fileName' => $name];
|
||
$file = ['field' => 'file', 'name' => $name, 'content' => $file];
|
||
$result = HttpExtend::submit($this->upload(), $data, $file, [], 'POST', false);
|
||
return json_decode($result, true);
|
||
}
|
||
|
||
/**
|
||
* 读取文件内容.
|
||
* @param string $name 文件名称
|
||
* @param bool $safe 安全模式
|
||
*/
|
||
public function get(string $name, bool $safe = false): string
|
||
{
|
||
$url = $this->url($name, $safe) . '?e=' . time();
|
||
$token = "{$this->accessKey}:{$this->safeBase64(hash_hmac('sha1', $url, $this->secretKey, true))}";
|
||
return Storage::curlGet("{$url}&token={$token}");
|
||
}
|
||
|
||
/**
|
||
* 删除存储文件.
|
||
* @param string $name 文件名称
|
||
* @param bool $safe 安全模式
|
||
*/
|
||
public function del(string $name, bool $safe = false): bool
|
||
{
|
||
[$EncodedEntryURI, $AccessToken] = $this->getAccessToken($name, 'delete');
|
||
$data = json_decode(HttpExtend::post("https://rs.qiniu.com/delete/{$EncodedEntryURI}", [], [
|
||
'headers' => ["Authorization:QBox {$AccessToken}"],
|
||
]), true);
|
||
return empty($data['error']);
|
||
}
|
||
|
||
/**
|
||
* 判断是否存在.
|
||
* @param string $name 文件名称
|
||
* @param bool $safe 安全模式
|
||
*/
|
||
public function has(string $name, bool $safe = false): bool
|
||
{
|
||
return !empty($this->info($name, $safe));
|
||
}
|
||
|
||
/**
|
||
* 获取访问地址
|
||
* @param string $name 文件名称
|
||
* @param bool $safe 安全模式
|
||
* @param ?string $attname 下载名称
|
||
*/
|
||
public function url(string $name, bool $safe = false, ?string $attname = null): string
|
||
{
|
||
return "{$this->domain}/{$this->delSuffix($name)}{$this->getSuffix($attname, $name)}";
|
||
}
|
||
|
||
/**
|
||
* 获取存储路径.
|
||
* @param string $name 文件名称
|
||
* @param bool $safe 安全模式
|
||
*/
|
||
public function path(string $name, bool $safe = false): string
|
||
{
|
||
return $this->url($name, $safe);
|
||
}
|
||
|
||
/**
|
||
* 获取文件信息.
|
||
* @param string $name 文件名称
|
||
* @param bool $safe 安全模式
|
||
* @param ?string $attname 下载名称
|
||
*/
|
||
public function info(string $name, bool $safe = false, ?string $attname = null): array
|
||
{
|
||
[$entry, $token] = $this->getAccessToken($name);
|
||
$data = json_decode(HttpExtend::get("https://rs.qiniu.com/stat/{$entry}", [], ['headers' => ["Authorization: QBox {$token}"]]), true);
|
||
return isset($data['md5']) ? ['file' => $name, 'url' => $this->url($name, $safe, $attname), 'key' => $name] : [];
|
||
}
|
||
|
||
/**
|
||
* 获取上传地址
|
||
* @throws Exception
|
||
*/
|
||
public function upload(): string
|
||
{
|
||
try {
|
||
$proc = $this->app->request->isSsl() ? 'https' : 'http';
|
||
$region = sysconf('storage.qiniu_region|raw');
|
||
} catch (\Exception $exception) {
|
||
throw new Exception($exception->getMessage());
|
||
}
|
||
// 注:汉字为兼容旧版本区域配置
|
||
switch ($region) {
|
||
case '华东':
|
||
case 'up.qiniup.com':
|
||
return "{$proc}://up.qiniup.com";
|
||
case 'up-cn-east-2.qiniup.com':
|
||
return "{$proc}://up-cn-east-2.qiniup.com";
|
||
case '华北':
|
||
case 'up-z1.qiniup.com':
|
||
return "{$proc}://up-z1.qiniup.com";
|
||
case '华南':
|
||
case 'up-z2.qiniup.com':
|
||
return "{$proc}://up-z2.qiniup.com";
|
||
case '北美':
|
||
case 'up-na0.qiniup.com':
|
||
return "{$proc}://up-na0.qiniup.com";
|
||
case '东南亚':
|
||
case 'up-as0.qiniup.com':
|
||
return "{$proc}://up-as0.qiniup.com";
|
||
case 'up-ap-northeast-1.qiniup.com':
|
||
return "{$proc}://up-ap-northeast-1.qiniup.com";
|
||
default:
|
||
throw new Exception(lang('未配置七牛云空间区域哦'));
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 生成上传令牌.
|
||
* @param ?string $name 文件名称
|
||
* @param int $expires 有效时间
|
||
* @param ?string $attname 下载名称
|
||
*/
|
||
public function token(?string $name = null, int $expires = 3600, ?string $attname = null): string
|
||
{
|
||
$key = is_null($name) ? '$(key)' : $name;
|
||
$url = "{$this->domain}/$(key){$this->getSuffix($attname, $name)}";
|
||
$policy = $this->safeBase64(json_encode([
|
||
'deadline' => time() + $expires, 'scope' => is_null($name) ? $this->bucket : "{$this->bucket}:{$name}",
|
||
'returnBody' => json_encode(['uploaded' => true, 'filename' => '$(key)', 'url' => $url, 'key' => $key, 'file' => $key], JSON_UNESCAPED_UNICODE),
|
||
]));
|
||
return "{$this->accessKey}:{$this->safeBase64(hash_hmac('sha1', $policy, $this->secretKey, true))}:{$policy}";
|
||
}
|
||
|
||
/**
|
||
* 获取存储区域
|
||
*/
|
||
public static function region(): array
|
||
{
|
||
return [
|
||
'up.qiniup.com' => lang('华东-浙江'),
|
||
'up-cn-east-2.qiniup.com' => lang('华东-浙江2'),
|
||
'up-z1.qiniup.com' => lang('华北-河北'),
|
||
'up-z2.qiniup.com' => lang('华南-广东'),
|
||
'up-na0.qiniup.com' => lang('北美-洛杉矶'),
|
||
'up-as0.qiniup.com' => lang('亚太-新加坡'),
|
||
'up-ap-northeast-1.qiniup.com' => lang('亚太-首尔'),
|
||
];
|
||
}
|
||
|
||
/**
|
||
* 初始化入口.
|
||
* @throws Exception
|
||
*/
|
||
protected function init()
|
||
{
|
||
// 读取配置文件
|
||
$this->bucket = sysconf('storage.qiniu_bucket|raw');
|
||
$this->accessKey = sysconf('storage.qiniu_access_key|raw');
|
||
$this->secretKey = sysconf('storage.qiniu_secret_key|raw');
|
||
// 计算链接前缀
|
||
$host = strtolower(sysconf('storage.qiniu_http_domain|raw'));
|
||
$type = strtolower(sysconf('storage.qiniu_http_protocol|raw'));
|
||
if ($type === 'auto') {
|
||
$this->domain = "//{$host}";
|
||
} elseif (in_array($type, ['http', 'https'])) {
|
||
$this->domain = "{$type}://{$host}";
|
||
} else {
|
||
throw new Exception(lang('未配置七牛云域名'));
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 安全BASE64编码
|
||
*/
|
||
private function safeBase64(string $content): string
|
||
{
|
||
return str_replace(['+', '/'], ['-', '_'], base64_encode($content));
|
||
}
|
||
|
||
/**
|
||
* 生成管理凭证
|
||
* @param string $name 文件名称
|
||
* @param string $type 操作类型
|
||
*/
|
||
private function getAccessToken(string $name, string $type = 'stat'): array
|
||
{
|
||
$entry = $this->safeBase64("{$this->bucket}:{$name}");
|
||
$sign = hash_hmac('sha1', "/{$type}/{$entry}\n", $this->secretKey, true);
|
||
return [$entry, "{$this->accessKey}:{$this->safeBase64($sign)}"];
|
||
}
|
||
}
|