A:初版
This commit is contained in:
1 parent
237cbd8788
commit
ab3721912d
102 files changed
+7929
-20
No files matched your search
@@ -0,0 +1,219 @@
|
||||
using Sharp.NetServer.Attributes;
|
||||
using Sharp.NetServer.Controllers;
|
||||
using Sharp.NetServer.Models;
|
||||
using Sharp.NetServer.Abstractions;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace NetServer.Test.Examples.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// 认证控制器示例
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/auth")]
|
||||
public class AuthController : BaseController
|
||||
{
|
||||
// 模拟用户数据库
|
||||
private static readonly Dictionary<string, string> UserCredentials = new()
|
||||
{
|
||||
{ "admin", "admin123" },
|
||||
{ "user", "user123" }
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 用户登录
|
||||
/// POST /api/auth/login
|
||||
/// Body: { "username": "admin", "password": "admin123" }
|
||||
/// </summary>
|
||||
[AllowAnonymous]
|
||||
[RateLimit(10, RateLimitScope.Ip)] // 防止暴力破解
|
||||
[HttpPost("login")]
|
||||
public async Task LoginAsync(HttpContext context)
|
||||
{
|
||||
var loginData = await ReadJsonBodyAsync<Dictionary<string, string>>(context);
|
||||
|
||||
if (loginData == null ||
|
||||
!loginData.TryGetValue("username", out var username) ||
|
||||
!loginData.TryGetValue("password", out var password))
|
||||
{
|
||||
await BadRequestAsync(context, "用户名和密码不能为空");
|
||||
return;
|
||||
}
|
||||
|
||||
// 验证凭据
|
||||
if (UserCredentials.TryGetValue(username, out var correctPassword) &&
|
||||
correctPassword == password)
|
||||
{
|
||||
// 生成简单的 Token(实际应用中应使用 JWT)
|
||||
var token = GenerateSimpleToken(username);
|
||||
|
||||
// 设置 Cookie
|
||||
SetCookie(context, "auth_token", token, expiresInSeconds: 3600);
|
||||
|
||||
await WriteSuccessResponseAsync(context, new
|
||||
{
|
||||
token,
|
||||
username,
|
||||
role = username == "admin" ? "Admin" : "User",
|
||||
expiresIn = 3600
|
||||
}, "登录成功");
|
||||
}
|
||||
else
|
||||
{
|
||||
await UnauthorizedAsync(context, "用户名或密码错误");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用户登出
|
||||
/// POST /api/auth/logout
|
||||
/// </summary>
|
||||
[Authorize]
|
||||
[HttpPost("logout")]
|
||||
public async Task LogoutAsync(HttpContext context)
|
||||
{
|
||||
// 删除 Cookie
|
||||
DeleteCookie(context, "auth_token");
|
||||
|
||||
await WriteSuccessResponseAsync(context, new { }, "登出成功");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前用户信息
|
||||
/// GET /api/auth/me
|
||||
/// </summary>
|
||||
[Authorize]
|
||||
[HttpGet("me")]
|
||||
public async Task GetCurrentUserAsync(HttpContext context)
|
||||
{
|
||||
var username = context.User?.Identity?.Name ?? "Unknown";
|
||||
var roles = context.User?.Claims
|
||||
.Where(c => c.Type == ClaimTypes.Role)
|
||||
.Select(c => c.Value)
|
||||
.ToList() ?? new List<string>();
|
||||
|
||||
await WriteSuccessResponseAsync(context, new
|
||||
{
|
||||
username,
|
||||
roles,
|
||||
isAuthenticated = context.User?.Identity?.IsAuthenticated ?? false
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 修改密码
|
||||
/// POST /api/auth/change-password
|
||||
/// Body: { "oldPassword": "admin123", "newPassword": "newpass123" }
|
||||
/// </summary>
|
||||
[Authorize]
|
||||
[RateLimit(5, RateLimitScope.User)]
|
||||
[HttpPost("change-password")]
|
||||
public async Task ChangePasswordAsync(HttpContext context)
|
||||
{
|
||||
var username = context.User?.Identity?.Name;
|
||||
if (string.IsNullOrEmpty(username))
|
||||
{
|
||||
await UnauthorizedAsync(context);
|
||||
return;
|
||||
}
|
||||
|
||||
var passwordData = await ReadJsonBodyAsync<Dictionary<string, string>>(context);
|
||||
if (passwordData == null ||
|
||||
!passwordData.TryGetValue("oldPassword", out var oldPassword) ||
|
||||
!passwordData.TryGetValue("newPassword", out var newPassword))
|
||||
{
|
||||
await BadRequestAsync(context, "旧密码和新密码不能为空");
|
||||
return;
|
||||
}
|
||||
|
||||
// 验证旧密码
|
||||
if (!UserCredentials.TryGetValue(username, out var currentPassword) ||
|
||||
currentPassword != oldPassword)
|
||||
{
|
||||
await BadRequestAsync(context, "旧密码错误");
|
||||
return;
|
||||
}
|
||||
|
||||
// 更新密码
|
||||
UserCredentials[username] = newPassword;
|
||||
|
||||
await WriteSuccessResponseAsync(context, new { }, "密码修改成功");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 刷新 Token
|
||||
/// POST /api/auth/refresh
|
||||
/// </summary>
|
||||
[Authorize]
|
||||
[HttpPost("refresh")]
|
||||
public async Task RefreshTokenAsync(HttpContext context)
|
||||
{
|
||||
var username = context.User?.Identity?.Name;
|
||||
if (string.IsNullOrEmpty(username))
|
||||
{
|
||||
await UnauthorizedAsync(context);
|
||||
return;
|
||||
}
|
||||
|
||||
var newToken = GenerateSimpleToken(username);
|
||||
SetCookie(context, "auth_token", newToken, expiresInSeconds: 3600);
|
||||
|
||||
await WriteSuccessResponseAsync(context, new
|
||||
{
|
||||
token = newToken,
|
||||
expiresIn = 3600
|
||||
}, "Token 刷新成功");
|
||||
}
|
||||
|
||||
private static string GenerateSimpleToken(string username)
|
||||
{
|
||||
// 简单的 Token 生成(实际应用中应使用 JWT)
|
||||
var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
||||
var role = username == "admin" ? "Admin" : "User";
|
||||
return Convert.ToBase64String(
|
||||
System.Text.Encoding.UTF8.GetBytes($"{username}:{role}:{timestamp}")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 简单的认证提供者实现
|
||||
/// </summary>
|
||||
public class SimpleAuthenticationProvider : IAuthenticationProvider
|
||||
{
|
||||
public Task<ClaimsPrincipal?> AuthenticateAsync(string? token)
|
||||
{
|
||||
if (string.IsNullOrEmpty(token))
|
||||
return Task.FromResult<ClaimsPrincipal?>(null);
|
||||
|
||||
try
|
||||
{
|
||||
// 解析简单的 Token
|
||||
var decoded = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(token));
|
||||
var parts = decoded.Split(':');
|
||||
|
||||
if (parts.Length >= 2)
|
||||
{
|
||||
var username = parts[0];
|
||||
var role = parts[1];
|
||||
|
||||
var claims = new List<Claim>
|
||||
{
|
||||
new Claim(ClaimTypes.Name, username),
|
||||
new Claim(ClaimTypes.Role, role)
|
||||
};
|
||||
|
||||
var identity = new ClaimsIdentity(claims, "SimpleAuth");
|
||||
var principal = new ClaimsPrincipal(identity);
|
||||
|
||||
return Task.FromResult<ClaimsPrincipal?>(principal);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Token 解析失败
|
||||
}
|
||||
|
||||
return Task.FromResult<ClaimsPrincipal?>(null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
using Sharp.NetServer.Attributes;
|
||||
using Sharp.NetServer.Controllers;
|
||||
using Sharp.NetServer.Models;
|
||||
|
||||
namespace NetServer.Test.Examples.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// 产品管理控制器示例
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/products")]
|
||||
public class ProductController : BaseController
|
||||
{
|
||||
private static readonly List<Product> Products = new()
|
||||
{
|
||||
new Product { Id = 1, Name = "笔记本电脑", Price = 5999.00m, Stock = 50, Category = "电子产品" },
|
||||
new Product { Id = 2, Name = "无线鼠标", Price = 99.00m, Stock = 200, Category = "电子产品" },
|
||||
new Product { Id = 3, Name = "机械键盘", Price = 399.00m, Stock = 100, Category = "电子产品" },
|
||||
new Product { Id = 4, Name = "显示器", Price = 1299.00m, Stock = 30, Category = "电子产品" },
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有产品(公开接口,带缓存和限流)
|
||||
/// GET /api/products
|
||||
/// </summary>
|
||||
[AllowAnonymous]
|
||||
[RateLimit(200, RateLimitScope.Ip)]
|
||||
[ResponseCache(60, VaryByQueryKeys = true)]
|
||||
[HttpGet]
|
||||
public async Task GetAllProductsAsync(HttpContext context)
|
||||
{
|
||||
var category = GetQueryParam(context, "category", "");
|
||||
var products = string.IsNullOrEmpty(category)
|
||||
? Products
|
||||
: Products.Where(p => p.Category.Equals(category, StringComparison.OrdinalIgnoreCase)).ToList();
|
||||
|
||||
await WriteSuccessResponseAsync(context, products);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取产品详情
|
||||
/// GET /api/products/1
|
||||
/// </summary>
|
||||
[AllowAnonymous]
|
||||
[ResponseCache(30)]
|
||||
[HttpGet("{id}")]
|
||||
public async Task GetProductByIdAsync(HttpContext context)
|
||||
{
|
||||
var idStr = context.Request.Url.Segments.LastOrDefault()?.TrimEnd('/');
|
||||
if (int.TryParse(idStr, out var id))
|
||||
{
|
||||
var product = Products.FirstOrDefault(p => p.Id == id);
|
||||
if (product != null)
|
||||
{
|
||||
await WriteSuccessResponseAsync(context, product);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
await NotFoundAsync(context, "产品未找到");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建产品(需要认证)
|
||||
/// POST /api/products
|
||||
/// Body: { "name": "新产品", "price": 199.00, "stock": 100, "category": "类别" }
|
||||
/// </summary>
|
||||
[Authorize(Roles = "Admin")]
|
||||
[RateLimit(20, RateLimitScope.User)]
|
||||
[HttpPost]
|
||||
public async Task CreateProductAsync(HttpContext context)
|
||||
{
|
||||
var productData = await ReadJsonBodyAsync<Product>(context);
|
||||
|
||||
if (productData == null || string.IsNullOrEmpty(productData.Name) || productData.Price <= 0)
|
||||
{
|
||||
await BadRequestAsync(context, "无效的产品数据");
|
||||
return;
|
||||
}
|
||||
|
||||
productData.Id = Products.Max(p => p.Id) + 1;
|
||||
Products.Add(productData);
|
||||
|
||||
await WriteSuccessResponseAsync(context, productData, "产品创建成功");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新库存
|
||||
/// PATCH /api/products/1/stock
|
||||
/// Body: { "quantity": 10 }
|
||||
/// </summary>
|
||||
[Authorize]
|
||||
[HttpPatch("{id}/stock")]
|
||||
public async Task UpdateStockAsync(HttpContext context)
|
||||
{
|
||||
var idStr = context.Request.Url.Segments[^2].TrimEnd('/');
|
||||
if (!int.TryParse(idStr, out var id))
|
||||
{
|
||||
await BadRequestAsync(context, "无效的产品ID");
|
||||
return;
|
||||
}
|
||||
|
||||
var product = Products.FirstOrDefault(p => p.Id == id);
|
||||
if (product == null)
|
||||
{
|
||||
await NotFoundAsync(context, "产品未找到");
|
||||
return;
|
||||
}
|
||||
|
||||
var stockUpdate = await ReadJsonBodyAsync<Dictionary<string, object>>(context);
|
||||
if (stockUpdate != null && stockUpdate.TryGetValue("quantity", out var quantityObj))
|
||||
{
|
||||
if (int.TryParse(quantityObj.ToString(), out var quantity))
|
||||
{
|
||||
product.Stock = quantity;
|
||||
await WriteSuccessResponseAsync(context, product, "库存更新成功");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
await BadRequestAsync(context, "无效的库存数量");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除产品
|
||||
/// DELETE /api/products/1
|
||||
/// </summary>
|
||||
[Authorize(Roles = "Admin")]
|
||||
[HttpDelete("{id}")]
|
||||
public async Task DeleteProductAsync(HttpContext context)
|
||||
{
|
||||
var idStr = context.Request.Url.Segments.LastOrDefault()?.TrimEnd('/');
|
||||
if (!int.TryParse(idStr, out var id))
|
||||
{
|
||||
await BadRequestAsync(context, "无效的产品ID");
|
||||
return;
|
||||
}
|
||||
|
||||
var product = Products.FirstOrDefault(p => p.Id == id);
|
||||
if (product == null)
|
||||
{
|
||||
await NotFoundAsync(context, "产品未找到");
|
||||
return;
|
||||
}
|
||||
|
||||
Products.Remove(product);
|
||||
await WriteSuccessResponseAsync(context, new { id }, "产品删除成功");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 搜索产品
|
||||
/// GET /api/products/search?q=键盘&minPrice=100&maxPrice=500
|
||||
/// </summary>
|
||||
[AllowAnonymous]
|
||||
[RateLimit(150, RateLimitScope.Ip)]
|
||||
[HttpGet("search")]
|
||||
public async Task SearchProductsAsync(HttpContext context)
|
||||
{
|
||||
var query = GetQueryParam(context, "q", "");
|
||||
var minPriceStr = GetQueryParam(context, "minPrice", "0");
|
||||
var maxPriceStr = GetQueryParam(context, "maxPrice", decimal.MaxValue.ToString());
|
||||
|
||||
decimal.TryParse(minPriceStr, out var minPrice);
|
||||
decimal.TryParse(maxPriceStr, out var maxPrice);
|
||||
|
||||
var results = Products.Where(p =>
|
||||
(string.IsNullOrEmpty(query) || p.Name.Contains(query, StringComparison.OrdinalIgnoreCase)) &&
|
||||
p.Price >= minPrice &&
|
||||
p.Price <= maxPrice
|
||||
).ToList();
|
||||
|
||||
await WriteSuccessResponseAsync(context, new
|
||||
{
|
||||
query,
|
||||
minPrice,
|
||||
maxPrice,
|
||||
count = results.Count,
|
||||
products = results
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 产品数据模型
|
||||
/// </summary>
|
||||
public class Product
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public decimal Price { get; set; }
|
||||
public int Stock { get; set; }
|
||||
public string Category { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
using Sharp.NetServer.Attributes;
|
||||
using Sharp.NetServer.Controllers;
|
||||
using Sharp.NetServer.Models;
|
||||
|
||||
namespace NetServer.Test.Examples.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// 用户管理控制器示例
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/users")]
|
||||
[Authorize] // 所有接口默认需要认证
|
||||
public class UserController : BaseController
|
||||
{
|
||||
// 模拟数据库
|
||||
private static readonly List<User> Users = new()
|
||||
{
|
||||
new User { Id = 1, Name = "张三", Email = "zhangsan@example.com", Role = "Admin" },
|
||||
new User { Id = 2, Name = "李四", Email = "lisi@example.com", Role = "User" },
|
||||
new User { Id = 3, Name = "王五", Email = "wangwu@example.com", Role = "User" }
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有用户(需要 Admin 角色)
|
||||
/// GET /api/users
|
||||
/// </summary>
|
||||
[Authorize(Roles = "Admin")]
|
||||
[RateLimit(50, RateLimitScope.User)]
|
||||
[HttpGet]
|
||||
public async Task GetAllUsersAsync(HttpContext context)
|
||||
{
|
||||
await WriteSuccessResponseAsync(context, Users);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取单个用户
|
||||
/// GET /api/users/1
|
||||
/// </summary>
|
||||
[HttpGet("{id}")]
|
||||
[ResponseCache(30)] // 缓存 30 秒
|
||||
public async Task GetUserByIdAsync(HttpContext context)
|
||||
{
|
||||
var idStr = context.Request.Url.Segments.LastOrDefault()?.TrimEnd('/');
|
||||
if (int.TryParse(idStr, out var id))
|
||||
{
|
||||
var user = Users.FirstOrDefault(u => u.Id == id);
|
||||
if (user != null)
|
||||
{
|
||||
await WriteSuccessResponseAsync(context, user);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
await NotFoundAsync(context, "用户未找到");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建用户(需要 Admin 角色)
|
||||
/// POST /api/users
|
||||
/// Body: { "name": "赵六", "email": "zhaoliu@example.com", "role": "User" }
|
||||
/// </summary>
|
||||
[Authorize(Roles = "Admin")]
|
||||
[RateLimit(10, RateLimitScope.User)]
|
||||
[HttpPost]
|
||||
public async Task CreateUserAsync(HttpContext context)
|
||||
{
|
||||
var userData = await ReadJsonBodyAsync<User>(context);
|
||||
|
||||
if (userData == null || string.IsNullOrEmpty(userData.Name))
|
||||
{
|
||||
await BadRequestAsync(context, "无效的用户数据");
|
||||
return;
|
||||
}
|
||||
|
||||
userData.Id = Users.Max(u => u.Id) + 1;
|
||||
Users.Add(userData);
|
||||
|
||||
await WriteSuccessResponseAsync(context, userData, "用户创建成功");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新用户
|
||||
/// PUT /api/users/1
|
||||
/// Body: { "name": "新名字", "email": "newemail@example.com" }
|
||||
/// </summary>
|
||||
[HttpPut("{id}")]
|
||||
public async Task UpdateUserAsync(HttpContext context)
|
||||
{
|
||||
var idStr = context.Request.Url.Segments.LastOrDefault()?.TrimEnd('/');
|
||||
if (!int.TryParse(idStr, out var id))
|
||||
{
|
||||
await BadRequestAsync(context, "无效的用户ID");
|
||||
return;
|
||||
}
|
||||
|
||||
var user = Users.FirstOrDefault(u => u.Id == id);
|
||||
if (user == null)
|
||||
{
|
||||
await NotFoundAsync(context, "用户未找到");
|
||||
return;
|
||||
}
|
||||
|
||||
var updateData = await ReadJsonBodyAsync<User>(context);
|
||||
if (updateData != null)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(updateData.Name))
|
||||
user.Name = updateData.Name;
|
||||
if (!string.IsNullOrEmpty(updateData.Email))
|
||||
user.Email = updateData.Email;
|
||||
if (!string.IsNullOrEmpty(updateData.Role))
|
||||
user.Role = updateData.Role;
|
||||
}
|
||||
|
||||
await WriteSuccessResponseAsync(context, user, "用户更新成功");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除用户(需要 Admin 角色)
|
||||
/// DELETE /api/users/1
|
||||
/// </summary>
|
||||
[Authorize(Roles = "Admin")]
|
||||
[HttpDelete("{id}")]
|
||||
public async Task DeleteUserAsync(HttpContext context)
|
||||
{
|
||||
var idStr = context.Request.Url.Segments.LastOrDefault()?.TrimEnd('/');
|
||||
if (!int.TryParse(idStr, out var id))
|
||||
{
|
||||
await BadRequestAsync(context, "无效的用户ID");
|
||||
return;
|
||||
}
|
||||
|
||||
var user = Users.FirstOrDefault(u => u.Id == id);
|
||||
if (user == null)
|
||||
{
|
||||
await NotFoundAsync(context, "用户未找到");
|
||||
return;
|
||||
}
|
||||
|
||||
Users.Remove(user);
|
||||
await WriteSuccessResponseAsync(context, new { id }, "用户删除成功");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 搜索用户(允许匿名访问)
|
||||
/// GET /api/users/search?keyword=张
|
||||
/// </summary>
|
||||
[AllowAnonymous]
|
||||
[RateLimit(100, RateLimitScope.Ip)]
|
||||
[HttpGet("search")]
|
||||
public async Task SearchUsersAsync(HttpContext context)
|
||||
{
|
||||
var keyword = GetQueryParam(context, "keyword", "");
|
||||
var results = Users.Where(u =>
|
||||
u.Name.Contains(keyword, StringComparison.OrdinalIgnoreCase) ||
|
||||
u.Email.Contains(keyword, StringComparison.OrdinalIgnoreCase)
|
||||
).ToList();
|
||||
|
||||
await WriteSuccessResponseAsync(context, results);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用户数据模型
|
||||
/// </summary>
|
||||
public class User
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string Email { get; set; } = string.Empty;
|
||||
public string Role { get; set; } = "User";
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
using Sharp.NetServer;
|
||||
using NetServer.Test.Examples.Controllers;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace NetServer.Test.Examples;
|
||||
|
||||
/// <summary>
|
||||
/// 完整的使用示例程序
|
||||
/// 演示如何使用 Sharp.NetServer 框架构建 RESTful API
|
||||
/// </summary>
|
||||
public class ExampleProgram
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
Console.WriteLine("=============================================");
|
||||
Console.WriteLine(" Sharp.NetServer 完整示例");
|
||||
Console.WriteLine("=============================================");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("这个示例展示了一个完整的 RESTful API 服务器");
|
||||
Console.WriteLine("包含用户管理、产品管理和认证功能");
|
||||
Console.WriteLine();
|
||||
|
||||
// 创建服务器构建器
|
||||
var builder = new HttpServerBuilder();
|
||||
|
||||
// 配置监听地址
|
||||
var port = args.Length > 0 && int.TryParse(args[0], out var p) ? p : 5000;
|
||||
builder.UseUrls($"http://localhost:{port}/");
|
||||
|
||||
// 添加控制器(从当前程序集)
|
||||
builder.AddControllers(typeof(ExampleProgram).Assembly);
|
||||
|
||||
// 配置 CORS - 允许跨域访问
|
||||
builder.UseCors(options =>
|
||||
{
|
||||
options.AllowAnyOrigin = true; // 生产环境应限制来源
|
||||
options.AllowedMethods = new[] { "GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS" };
|
||||
options.AllowedHeaders = new[] { "Content-Type", "Authorization", "X-Requested-With" };
|
||||
options.AllowCredentials = false; // AllowAnyOrigin=true 时必须为 false
|
||||
});
|
||||
|
||||
// 启用认证和授权
|
||||
builder.UseAuthentication();
|
||||
builder.UseAuthorization();
|
||||
|
||||
// 配置服务 - 注册认证提供者
|
||||
builder.ConfigureServices(services =>
|
||||
{
|
||||
services.AddSingleton<Sharp.NetServer.Abstractions.IAuthenticationProvider, SimpleAuthenticationProvider>();
|
||||
});
|
||||
|
||||
// 启用限流 - 防止滥用
|
||||
builder.UseRateLimit();
|
||||
|
||||
// 启用响应缓存 - 提高性能
|
||||
builder.UseResponseCache();
|
||||
|
||||
// 配置日志
|
||||
builder.ConfigureLogging(services =>
|
||||
{
|
||||
services.AddLogging(logging =>
|
||||
{
|
||||
logging.AddConsole();
|
||||
logging.SetMinimumLevel(LogLevel.Information);
|
||||
logging.AddFilter("Microsoft", LogLevel.Warning); // 减少 Microsoft 库的日志
|
||||
});
|
||||
});
|
||||
|
||||
// 添加自定义中间件 - 请求计时
|
||||
builder.UseMiddleware(next => async context =>
|
||||
{
|
||||
var startTime = DateTime.UtcNow;
|
||||
var path = context.Request.RawUrl;
|
||||
|
||||
await next(context);
|
||||
|
||||
var duration = (DateTime.UtcNow - startTime).TotalMilliseconds;
|
||||
if (duration > 100) // 慢请求警告
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
Console.WriteLine($"[SLOW] {path} took {duration:F2}ms");
|
||||
Console.ResetColor();
|
||||
}
|
||||
});
|
||||
|
||||
// 构建服务器
|
||||
var server = builder.Build();
|
||||
|
||||
Console.WriteLine("正在启动服务器...");
|
||||
Console.WriteLine();
|
||||
|
||||
// 在后台启动服务器
|
||||
var cts = new CancellationTokenSource();
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await server.StartAsync(cts.Token);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.WriteLine($"服务器错误: {ex.Message}");
|
||||
Console.ResetColor();
|
||||
}
|
||||
}, cts.Token);
|
||||
|
||||
// 等待服务器启动
|
||||
Thread.Sleep(1000);
|
||||
|
||||
Console.ForegroundColor = ConsoleColor.Green;
|
||||
Console.WriteLine("✓ 服务器启动成功!");
|
||||
Console.ResetColor();
|
||||
Console.WriteLine();
|
||||
Console.WriteLine($"监听地址: http://localhost:{port}/");
|
||||
Console.WriteLine();
|
||||
|
||||
PrintApiEndpoints(port);
|
||||
PrintExampleRequests(port);
|
||||
|
||||
Console.WriteLine();
|
||||
Console.ForegroundColor = ConsoleColor.Cyan;
|
||||
Console.WriteLine("按任意键停止服务器...");
|
||||
Console.ResetColor();
|
||||
Console.ReadKey();
|
||||
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("正在停止服务器...");
|
||||
cts.Cancel();
|
||||
server.Stop();
|
||||
|
||||
Console.ForegroundColor = ConsoleColor.Green;
|
||||
Console.WriteLine("✓ 服务器已停止");
|
||||
Console.ResetColor();
|
||||
}
|
||||
|
||||
private static void PrintApiEndpoints(int port)
|
||||
{
|
||||
Console.WriteLine("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
|
||||
Console.WriteLine("可用的 API 端点:");
|
||||
Console.WriteLine("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
|
||||
Console.WriteLine();
|
||||
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
Console.WriteLine("【认证接口】");
|
||||
Console.ResetColor();
|
||||
Console.WriteLine($" POST http://localhost:{port}/api/auth/login");
|
||||
Console.WriteLine($" POST http://localhost:{port}/api/auth/logout");
|
||||
Console.WriteLine($" GET http://localhost:{port}/api/auth/me");
|
||||
Console.WriteLine($" POST http://localhost:{port}/api/auth/change-password");
|
||||
Console.WriteLine();
|
||||
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
Console.WriteLine("【用户管理】");
|
||||
Console.ResetColor();
|
||||
Console.WriteLine($" GET http://localhost:{port}/api/users");
|
||||
Console.WriteLine($" GET http://localhost:{port}/api/users/{{id}}");
|
||||
Console.WriteLine($" POST http://localhost:{port}/api/users");
|
||||
Console.WriteLine($" PUT http://localhost:{port}/api/users/{{id}}");
|
||||
Console.WriteLine($" DELETE http://localhost:{port}/api/users/{{id}}");
|
||||
Console.WriteLine($" GET http://localhost:{port}/api/users/search?keyword=张");
|
||||
Console.WriteLine();
|
||||
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
Console.WriteLine("【产品管理】");
|
||||
Console.ResetColor();
|
||||
Console.WriteLine($" GET http://localhost:{port}/api/products");
|
||||
Console.WriteLine($" GET http://localhost:{port}/api/products/{{id}}");
|
||||
Console.WriteLine($" POST http://localhost:{port}/api/products");
|
||||
Console.WriteLine($" PATCH http://localhost:{port}/api/products/{{id}}/stock");
|
||||
Console.WriteLine($" DELETE http://localhost:{port}/api/products/{{id}}");
|
||||
Console.WriteLine($" GET http://localhost:{port}/api/products/search?q=键盘");
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
private static void PrintExampleRequests(int port)
|
||||
{
|
||||
Console.WriteLine("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
|
||||
Console.WriteLine("示例请求(可以使用 curl 或 Postman 测试):");
|
||||
Console.WriteLine("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
|
||||
Console.WriteLine();
|
||||
|
||||
Console.ForegroundColor = ConsoleColor.Cyan;
|
||||
Console.WriteLine("1. 用户登录(获取 Token):");
|
||||
Console.ResetColor();
|
||||
Console.WriteLine($" curl -X POST http://localhost:{port}/api/auth/login \\");
|
||||
Console.WriteLine($" -H \"Content-Type: application/json\" \\");
|
||||
Console.WriteLine($" -d '{{\"username\":\"admin\",\"password\":\"admin123\"}}'");
|
||||
Console.WriteLine();
|
||||
|
||||
Console.ForegroundColor = ConsoleColor.Cyan;
|
||||
Console.WriteLine("2. 获取所有产品(无需认证):");
|
||||
Console.ResetColor();
|
||||
Console.WriteLine($" curl http://localhost:{port}/api/products");
|
||||
Console.WriteLine();
|
||||
|
||||
Console.ForegroundColor = ConsoleColor.Cyan;
|
||||
Console.WriteLine("3. 搜索用户(无需认证):");
|
||||
Console.ResetColor();
|
||||
Console.WriteLine($" curl http://localhost:{port}/api/users/search?keyword=张");
|
||||
Console.WriteLine();
|
||||
|
||||
Console.ForegroundColor = ConsoleColor.Cyan;
|
||||
Console.WriteLine("4. 获取用户信息(需要认证):");
|
||||
Console.ResetColor();
|
||||
Console.WriteLine($" curl http://localhost:{port}/api/users/1 \\");
|
||||
Console.WriteLine($" -H \"Authorization: Bearer YOUR_TOKEN\"");
|
||||
Console.WriteLine();
|
||||
|
||||
Console.ForegroundColor = ConsoleColor.Cyan;
|
||||
Console.WriteLine("5. 创建产品(需要 Admin 角色):");
|
||||
Console.ResetColor();
|
||||
Console.WriteLine($" curl -X POST http://localhost:{port}/api/products \\");
|
||||
Console.WriteLine($" -H \"Content-Type: application/json\" \\");
|
||||
Console.WriteLine($" -H \"Authorization: Bearer YOUR_TOKEN\" \\");
|
||||
Console.WriteLine($" -d '{{\"name\":\"新产品\",\"price\":299,\"stock\":50,\"category\":\"测试\"}}'");
|
||||
Console.WriteLine();
|
||||
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
Console.WriteLine("💡 提示:");
|
||||
Console.ResetColor();
|
||||
Console.WriteLine(" - 默认管理员账号: admin / admin123");
|
||||
Console.WriteLine(" - 默认普通用户: user / user123");
|
||||
Console.WriteLine(" - Token 有效期: 1小时");
|
||||
Console.WriteLine(" - 某些接口有限流保护");
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user