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 HoneyBox.Admin.Business.Services.Interfaces;
|
||||
using HoneyBox.Core.Interfaces;
|
||||
using HoneyBox.Model.Base;
|
||||
using HoneyBox.Model.Models.Asset;
|
||||
|
|
@ -25,7 +24,7 @@ public class UserController : ControllerBase
|
|||
private readonly IAssetService _assetService;
|
||||
private readonly IVipService _vipService;
|
||||
private readonly IQuanYiService _quanYiService;
|
||||
private readonly IUploadService _uploadService;
|
||||
private readonly IImageUploadService _imageUploadService;
|
||||
private readonly ILogger<UserController> _logger;
|
||||
|
||||
public UserController(
|
||||
|
|
@ -34,7 +33,7 @@ public class UserController : ControllerBase
|
|||
IAssetService assetService,
|
||||
IVipService vipService,
|
||||
IQuanYiService quanYiService,
|
||||
IUploadService uploadService,
|
||||
IImageUploadService imageUploadService,
|
||||
ILogger<UserController> logger)
|
||||
{
|
||||
_userService = userService;
|
||||
|
|
@ -42,7 +41,7 @@ public class UserController : ControllerBase
|
|||
_assetService = assetService;
|
||||
_vipService = vipService;
|
||||
_quanYiService = quanYiService;
|
||||
_uploadService = uploadService;
|
||||
_imageUploadService = imageUploadService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
|
|
@ -170,7 +169,7 @@ public class UserController : ControllerBase
|
|||
if (!string.IsNullOrWhiteSpace(request.Imagebase))
|
||||
{
|
||||
// Base64图片上传到腾讯云COS
|
||||
var headimgUrl = await _uploadService.UploadBase64ImageAsync(request.Imagebase, $"avatar_{userId}");
|
||||
var headimgUrl = await _imageUploadService.UploadBase64ImageAsync(request.Imagebase, $"avatar_{userId}");
|
||||
if (!string.IsNullOrWhiteSpace(headimgUrl))
|
||||
{
|
||||
updateDto.Headimg = headimgUrl;
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@
|
|||
<ProjectReference Include="..\HoneyBox.Model\HoneyBox.Model.csproj" />
|
||||
<ProjectReference Include="..\HoneyBox.Core\HoneyBox.Core.csproj" />
|
||||
<ProjectReference Include="..\HoneyBox.Infrastructure\HoneyBox.Infrastructure.csproj" />
|
||||
<ProjectReference Include="..\HoneyBox.Admin.Business\HoneyBox.Admin.Business.csproj" />
|
||||
</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="Newtonsoft.Json" Version="13.0.4" />
|
||||
<PackageReference Include="StackExchange.Redis" Version="2.10.1" />
|
||||
<PackageReference Include="Tencent.QCloud.Cos.Sdk" Version="5.4.44" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using Autofac;
|
||||
using HoneyBox.Core.Interfaces;
|
||||
using HoneyBox.Infrastructure.Cache;
|
||||
using HoneyBox.Infrastructure.External.Storage;
|
||||
|
||||
namespace HoneyBox.Infrastructure.Modules;
|
||||
|
||||
|
|
@ -21,6 +22,11 @@ public class InfrastructureModule : Module
|
|||
.As<IRedisService>()
|
||||
.SingleInstance();
|
||||
|
||||
// 注册图片上传服务
|
||||
builder.RegisterType<CosImageUploadService>()
|
||||
.As<IImageUploadService>()
|
||||
.InstancePerLifetimeScope();
|
||||
|
||||
// 后续可在此注册其他基础设施服务
|
||||
// 如: 外部服务客户端、消息队列等
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user