diff --git a/src/.config/dotnet-tools.json b/src/.config/dotnet-tools.json
new file mode 100644
index 0000000..05b4dd6
--- /dev/null
+++ b/src/.config/dotnet-tools.json
@@ -0,0 +1,13 @@
+{
+ "version": 1,
+ "isRoot": true,
+ "tools": {
+ "dotnet-ef": {
+ "version": "9.0.6",
+ "commands": [
+ "dotnet-ef"
+ ],
+ "rollForward": false
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/.dockerignore b/src/.dockerignore
new file mode 100644
index 0000000..fe1152b
--- /dev/null
+++ b/src/.dockerignore
@@ -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/**
\ No newline at end of file
diff --git a/src/Common/Enums/ResponseCode.cs b/src/Common/Enums/ResponseCode.cs
new file mode 100644
index 0000000..22f428a
--- /dev/null
+++ b/src/Common/Enums/ResponseCode.cs
@@ -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
+
+}
\ No newline at end of file
diff --git a/src/Common/Extensions/ClaimsPrincipalExtensions.cs b/src/Common/Extensions/ClaimsPrincipalExtensions.cs
new file mode 100644
index 0000000..5c5ed70
--- /dev/null
+++ b/src/Common/Extensions/ClaimsPrincipalExtensions.cs
@@ -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;
+ }
+}
diff --git a/src/Common/Extensions/EndpointExtensions.cs b/src/Common/Extensions/EndpointExtensions.cs
new file mode 100644
index 0000000..de883f8
--- /dev/null
+++ b/src/Common/Extensions/EndpointExtensions.cs
@@ -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
+ }
+ };
+
+ ///
+ /// 无需授权的分组
+ ///
+ ///
+ ///
+ ///
+ public static RouteGroupBuilder MapPublicGroup(this IEndpointRouteBuilder app, string? prefix = null)
+ {
+ return app.MapGroup(prefix ?? string.Empty)
+ .AllowAnonymous();
+ }
+
+ ///
+ /// 需要权限校验的分组
+ ///
+ ///
+ ///
+ ///
+ public static RouteGroupBuilder MapAuthorizedGroup(this IEndpointRouteBuilder app, string? prefix = null)
+ {
+ return app.MapGroup(prefix ?? string.Empty)
+ .RequireAuthorization()
+ .WithOpenApi(x => new(x)
+ {
+ Security = [new() { [SecurityScheme] = [] }],
+ });
+ }
+
+ ///
+ /// 绑定一个 IEndpoint 实现到路由中
+ ///
+ ///
+ ///
+ ///
+ public static IEndpointRouteBuilder MapEndpoint(this IEndpointRouteBuilder app)
+ where TEndpoint : IEndpoint
+ {
+ TEndpoint.Map(app);
+ return app;
+ }
+ }
+}
diff --git a/src/Common/Extensions/HttpContextExtensions.cs b/src/Common/Extensions/HttpContextExtensions.cs
new file mode 100644
index 0000000..d22f83f
--- /dev/null
+++ b/src/Common/Extensions/HttpContextExtensions.cs
@@ -0,0 +1,59 @@
+using lai_transfer.Common.Results;
+
+namespace lai_transfer.Common.Extensions
+{
+
+ public static class HttpContextExtensions
+ {
+ ///
+ /// 从HttpContext中获取存储的值
+ ///
+ /// Http上下文
+ /// 要获取的键名(如"AuthToken"或"BaseUrl")
+ /// 存储的值,如果不存在则返回null
+ public static string? GetContextItem(this HttpContext httpContext, string key)
+ {
+ return httpContext.Items.TryGetValue(key, out var value) ? value?.ToString() : null;
+ }
+
+
+ ///
+ /// 获取 转发接口中的 httpContext 中的 Authorization 相关的 Token 和 BaseUrl数据
+ ///
+ ///
+ ///
+ ///
+ 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);
+ }
+
+ ///
+ /// 获取当前请求的完整路径(不包括主机和端口)
+ ///
+ /// Http上下文
+ /// 是否包含查询字符串
+ /// 完整的请求路径
+ 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;
+ }
+
+ }
+}
diff --git a/src/Common/Extensions/RouteHandlerBuilderAuthorizationExtensions.cs b/src/Common/Extensions/RouteHandlerBuilderAuthorizationExtensions.cs
new file mode 100644
index 0000000..465bcfd
--- /dev/null
+++ b/src/Common/Extensions/RouteHandlerBuilderAuthorizationExtensions.cs
@@ -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
+ {
+ ///
+ /// 添加授权头处理过滤器
+ ///
+ public static RouteHandlerBuilder WithMJAuthorizationHeader(this RouteHandlerBuilder builder)
+ {
+ return builder
+ .AddEndpointFilter()
+ .Produces(StatusCodes.Status401Unauthorized);
+ }
+ }
+}
diff --git a/src/Common/Extensions/RouteHandlerBuilderValidationExtensions.cs b/src/Common/Extensions/RouteHandlerBuilderValidationExtensions.cs
new file mode 100644
index 0000000..ed1c9a5
--- /dev/null
+++ b/src/Common/Extensions/RouteHandlerBuilderValidationExtensions.cs
@@ -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
+ {
+ ///
+ /// Adds a request validation filter to the route handler.
+ ///
+ ///
+ ///
+ /// A that can be used to futher customize the endpoint.
+ public static RouteHandlerBuilder WithRequestValidation(this RouteHandlerBuilder builder)
+ {
+ return builder
+ .AddEndpointFilter>()
+ .ProducesValidationProblem();
+ }
+
+ ///
+ /// Adds a request validation filter to the route handler to ensure a exists with the id returned by .
+ ///
+ ///
+ ///
+ ///
+ /// A function which selects the Id property from the
+ /// A that can be used to futher customize the endpoint.
+ public static RouteHandlerBuilder WithEnsureEntityExists(this RouteHandlerBuilder builder, Func idSelector) where TEntity : class, IEntity
+ {
+ return builder
+ .AddEndpointFilterFactory((endpointFilterFactoryContext, next) => async context =>
+ {
+ var db = context.HttpContext.RequestServices.GetRequiredService();
+ var filter = new EnsureEntityExistsFilter(db, idSelector);
+ return await filter.InvokeAsync(context, next);
+ })
+ .ProducesProblem(StatusCodes.Status404NotFound);
+ }
+
+ ///
+ /// Adds a request validation filter to the route handler to ensure the current owns the with the id returned by .
+ ///
+ ///
+ ///
+ ///
+ /// A function which selects the Id property from the
+ /// A that can be used to futher customize the endpoint.
+ public static RouteHandlerBuilder WithEnsureUserOwnsEntity(this RouteHandlerBuilder builder, Func idSelector) where TEntity : class, IEntity, IOwnedEntity
+ {
+ return builder
+ .AddEndpointFilterFactory((endpointFilterFactoryContext, next) => async context =>
+ {
+ var db = context.HttpContext.RequestServices.GetRequiredService();
+ var filter = new EnsureUserOwnsEntityFilter(db, idSelector);
+ return await filter.InvokeAsync(context, next);
+ })
+ .ProducesProblem(StatusCodes.Status404NotFound)
+ .Produces(StatusCodes.Status403Forbidden);
+ }
+ }
+}
diff --git a/src/Common/Extensions/ValidationRulesExtension.cs b/src/Common/Extensions/ValidationRulesExtension.cs
new file mode 100644
index 0000000..9f128ec
--- /dev/null
+++ b/src/Common/Extensions/ValidationRulesExtension.cs
@@ -0,0 +1,24 @@
+using FluentValidation;
+
+namespace lai_transfer.Common.Extensions;
+
+public static class ValidationRulesExtension
+{
+ public static IRuleBuilderOptions AuthUsernameRule(
+ this IRuleBuilderInitial rule)
+ {
+ return rule.NotEmpty().WithMessage("用户名不能为空");
+ }
+
+ public static IRuleBuilderOptions AuthPasswordRule(
+ this IRuleBuilderInitial rule)
+ {
+ return rule.NotEmpty().MinimumLength(8);
+ }
+
+ public static IRuleBuilderOptions AuthNameRule(
+ this IRuleBuilderInitial rule)
+ {
+ return rule.NotEmpty().MaximumLength(50);
+ }
+}
diff --git a/src/Common/Filters/EnsureEntityExistsFilter.cs b/src/Common/Filters/EnsureEntityExistsFilter.cs
new file mode 100644
index 0000000..bce0bce
--- /dev/null
+++ b/src/Common/Filters/EnsureEntityExistsFilter.cs
@@ -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(ApplicationDbContext database, Func idSelector) : IEndpointFilter
+ where TEntity : class, IEntity
+{
+ public async ValueTask