修改mj转发 新增图片合并
This commit is contained in:
parent
1f71879e0f
commit
8e53379176
@ -27,11 +27,17 @@ namespace lai_transfer.Common.Extensions
|
||||
{
|
||||
string? token = httpContext.GetContextItem("AuthToken");
|
||||
string? baseUrl = httpContext.GetContextItem("BaseUrl");
|
||||
if (!String.IsNullOrWhiteSpace(baseUrl) && baseUrl.EndsWith('/'))
|
||||
{
|
||||
baseUrl = baseUrl.TrimEnd('/');
|
||||
}
|
||||
bool storage = Convert.ToBoolean(httpContext.GetContextItem("Storage") ?? "false");
|
||||
bool splice = Convert.ToBoolean(httpContext.GetContextItem("Splice") ?? "false");
|
||||
if (string.IsNullOrWhiteSpace(token) || string.IsNullOrWhiteSpace(baseUrl))
|
||||
{
|
||||
throw new InvalidOperationException("Authentication token or base URL is not set in the context.");
|
||||
}
|
||||
return new TransferAuthorizationResult(token, baseUrl);
|
||||
return new TransferAuthorizationResult(token, baseUrl, storage, splice, "");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -1,4 +1,7 @@
|
||||
namespace lai_transfer.Common.Filters
|
||||
using lai_transfer.Common.Helper;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace lai_transfer.Common.Filters
|
||||
{
|
||||
public class SplitMJAuthorizationFilter(ILogger<SplitMJAuthorizationFilter> logger) : IEndpointFilter
|
||||
{
|
||||
@ -25,43 +28,43 @@
|
||||
_logger.LogWarning("Authorization header is missing");
|
||||
return TypedResults.Unauthorized();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
// 2. 处理令牌,判断是不是有前缀 删除前缀
|
||||
if (authorization.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
authorization = authorization["Bearer ".Length..];
|
||||
}
|
||||
if (authorization.Contains("?url="))
|
||||
|
||||
var result1 = QueryStringHelper.Parse(authorization);
|
||||
|
||||
string token = result1.Prefix;
|
||||
string baseUrl = result1.GetString("url");
|
||||
// 是否存储
|
||||
bool storage = result1.GetBool("storage", false);
|
||||
|
||||
// 是否拼接
|
||||
bool splice = result1.GetBool("splice", false);
|
||||
|
||||
// token 不能为空
|
||||
if (String.IsNullOrWhiteSpace(token))
|
||||
{
|
||||
// 使用Split方法拆分字符串
|
||||
string[] parts = authorization.Split("?url=", 2);
|
||||
if (parts.Length == 2)
|
||||
{
|
||||
string token = parts[0].Trim();
|
||||
string baseUrl = parts[1].TrimEnd('/');
|
||||
if (string.IsNullOrWhiteSpace(authorization) || string.IsNullOrWhiteSpace(baseUrl))
|
||||
{
|
||||
_logger.LogWarning("令牌或URL为空");
|
||||
_logger.LogWarning("Authorization Invalid");
|
||||
return TypedResults.Unauthorized();
|
||||
}
|
||||
|
||||
if (String.IsNullOrWhiteSpace(baseUrl))
|
||||
{
|
||||
_logger.LogWarning("BaseUrl Invalid");
|
||||
return TypedResults.BadRequest("BaseUrl Invalid");
|
||||
}
|
||||
|
||||
httpContext.Items["AuthToken"] = token;
|
||||
// 将baseUrl也存入HttpContext以便后续使用
|
||||
httpContext.Items["BaseUrl"] = baseUrl;
|
||||
httpContext.Items["Storage"] = storage;
|
||||
httpContext.Items["Splice"] = splice;
|
||||
return await next(context);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("令牌解析错误");
|
||||
return TypedResults.Unauthorized();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("令牌解析错误,没有包含 url");
|
||||
return TypedResults.Unauthorized();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -21,6 +21,8 @@ namespace lai_transfer.Common.Helper
|
||||
|
||||
Translate.Init(reader);
|
||||
|
||||
Qiniu.Init(reader);
|
||||
|
||||
_logger.LogInformation("配置加载完成");
|
||||
}
|
||||
catch (Exception ex)
|
||||
@ -88,6 +90,43 @@ namespace lai_transfer.Common.Helper
|
||||
}
|
||||
|
||||
// 存储Origin配置
|
||||
|
||||
public static class Qiniu
|
||||
{
|
||||
public static string AccessKey { get; internal set; } = string.Empty;
|
||||
public static string SecretKey { get; internal set; } = string.Empty;
|
||||
public static string BucketName { get; internal set; } = string.Empty;
|
||||
public static string Domain { get; internal set; } = string.Empty;
|
||||
public static double MaxFileSize { get; internal set; } = 10; // MB
|
||||
public static bool OriginImage { get; internal set; } = false;
|
||||
/// <summary>
|
||||
/// 初始化Qiniu配置
|
||||
/// </summary>
|
||||
public static void Init(JsonConfigReader reader)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("正在加载Qiniu配置...");
|
||||
// 加载Qiniu配置
|
||||
AccessKey = reader.GetString("QiNiu.AccessKey") ?? string.Empty;
|
||||
SecretKey = reader.GetString("QiNiu.SecretKey") ?? string.Empty;
|
||||
BucketName = reader.GetString("QiNiu.BucketName") ?? string.Empty;
|
||||
Domain = reader.GetString("QiNiu.Domain") ?? string.Empty;
|
||||
MaxFileSize = reader.GetDouble("QiNiu.MaxFileSize") ?? 10; // MB
|
||||
OriginImage = reader.GetBool("QiNiu.OriginImage") ?? false;
|
||||
_logger.LogInformation("QiNiu配置加载完成");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "加载Qiniu配置失败");
|
||||
// 报错,结束程序运行
|
||||
throw new InvalidOperationException("无法加载Qiniu配置,请检查配置文件是否存在或格式是否正确。", ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public static class Origin
|
||||
{
|
||||
// 将private set改为internal set,允许同一程序集中的代码设置属性值
|
||||
|
||||
132
src/Common/Helper/QueryStringHelper.cs
Normal file
132
src/Common/Helper/QueryStringHelper.cs
Normal file
@ -0,0 +1,132 @@
|
||||
namespace lai_transfer.Common.Helper
|
||||
{
|
||||
public static class QueryStringHelper
|
||||
{
|
||||
public class ParseResult
|
||||
{
|
||||
public string Prefix { get; set; }
|
||||
public Dictionary<string, string> Parameters { get; set; }
|
||||
|
||||
public ParseResult()
|
||||
{
|
||||
Parameters = new Dictionary<string, string>();
|
||||
}
|
||||
|
||||
// 获取字符串值
|
||||
public string GetString(string key, string defaultValue = "")
|
||||
{
|
||||
return Parameters.TryGetValue(key, out string value) ? value : defaultValue;
|
||||
}
|
||||
|
||||
// 获取布尔值
|
||||
public bool GetBool(string key, bool defaultValue = false)
|
||||
{
|
||||
if (Parameters.TryGetValue(key, out string value))
|
||||
{
|
||||
// 支持多种布尔值格式
|
||||
string lowerValue = value.ToLower();
|
||||
if (lowerValue == "true" || lowerValue == "1" || lowerValue == "yes")
|
||||
return true;
|
||||
if (lowerValue == "false" || lowerValue == "0" || lowerValue == "no")
|
||||
return false;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
// 获取整数值
|
||||
public int GetInt(string key, int defaultValue = 0)
|
||||
{
|
||||
if (Parameters.TryGetValue(key, out string value))
|
||||
{
|
||||
if (int.TryParse(value, out int result))
|
||||
return result;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
// 获取长整数值
|
||||
public long GetLong(string key, long defaultValue = 0)
|
||||
{
|
||||
if (Parameters.TryGetValue(key, out string value))
|
||||
{
|
||||
if (long.TryParse(value, out long result))
|
||||
return result;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
// 获取双精度浮点数
|
||||
public double GetDouble(string key, double defaultValue = 0.0)
|
||||
{
|
||||
if (Parameters.TryGetValue(key, out string value))
|
||||
{
|
||||
if (double.TryParse(value, out double result))
|
||||
return result;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
// 尝试获取布尔值
|
||||
public bool TryGetBool(string key, out bool result)
|
||||
{
|
||||
result = false;
|
||||
if (Parameters.TryGetValue(key, out string value))
|
||||
{
|
||||
string lowerValue = value.ToLower();
|
||||
if (lowerValue == "true" || lowerValue == "1" || lowerValue == "yes")
|
||||
{
|
||||
result = true;
|
||||
return true;
|
||||
}
|
||||
if (lowerValue == "false" || lowerValue == "0" || lowerValue == "no")
|
||||
{
|
||||
result = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static ParseResult Parse(string input)
|
||||
{
|
||||
var result = new ParseResult();
|
||||
|
||||
if (string.IsNullOrEmpty(input))
|
||||
return result;
|
||||
|
||||
// 找到第一个 ? 的位置
|
||||
int firstQuestionMark = input.IndexOf('?');
|
||||
|
||||
if (firstQuestionMark == -1)
|
||||
{
|
||||
// 没有参数,整个字符串都是前缀
|
||||
result.Prefix = input;
|
||||
return result;
|
||||
}
|
||||
|
||||
// 提取前缀部分(第一个 ? 之前的内容)
|
||||
result.Prefix = input.Substring(0, firstQuestionMark);
|
||||
|
||||
// 从第一个 ? 之后开始处理参数
|
||||
string queryString = input.Substring(firstQuestionMark + 1);
|
||||
|
||||
// 按 ? 分割参数
|
||||
var pairs = queryString.Split('?');
|
||||
|
||||
foreach (var pair in pairs)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(pair))
|
||||
continue;
|
||||
|
||||
var keyValue = pair.Split(new[] { '=' }, 2);
|
||||
if (keyValue.Length == 2)
|
||||
{
|
||||
result.Parameters[keyValue[0]] = keyValue[1];
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
199
src/Common/Helper/RetryHelper.cs
Normal file
199
src/Common/Helper/RetryHelper.cs
Normal file
@ -0,0 +1,199 @@
|
||||
namespace lai_transfer.Common.Helper;
|
||||
|
||||
public class RetryHelper
|
||||
{
|
||||
private static readonly ILogger _logger = LogHelper.GetLogger<RetryHelper>();
|
||||
/// <summary>
|
||||
/// 异步重试执行函数(无返回值)
|
||||
/// </summary>
|
||||
/// <param name="action">要执行的函数</param>
|
||||
/// <param name="retryCount">重试次数</param>
|
||||
/// <param name="delay">重试间隔</param>
|
||||
/// <param name="cancellationToken">取消令牌</param>
|
||||
public static async Task RetryAsync(
|
||||
Func<Task> action,
|
||||
int retryCount = 3,
|
||||
TimeSpan? delay = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(action);
|
||||
|
||||
var exceptions = new List<Exception>();
|
||||
var currentDelay = delay ?? TimeSpan.FromSeconds(1);
|
||||
|
||||
for (int attempt = 0; attempt <= retryCount; attempt++)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (attempt > 0)
|
||||
{
|
||||
_logger?.LogInformation("重试执行,第 {Attempt} 次尝试", attempt);
|
||||
}
|
||||
|
||||
await action().ConfigureAwait(false);
|
||||
return; // 执行成功,直接返回
|
||||
}
|
||||
catch (Exception ex) when (ex is not OperationCanceledException)
|
||||
{
|
||||
exceptions.Add(ex);
|
||||
_logger?.LogWarning(ex, "第 {Attempt} 次执行失败", attempt + 1);
|
||||
|
||||
if (attempt == retryCount)
|
||||
{
|
||||
throw new AggregateException($"在 {retryCount + 1} 次尝试后操作仍失败", exceptions);
|
||||
}
|
||||
|
||||
// 等待一段时间后重试
|
||||
if (currentDelay > TimeSpan.Zero)
|
||||
{
|
||||
_logger?.LogInformation("等待 {Delay} 后重试", currentDelay);
|
||||
await Task.Delay(currentDelay, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步重试执行函数(有返回值)
|
||||
/// </summary>
|
||||
/// <typeparam name="TResult">返回值类型</typeparam>
|
||||
/// <param name="func">要执行的函数</param>
|
||||
/// <param name="retryCount">重试次数</param>
|
||||
/// <param name="delay">重试间隔(默认1s)</param>
|
||||
/// <param name="cancellationToken">取消令牌</param>
|
||||
/// <returns>函数执行结果</returns>
|
||||
public static async Task<TResult> RetryAsync<TResult>(
|
||||
Func<Task<TResult>> func,
|
||||
int retryCount = 3,
|
||||
TimeSpan? delay = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(func);
|
||||
|
||||
var exceptions = new List<Exception>();
|
||||
var currentDelay = delay ?? TimeSpan.FromSeconds(1);
|
||||
|
||||
for (int attempt = 0; attempt <= retryCount; attempt++)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (attempt > 0)
|
||||
{
|
||||
_logger?.LogInformation("重试执行,第 {Attempt} 次尝试", attempt);
|
||||
}
|
||||
|
||||
return await func().ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex) when (ex is not OperationCanceledException)
|
||||
{
|
||||
exceptions.Add(ex);
|
||||
_logger?.LogWarning(ex, "第 {Attempt} 次执行失败", attempt + 1);
|
||||
|
||||
if (attempt == retryCount)
|
||||
{
|
||||
throw new AggregateException($"在 {retryCount + 1} 次尝试后操作仍失败", exceptions);
|
||||
}
|
||||
|
||||
// 等待一段时间后重试
|
||||
if (currentDelay > TimeSpan.Zero)
|
||||
{
|
||||
_logger?.LogInformation("等待 {Delay} 后重试", currentDelay);
|
||||
await Task.Delay(currentDelay, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("不应该执行到这里");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 同步重试执行函数(无返回值)
|
||||
/// </summary>
|
||||
public static void Retry(
|
||||
Action action,
|
||||
int retryCount = 3,
|
||||
TimeSpan? delay = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(action);
|
||||
|
||||
var exceptions = new List<Exception>();
|
||||
var currentDelay = delay ?? TimeSpan.FromSeconds(1);
|
||||
|
||||
for (int attempt = 0; attempt <= retryCount; attempt++)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (attempt > 0)
|
||||
{
|
||||
_logger?.LogInformation("重试执行,第 {Attempt} 次尝试", attempt);
|
||||
}
|
||||
|
||||
action();
|
||||
return; // 执行成功,直接返回
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
exceptions.Add(ex);
|
||||
_logger?.LogWarning(ex, "第 {Attempt} 次执行失败", attempt + 1);
|
||||
|
||||
if (attempt == retryCount)
|
||||
{
|
||||
throw new AggregateException($"在 {retryCount + 1} 次尝试后操作仍失败", exceptions);
|
||||
}
|
||||
|
||||
// 等待一段时间后重试
|
||||
if (currentDelay > TimeSpan.Zero)
|
||||
{
|
||||
_logger?.LogInformation("等待 {Delay} 后重试", currentDelay);
|
||||
Thread.Sleep(currentDelay);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 同步重试执行函数(有返回值)
|
||||
/// </summary>
|
||||
public static TResult Retry<TResult>(
|
||||
Func<TResult> func,
|
||||
int retryCount = 3,
|
||||
TimeSpan? delay = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(func);
|
||||
|
||||
var exceptions = new List<Exception>();
|
||||
var currentDelay = delay ?? TimeSpan.FromSeconds(1);
|
||||
|
||||
for (int attempt = 0; attempt <= retryCount; attempt++)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (attempt > 0)
|
||||
{
|
||||
_logger?.LogInformation("重试执行,第 {Attempt} 次尝试", attempt);
|
||||
}
|
||||
|
||||
return func();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
exceptions.Add(ex);
|
||||
_logger?.LogWarning(ex, "第 {Attempt} 次执行失败", attempt + 1);
|
||||
|
||||
if (attempt == retryCount)
|
||||
{
|
||||
throw new AggregateException($"在 {retryCount + 1} 次尝试后操作仍失败", exceptions);
|
||||
}
|
||||
|
||||
// 等待一段时间后重试
|
||||
if (currentDelay > TimeSpan.Zero)
|
||||
{
|
||||
_logger?.LogInformation("等待 {Delay} 后重试", currentDelay);
|
||||
Thread.Sleep(currentDelay);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("不应该执行到这里");
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,22 @@
|
||||
namespace lai_transfer.Common.Results
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace lai_transfer.Common.Results
|
||||
{
|
||||
public record TransferAuthorizationResult(string Token, string BaseUrl);
|
||||
public class TransferAuthorizationResult
|
||||
{
|
||||
public string Token { get; set; }
|
||||
public string BaseUrl { get; set; }
|
||||
public bool Storage { get; set; }
|
||||
public bool Splice { get; set; }
|
||||
public string FullPath { get; set; }
|
||||
|
||||
public TransferAuthorizationResult(string Token, string BaseUrl, bool Storage, bool Splice, string FullPath)
|
||||
{
|
||||
this.Token = Token;
|
||||
this.BaseUrl = BaseUrl;
|
||||
this.Storage = Storage;
|
||||
this.Splice = Splice;
|
||||
this.FullPath = FullPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
16
src/Common/Types/MJType.cs
Normal file
16
src/Common/Types/MJType.cs
Normal file
@ -0,0 +1,16 @@
|
||||
namespace lai_transfer.Common.Types
|
||||
{
|
||||
public class MJType
|
||||
{
|
||||
public class MJOptionType
|
||||
{
|
||||
public const string IMAGINE = "IMAGINE";
|
||||
|
||||
}
|
||||
|
||||
public class MJStatus
|
||||
{
|
||||
public const string NOT_START = "not_start";
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
src/Configuration/DataBase/db.realm
Normal file
BIN
src/Configuration/DataBase/db.realm
Normal file
Binary file not shown.
BIN
src/Configuration/DataBase/db.realm.lock
Normal file
BIN
src/Configuration/DataBase/db.realm.lock
Normal file
Binary file not shown.
@ -15,5 +15,13 @@
|
||||
"OpenaiGptMaxTokens": 2048,
|
||||
"OpenaiGptTemperature": 0,
|
||||
"TimeOut": 20
|
||||
},
|
||||
"QiNiu": {
|
||||
"AccessKey": "akGxVLyV-xxx",
|
||||
"SecretKey": "a0zHruUrouxCxxx",
|
||||
"BucketName": "open-laitoolxxx",
|
||||
"Domain": "https://opss.laitool.cn",
|
||||
"MaxFileSize": 10,
|
||||
"OriginImage": true
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,17 +1,20 @@
|
||||
using FluentValidation;
|
||||
using lai_transfer.Common.Helper;
|
||||
using lai_transfer.Configuration;
|
||||
using lai_transfer.Model.Entity;
|
||||
using lai_transfer.Services;
|
||||
using lai_transfer.EndpointServices.MJTransferEndpoint;
|
||||
using lai_transfer.Services.Midjourney;
|
||||
using lai_transfer.Services.PeriodicTimeService;
|
||||
using lai_transfer.Services.RealmService;
|
||||
using lai_transfer.Services.Translate;
|
||||
using lai_transfer.Tool.HttpTool;
|
||||
using lai_transfer.Tool.ImageTool;
|
||||
using lai_transfer.Tool.QiniuService;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using Serilog;
|
||||
using System.Text;
|
||||
using Midjourney.Infrastructure.Services;
|
||||
using lai_transfer.Common.Helper;
|
||||
|
||||
namespace lai_transfer
|
||||
{
|
||||
@ -58,6 +61,19 @@ namespace lai_transfer
|
||||
/// <param name="services"></param>
|
||||
private static void AddService(this IServiceCollection services)
|
||||
{
|
||||
// 注册翻译服务
|
||||
services.AddScoped<IGlobalTranslateService, TranslateService>();
|
||||
services.AddSingleton<RealmService>();
|
||||
services.AddHttpClient();
|
||||
|
||||
services.AddScoped<IHttpService, HttpService>();
|
||||
services.AddScoped<IMidjourneyService, MidjourneyService>();
|
||||
services.AddScoped<IQiniuService, QiniuService>();
|
||||
services.AddScoped<IImageService, ImageService>();
|
||||
services.AddScoped<IMJTaskRealmService, MJTaskRealmService>();
|
||||
|
||||
services.AddHostedService<PeriodicTimedService>();
|
||||
|
||||
if (ConfigHelper.Translate.Model == "GPT")
|
||||
{
|
||||
// 翻译服务注入
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
using lai_transfer.Common.Helper;
|
||||
using lai_transfer.Common.Results;
|
||||
using lai_transfer.Endpoints;
|
||||
using lai_transfer.Services.Midjourney;
|
||||
using lai_transfer.Tool.Extensions;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
@ -18,16 +19,10 @@ namespace lai_transfer.EndpointServices.MJTransferEndpoint
|
||||
|
||||
public static async Task<IResult> Handle(string id, HttpContext httpContext)
|
||||
{
|
||||
// 先尝试调用原始API
|
||||
TransferResult? originTransferResult = await TryOriginApiAsync(id);
|
||||
if (originTransferResult != null)
|
||||
{
|
||||
return Results.Text(originTransferResult.Content, originTransferResult.ContentType, statusCode: originTransferResult.StatusCode);
|
||||
}
|
||||
// 原始请求失败,请求到指定的API
|
||||
TransferResult transferResult = await SendUrlTaskFetchById(id, httpContext);
|
||||
// 返回结果
|
||||
return Results.Text(transferResult.Content, transferResult.ContentType, statusCode: transferResult.StatusCode);
|
||||
TransferAuthorizationResult authorizationResult = httpContext.GetAuthorizationItemsAndValidation();
|
||||
authorizationResult.FullPath = httpContext.GetFullRequestPath();
|
||||
IMidjourneyService midjourneyService = httpContext.RequestServices.GetRequiredService<IMidjourneyService>();
|
||||
return await midjourneyService.MidjourneyFetchTaskById(id, authorizationResult);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -24,13 +24,15 @@ namespace lai_transfer.EndpointServices.MJTransferEndpoint
|
||||
{
|
||||
try
|
||||
{
|
||||
//string baseUrl =
|
||||
TransferAuthorizationResult transferAuthorizationResult = httpContext.GetAuthorizationItemsAndValidation();
|
||||
string url = $"{transferAuthorizationResult.BaseUrl}/mj/task/{id}/image-seed";
|
||||
|
||||
TransferResult res = await RetryHelper.RetryAsync<TransferResult>(async () =>
|
||||
{
|
||||
// 设置HttpClient
|
||||
using HttpClient client = new HttpClient();
|
||||
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {transferAuthorizationResult.Token}");
|
||||
client.DefaultRequestHeaders.Add("mj-api-secret", transferAuthorizationResult.Token);
|
||||
client.Timeout = Timeout.InfiniteTimeSpan;
|
||||
|
||||
// 发送请求
|
||||
@ -39,6 +41,9 @@ namespace lai_transfer.EndpointServices.MJTransferEndpoint
|
||||
|
||||
// 返回结果
|
||||
return new TransferResult(content, response.Content.Headers.ContentType?.ToString() ?? "application/json", (int)response.StatusCode);
|
||||
}, 5);
|
||||
|
||||
return res;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@ -27,9 +27,13 @@ namespace lai_transfer.EndpointServices.MJTransferEndpoint
|
||||
{
|
||||
TransferAuthorizationResult authorizationResult = httpContext.GetAuthorizationItemsAndValidation();
|
||||
string url = $"{authorizationResult.BaseUrl}/mj/submit/action";
|
||||
|
||||
TransferResult res = await RetryHelper.RetryAsync<TransferResult>(async () =>
|
||||
{
|
||||
// 设置HttpClient
|
||||
using HttpClient client = new HttpClient();
|
||||
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {authorizationResult.Token}");
|
||||
client.DefaultRequestHeaders.Add("mj-api-secret", authorizationResult.Token);
|
||||
client.Timeout = Timeout.InfiniteTimeSpan;
|
||||
|
||||
string body = JSONHelper.RemoveJsonProperties(model, "notifyHook");
|
||||
@ -37,6 +41,8 @@ namespace lai_transfer.EndpointServices.MJTransferEndpoint
|
||||
var response = await client.PostAsync(url, new StringContent(body, System.Text.Encoding.UTF8, "application/json"));
|
||||
string content = await response.Content.ReadAsStringAsync();
|
||||
return new TransferResult(content, response.Content.Headers.ContentType?.ToString() ?? "application/json", (int)response.StatusCode);
|
||||
}, 5);
|
||||
return res;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@ -26,9 +26,12 @@ namespace lai_transfer.EndpointServices.MJTransferEndpoint
|
||||
{
|
||||
TransferAuthorizationResult authorizationResult = httpContext.GetAuthorizationItemsAndValidation();
|
||||
string url = $"{authorizationResult.BaseUrl}/mj/submit/blend";
|
||||
TransferResult res = await RetryHelper.RetryAsync<TransferResult>(async () =>
|
||||
{
|
||||
// 设置HttpClient
|
||||
using HttpClient client = new HttpClient();
|
||||
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {authorizationResult.Token}");
|
||||
client.DefaultRequestHeaders.Add("mj-api-secret", authorizationResult.Token);
|
||||
client.Timeout = Timeout.InfiniteTimeSpan;
|
||||
// 删除回调参数 notifyHook
|
||||
string body = JSONHelper.RemoveJsonProperties(model, "notifyHook");
|
||||
@ -37,6 +40,8 @@ namespace lai_transfer.EndpointServices.MJTransferEndpoint
|
||||
string content = await response.Content.ReadAsStringAsync();
|
||||
|
||||
return new TransferResult(content, response.Content.Headers.ContentType?.ToString() ?? "application/json", (int)response.StatusCode);
|
||||
}, 5);
|
||||
return res;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@ -26,9 +26,12 @@ namespace lai_transfer.EndpointServices.MJTransferEndpoint
|
||||
{
|
||||
TransferAuthorizationResult authorizationResult = httpContext.GetAuthorizationItemsAndValidation();
|
||||
string url = $"{authorizationResult.BaseUrl}/mj/submit/describe";
|
||||
TransferResult res = await RetryHelper.RetryAsync<TransferResult>(async () =>
|
||||
{
|
||||
// 设置HttpClient
|
||||
using HttpClient client = new HttpClient();
|
||||
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {authorizationResult.Token}");
|
||||
client.DefaultRequestHeaders.Add("mj-api-secret", authorizationResult.Token);
|
||||
client.Timeout = Timeout.InfiniteTimeSpan;
|
||||
|
||||
// 删除回调参数 notifyHook
|
||||
@ -38,6 +41,8 @@ namespace lai_transfer.EndpointServices.MJTransferEndpoint
|
||||
string content = await response.Content.ReadAsStringAsync();
|
||||
|
||||
return new TransferResult(content, response.Content.Headers.ContentType?.ToString() ?? "application/json", (int)response.StatusCode);
|
||||
}, 5);
|
||||
return res;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@ -2,10 +2,8 @@
|
||||
using lai_transfer.Common.Helper;
|
||||
using lai_transfer.Common.Results;
|
||||
using lai_transfer.Endpoints;
|
||||
using lai_transfer.Tool.Extensions;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Newtonsoft.Json;
|
||||
using System.Text.Json;
|
||||
using lai_transfer.Services.Midjourney;
|
||||
using static lai_transfer.Model.Midjourney.MidjourneyRequest;
|
||||
|
||||
namespace lai_transfer.EndpointServices.MJTransferEndpoint
|
||||
{
|
||||
@ -17,133 +15,13 @@ namespace lai_transfer.EndpointServices.MJTransferEndpoint
|
||||
.WithSummary("Midjourney 根据ID列表查询任务")
|
||||
.WithMJAuthorizationHeader();
|
||||
|
||||
private static async Task<IResult> Handle(JsonElement model, HttpContext httpContext)
|
||||
{
|
||||
|
||||
// 先尝试调用原始API
|
||||
TransferResult? originTransferResult = await TryOriginApiAsync(model);
|
||||
if (originTransferResult != null)
|
||||
{
|
||||
return Results.Text(originTransferResult.Content, originTransferResult.ContentType, statusCode: originTransferResult.StatusCode);
|
||||
}
|
||||
|
||||
TransferResult transferResult = await SendOriginalFetchListByCondition(model, httpContext);
|
||||
return Results.Text(transferResult.Content, transferResult.ContentType, statusCode: transferResult.StatusCode);
|
||||
}
|
||||
|
||||
private static async Task<TransferResult> SendOriginalFetchListByCondition(JsonElement model, HttpContext httpContext)
|
||||
{
|
||||
try
|
||||
private static async Task<IResult> Handle(FetchListByCondition model, HttpContext httpContext)
|
||||
{
|
||||
TransferAuthorizationResult transferAuthorizationResult = httpContext.GetAuthorizationItemsAndValidation();
|
||||
string url = $"{transferAuthorizationResult.BaseUrl}/mj/task/list-by-condition";
|
||||
// 设置HttpClient
|
||||
using HttpClient client = new HttpClient();
|
||||
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {transferAuthorizationResult.Token}");
|
||||
client.Timeout = Timeout.InfiniteTimeSpan;
|
||||
|
||||
string body = model.GetRawText();
|
||||
// 发送请求
|
||||
var response = await client.PostAsync(url, new StringContent(body, System.Text.Encoding.UTF8, "application/json"));
|
||||
string content = await response.Content.ReadAsStringAsync();
|
||||
return new TransferResult(content, response.Content.Headers.ContentType?.ToString() ?? "application/json", (int)response.StatusCode);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"转发 {httpContext.GetFullRequestPath()} 失败");
|
||||
// 处理异常,返回错误信息
|
||||
return new TransferResult(ex.Message, "application/json", StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
IMidjourneyService midjourneyService = httpContext.RequestServices.GetRequiredService<IMidjourneyService>();
|
||||
|
||||
/// <summary>
|
||||
/// 获取原始数据信息 bzu
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
private static async Task<TransferResult?> TryOriginApiAsync(JsonElement model)
|
||||
{
|
||||
// 如果要访问任意JSON节点,可以使用JsonConfigReader
|
||||
//var reader = new JsonConfigReader("Configuration/config/transfer.json");
|
||||
string? baseUrl = ConfigHelper.Origin.BaseUrl;
|
||||
string? token = ConfigHelper.Origin.Token;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(baseUrl) || string.IsNullOrWhiteSpace(token))
|
||||
{
|
||||
_logger.LogWarning("配置文件中未找到 Origin.BaseUrl 或 Origin.Token");
|
||||
return null;
|
||||
}
|
||||
|
||||
string originUrl = $"{baseUrl.TrimEnd('/')}/mj/task/list-by-condition";
|
||||
|
||||
try
|
||||
{
|
||||
using var client = new HttpClient();
|
||||
client.DefaultRequestHeaders.Add("Authorization", token);
|
||||
client.Timeout = TimeSpan.FromSeconds(30);
|
||||
|
||||
string body = model.GetRawText();
|
||||
var response = await client.PostAsync(originUrl, new StringContent(body, System.Text.Encoding.UTF8, "application/json"));
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
|
||||
// 判断是不是返回空
|
||||
if ((int)response.StatusCode == 204 || string.IsNullOrWhiteSpace(content))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
_logger.LogWarning($"源API调用 /mj/task/list-by-condition 返回错误状态码, StatusCode: {response.StatusCode}");
|
||||
return null;
|
||||
}
|
||||
|
||||
// 有返回结果 这这边开始处理返回的数据信息
|
||||
List<Dictionary<string, object>>? properties = ProcessTaskArrayData(content);
|
||||
if (properties != null && properties.Count > 0)
|
||||
{
|
||||
return new TransferResult(
|
||||
JsonConvert.SerializeObject(properties),
|
||||
"application/json",
|
||||
StatusCodes.Status200OK);
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "原始API /mj/task/list-by-condition 调用失败");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static List<Dictionary<string, object>>? ProcessTaskArrayData(string content)
|
||||
{
|
||||
try
|
||||
{
|
||||
List<Dictionary<string, object>> result = [];
|
||||
// 解析 JSON 数据
|
||||
var jsonObject = JsonConvert.DeserializeObject<List<Dictionary<string, object>>>(content);
|
||||
if (jsonObject == null || jsonObject.Count == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
// 处理每个任务数据
|
||||
for (int i = 0; i < jsonObject.Count; i++)
|
||||
{
|
||||
var properties = MJGetFetchIdService.ProcessTaskObjectData(jsonObject[i]);
|
||||
result.Add(properties);
|
||||
}
|
||||
|
||||
// 返回数据
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "处理任务数据失败");
|
||||
return null;
|
||||
}
|
||||
return await midjourneyService.FetchListByCondition(model, transferAuthorizationResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,7 +2,8 @@
|
||||
using lai_transfer.Common.Helper;
|
||||
using lai_transfer.Common.Results;
|
||||
using lai_transfer.Endpoints;
|
||||
using lai_transfer.Services;
|
||||
using lai_transfer.Services.Midjourney;
|
||||
using lai_transfer.Services.Translate;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
@ -20,8 +21,7 @@ namespace lai_transfer.EndpointServices.MJTransferEndpoint
|
||||
|
||||
private static async Task<IResult> Handle(JsonElement model, HttpContext httpContext)
|
||||
{
|
||||
(string content, string contentType, int statusCode) = await SendOriginalImagine(model, httpContext);
|
||||
return Results.Text(content, contentType, statusCode: statusCode);
|
||||
return await SendOriginalImagine(model, httpContext);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -30,111 +30,23 @@ namespace lai_transfer.EndpointServices.MJTransferEndpoint
|
||||
/// <param name="model"></param>
|
||||
/// <param name="httpContext"></param>
|
||||
/// <returns></returns>
|
||||
private static async Task<(string content, string contentType, int statusCode)> SendOriginalImagine(JsonElement model, HttpContext httpContext)
|
||||
private static async Task<IResult> SendOriginalImagine(JsonElement model, HttpContext httpContext)
|
||||
{
|
||||
try
|
||||
{
|
||||
TransferAuthorizationResult authorizationResult = httpContext.GetAuthorizationItemsAndValidation();
|
||||
string url = $"{authorizationResult.BaseUrl}/mj/submit/imagine";
|
||||
string requestUrl = httpContext.GetFullRequestPath();
|
||||
authorizationResult.FullPath = requestUrl;
|
||||
IMidjourneyService midjourneyService = httpContext.RequestServices.GetRequiredService<IMidjourneyService>();
|
||||
|
||||
// 设置HttpClient
|
||||
using HttpClient client = new HttpClient();
|
||||
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {authorizationResult.Token}");
|
||||
client.Timeout = Timeout.InfiniteTimeSpan;
|
||||
|
||||
// 强制翻译提示词
|
||||
string prompt = JSONHelper.GetJsonPropertyString(model, "prompt") ?? string.Empty;
|
||||
|
||||
// 使用HttpContext扩展方法获取翻译服务
|
||||
var translateService = ServiceLocator.GetRequiredService<ITranslateService>();
|
||||
prompt = await TranslatePrompt(prompt, translateService);
|
||||
|
||||
model = JSONHelper.SetJsonProperty(model, "prompt", prompt);
|
||||
|
||||
// 删除回调参数 notifyHook
|
||||
string body = JSONHelper.RemoveJsonProperties(model, "notifyHook");
|
||||
//return (body, "application/json", 200);
|
||||
// 发送请求
|
||||
var response = await client.PostAsync(url, new StringContent(body, Encoding.UTF8, "application/json"));
|
||||
string content = await response.Content.ReadAsStringAsync();
|
||||
return (content, response.Content.Headers.ContentType?.ToString() ?? "application/json", (int)response.StatusCode);
|
||||
IResult t = await midjourneyService.MidjourneyImagine(model, authorizationResult);
|
||||
return t;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"请求 {httpContext.GetFullRequestPath()} 失败");
|
||||
// 处理异常,返回错误信息
|
||||
return (ex.Message, "application/json", StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 翻译提示词
|
||||
/// </summary>
|
||||
/// <param name="prompt">原始提示词</param>
|
||||
/// <param name="translateService">翻译服务</param>
|
||||
/// <returns>翻译后的提示词</returns>
|
||||
public static async Task<string> TranslatePrompt(string prompt, ITranslateService translateService)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(prompt))
|
||||
return prompt;
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
// 检查是否启用翻译功能
|
||||
if (!ConfigHelper.Translate.Enable)
|
||||
return prompt;
|
||||
|
||||
// 如果不包含中文,直接返回
|
||||
if (string.IsNullOrWhiteSpace(prompt) || !translateService.ContainsChinese(prompt))
|
||||
return prompt;
|
||||
|
||||
string paramStr = "";
|
||||
var paramMatcher = Regex.Match(prompt, "\\x20+--[a-z]+.*$", RegexOptions.IgnoreCase);
|
||||
if (paramMatcher.Success)
|
||||
{
|
||||
paramStr = paramMatcher.Value;
|
||||
}
|
||||
string promptWithoutParam = prompt.Substring(0, prompt.Length - paramStr.Length);
|
||||
List<string> imageUrls = new List<string>();
|
||||
var imageMatcher = Regex.Matches(promptWithoutParam, "https?://[a-z0-9-_:@&?=+,.!/~*'%$]+\\x20+", RegexOptions.IgnoreCase);
|
||||
foreach (Match match in imageMatcher)
|
||||
{
|
||||
imageUrls.Add(match.Value);
|
||||
}
|
||||
string text = promptWithoutParam;
|
||||
foreach (string imageUrl in imageUrls)
|
||||
{
|
||||
text = text.Replace(imageUrl, "");
|
||||
}
|
||||
text = text.Trim();
|
||||
if (!string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
text = await translateService.TranslateToEnglish(text);
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(paramStr))
|
||||
{
|
||||
// 当有 --no 参数时, 翻译 --no 参数, 并替换原参数
|
||||
// --sref https://mjcdn.googlec.cc/1.jpg --no aa, bb, cc
|
||||
var paramNomatcher = Regex.Match(paramStr, "--no\\s+(.*?)(?=--|$)");
|
||||
if (paramNomatcher.Success)
|
||||
{
|
||||
string paramNoStr = paramNomatcher.Groups[1].Value.Trim();
|
||||
string paramNoStrEn = await translateService.TranslateToEnglish(paramNoStr);
|
||||
|
||||
// 提取 --no 之前的参数
|
||||
paramStr = paramStr.Substring(0, paramNomatcher.Index);
|
||||
|
||||
// 替换 --no 参数
|
||||
paramStr = paramStr + paramNomatcher.Result("--no " + paramNoStrEn + " ");
|
||||
}
|
||||
}
|
||||
return string.Concat(imageUrls) + text.Trim() + paramStr;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.LogError(ex, "翻译提示词失败,使用原始提示词");
|
||||
return prompt;
|
||||
return Results.BadRequest("request error : " + ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,7 +2,8 @@
|
||||
using lai_transfer.Common.Helper;
|
||||
using lai_transfer.Common.Results;
|
||||
using lai_transfer.Endpoints;
|
||||
using lai_transfer.Services;
|
||||
using lai_transfer.Services.Translate;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace lai_transfer.EndpointServices.MJTransferEndpoint
|
||||
@ -27,26 +28,35 @@ namespace lai_transfer.EndpointServices.MJTransferEndpoint
|
||||
{
|
||||
TransferAuthorizationResult transferAuthorizationResult = httpContext.GetAuthorizationItemsAndValidation();
|
||||
string url = $"{transferAuthorizationResult.BaseUrl}/mj/submit/modal";
|
||||
// 设置HttpClient
|
||||
using HttpClient client = new HttpClient();
|
||||
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {transferAuthorizationResult.Token}");
|
||||
client.Timeout = Timeout.InfiniteTimeSpan;
|
||||
|
||||
// 强制翻译提示词
|
||||
string prompt = JSONHelper.GetJsonPropertyString(model, "prompt") ?? string.Empty;
|
||||
if (string.IsNullOrWhiteSpace(prompt))
|
||||
{
|
||||
prompt = JSONHelper.GetJsonPropertyString(model, "Prompt") ?? string.Empty;
|
||||
}
|
||||
|
||||
// 使用HttpContext扩展方法获取翻译服务
|
||||
var translateService = ServiceLocator.GetRequiredService<ITranslateService>();
|
||||
prompt = await MJPostImagineService.TranslatePrompt(prompt, translateService);
|
||||
var translateService = ServiceLocator.GetRequiredService<IGlobalTranslateService>();
|
||||
prompt = await translateService.TranslatePrompt(prompt);
|
||||
|
||||
model = JSONHelper.SetJsonProperty(model, "prompt", prompt);
|
||||
|
||||
// 删除回调参数 notifyHook
|
||||
string body = JSONHelper.RemoveJsonProperties(model, "notifyHook");
|
||||
// 发送请求
|
||||
|
||||
TransferResult res = await RetryHelper.RetryAsync<TransferResult>(async () =>
|
||||
{
|
||||
// 设置HttpClient
|
||||
using HttpClient client = new HttpClient();
|
||||
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {transferAuthorizationResult.Token}");
|
||||
client.DefaultRequestHeaders.Add("mj-api-secret", transferAuthorizationResult.Token);
|
||||
client.Timeout = Timeout.InfiniteTimeSpan;
|
||||
var response = await client.PostAsync(url, new StringContent(body, System.Text.Encoding.UTF8, "application/json"));
|
||||
string content = await response.Content.ReadAsStringAsync();
|
||||
return new TransferResult(content, response.Content.Headers.ContentType?.ToString() ?? "application/json", (int)response.StatusCode);
|
||||
}, 5);
|
||||
return res;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
using lai_transfer.Common.Helper;
|
||||
using lai_transfer.Common.Results;
|
||||
using lai_transfer.Endpoints;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace lai_transfer.EndpointServices.MJTransferEndpoint
|
||||
@ -26,17 +27,22 @@ namespace lai_transfer.EndpointServices.MJTransferEndpoint
|
||||
{
|
||||
TransferAuthorizationResult transferAuthorizationResult = httpContext.GetAuthorizationItemsAndValidation();
|
||||
string url = $"{transferAuthorizationResult.BaseUrl}/mj/submit/shorten";
|
||||
// 删除回调参数 notifyHook
|
||||
string body = JSONHelper.RemoveJsonProperties(model, "notifyHook");
|
||||
|
||||
TransferResult res = await RetryHelper.RetryAsync<TransferResult>(async () =>
|
||||
{
|
||||
// 设置HttpClient
|
||||
using HttpClient client = new HttpClient();
|
||||
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {transferAuthorizationResult.Token}");
|
||||
client.DefaultRequestHeaders.Add("mj-api-secret", transferAuthorizationResult.Token);
|
||||
client.Timeout = Timeout.InfiniteTimeSpan;
|
||||
// 删除回调参数 notifyHook
|
||||
string body = JSONHelper.RemoveJsonProperties(model, "notifyHook");
|
||||
// 发送请求
|
||||
var response = await client.PostAsync(url, new StringContent(body, System.Text.Encoding.UTF8, "application/json"));
|
||||
string content = await response.Content.ReadAsStringAsync();
|
||||
return new TransferResult(content, response.Content.Headers.ContentType?.ToString() ?? "application/json", (int)response.StatusCode);
|
||||
}, 5);
|
||||
return res;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
using lai_transfer.Common.Helper;
|
||||
using lai_transfer.Common.Results;
|
||||
using lai_transfer.Endpoints;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace lai_transfer.EndpointServices.MJTransferEndpoint
|
||||
@ -26,17 +27,22 @@ namespace lai_transfer.EndpointServices.MJTransferEndpoint
|
||||
{
|
||||
TransferAuthorizationResult transferAuthorizationResult = httpContext.GetAuthorizationItemsAndValidation();
|
||||
string url = $"{transferAuthorizationResult.BaseUrl}/mj/insight-face/swap";
|
||||
// 删除回调参数 notifyHook
|
||||
string body = JSONHelper.RemoveJsonProperties(model, "notifyHook");
|
||||
|
||||
TransferResult res = await RetryHelper.RetryAsync<TransferResult>(async () =>
|
||||
{
|
||||
// 设置HttpClient
|
||||
using HttpClient client = new();
|
||||
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {transferAuthorizationResult.Token}");
|
||||
client.DefaultRequestHeaders.Add("mj-api-secret", transferAuthorizationResult.Token);
|
||||
client.Timeout = Timeout.InfiniteTimeSpan;
|
||||
// 删除回调参数 notifyHook
|
||||
string body = JSONHelper.RemoveJsonProperties(model, "notifyHook");
|
||||
// 发送请求
|
||||
var response = await client.PostAsync(url, new StringContent(body, System.Text.Encoding.UTF8, "application/json"));
|
||||
string content = await response.Content.ReadAsStringAsync();
|
||||
return new TransferResult(content, response.Content.Headers.ContentType?.ToString() ?? "application/json", (int)response.StatusCode);
|
||||
}, 5);
|
||||
return res;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@ -1,88 +0,0 @@
|
||||
using lai_transfer.Common.Extensions;
|
||||
using lai_transfer.Common.Helper;
|
||||
using lai_transfer.Services;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace lai_transfer.Examples
|
||||
{
|
||||
/// <summary>
|
||||
/// 展示如何使用服务获取扩展方法的示例
|
||||
/// </summary>
|
||||
public static class ServiceUsageExamples
|
||||
{
|
||||
/// <summary>
|
||||
/// 在有HttpContext的场景下使用服务
|
||||
/// </summary>
|
||||
/// <param name="httpContext">Http上下文</param>
|
||||
public static async Task ExampleWithHttpContext(HttpContext httpContext)
|
||||
{
|
||||
// 方式1:使用HttpContext扩展方法获取服务
|
||||
var translateService = httpContext.GetRequiredService<ITranslateService>();
|
||||
var loggerFactory = httpContext.GetService<ILoggerFactory>();
|
||||
var logger = loggerFactory?.CreateLogger("ServiceUsageExamples");
|
||||
|
||||
// 使用服务
|
||||
string result = await translateService.TranslateToEnglish("你好世界");
|
||||
logger?.LogInformation($"翻译结果: {result}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在没有HttpContext的场景下使用服务(如静态方法、后台任务等)
|
||||
/// </summary>
|
||||
public static async Task ExampleWithoutHttpContext()
|
||||
{
|
||||
// 方式2:使用全局服务定位器获取服务
|
||||
var translateService = ServiceLocator.GetRequiredService<ITranslateService>();
|
||||
var loggerFactory = ServiceLocator.GetService<ILoggerFactory>();
|
||||
var logger = loggerFactory?.CreateLogger("ServiceUsageExamples");
|
||||
|
||||
// 使用服务
|
||||
string result = await translateService.TranslateToEnglish("你好世界");
|
||||
logger?.LogInformation($"翻译结果: {result}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在需要服务作用域的场景下使用
|
||||
/// </summary>
|
||||
public static async Task ExampleWithScope()
|
||||
{
|
||||
// 创建新的作用域
|
||||
using var scope = ServiceLocator.CreateScope();
|
||||
var scopedServices = scope.ServiceProvider;
|
||||
|
||||
// 从作用域中获取服务
|
||||
var translateService = scopedServices.GetRequiredService<ITranslateService>();
|
||||
|
||||
// 使用服务
|
||||
string result = await translateService.TranslateToEnglish("你好世界");
|
||||
|
||||
// 作用域结束时会自动释放资源
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在端点处理器中的使用示例
|
||||
/// </summary>
|
||||
public static async Task<IResult> EndpointExample(HttpContext httpContext)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 直接从HttpContext获取服务,无需在方法参数中声明
|
||||
var translateService = httpContext.GetRequiredService<ITranslateService>();
|
||||
var loggerFactory = httpContext.GetRequiredService<ILoggerFactory>();
|
||||
var logger = loggerFactory.CreateLogger("ServiceUsageExamples");
|
||||
|
||||
// 业务逻辑
|
||||
string prompt = "测试文本";
|
||||
string translatedPrompt = await translateService.TranslateToEnglish(prompt);
|
||||
|
||||
logger.LogInformation($"翻译完成: {prompt} -> {translatedPrompt}");
|
||||
|
||||
return Results.Ok(new { original = prompt, translated = translatedPrompt });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Results.Problem($"处理失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
3
src/FodyWeavers.xml
Normal file
3
src/FodyWeavers.xml
Normal file
@ -0,0 +1,3 @@
|
||||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
|
||||
<Realm />
|
||||
</Weavers>
|
||||
59
src/Model/Entity/MJTask.cs
Normal file
59
src/Model/Entity/MJTask.cs
Normal file
@ -0,0 +1,59 @@
|
||||
using lai_transfer.Tool.Extensions;
|
||||
using Realms;
|
||||
|
||||
namespace lai_transfer.Model.Entity
|
||||
{
|
||||
[MapTo("MJTask")]
|
||||
public class MJTask : RealmObject
|
||||
{
|
||||
[PrimaryKey]
|
||||
[MapTo("_id")]
|
||||
public string ID { get; set; } = string.Empty;
|
||||
|
||||
[MapTo("mjId")]
|
||||
public string MJId { get; set; }
|
||||
|
||||
[MapTo("type")]
|
||||
public string Type { get; set; }
|
||||
|
||||
[MapTo("status")]
|
||||
public string Status { get; set; }
|
||||
|
||||
[MapTo("baseUrl")]
|
||||
public string BaseUrl { get; set; }
|
||||
|
||||
[MapTo("property")]
|
||||
public string Property { get; set; }
|
||||
|
||||
[MapTo("authorizationResult")]
|
||||
public string AuthorizationResult { get; set; }
|
||||
|
||||
[MapTo("createdTime")]
|
||||
public DateTimeOffset CreatedTime { get; set; } = BeijingTimeExtension.GetBeijingTime();
|
||||
|
||||
[MapTo("checkedTime")]
|
||||
public DateTimeOffset CheckedTime { get; set; } = BeijingTimeExtension.GetBeijingTime();
|
||||
}
|
||||
|
||||
public class MJTaskDTO
|
||||
{
|
||||
|
||||
public string ID { get; set; } = string.Empty;
|
||||
|
||||
public string MJId { get; set; }
|
||||
|
||||
public string Type { get; set; }
|
||||
|
||||
public string Status { get; set; }
|
||||
|
||||
public string BaseUrl { get; set; }
|
||||
|
||||
public string Property { get; set; }
|
||||
|
||||
public string AuthorizationResult { get; set; }
|
||||
|
||||
public DateTimeOffset CreatedTime { get; set; } = BeijingTimeExtension.GetBeijingTime();
|
||||
|
||||
public DateTimeOffset CheckedTime { get; set; } = BeijingTimeExtension.GetBeijingTime();
|
||||
}
|
||||
}
|
||||
10
src/Model/Midjourney/MidjourneyRequest.cs
Normal file
10
src/Model/Midjourney/MidjourneyRequest.cs
Normal file
@ -0,0 +1,10 @@
|
||||
namespace lai_transfer.Model.Midjourney
|
||||
{
|
||||
public class MidjourneyRequest
|
||||
{
|
||||
public class FetchListByCondition
|
||||
{
|
||||
public List<string> Ids { get; set; } = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
158
src/Model/Midjourney/MidjourneyTaskResponse.cs
Normal file
158
src/Model/Midjourney/MidjourneyTaskResponse.cs
Normal file
@ -0,0 +1,158 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace lai_transfer.Model.Midjourney
|
||||
{
|
||||
public class MidjourneyTaskResponse
|
||||
{
|
||||
|
||||
public class TaskResponse
|
||||
{
|
||||
[JsonPropertyName("id")]
|
||||
public string Id { get; set; }
|
||||
|
||||
[JsonPropertyName("accountId")]
|
||||
public string AccountId { get; set; }
|
||||
|
||||
[JsonPropertyName("action")]
|
||||
public string Action { get; set; }
|
||||
|
||||
[JsonPropertyName("status")]
|
||||
public string Status { get; set; }
|
||||
|
||||
[JsonPropertyName("mode")]
|
||||
public string Mode { get; set; }
|
||||
|
||||
[JsonPropertyName("prompt")]
|
||||
public string Prompt { get; set; }
|
||||
|
||||
[JsonPropertyName("promptEn")]
|
||||
public string PromptEn { get; set; }
|
||||
|
||||
[JsonPropertyName("description")]
|
||||
public string Description { get; set; }
|
||||
|
||||
[JsonPropertyName("submitTime")]
|
||||
public long? SubmitTime { get; set; }
|
||||
|
||||
[JsonPropertyName("startTime")]
|
||||
public long? StartTime { get; set; }
|
||||
|
||||
[JsonPropertyName("finishTime")]
|
||||
public long? FinishTime { get; set; }
|
||||
|
||||
[JsonPropertyName("progress")]
|
||||
public string Progress { get; set; }
|
||||
|
||||
[JsonPropertyName("imageUrl")]
|
||||
public string ImageUrl { get; set; }
|
||||
|
||||
[JsonPropertyName("imageUrls")]
|
||||
public List<ImageUrlItem> ImageUrls { get; set; }
|
||||
|
||||
[JsonPropertyName("videoUrl")]
|
||||
public string? VideoUrl { get; set; }
|
||||
|
||||
[JsonPropertyName("videoUrls")]
|
||||
public object? VideoUrls { get; set; }
|
||||
|
||||
[JsonPropertyName("imageHeight")]
|
||||
public int? ImageHeight { get; set; }
|
||||
|
||||
[JsonPropertyName("imageWidth")]
|
||||
public int? ImageWidth { get; set; }
|
||||
|
||||
[JsonPropertyName("failReason")]
|
||||
public string? FailReason { get; set; }
|
||||
|
||||
[JsonPropertyName("notifyHook")]
|
||||
public string NotifyHook { get; set; }
|
||||
|
||||
[JsonPropertyName("state")]
|
||||
public object? State { get; set; }
|
||||
|
||||
[JsonPropertyName("buttons")]
|
||||
public List<Button> Buttons { get; set; }
|
||||
|
||||
[JsonPropertyName("properties")]
|
||||
public Properties Properties { get; set; }
|
||||
}
|
||||
|
||||
public class ImageUrlItem
|
||||
{
|
||||
[JsonPropertyName("url")]
|
||||
public string Url { get; set; }
|
||||
|
||||
[JsonPropertyName("webp")]
|
||||
public string Webp { get; set; }
|
||||
|
||||
[JsonPropertyName("no")]
|
||||
public int? No { get; set; }
|
||||
|
||||
[JsonPropertyName("thumbnail")]
|
||||
public string Thumbnail { get; set; }
|
||||
|
||||
[JsonPropertyName("status")]
|
||||
public string Status { get; set; }
|
||||
}
|
||||
|
||||
public class Button
|
||||
{
|
||||
[JsonPropertyName("customId")]
|
||||
public string CustomId { get; set; }
|
||||
|
||||
[JsonPropertyName("emoji")]
|
||||
public string Emoji { get; set; }
|
||||
|
||||
[JsonPropertyName("label")]
|
||||
public string Label { get; set; }
|
||||
|
||||
[JsonPropertyName("type")]
|
||||
public int? Type { get; set; }
|
||||
|
||||
[JsonPropertyName("style")]
|
||||
public int? Style { get; set; }
|
||||
}
|
||||
|
||||
public class Properties
|
||||
{
|
||||
[JsonPropertyName("botType")]
|
||||
public string BotType { get; set; }
|
||||
|
||||
[JsonPropertyName("messageId")]
|
||||
public string MessageId { get; set; }
|
||||
|
||||
[JsonPropertyName("notifyHook")]
|
||||
public string NotifyHook { get; set; }
|
||||
|
||||
[JsonPropertyName("finalPrompt")]
|
||||
public string FinalPrompt { get; set; }
|
||||
|
||||
[JsonPropertyName("discordInstanceId")]
|
||||
public string DiscordInstanceId { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
public class MidjourneySubmitResponse
|
||||
{
|
||||
public class Response
|
||||
{
|
||||
[JsonPropertyName("code")]
|
||||
public int? Code { get; set; }
|
||||
|
||||
[JsonPropertyName("description")]
|
||||
public string Description { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("result")]
|
||||
public string Result { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("properties")]
|
||||
public ResponseProperties Properties { get; set; } = new();
|
||||
}
|
||||
|
||||
public class ResponseProperties
|
||||
{
|
||||
[JsonPropertyName("discordInstanceId")]
|
||||
public string DiscordInstanceId { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,8 +1,13 @@
|
||||
using lai_transfer;
|
||||
using lai_transfer.Common.Helper;
|
||||
using Serilog;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// 临时初始化LogHelper用于配置加载
|
||||
LogHelper.Initialize(LoggerFactory.Create(builder => builder.AddSerilog()));
|
||||
|
||||
ConfigHelper.Initialize();
|
||||
|
||||
// Add services to the container.
|
||||
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
|
||||
@ -14,7 +19,8 @@ app.Configure();
|
||||
|
||||
// 初始化日志帮助类
|
||||
LogHelper.Initialize(app.Services.GetRequiredService<ILoggerFactory>());
|
||||
ConfigHelper.Initialize();
|
||||
|
||||
|
||||
|
||||
// 初始化服务定位器
|
||||
ServiceLocator.Initialize(app.Services);
|
||||
|
||||
43
src/Services/Midjourney/IMidjourneyService.cs
Normal file
43
src/Services/Midjourney/IMidjourneyService.cs
Normal file
@ -0,0 +1,43 @@
|
||||
using lai_transfer.Common.Results;
|
||||
using lai_transfer.Model.Midjourney;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using System.Text.Json;
|
||||
using static lai_transfer.Model.Midjourney.MidjourneyRequest;
|
||||
|
||||
namespace lai_transfer.Services.Midjourney
|
||||
{
|
||||
public interface IMidjourneyService
|
||||
{
|
||||
/// <summary>
|
||||
/// Midjourney 提交出图任务的方法
|
||||
/// </summary>
|
||||
/// <param name="model"></param>
|
||||
/// <param name="authorizationResult"></param>
|
||||
/// <returns></returns>
|
||||
Task<IResult> MidjourneyImagine(JsonElement model, TransferAuthorizationResult authorizationResult);
|
||||
|
||||
/// <summary>
|
||||
/// Midjourney 获取指定ID的任务,直接从数据库获取
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <param name="authorizationResult"></param>
|
||||
/// <returns></returns>
|
||||
Task<IResult> MidjourneyFetchTaskById(string id, TransferAuthorizationResult authorizationResult);
|
||||
|
||||
/// <summary>
|
||||
/// Midjourney 获取指定ID的任务,并且检查图片进行拼接
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <param name="authorizationResult"></param>
|
||||
/// <returns></returns>
|
||||
Task<MidjourneyTaskResponse.TaskResponse?> MidjourneyFetchTaskByIdAndImageOption(string id, TransferAuthorizationResult authorizationResult);
|
||||
|
||||
/// <summary>
|
||||
/// Midjourney 批量查询任务接口
|
||||
/// </summary>
|
||||
/// <param name="model"></param>
|
||||
/// <param name="authorizationResult"></param>
|
||||
/// <returns></returns>
|
||||
Task<IResult> FetchListByCondition(FetchListByCondition model, TransferAuthorizationResult authorizationResult);
|
||||
}
|
||||
}
|
||||
332
src/Services/Midjourney/MidjourneyService.cs
Normal file
332
src/Services/Midjourney/MidjourneyService.cs
Normal file
@ -0,0 +1,332 @@
|
||||
using lai_transfer.Common.Helper;
|
||||
using lai_transfer.Common.Results;
|
||||
using lai_transfer.Model.Entity;
|
||||
using lai_transfer.Model.Midjourney;
|
||||
using lai_transfer.Services.RealmService;
|
||||
using lai_transfer.Services.Translate;
|
||||
using lai_transfer.Tool.Extensions;
|
||||
using lai_transfer.Tool.HttpTool;
|
||||
using lai_transfer.Tool.ImageTool;
|
||||
using lai_transfer.Tool.QiniuService;
|
||||
using System.Text.Json;
|
||||
using static lai_transfer.Tool.QiniuService.QiniuDataModel;
|
||||
|
||||
namespace lai_transfer.Services.Midjourney
|
||||
{
|
||||
public class MidjourneyService(IHttpService httpService,
|
||||
IGlobalTranslateService globalTranslateService,
|
||||
IQiniuService qiniuService,
|
||||
IImageService imageService,
|
||||
IMJTaskRealmService mJTaskRealmService) : IMidjourneyService
|
||||
{
|
||||
private static readonly ILogger _logger = LogHelper.GetLogger<MidjourneyService>();
|
||||
private readonly IHttpService _httpService = httpService;
|
||||
private readonly IGlobalTranslateService _globalTranslateService = globalTranslateService;
|
||||
private readonly IQiniuService _qiniuService = qiniuService;
|
||||
private readonly IImageService _imageService = imageService;
|
||||
private readonly IMJTaskRealmService _mJTaskRealmService = mJTaskRealmService;
|
||||
|
||||
#region Public Method
|
||||
public async Task<IResult> MidjourneyImagine(JsonElement model, TransferAuthorizationResult authorizationResult)
|
||||
{
|
||||
try
|
||||
{
|
||||
string url = $"{authorizationResult.BaseUrl}/mj/submit/imagine";
|
||||
|
||||
// 处理提示词
|
||||
string prompt = JSONHelper.GetJsonPropertyString(model, "prompt") ?? string.Empty;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(prompt))
|
||||
{
|
||||
prompt = JSONHelper.GetJsonPropertyString(model, "Prompt") ?? string.Empty;
|
||||
}
|
||||
|
||||
_logger.LogInformation($"原始提示词: {prompt}");
|
||||
|
||||
// 开始调用翻译服务
|
||||
prompt = await _globalTranslateService.TranslatePrompt(prompt);
|
||||
_logger.LogInformation($"翻译后提示词: {prompt}");
|
||||
|
||||
model = JSONHelper.SetJsonProperty(model, "prompt", prompt);
|
||||
|
||||
// 删除回调参数 notifyHook
|
||||
string body = JSONHelper.RemoveJsonProperties(model, "notifyHook");
|
||||
|
||||
// 开始提交任务,重试五次
|
||||
MidjourneySubmitResponse.Response? d = await RetryHelper.RetryAsync<MidjourneySubmitResponse.Response?>(async () =>
|
||||
{
|
||||
return await _httpService.PostAsync<MidjourneySubmitResponse.Response?>(url, body, new HttpRequestConfig
|
||||
{
|
||||
Timeout = Timeout.InfiniteTimeSpan,
|
||||
BearerToken = authorizationResult.Token,
|
||||
Headers = new Dictionary<string, string>()
|
||||
{
|
||||
{ "mj-api-secret", authorizationResult.Token }
|
||||
}
|
||||
|
||||
});
|
||||
}, 5, TimeSpan.FromSeconds(1));
|
||||
|
||||
if (d == null)
|
||||
{
|
||||
_logger.LogError("请求失败,返回数据为空,请求数据{data}", body);
|
||||
return Results.BadRequest("reponse is null");
|
||||
}
|
||||
|
||||
string mjId = d.Result;
|
||||
if (String.IsNullOrWhiteSpace(mjId))
|
||||
{
|
||||
_logger.LogError("请求失败,返回 mj id 为空,请求数据{data}", body);
|
||||
return Results.BadRequest("mj id is null");
|
||||
}
|
||||
|
||||
// 处理任务
|
||||
await MidjourneyFetchTaskByIdAndImageOption(mjId, authorizationResult);
|
||||
|
||||
// 返回数据
|
||||
return Results.Json(d);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"Error: {ex.Message}");
|
||||
return Results.BadRequest("request error : " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IResult> MidjourneyFetchTaskById(string id, TransferAuthorizationResult authorizationResult)
|
||||
{
|
||||
try
|
||||
{
|
||||
MidjourneyTaskResponse.TaskResponse? d = await MidjourneyFetchTaskByIdResponse(id, authorizationResult);
|
||||
return Results.Json(d);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "{url} 请求失败,{error}", authorizationResult.FullPath, ex.Message);
|
||||
return Results.BadRequest("request error : " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<MidjourneyTaskResponse.TaskResponse?> MidjourneyFetchTaskByIdAndImageOption(string id, TransferAuthorizationResult authorizationResult)
|
||||
{
|
||||
try
|
||||
{
|
||||
string originUrl = $"{authorizationResult.BaseUrl}/mj/task/{id}/fetch";
|
||||
|
||||
// 提交获取任务
|
||||
MidjourneyTaskResponse.TaskResponse? d =
|
||||
await RetryHelper.RetryAsync<MidjourneyTaskResponse.TaskResponse?>(async () =>
|
||||
{
|
||||
return await _httpService.GetAsync<MidjourneyTaskResponse.TaskResponse?>(originUrl, new HttpRequestConfig
|
||||
{
|
||||
BearerToken = authorizationResult.Token,
|
||||
Headers = new Dictionary<string, string>()
|
||||
{
|
||||
{ "mj-api-secret", authorizationResult.Token }
|
||||
}
|
||||
});
|
||||
}, 5, TimeSpan.FromSeconds(1));
|
||||
|
||||
// 开始处理图片
|
||||
if (d is null)
|
||||
{
|
||||
_logger.LogError("请求失败,返回数据为空,请求数据{data}", id);
|
||||
return d;
|
||||
}
|
||||
|
||||
|
||||
if (d.Status != null && d.Status.Equals("failure", StringComparison.CurrentCultureIgnoreCase))
|
||||
{
|
||||
_logger.LogError("任务失败,请求数据{data},返回原始数据", id);
|
||||
}
|
||||
|
||||
bool isImagine = d.Action.Equals("imagine", StringComparison.OrdinalIgnoreCase);
|
||||
// 判断Action
|
||||
if (!isImagine)
|
||||
{
|
||||
_logger.LogError("当前任务的Action不是绘图,直接返回原始数据");
|
||||
}
|
||||
|
||||
string newImageUrl = string.Empty;
|
||||
if (isImagine && d.Status != null && d.Status.Equals("success", StringComparison.CurrentCultureIgnoreCase))
|
||||
{
|
||||
byte[] files = [];
|
||||
string imageUrl = d.ImageUrl;
|
||||
// 转存
|
||||
if (!authorizationResult.Splice && authorizationResult.Storage)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(imageUrl))
|
||||
{
|
||||
files = await RetryHelper.RetryAsync<byte[]>(async () =>
|
||||
{
|
||||
return await _httpService.DownloadFileAsync(imageUrl);
|
||||
}, 5);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogError("请求失败,返回图片链接不存在,请求数据{data},返回原始数据", id);
|
||||
}
|
||||
}
|
||||
|
||||
// 重新拼接
|
||||
if (authorizationResult.Splice)
|
||||
{
|
||||
List<string> imageUrls = [];
|
||||
for (int i = 0; i < d.ImageUrls.Count; i++)
|
||||
{
|
||||
if (ConfigHelper.Qiniu.OriginImage)
|
||||
{
|
||||
imageUrls.Add(d.ImageUrls[i].Url ?? "");
|
||||
}
|
||||
else
|
||||
{
|
||||
imageUrls.Add(d.ImageUrls[i].Webp ?? "");
|
||||
}
|
||||
}
|
||||
// 图片不够,不做后续的修改
|
||||
if (imageUrls.Count == 4)
|
||||
{
|
||||
files = await _imageService.ImageCombiner(imageUrls, (int)d.ImageWidth, (int)d.ImageHeight);
|
||||
}
|
||||
}
|
||||
|
||||
if (files == null || files.Length <= 0)
|
||||
{
|
||||
_logger.LogError("请求失败,下载图片链接失败,请求数据 {data},图片链接 {imageUrl},返回原始数据", id, imageUrl);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
// 开始转存
|
||||
string fileName;
|
||||
string fileKey;
|
||||
if (ConfigHelper.Qiniu.OriginImage)
|
||||
{
|
||||
fileName = "orgin_" + id + ".png";
|
||||
fileKey = "mj/orgin/" + fileName;
|
||||
}
|
||||
else
|
||||
{
|
||||
fileName = "orgin_" + id + ".webp";
|
||||
fileKey = "mj/webp/" + fileName;
|
||||
}
|
||||
|
||||
UploadResult u = await _qiniuService.UploadFileToQiNiu(files, fileName, fileKey);
|
||||
if (u.Success)
|
||||
{
|
||||
// 成功
|
||||
newImageUrl = u.Url;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 失败,不修改图片链接
|
||||
newImageUrl = imageUrl;
|
||||
}
|
||||
}
|
||||
|
||||
d.ImageUrl = String.IsNullOrWhiteSpace(newImageUrl) ? d.ImageUrl : newImageUrl;
|
||||
}
|
||||
|
||||
// 开始修改数据
|
||||
|
||||
MJTaskDTO? mjDto = await _mJTaskRealmService.GetMjTaskByMjId(d.Id);
|
||||
if (mjDto is null)
|
||||
{
|
||||
// 新增
|
||||
mjDto = new MJTaskDTO()
|
||||
{
|
||||
ID = Guid.NewGuid().ToString(),
|
||||
MJId = d.Id,
|
||||
Type = d.Action,
|
||||
Status = d.Status,
|
||||
BaseUrl = authorizationResult.BaseUrl,
|
||||
Property = JsonSerializer.Serialize(d),
|
||||
AuthorizationResult = JsonSerializer.Serialize(authorizationResult),
|
||||
CreatedTime = BeijingTimeExtension.GetBeijingTime(),
|
||||
CheckedTime = BeijingTimeExtension.GetBeijingTime()
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
// 修改
|
||||
mjDto.Status = d.Status;
|
||||
mjDto.Property = JsonSerializer.Serialize(d);
|
||||
mjDto.AuthorizationResult = JsonSerializer.Serialize(authorizationResult);
|
||||
mjDto.CheckedTime = BeijingTimeExtension.GetBeijingTime();
|
||||
}
|
||||
|
||||
// 调用修改或新增函数
|
||||
await _mJTaskRealmService.ModifyOrAddMjTask(d.Id, mjDto);
|
||||
return d;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError("{url} 请求失败,{error}", authorizationResult.FullPath, ex.Message);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IResult> FetchListByCondition(MidjourneyRequest.FetchListByCondition model, TransferAuthorizationResult authorizationResult)
|
||||
{
|
||||
try
|
||||
{
|
||||
List<string> ids = model.Ids;
|
||||
List<MidjourneyTaskResponse.TaskResponse> responses = [];
|
||||
if (ids.Count <= 0)
|
||||
{
|
||||
return Results.Json(responses);
|
||||
}
|
||||
|
||||
// 开始处理数据
|
||||
for (int i = 0; i < ids.Count; i++)
|
||||
{
|
||||
string id = ids[i];
|
||||
MidjourneyTaskResponse.TaskResponse? d = await MidjourneyFetchTaskByIdResponse(id, authorizationResult);
|
||||
if (d is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
responses.Add(d);
|
||||
}
|
||||
return Results.Json(responses);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "{url} 请求失败,{error}", authorizationResult.FullPath, ex.Message);
|
||||
return Results.BadRequest("request error : " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Method
|
||||
|
||||
/// <summary>
|
||||
/// 获取MJTask的实际任务
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <param name="authorizationResult"></param>
|
||||
/// <returns></returns>
|
||||
private async Task<MidjourneyTaskResponse.TaskResponse?> MidjourneyFetchTaskByIdResponse(string id, TransferAuthorizationResult authorizationResult)
|
||||
{
|
||||
try
|
||||
{
|
||||
MJTaskDTO? mjDto = await _mJTaskRealmService.GetMjTaskByMjId(id);
|
||||
if (mjDto is not null)
|
||||
{
|
||||
return JsonSerializer.Deserialize<MidjourneyTaskResponse.TaskResponse>(mjDto.Property);
|
||||
}
|
||||
|
||||
MidjourneyTaskResponse.TaskResponse? d = await MidjourneyFetchTaskByIdAndImageOption(id, authorizationResult);
|
||||
return d;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "{url} 请求失败,获取任务失败,{error}", authorizationResult.FullPath, ex.Message);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
105
src/Services/PeriodicTimeService/PeriodicTimedService .cs
Normal file
105
src/Services/PeriodicTimeService/PeriodicTimedService .cs
Normal file
@ -0,0 +1,105 @@
|
||||
using lai_transfer.Common.Helper;
|
||||
using lai_transfer.Common.Results;
|
||||
using lai_transfer.EndpointServices.MJTransferEndpoint;
|
||||
using lai_transfer.Model.Entity;
|
||||
using lai_transfer.Services.Midjourney;
|
||||
using lai_transfer.Services.RealmService;
|
||||
using lai_transfer.Tool.Extensions;
|
||||
using System.Diagnostics;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace lai_transfer.Services.PeriodicTimeService
|
||||
{
|
||||
public class PeriodicTimedService(IServiceScopeFactory serviceScopeFactory) : BackgroundService
|
||||
{
|
||||
private static readonly ILogger _logger = LogHelper.GetLogger<MJPostActionService>();
|
||||
|
||||
private readonly TimeSpan _interval = TimeSpan.FromSeconds(20);
|
||||
private readonly IServiceScopeFactory _serviceScopeFactory = serviceScopeFactory;
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
_logger.LogInformation("周期性定时服务已启动");
|
||||
|
||||
await ExecuteTaskAsync(stoppingToken);
|
||||
}
|
||||
|
||||
private async Task ExecuteTaskAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
while (!stoppingToken.IsCancellationRequested)
|
||||
{
|
||||
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
var startTime = BeijingTimeExtension.GetBeijingTime();
|
||||
_logger.LogInformation("{time} 开始执行定时任务...", startTime);
|
||||
|
||||
try
|
||||
{
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
var serviceProvider = scope.ServiceProvider;
|
||||
var _midjourneyService = serviceProvider.GetRequiredService<IMidjourneyService>();
|
||||
var _mJTaskRealmService = serviceProvider.GetRequiredService<IMJTaskRealmService>();
|
||||
|
||||
List<MJTaskDTO> mJTasks = await _mJTaskRealmService.GetMJTaskStatusNotFinish();
|
||||
|
||||
if (mJTasks.Count == 0)
|
||||
{
|
||||
_logger.LogInformation("不存在未完成的任务,执行完毕!");
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < mJTasks.Count; i++)
|
||||
{
|
||||
MJTaskDTO task = mJTasks[i];
|
||||
if (task.AuthorizationResult == null)
|
||||
{
|
||||
// 删除当前这个,不处理 等外部请求再次触发
|
||||
_logger.LogInformation("{mjid} 任务未找到对应的数据信息,开始删除数据,用户使用fetch接口时,自动创建", task.MJId);
|
||||
await _mJTaskRealmService.DeleteMJTaskById(task.ID);
|
||||
continue;
|
||||
}
|
||||
TransferAuthorizationResult? authorizationResult = JsonSerializer.Deserialize<TransferAuthorizationResult>(task.AuthorizationResult);
|
||||
if (authorizationResult is null)
|
||||
{
|
||||
// 删除当前这个,不处理 等外部请求再次触发
|
||||
_logger.LogInformation("{mjid} 任务未找到对应的数据信息,开始删除数据,用户使用fetch接口时,自动创建", task.MJId);
|
||||
await _mJTaskRealmService.DeleteMJTaskById(task.ID);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation("{mjid} 开始处理", task.MJId);
|
||||
await _midjourneyService.MidjourneyFetchTaskByIdAndImageOption(task.MJId, authorizationResult);
|
||||
}
|
||||
|
||||
// 等一秒执行下一个
|
||||
await Task.Delay(1000, stoppingToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex) when (ex is not OperationCanceledException)
|
||||
{
|
||||
_logger.LogError(ex, "任务执行失败");
|
||||
}
|
||||
finally
|
||||
{
|
||||
stopwatch.Stop();
|
||||
var executionTime = stopwatch.Elapsed;
|
||||
_logger.LogInformation("任务执行完成,耗时: {ExecutionTime}", executionTime);
|
||||
|
||||
// 计算剩余等待时间
|
||||
var remainingTime = _interval - executionTime;
|
||||
if (remainingTime > TimeSpan.Zero)
|
||||
{
|
||||
_logger.LogDebug("等待 {RemainingTime} 后执行下一次任务", remainingTime);
|
||||
await Task.Delay(remainingTime, stoppingToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("任务执行时间 {ExecutionTime} 超过间隔 {Interval}, 立即开始下一次",
|
||||
executionTime, _interval);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
192
src/Services/RealmService/MJTaskRealmService.cs
Normal file
192
src/Services/RealmService/MJTaskRealmService.cs
Normal file
@ -0,0 +1,192 @@
|
||||
using lai_transfer.Model.Entity;
|
||||
using lai_transfer.Tool.Extensions;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace lai_transfer.Services.RealmService
|
||||
{
|
||||
|
||||
public interface IMJTaskRealmService
|
||||
{
|
||||
/// <summary>
|
||||
/// 修改或者是创建MJTask
|
||||
/// 判断mjId,存在修改 不存再创建
|
||||
/// </summary>
|
||||
/// <param name="mjId"></param>
|
||||
/// <param name="mJTask"></param>
|
||||
/// <returns></returns>
|
||||
Task<MJTaskDTO?> ModifyOrAddMjTask(string mjId, MJTaskDTO mJTask);
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定的ID的任务
|
||||
/// </summary>
|
||||
/// <param name="Id"></param>
|
||||
/// <returns></returns>
|
||||
Task<MJTaskDTO?> GetMjTaskById(string Id);
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定的 MJ ID 的任务
|
||||
/// </summary>
|
||||
/// <param name="mjId"></param>
|
||||
/// <returns></returns>
|
||||
Task<MJTaskDTO?> GetMjTaskByMjId(string mjId);
|
||||
|
||||
/// <summary>
|
||||
/// 获取没有的完成的MJ的任务
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task<List<MJTaskDTO>> GetMJTaskStatusNotFinish();
|
||||
|
||||
/// <summary>
|
||||
/// 删除指定ID的MJTask
|
||||
/// </summary>
|
||||
/// <param name="Id"></param>
|
||||
/// <returns></returns>
|
||||
Task DeleteMJTaskById(string Id);
|
||||
}
|
||||
|
||||
|
||||
public class MJTaskRealmService(RealmService realmService) : IMJTaskRealmService
|
||||
{
|
||||
private readonly RealmService _realmService = realmService;
|
||||
|
||||
public async Task<MJTaskDTO?> ModifyOrAddMjTask(string mjId, MJTaskDTO mJTask)
|
||||
{
|
||||
// 查找数据是否存在
|
||||
return await _realmService.ExecuteTransactionAsync<MJTaskDTO?>(realm =>
|
||||
{
|
||||
MJTask? mj = realm.All<MJTask>().FirstOrDefault(x => x.MJId == mjId);
|
||||
if (mj is null)
|
||||
{
|
||||
// 新增
|
||||
MJTask newMj = new()
|
||||
{
|
||||
ID = mJTask.ID,
|
||||
MJId = mJTask.MJId,
|
||||
Type = mJTask.Type,
|
||||
Status = mJTask.Status,
|
||||
BaseUrl = mJTask.BaseUrl,
|
||||
Property = mJTask.Property,
|
||||
AuthorizationResult = mJTask.AuthorizationResult,
|
||||
CreatedTime = BeijingTimeExtension.GetBeijingTime(),
|
||||
CheckedTime = BeijingTimeExtension.GetBeijingTime(),
|
||||
};
|
||||
realm.Add(newMj);
|
||||
mj = newMj;
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 修改
|
||||
mj.Type = mJTask.Type;
|
||||
mj.Status = mJTask.Status;
|
||||
mj.BaseUrl = mJTask.BaseUrl;
|
||||
mj.Property = mJTask.Property;
|
||||
mj.AuthorizationResult = mJTask.AuthorizationResult;
|
||||
mj.CreatedTime = mJTask.CreatedTime;
|
||||
mj.CheckedTime = mJTask.CheckedTime;
|
||||
return mJTask;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<MJTaskDTO?> GetMjTaskById(string Id)
|
||||
{
|
||||
// 查找数据是否存在
|
||||
return await _realmService.ExecuteQueryAsync<MJTaskDTO?>(realm =>
|
||||
{
|
||||
MJTask? mj = realm.Find<MJTask>(Id);
|
||||
|
||||
if (mj is null)
|
||||
return null;
|
||||
|
||||
// 开始处理返回数据
|
||||
MJTaskDTO mjDto = new()
|
||||
{
|
||||
ID = mj.ID,
|
||||
MJId = mj.MJId,
|
||||
Type = mj.Type,
|
||||
Status = mj.Status,
|
||||
BaseUrl = mj.BaseUrl,
|
||||
Property = mj.Property,
|
||||
AuthorizationResult = mj.AuthorizationResult,
|
||||
CreatedTime = mj.CreatedTime,
|
||||
CheckedTime = mj.CheckedTime,
|
||||
};
|
||||
return mjDto;
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<MJTaskDTO?> GetMjTaskByMjId(string mjId)
|
||||
{
|
||||
// 查找数据是否存在
|
||||
return await _realmService.ExecuteQueryAsync<MJTaskDTO?>(realm =>
|
||||
{
|
||||
MJTask? mj = realm.All<MJTask>().FirstOrDefault(x => x.MJId == mjId);
|
||||
|
||||
if (mj is null)
|
||||
return null;
|
||||
|
||||
// 开始处理返回数据
|
||||
MJTaskDTO mjDto = new()
|
||||
{
|
||||
ID = mj.ID,
|
||||
MJId = mj.MJId,
|
||||
Type = mj.Type,
|
||||
Status = mj.Status,
|
||||
BaseUrl = mj.BaseUrl,
|
||||
Property = mj.Property,
|
||||
AuthorizationResult = mj.AuthorizationResult,
|
||||
CreatedTime = mj.CreatedTime,
|
||||
CheckedTime = mj.CheckedTime,
|
||||
};
|
||||
return mjDto;
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<List<MJTaskDTO>> GetMJTaskStatusNotFinish()
|
||||
{
|
||||
// 查找数据是否存在
|
||||
return await _realmService.ExecuteQueryAsync<List<MJTaskDTO>>(realm =>
|
||||
{
|
||||
// 使用 StringComparison.OrdinalIgnoreCase 进行不区分大小写的比较
|
||||
var query = realm.All<MJTask>()
|
||||
.Where(x => !x.Status.Equals("success", StringComparison.OrdinalIgnoreCase)
|
||||
&& !x.Status.Equals("failure", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
List<MJTask> mjs = query.ToList();
|
||||
|
||||
List<MJTaskDTO> mJTaskDTOs = new List<MJTaskDTO>();
|
||||
for (int i = 0; i < mjs.Count; i++)
|
||||
{
|
||||
MJTask mj = mjs[i];
|
||||
// 开始处理返回数据
|
||||
MJTaskDTO mjDto = new()
|
||||
{
|
||||
ID = mj.ID,
|
||||
MJId = mj.MJId,
|
||||
Type = mj.Type,
|
||||
Status = mj.Status,
|
||||
BaseUrl = mj.BaseUrl,
|
||||
Property = mj.Property,
|
||||
AuthorizationResult = mj.AuthorizationResult,
|
||||
CreatedTime = mj.CreatedTime,
|
||||
CheckedTime = mj.CheckedTime,
|
||||
};
|
||||
mJTaskDTOs.Add(mjDto);
|
||||
}
|
||||
return mJTaskDTOs;
|
||||
});
|
||||
}
|
||||
|
||||
public async Task DeleteMJTaskById(string Id)
|
||||
{
|
||||
await _realmService.ExecuteTransactionAsync(realm =>
|
||||
{
|
||||
MJTask? mj = realm.Find<MJTask>(Id);
|
||||
if (mj is not null)
|
||||
realm.Remove(mj);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
205
src/Services/RealmService/RealmService.cs
Normal file
205
src/Services/RealmService/RealmService.cs
Normal file
@ -0,0 +1,205 @@
|
||||
using lai_transfer.Common.Helper;
|
||||
using Realms;
|
||||
|
||||
namespace lai_transfer.Services.RealmService
|
||||
{
|
||||
public class RealmService : IDisposable
|
||||
{
|
||||
private readonly RealmConfiguration _config;
|
||||
private bool _disposed;
|
||||
|
||||
private static readonly ILogger _logger = LogHelper.GetLogger<RealmService>();
|
||||
|
||||
public RealmService()
|
||||
{
|
||||
string dataBaseDirectory = Path.Combine(Directory.GetCurrentDirectory(), "Configuration", "DataBase");
|
||||
Directory.CreateDirectory(dataBaseDirectory);
|
||||
|
||||
string realmPath = Path.Combine(dataBaseDirectory, "db.realm");
|
||||
|
||||
_config = new RealmConfiguration(realmPath)
|
||||
{
|
||||
SchemaVersion = 3,
|
||||
MigrationCallback = HandleMigration
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取 Realm 实例,用于直接操作数据库(同步方法,注意线程安全)
|
||||
/// </summary>
|
||||
public Realm GetInstance() => Realm.GetInstance(_config);
|
||||
|
||||
/// <summary>
|
||||
/// 异步执行事务操作(无返回值)
|
||||
/// </summary>
|
||||
public async Task ExecuteTransactionAsync(Action<Realm> action)
|
||||
{
|
||||
await Task.Run(async () =>
|
||||
{
|
||||
using var realm = await Realm.GetInstanceAsync(_config);
|
||||
await realm.WriteAsync(() =>
|
||||
{
|
||||
action(realm);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 在 RealmService 中添加支持返回值的事务方法
|
||||
public async Task<T> ExecuteTransactionWithResultAsync<T>(Func<Realm, T> action)
|
||||
{
|
||||
using var realm = await Realm.GetInstanceAsync(_config);
|
||||
using var transaction = realm.BeginWrite();
|
||||
|
||||
try
|
||||
{
|
||||
var result = action(realm);
|
||||
transaction.Commit();
|
||||
return result;
|
||||
}
|
||||
catch
|
||||
{
|
||||
transaction.Rollback();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步执行事务操作(有返回值)
|
||||
/// </summary>
|
||||
public async Task<T> ExecuteTransactionAsync<T>(Func<Realm, T> func)
|
||||
{
|
||||
return await Task.Run(async () =>
|
||||
{
|
||||
using var realm = await Realm.GetInstanceAsync(_config);
|
||||
return await realm.WriteAsync(() =>
|
||||
{
|
||||
return func(realm);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 同步执行事务操作(无返回值)
|
||||
/// </summary>
|
||||
public void ExecuteTransaction(Action<Realm> action)
|
||||
{
|
||||
using var realm = Realm.GetInstance(_config);
|
||||
realm.Write(() =>
|
||||
{
|
||||
action(realm);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 同步执行事务操作(有返回值)
|
||||
/// </summary>
|
||||
public T ExecuteTransaction<T>(Func<Realm, T> func)
|
||||
{
|
||||
using var realm = Realm.GetInstance(_config);
|
||||
return realm.Write(() =>
|
||||
{
|
||||
return func(realm);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 安全执行事务(无返回值)
|
||||
/// </summary>
|
||||
public async Task<bool> TryExecuteTransactionAsync(Action<Realm> action)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ExecuteTransactionAsync(action);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 安全执行事务(有返回值)
|
||||
/// </summary>
|
||||
public async Task<(bool Success, T? Result)> TryExecuteTransactionAsync<T>(Func<Realm, T> func)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await ExecuteTransactionAsync(func);
|
||||
return (true, result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Transaction failed: {ex.Message}");
|
||||
return (false, default);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步查询操作(只读)
|
||||
/// </summary>
|
||||
public async Task<T> ExecuteQueryAsync<T>(Func<Realm, T> query)
|
||||
{
|
||||
return await Task.Run(async () =>
|
||||
{
|
||||
using var realm = await Realm.GetInstanceAsync(_config);
|
||||
return query(realm);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 同步查询操作(只读)
|
||||
/// </summary>
|
||||
public T ExecuteQuery<T>(Func<Realm, T> query)
|
||||
{
|
||||
using var realm = Realm.GetInstance(_config);
|
||||
return query(realm);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
// 压缩数据库以释放空间
|
||||
try
|
||||
{
|
||||
Realm.Compact(_config);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Failed to compact database: {ex.Message}");
|
||||
}
|
||||
}
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
~RealmService() => Dispose(false);
|
||||
|
||||
private void HandleMigration(Migration migration, ulong oldSchemaVersion)
|
||||
{
|
||||
//if (oldSchemaVersion < 2)
|
||||
//{
|
||||
// _logger.LogInformation("正在执行 Realm 数据库迁移...");
|
||||
// var newObjects = migration.NewRealm.DynamicApi.All("Siliconflow");
|
||||
|
||||
// Console.WriteLine($"正在为 {newObjects.Count()} 个对象设置 newId 字段...");
|
||||
|
||||
// foreach (dynamic obj in newObjects)
|
||||
// {
|
||||
// obj.NewId = string.Empty;
|
||||
// }
|
||||
// _logger.LogInformation("Realm 数据库迁移完成。");
|
||||
//}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,12 +1,10 @@
|
||||
|
||||
|
||||
using lai_transfer.Common.Helper;
|
||||
using lai_transfer.Common.Helper;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace lai_transfer.Services
|
||||
namespace lai_transfer.Services.Translate
|
||||
{
|
||||
/// <summary>
|
||||
/// 百度翻译服务
|
||||
@ -1,10 +1,9 @@
|
||||
using lai_transfer.Common.Helper;
|
||||
using lai_transfer.Services;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Midjourney.Infrastructure.Services
|
||||
namespace lai_transfer.Services.Translate
|
||||
{
|
||||
/// <summary>
|
||||
/// OpenAI GPT翻译服务
|
||||
@ -1,4 +1,4 @@
|
||||
namespace lai_transfer.Services
|
||||
namespace lai_transfer.Services.Translate
|
||||
{
|
||||
public interface ITranslateService
|
||||
{
|
||||
80
src/Services/Translate/TranslateService.cs
Normal file
80
src/Services/Translate/TranslateService.cs
Normal file
@ -0,0 +1,80 @@
|
||||
using lai_transfer.Common.Helper;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace lai_transfer.Services.Translate
|
||||
{
|
||||
public interface IGlobalTranslateService
|
||||
{
|
||||
Task<string> TranslatePrompt(string prompt);
|
||||
}
|
||||
|
||||
public class TranslateService(ITranslateService translateService) : IGlobalTranslateService
|
||||
{
|
||||
private readonly ITranslateService _translateService = translateService;
|
||||
|
||||
public async Task<string> TranslatePrompt(string prompt)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(prompt))
|
||||
return prompt;
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
// 检查是否启用翻译功能
|
||||
if (!ConfigHelper.Translate.Enable)
|
||||
return prompt;
|
||||
|
||||
// 如果不包含中文,直接返回
|
||||
if (string.IsNullOrWhiteSpace(prompt) || !_translateService.ContainsChinese(prompt))
|
||||
return prompt;
|
||||
|
||||
string paramStr = "";
|
||||
var paramMatcher = Regex.Match(prompt, "\\x20+--[a-z]+.*$", RegexOptions.IgnoreCase);
|
||||
if (paramMatcher.Success)
|
||||
{
|
||||
paramStr = paramMatcher.Value;
|
||||
}
|
||||
string promptWithoutParam = prompt.Substring(0, prompt.Length - paramStr.Length);
|
||||
List<string> imageUrls = new List<string>();
|
||||
var imageMatcher = Regex.Matches(promptWithoutParam, "https?://[a-z0-9-_:@&?=+,.!/~*'%$]+\\x20+", RegexOptions.IgnoreCase);
|
||||
foreach (Match match in imageMatcher)
|
||||
{
|
||||
imageUrls.Add(match.Value);
|
||||
}
|
||||
string text = promptWithoutParam;
|
||||
foreach (string imageUrl in imageUrls)
|
||||
{
|
||||
text = text.Replace(imageUrl, "");
|
||||
}
|
||||
text = text.Trim();
|
||||
if (!string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
text = await _translateService.TranslateToEnglish(text);
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(paramStr))
|
||||
{
|
||||
// 当有 --no 参数时, 翻译 --no 参数, 并替换原参数
|
||||
// --sref https://mjcdn.googlec.cc/1.jpg --no aa, bb, cc
|
||||
var paramNomatcher = Regex.Match(paramStr, "--no\\s+(.*?)(?=--|$)");
|
||||
if (paramNomatcher.Success)
|
||||
{
|
||||
string paramNoStr = paramNomatcher.Groups[1].Value.Trim();
|
||||
string paramNoStrEn = await _translateService.TranslateToEnglish(paramNoStr);
|
||||
|
||||
// 提取 --no 之前的参数
|
||||
paramStr = paramStr.Substring(0, paramNomatcher.Index);
|
||||
|
||||
// 替换 --no 参数
|
||||
paramStr = paramStr + paramNomatcher.Result("--no " + paramNoStrEn + " ");
|
||||
}
|
||||
}
|
||||
return string.Concat(imageUrls) + text.Trim() + paramStr;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.LogError(ex, "翻译提示词失败,使用原始提示词");
|
||||
return prompt;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
302
src/Tool/HttpTool/HttpService.cs
Normal file
302
src/Tool/HttpTool/HttpService.cs
Normal file
@ -0,0 +1,302 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace lai_transfer.Tool.HttpTool;
|
||||
|
||||
public class HttpRequestConfig
|
||||
{
|
||||
public string? BaseUrl { get; set; }
|
||||
public Dictionary<string, string> Headers { get; set; } = new();
|
||||
public Dictionary<string, string> QueryParams { get; set; } = new();
|
||||
public string? BearerToken { get; set; }
|
||||
public TimeSpan? Timeout { get; set; }
|
||||
public string? MediaType { get; set; } = "application/json";
|
||||
public CancellationToken CancellationToken { get; set; } = CancellationToken.None;
|
||||
}
|
||||
|
||||
public class HttpRequestConfig<T> : HttpRequestConfig
|
||||
{
|
||||
public T? Data { get; set; }
|
||||
}
|
||||
|
||||
public interface IHttpService
|
||||
{
|
||||
Task<T?> GetAsync<T>(string url, HttpRequestConfig? config = null);
|
||||
Task<string> GetStringAsync(string url, HttpRequestConfig? config = null);
|
||||
Task<T?> PostAsync<T>(string url, object data, HttpRequestConfig? config = null);
|
||||
Task<T?> PostAsync<T>(string url, HttpRequestConfig<T> config);
|
||||
Task<T?> PutAsync<T>(string url, object data, HttpRequestConfig? config = null);
|
||||
Task<bool> DeleteAsync(string url, HttpRequestConfig? config = null);
|
||||
Task<Stream> GetStreamAsync(string url, HttpRequestConfig? config = null);
|
||||
Task<byte[]> DownloadFileAsync(string url, HttpRequestConfig? config = null);
|
||||
}
|
||||
|
||||
public class HttpService : IHttpService
|
||||
{
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly ILogger<HttpService> _logger;
|
||||
|
||||
public HttpService(IHttpClientFactory httpClientFactory, ILogger<HttpService> logger)
|
||||
{
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 配置HttpRequestMessage
|
||||
/// </summary>
|
||||
private HttpRequestMessage CreateHttpRequest(HttpMethod method, string url, HttpRequestConfig? config)
|
||||
{
|
||||
var requestUrl = BuildUrl(url, config);
|
||||
var request = new HttpRequestMessage(method, requestUrl);
|
||||
|
||||
// 设置请求头
|
||||
SetRequestHeaders(request, config);
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构建完整URL(包含查询参数)
|
||||
/// </summary>
|
||||
private string BuildUrl(string url, HttpRequestConfig? config)
|
||||
{
|
||||
if (config?.QueryParams == null || !config.QueryParams.Any())
|
||||
return url;
|
||||
|
||||
var uriBuilder = new UriBuilder(url);
|
||||
var query = System.Web.HttpUtility.ParseQueryString(uriBuilder.Query);
|
||||
|
||||
foreach (var param in config.QueryParams)
|
||||
{
|
||||
query[param.Key] = param.Value;
|
||||
}
|
||||
|
||||
uriBuilder.Query = query.ToString();
|
||||
return uriBuilder.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置请求头
|
||||
/// </summary>
|
||||
private void SetRequestHeaders(HttpRequestMessage request, HttpRequestConfig? config)
|
||||
{
|
||||
if (config == null) return;
|
||||
|
||||
// 设置Bearer Token
|
||||
if (!string.IsNullOrEmpty(config.BearerToken))
|
||||
{
|
||||
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", config.BearerToken);
|
||||
}
|
||||
|
||||
// 设置自定义请求头
|
||||
foreach (var header in config.Headers)
|
||||
{
|
||||
request.Headers.TryAddWithoutValidation(header.Key, header.Value);
|
||||
}
|
||||
|
||||
// 设置Content-Type
|
||||
if (request.Content != null && !string.IsNullOrEmpty(config.MediaType))
|
||||
{
|
||||
request.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(config.MediaType);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送HTTP请求
|
||||
/// </summary>
|
||||
private async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, HttpRequestConfig? config, string clientName = "DefaultClient")
|
||||
{
|
||||
using var httpClient = _httpClientFactory.CreateClient(clientName);
|
||||
|
||||
// 设置超时
|
||||
if (config?.Timeout.HasValue == true)
|
||||
{
|
||||
httpClient.Timeout = config.Timeout.Value;
|
||||
}
|
||||
|
||||
return await httpClient.SendAsync(request, config?.CancellationToken ?? CancellationToken.None);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GET请求 - 返回泛型类型
|
||||
/// </summary>
|
||||
public async Task<T?> GetAsync<T>(string url, HttpRequestConfig? config = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var request = CreateHttpRequest(HttpMethod.Get, url, config);
|
||||
var response = await SendAsync(request, config);
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
return JsonSerializer.Deserialize<T>(content, new JsonSerializerOptions
|
||||
{
|
||||
PropertyNameCaseInsensitive = true
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "GET请求失败: {Url}", url);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GET请求 - 返回字符串
|
||||
/// </summary>
|
||||
public async Task<string> GetStringAsync(string url, HttpRequestConfig? config = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var request = CreateHttpRequest(HttpMethod.Get, url, config);
|
||||
var response = await SendAsync(request, config);
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
return await response.Content.ReadAsStringAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "GET字符串请求失败: {Url}", url);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// POST请求
|
||||
/// </summary>
|
||||
public async Task<T?> PostAsync<T>(string url, object data, HttpRequestConfig? config = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var request = CreateHttpRequest(HttpMethod.Post, url, config);
|
||||
// 判断data是不是string 不是的话才序列话
|
||||
string json;
|
||||
if (data is string str)
|
||||
{
|
||||
// 如果 data 已经是字符串,直接使用
|
||||
json = str;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果是对象,序列化为 JSON
|
||||
json = JsonSerializer.Serialize(data);
|
||||
}
|
||||
request.Content = new StringContent(json, Encoding.UTF8, config?.MediaType ?? "application/json");
|
||||
|
||||
var response = await SendAsync(request, config);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
return JsonSerializer.Deserialize<T>(content, new JsonSerializerOptions
|
||||
{
|
||||
PropertyNameCaseInsensitive = true
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "POST请求失败: {Url}", url);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// POST请求 - 泛型配置版本
|
||||
/// </summary>
|
||||
public async Task<T?> PostAsync<T>(string url, HttpRequestConfig<T> config)
|
||||
{
|
||||
return await PostAsync<T>(url, config.Data!, config);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// PUT请求
|
||||
/// </summary>
|
||||
public async Task<T?> PutAsync<T>(string url, object data, HttpRequestConfig? config = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var request = CreateHttpRequest(HttpMethod.Put, url, config);
|
||||
|
||||
var json = JsonSerializer.Serialize(data);
|
||||
request.Content = new StringContent(json, Encoding.UTF8, config?.MediaType ?? "application/json");
|
||||
|
||||
var response = await SendAsync(request, config);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
return JsonSerializer.Deserialize<T>(content, new JsonSerializerOptions
|
||||
{
|
||||
PropertyNameCaseInsensitive = true
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "PUT请求失败: {Url}", url);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DELETE请求
|
||||
/// </summary>
|
||||
public async Task<bool> DeleteAsync(string url, HttpRequestConfig? config = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var request = CreateHttpRequest(HttpMethod.Delete, url, config);
|
||||
var response = await SendAsync(request, config);
|
||||
|
||||
return response.IsSuccessStatusCode;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "DELETE请求失败: {Url}", url);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 下载文件流
|
||||
/// </summary>
|
||||
public async Task<Stream> GetStreamAsync(string url, HttpRequestConfig? config = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var request = CreateHttpRequest(HttpMethod.Get, url, config);
|
||||
var response = await SendAsync(request, config);
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
var stream = await response.Content.ReadAsStreamAsync();
|
||||
|
||||
// 转移所有权,调用方负责释放
|
||||
response = null;
|
||||
return stream;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "获取流失败: {Url}", url);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 下载文件字节数组
|
||||
/// </summary>
|
||||
public async Task<byte[]> DownloadFileAsync(string url, HttpRequestConfig? config = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var stream = await GetStreamAsync(url, config);
|
||||
using var memoryStream = new MemoryStream();
|
||||
await stream.CopyToAsync(memoryStream);
|
||||
return memoryStream.ToArray();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "下载文件失败: {Url}", url);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
50
src/Tool/HttpTool/IHttpService.cs
Normal file
50
src/Tool/HttpTool/IHttpService.cs
Normal file
@ -0,0 +1,50 @@
|
||||
namespace LMS.Tools.HttpTool
|
||||
{
|
||||
/// <summary>
|
||||
/// HTTP服务接口
|
||||
/// </summary>
|
||||
public interface IHttpService
|
||||
{
|
||||
/// <summary>
|
||||
/// 下载文件并返回字节数组
|
||||
/// </summary>
|
||||
/// <param name="url">文件URL</param>
|
||||
/// <param name="maxFileSize">最大文件大小限制(字节)</param>
|
||||
/// <returns>文件字节数组</returns>
|
||||
Task<byte[]?> DownloadFileAsync(string url, double maxFileSize);
|
||||
|
||||
/// <summary>
|
||||
/// 执行自定义异步HTTP请求
|
||||
/// </summary>
|
||||
/// <typeparam name="T">自定义请求函数返回的结果类型</typeparam>
|
||||
/// <param name="requestFunc">接收已初始化HttpClient的函数,返回表示要执行的异步操作的任务。不能为null。</param>
|
||||
/// <returns>表示异步操作的任务。任务结果包含自定义请求函数返回的值。</returns>
|
||||
/// <remarks>
|
||||
/// 此方法允许您使用预配置的HttpClient执行完全自定义的HTTP请求。
|
||||
/// 您可以在requestFunc中执行任何HTTP操作(GET、POST、PUT、DELETE等),
|
||||
/// 并返回任何类型的结果。HttpClient的生命周期由服务管理。
|
||||
///
|
||||
/// 使用示例:
|
||||
/// var result = await httpService.CustomRequestAsync(async client =>
|
||||
/// {
|
||||
/// var response = await client.GetAsync("https://api.example.com/data");
|
||||
/// return await response.Content.ReadAsStringAsync();
|
||||
/// });
|
||||
/// </remarks>
|
||||
Task<T> CustomRequestAsync<T>(Func<HttpClient, Task<T>> requestFunc);
|
||||
|
||||
/// <summary>
|
||||
/// 检查URL是否可访问
|
||||
/// </summary>
|
||||
/// <param name="url">要检查的URL</param>
|
||||
/// <returns>是否可访问</returns>
|
||||
Task<bool> IsUrlAccessibleAsync(string url);
|
||||
|
||||
/// <summary>
|
||||
/// 获取URL的Content-Type
|
||||
/// </summary>
|
||||
/// <param name="url">要检查的URL</param>
|
||||
/// <returns>Content-Type</returns>
|
||||
Task<string?> GetContentTypeAsync(string url);
|
||||
}
|
||||
}
|
||||
14
src/Tool/ImageTool/IImageService.cs
Normal file
14
src/Tool/ImageTool/IImageService.cs
Normal file
@ -0,0 +1,14 @@
|
||||
namespace lai_transfer.Tool.ImageTool
|
||||
{
|
||||
public interface IImageService
|
||||
{
|
||||
/// <summary>
|
||||
/// 合并图片 生成一个四宫格
|
||||
/// </summary>
|
||||
/// <param name="imageUrls"></param>
|
||||
/// <param name="width"></param>
|
||||
/// <param name="hight"></param>
|
||||
/// <returns></returns>
|
||||
Task<byte[]> ImageCombiner(List<string> imageUrls, int width, int hight);
|
||||
}
|
||||
}
|
||||
118
src/Tool/ImageTool/ImageService.cs
Normal file
118
src/Tool/ImageTool/ImageService.cs
Normal file
@ -0,0 +1,118 @@
|
||||
|
||||
using lai_transfer.Common.Helper;
|
||||
using lai_transfer.Tool.HttpTool;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace lai_transfer.Tool.ImageTool
|
||||
{
|
||||
|
||||
|
||||
public class ImageService(IHttpService httpService) : IImageService
|
||||
{
|
||||
|
||||
private static readonly ILogger _logger = LogHelper.GetLogger<ImageService>();
|
||||
|
||||
private readonly TimeSpan _downloadTimeout = TimeSpan.FromMinutes(3);
|
||||
|
||||
private readonly IHttpService _httpService = httpService;
|
||||
|
||||
#region Public Method
|
||||
public async Task<byte[]> ImageCombiner(List<string> imageUrls, int width, int height)
|
||||
{
|
||||
if (imageUrls == null || imageUrls.Count != 4)
|
||||
throw new ArgumentException("必须提供4个图片URL");
|
||||
|
||||
int finalWidth = width * 2;
|
||||
int finalHeight = height * 2;
|
||||
|
||||
// 创建画布
|
||||
using var surface = SKSurface.Create(new SKImageInfo(finalWidth, finalHeight));
|
||||
var canvas = surface.Canvas;
|
||||
|
||||
// 填充白色背景
|
||||
canvas.Clear(SKColors.White);
|
||||
|
||||
// 下载并绘制图片
|
||||
var tasks = new List<Task>();
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
tasks.Add(DrawImageAsync(canvas, imageUrls[i], i, width, height));
|
||||
}
|
||||
|
||||
await Task.WhenAll(tasks);
|
||||
|
||||
// 编码为JPEG
|
||||
using var image = surface.Snapshot();
|
||||
|
||||
SKEncodedImageFormat sif = SKEncodedImageFormat.Png;
|
||||
if (!ConfigHelper.Qiniu.OriginImage)
|
||||
{
|
||||
sif = SKEncodedImageFormat.Webp;
|
||||
}
|
||||
|
||||
using var data = image.Encode(sif, 100);
|
||||
|
||||
byte[] spliceImageByte = data.ToArray();
|
||||
|
||||
return spliceImageByte;
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Method
|
||||
|
||||
private async Task DrawImageAsync(SKCanvas canvas, string imageUrl, int position, int width, int height)
|
||||
{
|
||||
int row = position / 2;
|
||||
int col = position % 2;
|
||||
int x = col * width;
|
||||
int y = row * height;
|
||||
|
||||
SKBitmap bitmap = null;
|
||||
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrEmpty(imageUrl))
|
||||
{
|
||||
// 下载图片
|
||||
byte[] imageData =
|
||||
await RetryHelper.RetryAsync<byte[]>(async () =>
|
||||
{
|
||||
return await _httpService.DownloadFileAsync(imageUrl);
|
||||
}, 5);
|
||||
bitmap = SKBitmap.Decode(imageData);
|
||||
|
||||
if (bitmap != null)
|
||||
{
|
||||
// 调整尺寸并绘制
|
||||
canvas.DrawBitmap(bitmap, x, y);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果URL为空或下载失败,绘制白色方块
|
||||
DrawWhiteBlock(canvas, x, y, width, height);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 下载或处理失败,绘制白色方块
|
||||
Console.WriteLine($"图片 {position} 处理失败: {ex.Message}");
|
||||
DrawWhiteBlock(canvas, x, y, width, height);
|
||||
}
|
||||
finally
|
||||
{
|
||||
bitmap?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawWhiteBlock(SKCanvas canvas, float x, float y, float width, float height)
|
||||
{
|
||||
using var paint = new SKPaint { Color = SKColors.White };
|
||||
canvas.DrawRect(x, y, width, height, paint);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
57
src/Tool/QiniuService/IQiniuService.cs
Normal file
57
src/Tool/QiniuService/IQiniuService.cs
Normal file
@ -0,0 +1,57 @@
|
||||
using static lai_transfer.Tool.QiniuService.QiniuDataModel;
|
||||
|
||||
namespace lai_transfer.Tool.QiniuService
|
||||
{
|
||||
public interface IQiniuService
|
||||
{
|
||||
/// <summary>
|
||||
/// 检查文件的字节大小是否符合要求
|
||||
/// </summary>
|
||||
/// <param name="fileBytes"></param>
|
||||
/// <returns></returns>
|
||||
public UploadResult CheckFileBytesSize(byte[] fileBytes);
|
||||
|
||||
/// <summary>
|
||||
/// 生成七牛云上传的路径 key
|
||||
/// </summary>
|
||||
/// <param name="userId"></param>
|
||||
/// <param name="fileName"></param>
|
||||
/// <returns></returns>
|
||||
string GenerateFileKey(long userId, string fileName);
|
||||
|
||||
/// <summary>
|
||||
/// 计算文件的 SHA1 哈希值
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <returns></returns>
|
||||
string ComputeSHA1Hash(byte[] data);
|
||||
|
||||
/// <summary>
|
||||
/// 获取七牛云的配置 用于上传图片
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task<QiniuSettings> InitQiniuSetting();
|
||||
|
||||
/// <summary>
|
||||
/// 生成七牛的上传凭证
|
||||
/// </summary>
|
||||
/// <param name="qiniuSettings"></param>
|
||||
/// <returns></returns>
|
||||
string GeneratePolicy(QiniuSettings qiniuSettings);
|
||||
|
||||
/// <summary>
|
||||
/// 将 byte 数组上传到七牛云
|
||||
/// </summary>
|
||||
/// <param name="fileBytes"></param>
|
||||
/// <returns></returns>
|
||||
Task<UploadResult> UploadFileToQiNiu(byte[] fileBytes, string fileName, string fileKey);
|
||||
|
||||
/// <summary>
|
||||
/// 构建文件的访问 URL
|
||||
/// </summary>
|
||||
/// <param name="domain"></param>
|
||||
/// <param name="fileKey"></param>
|
||||
/// <returns></returns>
|
||||
string BuildFileUrl(string domain, string fileKey);
|
||||
}
|
||||
}
|
||||
33
src/Tool/QiniuService/QiniuDataModel.cs
Normal file
33
src/Tool/QiniuService/QiniuDataModel.cs
Normal file
@ -0,0 +1,33 @@
|
||||
namespace lai_transfer.Tool.QiniuService
|
||||
{
|
||||
public class QiniuDataModel
|
||||
{
|
||||
|
||||
public class QiniuSettings
|
||||
{
|
||||
public string AccessKey { get; set; }
|
||||
public string SecretKey { get; set; }
|
||||
public string BucketName { get; set; }
|
||||
public string Domain { get; set; }
|
||||
|
||||
public double MaxFileSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 删除时间 天数 没有值 则不删除
|
||||
/// </summary>
|
||||
public int? DeleteDay { get; set; }
|
||||
}
|
||||
|
||||
public class UploadResult
|
||||
{
|
||||
public bool Success { get; set; }
|
||||
public string Message { get; set; }
|
||||
public string Url { get; set; }
|
||||
public string FileKey { get; set; }
|
||||
public string Hash { get; set; }
|
||||
public long FileId { get; set; }
|
||||
public long FileSize { get; set; }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
145
src/Tool/QiniuService/QiniuService.cs
Normal file
145
src/Tool/QiniuService/QiniuService.cs
Normal file
@ -0,0 +1,145 @@
|
||||
using lai_transfer.Common.Helper;
|
||||
using Qiniu.Http;
|
||||
using Qiniu.IO;
|
||||
using Qiniu.IO.Model;
|
||||
using Qiniu.Util;
|
||||
using System.Security.Cryptography;
|
||||
using static lai_transfer.Tool.QiniuService.QiniuDataModel;
|
||||
|
||||
namespace lai_transfer.Tool.QiniuService
|
||||
{
|
||||
public class QiniuService(ILogger<QiniuService> logger) : IQiniuService
|
||||
{
|
||||
private readonly UploadManager _uploadManager = new();
|
||||
private readonly ILogger<QiniuService> _logger = logger;
|
||||
|
||||
/// <summary>
|
||||
/// 检查文件的字节大小是否符合要求
|
||||
/// </summary>
|
||||
/// <param name="fileBytes"></param>
|
||||
/// <returns></returns>
|
||||
public UploadResult CheckFileBytesSize(byte[] fileBytes)
|
||||
{
|
||||
if (fileBytes == null || fileBytes.Length == 0)
|
||||
{
|
||||
return new UploadResult
|
||||
{
|
||||
Success = false,
|
||||
Message = "文件字节数据不能为空"
|
||||
};
|
||||
}
|
||||
|
||||
double MaxFileSize = ConfigHelper.Qiniu.MaxFileSize;
|
||||
|
||||
if (fileBytes.Length > MaxFileSize * 1024 * 1024)
|
||||
{
|
||||
return new UploadResult
|
||||
{
|
||||
Success = false,
|
||||
Message = $"文件大小不能超过 {MaxFileSize}MB"
|
||||
};
|
||||
}
|
||||
|
||||
return new UploadResult { Success = true, Message = string.Empty };
|
||||
}
|
||||
|
||||
public string ComputeSHA1Hash(byte[] data)
|
||||
{
|
||||
var hash = SHA1.HashData(data);
|
||||
return Convert.ToHexString(hash).ToLower();
|
||||
}
|
||||
|
||||
public string GenerateFileKey(long userId, string fileName)
|
||||
{
|
||||
var date = DateTime.Now.ToString("yyyyMMdd");
|
||||
//var extension = Path.GetExtension(fileName);
|
||||
return $"user/{userId}/{date}/{fileName}";
|
||||
}
|
||||
|
||||
public async Task<QiniuSettings> InitQiniuSetting()
|
||||
{
|
||||
QiniuSettings qiniuSettings = new()
|
||||
{
|
||||
AccessKey = ConfigHelper.Qiniu.AccessKey,
|
||||
Domain = ConfigHelper.Qiniu.Domain,
|
||||
SecretKey = ConfigHelper.Qiniu.SecretKey,
|
||||
BucketName = ConfigHelper.Qiniu.BucketName,
|
||||
};
|
||||
|
||||
if (qiniuSettings == null || string.IsNullOrEmpty(qiniuSettings.AccessKey) || string.IsNullOrEmpty(qiniuSettings.SecretKey) || string.IsNullOrEmpty(qiniuSettings.BucketName) || string.IsNullOrEmpty(qiniuSettings.Domain))
|
||||
{
|
||||
throw new Exception("七牛云配置不完整,请检查配置,请联系管理员");
|
||||
}
|
||||
return qiniuSettings;
|
||||
}
|
||||
|
||||
public string GeneratePolicy(QiniuSettings qiniuSettings)
|
||||
{
|
||||
Mac mac = new(qiniuSettings.AccessKey, qiniuSettings.SecretKey);
|
||||
var putPolicy = new PutPolicy
|
||||
{
|
||||
Scope = qiniuSettings.BucketName
|
||||
};
|
||||
if (qiniuSettings.DeleteDay != null)
|
||||
{
|
||||
putPolicy.DeleteAfterDays = qiniuSettings.DeleteDay.Value; // 设置过期时间
|
||||
}
|
||||
putPolicy.SetExpires(3600);
|
||||
string token = Auth.CreateUploadToken(mac, putPolicy.ToJsonString());
|
||||
return token;
|
||||
}
|
||||
|
||||
public string BuildFileUrl(string domain, string fileKey)
|
||||
{
|
||||
return $"{domain}/{fileKey}";
|
||||
}
|
||||
|
||||
public async Task<UploadResult> UploadFileToQiNiu(byte[] fileBytes, string fileName, string fileKey)
|
||||
{
|
||||
try
|
||||
{
|
||||
QiniuSettings qiniuSettings = await InitQiniuSetting();
|
||||
string token = GeneratePolicy(qiniuSettings);
|
||||
string hash = ComputeSHA1Hash(fileBytes);
|
||||
HttpResult uploadResult;
|
||||
|
||||
_logger.LogInformation("开始上传文件, 文件名: {fileName}, 文件大小: {fileLength} 字节, 文件Key: {fileKey}", fileName, fileBytes.Length, fileKey);
|
||||
|
||||
using (var stream = new MemoryStream(fileBytes))
|
||||
{
|
||||
uploadResult = await _uploadManager.UploadStreamAsync(stream, fileKey, token);
|
||||
}
|
||||
// 8. 检查上传结果
|
||||
if (uploadResult.Code != 200)
|
||||
{
|
||||
_logger.LogError("文件上传失败, 错误信息: {error}", uploadResult.Text);
|
||||
throw new Exception(uploadResult.Text);
|
||||
}
|
||||
|
||||
var qiniuUrl = BuildFileUrl(qiniuSettings.Domain, fileKey);
|
||||
|
||||
_logger.LogInformation("文件上传成功, 文件Key: {fileKey},文件链接: {url}", fileKey, qiniuUrl);
|
||||
UploadResult uploadResult1 = new()
|
||||
{
|
||||
Success = true,
|
||||
Message = "upload success",
|
||||
Url = qiniuUrl,
|
||||
FileKey = fileKey,
|
||||
Hash = hash,
|
||||
FileId = 0,
|
||||
FileSize = fileBytes.LongLength,
|
||||
};
|
||||
return uploadResult1;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "上传文件到七牛云失败, 文件名: {fileName},失败原因: {message}", fileName, ex.Message);
|
||||
return new UploadResult
|
||||
{
|
||||
Success = false,
|
||||
Message = ex.Message
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -26,14 +26,20 @@
|
||||
</PackageReference>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="9.0.0-preview.2.efcore.9.0.0" />
|
||||
<PackageReference Include="Qiniu.Shared" Version="7.2.15" />
|
||||
<PackageReference Include="Realm" Version="20.1.0" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="9.0.0" />
|
||||
<PackageReference Include="Serilog.Settings.Configuration" Version="9.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
|
||||
<PackageReference Include="SkiaSharp" Version="3.119.1" />
|
||||
<PackageReference Include="SkiaSharp.NativeAssets.Linux" Version="3.119.1" />
|
||||
<PackageReference Include="SkiaSharp.NativeAssets.Linux.NoDependencies" Version="3.119.1" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="8.1.4" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Common\Middleware\" />
|
||||
<Folder Include="Examples\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user