diff --git a/server/MiAssessment/src/MiAssessment.Core/Interfaces/IUploadConfigService.cs b/server/MiAssessment/src/MiAssessment.Core/Interfaces/IUploadConfigService.cs
index 3009c59..e8af761 100644
--- a/server/MiAssessment/src/MiAssessment.Core/Interfaces/IUploadConfigService.cs
+++ b/server/MiAssessment/src/MiAssessment.Core/Interfaces/IUploadConfigService.cs
@@ -16,12 +16,12 @@ public interface IUploadConfigService
}
///
-/// 预签名上传信息(COS POST Object 方式)
+/// 预签名上传信息(COS PUT 预签名URL方式)
///
public class PresignedUploadInfo
{
///
- /// COS上传地址(POST Object 目标URL)
+ /// 预签名上传URL(PUT方式直传)
///
public string UploadUrl { get; set; } = string.Empty;
@@ -30,31 +30,6 @@ public class PresignedUploadInfo
///
public string FileUrl { get; set; } = string.Empty;
- ///
- /// COS对象Key(文件路径)
- ///
- public string Key { get; set; } = string.Empty;
-
- ///
- /// Base64编码的上传策略
- ///
- public string Policy { get; set; } = string.Empty;
-
- ///
- /// SecretId
- ///
- public string SecretId { get; set; } = string.Empty;
-
- ///
- /// 密钥有效时间范围(Unix时间戳格式: startTime;endTime)
- ///
- public string KeyTime { get; set; } = string.Empty;
-
- ///
- /// HMAC-SHA1签名
- ///
- public string Signature { get; set; } = string.Empty;
-
///
/// URL过期时间(秒)
///
diff --git a/server/MiAssessment/src/MiAssessment.Core/Services/UploadConfigService.cs b/server/MiAssessment/src/MiAssessment.Core/Services/UploadConfigService.cs
index aa22b26..34d18d0 100644
--- a/server/MiAssessment/src/MiAssessment.Core/Services/UploadConfigService.cs
+++ b/server/MiAssessment/src/MiAssessment.Core/Services/UploadConfigService.cs
@@ -11,7 +11,7 @@ namespace MiAssessment.Core.Services;
///
/// 上传配置服务实现
-/// 从Admin库读取COS配置,生成POST Object签名供小程序直传
+/// 从Admin库读取COS配置,生成PUT预签名URL供小程序直传
///
public class UploadConfigService : IUploadConfigService
{
@@ -60,53 +60,49 @@ public class UploadConfigService : IUploadConfigService
var uniqueFileName = $"{timestamp}_{guid}{extension}";
var objectKey = $"{UploadBasePath}/{datePath}/{uniqueFileName}";
- // 生成POST Object签名信息
- var host = $"{setting.Bucket}.cos.{setting.Region}.myqcloud.com";
- var uploadUrl = $"https://{host}";
+ // 生成PUT预签名URL
+ var presignedUrl = GeneratePresignedUrl(setting, objectKey, "PUT", contentType, DefaultExpiresInSeconds);
var fileUrl = GenerateAccessUrl(setting.Domain!, objectKey);
- var startTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
- var endTime = startTime + DefaultExpiresInSeconds;
- var keyTime = $"{startTime};{endTime}";
-
- // 生成policy
- var policy = GeneratePostPolicy(objectKey, setting.AccessKeyId!, keyTime);
- var policyBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(policy));
-
- // 生成签名: SignKey -> StringToSign(SHA1 of raw policy) -> Signature
- var signKey = HmacSha1(setting.AccessKeySecret!, keyTime);
- var stringToSign = Sha1Hash(policy);
- var signature = HmacSha1(signKey, stringToSign);
-
- _logger.LogInformation("生成POST Object签名成功: {ObjectKey}", objectKey);
+ _logger.LogInformation("生成预签名URL成功: {ObjectKey}", objectKey);
return new PresignedUploadInfo
{
- UploadUrl = uploadUrl,
+ UploadUrl = presignedUrl,
FileUrl = fileUrl,
- Key = objectKey,
- Policy = policyBase64,
- SecretId = setting.AccessKeyId!,
- KeyTime = keyTime,
- Signature = signature,
ExpiresIn = DefaultExpiresInSeconds
};
}
///
- /// 生成POST Object的policy JSON
- /// 参考: https://cloud.tencent.com/document/product/436/14690
+ /// 生成腾讯云COS预签名URL(PUT方式)
///
- private static string GeneratePostPolicy(string objectKey, string secretId, string keyTime)
+ private static string GeneratePresignedUrl(UploadSetting setting, string objectKey, string httpMethod, string contentType, int expiresInSeconds)
{
- var expiration = DateTimeOffset.UtcNow.AddSeconds(DefaultExpiresInSeconds).ToString("yyyy-MM-ddTHH:mm:ss.fffZ");
+ var startTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
+ var endTime = startTime + expiresInSeconds;
+ var keyTime = $"{startTime};{endTime}";
- // 手动拼接JSON,因为COS要求key中包含连字符(q-sign-algorithm等)
- var policy = $$"""
- {"expiration":"{{expiration}}","conditions":[{"q-sign-algorithm":"sha1"},{"q-ak":"{{secretId}}"},{"q-sign-time":"{{keyTime}}"},{"key":"{{objectKey}}"}]}
- """.Trim();
+ var host = $"{setting.Bucket}.cos.{setting.Region}.myqcloud.com";
+ var urlPath = $"/{objectKey}";
- return policy;
+ // 1. SignKey
+ var signKey = HmacSha1(setting.AccessKeySecret!, keyTime);
+
+ // 2. HttpString
+ var httpString = $"{httpMethod.ToLowerInvariant()}\n{urlPath}\n\nhost={host.ToLowerInvariant()}\n";
+
+ // 3. StringToSign
+ var sha1HttpString = Sha1Hash(httpString);
+ var stringToSign = $"sha1\n{keyTime}\n{sha1HttpString}\n";
+
+ // 4. Signature
+ var signature = HmacSha1(signKey, stringToSign);
+
+ // 5. Authorization
+ var authorization = $"q-sign-algorithm=sha1&q-ak={setting.AccessKeyId}&q-sign-time={keyTime}&q-key-time={keyTime}&q-header-list=host&q-url-param-list=&q-signature={signature}";
+
+ return $"https://{host}{urlPath}?{authorization}";
}
///
diff --git a/uniapp/utils/upload.js b/uniapp/utils/upload.js
index 6e6dbe2..b3782e9 100644
--- a/uniapp/utils/upload.js
+++ b/uniapp/utils/upload.js
@@ -1,7 +1,8 @@
/**
* COS直传工具
- * 通过POST Object方式将文件直传到腾讯云COS
- * 参考: https://cloud.tencent.com/document/product/436/14690
+ * 通过PUT预签名URL将文件直传到腾讯云COS
+ * 使用 uni.getFileSystemManager().readFile 读取文件二进制数据
+ * 再通过 uni.request PUT 方式上传(uni.uploadFile 只支持 POST)
*/
import { getPresignedUploadUrl } from '@/api/user.js'
@@ -41,39 +42,39 @@ export async function chooseAndUploadImage(options = {}) {
const mimeMap = { jpg: 'image/jpeg', jpeg: 'image/jpeg', png: 'image/png', gif: 'image/gif', webp: 'image/webp' }
const contentType = mimeMap[ext] || 'image/png'
- // 2. 获取POST Object签名信息
+ // 2. 获取预签名URL
const presignedRes = await getPresignedUploadUrl(fileName, contentType)
if (!presignedRes || presignedRes.code !== 0 || !presignedRes.data) {
throw new Error(presignedRes?.message || '获取上传地址失败')
}
- const { uploadUrl, fileUrl, key, policy, secretId, keyTime, signature } = presignedRes.data
+ const { uploadUrl, fileUrl } = presignedRes.data
- // 3. POST Object方式直传COS
+ // 3. 读取文件二进制数据,然后用PUT方式上传到COS
await new Promise((resolve, reject) => {
- uni.uploadFile({
- url: uploadUrl,
+ uni.getFileSystemManager().readFile({
filePath: tempFilePath,
- name: 'file',
- formData: {
- 'key': key,
- 'policy': policy,
- 'q-sign-algorithm': 'sha1',
- 'q-ak': secretId,
- 'q-key-time': keyTime,
- 'q-sign-time': keyTime,
- 'q-signature': signature
+ success: (readRes) => {
+ // 使用PUT方法上传二进制数据
+ uni.request({
+ url: uploadUrl,
+ method: 'PUT',
+ data: readRes.data,
+ header: {
+ 'Content-Type': contentType
+ },
+ success: (res) => {
+ if (res.statusCode === 200) {
+ resolve(res)
+ } else {
+ console.error('COS上传失败:', res.statusCode, res.data)
+ reject(new Error(`上传失败,状态码: ${res.statusCode}`))
+ }
+ },
+ fail: (err) => reject(new Error('上传COS失败: ' + (err.errMsg || '网络错误')))
+ })
},
- success: (res) => {
- // COS POST Object成功返回 200 或 204
- if (res.statusCode >= 200 && res.statusCode < 300) {
- resolve(res)
- } else {
- console.error('COS上传失败:', res.statusCode, res.data)
- reject(new Error(`上传失败,状态码: ${res.statusCode}`))
- }
- },
- fail: (err) => reject(new Error(err.errMsg || '上传失败'))
+ fail: (err) => reject(new Error('读取文件失败: ' + err.errMsg))
})
})