diff --git a/YD_Event.Application/Client/Dtos/MyDtos.cs b/YD_Event.Application/Client/Dtos/MyDtos.cs
index 5d4c480..2c4d568 100644
--- a/YD_Event.Application/Client/Dtos/MyDtos.cs
+++ b/YD_Event.Application/Client/Dtos/MyDtos.cs
@@ -600,3 +600,14 @@ public class MyRegistrationDto
///
public string ProjectNames { get; set; }
}
+
+///
+/// 更新头像输出
+///
+public class UpdateAvatarOutput
+{
+ ///
+ /// 头像URL
+ ///
+ public string AvatarUrl { get; set; } = string.Empty;
+}
diff --git a/YD_Event.Application/Client/MyController.cs b/YD_Event.Application/Client/MyController.cs
index 3034738..1245397 100644
--- a/YD_Event.Application/Client/MyController.cs
+++ b/YD_Event.Application/Client/MyController.cs
@@ -1,5 +1,6 @@
using Microsoft.AspNetCore.Mvc;
using YD_Event.Application.Client.Dtos;
+using YD_Event.Application.Common;
using YD_Event.Core.Entities;
using YD_Event.Core.Enum;
using SqlSugar;
@@ -8,6 +9,7 @@ using Microsoft.AspNetCore.Authorization;
using System.Security.Claims;
using Furion.FriendlyException;
using EasyCaching.Core;
+using SkiaSharp;
using ProjectEntity = YD_Event.Core.Entities.Project;
namespace YD_Event.Application.Client;
@@ -31,6 +33,7 @@ public class MyController : IDynamicApiController
private readonly ISqlSugarRepository _merchantCertificationRepository;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IEasyCachingProvider _cachingProvider;
+ private readonly FileService _fileService;
public MyController(
ISqlSugarRepository appUserRepository,
@@ -44,7 +47,8 @@ public class MyController : IDynamicApiController
ISqlSugarRepository eventRepository,
ISqlSugarRepository merchantCertificationRepository,
IHttpContextAccessor httpContextAccessor,
- IEasyCachingProvider cachingProvider)
+ IEasyCachingProvider cachingProvider,
+ FileService fileService)
{
_appUserRepository = appUserRepository;
_teamRepository = teamRepository;
@@ -58,6 +62,7 @@ public class MyController : IDynamicApiController
_merchantCertificationRepository = merchantCertificationRepository;
_httpContextAccessor = httpContextAccessor;
_cachingProvider = cachingProvider;
+ _fileService = fileService;
}
private long GetCurrentUserId()
@@ -658,4 +663,157 @@ public class MyController : IDynamicApiController
if (string.IsNullOrEmpty(mobile) || mobile.Length < 7) return mobile;
return mobile.Substring(0, 3) + "****" + mobile.Substring(7);
}
+
+ ///
+ /// 更新头像 - 上传图片文件
+ ///
+ /// 头像图片文件
+ /// 更新后的头像信息
+ [HttpPost("avatar/upload")]
+ [DisplayName("上传头像")]
+ public async Task UpdateAvatarByFile(IFormFile file)
+ {
+ if (file == null || file.Length == 0)
+ {
+ throw Oops.Bah("请上传头像图片");
+ }
+
+ // 验证文件类型
+ var allowedExtensions = new[] { ".jpg", ".jpeg", ".png", ".gif", ".bmp" };
+ var extension = Path.GetExtension(file.FileName).ToLower();
+ if (!allowedExtensions.Contains(extension))
+ {
+ throw Oops.Bah("仅支持 JPG、JPEG、PNG、GIF、BMP 格式的图片");
+ }
+
+ // 验证文件大小 (最大 5MB)
+ if (file.Length > 5 * 1024 * 1024)
+ {
+ throw Oops.Bah("图片大小不能超过 5MB");
+ }
+
+ // 处理图片
+ using var processedStream = await ProcessAvatarImage(file.OpenReadStream());
+
+ // 上传处理后的图片
+ var now = DateTime.Now;
+ var fileName = $"avatar/{now.Year}/{now.Month:D2}/{now.Day:D2}/{Yitter.IdGenerator.YitIdHelper.NextId()}.jpg";
+ var avatarUrl = await _fileService.UploadFileStreamAsync(fileName, processedStream);
+
+ // 更新用户头像
+ var userId = GetCurrentUserId();
+ var user = await _appUserRepository.GetFirstAsync(u => u.Id == userId);
+ if (user == null)
+ {
+ throw Oops.Bah("用户信息不存在");
+ }
+
+ user.Avatar = avatarUrl;
+ user.UpdatedTime = DateTime.Now;
+ await _appUserRepository.UpdateAsync(user);
+
+ return new UpdateAvatarOutput
+ {
+ AvatarUrl = avatarUrl
+ };
+ }
+
+ ///
+ /// 更新头像 - 使用图片URL
+ ///
+ /// 头像图片URL
+ /// 更新后的头像信息
+ [HttpPost("avatar/url")]
+ [DisplayName("更新头像URL")]
+ public async Task UpdateAvatarByUrl([FromBody] string avatarUrl)
+ {
+ if (string.IsNullOrWhiteSpace(avatarUrl))
+ {
+ throw Oops.Bah("头像URL不能为空");
+ }
+
+ // 验证URL格式
+ if (!Uri.TryCreate(avatarUrl, UriKind.Absolute, out _))
+ {
+ throw Oops.Bah("头像URL格式不正确");
+ }
+
+ // 更新用户头像
+ var userId = GetCurrentUserId();
+ var user = await _appUserRepository.GetFirstAsync(u => u.Id == userId);
+ if (user == null)
+ {
+ throw Oops.Bah("用户信息不存在");
+ }
+
+ user.Avatar = avatarUrl;
+ user.UpdatedTime = DateTime.Now;
+ await _appUserRepository.UpdateAsync(user);
+
+ return new UpdateAvatarOutput
+ {
+ AvatarUrl = avatarUrl
+ };
+ }
+
+ ///
+ /// 处理头像图片 - 压缩和格式转换
+ ///
+ /// 原始图片流
+ /// 处理后的图片流
+ private async Task ProcessAvatarImage(Stream inputStream)
+ {
+ // 将流复制到内存流,以便可以多次读取
+ var memoryStream = new MemoryStream();
+ await inputStream.CopyToAsync(memoryStream);
+ memoryStream.Position = 0;
+
+ // 使用 SkiaSharp 处理图片
+ using var originalBitmap = SKBitmap.Decode(memoryStream);
+ if (originalBitmap == null)
+ {
+ throw Oops.Bah("图片解码失败,请检查图片格式");
+ }
+
+ // 计算缩放后的尺寸(最大 200x200)
+ const int maxSize = 200;
+ int width = originalBitmap.Width;
+ int height = originalBitmap.Height;
+
+ if (width > maxSize || height > maxSize)
+ {
+ if (width > height)
+ {
+ height = (int)(height * (double)maxSize / width);
+ width = maxSize;
+ }
+ else
+ {
+ width = (int)(width * (double)maxSize / height);
+ height = maxSize;
+ }
+ }
+
+ // 创建缩放后的图片
+ using var scaledBitmap = originalBitmap.Resize(new SKImageInfo(width, height), SKFilterQuality.High);
+
+ // 裁剪为正方形(从中心裁剪)
+ int cropSize = Math.Min(width, height);
+ int cropX = (width - cropSize) / 2;
+ int cropY = (height - cropSize) / 2;
+
+ using var croppedBitmap = new SKBitmap(cropSize, cropSize);
+ using var canvas = new SKCanvas(croppedBitmap);
+ canvas.Clear(SKColors.Transparent);
+ canvas.DrawBitmap(scaledBitmap, new SKRect(cropX, cropY, cropX + cropSize, cropY + cropSize), new SKRect(0, 0, cropSize, cropSize));
+
+ // 转换为 JPG 格式并压缩
+ var outputStream = new MemoryStream();
+ using var image = SKImage.FromBitmap(croppedBitmap);
+ using var data = image.Encode(SKEncodedImageFormat.Jpeg, 80); // 80% 质量
+ data.SaveTo(outputStream);
+ outputStream.Position = 0;
+
+ return outputStream;
+ }
}