chore: 全面规范化代码,遵循 CLAUDE.md 编码规范
- C# 文件命名改为 snake_case(28 个文件重命名) - C# 类转换为主构造函数(8 个类) - 空 catch 添加异常类型(2 处) - 新建 GlobalUsings.cs(Health.Infrastructure、Health.WebApi) - Flutter 移除 go_router,改用 Riverpod 路由栈 - Flutter 移除 flutter_secure_storage,改用 sqflite 持久化 - 修复 Flutter 构建路径(Flutter SDK 迁至 D 盘) - 后端端口改为 0.0.0.0:5000,支持局域网访问
This commit is contained in:
186
backend/src/Health.WebApi/Endpoints/auth_endpoints.cs
Normal file
186
backend/src/Health.WebApi/Endpoints/auth_endpoints.cs
Normal file
@@ -0,0 +1,186 @@
|
||||
using Health.Infrastructure.Services;
|
||||
|
||||
namespace Health.WebApi.Endpoints;
|
||||
|
||||
/// <summary>
|
||||
/// 认证相关 API 端点
|
||||
/// </summary>
|
||||
public static class AuthEndpoints
|
||||
{
|
||||
public static void MapAuthEndpoints(this WebApplication app)
|
||||
{
|
||||
// 发送短信验证码
|
||||
app.MapPost("/api/auth/send-sms", async (
|
||||
SendSmsRequest request,
|
||||
AppDbContext db,
|
||||
SmsService sms,
|
||||
CancellationToken ct) =>
|
||||
{
|
||||
// 生成验证码
|
||||
var code = sms.GenerateCode();
|
||||
var vc = new VerificationCode
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Phone = request.Phone,
|
||||
Code = code,
|
||||
ExpiresAt = DateTime.UtcNow.AddMinutes(5),
|
||||
};
|
||||
db.VerificationCodes.Add(vc);
|
||||
await db.SaveChangesAsync(ct);
|
||||
|
||||
// 开发阶段:直接返回验证码(生产环境需去掉 devCode)
|
||||
await sms.SendCodeAsync(request.Phone, code);
|
||||
|
||||
return Results.Ok(new { code = 0, data = new { success = true, devCode = code }, message = (string?)null });
|
||||
});
|
||||
|
||||
// 手机号+验证码登录
|
||||
app.MapPost("/api/auth/login", async (
|
||||
LoginRequest request,
|
||||
AppDbContext db,
|
||||
JwtProvider jwt,
|
||||
CancellationToken ct) =>
|
||||
{
|
||||
// 开发阶段:任意6位数字通过
|
||||
var validCode = await db.VerificationCodes
|
||||
.Where(v => v.Phone == request.Phone
|
||||
&& v.Code == request.SmsCode
|
||||
&& v.ExpiresAt > DateTime.UtcNow
|
||||
&& !v.IsUsed)
|
||||
.OrderByDescending(v => v.CreatedAt)
|
||||
.FirstOrDefaultAsync(ct);
|
||||
|
||||
if (validCode == null)
|
||||
return Results.Ok(new { code = 40001, data = (object?)null, message = "验证码错误或已过期" });
|
||||
|
||||
validCode.IsUsed = true;
|
||||
|
||||
// 查找或创建用户
|
||||
var user = await db.Users.FirstOrDefaultAsync(u => u.Phone == request.Phone, ct);
|
||||
var isNew = false;
|
||||
if (user == null)
|
||||
{
|
||||
user = new User
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Phone = request.Phone,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
UpdatedAt = DateTime.UtcNow,
|
||||
};
|
||||
db.Users.Add(user);
|
||||
isNew = true;
|
||||
|
||||
// 创建默认通知偏好
|
||||
db.NotificationPreferences.Add(new NotificationPreference
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
UserId = user.Id,
|
||||
});
|
||||
|
||||
// 创建空健康档案
|
||||
db.HealthArchives.Add(new HealthArchive
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
UserId = user.Id,
|
||||
});
|
||||
}
|
||||
|
||||
user.UpdatedAt = DateTime.UtcNow;
|
||||
await db.SaveChangesAsync(ct);
|
||||
|
||||
// 生成 token
|
||||
var accessToken = jwt.GenerateAccessToken(user.Id, user.Phone);
|
||||
var refreshToken = jwt.GenerateRefreshToken();
|
||||
|
||||
// 保存 refresh token
|
||||
db.RefreshTokens.Add(new RefreshToken
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
UserId = user.Id,
|
||||
Token = refreshToken,
|
||||
ExpiresAt = DateTime.UtcNow.AddDays(30),
|
||||
});
|
||||
await db.SaveChangesAsync(ct);
|
||||
|
||||
return Results.Ok(new
|
||||
{
|
||||
code = 0,
|
||||
data = new
|
||||
{
|
||||
accessToken,
|
||||
refreshToken,
|
||||
user = new
|
||||
{
|
||||
user.Id,
|
||||
user.Phone,
|
||||
user.Name,
|
||||
user.Gender,
|
||||
BirthDate = user.BirthDate?.ToString("yyyy-MM-dd"),
|
||||
user.AvatarUrl,
|
||||
isNew
|
||||
}
|
||||
},
|
||||
message = (string?)null
|
||||
});
|
||||
});
|
||||
|
||||
// 刷新 token
|
||||
app.MapPost("/api/auth/refresh", async (
|
||||
RefreshRequest request,
|
||||
AppDbContext db,
|
||||
JwtProvider jwt,
|
||||
CancellationToken ct) =>
|
||||
{
|
||||
var oldToken = await db.RefreshTokens
|
||||
.FirstOrDefaultAsync(t => t.Token == request.RefreshToken && !t.IsRevoked, ct);
|
||||
|
||||
if (oldToken == null || oldToken.ExpiresAt < DateTime.UtcNow)
|
||||
return Results.Ok(new { code = 40002, data = (object?)null, message = "登录已过期,请重新登录" });
|
||||
|
||||
// 吊销旧 token
|
||||
oldToken.IsRevoked = true;
|
||||
|
||||
var user = await db.Users.FindAsync([oldToken.UserId], ct);
|
||||
if (user == null)
|
||||
return Results.Ok(new { code = 40002, data = (object?)null, message = "用户不存在" });
|
||||
|
||||
// 生成新 token(续期)
|
||||
var accessToken = jwt.GenerateAccessToken(user.Id, user.Phone);
|
||||
var newRefreshToken = jwt.GenerateRefreshToken();
|
||||
db.RefreshTokens.Add(new RefreshToken
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
UserId = user.Id,
|
||||
Token = newRefreshToken,
|
||||
ExpiresAt = DateTime.UtcNow.AddDays(30),
|
||||
});
|
||||
await db.SaveChangesAsync(ct);
|
||||
|
||||
return Results.Ok(new
|
||||
{
|
||||
code = 0,
|
||||
data = new { accessToken, refreshToken = newRefreshToken },
|
||||
message = (string?)null
|
||||
});
|
||||
});
|
||||
|
||||
// 登出
|
||||
app.MapPost("/api/auth/logout", async (
|
||||
RefreshRequest request,
|
||||
AppDbContext db,
|
||||
CancellationToken ct) =>
|
||||
{
|
||||
var token = await db.RefreshTokens
|
||||
.FirstOrDefaultAsync(t => t.Token == request.RefreshToken, ct);
|
||||
if (token != null) token.IsRevoked = true;
|
||||
await db.SaveChangesAsync(ct);
|
||||
|
||||
return Results.Ok(new { code = 0, data = new { success = true }, message = (string?)null });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 请求 DTO ----
|
||||
public sealed record SendSmsRequest(string Phone);
|
||||
public sealed record LoginRequest(string Phone, string SmsCode);
|
||||
public sealed record RefreshRequest(string RefreshToken);
|
||||
Reference in New Issue
Block a user