From 1f71879e0f1d1b7cef23c91a8e57e1868c98fbe4 Mon Sep 17 00:00:00 2001 From: lq1405 <2769838458@qq.com> Date: Fri, 25 Jul 2025 12:47:29 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=8F=90=E7=A4=BA=E8=AF=8D?= =?UTF-8?q?=E7=BF=BB=E8=AF=91=20=E6=89=80=E6=9C=89=E7=9A=84=E6=8F=90?= =?UTF-8?q?=E7=A4=BA=E8=AF=8D=20=E4=B8=AD=E6=96=87=E7=BF=BB=E8=AF=91?= =?UTF-8?q?=E4=B8=BA=E8=8B=B1=E6=96=87=EF=BC=81=EF=BC=81=EF=BC=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 4 +- .../Extensions/HttpContextExtensions.cs | 45 ++++ ...teHandlerBuilderAuthorizationExtensions.cs | 2 - .../Filters/SplitMJAuthorizationFilter.cs | 2 - src/Common/Helper/ConfigHelper.cs | 104 +++++++- src/Common/Helper/JSONHelper.cs | 239 ++++++++++++++++++ src/Common/Helper/ServiceLocator.cs | 83 ++++++ src/Configuration/config/transfer-temp.json | 19 ++ src/Configuration/config/transfer.json | 6 - src/ConfigureServices.cs | 25 ++ .../MJTransferEndpoint/MJGetFetchIdService.cs | 6 +- .../MJPostImagineService.cs | 95 ++++++- .../MJTransferEndpoint/MJPostModalService.cs | 11 + .../MJPostUploadDiscordImages.cs | 1 - src/Examples/ServiceUsageExamples.cs | 88 +++++++ src/Program.cs | 3 + src/Services/BaiduTranslateService.cs | 104 ++++++++ src/Services/GPTTranslateService.cs | 105 ++++++++ src/Services/ITranslateService.cs | 9 + src/Tool/Extensions/JsonConfigReader.cs | 7 + 20 files changed, 926 insertions(+), 32 deletions(-) create mode 100644 src/Common/Helper/ServiceLocator.cs create mode 100644 src/Configuration/config/transfer-temp.json delete mode 100644 src/Configuration/config/transfer.json create mode 100644 src/Examples/ServiceUsageExamples.cs create mode 100644 src/Services/BaiduTranslateService.cs create mode 100644 src/Services/GPTTranslateService.cs create mode 100644 src/Services/ITranslateService.cs diff --git a/.gitignore b/.gitignore index 9491a2f..223ece1 100644 --- a/.gitignore +++ b/.gitignore @@ -360,4 +360,6 @@ MigrationBackup/ .ionide/ # Fody - auto-generated XML schema -FodyWeavers.xsd \ No newline at end of file +FodyWeavers.xsd +/src/Configuration/config/transfer.json +/src/Configuration/config/transfer.json diff --git a/src/Common/Extensions/HttpContextExtensions.cs b/src/Common/Extensions/HttpContextExtensions.cs index d22f83f..45e8d88 100644 --- a/src/Common/Extensions/HttpContextExtensions.cs +++ b/src/Common/Extensions/HttpContextExtensions.cs @@ -55,5 +55,50 @@ namespace lai_transfer.Common.Extensions return fullPath; } + /// + /// 从HttpContext的RequestServices中获取服务 + /// + /// 服务类型 + /// Http上下文 + /// 服务实例,如果未找到则返回null + public static T? GetService(this HttpContext httpContext) where T : class + { + return httpContext.RequestServices.GetService(); + } + + /// + /// 从HttpContext的RequestServices中获取必需的服务 + /// + /// 服务类型 + /// Http上下文 + /// 服务实例 + /// 如果服务未注册则抛出异常 + public static T GetRequiredService(this HttpContext httpContext) where T : notnull + { + return httpContext.RequestServices.GetRequiredService(); + } + + /// + /// 从HttpContext的RequestServices中获取服务(非泛型版本) + /// + /// Http上下文 + /// 服务类型 + /// 服务实例,如果未找到则返回null + public static object? GetService(this HttpContext httpContext, Type serviceType) + { + return httpContext.RequestServices.GetService(serviceType); + } + + /// + /// 从HttpContext的RequestServices中获取必需的服务(非泛型版本) + /// + /// Http上下文 + /// 服务类型 + /// 服务实例 + /// 如果服务未注册则抛出异常 + public static object GetRequiredService(this HttpContext httpContext, Type serviceType) + { + return httpContext.RequestServices.GetRequiredService(serviceType); + } } } diff --git a/src/Common/Extensions/RouteHandlerBuilderAuthorizationExtensions.cs b/src/Common/Extensions/RouteHandlerBuilderAuthorizationExtensions.cs index 465bcfd..4882d92 100644 --- a/src/Common/Extensions/RouteHandlerBuilderAuthorizationExtensions.cs +++ b/src/Common/Extensions/RouteHandlerBuilderAuthorizationExtensions.cs @@ -1,6 +1,4 @@ using lai_transfer.Common.Filters; -using lai_transfer.Configuration; -using Microsoft.AspNetCore.Mvc.Filters; namespace lai_transfer.Common.Extensions { diff --git a/src/Common/Filters/SplitMJAuthorizationFilter.cs b/src/Common/Filters/SplitMJAuthorizationFilter.cs index ceef808..f7464c1 100644 --- a/src/Common/Filters/SplitMJAuthorizationFilter.cs +++ b/src/Common/Filters/SplitMJAuthorizationFilter.cs @@ -62,8 +62,6 @@ return TypedResults.Unauthorized(); } } - - } } } \ No newline at end of file diff --git a/src/Common/Helper/ConfigHelper.cs b/src/Common/Helper/ConfigHelper.cs index 936d316..392a5a1 100644 --- a/src/Common/Helper/ConfigHelper.cs +++ b/src/Common/Helper/ConfigHelper.cs @@ -6,14 +6,6 @@ namespace lai_transfer.Common.Helper { private static readonly ILogger _logger = LogHelper.GetLogger(); - // 存储Origin配置 - public static class Origin - { - // 将private set改为internal set,允许同一程序集中的代码设置属性值 - public static string BaseUrl { get; internal set; } - public static string Token { get; internal set; } - } - // 初始化配置 public static void Initialize() { @@ -24,15 +16,105 @@ namespace lai_transfer.Common.Helper // 读取配置文件 var reader = new JsonConfigReader("Configuration/config/transfer.json"); - // 加载Origin配置 - Origin.BaseUrl = reader.GetString("Origin.BaseUrl") ?? string.Empty; - Origin.Token = reader.GetString("Origin.Token") ?? string.Empty; + // 初始化Origin配置 + Origin.Init(reader); + + Translate.Init(reader); _logger.LogInformation("配置加载完成"); } catch (Exception ex) { _logger.LogError(ex, "加载配置文件失败"); + // 报错,结束程序运行 + throw new InvalidOperationException("无法加载应用程序配置,请检查配置文件是否存在或格式是否正确。", ex); + } + } + + public static class Translate + { + // 将private set改为internal set,允许同一程序集中的代码设置属性值 + public static bool Enable { get; internal set; } = false; + + // 安全模式 会优先百度 百度 报错 再使用OpenAI + public static string Model { get; internal set; } = "BAIDU"; + + public static string BaiduAppId { get; internal set; } = string.Empty; + public static string BaiduAppSecret { get; internal set; } = string.Empty; + public static string OpenaiGptApiUrl { get; internal set; } = string.Empty; + public static string OpenaiGptApiKey { get; internal set; } = string.Empty; + public static TimeSpan TimeOut { get; internal set; } = TimeSpan.FromSeconds(10); + + public static string OpenaiGptModel { get; internal set; } = "gpt-4o-mini"; + + public static int OpenaiGptMaxTokens { get; internal set; } = 2048; + + public static double OpenaiGptTemperature { get; internal set; } = 0; + + + /// + /// 初始化翻译配置 + /// + + public static void Init(JsonConfigReader reader) + { + try + { + _logger.LogInformation("正在加载翻译配置..."); + // 加载翻译配置 + Enable = reader.GetBool("Translate.Enable") ?? false; + Model = reader.GetString("Translate.Model") ?? "BAIDU"; + BaiduAppId = reader.GetString("Translate.BaiduAppId") ?? string.Empty; + BaiduAppSecret = reader.GetString("Translate.BaiduAppSecret") ?? string.Empty; + + OpenaiGptApiUrl = reader.GetString("Translate.OpenaiGptApiUrl") ?? "https://laitooo.net"; + OpenaiGptApiKey = reader.GetString("Translate.OpenaiGptApiKey") ?? string.Empty; + OpenaiGptModel = reader.GetString("Translate.OpenaiGptModel") ?? "Doubao-lite-32k"; + + OpenaiGptMaxTokens = reader.GetInt("Translate.OpenaiGptMaxTokens") ?? 2048; + OpenaiGptTemperature = reader.GetDouble("Translate.OpenaiGptTemperature") ?? 0; + + TimeOut = TimeSpan.FromSeconds(reader.GetInt("Translate.TimeOut") ?? 20); + _logger.LogInformation("翻译配置加载完成"); + } + catch (Exception ex) + { + _logger.LogError(ex, "加载翻译配置失败"); + // 报错,结束程序运行 + throw new InvalidOperationException("无法加载翻译配置,请检查配置文件是否存在或格式是否正确。", ex); + } + } + + } + + // 存储Origin配置 + public static class Origin + { + // 将private set改为internal set,允许同一程序集中的代码设置属性值 + public static string BaseUrl { get; internal set; } = string.Empty; + public static string Token { get; internal set; } = string.Empty; + + /// + /// 初始化Origin配置 + /// + public static void Init(JsonConfigReader reader) + { + try + { + + _logger.LogInformation("正在加载Origin配置..."); + // 加载Origin配置 + BaseUrl = reader.GetString("Origin.BaseUrl") ?? string.Empty; + Token = reader.GetString("Origin.Token") ?? string.Empty; + + _logger.LogInformation("Origin配置加载完成"); + } + catch (Exception ex) + { + _logger.LogError(ex, "加载Origin配置失败"); + // 报错,结束程序运行 + throw new InvalidOperationException("无法加载Origin配置,请检查配置文件是否存在或格式是否正确。", ex); + } } } } diff --git a/src/Common/Helper/JSONHelper.cs b/src/Common/Helper/JSONHelper.cs index 7a24dbf..cfc08c5 100644 --- a/src/Common/Helper/JSONHelper.cs +++ b/src/Common/Helper/JSONHelper.cs @@ -34,5 +34,244 @@ namespace lai_transfer.Common.Helper return Encoding.UTF8.GetString(stream.ToArray()); } + + /// + /// 获取指定属性的值 + /// + /// JSON元素 + /// 属性名称 + /// 属性值的JsonElement,如果属性不存在则返回null + public static JsonElement? GetJsonProperty(JsonElement element, string propertyName) + { + // 检查元素是否为对象 + if (element.ValueKind != JsonValueKind.Object) + return null; + + // 尝试获取属性 + if (element.TryGetProperty(propertyName, out JsonElement property)) + { + return property; + } + + return null; + } + + /// + /// 获取指定属性的字符串值 + /// + /// JSON元素 + /// 属性名称 + /// 属性的字符串值,如果属性不存在或无法转换则返回null + public static string? GetJsonPropertyString(JsonElement element, string propertyName) + { + var property = GetJsonProperty(element, propertyName); + return property?.GetString(); + } + + /// + /// 获取指定属性的整数值 + /// + /// JSON元素 + /// 属性名称 + /// 属性的整数值,如果属性不存在或无法转换则返回null + public static int? GetJsonPropertyInt(JsonElement element, string propertyName) + { + var property = GetJsonProperty(element, propertyName); + if (property.HasValue && property.Value.TryGetInt32(out int value)) + { + return value; + } + return null; + } + + /// + /// 获取指定属性的布尔值 + /// + /// JSON元素 + /// 属性名称 + /// 属性的布尔值,如果属性不存在或无法转换则返回null + public static bool? GetJsonPropertyBool(JsonElement element, string propertyName) + { + var property = GetJsonProperty(element, propertyName); + if (property.HasValue) + { + try + { + return property.Value.GetBoolean(); + } + catch + { + return null; + } + } + return null; + } + + /// + /// 设置或更新JSON对象中指定属性的值 + /// + /// JSON元素 + /// 属性名称 + /// 要设置的值 + /// 更新后的JsonElement + public static JsonElement SetJsonProperty(JsonElement element, string propertyName, object? value) + { + // 检查元素是否为对象 + if (element.ValueKind != JsonValueKind.Object) + return element; + + using var stream = new MemoryStream(); + using var writer = new Utf8JsonWriter(stream); + + writer.WriteStartObject(); + + bool propertyFound = false; + + // 遍历所有现有属性 + foreach (var property in element.EnumerateObject()) + { + if (property.Name == propertyName) + { + // 写入新值 + WritePropertyValue(writer, propertyName, value); + propertyFound = true; + } + else + { + // 保留现有属性 + property.WriteTo(writer); + } + } + + // 如果属性不存在,添加新属性 + if (!propertyFound) + { + WritePropertyValue(writer, propertyName, value); + } + + writer.WriteEndObject(); + writer.Flush(); + + // 解析生成的JSON并返回JsonElement + var jsonString = Encoding.UTF8.GetString(stream.ToArray()); + using var document = JsonDocument.Parse(jsonString); + return document.RootElement.Clone(); + } + + /// + /// 设置或更新JSON对象中指定属性的值,返回JSON字符串 + /// + /// JSON元素 + /// 属性名称 + /// 要设置的值 + /// 更新后的JSON字符串 + public static string SetJsonPropertyAsString(JsonElement element, string propertyName, object? value) + { + var updatedElement = SetJsonProperty(element, propertyName, value); + return updatedElement.GetRawText(); + } + + /// + /// 设置多个属性的值 + /// + /// JSON元素 + /// 属性名称和值的字典 + /// 更新后的JSON字符串 + public static string SetJsonProperties(JsonElement element, Dictionary properties) + { + // 检查元素是否为对象 + if (element.ValueKind != JsonValueKind.Object) + return element.GetRawText(); + + using var stream = new MemoryStream(); + using var writer = new Utf8JsonWriter(stream); + + writer.WriteStartObject(); + + var processedProperties = new HashSet(); + + // 遍历所有现有属性 + foreach (var property in element.EnumerateObject()) + { + if (properties.ContainsKey(property.Name)) + { + // 写入新值 + WritePropertyValue(writer, property.Name, properties[property.Name]); + processedProperties.Add(property.Name); + } + else + { + // 保留现有属性 + property.WriteTo(writer); + } + } + + // 添加不存在的新属性 + foreach (var kvp in properties) + { + if (!processedProperties.Contains(kvp.Key)) + { + WritePropertyValue(writer, kvp.Key, kvp.Value); + } + } + + writer.WriteEndObject(); + writer.Flush(); + + return Encoding.UTF8.GetString(stream.ToArray()); + } + + /// + /// 写入属性值到JSON writer + /// + /// JSON writer + /// 属性名称 + /// 属性值 + private static void WritePropertyValue(Utf8JsonWriter writer, string propertyName, object? value) + { + writer.WritePropertyName(propertyName); + + switch (value) + { + case null: + writer.WriteNullValue(); + break; + case string stringValue: + writer.WriteStringValue(stringValue); + break; + case int intValue: + writer.WriteNumberValue(intValue); + break; + case long longValue: + writer.WriteNumberValue(longValue); + break; + case float floatValue: + writer.WriteNumberValue(floatValue); + break; + case double doubleValue: + writer.WriteNumberValue(doubleValue); + break; + case decimal decimalValue: + writer.WriteNumberValue(decimalValue); + break; + case bool boolValue: + writer.WriteBooleanValue(boolValue); + break; + case DateTime dateTimeValue: + writer.WriteStringValue(dateTimeValue.ToString("O")); // ISO 8601 format + break; + case JsonElement jsonElement: + jsonElement.WriteTo(writer); + break; + default: + { + // 对于复杂对象,序列化为JSON字符串 + var jsonString = JsonSerializer.Serialize(value); + using var doc = JsonDocument.Parse(jsonString); + doc.RootElement.WriteTo(writer); + break; + } + } + } } } diff --git a/src/Common/Helper/ServiceLocator.cs b/src/Common/Helper/ServiceLocator.cs new file mode 100644 index 0000000..794acbf --- /dev/null +++ b/src/Common/Helper/ServiceLocator.cs @@ -0,0 +1,83 @@ +namespace lai_transfer.Common.Helper +{ + /// + /// ȫַλûHttpContextĵطȡעķ + /// + public static class ServiceLocator + { + private static IServiceProvider? _serviceProvider; + + /// + /// ʼṩߣProgram.csеã + /// + /// ṩ + public static void Initialize(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + + /// + /// ȡ + /// + /// + /// ʵδҵ򷵻null + public static T? GetService() where T : class + { + EnsureInitialized(); + return _serviceProvider!.GetService(); + } + + /// + /// ȡķ + /// + /// + /// ʵ + /// δע׳쳣 + public static T GetRequiredService() where T : notnull + { + EnsureInitialized(); + return _serviceProvider!.GetRequiredService(); + } + + /// + /// ȡ񣨷ǷͰ汾 + /// + /// + /// ʵδҵ򷵻null + public static object? GetService(Type serviceType) + { + EnsureInitialized(); + return _serviceProvider!.GetService(serviceType); + } + + /// + /// ȡķ񣨷ǷͰ汾 + /// + /// + /// ʵ + /// δע׳쳣 + public static object GetRequiredService(Type serviceType) + { + EnsureInitialized(); + return _serviceProvider!.GetRequiredService(serviceType); + } + + /// + /// Χ + /// + /// Χ + public static IServiceScope CreateScope() + { + EnsureInitialized(); + return _serviceProvider!.CreateScope(); + } + + private static void EnsureInitialized() + { + if (_serviceProvider == null) + { + throw new InvalidOperationException("ServiceLocator δʼ Program.cs е ServiceLocator.Initialize(app.Services)"); + } + } + } +} \ No newline at end of file diff --git a/src/Configuration/config/transfer-temp.json b/src/Configuration/config/transfer-temp.json new file mode 100644 index 0000000..a1fb908 --- /dev/null +++ b/src/Configuration/config/transfer-temp.json @@ -0,0 +1,19 @@ +{ + "Origin": { + "BaseUrl": "https://mjapi.bzu.cn", + "Token": "23830faf80e8e69" + }, + "Translate": { + "Enable": true, + "Model": "BAIDU", + "BaiduAppId": "2024032500", + "BaiduAppSecret": "hcCG8H", + "OpenaiGptApiUrl": "https://laitool.net/v1/chat/completions", + + "OpenaiGptApiKey": "sk-r5", + "OpenaiGptModel": "Doubao-pro-32k", + "OpenaiGptMaxTokens": 2048, + "OpenaiGptTemperature": 0, + "TimeOut": 20 + } +} diff --git a/src/Configuration/config/transfer.json b/src/Configuration/config/transfer.json deleted file mode 100644 index aa83fe3..0000000 --- a/src/Configuration/config/transfer.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "Origin": { - "BaseUrl": "https://mjapi.bzu.cn", - "Token": "23830faf80e8e69988b3fcd9aa08e9ad123" - } -} diff --git a/src/ConfigureServices.cs b/src/ConfigureServices.cs index d8bc178..35cade1 100644 --- a/src/ConfigureServices.cs +++ b/src/ConfigureServices.cs @@ -1,12 +1,17 @@ using FluentValidation; using lai_transfer.Configuration; using lai_transfer.Model.Entity; +using lai_transfer.Services; +using lai_transfer.EndpointServices.MJTransferEndpoint; 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 { @@ -40,11 +45,31 @@ namespace lai_transfer // 添加JWT认证配置 builder.Services.AddJWTAuthentication(); + builder.Services.AddService(); + // 注册校验 builder.Services.AddValidatorsFromAssembly(typeof(ConfigureServices).Assembly); } + /// + /// 注册一些服务 + /// + /// + private static void AddService(this IServiceCollection services) + { + if (ConfigHelper.Translate.Model == "GPT") + { + // 翻译服务注入 + services.AddSingleton(); + } + else + { + services.AddSingleton(); + } + + } + /// /// JWT 配置 /// diff --git a/src/EndpointServices/MJTransferEndpoint/MJGetFetchIdService.cs b/src/EndpointServices/MJTransferEndpoint/MJGetFetchIdService.cs index ad320c5..e745a43 100644 --- a/src/EndpointServices/MJTransferEndpoint/MJGetFetchIdService.cs +++ b/src/EndpointServices/MJTransferEndpoint/MJGetFetchIdService.cs @@ -220,7 +220,7 @@ namespace lai_transfer.EndpointServices.MJTransferEndpoint if (isPartner && !string.IsNullOrEmpty(partnerTaskId)) { - _logger.LogInformation($"处理合作伙伴任务: {partnerTaskId}"); + _logger.LogInformation($"处理合作伙伴任务: {partnerTaskId}, TaskId : {data?.id ?? string.Empty}"); // 1111111111 @@ -279,7 +279,7 @@ namespace lai_transfer.EndpointServices.MJTransferEndpoint if (isOfficial && !string.IsNullOrEmpty(officialTaskId)) { - _logger.LogInformation($"处理官方任务: {officialTaskId}"); + _logger.LogInformation($"处理官方任务: {officialTaskId}, TaskId : {data?.id ?? string.Empty}"); // 直接遍历,让异常处理兜底 var imageUrls = new List(); @@ -336,7 +336,7 @@ namespace lai_transfer.EndpointServices.MJTransferEndpoint if (isYouChuan && !string.IsNullOrEmpty(youChuanTaskId)) { - _logger.LogInformation($"处理悠船任务: {youChuanTaskId}"); + _logger.LogInformation($"处理悠船任务: {youChuanTaskId}, TaskId : {data?.id ?? string.Empty}"); // 直接遍历,让异常处理兜底 var imageUrls = new List(); diff --git a/src/EndpointServices/MJTransferEndpoint/MJPostImagineService.cs b/src/EndpointServices/MJTransferEndpoint/MJPostImagineService.cs index 766c30f..d417159 100644 --- a/src/EndpointServices/MJTransferEndpoint/MJPostImagineService.cs +++ b/src/EndpointServices/MJTransferEndpoint/MJPostImagineService.cs @@ -1,23 +1,26 @@ using lai_transfer.Common.Extensions; using lai_transfer.Common.Helper; using lai_transfer.Common.Results; -using lai_transfer.Configuration; using lai_transfer.Endpoints; +using lai_transfer.Services; using System.Text; using System.Text.Json; +using System.Text.RegularExpressions; namespace lai_transfer.EndpointServices.MJTransferEndpoint { public class MJPostImagineService : IEndpoint { + private static readonly ILogger _logger = LogHelper.GetLogger(); + public static void Map(IEndpointRouteBuilder app) => app .MapPost("/submit/imagine", Handle) .WithSummary("Midjourney 提交 Imagine 任务") .WithMJAuthorizationHeader(); - private static async Task Handle(JsonElement model, HttpContext httpContext, ILogger logger) + private static async Task Handle(JsonElement model, HttpContext httpContext) { - (string content, string contentType, int statusCode) = await SendOriginalImagine(model, httpContext, logger); + (string content, string contentType, int statusCode) = await SendOriginalImagine(model, httpContext); return Results.Text(content, contentType, statusCode: statusCode); } @@ -27,7 +30,7 @@ namespace lai_transfer.EndpointServices.MJTransferEndpoint /// /// /// - private static async Task<(string content, string contentType, int statusCode)> SendOriginalImagine(JsonElement model, HttpContext httpContext, ILogger logger) + private static async Task<(string content, string contentType, int statusCode)> SendOriginalImagine(JsonElement model, HttpContext httpContext) { try { @@ -39,9 +42,18 @@ namespace lai_transfer.EndpointServices.MJTransferEndpoint client.DefaultRequestHeaders.Add("Authorization", $"Bearer {authorizationResult.Token}"); client.Timeout = Timeout.InfiniteTimeSpan; - // 删除回调参数 notifyHook + // 强制翻译提示词 + string prompt = JSONHelper.GetJsonPropertyString(model, "prompt") ?? string.Empty; + // 使用HttpContext扩展方法获取翻译服务 + var translateService = ServiceLocator.GetRequiredService(); + 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(); @@ -49,10 +61,81 @@ namespace lai_transfer.EndpointServices.MJTransferEndpoint } catch (Exception ex) { - logger.LogError(ex, $"请求 {httpContext.GetFullRequestPath()} 失败"); + _logger.LogError(ex, $"请求 {httpContext.GetFullRequestPath()} 失败"); // 处理异常,返回错误信息 return (ex.Message, "application/json", StatusCodes.Status500InternalServerError); } } + + /// + /// 翻译提示词 + /// + /// 原始提示词 + /// 翻译服务 + /// 翻译后的提示词 + public static async Task 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 imageUrls = new List(); + 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; + } + } } } diff --git a/src/EndpointServices/MJTransferEndpoint/MJPostModalService.cs b/src/EndpointServices/MJTransferEndpoint/MJPostModalService.cs index 5bbdc5f..75bbbab 100644 --- a/src/EndpointServices/MJTransferEndpoint/MJPostModalService.cs +++ b/src/EndpointServices/MJTransferEndpoint/MJPostModalService.cs @@ -2,6 +2,7 @@ using lai_transfer.Common.Helper; using lai_transfer.Common.Results; using lai_transfer.Endpoints; +using lai_transfer.Services; using System.Text.Json; namespace lai_transfer.EndpointServices.MJTransferEndpoint @@ -30,6 +31,16 @@ namespace lai_transfer.EndpointServices.MJTransferEndpoint using HttpClient client = new HttpClient(); client.DefaultRequestHeaders.Add("Authorization", $"Bearer {transferAuthorizationResult.Token}"); client.Timeout = Timeout.InfiniteTimeSpan; + + // 强制翻译提示词 + string prompt = JSONHelper.GetJsonPropertyString(model, "prompt") ?? string.Empty; + + // 使用HttpContext扩展方法获取翻译服务 + var translateService = ServiceLocator.GetRequiredService(); + prompt = await MJPostImagineService.TranslatePrompt(prompt, translateService); + + model = JSONHelper.SetJsonProperty(model, "prompt", prompt); + // 删除回调参数 notifyHook string body = JSONHelper.RemoveJsonProperties(model, "notifyHook"); // 发送请求 diff --git a/src/EndpointServices/MJTransferEndpoint/MJPostUploadDiscordImages.cs b/src/EndpointServices/MJTransferEndpoint/MJPostUploadDiscordImages.cs index 25af673..cac54d6 100644 --- a/src/EndpointServices/MJTransferEndpoint/MJPostUploadDiscordImages.cs +++ b/src/EndpointServices/MJTransferEndpoint/MJPostUploadDiscordImages.cs @@ -9,7 +9,6 @@ namespace lai_transfer.EndpointServices.MJTransferEndpoint { public class MJPostUploadDiscordImages : IEndpoint { - private static readonly ILogger _logger = LogHelper.GetLogger(); public static void Map(IEndpointRouteBuilder app) => app .MapPost("/submit/upload-discord-images", Handle) diff --git a/src/Examples/ServiceUsageExamples.cs b/src/Examples/ServiceUsageExamples.cs new file mode 100644 index 0000000..23cdd7b --- /dev/null +++ b/src/Examples/ServiceUsageExamples.cs @@ -0,0 +1,88 @@ +using lai_transfer.Common.Extensions; +using lai_transfer.Common.Helper; +using lai_transfer.Services; +using System.Threading.Tasks; + +namespace lai_transfer.Examples +{ + /// + /// չʾʹ÷ȡչʾ + /// + public static class ServiceUsageExamples + { + /// + /// HttpContextijʹ÷ + /// + /// Http + public static async Task ExampleWithHttpContext(HttpContext httpContext) + { + // ʽ1ʹHttpContextչȡ + var translateService = httpContext.GetRequiredService(); + var loggerFactory = httpContext.GetService(); + var logger = loggerFactory?.CreateLogger("ServiceUsageExamples"); + + // ʹ÷ + string result = await translateService.TranslateToEnglish(""); + logger?.LogInformation($": {result}"); + } + + /// + /// ûHttpContextijʹ÷羲̨̬ȣ + /// + public static async Task ExampleWithoutHttpContext() + { + // ʽ2ʹȫַλȡ + var translateService = ServiceLocator.GetRequiredService(); + var loggerFactory = ServiceLocator.GetService(); + var logger = loggerFactory?.CreateLogger("ServiceUsageExamples"); + + // ʹ÷ + string result = await translateService.TranslateToEnglish(""); + logger?.LogInformation($": {result}"); + } + + /// + /// Ҫijʹ + /// + public static async Task ExampleWithScope() + { + // µ + using var scope = ServiceLocator.CreateScope(); + var scopedServices = scope.ServiceProvider; + + // лȡ + var translateService = scopedServices.GetRequiredService(); + + // ʹ÷ + string result = await translateService.TranslateToEnglish(""); + + // ʱԶͷԴ + } + + /// + /// ڶ˵㴦еʹʾ + /// + public static async Task EndpointExample(HttpContext httpContext) + { + try + { + // ֱӴHttpContextȡڷ + var translateService = httpContext.GetRequiredService(); + var loggerFactory = httpContext.GetRequiredService(); + 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}"); + } + } + } +} \ No newline at end of file diff --git a/src/Program.cs b/src/Program.cs index 3f1281a..c2aebc9 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -16,6 +16,9 @@ app.Configure(); LogHelper.Initialize(app.Services.GetRequiredService()); ConfigHelper.Initialize(); +// ʼλ +ServiceLocator.Initialize(app.Services); + // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { diff --git a/src/Services/BaiduTranslateService.cs b/src/Services/BaiduTranslateService.cs new file mode 100644 index 0000000..f1d0e8b --- /dev/null +++ b/src/Services/BaiduTranslateService.cs @@ -0,0 +1,104 @@ + + +using lai_transfer.Common.Helper; +using System.Security.Cryptography; +using System.Text; +using System.Text.Json; +using System.Text.RegularExpressions; + +namespace lai_transfer.Services +{ + /// + /// 百度翻译服务 + /// + public class BaiduTranslateService : ITranslateService + { + private const string TRANSLATE_API = "https://fanyi-api.baidu.com/api/trans/vip/translate"; + private static readonly ILogger _logger = LogHelper.GetLogger(); + public BaiduTranslateService() + { + } + + public async Task TranslateToEnglish(string prompt) + { + var appid = ConfigHelper.Translate.BaiduAppId; + var appSecret = ConfigHelper.Translate.BaiduAppSecret; + + if (string.IsNullOrWhiteSpace(appid) || string.IsNullOrWhiteSpace(appSecret)) + { + return prompt; + } + + if (!ContainsChinese(prompt)) + { + return prompt; + } + + string salt = new Random().Next(10000, 99999).ToString(); + string sign = ComputeMd5Hash(appid + prompt + salt + appSecret); + + var body = new Dictionary + { + { "from", "zh" }, + { "to", "en" }, + { "appid", appid }, + { "salt", salt }, + { "q", prompt }, + { "sign", sign } + }; + + try + { + using (var client = new HttpClient()) + { + var content = new FormUrlEncodedContent(body); + var response = await client.PostAsync(TRANSLATE_API, content); + + if (!response.IsSuccessStatusCode || string.IsNullOrWhiteSpace(response.Content.ReadAsStringAsync().Result)) + { + throw new InvalidOperationException($"{response.StatusCode} - {response.Content.ReadAsStringAsync().Result}"); + } + + var result = JsonDocument.Parse(response.Content.ReadAsStringAsync().Result); + if (result.RootElement.TryGetProperty("error_code", out var errorCode)) + { + throw new InvalidOperationException($"{errorCode.GetString()} - {result.RootElement.GetProperty("error_msg").GetString()}"); + } + + var transResult = result.RootElement.GetProperty("trans_result").EnumerateArray(); + var translatedStrings = new List(); + foreach (var item in transResult) + { + translatedStrings.Add(item.GetProperty("dst").GetString()); + } + + return string.Join("\n", translatedStrings); + } + } + catch (Exception e) + { + _logger.LogWarning(e, "Failed to call Baidu Translate"); + } + + return prompt; + } + + private static string ComputeMd5Hash(string input) + { + using (var md5 = MD5.Create()) + { + var inputBytes = Encoding.UTF8.GetBytes(input); + var hashBytes = md5.ComputeHash(inputBytes); + return BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant(); + } + } + + public bool ContainsChinese(string prompt) + { + // 匹配基本汉字区、扩展A区和部分扩展B区 + string chinesePattern = @"[\u4e00-\u9fa5\u3400-\u4DBF]"; + + return Regex.IsMatch(prompt, chinesePattern); + } + } +} \ No newline at end of file diff --git a/src/Services/GPTTranslateService.cs b/src/Services/GPTTranslateService.cs new file mode 100644 index 0000000..a5178fe --- /dev/null +++ b/src/Services/GPTTranslateService.cs @@ -0,0 +1,105 @@ +using lai_transfer.Common.Helper; +using lai_transfer.Services; +using System.Text; +using System.Text.Json; +using System.Text.RegularExpressions; + +namespace Midjourney.Infrastructure.Services +{ + /// + /// OpenAI GPT翻译服务 + /// + public class GPTTranslateService : ITranslateService + { + private const string TRANSLATE_API = "https://api.openai.com/v1/chat/completions"; + + private static readonly ILogger _logger = LogHelper.GetLogger(); + + private readonly string _apiUrl; + private readonly string _apiKey; + private readonly TimeSpan _timeout; + private readonly string _model; + private readonly int _maxTokens; + private readonly double _temperature; + private readonly HttpClient _httpClient; + + public GPTTranslateService() + { + + _apiUrl = ConfigHelper.Translate.OpenaiGptApiUrl; + _apiKey = ConfigHelper.Translate.OpenaiGptApiKey; + _timeout = ConfigHelper.Translate.TimeOut; + _model = ConfigHelper.Translate.OpenaiGptModel; + _maxTokens = ConfigHelper.Translate.OpenaiGptMaxTokens; + _temperature = ConfigHelper.Translate.OpenaiGptTemperature; + + _httpClient = new HttpClient() { Timeout = _timeout }; + _httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {_apiKey}"); + } + + public async Task TranslateToEnglish(string prompt) + { + if (string.IsNullOrWhiteSpace(_apiKey) || string.IsNullOrWhiteSpace(_apiUrl)) + { + return prompt; + } + + if (!ContainsChinese(prompt)) + { + return prompt; + } + + var requestBody = new + { + model = _model, + stream = false, + messages = new[] + { + new { role = "system", content = "把中文翻译成英文" }, + new { role = "user", content = prompt } + }, + max_tokens = _maxTokens, + temperature = _temperature + }; + + try + { + var content = new StringContent(JsonSerializer.Serialize(requestBody), Encoding.UTF8, "application/json"); + var response = await _httpClient.PostAsync(_apiUrl, content); + + if (!response.IsSuccessStatusCode || string.IsNullOrWhiteSpace(response.Content.ReadAsStringAsync().Result)) + { + throw new InvalidOperationException($"{response.StatusCode} - {response.Content.ReadAsStringAsync().Result}"); + } + + var result = JsonDocument.Parse(response.Content.ReadAsStringAsync().Result); + var choices = result.RootElement.GetProperty("choices").EnumerateArray(); + var translatedText = choices.First().GetProperty("message").GetProperty("content").GetString(); + + return translatedText?.Trim() ?? prompt; + } + catch (HttpRequestException e) + { + _logger.LogWarning(e, "HTTP request failed"); + } + catch (JsonException e) + { + _logger.LogWarning(e, "Failed to parse JSON response"); + } + catch (Exception e) + { + _logger.LogWarning(e, "Failed to call OpenAI Translate"); + } + + return prompt; + } + + public bool ContainsChinese(string prompt) + { + // 匹配基本汉字区、扩展A区和部分扩展B区 + string chinesePattern = @"[\u4e00-\u9fa5\u3400-\u4DBF]"; + + return Regex.IsMatch(prompt, chinesePattern); + } + } +} \ No newline at end of file diff --git a/src/Services/ITranslateService.cs b/src/Services/ITranslateService.cs new file mode 100644 index 0000000..cbcbcf0 --- /dev/null +++ b/src/Services/ITranslateService.cs @@ -0,0 +1,9 @@ +namespace lai_transfer.Services +{ + public interface ITranslateService + { + Task TranslateToEnglish(string prompt); + + bool ContainsChinese(string prompt); + } +} diff --git a/src/Tool/Extensions/JsonConfigReader.cs b/src/Tool/Extensions/JsonConfigReader.cs index 0861714..f3400e1 100644 --- a/src/Tool/Extensions/JsonConfigReader.cs +++ b/src/Tool/Extensions/JsonConfigReader.cs @@ -95,6 +95,13 @@ namespace lai_transfer.Tool.Extensions return section?.GetValue(); } + // 添加一个 getdouble 方法 + public double? GetDouble(string path, bool forceReload = false) + { + var section = GetSection(path, forceReload); + return section?.GetValue(); + } + /// /// 获取指定路径的布尔值 ///