添加项目文件。
This commit is contained in:
parent
2515f555f8
commit
76efbd96f9
13
src/.config/dotnet-tools.json
Normal file
13
src/.config/dotnet-tools.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"version": 1,
|
||||
"isRoot": true,
|
||||
"tools": {
|
||||
"dotnet-ef": {
|
||||
"version": "9.0.6",
|
||||
"commands": [
|
||||
"dotnet-ef"
|
||||
],
|
||||
"rollForward": false
|
||||
}
|
||||
}
|
||||
}
|
||||
30
src/.dockerignore
Normal file
30
src/.dockerignore
Normal file
@ -0,0 +1,30 @@
|
||||
**/.classpath
|
||||
**/.dockerignore
|
||||
**/.env
|
||||
**/.git
|
||||
**/.gitignore
|
||||
**/.project
|
||||
**/.settings
|
||||
**/.toolstarget
|
||||
**/.vs
|
||||
**/.vscode
|
||||
**/*.*proj.user
|
||||
**/*.dbmdl
|
||||
**/*.jfm
|
||||
**/azds.yaml
|
||||
**/bin
|
||||
**/charts
|
||||
**/docker-compose*
|
||||
**/Dockerfile*
|
||||
**/node_modules
|
||||
**/npm-debug.log
|
||||
**/obj
|
||||
**/secrets.dev.yaml
|
||||
**/values.dev.yaml
|
||||
LICENSE
|
||||
README.md
|
||||
!**/.gitignore
|
||||
!.git/HEAD
|
||||
!.git/config
|
||||
!.git/packed-refs
|
||||
!.git/refs/heads/**
|
||||
40
src/Common/Enums/ResponseCode.cs
Normal file
40
src/Common/Enums/ResponseCode.cs
Normal file
@ -0,0 +1,40 @@
|
||||
using lai_transfer.Tool.Attributes;
|
||||
|
||||
namespace lai_transfer.Common.Enums;
|
||||
|
||||
public enum ResponseCode
|
||||
{
|
||||
|
||||
#region 成功
|
||||
|
||||
[Result("请求成功")]
|
||||
[Description("请求成功")]
|
||||
Success = 1,
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region 系统报错
|
||||
|
||||
[Result("系统错误")]
|
||||
[Description("系统错误")]
|
||||
SystemError = 5000,
|
||||
|
||||
[Result("参数错误")]
|
||||
[Description("参数错误")]
|
||||
ParameterError = 5001,
|
||||
|
||||
[Result("无效的操作")]
|
||||
[Description("无效的操作")]
|
||||
InvalidOptions = 5002,
|
||||
|
||||
[Result("数据不存在")]
|
||||
[Description("数据不存在")]
|
||||
IdDateNotFound = 5003,
|
||||
|
||||
[Result("操作错误")]
|
||||
[Description("操作错误")]
|
||||
OptionsError = 5004,
|
||||
#endregion
|
||||
|
||||
}
|
||||
16
src/Common/Extensions/ClaimsPrincipalExtensions.cs
Normal file
16
src/Common/Extensions/ClaimsPrincipalExtensions.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace lai_transfer.Common.Extensions;
|
||||
|
||||
public static class ClaimsPrincipalExtensions
|
||||
{
|
||||
public static long GetUserId(this ClaimsPrincipal claimsPrincipal)
|
||||
{
|
||||
if (!int.TryParse(claimsPrincipal.FindFirstValue(ClaimTypes.NameIdentifier), out var id))
|
||||
{
|
||||
throw new InvalidOperationException("Invalid UserId");
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
}
|
||||
62
src/Common/Extensions/EndpointExtensions.cs
Normal file
62
src/Common/Extensions/EndpointExtensions.cs
Normal file
@ -0,0 +1,62 @@
|
||||
using lai_transfer.Endpoints;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.OpenApi.Models;
|
||||
|
||||
namespace lai_transfer.Common.Extensions
|
||||
{
|
||||
public static class EndpointExtensions
|
||||
{
|
||||
private static readonly OpenApiSecurityScheme SecurityScheme = new()
|
||||
{
|
||||
Type = SecuritySchemeType.Http,
|
||||
Name = JwtBearerDefaults.AuthenticationScheme,
|
||||
Scheme = JwtBearerDefaults.AuthenticationScheme,
|
||||
Reference = new()
|
||||
{
|
||||
Type = ReferenceType.SecurityScheme,
|
||||
Id = JwtBearerDefaults.AuthenticationScheme
|
||||
}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 无需授权的分组
|
||||
/// </summary>
|
||||
/// <param name="app"></param>
|
||||
/// <param name="prefix"></param>
|
||||
/// <returns></returns>
|
||||
public static RouteGroupBuilder MapPublicGroup(this IEndpointRouteBuilder app, string? prefix = null)
|
||||
{
|
||||
return app.MapGroup(prefix ?? string.Empty)
|
||||
.AllowAnonymous();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 需要权限校验的分组
|
||||
/// </summary>
|
||||
/// <param name="app"></param>
|
||||
/// <param name="prefix"></param>
|
||||
/// <returns></returns>
|
||||
public static RouteGroupBuilder MapAuthorizedGroup(this IEndpointRouteBuilder app, string? prefix = null)
|
||||
{
|
||||
return app.MapGroup(prefix ?? string.Empty)
|
||||
.RequireAuthorization()
|
||||
.WithOpenApi(x => new(x)
|
||||
{
|
||||
Security = [new() { [SecurityScheme] = [] }],
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 绑定一个 IEndpoint 实现到路由中
|
||||
/// </summary>
|
||||
/// <typeparam name="TEndpoint"></typeparam>
|
||||
/// <param name="app"></param>
|
||||
/// <returns></returns>
|
||||
public static IEndpointRouteBuilder MapEndpoint<TEndpoint>(this IEndpointRouteBuilder app)
|
||||
where TEndpoint : IEndpoint
|
||||
{
|
||||
TEndpoint.Map(app);
|
||||
return app;
|
||||
}
|
||||
}
|
||||
}
|
||||
59
src/Common/Extensions/HttpContextExtensions.cs
Normal file
59
src/Common/Extensions/HttpContextExtensions.cs
Normal file
@ -0,0 +1,59 @@
|
||||
using lai_transfer.Common.Results;
|
||||
|
||||
namespace lai_transfer.Common.Extensions
|
||||
{
|
||||
|
||||
public static class HttpContextExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 从HttpContext中获取存储的值
|
||||
/// </summary>
|
||||
/// <param name="httpContext">Http上下文</param>
|
||||
/// <param name="key">要获取的键名(如"AuthToken"或"BaseUrl")</param>
|
||||
/// <returns>存储的值,如果不存在则返回null</returns>
|
||||
public static string? GetContextItem(this HttpContext httpContext, string key)
|
||||
{
|
||||
return httpContext.Items.TryGetValue(key, out var value) ? value?.ToString() : null;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 获取 转发接口中的 httpContext 中的 Authorization 相关的 Token 和 BaseUrl数据
|
||||
/// </summary>
|
||||
/// <param name="httpContext"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="InvalidOperationException"></exception>
|
||||
public static TransferAuthorizationResult GetAuthorizationItemsAndValidation(this HttpContext httpContext)
|
||||
{
|
||||
string? token = httpContext.GetContextItem("AuthToken");
|
||||
string? baseUrl = httpContext.GetContextItem("BaseUrl");
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前请求的完整路径(不包括主机和端口)
|
||||
/// </summary>
|
||||
/// <param name="httpContext">Http上下文</param>
|
||||
/// <param name="includeQueryString">是否包含查询字符串</param>
|
||||
/// <returns>完整的请求路径</returns>
|
||||
public static string GetFullRequestPath(this HttpContext httpContext, bool includeQueryString = true)
|
||||
{
|
||||
string pathBase = httpContext.Request.PathBase.Value ?? string.Empty;
|
||||
string path = httpContext.Request.Path.Value ?? string.Empty;
|
||||
string fullPath = $"{pathBase}{path}";
|
||||
|
||||
if (includeQueryString)
|
||||
{
|
||||
string queryString = httpContext.Request.QueryString.Value ?? string.Empty;
|
||||
fullPath += queryString;
|
||||
}
|
||||
|
||||
return fullPath;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
using lai_transfer.Common.Filters;
|
||||
using lai_transfer.Configuration;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
|
||||
namespace lai_transfer.Common.Extensions
|
||||
{
|
||||
|
||||
public static class RouteHandlerBuilderAuthorizationExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 添加授权头处理过滤器
|
||||
/// </summary>
|
||||
public static RouteHandlerBuilder WithMJAuthorizationHeader(this RouteHandlerBuilder builder)
|
||||
{
|
||||
return builder
|
||||
.AddEndpointFilter<SplitMJAuthorizationFilter>()
|
||||
.Produces(StatusCodes.Status401Unauthorized);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,65 @@
|
||||
using lai_transfer.Common.Filters;
|
||||
using lai_transfer.Common.Types;
|
||||
using lai_transfer.Configuration;
|
||||
using System;
|
||||
using System.Security.Principal;
|
||||
|
||||
namespace lai_transfer.Common.Extensions
|
||||
{
|
||||
public static class RouteHandlerBuilderValidationExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a request validation filter to the route handler.
|
||||
/// </summary>
|
||||
/// <typeparam name="TRequest"></typeparam>
|
||||
/// <param name="builder"></param>
|
||||
/// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to futher customize the endpoint.</returns>
|
||||
public static RouteHandlerBuilder WithRequestValidation<TRequest>(this RouteHandlerBuilder builder)
|
||||
{
|
||||
return builder
|
||||
.AddEndpointFilter<RequestValidationFilter<TRequest>>()
|
||||
.ProducesValidationProblem();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a request validation filter to the route handler to ensure a <typeparamref name="TEntity"/> exists with the id returned by <paramref name="idSelector"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity"></typeparam>
|
||||
/// <typeparam name="TRequest"></typeparam>
|
||||
/// <param name="builder"></param>
|
||||
/// <param name="idSelector">A function which selects the <c>Id</c> property from the <typeparamref name="TRequest"/></param>
|
||||
/// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to futher customize the endpoint.</returns>
|
||||
public static RouteHandlerBuilder WithEnsureEntityExists<TEntity, TRequest>(this RouteHandlerBuilder builder, Func<TRequest, int?> idSelector) where TEntity : class, IEntity
|
||||
{
|
||||
return builder
|
||||
.AddEndpointFilterFactory((endpointFilterFactoryContext, next) => async context =>
|
||||
{
|
||||
var db = context.HttpContext.RequestServices.GetRequiredService<ApplicationDbContext>();
|
||||
var filter = new EnsureEntityExistsFilter<TRequest, TEntity>(db, idSelector);
|
||||
return await filter.InvokeAsync(context, next);
|
||||
})
|
||||
.ProducesProblem(StatusCodes.Status404NotFound);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a request validation filter to the route handler to ensure the current <seealso cref="ClaimsPrincipal"/> owns the <typeparamref name="TEntity"/> with the id returned by <paramref name="idSelector"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity"></typeparam>
|
||||
/// <typeparam name="TRequest"></typeparam>
|
||||
/// <param name="builder"></param>
|
||||
/// <param name="idSelector">A function which selects the <c>Id</c> property from the <typeparamref name="TRequest"/></param>
|
||||
/// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to futher customize the endpoint.</returns>
|
||||
public static RouteHandlerBuilder WithEnsureUserOwnsEntity<TEntity, TRequest>(this RouteHandlerBuilder builder, Func<TRequest, int> idSelector) where TEntity : class, IEntity, IOwnedEntity
|
||||
{
|
||||
return builder
|
||||
.AddEndpointFilterFactory((endpointFilterFactoryContext, next) => async context =>
|
||||
{
|
||||
var db = context.HttpContext.RequestServices.GetRequiredService<ApplicationDbContext>();
|
||||
var filter = new EnsureUserOwnsEntityFilter<TRequest, TEntity>(db, idSelector);
|
||||
return await filter.InvokeAsync(context, next);
|
||||
})
|
||||
.ProducesProblem(StatusCodes.Status404NotFound)
|
||||
.Produces(StatusCodes.Status403Forbidden);
|
||||
}
|
||||
}
|
||||
}
|
||||
24
src/Common/Extensions/ValidationRulesExtension.cs
Normal file
24
src/Common/Extensions/ValidationRulesExtension.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using FluentValidation;
|
||||
|
||||
namespace lai_transfer.Common.Extensions;
|
||||
|
||||
public static class ValidationRulesExtension
|
||||
{
|
||||
public static IRuleBuilderOptions<T, string> AuthUsernameRule<T>(
|
||||
this IRuleBuilderInitial<T, string> rule)
|
||||
{
|
||||
return rule.NotEmpty().WithMessage("用户名不能为空");
|
||||
}
|
||||
|
||||
public static IRuleBuilderOptions<T, string> AuthPasswordRule<T>(
|
||||
this IRuleBuilderInitial<T, string> rule)
|
||||
{
|
||||
return rule.NotEmpty().MinimumLength(8);
|
||||
}
|
||||
|
||||
public static IRuleBuilderOptions<T, string> AuthNameRule<T>(
|
||||
this IRuleBuilderInitial<T, string> rule)
|
||||
{
|
||||
return rule.NotEmpty().MaximumLength(50);
|
||||
}
|
||||
}
|
||||
30
src/Common/Filters/EnsureEntityExistsFilter.cs
Normal file
30
src/Common/Filters/EnsureEntityExistsFilter.cs
Normal file
@ -0,0 +1,30 @@
|
||||
using lai_transfer.Common.Results;
|
||||
using lai_transfer.Common.Types;
|
||||
using lai_transfer.Configuration;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace lai_transfer.Common.Filters;
|
||||
|
||||
public class EnsureEntityExistsFilter<TRequest, TEntity>(ApplicationDbContext database, Func<TRequest, int?> idSelector) : IEndpointFilter
|
||||
where TEntity : class, IEntity
|
||||
{
|
||||
public async ValueTask<object?> InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next)
|
||||
{
|
||||
var request = context.Arguments.OfType<TRequest>().Single();
|
||||
var cancellationToken = context.HttpContext.RequestAborted;
|
||||
var id = idSelector(request);
|
||||
|
||||
if (!id.HasValue)
|
||||
{
|
||||
return await next(context);
|
||||
}
|
||||
|
||||
var exists = await database
|
||||
.Set<TEntity>()
|
||||
.AnyAsync(x => x.Id == id, cancellationToken);
|
||||
|
||||
return exists
|
||||
? await next(context)
|
||||
: new NotFoundProblem($"{typeof(TEntity).Name} with id {id} was not found.");
|
||||
}
|
||||
}
|
||||
41
src/Common/Filters/EnsureUserOwnsEntityFilter.cs
Normal file
41
src/Common/Filters/EnsureUserOwnsEntityFilter.cs
Normal file
@ -0,0 +1,41 @@
|
||||
using lai_transfer.Common.Extensions;
|
||||
using lai_transfer.Common.Results;
|
||||
using lai_transfer.Common.Types;
|
||||
using lai_transfer.Configuration;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace lai_transfer.Common.Filters;
|
||||
|
||||
/// <summary>
|
||||
/// 判断 对应 userId 的 Entity 数据是不是存在
|
||||
/// </summary>
|
||||
/// <typeparam name="TRequest"></typeparam>
|
||||
/// <typeparam name="TEntity"></typeparam>
|
||||
/// <param name="database"></param>
|
||||
/// <param name="idSelector"></param>
|
||||
public class EnsureUserOwnsEntityFilter<TRequest, TEntity>(ApplicationDbContext database, Func<TRequest, int> idSelector) : IEndpointFilter
|
||||
where TEntity : class, IEntity, IOwnedEntity
|
||||
{
|
||||
public async ValueTask<object?> InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next)
|
||||
{
|
||||
var request = context.Arguments.OfType<TRequest>().Single();
|
||||
var cancellationToken = context.HttpContext.RequestAborted;
|
||||
var userId = context.HttpContext.User.GetUserId();
|
||||
var id = idSelector(request);
|
||||
|
||||
var entity = await database
|
||||
.Set<TEntity>()
|
||||
.Where(x => x.Id == id)
|
||||
.Select(x => new Entity(x.Id, x.UserId))
|
||||
.SingleOrDefaultAsync(cancellationToken);
|
||||
|
||||
return entity switch
|
||||
{
|
||||
null => new NotFoundProblem($"{typeof(TEntity).Name} with id {id} was not found."),
|
||||
_ when entity.UserId != userId => TypedResults.Forbid(),
|
||||
_ => await next(context)
|
||||
};
|
||||
}
|
||||
|
||||
private record Entity(int Id, int UserId);
|
||||
}
|
||||
11
src/Common/Filters/RequestLoggingFilter.cs
Normal file
11
src/Common/Filters/RequestLoggingFilter.cs
Normal file
@ -0,0 +1,11 @@
|
||||
namespace lai_transfer.Common.Filters
|
||||
{
|
||||
public class RequestLoggingFilter(ILogger<RequestLoggingFilter> logger) : IEndpointFilter
|
||||
{
|
||||
public async ValueTask<object?> InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next)
|
||||
{
|
||||
logger.LogInformation("HTTP {Method} {Path} received", context.HttpContext.Request.Method, context.HttpContext.Request.Path);
|
||||
return await next(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
29
src/Common/Filters/RequestValidationFilter.cs
Normal file
29
src/Common/Filters/RequestValidationFilter.cs
Normal file
@ -0,0 +1,29 @@
|
||||
using FluentValidation;
|
||||
|
||||
namespace lai_transfer.Common.Filters;
|
||||
|
||||
public class RequestValidationFilter<TRequest>(ILogger<RequestValidationFilter<TRequest>> logger, IValidator<TRequest>? validator = null) : IEndpointFilter
|
||||
{
|
||||
public async ValueTask<object?> InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next)
|
||||
{
|
||||
var requestName = typeof(TRequest).FullName;
|
||||
|
||||
if (validator is null)
|
||||
{
|
||||
logger.LogInformation("{Request}: No validator configured.", requestName);
|
||||
return await next(context);
|
||||
}
|
||||
|
||||
logger.LogInformation("{Request}: Validating...", requestName);
|
||||
var request = context.Arguments.OfType<TRequest>().First();
|
||||
var validationResult = await validator.ValidateAsync(request, context.HttpContext.RequestAborted);
|
||||
if (!validationResult.IsValid)
|
||||
{
|
||||
logger.LogWarning("{Request}: Validation failed.", requestName);
|
||||
return TypedResults.ValidationProblem(validationResult.ToDictionary());
|
||||
}
|
||||
|
||||
logger.LogInformation("{Request}: Validation succeeded.", requestName);
|
||||
return await next(context);
|
||||
}
|
||||
}
|
||||
69
src/Common/Filters/SplitMJAuthorizationFilter.cs
Normal file
69
src/Common/Filters/SplitMJAuthorizationFilter.cs
Normal file
@ -0,0 +1,69 @@
|
||||
namespace lai_transfer.Common.Filters
|
||||
{
|
||||
public class SplitMJAuthorizationFilter(ILogger<SplitMJAuthorizationFilter> logger) : IEndpointFilter
|
||||
{
|
||||
private readonly ILogger<SplitMJAuthorizationFilter> _logger = logger;
|
||||
public async ValueTask<object?> InvokeAsync(
|
||||
EndpointFilterInvocationContext context,
|
||||
EndpointFilterDelegate next)
|
||||
{
|
||||
// 1. 从请求头获取 Authorization
|
||||
var httpContext = context.HttpContext;
|
||||
|
||||
string authorization = string.Empty;
|
||||
if (httpContext.Request.Headers.TryGetValue("mj-api-secret", out var mjSecret))
|
||||
{
|
||||
authorization = mjSecret.ToString();
|
||||
}
|
||||
else if (httpContext.Request.Headers.TryGetValue("Authorization", out var authHeader))
|
||||
{
|
||||
authorization = authHeader.ToString();
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(authorization))
|
||||
{
|
||||
_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="))
|
||||
{
|
||||
// 使用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为空");
|
||||
return TypedResults.Unauthorized();
|
||||
}
|
||||
httpContext.Items["AuthToken"] = token;
|
||||
// 将baseUrl也存入HttpContext以便后续使用
|
||||
httpContext.Items["BaseUrl"] = baseUrl;
|
||||
return await next(context);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("令牌解析错误");
|
||||
return TypedResults.Unauthorized();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("令牌解析错误,没有包含 url");
|
||||
return TypedResults.Unauthorized();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
39
src/Common/Helper/ConfigHelper.cs
Normal file
39
src/Common/Helper/ConfigHelper.cs
Normal file
@ -0,0 +1,39 @@
|
||||
using lai_transfer.Tool.Extensions;
|
||||
|
||||
namespace lai_transfer.Common.Helper
|
||||
{
|
||||
public class ConfigHelper
|
||||
{
|
||||
private static readonly ILogger _logger = LogHelper.GetLogger<ConfigHelper>();
|
||||
|
||||
// 存储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()
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("正在加载应用程序配置...");
|
||||
|
||||
// 读取配置文件
|
||||
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;
|
||||
|
||||
_logger.LogInformation("配置加载完成");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "加载配置文件失败");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
38
src/Common/Helper/JSONHelper.cs
Normal file
38
src/Common/Helper/JSONHelper.cs
Normal file
@ -0,0 +1,38 @@
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace lai_transfer.Common.Helper
|
||||
{
|
||||
public static class JSONHelper
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 高效移除JSON属性的方法,避免完全序列化和反序列化
|
||||
/// </summary>
|
||||
public static string RemoveJsonProperties(JsonElement element, params string[] propertiesToRemove)
|
||||
{
|
||||
// 检查元素是否为对象
|
||||
if (element.ValueKind != JsonValueKind.Object)
|
||||
return element.GetRawText();
|
||||
|
||||
using var stream = new MemoryStream();
|
||||
using var writer = new Utf8JsonWriter(stream);
|
||||
|
||||
writer.WriteStartObject();
|
||||
|
||||
// 遍历所有属性,跳过需要移除的
|
||||
foreach (var property in element.EnumerateObject())
|
||||
{
|
||||
if (!propertiesToRemove.Contains(property.Name))
|
||||
{
|
||||
property.WriteTo(writer);
|
||||
}
|
||||
}
|
||||
|
||||
writer.WriteEndObject();
|
||||
writer.Flush();
|
||||
|
||||
return Encoding.UTF8.GetString(stream.ToArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
46
src/Common/Helper/LogHelper.cs
Normal file
46
src/Common/Helper/LogHelper.cs
Normal file
@ -0,0 +1,46 @@
|
||||
namespace lai_transfer.Common.Helper
|
||||
{
|
||||
/// <summary>
|
||||
/// 提供静态日志访问的帮助类
|
||||
/// </summary>
|
||||
public static class LogHelper
|
||||
{
|
||||
private static ILoggerFactory? _factory;
|
||||
|
||||
// 在应用启动时初始化
|
||||
public static void Initialize(ILoggerFactory factory)
|
||||
{
|
||||
_factory = factory;
|
||||
}
|
||||
|
||||
// 获取指定类型的日志记录器
|
||||
public static ILogger<T> GetLogger<T>()
|
||||
{
|
||||
if (_factory == null)
|
||||
throw new InvalidOperationException("LogHelper未初始化。请在应用启动时调用Initialize方法。");
|
||||
|
||||
return _factory.CreateLogger<T>();
|
||||
}
|
||||
|
||||
// 获取指定类别名称的日志记录器
|
||||
public static ILogger GetLogger(string categoryName)
|
||||
{
|
||||
if (_factory == null)
|
||||
throw new InvalidOperationException("LogHelper未初始化。请在应用启动时调用Initialize方法。");
|
||||
|
||||
return _factory.CreateLogger(categoryName);
|
||||
}
|
||||
|
||||
// 记录错误日志的快捷方法
|
||||
public static void LogError(Exception ex, string message, params object[] args)
|
||||
{
|
||||
GetLogger("GlobalErrorLogger").LogError(ex, message, args);
|
||||
}
|
||||
|
||||
// 记录信息日志的快捷方法
|
||||
public static void LogInfo(string message, params object[] args)
|
||||
{
|
||||
GetLogger("GlobalInfoLogger").LogInformation(message, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
164
src/Common/Results/APIResponseModel.cs
Normal file
164
src/Common/Results/APIResponseModel.cs
Normal file
@ -0,0 +1,164 @@
|
||||
using lai_transfer.Common.Enums;
|
||||
using lai_transfer.Tool.Extensions;
|
||||
|
||||
namespace lai_transfer.Common.Results
|
||||
{
|
||||
public class APIResponseModel<T>
|
||||
{
|
||||
public APIResponseModel()
|
||||
{
|
||||
Code = (int)ResponseCode.Success;
|
||||
Message = string.Empty;
|
||||
Data = default(T);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 返回的码 0,1
|
||||
/// </summary>
|
||||
public int Code { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 返回的信息,成功或者失败的信息
|
||||
/// </summary>
|
||||
public string? Message { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 返回的数据,可以是任何类型
|
||||
/// </summary>
|
||||
public object? Data { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 创建返回消息
|
||||
/// </summary>
|
||||
/// <param name="code">返回码</param>
|
||||
/// <param name="data">返回数据</param>
|
||||
/// <param name="message">返回消息</param>
|
||||
/// <returns></returns>
|
||||
public static APIResponseModel<T> CreateResponseModel(ResponseCode code, T data)
|
||||
{
|
||||
return new APIResponseModel<T>
|
||||
{
|
||||
Code = (int)code,
|
||||
Message = code.GetResult(),
|
||||
Data = data
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建正常的返回数据
|
||||
/// </summary>
|
||||
/// <param name="data">返回的数据</param>
|
||||
/// <param name="message">返回成功的消息</param>
|
||||
/// <returns></returns>
|
||||
public static APIResponseModel<T> CreateSuccessResponseModel(T data, string? message = null)
|
||||
{
|
||||
return new APIResponseModel<T>
|
||||
{
|
||||
Code = (int)ResponseCode.Success,
|
||||
Message = message ?? "Requset SuccessFul",
|
||||
Data = data
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个返回成功的消息
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <param name="message"></param>
|
||||
/// <returns></returns>
|
||||
public static APIResponseModel<T> CreateSuccessResponseModel(string message)
|
||||
{
|
||||
// 判断 T 的类型是不是 MessageResult,是的话,构建Data
|
||||
if (typeof(T) == typeof(IOperationResult))
|
||||
{
|
||||
return new APIResponseModel<T>
|
||||
{
|
||||
Code = (int)ResponseCode.Success,
|
||||
Message = message,
|
||||
Data = new MessageResult(message)
|
||||
};
|
||||
}
|
||||
return new APIResponseModel<T>
|
||||
{
|
||||
Code = (int)ResponseCode.Success,
|
||||
Message = message,
|
||||
Data = null
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建正常的返回数据
|
||||
/// </summary>
|
||||
/// <param name="data">返回的数据</param>
|
||||
/// <returns></returns>
|
||||
public static APIResponseModel<T> CreateSuccessResponseModel(ResponseCode code)
|
||||
{
|
||||
return new APIResponseModel<T>
|
||||
{
|
||||
Code = (int)code,
|
||||
Message = code.GetResult(),
|
||||
Data = null
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回错误消息
|
||||
/// </summary>
|
||||
/// <param name="code">错误的码</param>
|
||||
/// <param name="data">返回的数据</param>
|
||||
/// <returns></returns>
|
||||
public static APIResponseModel<T> CreateErrorResponseModel(ResponseCode code, T data)
|
||||
{
|
||||
return new APIResponseModel<T>
|
||||
{
|
||||
Code = (int)code,
|
||||
Message = code.GetResult(),
|
||||
Data = data
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回错误消息
|
||||
/// </summary>
|
||||
/// <param name="code">错误的码</param>
|
||||
/// <param name="data">返回的数据</param>
|
||||
/// <param name="message">返回的错误消息</param>
|
||||
/// <returns></returns>
|
||||
public static APIResponseModel<T> CreateErrorResponseModel(ResponseCode code, T data, string message)
|
||||
{
|
||||
return new APIResponseModel<T>
|
||||
{
|
||||
Code = (int)code,
|
||||
Message = message,
|
||||
Data = data
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个错误的返回数据,只有错误消息
|
||||
/// </summary>
|
||||
/// <param name="code">错误的码</param>
|
||||
/// <param name="message">错误消息提示</param>
|
||||
/// <returns></returns>
|
||||
public static APIResponseModel<T> CreateErrorResponseModel(ResponseCode code, string message)
|
||||
{
|
||||
// 判断 T 的类型是不是 MessageResult,是的话,构建Data
|
||||
if (typeof(T) == typeof(IOperationResult))
|
||||
{
|
||||
return new APIResponseModel<T>
|
||||
{
|
||||
Code = (int)code,
|
||||
Message = message,
|
||||
Data = new ErrorResult(message)
|
||||
};
|
||||
}
|
||||
return new APIResponseModel<T>
|
||||
{
|
||||
Code = (int)code,
|
||||
Message = message,
|
||||
Data = null
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
4
src/Common/Results/ErrorResult.cs
Normal file
4
src/Common/Results/ErrorResult.cs
Normal file
@ -0,0 +1,4 @@
|
||||
namespace lai_transfer.Common.Results
|
||||
{
|
||||
public record ErrorResult(string Error) : IOperationResult;
|
||||
}
|
||||
6
src/Common/Results/IOperationResult.cs
Normal file
6
src/Common/Results/IOperationResult.cs
Normal file
@ -0,0 +1,6 @@
|
||||
namespace lai_transfer.Common.Results
|
||||
{
|
||||
public interface IOperationResult
|
||||
{
|
||||
}
|
||||
}
|
||||
4
src/Common/Results/MessageResult.cs
Normal file
4
src/Common/Results/MessageResult.cs
Normal file
@ -0,0 +1,4 @@
|
||||
namespace lai_transfer.Common.Results
|
||||
{
|
||||
public record MessageResult(string Message) : IOperationResult;
|
||||
}
|
||||
36
src/Common/Results/NotFoundProblem.cs
Normal file
36
src/Common/Results/NotFoundProblem.cs
Normal file
@ -0,0 +1,36 @@
|
||||
using Microsoft.AspNetCore.Http.HttpResults;
|
||||
using Microsoft.AspNetCore.Http.Metadata;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Reflection;
|
||||
|
||||
namespace lai_transfer.Common.Results;
|
||||
|
||||
public sealed class NotFoundProblem : IResult, IEndpointMetadataProvider, IStatusCodeHttpResult, IContentTypeHttpResult, IValueHttpResult, IValueHttpResult<ProblemDetails>
|
||||
{
|
||||
private readonly ProblemHttpResult problem;
|
||||
|
||||
public NotFoundProblem(string errorMessage)
|
||||
{
|
||||
problem = TypedResults.Problem
|
||||
(
|
||||
statusCode: StatusCode,
|
||||
title: "Not Found",
|
||||
detail: errorMessage
|
||||
);
|
||||
}
|
||||
|
||||
public int? StatusCode => StatusCodes.Status404NotFound;
|
||||
public string? ContentType => problem.ContentType;
|
||||
public object? Value => problem.ProblemDetails;
|
||||
ProblemDetails? IValueHttpResult<ProblemDetails>.Value => problem.ProblemDetails;
|
||||
|
||||
public static void PopulateMetadata(MethodInfo method, EndpointBuilder builder)
|
||||
{
|
||||
builder.Metadata.Add(new ProducesResponseTypeMetadata(StatusCodes.Status404NotFound, typeof(ProblemDetails), ["application/problem+json"]));
|
||||
}
|
||||
|
||||
public async Task ExecuteAsync(HttpContext httpContext)
|
||||
{
|
||||
await problem.ExecuteAsync(httpContext);
|
||||
}
|
||||
}
|
||||
4
src/Common/Results/TransferAuthorizationResult.cs
Normal file
4
src/Common/Results/TransferAuthorizationResult.cs
Normal file
@ -0,0 +1,4 @@
|
||||
namespace lai_transfer.Common.Results
|
||||
{
|
||||
public record TransferAuthorizationResult(string Token, string BaseUrl);
|
||||
}
|
||||
10
src/Common/Results/TransferResult.cs
Normal file
10
src/Common/Results/TransferResult.cs
Normal file
@ -0,0 +1,10 @@
|
||||
namespace lai_transfer.Common.Results
|
||||
{
|
||||
/// <summary>
|
||||
/// 转发的请求返回
|
||||
/// </summary>
|
||||
/// <param name="Content"></param>
|
||||
/// <param name="ContentType"></param>
|
||||
/// <param name="StatusCode"></param>
|
||||
public record TransferResult(string Content, string ContentType, int StatusCode);
|
||||
}
|
||||
12
src/Common/Types/IEntity.cs
Normal file
12
src/Common/Types/IEntity.cs
Normal file
@ -0,0 +1,12 @@
|
||||
namespace lai_transfer.Common.Types;
|
||||
|
||||
public interface IEntity
|
||||
{
|
||||
int Id { get; }
|
||||
Guid ReferenceId { get; }
|
||||
}
|
||||
|
||||
public interface IOwnedEntity
|
||||
{
|
||||
int UserId { get; }
|
||||
}
|
||||
17
src/Configuration/ApplicationDbContext.cs
Normal file
17
src/Configuration/ApplicationDbContext.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using lai_transfer.Model.Entity;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace lai_transfer.Configuration
|
||||
{
|
||||
public class ApplicationDbContext : IdentityDbContext<User, Role, long>
|
||||
{
|
||||
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { }
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder builder)
|
||||
{
|
||||
base.OnModelCreating(builder);
|
||||
}
|
||||
}
|
||||
}
|
||||
26
src/Configuration/MyDbcontextDesignFactory.cs
Normal file
26
src/Configuration/MyDbcontextDesignFactory.cs
Normal file
@ -0,0 +1,26 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Design;
|
||||
|
||||
namespace lai_transfer.Configuration
|
||||
{
|
||||
public class MyDbcontextDesignFactory : IDesignTimeDbContextFactory<ApplicationDbContext>
|
||||
{
|
||||
//public MyDbcontextDesignFactory CreateDbContext(string[] args)
|
||||
//{
|
||||
// DbContextOptionsBuilder<ApplicationDbContext> optionsBuilder = new DbContextOptionsBuilder<ApplicationDbContext>();
|
||||
// string str = Environment.GetEnvironmentVariable("CONNECTION_STRING");
|
||||
// optionsBuilder.UseMySql(str, ServerVersion.Parse("8.0.18-mysql"));
|
||||
// ApplicationDbContext db = new ApplicationDbContext(optionsBuilder.Options);
|
||||
// return db;
|
||||
//}
|
||||
|
||||
public ApplicationDbContext CreateDbContext(string[] args)
|
||||
{
|
||||
DbContextOptionsBuilder<ApplicationDbContext> optionsBuilder = new DbContextOptionsBuilder<ApplicationDbContext>();
|
||||
string str = "server=123.129.219.240;port=14080;user=luo;password=Luoqiang1405;database=LMS_TEST_1";
|
||||
optionsBuilder.UseMySql(str, ServerVersion.Parse("8.0.18-mysql"));
|
||||
ApplicationDbContext db = new ApplicationDbContext(optionsBuilder.Options);
|
||||
return db;
|
||||
}
|
||||
}
|
||||
}
|
||||
31
src/Configuration/SerilogConfig.cs
Normal file
31
src/Configuration/SerilogConfig.cs
Normal file
@ -0,0 +1,31 @@
|
||||
using Serilog;
|
||||
|
||||
namespace lai_transfer.Configuration
|
||||
{
|
||||
public static class SerilogConfig
|
||||
{
|
||||
public static void AddLoggerService(this IServiceCollection services)
|
||||
{
|
||||
// 确保logs目录存在
|
||||
Directory.CreateDirectory("logs");
|
||||
|
||||
// 加载独立的serilog.json配置
|
||||
var configuration = new ConfigurationBuilder()
|
||||
.SetBasePath(Directory.GetCurrentDirectory())
|
||||
.AddJsonFile("Configuration/config/serilog.json", optional: false, reloadOnChange: true)
|
||||
.Build();
|
||||
|
||||
// 配置Serilog
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.ReadFrom.Configuration(configuration)
|
||||
.CreateLogger();
|
||||
|
||||
// 添加Serilog到.NET Core的日志系统
|
||||
services.AddLogging(builder =>
|
||||
{
|
||||
builder.ClearProviders();
|
||||
builder.AddSerilog(dispose: true);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
34
src/Configuration/SwaggerConfig.cs
Normal file
34
src/Configuration/SwaggerConfig.cs
Normal file
@ -0,0 +1,34 @@
|
||||
using Microsoft.OpenApi.Models;
|
||||
|
||||
namespace lai_transfer.Configuration
|
||||
{
|
||||
public static class SwaggerConfig
|
||||
{
|
||||
public static void AddSwaggerService(this IServiceCollection services)
|
||||
{
|
||||
services.AddSwaggerGen(c =>
|
||||
{
|
||||
var scheme = new OpenApiSecurityScheme()
|
||||
{
|
||||
Description = "Authorization header. \r\nExample: 'Bearer 12345abcdef'",
|
||||
Reference = new OpenApiReference
|
||||
{
|
||||
Type = ReferenceType.SecurityScheme,
|
||||
Id = "Authorization"
|
||||
},
|
||||
Scheme = "oauth2",
|
||||
Name = "Authorization",
|
||||
In = ParameterLocation.Header,
|
||||
Type = SecuritySchemeType.ApiKey,
|
||||
};
|
||||
c.AddSecurityDefinition("Authorization", scheme);
|
||||
c.CustomSchemaIds(type => type.FullName?.Replace("+", "."));
|
||||
var requirement = new OpenApiSecurityRequirement
|
||||
{
|
||||
[scheme] = []
|
||||
};
|
||||
c.AddSecurityRequirement(requirement);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
23
src/Configuration/config/serilog.json
Normal file
23
src/Configuration/config/serilog.json
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"Serilog": {
|
||||
"MinimumLevel": {
|
||||
"Default": "Information",
|
||||
"Override": {
|
||||
"Microsoft": "Warning",
|
||||
"System": "Warning"
|
||||
}
|
||||
},
|
||||
"WriteTo": [
|
||||
{
|
||||
"Name": "File",
|
||||
"Args": {
|
||||
"path": "logs/app-.log",
|
||||
"rollingInterval": "Day",
|
||||
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}",
|
||||
"retainedFileCountLimit": 31
|
||||
}
|
||||
}
|
||||
],
|
||||
"Enrich": [ "FromLogContext" ]
|
||||
}
|
||||
}
|
||||
6
src/Configuration/config/transfer.json
Normal file
6
src/Configuration/config/transfer.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"Origin": {
|
||||
"BaseUrl": "https://mjapi.bzu.cn",
|
||||
"Token": "23830faf80e8e69988b3fcd9aa08e9ad123"
|
||||
}
|
||||
}
|
||||
40
src/ConfigureApp.cs
Normal file
40
src/ConfigureApp.cs
Normal file
@ -0,0 +1,40 @@
|
||||
using lai_transfer.Endpoints;
|
||||
using Serilog;
|
||||
|
||||
namespace lai_transfer
|
||||
{
|
||||
public static class ConfigureApp
|
||||
{
|
||||
/// <summary>
|
||||
/// 添加APP配置
|
||||
/// </summary>
|
||||
/// <param name="app"></param>
|
||||
public static void Configure(this WebApplication app)
|
||||
{
|
||||
// 配置 Swagger
|
||||
app.SwaggerService();
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
app.UseCors("AllowAll");
|
||||
app.MapEndpoints();
|
||||
//app.UseAuthentication();
|
||||
//app.UseAuthorization();
|
||||
}
|
||||
|
||||
private static void SwaggerService(this WebApplication app)
|
||||
{
|
||||
// Configure the HTTP request pipeline.
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI();
|
||||
app.UseWhen(context => context.Request.Path.Value == "/", builder =>
|
||||
builder.Run(context =>
|
||||
{
|
||||
context.Response.Redirect("/swagger");
|
||||
return Task.CompletedTask;
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
138
src/ConfigureServices.cs
Normal file
138
src/ConfigureServices.cs
Normal file
@ -0,0 +1,138 @@
|
||||
using FluentValidation;
|
||||
using lai_transfer.Configuration;
|
||||
using lai_transfer.Model.Entity;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using Serilog;
|
||||
using System.Text;
|
||||
|
||||
namespace lai_transfer
|
||||
{
|
||||
public static class ConfigureServices
|
||||
{
|
||||
/// <summary>
|
||||
/// 添加 builder.Services 的配置
|
||||
/// </summary>
|
||||
/// <param name="builder"></param>
|
||||
public static void AddServices(this WebApplicationBuilder builder)
|
||||
{
|
||||
// 添加 Swagger 配置
|
||||
builder.Services.AddSwaggerService();
|
||||
|
||||
// 配置跨域
|
||||
builder.Services.AddCorsServices();
|
||||
|
||||
|
||||
// 添加日志配置
|
||||
builder.Services.AddLoggerService();
|
||||
builder.Host.UseSerilog();
|
||||
// 关键步骤:注册 Serilog.ILogger 到 DI 容器
|
||||
builder.Services.AddSingleton(Log.Logger);
|
||||
|
||||
// 添加数据库连接
|
||||
builder.Services.AddDbConnection();
|
||||
|
||||
// 添加Ideneity配置
|
||||
builder.Services.AddIdeneity();
|
||||
|
||||
// 添加JWT认证配置
|
||||
builder.Services.AddJWTAuthentication();
|
||||
|
||||
// 注册校验
|
||||
builder.Services.AddValidatorsFromAssembly(typeof(ConfigureServices).Assembly);
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// JWT 配置
|
||||
/// </summary>
|
||||
/// <param name="services"></param>
|
||||
private static void AddJWTAuthentication(this IServiceCollection services)
|
||||
{
|
||||
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
||||
.AddJwtBearer(x =>
|
||||
{
|
||||
// TODO: 这里的密钥需要从配置文件中读取,或者从环境变量中读取
|
||||
string secKeyEnv = "secKeyEnv";
|
||||
//string secKeyEnv = Environment.GetEnvironmentVariable("SecKey");
|
||||
byte[] keyBytes = Encoding.UTF8.GetBytes(secKeyEnv);
|
||||
var secKey = new SymmetricSecurityKey(keyBytes);
|
||||
x.TokenValidationParameters = new()
|
||||
{
|
||||
ValidateIssuer = false,
|
||||
ValidateAudience = false,
|
||||
ValidateLifetime = true,
|
||||
ValidateIssuerSigningKey = true,
|
||||
IssuerSigningKey = secKey
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 配置Identity服务配置
|
||||
/// </summary>
|
||||
/// <param name="services"></param>
|
||||
private static void AddIdeneity(this IServiceCollection services)
|
||||
{
|
||||
services.AddIdentityCore<User>(options =>
|
||||
{
|
||||
options.SignIn.RequireConfirmedAccount = true; //已有账号才能登录
|
||||
options.SignIn.RequireConfirmedEmail = true; //
|
||||
options.Password.RequireDigit = true; // 数据库中至少有一个数字
|
||||
options.Password.RequireLowercase = true; // 数据库中至少有一个小写字母
|
||||
options.Password.RequireUppercase = true; // 数据库中至少有一个大写字母
|
||||
options.Password.RequireNonAlphanumeric = true; // 数据库中至少有一个特殊字符
|
||||
options.Password.RequiredLength = 8; // 密码长度最少8位
|
||||
|
||||
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5); // 锁定时间
|
||||
options.Lockout.MaxFailedAccessAttempts = 10; // 尝试次数
|
||||
options.Lockout.AllowedForNewUsers = true; // 新用户是否可以锁定
|
||||
|
||||
//options.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+"; // 用户名允许的字符
|
||||
options.User.RequireUniqueEmail = true; // 允许重复邮箱
|
||||
});
|
||||
|
||||
var idBuilder = new IdentityBuilder(typeof(User), typeof(Role), services);
|
||||
idBuilder.AddEntityFrameworkStores<ApplicationDbContext>()
|
||||
.AddDefaultTokenProviders()
|
||||
.AddUserManager<UserManager<User>>()
|
||||
.AddRoleManager<RoleManager<Role>>();
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 配置数据库连接
|
||||
/// </summary>
|
||||
/// <param name="services"></param>
|
||||
private static void AddDbConnection(this IServiceCollection services)
|
||||
{
|
||||
services.AddDbContext<ApplicationDbContext>(options =>
|
||||
{
|
||||
string connectionString = $"server=yisurds-66dc0b453c05d4.rds.ysydb1.com;port=14080;user=luo;password=Luoqiang1405;database=LMS_TEST_1;ConvertZeroDateTime=True;SslMode=None;";
|
||||
options.UseMySql(connectionString, ServerVersion.Parse("8.0.18-mysql"));
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 配置跨域
|
||||
/// </summary>
|
||||
/// <param name="services"></param>
|
||||
|
||||
private static void AddCorsServices(this IServiceCollection services)
|
||||
{
|
||||
services.AddCors(options =>
|
||||
{
|
||||
options.AddPolicy("AllowAll",
|
||||
builder =>
|
||||
{
|
||||
builder.AllowAnyOrigin()
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyHeader();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
30
src/Dockerfile
Normal file
30
src/Dockerfile
Normal file
@ -0,0 +1,30 @@
|
||||
# 请参阅 https://aka.ms/customizecontainer 以了解如何自定义调试容器,以及 Visual Studio 如何使用此 Dockerfile 生成映像以更快地进行调试。
|
||||
|
||||
# 此阶段用于在快速模式(默认为调试配置)下从 VS 运行时
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base
|
||||
USER $APP_UID
|
||||
WORKDIR /app
|
||||
EXPOSE 8080
|
||||
EXPOSE 8081
|
||||
|
||||
|
||||
# 此阶段用于生成服务项目
|
||||
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
|
||||
ARG BUILD_CONFIGURATION=Release
|
||||
WORKDIR /src
|
||||
COPY ["lai_transfer.csproj", "."]
|
||||
RUN dotnet restore "./lai_transfer.csproj"
|
||||
COPY . .
|
||||
WORKDIR "/src/."
|
||||
RUN dotnet build "./lai_transfer.csproj" -c $BUILD_CONFIGURATION -o /app/build
|
||||
|
||||
# 此阶段用于发布要复制到最终阶段的服务项目
|
||||
FROM build AS publish
|
||||
ARG BUILD_CONFIGURATION=Release
|
||||
RUN dotnet publish "./lai_transfer.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
|
||||
|
||||
# 此阶段在生产中使用,或在常规模式下从 VS 运行时使用(在不使用调试配置时为默认值)
|
||||
FROM base AS final
|
||||
WORKDIR /app
|
||||
COPY --from=publish /app/publish .
|
||||
ENTRYPOINT ["dotnet", "lai_transfer.dll"]
|
||||
375
src/EndpointServices/MJTransferEndpoint/MJGetFetchIdService.cs
Normal file
375
src/EndpointServices/MJTransferEndpoint/MJGetFetchIdService.cs
Normal file
@ -0,0 +1,375 @@
|
||||
using lai_transfer.Common.Extensions;
|
||||
using lai_transfer.Common.Helper;
|
||||
using lai_transfer.Common.Results;
|
||||
using lai_transfer.Endpoints;
|
||||
using lai_transfer.Tool.Extensions;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace lai_transfer.EndpointServices.MJTransferEndpoint
|
||||
{
|
||||
public class MJGetFetchIdService : IEndpoint
|
||||
{
|
||||
// 使用静态字段保存日志记录器
|
||||
private static readonly ILogger _logger = LogHelper.GetLogger<MJGetFetchIdService>();
|
||||
public static void Map(IEndpointRouteBuilder app) => app
|
||||
.MapGet("task/{id}/fetch", Handle)
|
||||
.WithSummary("Midjourney 指定ID获取任务")
|
||||
.WithMJAuthorizationHeader();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取传入的 url 对应的任务
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <param name="httpContext"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task<TransferResult> SendUrlTaskFetchById(string id, HttpContext httpContext)
|
||||
{
|
||||
try
|
||||
{
|
||||
//string baseUrl =
|
||||
TransferAuthorizationResult transferAuthorizationResult = httpContext.GetAuthorizationItemsAndValidation();
|
||||
string url = $"{transferAuthorizationResult.BaseUrl}/mj/task/{id}/fetch";
|
||||
|
||||
// 设置HttpClient
|
||||
using HttpClient client = new HttpClient();
|
||||
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {transferAuthorizationResult.Token}");
|
||||
client.Timeout = Timeout.InfiniteTimeSpan;
|
||||
|
||||
// 发送请求
|
||||
var response = await client.GetAsync(url);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取原始数据信息 bzu
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
private static async Task<TransferResult?> TryOriginApiAsync(string id)
|
||||
{
|
||||
|
||||
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/{id}/fetch";
|
||||
|
||||
try
|
||||
{
|
||||
using var client = new HttpClient();
|
||||
client.DefaultRequestHeaders.Add("Authorization", token);
|
||||
client.Timeout = TimeSpan.FromSeconds(30);
|
||||
|
||||
var response = await client.GetAsync(originUrl);
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
// 判断是不是返回空
|
||||
if ((int)response.StatusCode == 204 || string.IsNullOrWhiteSpace(content))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
_logger.LogWarning($"源API调用返回错误状态码,TaskId: {id}, StatusCode: {response.StatusCode}");
|
||||
return null;
|
||||
}
|
||||
|
||||
// 有返回结果 这这边开始处理返回的数据信息
|
||||
Dictionary<string, object>? properties = ProcessTaskData(content);
|
||||
if (properties != null)
|
||||
{
|
||||
return new TransferResult(JsonConvert.SerializeObject(properties), "application/json", (int)response.StatusCode);
|
||||
}
|
||||
|
||||
// 这边是原始请求失败
|
||||
return null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "原始API调用失败,TaskId: {TaskId},准备尝试备用API", id);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理返回数据
|
||||
/// </summary>
|
||||
/// <param name="content"></param>
|
||||
/// <returns></returns>
|
||||
private static Dictionary<string, object>? ProcessTaskData(string content)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 解析 JSON 数据
|
||||
var properties = JsonConvert.DeserializeObject<Dictionary<string, object>>(content);
|
||||
if (properties == null)
|
||||
{
|
||||
_logger.LogWarning("解析任务数据失败,返回内容为空或格式不正确");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (properties.TryGetValue("isPartner", out var isPartner))
|
||||
{
|
||||
if ((bool)isPartner == true)
|
||||
{
|
||||
properties = ProcessPartnerTaskDataAsync(properties);
|
||||
}
|
||||
}
|
||||
else if (properties.TryGetValue("isOfficial", out var isOfficial))
|
||||
{
|
||||
if ((bool)isOfficial == true)
|
||||
{
|
||||
properties = ProcessOfficialTaskDataAsync(properties);
|
||||
}
|
||||
}
|
||||
else if (properties.TryGetValue("isYouChuan", out var isYouChuan))
|
||||
{
|
||||
if ((bool)isYouChuan == true)
|
||||
{
|
||||
properties = ProcessYouChuanTaskDataAsync(properties);
|
||||
}
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "处理任务数据时发生错误");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static Dictionary<string, object> ProcessTaskObjectData(Dictionary<string, object> properties)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (properties.TryGetValue("isPartner", out var isPartner))
|
||||
{
|
||||
if ((bool)isPartner == true)
|
||||
{
|
||||
properties = ProcessPartnerTaskDataAsync(properties);
|
||||
}
|
||||
}
|
||||
else if (properties.TryGetValue("isOfficial", out var isOfficial))
|
||||
{
|
||||
if ((bool)isOfficial == true)
|
||||
{
|
||||
properties = ProcessOfficialTaskDataAsync(properties);
|
||||
}
|
||||
}
|
||||
else if (properties.TryGetValue("isYouChuan", out var isYouChuan))
|
||||
{
|
||||
if ((bool)isYouChuan == true)
|
||||
{
|
||||
properties = ProcessYouChuanTaskDataAsync(properties);
|
||||
}
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "处理任务数据时发生错误");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 合作伙伴任务数据处理
|
||||
/// </summary>
|
||||
/// <param name="properties"></param>
|
||||
/// <returns></returns>
|
||||
public static Dictionary<string, object> ProcessPartnerTaskDataAsync(Dictionary<string, object> properties)
|
||||
{
|
||||
try
|
||||
{
|
||||
string jsonStr = JsonConvert.SerializeObject(properties);
|
||||
dynamic data = JsonConvert.DeserializeObject(jsonStr) ?? new object { };
|
||||
|
||||
// 检查是否是合作伙伴任务
|
||||
// 直接用dynamic访问,不做类型判断
|
||||
bool isPartner = data?.isPartner ?? false;
|
||||
string partnerTaskId = data?.partnerTaskId ?? string.Empty;
|
||||
|
||||
if (isPartner && !string.IsNullOrEmpty(partnerTaskId))
|
||||
{
|
||||
_logger.LogInformation($"处理合作伙伴任务: {partnerTaskId}");
|
||||
|
||||
// 直接遍历,让异常处理兜底
|
||||
var imageUrls = new List<object>();
|
||||
|
||||
foreach (var item in data?.partnerTaskInfo?.imgUrls ?? Array.Empty<object>())
|
||||
{
|
||||
string url = item?.url ?? string.Empty;
|
||||
if (!string.IsNullOrEmpty(url))
|
||||
{
|
||||
imageUrls.Add(new { url });
|
||||
}
|
||||
}
|
||||
|
||||
if (imageUrls.Count > 0)
|
||||
{
|
||||
properties["imageUrls"] = imageUrls;
|
||||
_logger.LogInformation($"成功提取了{imageUrls.Count}个图片URL");
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation("未找到partnerTaskInfo或者是图片信信息");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation($"任务不是partner任务或缺少必要信息: isPartner={isPartner}, partnerTaskId={partnerTaskId}");
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "处理任务数据时发生错误");
|
||||
return properties; // 返回原始数据,避免处理错误导致数据丢失
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 官方任务数据处理
|
||||
/// </summary>
|
||||
/// <param name="properties"></param>
|
||||
/// <returns></returns>
|
||||
public static Dictionary<string, object> ProcessOfficialTaskDataAsync(Dictionary<string, object> properties)
|
||||
{
|
||||
try
|
||||
{
|
||||
string jsonStr = JsonConvert.SerializeObject(properties);
|
||||
dynamic data = JsonConvert.DeserializeObject(jsonStr) ?? new object { };
|
||||
|
||||
// 检查是否是合作伙伴任务
|
||||
// 直接用dynamic访问,不做类型判断
|
||||
bool isOfficial = data?.isOfficial ?? false;
|
||||
string officialTaskId = data?.officialTaskId ?? string.Empty;
|
||||
|
||||
if (isOfficial && !string.IsNullOrEmpty(officialTaskId))
|
||||
{
|
||||
_logger.LogInformation($"处理官方任务: {officialTaskId}");
|
||||
|
||||
// 直接遍历,让异常处理兜底
|
||||
var imageUrls = new List<object>();
|
||||
|
||||
foreach (var item in data?.officialTaskInfo?.imgUrls ?? Array.Empty<object>())
|
||||
{
|
||||
string url = item?.url ?? string.Empty;
|
||||
if (!string.IsNullOrEmpty(url))
|
||||
{
|
||||
imageUrls.Add(new { url });
|
||||
}
|
||||
}
|
||||
|
||||
if (imageUrls.Count > 0)
|
||||
{
|
||||
properties["imageUrls"] = imageUrls;
|
||||
_logger.LogInformation($"成功提取了{imageUrls.Count}个图片URL");
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation("未找到officialTaskInfo或者是图片信信息");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation($"任务不是official任务或缺少必要信息: isOfficial={isOfficial}, officialTaskId={officialTaskId}");
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "处理任务数据时发生错误");
|
||||
return properties; // 返回原始数据,避免处理错误导致数据丢失
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 悠船任务数据处理
|
||||
/// </summary>
|
||||
/// <param name="properties"></param>
|
||||
/// <returns></returns>
|
||||
public static Dictionary<string, object> ProcessYouChuanTaskDataAsync(Dictionary<string, object> properties)
|
||||
{
|
||||
try
|
||||
{
|
||||
string jsonStr = JsonConvert.SerializeObject(properties);
|
||||
dynamic data = JsonConvert.DeserializeObject(jsonStr) ?? new object { };
|
||||
|
||||
// 检查是否是合作伙伴任务
|
||||
// 直接用dynamic访问,不做类型判断
|
||||
bool isYouChuan = data?.isYouChuan ?? false;
|
||||
string youChuanTaskId = data?.youChuanTaskId ?? string.Empty;
|
||||
|
||||
if (isYouChuan && !string.IsNullOrEmpty(youChuanTaskId))
|
||||
{
|
||||
_logger.LogInformation($"处理悠船任务: {youChuanTaskId}");
|
||||
|
||||
// 直接遍历,让异常处理兜底
|
||||
var imageUrls = new List<object>();
|
||||
|
||||
foreach (var item in data?.youChuanTaskInfo?.imgUrls ?? Array.Empty<object>())
|
||||
{
|
||||
string url = item?.url ?? string.Empty;
|
||||
if (!string.IsNullOrEmpty(url))
|
||||
{
|
||||
imageUrls.Add(new { url });
|
||||
}
|
||||
}
|
||||
|
||||
if (imageUrls.Count > 0)
|
||||
{
|
||||
properties["imageUrls"] = imageUrls;
|
||||
_logger.LogInformation($"成功提取了{imageUrls.Count}个图片URL");
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation("未找到youChuanTaskInfo或者是图片信信息");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation($"任务不是YouChuan任务或缺少必要信息: isYouChuan={isYouChuan}, youChuanTaskId={youChuanTaskId}");
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "处理任务数据时发生错误");
|
||||
return properties; // 返回原始数据,避免处理错误导致数据丢失
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,51 @@
|
||||
using lai_transfer.Common.Extensions;
|
||||
using lai_transfer.Common.Helper;
|
||||
using lai_transfer.Common.Results;
|
||||
using lai_transfer.Endpoints;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace lai_transfer.EndpointServices.MJTransferEndpoint
|
||||
{
|
||||
public class MJGetImageSeedService : IEndpoint
|
||||
{
|
||||
private static readonly ILogger _logger = LogHelper.GetLogger<MJGetImageSeedService>();
|
||||
public static void Map(IEndpointRouteBuilder app) => app
|
||||
.MapGet("/task/{id}/image-seed", Handle)
|
||||
.WithSummary("Midjourney 提交 Action 任务")
|
||||
.WithMJAuthorizationHeader();
|
||||
|
||||
private static async Task<IResult> Handle(string id, HttpContext httpContext)
|
||||
{
|
||||
TransferResult transferResult = await SendOriginalImageSeed(id, httpContext);
|
||||
return Results.Text(transferResult.Content, transferResult.ContentType, statusCode: transferResult.StatusCode);
|
||||
}
|
||||
|
||||
private static async Task<TransferResult> SendOriginalImageSeed(string id, HttpContext httpContext)
|
||||
{
|
||||
try
|
||||
{
|
||||
//string baseUrl =
|
||||
TransferAuthorizationResult transferAuthorizationResult = httpContext.GetAuthorizationItemsAndValidation();
|
||||
string url = $"{transferAuthorizationResult.BaseUrl}/mj/task/{id}/image-seed";
|
||||
|
||||
// 设置HttpClient
|
||||
using HttpClient client = new HttpClient();
|
||||
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {transferAuthorizationResult.Token}");
|
||||
client.Timeout = Timeout.InfiniteTimeSpan;
|
||||
|
||||
// 发送请求
|
||||
var response = await client.GetAsync(url);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,50 @@
|
||||
using lai_transfer.Common.Extensions;
|
||||
using lai_transfer.Common.Helper;
|
||||
using lai_transfer.Common.Results;
|
||||
using lai_transfer.Configuration;
|
||||
using lai_transfer.Endpoints;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace lai_transfer.EndpointServices.MJTransferEndpoint
|
||||
{
|
||||
public class MJPostActionService : IEndpoint
|
||||
{
|
||||
private static readonly ILogger _logger = LogHelper.GetLogger<MJPostActionService>();
|
||||
public static void Map(IEndpointRouteBuilder app) => app
|
||||
.MapPost("/submit/action", Handle)
|
||||
.WithSummary("Midjourney 提交 Action 任务")
|
||||
.WithMJAuthorizationHeader();
|
||||
|
||||
private static async Task<IResult> Handle(JsonElement model, HttpContext httpContext)
|
||||
{
|
||||
(string content, string contentType, int statusCode) = await SendOriginalAction(model, httpContext);
|
||||
return Results.Text(content, contentType, statusCode: statusCode);
|
||||
}
|
||||
|
||||
private static async Task<TransferResult> SendOriginalAction(JsonElement model, HttpContext httpContext)
|
||||
{
|
||||
try
|
||||
{
|
||||
TransferAuthorizationResult authorizationResult = httpContext.GetAuthorizationItemsAndValidation();
|
||||
string url = $"{authorizationResult.BaseUrl}/mj/submit/action";
|
||||
// 设置HttpClient
|
||||
using HttpClient client = new HttpClient();
|
||||
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {authorizationResult.Token}");
|
||||
client.Timeout = Timeout.InfiniteTimeSpan;
|
||||
|
||||
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);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"请求 {httpContext.GetFullRequestPath()} 失败");
|
||||
// 处理异常,返回错误信息
|
||||
return new TransferResult(ex.Message, "application/json", StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,49 @@
|
||||
using lai_transfer.Common.Extensions;
|
||||
using lai_transfer.Common.Helper;
|
||||
using lai_transfer.Common.Results;
|
||||
using lai_transfer.Endpoints;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace lai_transfer.EndpointServices.MJTransferEndpoint
|
||||
{
|
||||
public class MJPostBlendService : IEndpoint
|
||||
{
|
||||
private static readonly ILogger _logger = LogHelper.GetLogger<MJPostBlendService>();
|
||||
public static void Map(IEndpointRouteBuilder app) => app
|
||||
.MapPost("/submit/blend", Handle)
|
||||
.WithSummary("Midjourney 提交 Blend 任务")
|
||||
.WithMJAuthorizationHeader();
|
||||
|
||||
private static async Task<IResult> Handle(JsonElement model, HttpContext httpContext)
|
||||
{
|
||||
TransferResult transferResult = await SendOriginalBlend(model, httpContext);
|
||||
return Results.Text(transferResult.Content, transferResult.ContentType, statusCode: transferResult.StatusCode);
|
||||
}
|
||||
|
||||
private static async Task<TransferResult> SendOriginalBlend(JsonElement model, HttpContext httpContext)
|
||||
{
|
||||
try
|
||||
{
|
||||
TransferAuthorizationResult authorizationResult = httpContext.GetAuthorizationItemsAndValidation();
|
||||
string url = $"{authorizationResult.BaseUrl}/mj/submit/blend";
|
||||
// 设置HttpClient
|
||||
using HttpClient client = new HttpClient();
|
||||
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {authorizationResult.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);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"转发 {httpContext.GetFullRequestPath()} 失败");
|
||||
// 处理异常,返回错误信息
|
||||
return new TransferResult(ex.Message, "application/json", StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,50 @@
|
||||
using lai_transfer.Common.Extensions;
|
||||
using lai_transfer.Common.Helper;
|
||||
using lai_transfer.Common.Results;
|
||||
using lai_transfer.Endpoints;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace lai_transfer.EndpointServices.MJTransferEndpoint
|
||||
{
|
||||
public class MJPostDescribeService : IEndpoint
|
||||
{
|
||||
private static readonly ILogger _logger = LogHelper.GetLogger<MJPostDescribeService>();
|
||||
public static void Map(IEndpointRouteBuilder app) => app
|
||||
.MapPost("/submit/describe", Handle)
|
||||
.WithSummary("Midjourney 提交 Describe 任务")
|
||||
.WithMJAuthorizationHeader();
|
||||
|
||||
private static async Task<IResult> Handle(JsonElement model, HttpContext httpContext)
|
||||
{
|
||||
TransferResult transferResult = await SendOriginalDescribe(model, httpContext);
|
||||
return Results.Text(transferResult.Content, transferResult.ContentType, statusCode: transferResult.StatusCode);
|
||||
}
|
||||
|
||||
private static async Task<TransferResult> SendOriginalDescribe(JsonElement model, HttpContext httpContext)
|
||||
{
|
||||
try
|
||||
{
|
||||
TransferAuthorizationResult authorizationResult = httpContext.GetAuthorizationItemsAndValidation();
|
||||
string url = $"{authorizationResult.BaseUrl}/mj/submit/describe";
|
||||
// 设置HttpClient
|
||||
using HttpClient client = new HttpClient();
|
||||
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {authorizationResult.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);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"转发 {httpContext.GetFullRequestPath()} 失败");
|
||||
// 处理异常,返回错误信息
|
||||
return new TransferResult(ex.Message, "application/json", StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,149 @@
|
||||
using lai_transfer.Common.Extensions;
|
||||
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;
|
||||
|
||||
namespace lai_transfer.EndpointServices.MJTransferEndpoint
|
||||
{
|
||||
public class MJPostFetchListByConditionService : IEndpoint
|
||||
{
|
||||
private static readonly ILogger _logger = LogHelper.GetLogger<MJPostFetchListByConditionService>();
|
||||
public static void Map(IEndpointRouteBuilder app) => app
|
||||
.MapPost("/task/list-by-condition", Handle)
|
||||
.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
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,56 @@
|
||||
using lai_transfer.Common.Extensions;
|
||||
using lai_transfer.Common.Helper;
|
||||
using lai_transfer.Common.Results;
|
||||
using lai_transfer.Endpoints;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Newtonsoft.Json;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace lai_transfer.EndpointServices.MJTransferEndpoint
|
||||
{
|
||||
public class MJPostFetchListByIdsService : IEndpoint
|
||||
{
|
||||
private static readonly ILogger _logger = LogHelper.GetLogger<MJPostFetchListByIdsService>();
|
||||
public static void Map(IEndpointRouteBuilder app) => app
|
||||
.MapPost("/task/list-by-ids", Handle)
|
||||
.WithSummary("Midjourney 根据ID列表查询任务-字段displays")
|
||||
.WithMJAuthorizationHeader();
|
||||
|
||||
private static async Task<IResult> Handle(JsonElement model, HttpContext httpContext)
|
||||
{
|
||||
TransferResult transferResult = await SendOriginalFetchListByIds(model, httpContext);
|
||||
return Results.Text(transferResult.Content, transferResult.ContentType, statusCode: transferResult.StatusCode);
|
||||
}
|
||||
|
||||
private static async Task<TransferResult> SendOriginalFetchListByIds(JsonElement model, HttpContext httpContext)
|
||||
{
|
||||
try
|
||||
{
|
||||
//TransferAuthorizationResult transferAuthorizationResult = httpContext.GetAuthorizationItemsAndValidation();
|
||||
//string url = $"{transferAuthorizationResult.BaseUrl}/mj/task/list-by-ids";
|
||||
//// 设置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);
|
||||
string content = JsonConvert.SerializeObject(new
|
||||
{
|
||||
code = 6,
|
||||
description = "该方法已停用!!"
|
||||
});
|
||||
return await Task.FromResult(new TransferResult(content, "application/json", StatusCodes.Status200OK));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"转发 {httpContext.GetFullRequestPath()} 失败");
|
||||
// 处理异常,返回错误信息
|
||||
return await Task.FromResult(new TransferResult(ex.Message, "application/json", StatusCodes.Status500InternalServerError));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
using lai_transfer.Common.Extensions;
|
||||
using lai_transfer.Common.Helper;
|
||||
using lai_transfer.Common.Results;
|
||||
using lai_transfer.Configuration;
|
||||
using lai_transfer.Endpoints;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace lai_transfer.EndpointServices.MJTransferEndpoint
|
||||
{
|
||||
public class MJPostImagineService : IEndpoint
|
||||
{
|
||||
public static void Map(IEndpointRouteBuilder app) => app
|
||||
.MapPost("/submit/imagine", Handle)
|
||||
.WithSummary("Midjourney 提交 Imagine 任务")
|
||||
.WithMJAuthorizationHeader();
|
||||
|
||||
private static async Task<IResult> Handle(JsonElement model, HttpContext httpContext, ILogger<MJPostImagineService> logger)
|
||||
{
|
||||
(string content, string contentType, int statusCode) = await SendOriginalImagine(model, httpContext, logger);
|
||||
return Results.Text(content, contentType, statusCode: statusCode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 实际转发请求的方法
|
||||
/// </summary>
|
||||
/// <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, ILogger<MJPostImagineService> logger)
|
||||
{
|
||||
try
|
||||
{
|
||||
TransferAuthorizationResult authorizationResult = httpContext.GetAuthorizationItemsAndValidation();
|
||||
string url = $"{authorizationResult.BaseUrl}/mj/submit/imagine";
|
||||
|
||||
// 设置HttpClient
|
||||
using HttpClient client = new HttpClient();
|
||||
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {authorizationResult.Token}");
|
||||
client.Timeout = Timeout.InfiniteTimeSpan;
|
||||
|
||||
// 删除回调参数 notifyHook
|
||||
|
||||
string body = JSONHelper.RemoveJsonProperties(model, "notifyHook");
|
||||
// 发送请求
|
||||
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);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, $"请求 {httpContext.GetFullRequestPath()} 失败");
|
||||
// 处理异常,返回错误信息
|
||||
return (ex.Message, "application/json", StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,48 @@
|
||||
using lai_transfer.Common.Extensions;
|
||||
using lai_transfer.Common.Helper;
|
||||
using lai_transfer.Common.Results;
|
||||
using lai_transfer.Endpoints;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace lai_transfer.EndpointServices.MJTransferEndpoint
|
||||
{
|
||||
public class MJPostModalService : IEndpoint
|
||||
{
|
||||
private static readonly ILogger _logger = LogHelper.GetLogger<MJPostModalService>();
|
||||
public static void Map(IEndpointRouteBuilder app) => app
|
||||
.MapPost("/submit/modal", Handle)
|
||||
.WithSummary("Midjourney 提交 Modal 任务")
|
||||
.WithMJAuthorizationHeader();
|
||||
|
||||
private static async Task<IResult> Handle(JsonElement model, HttpContext httpContext)
|
||||
{
|
||||
TransferResult transferResult = await SendOriginalModal(model, httpContext);
|
||||
return Results.Text(transferResult.Content, transferResult.ContentType, statusCode: transferResult.StatusCode);
|
||||
}
|
||||
|
||||
private static async Task<TransferResult> SendOriginalModal(JsonElement model, HttpContext httpContext)
|
||||
{
|
||||
try
|
||||
{
|
||||
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;
|
||||
// 删除回调参数 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);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"转发 {httpContext.GetFullRequestPath()} 失败");
|
||||
// 处理异常,返回错误信息
|
||||
return new TransferResult(ex.Message, "application/json", StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,49 @@
|
||||
using lai_transfer.Common.Extensions;
|
||||
using lai_transfer.Common.Helper;
|
||||
using lai_transfer.Common.Results;
|
||||
using lai_transfer.Endpoints;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace lai_transfer.EndpointServices.MJTransferEndpoint
|
||||
{
|
||||
public class MJPostShortenService : IEndpoint
|
||||
{
|
||||
private static readonly ILogger _logger = LogHelper.GetLogger<MJPostShortenService>();
|
||||
public static void Map(IEndpointRouteBuilder app) => app
|
||||
.MapPost("/submit/shorten", Handle)
|
||||
.WithSummary("Midjourney 提交 Shorten 任务")
|
||||
.WithMJAuthorizationHeader();
|
||||
|
||||
private static async Task<IResult> Handle(JsonElement model, HttpContext httpContext)
|
||||
{
|
||||
TransferResult transferResult = await SendOriginalShorten(model, httpContext);
|
||||
return Results.Text(transferResult.Content, transferResult.ContentType, statusCode: transferResult.StatusCode);
|
||||
}
|
||||
|
||||
private static async Task<TransferResult> SendOriginalShorten(JsonElement model, HttpContext httpContext)
|
||||
{
|
||||
try
|
||||
{
|
||||
TransferAuthorizationResult transferAuthorizationResult = httpContext.GetAuthorizationItemsAndValidation();
|
||||
string url = $"{transferAuthorizationResult.BaseUrl}/mj/submit/shorten";
|
||||
|
||||
// 设置HttpClient
|
||||
using HttpClient client = new HttpClient();
|
||||
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {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);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"请求 {httpContext.GetFullRequestPath()} 失败");
|
||||
// 处理异常,返回错误信息
|
||||
return new TransferResult(ex.Message, "application/json", StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,48 @@
|
||||
using lai_transfer.Common.Extensions;
|
||||
using lai_transfer.Common.Helper;
|
||||
using lai_transfer.Common.Results;
|
||||
using lai_transfer.Endpoints;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace lai_transfer.EndpointServices.MJTransferEndpoint
|
||||
{
|
||||
public class MJPostSwapFaceService : IEndpoint
|
||||
{
|
||||
private static readonly ILogger _logger = LogHelper.GetLogger<MJPostSwapFaceService>();
|
||||
public static void Map(IEndpointRouteBuilder app) => app
|
||||
.MapPost("/insight-face/swap", Handle)
|
||||
.WithSummary("Midjourney 提交 swap_face 任务")
|
||||
.WithMJAuthorizationHeader();
|
||||
|
||||
private static async Task<IResult> Handle(JsonElement model, HttpContext httpContext)
|
||||
{
|
||||
TransferResult transferResult = await SendOriginalSwapFace(model, httpContext);
|
||||
return Results.Text(transferResult.Content, transferResult.ContentType, statusCode: transferResult.StatusCode);
|
||||
}
|
||||
|
||||
private static async Task<TransferResult> SendOriginalSwapFace(JsonElement model, HttpContext httpContext)
|
||||
{
|
||||
try
|
||||
{
|
||||
TransferAuthorizationResult transferAuthorizationResult = httpContext.GetAuthorizationItemsAndValidation();
|
||||
string url = $"{transferAuthorizationResult.BaseUrl}/mj/insight-face/swap";
|
||||
|
||||
// 设置HttpClient
|
||||
using HttpClient client = new();
|
||||
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {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);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"转发 {httpContext.GetFullRequestPath()} 失败");
|
||||
return new TransferResult(ex.Message, "application/json", StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,44 @@
|
||||
using lai_transfer.Common.Extensions;
|
||||
using lai_transfer.Common.Helper;
|
||||
using lai_transfer.Common.Results;
|
||||
using lai_transfer.Endpoints;
|
||||
using Newtonsoft.Json;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace lai_transfer.EndpointServices.MJTransferEndpoint
|
||||
{
|
||||
public class MJPostUploadDiscordImages : IEndpoint
|
||||
{
|
||||
|
||||
private static readonly ILogger _logger = LogHelper.GetLogger<MJPostUploadDiscordImages>();
|
||||
public static void Map(IEndpointRouteBuilder app) => app
|
||||
.MapPost("/submit/upload-discord-images", Handle)
|
||||
.WithSummary("Midjourney 提交 上传文件到discord 任务(禁用)")
|
||||
.WithMJAuthorizationHeader();
|
||||
|
||||
private static async Task<IResult> Handle(JsonElement model, HttpContext httpContext)
|
||||
{
|
||||
TransferResult transferResult = await SendOriginalUploadDiscordImages(model, httpContext);
|
||||
return Results.Text(transferResult.Content, transferResult.ContentType, statusCode: transferResult.StatusCode);
|
||||
}
|
||||
|
||||
private static async Task<TransferResult> SendOriginalUploadDiscordImages(JsonElement model, HttpContext httpContext)
|
||||
{
|
||||
try
|
||||
{
|
||||
string content = JsonConvert.SerializeObject(new
|
||||
{
|
||||
code = 4,
|
||||
description = "图片上传已禁用!!"
|
||||
});
|
||||
return await Task.FromResult(new TransferResult(content, "application/json", StatusCodes.Status200OK));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"转发 {httpContext.GetFullRequestPath()} 失败");
|
||||
// 处理异常,返回错误信息
|
||||
return await Task.FromResult(new TransferResult(ex.Message, "application/json", StatusCodes.Status500InternalServerError));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
90
src/EndpointServices/UserEndpoint/UserRegisterService.cs
Normal file
90
src/EndpointServices/UserEndpoint/UserRegisterService.cs
Normal file
@ -0,0 +1,90 @@
|
||||
using FluentValidation;
|
||||
using lai_transfer.Common.Enums;
|
||||
using lai_transfer.Common.Extensions;
|
||||
using lai_transfer.Common.Results;
|
||||
using lai_transfer.Configuration;
|
||||
using lai_transfer.Endpoints;
|
||||
using lai_transfer.Model.Entity;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace lai_transfer.EndpointServices.UserEndpoint
|
||||
{
|
||||
public class UserRegisterService : IEndpoint
|
||||
{
|
||||
/// <summary>
|
||||
/// 请求的参数
|
||||
/// </summary>
|
||||
/// <param name="Username">用户名 必填</param>
|
||||
/// <param name="Password">加密后的密码 必填</param>
|
||||
/// <param name="Mail">邮箱 必填</param>
|
||||
/// <param name="TokenId">获取密钥的Key</param>
|
||||
/// <param name="AffiliateCode">验证码</param>
|
||||
public record Request(string UserName, string Password, string Mail, string TokenId, string AffiliateCode);
|
||||
|
||||
public class RequestValidator : AbstractValidator<Request>
|
||||
{
|
||||
public RequestValidator()
|
||||
{
|
||||
RuleFor(x => x.UserName).NotEmpty().Length(3, 20);
|
||||
RuleFor(x => x.Password).NotEmpty();
|
||||
RuleFor(x => x.Mail).NotEmpty().EmailAddress();
|
||||
RuleFor(x => x.TokenId).NotEmpty();
|
||||
RuleFor(x => x.AffiliateCode).NotEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
public static void Map(IEndpointRouteBuilder app) => app
|
||||
.MapPost("/register", Handle)
|
||||
.WithSummary("用户注册")
|
||||
.WithRequestValidation<Request>(); // 添加验证;
|
||||
|
||||
private static async Task<APIResponseModel<IOperationResult>> Handle(Request request, ApplicationDbContext dbContext, ILogger<RequestValidator> logger, UserManager<User> userManager)
|
||||
{
|
||||
using var transaction = await dbContext.Database.BeginTransactionAsync();
|
||||
try
|
||||
{
|
||||
// 1. 解密Password
|
||||
|
||||
// 2. 判断邮箱是不是已使用
|
||||
|
||||
// 3. 判断用户名是不是已使用
|
||||
|
||||
// 4. 判断验证码是不是有效或被使用
|
||||
|
||||
// 5. 创建用户
|
||||
var user = new User { UserName = request.UserName, Email = request.Mail, NickName = request.UserName };
|
||||
|
||||
string decryptedPassword = request.Password; // 这里需要替换成实际的解密逻辑
|
||||
|
||||
var result = await userManager.CreateAsync(user, decryptedPassword);
|
||||
if (!result.Succeeded)
|
||||
{
|
||||
foreach (var s in result.Errors)
|
||||
{
|
||||
return APIResponseModel<IOperationResult>.CreateErrorResponseModel(ResponseCode.OptionsError, s.Description);
|
||||
}
|
||||
}
|
||||
try
|
||||
{
|
||||
await userManager.AddToRoleAsync(user, "Simple User");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// 如果添加角色失败,删除用户
|
||||
await userManager.DeleteAsync(user);
|
||||
throw;
|
||||
}
|
||||
await dbContext.SaveChangesAsync();
|
||||
await transaction.CommitAsync();
|
||||
return APIResponseModel<IOperationResult>.CreateSuccessResponseModel("User Register Success");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, $"Registration failed for user {request.UserName}, mail {request.Mail}");
|
||||
await transaction.RollbackAsync();
|
||||
return APIResponseModel<IOperationResult>.CreateErrorResponseModel(ResponseCode.SystemError, "Registration failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
44
src/EndpointServices/WeatherForecast.cs
Normal file
44
src/EndpointServices/WeatherForecast.cs
Normal file
@ -0,0 +1,44 @@
|
||||
using FluentValidation;
|
||||
using lai_transfer.Common.Enums;
|
||||
using lai_transfer.Common.Extensions;
|
||||
using lai_transfer.Common.Results;
|
||||
using lai_transfer.Endpoints;
|
||||
|
||||
namespace lai_transfer.EndpointServices
|
||||
{
|
||||
public class WeatherForecast : IEndpoint
|
||||
{
|
||||
public record Request(string Username, string Password, string Name);
|
||||
public record Response(string Token) : IOperationResult;
|
||||
|
||||
public class RequestValidator : AbstractValidator<Request>
|
||||
{
|
||||
public RequestValidator()
|
||||
{
|
||||
RuleFor(x => x.Username).NotEmpty();
|
||||
RuleFor(x => x.Password).NotEmpty();
|
||||
RuleFor(x => x.Name).NotEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
public static void Map(IEndpointRouteBuilder app) => app
|
||||
.MapPost("/signup", Handle)
|
||||
.WithSummary("Creates a new user account")
|
||||
.WithRequestValidation<Request>(); // 添加验证;
|
||||
|
||||
private static Task<APIResponseModel<IOperationResult>> Handle(Request request)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 模拟生成 JWT Token
|
||||
var token = $"jwt_token_for_{request.Username}_{DateTime.UtcNow.Ticks}";
|
||||
var response = new Response(token);
|
||||
return Task.FromResult(APIResponseModel<IOperationResult>.CreateSuccessResponseModel(response));
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return Task.FromResult(APIResponseModel<IOperationResult>.CreateErrorResponseModel(ResponseCode.SystemError, "Registration failed"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
7
src/Endpoints/IEndpoint.cs
Normal file
7
src/Endpoints/IEndpoint.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace lai_transfer.Endpoints
|
||||
{
|
||||
public interface IEndpoint
|
||||
{
|
||||
static abstract void Map(IEndpointRouteBuilder app);
|
||||
}
|
||||
}
|
||||
52
src/Endpoints/MJEndpoint.cs
Normal file
52
src/Endpoints/MJEndpoint.cs
Normal file
@ -0,0 +1,52 @@
|
||||
using lai_transfer.Common.Extensions;
|
||||
using lai_transfer.EndpointServices.MJTransferEndpoint;
|
||||
|
||||
namespace lai_transfer.Endpoints
|
||||
{
|
||||
public static class MJEndpoint
|
||||
{
|
||||
public static void MapMJEndpoint(this IEndpointRouteBuilder app)
|
||||
{
|
||||
// 创建基础路由组
|
||||
var baseGroup = app.MapGroup("");
|
||||
|
||||
var defaultGroup = baseGroup.MapGroup("/mj")
|
||||
.WithTags("MidjourneyAPI-Default");
|
||||
|
||||
// 创建三个独立的路由组
|
||||
var relaxGroup = baseGroup.MapGroup("/mj-relax/mj")
|
||||
.WithTags("MidjourneyAPI-Relax");
|
||||
|
||||
var fastGroup = baseGroup.MapGroup("/mj-fast/mj")
|
||||
.WithTags("MidjourneyAPI-Fast");
|
||||
|
||||
var turboGroup = baseGroup.MapGroup("/mj-turbo/mj")
|
||||
.WithTags("MidjourneyAPI-Turbo");
|
||||
|
||||
// 为每个路由组配置端点
|
||||
MapEndpointsForGroup(defaultGroup);
|
||||
MapEndpointsForGroup(relaxGroup);
|
||||
MapEndpointsForGroup(fastGroup);
|
||||
MapEndpointsForGroup(turboGroup);
|
||||
}
|
||||
|
||||
private static void MapEndpointsForGroup(RouteGroupBuilder group)
|
||||
{
|
||||
group.MapPublicGroup()
|
||||
.MapEndpoint<MJPostImagineService>()
|
||||
.MapEndpoint<MJPostActionService>()
|
||||
.MapEndpoint<MJPostBlendService>()
|
||||
.MapEndpoint<MJPostModalService>()
|
||||
.MapEndpoint<MJPostDescribeService>()
|
||||
.MapEndpoint<MJPostUploadDiscordImages>()
|
||||
.MapEndpoint<MJPostShortenService>()
|
||||
.MapEndpoint<MJPostSwapFaceService>()
|
||||
.MapEndpoint<MJGetFetchIdService>()
|
||||
.MapEndpoint<MJPostFetchListByConditionService>()
|
||||
.MapEndpoint<MJPostFetchListByIdsService>()
|
||||
.MapEndpoint<MJGetImageSeedService>();
|
||||
|
||||
// 可以添加更多特定于此组的端点
|
||||
}
|
||||
}
|
||||
}
|
||||
23
src/Endpoints/MainEndpoints.cs
Normal file
23
src/Endpoints/MainEndpoints.cs
Normal file
@ -0,0 +1,23 @@
|
||||
using lai_transfer.Common.Filters;
|
||||
|
||||
namespace lai_transfer.Endpoints
|
||||
{
|
||||
public static class MainEndpoints
|
||||
{
|
||||
public static void MapEndpoints(this WebApplication app)
|
||||
{
|
||||
var endpoints = app.MapGroup("/api")
|
||||
.AddEndpointFilter<RequestLoggingFilter>()
|
||||
.WithOpenApi();
|
||||
|
||||
endpoints.MapWeatherForecastEndpoint();
|
||||
endpoints.MapUserEndpoint();
|
||||
|
||||
var emptyEndpoints = app.MapGroup("")
|
||||
.AddEndpointFilter<RequestLoggingFilter>()
|
||||
.WithOpenApi();
|
||||
emptyEndpoints.MapMJEndpoint();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
18
src/Endpoints/UserEndpoint.cs
Normal file
18
src/Endpoints/UserEndpoint.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using lai_transfer.Common.Extensions;
|
||||
using lai_transfer.EndpointServices.UserEndpoint;
|
||||
|
||||
namespace lai_transfer.Endpoints
|
||||
{
|
||||
public static class UserEndpoint
|
||||
{
|
||||
public static void MapUserEndpoint(this IEndpointRouteBuilder app)
|
||||
{
|
||||
var endpoints = app.MapGroup("/user")
|
||||
.WithTags("UserAPI");
|
||||
|
||||
endpoints.MapPublicGroup()
|
||||
.MapEndpoint<UserRegisterService>();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
18
src/Endpoints/WeatherForecastEndpoint.cs
Normal file
18
src/Endpoints/WeatherForecastEndpoint.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using lai_transfer.Common.Extensions;
|
||||
using lai_transfer.EndpointServices;
|
||||
|
||||
namespace lai_transfer.Endpoints
|
||||
{
|
||||
public static class WeatherForecastEndpoint
|
||||
{
|
||||
public static void MapWeatherForecastEndpoint(this IEndpointRouteBuilder app)
|
||||
{
|
||||
var endpoints = app.MapGroup("/auth")
|
||||
.WithTags("Authentication");
|
||||
|
||||
endpoints.MapPublicGroup()
|
||||
.MapEndpoint<WeatherForecast>();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
17
src/Model/Entity/Role.cs
Normal file
17
src/Model/Entity/Role.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
namespace lai_transfer.Model.Entity
|
||||
{
|
||||
public class Role : IdentityRole<long>
|
||||
{
|
||||
public long CreatedUserId { get; set; }
|
||||
|
||||
public long UpdatedUserId { get; set; }
|
||||
|
||||
public DateTime CreatedTime { get; set; }
|
||||
|
||||
public DateTime UpdatedTime { get; set; }
|
||||
|
||||
public string? Remark { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
40
src/Model/Entity/User.cs
Normal file
40
src/Model/Entity/User.cs
Normal file
@ -0,0 +1,40 @@
|
||||
using lai_transfer.Tool.Extensions;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace lai_transfer.Model.Entity
|
||||
{
|
||||
public class User : IdentityUser<long>
|
||||
{
|
||||
[MaxLength(50)]
|
||||
public required string NickName { get; set; }
|
||||
|
||||
[Column(TypeName = "datetime")]
|
||||
public DateTime CreatedDate { get; set; } = BeijingTimeExtension.GetBeijingTime();
|
||||
|
||||
[Column(TypeName = "datetime")]
|
||||
public DateTime UpdatedDate { get; set; } = BeijingTimeExtension.GetBeijingTime();
|
||||
|
||||
[Column(TypeName = "datetime")]
|
||||
public DateTime LastLoginDate { get; set; } = BeijingTimeExtension.GetBeijingTime();
|
||||
|
||||
public string? LastLoginIp { get; set; } = "";
|
||||
|
||||
public string? LastLoginDevice { get; set; } = "";
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 用户微信号
|
||||
/// </summary>
|
||||
public string? WXNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 备注
|
||||
/// </summary>
|
||||
public string? Remark { get; set; }
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
11
src/Model/Entity/UserRoles.cs
Normal file
11
src/Model/Entity/UserRoles.cs
Normal file
@ -0,0 +1,11 @@
|
||||
namespace lai_transfer.Model.Entity
|
||||
{
|
||||
public class UserRoles
|
||||
{
|
||||
|
||||
public required string RoleId { get; set; }
|
||||
|
||||
public required string UserId { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
26
src/Program.cs
Normal file
26
src/Program.cs
Normal file
@ -0,0 +1,26 @@
|
||||
using lai_transfer;
|
||||
using lai_transfer.Common.Helper;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
|
||||
// Add services to the container.
|
||||
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
|
||||
builder.Services.AddOpenApi();
|
||||
builder.AddServices();
|
||||
|
||||
var app = builder.Build();
|
||||
app.Configure();
|
||||
|
||||
// ³õʼ»¯ÈÕÖ¾°ïÖúÀà
|
||||
LogHelper.Initialize(app.Services.GetRequiredService<ILoggerFactory>());
|
||||
ConfigHelper.Initialize();
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
app.MapOpenApi();
|
||||
}
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
app.Run();
|
||||
46
src/Properties/launchSettings.json
Normal file
46
src/Properties/launchSettings.json
Normal file
@ -0,0 +1,46 @@
|
||||
{
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"dotnetRunMessages": true,
|
||||
"applicationUrl": "http://localhost:5141"
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"dotnetRunMessages": true,
|
||||
"applicationUrl": "https://localhost:7199;http://localhost:5141"
|
||||
},
|
||||
"Container (Dockerfile)": {
|
||||
"commandName": "Docker",
|
||||
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_HTTPS_PORTS": "8081",
|
||||
"ASPNETCORE_HTTP_PORTS": "8080"
|
||||
},
|
||||
"publishAllPorts": true,
|
||||
"useSSL": true
|
||||
},
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
},
|
||||
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:14921/",
|
||||
"sslPort": 44349
|
||||
}
|
||||
}
|
||||
}
|
||||
13
src/Tool/Attributes/DescriptionAttribute.cs
Normal file
13
src/Tool/Attributes/DescriptionAttribute.cs
Normal file
@ -0,0 +1,13 @@
|
||||
namespace lai_transfer.Tool.Attributes
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
public class DescriptionAttribute : Attribute
|
||||
{
|
||||
public string Description { get; set; }
|
||||
|
||||
public DescriptionAttribute(string description)
|
||||
{
|
||||
Description = description;
|
||||
}
|
||||
}
|
||||
}
|
||||
17
src/Tool/Attributes/NotEmptyAttribute.cs
Normal file
17
src/Tool/Attributes/NotEmptyAttribute.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace lai_transfer.Tool.Attributes
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class NotEmptyAttribute : ValidationAttribute
|
||||
{
|
||||
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
|
||||
{
|
||||
if (value is string str && string.IsNullOrWhiteSpace(str))
|
||||
{
|
||||
return new ValidationResult(ErrorMessage ?? "字段不能为空");
|
||||
}
|
||||
return ValidationResult.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
13
src/Tool/Attributes/ResultAttribute.cs
Normal file
13
src/Tool/Attributes/ResultAttribute.cs
Normal file
@ -0,0 +1,13 @@
|
||||
namespace lai_transfer.Tool.Attributes
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
public class ResultAttribute : Attribute
|
||||
{
|
||||
public string Result { get; set; }
|
||||
|
||||
public ResultAttribute(string result)
|
||||
{
|
||||
Result = result;
|
||||
}
|
||||
}
|
||||
}
|
||||
52
src/Tool/Extensions/BeijingTimeExtension.cs
Normal file
52
src/Tool/Extensions/BeijingTimeExtension.cs
Normal file
@ -0,0 +1,52 @@
|
||||
namespace lai_transfer.Tool.Extensions
|
||||
{
|
||||
public class BeijingTimeExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取北京时间,将时区转换为北京时间
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static DateTime GetBeijingTime()
|
||||
{
|
||||
return TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow,
|
||||
TimeZoneInfo.FindSystemTimeZoneById("China Standard Time"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 智能转换时间为北京时间
|
||||
/// 如果是UTC时间则转换,否则直接返回
|
||||
/// </summary>
|
||||
/// <param name="dateTime">输入的时间</param>
|
||||
/// <returns>北京时间</returns>
|
||||
public static DateTime TransferUtcToBeijingTime(DateTime dateTime)
|
||||
{
|
||||
// 只有UTC时间才需要转换
|
||||
if (dateTime.Kind == DateTimeKind.Utc)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 优先使用系统时区信息
|
||||
return TimeZoneInfo.ConvertTimeFromUtc(dateTime,
|
||||
TimeZoneInfo.FindSystemTimeZoneById("China Standard Time"));
|
||||
}
|
||||
catch (TimeZoneNotFoundException)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Linux系统可能使用这个ID
|
||||
return TimeZoneInfo.ConvertTimeFromUtc(dateTime,
|
||||
TimeZoneInfo.FindSystemTimeZoneById("Asia/Shanghai"));
|
||||
}
|
||||
catch (TimeZoneNotFoundException)
|
||||
{
|
||||
// 找不到时区就手动加8小时
|
||||
return dateTime.AddHours(8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 非UTC时间直接返回
|
||||
return dateTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
61
src/Tool/Extensions/ConvertExtension.cs
Normal file
61
src/Tool/Extensions/ConvertExtension.cs
Normal file
@ -0,0 +1,61 @@
|
||||
namespace lai_transfer.Tool.Extensions
|
||||
{
|
||||
public class ConvertExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 将字符串转换为long,默认或者是转换错误返回0
|
||||
/// </summary>
|
||||
/// <param name="str"></param>
|
||||
/// <returns></returns>
|
||||
public static long ObjectToLong(object obj)
|
||||
{
|
||||
if (obj == null)
|
||||
return 0;
|
||||
|
||||
if (obj is long longValue)
|
||||
return longValue;
|
||||
|
||||
if (obj is int intValue)
|
||||
return intValue;
|
||||
|
||||
if (obj is string strValue)
|
||||
{
|
||||
if (long.TryParse(strValue, out long result))
|
||||
return result;
|
||||
}
|
||||
|
||||
// 处理其他数值类型
|
||||
if (obj is IConvertible convertible)
|
||||
{
|
||||
try
|
||||
{
|
||||
return convertible.ToInt64(System.Globalization.CultureInfo.InvariantCulture);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 转换失败,返回0
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return 0; // 默认返回0
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将字符串转换为int,默认或者是转换错误返回0
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
public static int ConvertStringToIntOrDefault(string input)
|
||||
{
|
||||
if (int.TryParse(input, out int result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
73
src/Tool/Extensions/EnumExtensions.cs
Normal file
73
src/Tool/Extensions/EnumExtensions.cs
Normal file
@ -0,0 +1,73 @@
|
||||
using lai_transfer.Tool.Attributes;
|
||||
|
||||
namespace lai_transfer.Tool.Extensions
|
||||
{
|
||||
public static class EnumExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 判断是否为有效的权限类型
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsValidPermissionType(Type enumType, object value)
|
||||
{
|
||||
if (enumType == null || !enumType.IsEnum)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (value == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 处理整数类型
|
||||
if (value is int intValue)
|
||||
{
|
||||
return Enum.IsDefined(enumType, intValue);
|
||||
}
|
||||
|
||||
// 处理字符串类型
|
||||
if (value is string stringValue)
|
||||
{
|
||||
return Enum.TryParse(enumType, stringValue, true, out _);
|
||||
}
|
||||
|
||||
// 如果不是整数或字符串,尝试转换为枚举底层类型
|
||||
try
|
||||
{
|
||||
var underlyingType = Enum.GetUnderlyingType(enumType);
|
||||
var convertedValue = Convert.ChangeType(value, underlyingType);
|
||||
return Enum.IsDefined(enumType, convertedValue);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取对应的枚举的描述
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetDescription(this Enum value)
|
||||
{
|
||||
var field = value.GetType().GetField(value.ToString());
|
||||
var attribute = Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute)) as DescriptionAttribute;
|
||||
return attribute == null ? value.ToString() : attribute.Description;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取对应的枚举的结果
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetResult(this Enum value)
|
||||
{
|
||||
var field = value.GetType().GetField(value.ToString());
|
||||
var attribute = Attribute.GetCustomAttribute(field, typeof(ResultAttribute)) as ResultAttribute;
|
||||
return attribute == null ? value.ToString() : attribute.Result;
|
||||
}
|
||||
}
|
||||
}
|
||||
107
src/Tool/Extensions/JsonConfigReader.cs
Normal file
107
src/Tool/Extensions/JsonConfigReader.cs
Normal file
@ -0,0 +1,107 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
|
||||
namespace lai_transfer.Tool.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 通用JSON配置文件读取器
|
||||
/// </summary>
|
||||
public class JsonConfigReader(string filePath)
|
||||
{
|
||||
private readonly string _filePath = filePath ?? throw new ArgumentNullException(nameof(filePath));
|
||||
private JsonNode? _rootNode;
|
||||
private DateTime _lastReadTime = DateTime.MinValue;
|
||||
private readonly TimeSpan _cacheTimeout = TimeSpan.FromMinutes(5);
|
||||
|
||||
/// <summary>
|
||||
/// 读取整个JSON文件并返回根节点
|
||||
/// </summary>
|
||||
public JsonNode? GetRootNode(bool forceReload = false)
|
||||
{
|
||||
// 如果需要强制重新加载,或者缓存过期,或者尚未加载
|
||||
if (forceReload || _rootNode == null || DateTime.Now - _lastReadTime > _cacheTimeout)
|
||||
{
|
||||
string fullPath = Path.Combine(Directory.GetCurrentDirectory(), _filePath);
|
||||
if (File.Exists(fullPath))
|
||||
{
|
||||
string jsonContent = File.ReadAllText(_filePath);
|
||||
_rootNode = JsonNode.Parse(jsonContent);
|
||||
_lastReadTime = DateTime.Now;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new FileNotFoundException($"配置文件未找到: {_filePath}");
|
||||
}
|
||||
}
|
||||
|
||||
return _rootNode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定路径的配置节点
|
||||
/// </summary>
|
||||
/// <param name="path">使用点分隔的路径,例如"Origin.BaseUrl"</param>
|
||||
public JsonNode? GetSection(string path, bool forceReload = false)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path))
|
||||
return GetRootNode(forceReload);
|
||||
|
||||
var root = GetRootNode(forceReload);
|
||||
if (root == null)
|
||||
return null;
|
||||
|
||||
var pathSegments = path.Split('.');
|
||||
JsonNode? current = root;
|
||||
|
||||
foreach (var segment in pathSegments)
|
||||
{
|
||||
current = current?[segment];
|
||||
if (current == null)
|
||||
return null;
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将指定路径的节点转换为指定类型
|
||||
/// </summary>
|
||||
/// <typeparam name="T">目标类型</typeparam>
|
||||
/// <param name="path">使用点分隔的路径,例如"Origin"</param>
|
||||
public T? GetValue<T>(string path, bool forceReload = false)
|
||||
{
|
||||
var section = GetSection(path, forceReload);
|
||||
if (section == null)
|
||||
return default;
|
||||
|
||||
return section.Deserialize<T>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定路径的字符串值
|
||||
/// </summary>
|
||||
public string? GetString(string path, bool forceReload = false)
|
||||
{
|
||||
var section = GetSection(path, forceReload);
|
||||
return section?.GetValue<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定路径的整数值
|
||||
/// </summary>
|
||||
public int? GetInt(string path, bool forceReload = false)
|
||||
{
|
||||
var section = GetSection(path, forceReload);
|
||||
return section?.GetValue<int>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定路径的布尔值
|
||||
/// </summary>
|
||||
public bool? GetBool(string path, bool forceReload = false)
|
||||
{
|
||||
var section = GetSection(path, forceReload);
|
||||
return section?.GetValue<bool>();
|
||||
}
|
||||
}
|
||||
}
|
||||
8
src/appsettings.Development.json
Normal file
8
src/appsettings.Development.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
9
src/appsettings.json
Normal file
9
src/appsettings.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
14
src/docker_release.sh
Normal file
14
src/docker_release.sh
Normal file
@ -0,0 +1,14 @@
|
||||
#!/bin/bash
|
||||
VERSION=$1 # 如 1.0.0
|
||||
|
||||
# 构建镜像
|
||||
docker build -t yuzhile/lai_transfer:latest .
|
||||
|
||||
# 标记多个版本
|
||||
docker tag yuzhile/lai_transfer:latest yuzhile/lai_transfer:$VERSION
|
||||
|
||||
# 推送所有标记
|
||||
docker push yuzhile/lai_transfer:latest
|
||||
docker push yuzhile/lai_transfer:$VERSION
|
||||
|
||||
echo "成功发布 yuzhile/lai_transfer 版本 $VERSION"
|
||||
39
src/lai_transfer.csproj
Normal file
39
src/lai_transfer.csproj
Normal file
@ -0,0 +1,39 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<UserSecretsId>f320e299-f265-4200-aa03-9de3060d270c</UserSecretsId>
|
||||
<ContainerRegistry>docker.io</ContainerRegistry>
|
||||
<ContainerImageName>yuzhile/lai_transfer</ContainerImageName>
|
||||
<ContainerImageTag>latest</ContainerImageTag>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentValidation" Version="12.0.0" />
|
||||
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="12.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.6" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="9.0.6" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.6" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.6">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.6">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</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="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="Swashbuckle.AspNetCore" Version="8.1.4" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Common\Middleware\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
6
src/lai_transfer.http
Normal file
6
src/lai_transfer.http
Normal file
@ -0,0 +1,6 @@
|
||||
@lai_transfer_HostAddress = http://localhost:5141
|
||||
|
||||
GET {{lai_transfer_HostAddress}}/weatherforecast/
|
||||
Accept: application/json
|
||||
|
||||
###
|
||||
25
src/lai_transfer.sln
Normal file
25
src/lai_transfer.sln
Normal file
@ -0,0 +1,25 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.14.36212.18
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "lai_transfer", "lai_transfer.csproj", "{3CC32194-3C1F-4EE6-BBE0-743958C0F3AB}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{3CC32194-3C1F-4EE6-BBE0-743958C0F3AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{3CC32194-3C1F-4EE6-BBE0-743958C0F3AB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{3CC32194-3C1F-4EE6-BBE0-743958C0F3AB}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{3CC32194-3C1F-4EE6-BBE0-743958C0F3AB}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {29435341-C82F-4586-ABFE-5CA094DAC509}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
Loading…
x
Reference in New Issue
Block a user