This commit is contained in:
jimmy committed 2025-11-14 16:56:57 +08:00
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(" - 某些接口有限流保护");
}
}