using FluentValidation; // <--- 关键!这是现在唯一需要的 using using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.EntityFrameworkCore; using Microsoft.IdentityModel.Tokens; using SimpleTodoApiWithPg.Data; using SimpleTodoApiWithPg.Endpoints; using SimpleTodoApiWithPg.UserService; using System.Security.Cryptography.X509Certificates; var builder = WebApplication.CreateBuilder(args); builder.Logging.AddConsole(); //日志服务 builder.Services.AddScoped(); //注册用户服务 // 1. 添加OpenAPI/Swagger生成器服务到容器 builder.Services.AddEndpointsApiExplorer(); // 这个服务负责发现和描述API端点 builder.Services.AddSwaggerGen(); // 这个服务负责根据端点描述生成OpenAPI JSON文档 // 1. 扫描并注册你项目里的所有验证器 builder.Services.AddValidatorsFromAssemblyContaining(includeInternalTypes: true); // 注册所有 validator(按包含类型或指定程序集) var certPath = builder.Configuration["Jwt:CertificatePath"]; var certPassword = builder.Configuration["Jwt:CertificatePassword"]; var certificate = X509CertificateLoader.LoadPkcs12FromFile(certPath!, certPassword); builder.Services.AddAuthentication(options => { options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer = builder.Configuration["Jwt:Issuer"], ValidAudience = builder.Configuration["Jwt:Audience"], // 使用证书的公钥进行验证 IssuerSigningKey = new ECDsaSecurityKey(certificate.GetECDsaPublicKey()!), // 确保指定正确的签名算法 // 对于 ECDSA P-256,应该是 "ES256" ValidAlgorithms = [SecurityAlgorithms.EcdsaSha256] // 可选,增加安全性 }; // 新增:配置认证事件 options.Events = new JwtBearerEvents { // 当认证失败,需要向客户端发起质询(Challenge)时触发 OnChallenge = context => { // 阻止默认的响应行为,我们将来完全自定义响应 context.HandleResponse(); context.Response.StatusCode = 401; context.Response.ContentType = "application/json"; // 默认的错误信息 var message = "身份验证失败,请检查您的 Token。"; // 检查具体的失败原因,如果是 Token 过期,就给出更明确的提示 // AuthenticateFailure 属性包含了认证失败时的异常信息 if (context.AuthenticateFailure is SecurityTokenExpiredException) { message = "您的 Token 已过期,请重新登录或刷新 Token。"; } // 您也可以在这里记录日志,方便排查问题 // logger.LogError(context.AuthenticateFailure, "An authentication error occurred."); var response = new { success = false, message = message }; // 将自定义的响应内容写入 Response Body return context.Response.WriteAsJsonAsync(response); } }; }); builder.Services.AddAuthorization(); // === 配置 CORS === // 定义一个 CORS 策略名称 var MyAllowSpecificOrigins = "_myAllowSpecificOrigins"; // 添加 CORS 服务到依赖注入容器 builder.Services.AddCors(options => { options.AddPolicy(name: MyAllowSpecificOrigins, policy => { // 允许所有来源进行访问。 // 注意:这在生产环境中是极度不安全的,仅用于本地开发和测试。 // 在生产环境中,您应该明确指定允许的源: // policy.WithOrigins("http://localhost:8080", "https://yourfrontend.com") // 或者,如果您直接打开HTML文件,其来源是'null',您可以明确允许它: // policy.WithOrigins("null") // 在此测试场景下,这是一个选项,但AllowAnyOrigin更通用 policy.AllowAnyOrigin() // 允许任何来源的请求 .AllowAnyHeader() // 允许任何请求头 .AllowAnyMethod(); // 允许任何 HTTP 方法 (GET, POST, PUT, DELETE) }); }); // === 配置 EF Core 连接 PostgreSQL === // 从配置中获取连接字符串 var connectionString = builder.Configuration.GetConnectionString("DefaultConnection"); // 添加数据库上下文服务 builder.Services.AddDbContext(options => options.UseNpgsql(connectionString)); // 使用 Npgsql 作为 PostgreSQL 提供者 var app = builder.Build(); // 2. 在管道中启用中间件 (通常只在开发环境中启用) if (app.Environment.IsDevelopment()) { app.UseSwagger(); // 这个中间件负责提供生成的 swagger.json 文件 app.UseSwaggerUI(); // 这个中间件负责提供交互式的Swagger UI界面 } // === CORS 中间件必须放在其他中间件之前 (例如路由,授权,认证等) === // 启用 CORS app.UseCors(MyAllowSpecificOrigins); // === 应用数据库迁移 (仅在开发环境中推荐) === // 在应用启动时自动应用所有挂起的数据库迁移 using (var scope = app.Services.CreateScope()) { var dbContext = scope.ServiceProvider.GetRequiredService(); dbContext.Database.Migrate(); } // === 定义 API 端点 (Endpoints) === // GET / - 根路径,返回一个简单的消息 app.MapGet("/", () => "Hello World!"); app.MapUserEndpoints(); app.UseAuthentication(); app.UseAuthorization(); //app.MapPatch("/users", async (User user, UserService userService) => //{ // return await userService.PatchUserAsync(user); //}); //// GET /todos - 获取所有待办事项 //app.MapGet("/todos", async (AppDbContext dbContext) => //{ // var todos = await dbContext.Todos.ToListAsync(); // return TypedResults.Ok(todos); //}); //// GET /users - 获取所有用户 //app.MapGet("/users", async (AppDbContext dbContext) => //{ // var users = await dbContext.Users.ToListAsync(); // return TypedResults.Ok(users); //}); //// GET /todos/{id} - 根据 ID 获取单个待办事项 //app.MapGet("/todos/{id}", async (int id, AppDbContext dbContext) => //{ // var todo = await dbContext.Todos.FindAsync(id); // return todo is not null // ? TypedResults.Ok(todo) // : Results.NotFound($"待办事项 (ID: {id}) 不存在。"); //}); // GET /users/{id} - 根据 ID 获取单个用户 //app.MapGet("/users/{id}", async (int id, AppDbContext dbContext) => //{ // var user = await dbContext.Users.FindAsync(id); // return user is not null // ? TypedResults.Ok(user) // : Results.NotFound($"用户 (ID: {id}) 不存在。"); //}); //// POST /todos - 新增一个待办事项 //app.MapPost("/todos", async (Todo todo, AppDbContext dbContext) => //{ // // EF Core 会自动处理 ID 的生成(如果是自增主键) // dbContext.Todos.Add(todo); // await dbContext.SaveChangesAsync(); // 保存更改到数据库 // // 回传 201 Created 并提供新资源的位置 // return Results.Created($"/todos/{todo.Id}", todo); //}); //// POST /users - 新增一个用户 //app.MapPost("/users", async (User user, AppDbContext dbContext) => //{ // user.PasswordHash = RandomNumberGenerator.GetString("0123456789",22); // Console.WriteLine(user.PasswordHash); // user.PasswordHash = BCrypt.Net.BCrypt.HashPassword(user.PasswordHash,10,true); // 哈希密码 // // EF Core 会自动处理 ID 的生成(如果是自增主键) // dbContext.Users.Add(user); // await dbContext.SaveChangesAsync(); // 保存更改到数据库 // // 回传 201 Created 并提供新资源的位置 // return Results.Created($"/users/{user.Id}", user); //}); //// PUT /todos/{id} - 更新指定的待办事项 //app.MapPut("/todos/{id}", async (int id, Todo inputTodo, AppDbContext dbContext) => //{ // // 查找现有待办事项 // var existingTodo = await dbContext.Todos.FindAsync(id); // if (existingTodo is null) // { // return Results.NotFound($"待办事项 (ID: {id}) 不存在。"); // } // // 更新属性 // existingTodo.Title = inputTodo.Title; // existingTodo.IsCompleted = inputTodo.IsCompleted; // await dbContext.SaveChangesAsync(); // 保存更改到数据库 // return Results.NoContent(); // 回传 204 No Content 表示成功但无内容回传 //}); //// PUT /users/{id} - 更新指定的用户 //app.MapPut("/users/{id}", async (int id, User inputUser, AppDbContext dbContext) => //{ // // 查找现有用户 // var existingUser = await dbContext.Users.FindAsync(id); // if (existingUser is null) // { // return Results.NotFound($"用户 (ID: {id}) 不存在。"); // } // // 更新属性 // existingUser.Username = inputUser.Username; // existingUser.Email = inputUser.Email; // existingUser.PasswordHash = BCrypt.Net.BCrypt.HashPassword(inputUser.PasswordHash,10,true); // 哈希密码 // await dbContext.SaveChangesAsync(); // return Results.NoContent(); //}); //// DELETE /todos/{id} - 删除指定的待办事项 //app.MapDelete("/todos/{id}", async (int id, AppDbContext dbContext) => //{ // var todoToDelete = await dbContext.Todos.FindAsync(id); // if (todoToDelete is null) // { // return Results.NotFound($"待办事项 (ID: {id}) 不存在。"); // } // dbContext.Todos.Remove(todoToDelete); // await dbContext.SaveChangesAsync(); // 保存更改到数据库 // return TypedResults.Ok($"待办事项 (ID: {id}) 已成功删除。"); //}); //// DELETE /users/{id} - 删除指定的用户 //app.MapDelete("/users/{id}", async (int id, AppDbContext dbContext) => //{ // var userToDelete = await dbContext.Users.FindAsync(id); // if (userToDelete is null) return Results.NotFound("用户不存在"); // { // dbContext.Users.Remove(userToDelete); // await dbContext.SaveChangesAsync(); // 保存更改到数据库 // return TypedResults.Ok("用户已成功删除"); // } //}); // 运行应用程式 app.Run();