333
This commit is contained in:
parent
29b231c417
commit
f72751f62d
|
|
@ -1,5 +1,6 @@
|
|||
using HoneyBox.Admin.Business.Attributes;
|
||||
using HoneyBox.Admin.Business.Models;
|
||||
using HoneyBox.Admin.Business.Models.Upload;
|
||||
using HoneyBox.Admin.Business.Services.Interfaces;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
|
@ -20,7 +21,32 @@ public class UploadController : BusinessControllerBase
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// 上传单个图片
|
||||
/// 获取预签名上传URL(客户端直传COS)
|
||||
/// </summary>
|
||||
/// <param name="request">请求参数</param>
|
||||
/// <returns>预签名URL信息,如果不支持直传则返回null</returns>
|
||||
[HttpPost("presigned-url")]
|
||||
[BusinessPermission("upload:image")]
|
||||
public async Task<IActionResult> GetPresignedUrl([FromBody] GetPresignedUrlRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _uploadService.GetPresignedUploadUrlAsync(request);
|
||||
if (result == null)
|
||||
{
|
||||
// 不支持直传,返回特定标识让前端走服务端上传
|
||||
return Ok(new { supportsDirectUpload = false }, "当前存储配置不支持客户端直传");
|
||||
}
|
||||
return Ok(result, "获取成功");
|
||||
}
|
||||
catch (BusinessException ex)
|
||||
{
|
||||
return Error(ex.Code, ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 上传单个图片(服务端上传,用于本地存储或降级场景)
|
||||
/// </summary>
|
||||
/// <param name="file">图片文件</param>
|
||||
/// <returns>上传结果</returns>
|
||||
|
|
|
|||
|
|
@ -735,6 +735,12 @@ public class UploadSetting
|
|||
[JsonPropertyName("type")]
|
||||
public string? Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 腾讯云AppId
|
||||
/// </summary>
|
||||
[JsonPropertyName("AppId")]
|
||||
public string? AppId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 空间名称/Bucket
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -15,7 +15,12 @@ public interface IStorageProvider
|
|||
string StorageType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 上传文件
|
||||
/// 是否支持客户端直传
|
||||
/// </summary>
|
||||
bool SupportsDirectUpload { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 上传文件(服务端上传)
|
||||
/// </summary>
|
||||
/// <param name="fileStream">文件流</param>
|
||||
/// <param name="fileName">文件名</param>
|
||||
|
|
@ -23,6 +28,15 @@ public interface IStorageProvider
|
|||
/// <returns>上传结果</returns>
|
||||
Task<UploadResult> UploadAsync(Stream fileStream, string fileName, string contentType);
|
||||
|
||||
/// <summary>
|
||||
/// 获取预签名上传URL(客户端直传)
|
||||
/// </summary>
|
||||
/// <param name="fileName">文件名</param>
|
||||
/// <param name="contentType">内容类型</param>
|
||||
/// <param name="expiresInSeconds">URL有效期(秒),默认600秒</param>
|
||||
/// <returns>预签名URL信息</returns>
|
||||
Task<PresignedUrlResponse?> GetPresignedUploadUrlAsync(string fileName, string contentType, int expiresInSeconds = 600);
|
||||
|
||||
/// <summary>
|
||||
/// 删除文件
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ namespace HoneyBox.Admin.Business.Services.Interfaces;
|
|||
public interface IUploadService
|
||||
{
|
||||
/// <summary>
|
||||
/// 上传图片
|
||||
/// 上传图片(服务端上传,用于本地存储或不支持直传的场景)
|
||||
/// </summary>
|
||||
/// <param name="file">上传的文件</param>
|
||||
/// <returns>上传响应</returns>
|
||||
|
|
@ -21,4 +21,11 @@ public interface IUploadService
|
|||
/// <param name="files">上传的文件列表</param>
|
||||
/// <returns>上传响应列表</returns>
|
||||
Task<List<UploadResponse>> UploadImagesAsync(List<IFormFile> files);
|
||||
|
||||
/// <summary>
|
||||
/// 获取预签名上传URL(客户端直传)
|
||||
/// </summary>
|
||||
/// <param name="request">请求参数</param>
|
||||
/// <returns>预签名URL响应,如果不支持直传则返回null</returns>
|
||||
Task<PresignedUrlResponse?> GetPresignedUploadUrlAsync(GetPresignedUrlRequest request);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,9 @@ public class LocalStorageProvider : IStorageProvider
|
|||
/// <inheritdoc />
|
||||
public string StorageType => "1";
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool SupportsDirectUpload => false;
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<UploadResult> UploadAsync(Stream fileStream, string fileName, string contentType)
|
||||
{
|
||||
|
|
@ -101,6 +104,14 @@ public class LocalStorageProvider : IStorageProvider
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<PresignedUrlResponse?> GetPresignedUploadUrlAsync(string fileName, string contentType, int expiresInSeconds = 600)
|
||||
{
|
||||
// 本地存储不支持客户端直传,返回null
|
||||
_logger.LogDebug("本地存储不支持客户端直传");
|
||||
return Task.FromResult<PresignedUrlResponse?>(null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成唯一文件名
|
||||
/// 格式: {timestamp}_{guid}{extension}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ using HoneyBox.Admin.Business.Models.Config;
|
|||
using HoneyBox.Admin.Business.Models.Upload;
|
||||
using HoneyBox.Admin.Business.Services.Interfaces;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace HoneyBox.Admin.Business.Services.Storage;
|
||||
|
||||
|
|
@ -29,6 +31,9 @@ public class TencentCosProvider : IStorageProvider
|
|||
/// <inheritdoc />
|
||||
public string StorageType => "3";
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool SupportsDirectUpload => true;
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<UploadResult> UploadAsync(Stream fileStream, string fileName, string contentType)
|
||||
{
|
||||
|
|
@ -153,6 +158,114 @@ public class TencentCosProvider : IStorageProvider
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<PresignedUrlResponse?> GetPresignedUploadUrlAsync(string fileName, string contentType, int expiresInSeconds = 600)
|
||||
{
|
||||
try
|
||||
{
|
||||
var setting = _getUploadSetting();
|
||||
if (setting == null)
|
||||
{
|
||||
_logger.LogWarning("获取预签名URL失败,配置无效");
|
||||
return Task.FromResult<PresignedUrlResponse?>(null);
|
||||
}
|
||||
|
||||
// 验证必要的配置参数
|
||||
var validationError = ValidateConfig(setting);
|
||||
if (validationError != null)
|
||||
{
|
||||
_logger.LogWarning("获取预签名URL失败: {Error}", validationError);
|
||||
return Task.FromResult<PresignedUrlResponse?>(null);
|
||||
}
|
||||
|
||||
// 生成日期目录路径: uploads/2026/01/19/
|
||||
var now = DateTime.Now;
|
||||
var datePath = $"{now.Year}/{now.Month:D2}/{now.Day:D2}";
|
||||
|
||||
// 生成唯一文件名
|
||||
var uniqueFileName = GenerateUniqueFileName(fileName);
|
||||
|
||||
// 构建COS对象路径
|
||||
var objectKey = $"{UploadBasePath}/{datePath}/{uniqueFileName}";
|
||||
|
||||
// 手动生成预签名URL
|
||||
var presignedUrl = GeneratePresignedUrl(setting, objectKey, "PUT", contentType, expiresInSeconds);
|
||||
|
||||
// 生成访问URL
|
||||
var fileUrl = GenerateAccessUrl(setting.Domain!, objectKey);
|
||||
|
||||
_logger.LogInformation("生成预签名URL成功: {ObjectKey}, 有效期: {ExpiresIn}秒", objectKey, expiresInSeconds);
|
||||
|
||||
return Task.FromResult<PresignedUrlResponse?>(new PresignedUrlResponse
|
||||
{
|
||||
UploadUrl = presignedUrl,
|
||||
FileUrl = fileUrl,
|
||||
ObjectKey = objectKey,
|
||||
ExpiresIn = expiresInSeconds,
|
||||
StorageType = StorageType
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "生成预签名URL异常: {FileName}", fileName);
|
||||
return Task.FromResult<PresignedUrlResponse?>(null);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 手动生成腾讯云COS预签名URL
|
||||
/// </summary>
|
||||
private static string GeneratePresignedUrl(UploadSetting setting, string objectKey, string httpMethod, string contentType, int expiresInSeconds)
|
||||
{
|
||||
var startTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
||||
var endTime = startTime + expiresInSeconds;
|
||||
var keyTime = $"{startTime};{endTime}";
|
||||
|
||||
// 构建COS主机名
|
||||
var host = $"{setting.Bucket}.cos.{setting.Region}.myqcloud.com";
|
||||
var urlPath = $"/{objectKey}";
|
||||
|
||||
// 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}";
|
||||
|
||||
// 6. 构建完整URL
|
||||
var presignedUrl = $"https://{host}{urlPath}?{authorization}";
|
||||
|
||||
return presignedUrl;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// HMAC-SHA1 签名
|
||||
/// </summary>
|
||||
private static string HmacSha1(string key, string data)
|
||||
{
|
||||
using var hmac = new HMACSHA1(Encoding.UTF8.GetBytes(key));
|
||||
var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(data));
|
||||
return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SHA1 哈希
|
||||
/// </summary>
|
||||
private static string Sha1Hash(string data)
|
||||
{
|
||||
var hash = SHA1.HashData(Encoding.UTF8.GetBytes(data));
|
||||
return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证配置参数
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -203,4 +203,67 @@ public class UploadService : IUploadService
|
|||
_logger.LogDebug("使用存储提供者: {StorageType}", provider.StorageType);
|
||||
return provider;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<PresignedUrlResponse?> GetPresignedUploadUrlAsync(GetPresignedUrlRequest request)
|
||||
{
|
||||
// 验证请求参数
|
||||
if (string.IsNullOrWhiteSpace(request.FileName))
|
||||
{
|
||||
throw new BusinessException(BusinessErrorCodes.ValidationFailed, "文件名不能为空");
|
||||
}
|
||||
|
||||
// 验证文件扩展名
|
||||
var extension = Path.GetExtension(request.FileName);
|
||||
if (!IsValidExtension(extension))
|
||||
{
|
||||
throw new BusinessException(BusinessErrorCodes.ValidationFailed, "只支持 jpg、jpeg、png、gif、webp 格式的图片");
|
||||
}
|
||||
|
||||
// 验证文件大小
|
||||
if (request.FileSize > 0 && !IsValidFileSize(request.FileSize))
|
||||
{
|
||||
throw new BusinessException(BusinessErrorCodes.ValidationFailed, "文件大小不能超过10MB");
|
||||
}
|
||||
|
||||
// 获取存储提供者
|
||||
var provider = await GetStorageProviderAsync();
|
||||
|
||||
// 检查是否支持客户端直传
|
||||
if (!provider.SupportsDirectUpload)
|
||||
{
|
||||
_logger.LogDebug("当前存储提供者不支持客户端直传,将使用服务端上传");
|
||||
return null;
|
||||
}
|
||||
|
||||
// 获取预签名URL
|
||||
var contentType = request.ContentType;
|
||||
if (string.IsNullOrWhiteSpace(contentType))
|
||||
{
|
||||
contentType = GetContentTypeByExtension(extension);
|
||||
}
|
||||
|
||||
var result = await provider.GetPresignedUploadUrlAsync(request.FileName, contentType);
|
||||
if (result == null)
|
||||
{
|
||||
throw new BusinessException(BusinessErrorCodes.OperationFailed, "获取上传URL失败,请检查存储配置");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据扩展名获取ContentType
|
||||
/// </summary>
|
||||
private static string GetContentTypeByExtension(string extension)
|
||||
{
|
||||
return extension.ToLowerInvariant() switch
|
||||
{
|
||||
".jpg" or ".jpeg" => "image/jpeg",
|
||||
".png" => "image/png",
|
||||
".gif" => "image/gif",
|
||||
".webp" => "image/webp",
|
||||
_ => "application/octet-stream"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -206,6 +206,8 @@ export const StorageTypeLabels: Record<string, string> = {
|
|||
export interface UploadSetting {
|
||||
/** 存储类型 1本地 2阿里云 3腾讯云 */
|
||||
type?: string
|
||||
/** 腾讯云AppId */
|
||||
AppId?: string
|
||||
/** 空间名称/Bucket */
|
||||
Bucket?: string
|
||||
/** 地域 */
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { request, type ApiResponse } from '@/utils/request'
|
||||
import axios from 'axios'
|
||||
|
||||
/** 上传响应 */
|
||||
export interface UploadResponse {
|
||||
|
|
@ -10,13 +11,131 @@ export interface UploadResponse {
|
|||
fileSize: number
|
||||
}
|
||||
|
||||
/** 预签名URL请求 */
|
||||
export interface GetPresignedUrlRequest {
|
||||
/** 原始文件名 */
|
||||
fileName: string
|
||||
/** 文件MIME类型 */
|
||||
contentType: string
|
||||
/** 文件大小(字节) */
|
||||
fileSize: number
|
||||
}
|
||||
|
||||
/** 预签名URL响应 */
|
||||
export interface PresignedUrlResponse {
|
||||
/** 预签名上传URL */
|
||||
uploadUrl: string
|
||||
/** 文件最终访问URL */
|
||||
fileUrl: string
|
||||
/** 对象Key(COS路径) */
|
||||
objectKey: string
|
||||
/** URL过期时间(秒) */
|
||||
expiresIn: number
|
||||
/** 存储类型 */
|
||||
storageType: string
|
||||
}
|
||||
|
||||
/** 不支持直传的响应 */
|
||||
export interface DirectUploadNotSupportedResponse {
|
||||
supportsDirectUpload: false
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传文件
|
||||
* 获取预签名上传URL
|
||||
* @param params 请求参数
|
||||
* @returns 预签名URL信息
|
||||
*/
|
||||
export function getPresignedUrl(
|
||||
params: GetPresignedUrlRequest
|
||||
): Promise<ApiResponse<PresignedUrlResponse | DirectUploadNotSupportedResponse>> {
|
||||
return request<PresignedUrlResponse | DirectUploadNotSupportedResponse>({
|
||||
url: '/admin/upload/presigned-url',
|
||||
method: 'POST',
|
||||
data: params
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 直接上传文件到COS
|
||||
* @param uploadUrl 预签名上传URL
|
||||
* @param file 文件对象
|
||||
* @param contentType 文件MIME类型
|
||||
* @param onProgress 上传进度回调
|
||||
*/
|
||||
export async function uploadToCos(
|
||||
uploadUrl: string,
|
||||
file: File,
|
||||
contentType: string,
|
||||
onProgress?: (percent: number) => void
|
||||
): Promise<void> {
|
||||
await axios.put(uploadUrl, file, {
|
||||
headers: {
|
||||
'Content-Type': contentType
|
||||
},
|
||||
onUploadProgress: (progressEvent) => {
|
||||
if (progressEvent.total && onProgress) {
|
||||
const percent = Math.round((progressEvent.loaded * 100) / progressEvent.total)
|
||||
onProgress(percent)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传文件(智能选择直传或服务端上传)
|
||||
* @param file 文件对象
|
||||
* @param onProgress 上传进度回调
|
||||
* @returns 上传结果
|
||||
*/
|
||||
export function uploadFile(
|
||||
export async function uploadFile(
|
||||
file: File,
|
||||
onProgress?: (percent: number) => void
|
||||
): Promise<ApiResponse<UploadResponse>> {
|
||||
// 1. 先尝试获取预签名URL
|
||||
const presignedRes = await getPresignedUrl({
|
||||
fileName: file.name,
|
||||
contentType: file.type || 'application/octet-stream',
|
||||
fileSize: file.size
|
||||
})
|
||||
|
||||
// 2. 检查是否支持直传
|
||||
if (presignedRes.code === 0 && presignedRes.data) {
|
||||
const data = presignedRes.data
|
||||
|
||||
// 检查是否支持直传
|
||||
if ('supportsDirectUpload' in data && data.supportsDirectUpload === false) {
|
||||
// 不支持直传,走服务端上传
|
||||
return uploadFileToServer(file, onProgress)
|
||||
}
|
||||
|
||||
// 支持直传,直接上传到COS
|
||||
const presignedData = data as PresignedUrlResponse
|
||||
await uploadToCos(presignedData.uploadUrl, file, file.type || 'application/octet-stream', onProgress)
|
||||
|
||||
// 返回上传结果
|
||||
return {
|
||||
code: 0,
|
||||
message: '上传成功',
|
||||
data: {
|
||||
url: presignedData.fileUrl,
|
||||
fileName: file.name,
|
||||
fileSize: file.size
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获取预签名URL失败,降级到服务端上传
|
||||
console.warn('获取预签名URL失败,降级到服务端上传:', presignedRes.message)
|
||||
return uploadFileToServer(file, onProgress)
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传文件到服务端(降级方案)
|
||||
* @param file 文件对象
|
||||
* @param onProgress 上传进度回调
|
||||
* @returns 上传结果
|
||||
*/
|
||||
export function uploadFileToServer(
|
||||
file: File,
|
||||
onProgress?: (percent: number) => void
|
||||
): Promise<ApiResponse<UploadResponse>> {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
<!-- 已有图片时显示预览 -->
|
||||
<div v-if="modelValue" class="image-preview-wrapper">
|
||||
<el-image
|
||||
ref="imageRef"
|
||||
:src="modelValue"
|
||||
fit="cover"
|
||||
class="preview-image"
|
||||
|
|
@ -136,6 +137,7 @@ const uploading = ref(false)
|
|||
const uploadProgress = ref(0)
|
||||
const urlInputValue = ref('')
|
||||
const errorMessage = ref('')
|
||||
const imageRef = ref<InstanceType<typeof import('element-plus')['ElImage']>>()
|
||||
|
||||
// 计算属性
|
||||
const acceptTypes = computed(() => props.accept)
|
||||
|
|
@ -282,7 +284,8 @@ const handleUpload = async (options: UploadRequestOptions) => {
|
|||
|
||||
// 预览图片
|
||||
const handlePreview = () => {
|
||||
// el-image 组件自带预览功能,点击图片即可预览
|
||||
// 手动触发 el-image 的预览功能
|
||||
imageRef.value?.$el?.querySelector('img')?.click()
|
||||
}
|
||||
|
||||
// 删除图片
|
||||
|
|
|
|||
|
|
@ -54,6 +54,22 @@
|
|||
{{ formData.type === StorageType.Aliyun ? '阿里云OSS配置' : '腾讯云COS配置' }}
|
||||
</el-divider>
|
||||
|
||||
<!-- 腾讯云COS需要AppId -->
|
||||
<el-row v-if="formData.type === StorageType.Tencent" :gutter="24">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="AppId" prop="AppId">
|
||||
<el-input
|
||||
v-model="formData.AppId"
|
||||
placeholder="请输入腾讯云AppId"
|
||||
clearable
|
||||
/>
|
||||
<div class="form-tip">
|
||||
腾讯云账号的AppId,可在控制台账号信息中查看
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="空间名称(Bucket)" prop="Bucket">
|
||||
|
|
@ -174,6 +190,7 @@ const formRef = ref<FormInstance>()
|
|||
// 表单数据
|
||||
interface FormDataType {
|
||||
type: string
|
||||
AppId: string
|
||||
Bucket: string
|
||||
Region: string
|
||||
AccessKeyId: string
|
||||
|
|
@ -183,6 +200,7 @@ interface FormDataType {
|
|||
|
||||
const formData = reactive<FormDataType>({
|
||||
type: StorageType.Local,
|
||||
AppId: '',
|
||||
Bucket: '',
|
||||
Region: '',
|
||||
AccessKeyId: '',
|
||||
|
|
@ -230,6 +248,7 @@ const formRules: FormRules = {
|
|||
const handleTypeChange = () => {
|
||||
// 切换到本地存储时,清空云存储配置
|
||||
if (formData.type === StorageType.Local) {
|
||||
formData.AppId = ''
|
||||
formData.Bucket = ''
|
||||
formData.Region = ''
|
||||
formData.AccessKeyId = ''
|
||||
|
|
@ -249,6 +268,7 @@ const loadData = async () => {
|
|||
const data = res.data.value
|
||||
Object.assign(formData, {
|
||||
type: data.type || StorageType.Local,
|
||||
AppId: data.AppId || '',
|
||||
Bucket: data.Bucket || '',
|
||||
Region: data.Region || '',
|
||||
AccessKeyId: data.AccessKeyId || '',
|
||||
|
|
@ -283,6 +303,9 @@ const handleSave = async () => {
|
|||
|
||||
// 仅在云存储模式下提交云存储配置
|
||||
if (isCloudStorage.value) {
|
||||
if (formData.type === StorageType.Tencent) {
|
||||
submitData.AppId = formData.AppId
|
||||
}
|
||||
submitData.Bucket = formData.Bucket
|
||||
submitData.Region = formData.Region
|
||||
submitData.AccessKeyId = formData.AccessKeyId
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user