Files
Yi.Admin/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/Chat/AiImageService.cs

411 lines
14 KiB
C#
Raw Normal View History

2025-12-26 23:46:36 +08:00
using System.Text.Json;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
2026-01-03 16:00:18 +08:00
using SqlSugar;
2025-12-26 23:46:36 +08:00
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;
2025-12-26 23:46:36 +08:00
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;
2025-12-26 23:46:36 +08:00
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;
/// <summary>
/// AI图片生成服务
/// </summary>
[Authorize]
public class AiImageService : ApplicationService
{
private readonly ISqlSugarRepository<ImageStoreTaskAggregateRoot> _imageTaskRepository;
private readonly IBackgroundJobManager _backgroundJobManager;
private readonly AiBlacklistManager _aiBlacklistManager;
private readonly PremiumPackageManager _premiumPackageManager;
private readonly ModelManager _modelManager;
2025-12-26 23:46:36 +08:00
private readonly IGuidGenerator _guidGenerator;
private readonly IWebHostEnvironment _webHostEnvironment;
2026-01-02 19:26:09 +08:00
private readonly TokenManager _tokenManager;
private readonly ISqlSugarRepository<AiModelEntity> _aiModelRepository;
2026-01-03 01:12:47 +08:00
2025-12-26 23:46:36 +08:00
public AiImageService(
ISqlSugarRepository<ImageStoreTaskAggregateRoot> imageTaskRepository,
IBackgroundJobManager backgroundJobManager,
AiBlacklistManager aiBlacklistManager,
PremiumPackageManager premiumPackageManager,
ModelManager modelManager,
2025-12-26 23:46:36 +08:00
IGuidGenerator guidGenerator,
2026-01-03 01:12:47 +08:00
IWebHostEnvironment webHostEnvironment, TokenManager tokenManager,
ISqlSugarRepository<AiModelEntity> aiModelRepository)
2025-12-26 23:46:36 +08:00
{
_imageTaskRepository = imageTaskRepository;
_backgroundJobManager = backgroundJobManager;
_aiBlacklistManager = aiBlacklistManager;
_premiumPackageManager = premiumPackageManager;
_modelManager = modelManager;
2025-12-26 23:46:36 +08:00
_guidGenerator = guidGenerator;
_webHostEnvironment = webHostEnvironment;
2026-01-02 19:26:09 +08:00
_tokenManager = tokenManager;
_aiModelRepository = aiModelRepository;
2025-12-26 23:46:36 +08:00
}
/// <summary>
/// 生成图片(异步任务)
/// </summary>
/// <param name="input">图片生成输入参数</param>
/// <returns>任务ID</returns>
[HttpPost("ai-image/generate")]
[Authorize]
public async Task<Guid> GenerateAsync([FromBody] ImageGenerationInput input)
{
var userId = CurrentUser.GetId();
2026-01-03 01:12:47 +08:00
2025-12-26 23:46:36 +08:00
// 黑名单校验
await _aiBlacklistManager.VerifiyAiBlacklist(userId);
2026-01-02 19:26:09 +08:00
//校验token
if (input.TokenId is not null)
{
await _tokenManager.ValidateTokenAsync(input.TokenId, input.ModelId);
}
2026-01-03 01:12:47 +08:00
2025-12-26 23:46:36 +08:00
// VIP校验
if (!CurrentUser.IsAiVip())
{
throw new UserFriendlyException("图片生成功能需要VIP用户才能使用请购买VIP后重新登录重试");
}
// 尊享包校验 - 使用ModelManager统一判断
var isPremium = await _modelManager.IsPremiumModelAsync(input.ModelId);
if (isPremium)
2025-12-26 23:46:36 +08:00
{
var availableTokens = await _premiumPackageManager.GetAvailableTokensAsync(userId);
if (availableTokens <= 0)
{
throw new UserFriendlyException("尊享token包用量不足请先购买尊享token包");
}
}
// 创建任务实体
var task = new ImageStoreTaskAggregateRoot
{
Prompt = input.Prompt,
2026-01-02 19:26:09 +08:00
ReferenceImagesPrefixBase64 = input.ReferenceImagesPrefixBase64 ?? new List<string>(),
2025-12-26 23:46:36 +08:00
ReferenceImagesUrl = new List<string>(),
TaskStatus = TaskStatusEnum.Processing,
2026-01-02 19:26:09 +08:00
UserId = userId,
2026-01-03 16:00:18 +08:00
UserName = CurrentUser.UserName,
TokenId = input.TokenId,
ModelId = input.ModelId
2025-12-26 23:46:36 +08:00
};
await _imageTaskRepository.InsertAsync(task);
// 入队后台任务
await _backgroundJobManager.EnqueueAsync(new ImageGenerationJobArgs
{
2026-01-02 19:26:09 +08:00
TaskId = task.Id,
2025-12-26 23:46:36 +08:00
});
2026-01-02 19:26:09 +08:00
return task.Id;
2025-12-26 23:46:36 +08:00
}
/// <summary>
/// 查询任务状态
/// </summary>
/// <param name="taskId">任务ID</param>
/// <returns>任务详情</returns>
[HttpGet("ai-image/task/{taskId}")]
public async Task<ImageTaskOutput> 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,
2026-01-02 19:26:09 +08:00
// ReferenceImagesBase64 = task.ReferenceImagesBase64,
// ReferenceImagesUrl = task.ReferenceImagesUrl,
// StoreBase64 = task.StoreBase64,
2025-12-26 23:46:36 +08:00
StoreUrl = task.StoreUrl,
TaskStatus = task.TaskStatus,
PublishStatus = task.PublishStatus,
Categories = task.Categories,
CreationTime = task.CreationTime,
ErrorInfo = task.ErrorInfo,
2025-12-26 23:46:36 +08:00
};
}
/// <summary>
/// 上传Base64图片转换为URL
/// </summary>
/// <param name="base64Data">Base64图片数据包含前缀如 data:image/png;base64,</param>
/// <returns>图片访问URL</returns>
[HttpPost("ai-image/upload-base64")]
[AllowAnonymous]
2025-12-26 23:46:36 +08:00
public async Task<string> 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];
}
2026-01-03 01:12:47 +08:00
2025-12-26 23:46:36 +08:00
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
);
2025-12-26 23:46:36 +08:00
if (!Directory.Exists(uploadPath))
{
Directory.CreateDirectory(uploadPath);
}
// 保存文件
2025-12-26 23:46:36 +08:00
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}";
2025-12-26 23:46:36 +08:00
}
/// <summary>
/// 分页查询我的任务列表
2025-12-26 23:46:36 +08:00
/// </summary>
[HttpGet("ai-image/my-tasks")]
2026-01-03 16:00:18 +08:00
public async Task<PagedResult<ImageTaskOutput>> GetMyTaskPageAsync([FromQuery] ImageMyTaskPageInput input)
2025-12-26 23:46:36 +08:00
{
var userId = CurrentUser.GetId();
2026-01-03 16:00:18 +08:00
RefAsync<int> total = 0;
var output = await _imageTaskRepository._DbQueryable
2025-12-26 23:46:36 +08:00
.Where(x => x.UserId == userId)
2026-01-03 16:00:18 +08:00
.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)
2025-12-26 23:46:36 +08:00
.Select(x => new ImageTaskOutput
{
Id = x.Id,
Prompt = x.Prompt,
StoreUrl = x.StoreUrl,
TaskStatus = x.TaskStatus,
2026-01-03 01:12:47 +08:00
PublishStatus = x.PublishStatus,
Categories = x.Categories,
CreationTime = x.CreationTime,
ErrorInfo = x.ErrorInfo,
UserName = x.UserName,
UserId = x.UserId,
2026-01-04 12:32:31 +08:00
IsAnonymous = x.IsAnonymous
2025-12-26 23:46:36 +08:00
})
2026-01-03 16:00:18 +08:00
.ToPageListAsync(input.SkipCount, input.MaxResultCount, total);
2025-12-26 23:46:36 +08:00
2026-01-03 16:00:18 +08:00
return new PagedResult<ImageTaskOutput>(total, output);
2025-12-26 23:46:36 +08:00
}
2026-01-04 12:32:31 +08:00
/// <summary>
/// 删除个人图片
/// </summary>
/// <param name="ids"></param>
[HttpDelete("ai-image/my-tasks")]
2026-01-04 22:47:53 +08:00
public async Task DeleteMyTaskAsync([FromQuery] List<Guid> ids)
2026-01-04 12:32:31 +08:00
{
var userId = CurrentUser.GetId();
await _imageTaskRepository.DeleteAsync(x => ids.Contains(x.Id) && x.UserId == userId);
}
/// <summary>
/// 分页查询图片广场(已发布的图片)
/// </summary>
[HttpGet("ai-image/plaza")]
[AllowAnonymous]
2026-01-03 16:00:18 +08:00
public async Task<PagedResult<ImageTaskOutput>> GetPlazaPageAsync([FromQuery] ImagePlazaPageInput input)
{
2026-01-03 16:00:18 +08:00
RefAsync<int> total = 0;
var output = await _imageTaskRepository._DbQueryable
.Where(x => x.PublishStatus == PublishStatusEnum.Published)
.Where(x => x.TaskStatus == TaskStatusEnum.Success)
2026-01-03 16:00:18 +08:00
.WhereIF(input.TaskStatus is not null, x => x.TaskStatus == input.TaskStatus)
.WhereIF(!string.IsNullOrWhiteSpace(input.Prompt), x => x.Prompt.Contains(input.Prompt))
2026-01-04 12:32:31 +08:00
.WhereIF(!string.IsNullOrWhiteSpace(input.Categories),
x => SqlFunc.JsonLike(x.Categories, input.Categories))
.WhereIF(!string.IsNullOrWhiteSpace(input.UserName), x => x.UserName.Contains(input.UserName))
2026-01-03 16:00:18 +08:00
.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,
2026-01-03 16:17:57 +08:00
IsAnonymous = x.IsAnonymous,
StoreUrl = x.StoreUrl,
TaskStatus = x.TaskStatus,
PublishStatus = x.PublishStatus,
Categories = x.Categories,
2026-01-03 16:00:18 +08:00
CreationTime = x.CreationTime,
2026-01-03 16:17:57 +08:00
ErrorInfo = null,
2026-01-03 16:00:18 +08:00
UserName = x.UserName,
UserId = x.UserId,
})
2026-01-04 12:32:31 +08:00
.ToPageListAsync(input.SkipCount, input.MaxResultCount, total);
;
2026-01-03 16:00:18 +08:00
2026-01-03 16:17:57 +08:00
output.ForEach(x =>
{
if (x.IsAnonymous)
{
x.UserName = null;
x.UserId = null;
}
});
2026-01-04 12:32:31 +08:00
2026-01-03 16:00:18 +08:00
return new PagedResult<ImageTaskOutput>(total, output);
}
/// <summary>
/// 发布图片到广场
/// </summary>
[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("该任务已发布");
}
2026-01-03 16:17:57 +08:00
//设置发布
2026-01-04 12:32:31 +08:00
task.SetPublish(input.IsAnonymous, input.Categories);
await _imageTaskRepository.UpdateAsync(task);
}
2026-01-03 01:12:47 +08:00
/// <summary>
/// 获取图片模型列表
/// </summary>
/// <returns></returns>
[HttpPost("ai-image/model")]
2026-01-03 01:45:27 +08:00
[AllowAnonymous]
public async Task<List<ModelGetListOutput>> GetModelAsync()
{
var output = await _aiModelRepository._DbQueryable
.Where(x=>x.IsEnabled==true)
.Where(x => x.ModelType == ModelTypeEnum.Image)
2026-01-03 01:45:27 +08:00
.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;
}
2025-12-26 23:46:36 +08:00
}
/// <summary>
/// 分页结果
/// </summary>
/// <typeparam name="T">数据类型</typeparam>
public class PagedResult<T>
{
/// <summary>
/// 总数
/// </summary>
public long Total { get; set; }
/// <summary>
/// 数据列表
/// </summary>
public List<T> Items { get; set; }
public PagedResult(long total, List<T> items)
{
Total = total;
Items = items;
}
2026-01-03 01:12:47 +08:00
}