using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using HealthManager.Application.DTOs.Auth; using HealthManager.Domain.Interfaces; using HealthManager.Application.Services; using HealthManager.Domain.Entities; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; namespace HealthManager.WebApi.Controllers; [ApiController] [Route("api/auth")] public class AuthController( AuthService authService, IJwtProvider jwtProvider, VerificationService verificationService, RateLimitService rateLimit, TokenBlacklistService tokenBlacklist) : ControllerBase { [HttpPost("send-sms")] public async Task SendSms([FromBody] SendSmsRequest request) { if (!await rateLimit.CheckAsync($"sms:{request.Phone}", 1, 60)) return StatusCode(429, new { message = "发送过于频繁,请60秒后重试" }); var code = await verificationService.GenerateAsync(request.Phone, "login"); await rateLimit.IncrementAsync($"sms:{request.Phone}", 60); // Demo: log code to console since no real SMS gateway Console.WriteLine($"[SMS] Phone: {request.Phone}, Code: {code}"); return Ok(new { message = "验证码已发送" }); } [HttpPost("login")] public async Task Login([FromBody] LoginRequest request) { // Demo: skip SMS verification, accept any code var user = await authService.GetUserByPhoneAsync(request.Phone); if (user == null) { var db = HttpContext.RequestServices.GetRequiredService(); var deleted = await db.Users.IgnoreQueryFilters() .FirstOrDefaultAsync(u => u.Phone == request.Phone && u.IsDeleted); if (deleted != null) { deleted.IsDeleted = false; deleted.DeletedAt = null; deleted.UpdatedAt = DateTime.UtcNow; user = deleted; } else { user = new User { Phone = request.Phone, Name = "用户" + request.Phone[^4..], Role = "patient", PasswordHash = AuthService.HashPassword("demo123"), }; db.Users.Add(user); } await db.SaveChangesAsync(); } var accessToken = jwtProvider.GenerateAccessToken(user.Id, user.Name, user.Role); var refreshToken = jwtProvider.GenerateRefreshToken(); await authService.SaveRefreshTokenAsync(user.Id, refreshToken, DateTime.UtcNow.AddDays(7)); return Ok(new AuthResponse(user.Id, user.Name, user.Role, accessToken, refreshToken)); } [HttpPost("register")] public async Task Register([FromBody] RegisterRequest request) { var existing = await authService.GetUserByPhoneAsync(request.Phone); if (existing != null) return Conflict(new { message = "该手机号已注册" }); var user = new User { Phone = request.Phone, Name = request.Name, Role = "patient", PasswordHash = AuthService.HashPassword("demo123"), }; var db = HttpContext.RequestServices.GetRequiredService(); db.Users.Add(user); await db.SaveChangesAsync(); var accessToken = jwtProvider.GenerateAccessToken(user.Id, user.Name, user.Role); var refreshToken = jwtProvider.GenerateRefreshToken(); await authService.SaveRefreshTokenAsync(user.Id, refreshToken, DateTime.UtcNow.AddDays(7)); return Ok(new AuthResponse(user.Id, user.Name, user.Role, accessToken, refreshToken)); } [HttpPost("logout")] [Authorize] public async Task Logout() { var userId = Guid.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)!); var jti = User.FindFirstValue(JwtRegisteredClaimNames.Jti); var expClaim = User.FindFirstValue(JwtRegisteredClaimNames.Exp); if (!string.IsNullOrEmpty(jti) && !string.IsNullOrEmpty(expClaim)) { var exp = DateTimeOffset.FromUnixTimeSeconds(long.Parse(expClaim)).UtcDateTime; await tokenBlacklist.AddAsync(jti, userId, exp); } await authService.RevokeRefreshTokenAsync(userId); return Ok(new { message = "已登出" }); } [HttpPost("refresh")] public async Task RefreshToken([FromBody] RefreshTokenRequest request) { var saved = await authService.GetRefreshTokenAsync(request.RefreshToken); if (saved == null) return Unauthorized(new { message = "无效的刷新令牌" }); await authService.RevokeRefreshTokenAsync(saved.UserId); var accessToken = jwtProvider.GenerateAccessToken(saved.User.Id, saved.User.Name, saved.User.Role); var refreshToken = jwtProvider.GenerateRefreshToken(); await authService.SaveRefreshTokenAsync(saved.UserId, refreshToken, DateTime.UtcNow.AddDays(7)); return Ok(new AuthResponse(saved.User.Id, saved.User.Name, saved.User.Role, accessToken, refreshToken)); } [HttpGet("me")] [Authorize] public async Task GetProfile() { var userId = Guid.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)!); var db = HttpContext.RequestServices.GetRequiredService(); var user = await db.Users.FindAsync(userId); if (user == null) return NotFound(); return Ok(new UserProfileResponse( user.Id, user.Name, user.Phone, user.Role, user.Gender, user.Birthday, user.HeightCm, user.WeightKg, user.MedicalHistory, user.StentDate, user.StentType, user.Department, user.Title, user.Specialty, user.Introduction)); } [HttpPut("me")] [Authorize] public async Task UpdateProfile([FromBody] UpdateProfileRequest request) { var userId = Guid.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)!); var db = HttpContext.RequestServices.GetRequiredService(); var user = await db.Users.FindAsync(userId); if (user == null) return NotFound(); if (request.Name != null) user.Name = request.Name; if (request.Gender != null) user.Gender = request.Gender; if (request.Birthday.HasValue) user.Birthday = request.Birthday; if (request.HeightCm.HasValue) user.HeightCm = request.HeightCm; if (request.WeightKg.HasValue) user.WeightKg = request.WeightKg; if (request.MedicalHistory != null) user.MedicalHistory = request.MedicalHistory; if (request.StentDate.HasValue) user.StentDate = request.StentDate; if (request.StentType != null) user.StentType = request.StentType; if (request.Department != null) user.Department = request.Department; if (request.Title != null) user.Title = request.Title; if (request.Introduction != null) user.Introduction = request.Introduction; if (request.Specialty != null) user.Specialty = request.Specialty; user.UpdatedAt = DateTime.UtcNow; await db.SaveChangesAsync(); return Ok(new { message = "更新成功" }); } } public record RefreshTokenRequest(string RefreshToken);