refactor: 将图片上传服务移至Infrastructure层
- 移除 HoneyBox.Api 对 HoneyBox.Admin.Business 的引用 - 在 Core 层添加 IImageUploadService 接口 - 在 Infrastructure 层实现 CosImageUploadService - 从数据库 Configs 表读取 COS 配置 - 在 InfrastructureModule 注册服务
This commit is contained in:
parent
a95a2852d4
commit
144117e24d
|
|
@ -1,5 +1,4 @@
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using HoneyBox.Admin.Business.Services.Interfaces;
|
|
||||||
using HoneyBox.Core.Interfaces;
|
using HoneyBox.Core.Interfaces;
|
||||||
using HoneyBox.Model.Base;
|
using HoneyBox.Model.Base;
|
||||||
using HoneyBox.Model.Models.Asset;
|
using HoneyBox.Model.Models.Asset;
|
||||||
|
|
@ -25,7 +24,7 @@ public class UserController : ControllerBase
|
||||||
private readonly IAssetService _assetService;
|
private readonly IAssetService _assetService;
|
||||||
private readonly IVipService _vipService;
|
private readonly IVipService _vipService;
|
||||||
private readonly IQuanYiService _quanYiService;
|
private readonly IQuanYiService _quanYiService;
|
||||||
private readonly IUploadService _uploadService;
|
private readonly IImageUploadService _imageUploadService;
|
||||||
private readonly ILogger<UserController> _logger;
|
private readonly ILogger<UserController> _logger;
|
||||||
|
|
||||||
public UserController(
|
public UserController(
|
||||||
|
|
@ -34,7 +33,7 @@ public class UserController : ControllerBase
|
||||||
IAssetService assetService,
|
IAssetService assetService,
|
||||||
IVipService vipService,
|
IVipService vipService,
|
||||||
IQuanYiService quanYiService,
|
IQuanYiService quanYiService,
|
||||||
IUploadService uploadService,
|
IImageUploadService imageUploadService,
|
||||||
ILogger<UserController> logger)
|
ILogger<UserController> logger)
|
||||||
{
|
{
|
||||||
_userService = userService;
|
_userService = userService;
|
||||||
|
|
@ -42,7 +41,7 @@ public class UserController : ControllerBase
|
||||||
_assetService = assetService;
|
_assetService = assetService;
|
||||||
_vipService = vipService;
|
_vipService = vipService;
|
||||||
_quanYiService = quanYiService;
|
_quanYiService = quanYiService;
|
||||||
_uploadService = uploadService;
|
_imageUploadService = imageUploadService;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -170,7 +169,7 @@ public class UserController : ControllerBase
|
||||||
if (!string.IsNullOrWhiteSpace(request.Imagebase))
|
if (!string.IsNullOrWhiteSpace(request.Imagebase))
|
||||||
{
|
{
|
||||||
// Base64图片上传到腾讯云COS
|
// Base64图片上传到腾讯云COS
|
||||||
var headimgUrl = await _uploadService.UploadBase64ImageAsync(request.Imagebase, $"avatar_{userId}");
|
var headimgUrl = await _imageUploadService.UploadBase64ImageAsync(request.Imagebase, $"avatar_{userId}");
|
||||||
if (!string.IsNullOrWhiteSpace(headimgUrl))
|
if (!string.IsNullOrWhiteSpace(headimgUrl))
|
||||||
{
|
{
|
||||||
updateDto.Headimg = headimgUrl;
|
updateDto.Headimg = headimgUrl;
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,6 @@
|
||||||
<ProjectReference Include="..\HoneyBox.Model\HoneyBox.Model.csproj" />
|
<ProjectReference Include="..\HoneyBox.Model\HoneyBox.Model.csproj" />
|
||||||
<ProjectReference Include="..\HoneyBox.Core\HoneyBox.Core.csproj" />
|
<ProjectReference Include="..\HoneyBox.Core\HoneyBox.Core.csproj" />
|
||||||
<ProjectReference Include="..\HoneyBox.Infrastructure\HoneyBox.Infrastructure.csproj" />
|
<ProjectReference Include="..\HoneyBox.Infrastructure\HoneyBox.Infrastructure.csproj" />
|
||||||
<ProjectReference Include="..\HoneyBox.Admin.Business\HoneyBox.Admin.Business.csproj" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
namespace HoneyBox.Core.Interfaces;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 图片上传服务接口
|
||||||
|
/// </summary>
|
||||||
|
public interface IImageUploadService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 上传Base64编码的图片到云存储
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="base64Image">Base64编码的图片数据(可包含data:image/xxx;base64,前缀)</param>
|
||||||
|
/// <param name="fileNamePrefix">文件名前缀,如 "avatar"</param>
|
||||||
|
/// <returns>上传后的图片URL,失败返回null</returns>
|
||||||
|
Task<string?> UploadBase64ImageAsync(string base64Image, string fileNamePrefix = "image");
|
||||||
|
}
|
||||||
199
server/HoneyBox/src/HoneyBox.Infrastructure/External/Storage/CosImageUploadService.cs
vendored
Normal file
199
server/HoneyBox/src/HoneyBox.Infrastructure/External/Storage/CosImageUploadService.cs
vendored
Normal file
|
|
@ -0,0 +1,199 @@
|
||||||
|
using COSXML;
|
||||||
|
using COSXML.Auth;
|
||||||
|
using COSXML.Model.Object;
|
||||||
|
using HoneyBox.Core.Interfaces;
|
||||||
|
using HoneyBox.Model.Data;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace HoneyBox.Infrastructure.External.Storage;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 腾讯云COS图片上传服务实现
|
||||||
|
/// </summary>
|
||||||
|
public class CosImageUploadService : IImageUploadService
|
||||||
|
{
|
||||||
|
private readonly HoneyBoxDbContext _dbContext;
|
||||||
|
private readonly ILogger<CosImageUploadService> _logger;
|
||||||
|
private const string UploadBasePath = "uploads";
|
||||||
|
|
||||||
|
public CosImageUploadService(
|
||||||
|
HoneyBoxDbContext dbContext,
|
||||||
|
ILogger<CosImageUploadService> logger)
|
||||||
|
{
|
||||||
|
_dbContext = dbContext;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async Task<string?> UploadBase64ImageAsync(string base64Image, string fileNamePrefix = "image")
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(base64Image))
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Base64图片数据为空");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析Base64数据和MIME类型
|
||||||
|
string base64Data;
|
||||||
|
string contentType = "image/png";
|
||||||
|
string extension = ".png";
|
||||||
|
|
||||||
|
if (base64Image.Contains(','))
|
||||||
|
{
|
||||||
|
var parts = base64Image.Split(',');
|
||||||
|
base64Data = parts[1];
|
||||||
|
var header = parts[0];
|
||||||
|
if (header.Contains(':') && header.Contains(';'))
|
||||||
|
{
|
||||||
|
var mimeType = header.Split(':')[1].Split(';')[0];
|
||||||
|
contentType = mimeType;
|
||||||
|
extension = mimeType switch
|
||||||
|
{
|
||||||
|
"image/jpeg" => ".jpg",
|
||||||
|
"image/jpg" => ".jpg",
|
||||||
|
"image/png" => ".png",
|
||||||
|
"image/gif" => ".gif",
|
||||||
|
"image/webp" => ".webp",
|
||||||
|
_ => ".png"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
base64Data = base64Image;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解码Base64数据
|
||||||
|
byte[] imageBytes;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
imageBytes = Convert.FromBase64String(base64Data);
|
||||||
|
}
|
||||||
|
catch (FormatException ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex, "Base64解码失败");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证文件大小 (10MB)
|
||||||
|
if (imageBytes.Length > 10 * 1024 * 1024)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Base64图片大小超过限制: {Size} bytes", imageBytes.Length);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取COS配置
|
||||||
|
var setting = await GetCosSettingAsync();
|
||||||
|
if (setting == null)
|
||||||
|
{
|
||||||
|
_logger.LogError("无法获取COS配置");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成日期目录路径
|
||||||
|
var now = DateTime.Now;
|
||||||
|
var datePath = $"{now.Year}/{now.Month:D2}/{now.Day:D2}";
|
||||||
|
var uniqueFileName = $"{fileNamePrefix}_{now:yyyyMMddHHmmssfff}_{Guid.NewGuid():N}"[..32] + extension;
|
||||||
|
var objectKey = $"{UploadBasePath}/{datePath}/{uniqueFileName}";
|
||||||
|
|
||||||
|
// 创建COS客户端并上传
|
||||||
|
var cosXml = CreateCosXmlServer(setting);
|
||||||
|
var putObjectRequest = new PutObjectRequest(setting.Bucket!, objectKey, imageBytes);
|
||||||
|
putObjectRequest.SetRequestHeader("Content-Type", contentType);
|
||||||
|
|
||||||
|
var result = cosXml.PutObject(putObjectRequest);
|
||||||
|
|
||||||
|
if (result.IsSuccessful())
|
||||||
|
{
|
||||||
|
var url = GenerateAccessUrl(setting.Domain!, objectKey);
|
||||||
|
_logger.LogInformation("Base64图片上传成功: {Url}", url);
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogError("COS上传失败: {Error}", result.httpMessage);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (COSXML.CosException.CosClientException clientEx)
|
||||||
|
{
|
||||||
|
_logger.LogError(clientEx, "COS客户端错误");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
catch (COSXML.CosException.CosServerException serverEx)
|
||||||
|
{
|
||||||
|
_logger.LogError(serverEx, "COS服务端错误: {Info}", serverEx.GetInfo());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Base64图片上传异常");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<CosUploadSetting?> GetCosSettingAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var config = await _dbContext.Configs
|
||||||
|
.Where(c => c.ConfigKey == "uploads")
|
||||||
|
.Select(c => c.ConfigValue)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(config))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return JsonConvert.DeserializeObject<CosUploadSetting>(config);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "获取COS配置失败");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CosXml CreateCosXmlServer(CosUploadSetting setting)
|
||||||
|
{
|
||||||
|
var config = new CosXmlConfig.Builder()
|
||||||
|
.IsHttps(true)
|
||||||
|
.SetRegion(setting.Region!)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var credentialProvider = new DefaultQCloudCredentialProvider(
|
||||||
|
setting.AccessKeyId!,
|
||||||
|
setting.AccessKeySecret!,
|
||||||
|
600);
|
||||||
|
|
||||||
|
return new CosXmlServer(config, credentialProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GenerateAccessUrl(string domain, string objectKey)
|
||||||
|
{
|
||||||
|
var normalizedDomain = domain.TrimEnd('/');
|
||||||
|
if (!normalizedDomain.StartsWith("http://", StringComparison.OrdinalIgnoreCase) &&
|
||||||
|
!normalizedDomain.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
normalizedDomain = $"https://{normalizedDomain}";
|
||||||
|
}
|
||||||
|
var normalizedKey = objectKey.StartsWith('/') ? objectKey : $"/{objectKey}";
|
||||||
|
return $"{normalizedDomain}{normalizedKey}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CosUploadSetting
|
||||||
|
{
|
||||||
|
public string? Type { get; set; }
|
||||||
|
public string? AppId { get; set; }
|
||||||
|
public string? Bucket { get; set; }
|
||||||
|
public string? Region { get; set; }
|
||||||
|
public string? AccessKeyId { get; set; }
|
||||||
|
public string? AccessKeySecret { get; set; }
|
||||||
|
public string? Domain { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.0" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
||||||
<PackageReference Include="StackExchange.Redis" Version="2.10.1" />
|
<PackageReference Include="StackExchange.Redis" Version="2.10.1" />
|
||||||
|
<PackageReference Include="Tencent.QCloud.Cos.Sdk" Version="5.4.44" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using Autofac;
|
using Autofac;
|
||||||
using HoneyBox.Core.Interfaces;
|
using HoneyBox.Core.Interfaces;
|
||||||
using HoneyBox.Infrastructure.Cache;
|
using HoneyBox.Infrastructure.Cache;
|
||||||
|
using HoneyBox.Infrastructure.External.Storage;
|
||||||
|
|
||||||
namespace HoneyBox.Infrastructure.Modules;
|
namespace HoneyBox.Infrastructure.Modules;
|
||||||
|
|
||||||
|
|
@ -21,6 +22,11 @@ public class InfrastructureModule : Module
|
||||||
.As<IRedisService>()
|
.As<IRedisService>()
|
||||||
.SingleInstance();
|
.SingleInstance();
|
||||||
|
|
||||||
|
// 注册图片上传服务
|
||||||
|
builder.RegisterType<CosImageUploadService>()
|
||||||
|
.As<IImageUploadService>()
|
||||||
|
.InstancePerLifetimeScope();
|
||||||
|
|
||||||
// 后续可在此注册其他基础设施服务
|
// 后续可在此注册其他基础设施服务
|
||||||
// 如: 外部服务客户端、消息队列等
|
// 如: 外部服务客户端、消息队列等
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user