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;
}
}