邹景立 34104dad22 Update plugin headers and add rewrite-model script
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.
2026-02-01 14:24:36 +08:00

243 lines
8.4 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?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)}"];
}
}