智慧教务系统
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

270 lines
9.9 KiB

<?php
// +----------------------------------------------------------------------
// | Niucloud-admin 企业快速开发的多应用管理平台
// +----------------------------------------------------------------------
// | 官方网址:https://www.niucloud.com
// +----------------------------------------------------------------------
// | niucloud团队 版权所有 开源版本可自由商用
// +----------------------------------------------------------------------
// | Author: Niucloud Team
// +----------------------------------------------------------------------
namespace app\service\admin\upload;
use app\service\core\sys\CoreConfigService;
use core\base\BaseAdminService;
use core\exception\AdminException;
/**
* 对象存储直传服务类
*/
class DirectUploadService extends BaseAdminService
{
/**
* 获取上传凭证
* @param string $file_type 文件类型
* @param string $file_name 文件名
* @return array
*/
public function getUploadCredentials($file_type = 'image', $file_name = '')
{
// 获取存储配置
$config_data = (new CoreConfigService())->getConfig('STORAGE');
if (empty($config_data) || empty($config_data['value'])) {
throw new AdminException('存储配置未设置');
}
$storage_config = $config_data['value'];
if (is_string($storage_config)) {
$storage_config = json_decode($storage_config, true);
}
if (empty($storage_config)) {
throw new AdminException('存储配置格式错误');
}
$default_storage = $storage_config['default'] ?? 'local';
if ($default_storage == 'local') {
throw new AdminException('本地存储不支持直传');
}
if (!isset($storage_config[$default_storage])) {
throw new AdminException('存储配置错误');
}
$config = $storage_config[$default_storage];
// 生成文件路径
$file_path = $this->generateFilePath($file_type, $file_name);
// 根据不同存储类型获取凭证
switch ($default_storage) {
case 'tencent':
return $this->getTencentCredentials($config, $file_path, $file_type);
case 'aliyun':
return $this->getAliyunCredentials($config, $file_path, $file_type);
default:
throw new AdminException('不支持的存储类型');
}
}
/**
* 获取腾讯云COS直传凭证
* @param array $config 配置信息
* @param string $file_path 文件路径
* @param string $file_type 文件类型
* @return array
*/
private function getTencentCredentials($config, $file_path, $file_type)
{
// 检查必要的配置
$required_keys = ['bucket', 'region', 'access_key', 'secret_key'];
foreach ($required_keys as $key) {
if (empty($config[$key])) {
throw new AdminException("腾讯云配置缺少必要参数: {$key}");
}
}
// 使用UTC时间戳,确保与腾讯云服务器时间同步
$current_time = time();
$expired = $current_time + 3600; // 1小时过期
// 腾讯云COS要求:q-key-time和q-sign-time使用相同的时间范围
$key_time = $current_time . ';' . $expired;
$sign_time = $key_time; // POST上传时,sign-time通常与key-time相同
// 腾讯云COS POST表单策略 - 必须包含所有签名相关字段
$policy = [
'expiration' => gmdate('Y-m-d\TH:i:s.000\Z', $expired),
'conditions' => [
['starts-with', '$key', dirname($file_path) . '/'],
['eq', '$q-sign-algorithm', 'sha1'],
['eq', '$q-ak', $config['access_key']],
['eq', '$q-key-time', $key_time],
['eq', '$q-sign-time', $sign_time] // 必需字段
]
];
// 根据文件类型设置大小限制
$max_size = $this->getMaxFileSize($file_type);
if ($max_size > 0) {
$policy['conditions'][] = ['content-length-range', 0, $max_size];
}
// 重要:生成policy字符串时使用标准JSON格式
$policy_string = json_encode($policy, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
$policy_base64 = base64_encode($policy_string);
// 腾讯云COS POST签名计算 - 根据官方文档v4签名算法
// 1. 生成 SignKey = HMAC-SHA1(SecretKey, KeyTime)
$sign_key = hash_hmac('sha1', $key_time, $config['secret_key']);
// 2. 生成 StringToSign = SHA1(policy) - 注意:是policy字符串的SHA1,不是Base64
$string_to_sign = sha1($policy_string);
// 3. 生成 Signature = HMAC-SHA1(SignKey, StringToSign)
$signature = hash_hmac('sha1', $string_to_sign, $sign_key);
return [
'storage_type' => 'tencent',
'upload_url' => "https://{$config['bucket']}.cos.{$config['region']}.myqcloud.com",
'credentials' => [
'policy' => $policy_base64,
'q-sign-algorithm' => 'sha1',
'q-ak' => $config['access_key'],
'q-key-time' => $key_time,
'q-sign-time' => $sign_time,
'q-signature' => $signature,
'key' => $file_path,
],
'file_path' => $file_path,
'key' => $file_path,
'domain' => $config['domain'] ?? "https://{$config['bucket']}.cos.{$config['region']}.myqcloud.com",
'max_size' => $max_size,
'expired' => $expired,
// 调试信息(生产环境应移除)
'debug' => [
'policy_string' => $policy_string,
'string_to_sign' => $string_to_sign,
'sign_key' => $sign_key,
'current_time_utc' => gmdate('Y-m-d H:i:s', $current_time) . ' UTC',
'expired_time_utc' => gmdate('Y-m-d H:i:s', $expired) . ' UTC',
'timestamp_current' => $current_time,
'timestamp_expired' => $expired
]
];
}
/**
* 获取阿里云OSS直传凭证
* @param array $config 配置信息
* @param string $file_path 文件路径
* @param string $file_type 文件类型
* @return array
*/
private function getAliyunCredentials($config, $file_path, $file_type)
{
// 检查必要的配置
$required_keys = ['bucket', 'endpoint', 'access_key', 'secret_key'];
foreach ($required_keys as $key) {
if (empty($config[$key])) {
throw new AdminException("阿里云配置缺少必要参数: {$key}");
}
}
$expired = time() + 3600; // 1小时过期
$dir = dirname($file_path) . '/';
// 生成策略
$policy = [
'expiration' => gmdate('Y-m-d\TH:i:s.000\Z', $expired),
'conditions' => [
['starts-with' => '$key', $dir],
]
];
// 根据文件类型设置大小限制
$max_size = $this->getMaxFileSize($file_type);
if ($max_size > 0) {
$policy['conditions'][] = ['content-length-range', 0, $max_size];
}
$policy_encoded = base64_encode(json_encode($policy));
$signature = base64_encode(hash_hmac('sha1', $policy_encoded, $config['secret_key'], true));
return [
'storage_type' => 'aliyun',
'upload_url' => "https://{$config['bucket']}.{$config['endpoint']}",
'credentials' => [
'OSSAccessKeyId' => $config['access_key'],
'policy' => $policy_encoded,
'signature' => $signature,
'key' => $file_path,
'success_action_status' => '200',
],
'file_path' => $file_path,
'domain' => $config['domain'] ?? "https://{$config['bucket']}.{$config['endpoint']}",
'max_size' => $max_size,
'expired' => $expired
];
}
/**
* 生成文件路径
* @param string $file_type 文件类型
* @param string $file_name 文件名
* @return string
*/
private function generateFilePath($file_type, $file_name = '')
{
$type_dir_map = [
'image' => 'upload/attachment/image',
'video' => 'upload/attachment/video',
'document' => 'upload/attachment/document'
];
$dir = $type_dir_map[$file_type] ?? 'upload/attachment/file';
$date_dir = date('Y/m/d');
if (empty($file_name)) {
$file_name = uniqid() . '_${filename}'; // ${filename} 将被前端替换
}
return $dir . '/' . $date_dir . '/' . $file_name;
}
/**
* 根据文件类型获取最大文件大小
* @param string $file_type 文件类型
* @return int 字节数,0表示不限制
*/
private function getMaxFileSize($file_type)
{
$size_map = [
'image' => 10 * 1024 * 1024, // 10MB
'video' => 500 * 1024 * 1024, // 500MB
'document' => 50 * 1024 * 1024, // 50MB
];
return $size_map[$file_type] ?? 10 * 1024 * 1024;
}
/**
* 确认上传完成
* @param array $data 文件信息
* @return array
*/
public function confirmUpload($data)
{
// 这里可以将文件信息保存到附件表,供后续管理使用
// 暂时直接返回文件URL,实际项目中可以根据需要保存到数据库
return [
'url' => $data['file_url'],
'name' => $data['file_name'],
'size' => $data['file_size'],
'type' => $data['file_type'],
'storage_type' => $data['storage_type']
];
}
}