using System.Text.Json; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using SqlSugar; using Volo.Abp; using Volo.Abp.Application.Services; using Volo.Abp.BackgroundJobs; using Volo.Abp.Guids; using Volo.Abp.Users; using Yi.Framework.AiHub.Application.Contracts.Dtos; using Yi.Framework.AiHub.Application.Contracts.Dtos.Chat; using Yi.Framework.AiHub.Application.Jobs; using Yi.Framework.AiHub.Domain.Entities.Chat; using Yi.Framework.AiHub.Domain.Entities.Model; using Yi.Framework.AiHub.Domain.Extensions; using Yi.Framework.AiHub.Domain.Managers; using Yi.Framework.AiHub.Domain.Shared.Consts; using Yi.Framework.AiHub.Domain.Shared.Enums; using Yi.Framework.SqlSugarCore.Abstractions; namespace Yi.Framework.AiHub.Application.Services.Chat; /// /// AI图片生成服务 /// [Authorize] public class AiImageService : ApplicationService { private readonly ISqlSugarRepository _imageTaskRepository; private readonly IBackgroundJobManager _backgroundJobManager; private readonly AiBlacklistManager _aiBlacklistManager; private readonly PremiumPackageManager _premiumPackageManager; private readonly ModelManager _modelManager; private readonly IGuidGenerator _guidGenerator; private readonly IWebHostEnvironment _webHostEnvironment; private readonly TokenManager _tokenManager; private readonly ISqlSugarRepository _aiModelRepository; public AiImageService( ISqlSugarRepository imageTaskRepository, IBackgroundJobManager backgroundJobManager, AiBlacklistManager aiBlacklistManager, PremiumPackageManager premiumPackageManager, ModelManager modelManager, IGuidGenerator guidGenerator, IWebHostEnvironment webHostEnvironment, TokenManager tokenManager, ISqlSugarRepository aiModelRepository) { _imageTaskRepository = imageTaskRepository; _backgroundJobManager = backgroundJobManager; _aiBlacklistManager = aiBlacklistManager; _premiumPackageManager = premiumPackageManager; _modelManager = modelManager; _guidGenerator = guidGenerator; _webHostEnvironment = webHostEnvironment; _tokenManager = tokenManager; _aiModelRepository = aiModelRepository; } /// /// 生成图片(异步任务) /// /// 图片生成输入参数 /// 任务ID [HttpPost("ai-image/generate")] [Authorize] public async Task GenerateAsync([FromBody] ImageGenerationInput input) { var userId = CurrentUser.GetId(); // 黑名单校验 await _aiBlacklistManager.VerifiyAiBlacklist(userId); //校验token if (input.TokenId is not null) { await _tokenManager.ValidateTokenAsync(input.TokenId, input.ModelId); } // VIP校验 if (!CurrentUser.IsAiVip()) { throw new UserFriendlyException("图片生成功能需要VIP用户才能使用,请购买VIP后重新登录重试"); } // 尊享包校验 - 使用ModelManager统一判断 var isPremium = await _modelManager.IsPremiumModelAsync(input.ModelId); if (isPremium) { var availableTokens = await _premiumPackageManager.GetAvailableTokensAsync(userId); if (availableTokens <= 0) { throw new UserFriendlyException("尊享token包用量不足,请先购买尊享token包"); } } // 创建任务实体 var task = new ImageStoreTaskAggregateRoot { Prompt = input.Prompt, ReferenceImagesPrefixBase64 = input.ReferenceImagesPrefixBase64 ?? new List(), ReferenceImagesUrl = new List(), TaskStatus = TaskStatusEnum.Processing, UserId = userId, UserName = CurrentUser.UserName, TokenId = input.TokenId, ModelId = input.ModelId }; await _imageTaskRepository.InsertAsync(task); // 入队后台任务 await _backgroundJobManager.EnqueueAsync(new ImageGenerationJobArgs { TaskId = task.Id, }); return task.Id; } /// /// 查询任务状态 /// /// 任务ID /// 任务详情 [HttpGet("ai-image/task/{taskId}")] public async Task GetTaskAsync([FromRoute] Guid taskId) { var userId = CurrentUser.GetId(); var task = await _imageTaskRepository.GetFirstAsync(x => x.Id == taskId && x.UserId == userId); if (task == null) { throw new UserFriendlyException("任务不存在或无权访问"); } return new ImageTaskOutput { Id = task.Id, Prompt = task.Prompt, // ReferenceImagesBase64 = task.ReferenceImagesBase64, // ReferenceImagesUrl = task.ReferenceImagesUrl, // StoreBase64 = task.StoreBase64, StoreUrl = task.StoreUrl, TaskStatus = task.TaskStatus, PublishStatus = task.PublishStatus, Categories = task.Categories, CreationTime = task.CreationTime, ErrorInfo = task.ErrorInfo, }; } /// /// 上传Base64图片转换为URL /// /// Base64图片数据(包含前缀如 data:image/png;base64,) /// 图片访问URL [HttpPost("ai-image/upload-base64")] [AllowAnonymous] public async Task UploadBase64ToUrlAsync([FromBody] string base64Data) { if (string.IsNullOrWhiteSpace(base64Data)) { throw new UserFriendlyException("Base64数据不能为空"); } // 解析Base64数据 string mimeType = "image/png"; string base64Content = base64Data; if (base64Data.Contains(",")) { var parts = base64Data.Split(','); if (parts.Length == 2) { // 提取MIME类型 var header = parts[0]; if (header.Contains(":") && header.Contains(";")) { mimeType = header.Split(':')[1].Split(';')[0]; } base64Content = parts[1]; } } // 获取文件扩展名 var extension = mimeType switch { "image/png" => ".png", "image/jpeg" => ".jpg", "image/jpg" => ".jpg", "image/gif" => ".gif", "image/webp" => ".webp", _ => ".png" }; // 解码Base64 byte[] imageBytes; try { imageBytes = Convert.FromBase64String(base64Content); } catch (FormatException) { throw new UserFriendlyException("Base64格式无效"); } // ============================== // ✅ 按日期创建目录(yyyyMMdd) // ============================== var dateFolder = DateTime.Now.ToString("yyyyMMdd"); var uploadPath = Path.Combine( _webHostEnvironment.ContentRootPath, "wwwroot", "ai-images", dateFolder ); if (!Directory.Exists(uploadPath)) { Directory.CreateDirectory(uploadPath); } // 保存文件 var fileId = _guidGenerator.Create(); var fileName = $"{fileId}{extension}"; var filePath = Path.Combine(uploadPath, fileName); await File.WriteAllBytesAsync(filePath, imageBytes); // 返回包含日期目录的访问URL return $"/wwwroot/ai-images/{dateFolder}/{fileName}"; } /// /// 分页查询我的任务列表 /// [HttpGet("ai-image/my-tasks")] public async Task> GetMyTaskPageAsync([FromQuery] ImageMyTaskPageInput input) { var userId = CurrentUser.GetId(); RefAsync total = 0; var output = await _imageTaskRepository._DbQueryable .Where(x => x.UserId == userId) .WhereIF(input.TaskStatus is not null, x => x.TaskStatus == input.TaskStatus) .WhereIF(!string.IsNullOrWhiteSpace(input.Prompt), x => x.Prompt.Contains(input.Prompt)) .WhereIF(input.PublishStatus is not null, x => x.PublishStatus == input.PublishStatus) .WhereIF(input.StartTime is not null && input.EndTime is not null, x => x.CreationTime >= input.StartTime && x.CreationTime <= input.EndTime) .OrderByDescending(x => x.CreationTime) .Select(x => new ImageTaskOutput { Id = x.Id, Prompt = x.Prompt, StoreUrl = x.StoreUrl, TaskStatus = x.TaskStatus, PublishStatus = x.PublishStatus, Categories = x.Categories, CreationTime = x.CreationTime, ErrorInfo = x.ErrorInfo, UserName = x.UserName, UserId = x.UserId, IsAnonymous = x.IsAnonymous }) .ToPageListAsync(input.SkipCount, input.MaxResultCount, total); return new PagedResult(total, output); } /// /// 删除个人图片 /// /// [HttpDelete("ai-image/my-tasks")] public async Task DeleteMyTaskAsync([FromQuery] List ids) { var userId = CurrentUser.GetId(); await _imageTaskRepository.DeleteAsync(x => ids.Contains(x.Id) && x.UserId == userId); } /// /// 分页查询图片广场(已发布的图片) /// [HttpGet("ai-image/plaza")] [AllowAnonymous] public async Task> GetPlazaPageAsync([FromQuery] ImagePlazaPageInput input) { RefAsync total = 0; var output = await _imageTaskRepository._DbQueryable .Where(x => x.PublishStatus == PublishStatusEnum.Published) .Where(x => x.TaskStatus == TaskStatusEnum.Success) .WhereIF(input.TaskStatus is not null, x => x.TaskStatus == input.TaskStatus) .WhereIF(!string.IsNullOrWhiteSpace(input.Prompt), x => x.Prompt.Contains(input.Prompt)) .WhereIF(!string.IsNullOrWhiteSpace(input.Categories), x => SqlFunc.JsonLike(x.Categories, input.Categories)) .WhereIF(!string.IsNullOrWhiteSpace(input.UserName), x => x.UserName.Contains(input.UserName)) .WhereIF(input.StartTime is not null && input.EndTime is not null, x => x.CreationTime >= input.StartTime && x.CreationTime <= input.EndTime) .OrderByDescending(x => x.CreationTime) .Select(x => new ImageTaskOutput { Id = x.Id, Prompt = x.Prompt, IsAnonymous = x.IsAnonymous, StoreUrl = x.StoreUrl, TaskStatus = x.TaskStatus, PublishStatus = x.PublishStatus, Categories = x.Categories, CreationTime = x.CreationTime, ErrorInfo = null, UserName = x.UserName, UserId = x.UserId, }) .ToPageListAsync(input.SkipCount, input.MaxResultCount, total); ; output.ForEach(x => { if (x.IsAnonymous) { x.UserName = null; x.UserId = null; } }); return new PagedResult(total, output); } /// /// 发布图片到广场 /// [HttpPost("ai-image/publish")] public async Task PublishAsync([FromBody] PublishImageInput input) { var userId = CurrentUser.GetId(); var task = await _imageTaskRepository.GetFirstAsync(x => x.Id == input.TaskId && x.UserId == userId); if (task == null) { throw new UserFriendlyException("任务不存在或无权访问"); } if (task.TaskStatus != TaskStatusEnum.Success) { throw new UserFriendlyException("只有已完成的任务才能发布"); } if (task.PublishStatus == PublishStatusEnum.Published) { throw new UserFriendlyException("该任务已发布"); } //设置发布 task.SetPublish(input.IsAnonymous, input.Categories); await _imageTaskRepository.UpdateAsync(task); } /// /// 获取图片模型列表 /// /// [HttpPost("ai-image/model")] [AllowAnonymous] public async Task> GetModelAsync() { var output = await _aiModelRepository._DbQueryable .Where(x=>x.IsEnabled==true) .Where(x => x.ModelType == ModelTypeEnum.Image) .Where(x => x.ModelApiType == ModelApiTypeEnum.GenerateContent) .OrderByDescending(x => x.OrderNum) .Select(x => new ModelGetListOutput { Id = x.Id, ModelId = x.ModelId, ModelName = x.Name, ModelDescribe = x.Description, Remark = x.Description, IsPremiumPackage = x.IsPremium }).ToListAsync(); return output; } } /// /// 分页结果 /// /// 数据类型 public class PagedResult { /// /// 总数 /// public long Total { get; set; } /// /// 数据列表 /// public List Items { get; set; } public PagedResult(long total, List items) { Total = total; Items = items; } }