fix: align backend APIs and upload flow

This commit is contained in:
2026-09-15 14:10:55 +08:00
parent 32177a7293
commit 53e6195938
149 changed files with 4791 additions and 435 deletions
+15 -5
View File
@@ -4,6 +4,7 @@ using IdentityService.WebApi.Applications.Dtos;
using IdentityService.WebApi.Applications.Dtos.Common;
using IM.Commons;
using IM.Jwt;
using IM.InitCommon.Management;
using Microsoft.Extensions.Options;
using System.Security.Claims;
@@ -16,10 +17,11 @@ namespace IdentityService.WebApi.Applications.Auth
private readonly IOptions<JwtOptions> jwtOptions;
private readonly IdDomainService idDomainService;
private readonly IMapper mapper;
private readonly RuntimePolicy runtime;
public AuthService(ITokenService tokenService, IIdRepository idRepository,
IOptions<JwtOptions> options, IdDomainService idDomainService,
IMapper mapper
IMapper mapper, RuntimePolicy runtime
)
{
this.tokenService = tokenService;
@@ -27,6 +29,7 @@ namespace IdentityService.WebApi.Applications.Auth
jwtOptions = options;
this.idDomainService = idDomainService;
this.mapper = mapper;
this.runtime = runtime;
}
public async Task<Result<LoginResponse>> LoginAsync(string username, string password)
@@ -37,17 +40,20 @@ namespace IdentityService.WebApi.Applications.Auth
{
return Result<LoginResponse>.Fail(ResultCode.USER_NOT_FOUND);
}
if (user.Status != UserState.Normal || user.IsDeleted) return Result<LoginResponse>.Fail(ResultCode.AUTH_FAILED);
var idResult = await idRepository.CheckForSignInAsync(user, password, true);
if (!idResult.Succeeded)
{
return Result<LoginResponse>.Fail(ResultCode.PASSWORD_ERROR);
}
var token = await BuildTokenAsync(user);
var refreshToken = await tokenService.CreateRefreshTokenAsync(user.Id);
var refreshToken = await tokenService.CreateRefreshTokenAsync(user.Id, stamp: user.SecurityStamp, days: runtime.Current.ClientRefreshDays);
return Result<LoginResponse>.Success(new LoginResponse(user.Id, token, refreshToken, null, user.UserName, user.NickName,user.Avatar, user.CreationTime));
}
public async Task<Result<UserResponse?>> RegisterAsync(string userName, string password, string nickName)
{
if (!runtime.Current.RegistrationEnabled) return Result<UserResponse?>.Fail(ResultCode.PERMISSION_DENIED, "平台暂未开放注册");
if (password.Length < runtime.Current.PasswordMinLength) return Result<UserResponse?>.Fail(ResultCode.PARAMETER_ERROR, "密码不符合当前最小长度要求");
var userResult = await idDomainService.CreateUserAsync(userName, password, nickName);
if (!userResult.Succeeded)
return Result<UserResponse?>.Fail(userResult);
@@ -78,20 +84,24 @@ namespace IdentityService.WebApi.Applications.Auth
return Result<LoginResponse>.Fail(ResultCode.USER_NOT_FOUND);
}
if (user.Status != UserState.Normal || user.IsDeleted || validateRes.stamp != user.SecurityStamp) return Result<LoginResponse>.Fail(ResultCode.AUTH_FAILED);
var token = await BuildTokenAsync(user);
return Result<LoginResponse>.Success(new LoginResponse(user.Id, token, refreshToken, null, user.UserName, user.NickName, user.Avatar, user.CreationTime));
await tokenService.RevokeRefreshTokenAsync(refreshToken);
var nextRefresh = await tokenService.CreateRefreshTokenAsync(user.Id, stamp: user.SecurityStamp, days: runtime.Current.ClientRefreshDays);
return Result<LoginResponse>.Success(new LoginResponse(user.Id, token, nextRefresh, null, user.UserName, user.NickName, user.Avatar, user.CreationTime));
}
private async Task<string> BuildTokenAsync(Domain.Entities.User user)
{
var roles = await idRepository.GetRolesAsync(user);
List<Claim> claims = new List<Claim>();
claims.Add(new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()));
claims.Add(new Claim("session_stamp", user.SecurityStamp ?? ""));
foreach (string role in roles)
{
claims.Add(new Claim(ClaimTypes.Role, role));
}
return tokenService.GetToken(claims, jwtOptions.Value);
var original = jwtOptions.Value;
return tokenService.GetToken(claims, new JwtOptions { Key = original.Key, Issuer = original.Issuer, Audience = original.Audience, RefreshTokenDays = original.RefreshTokenDays, AccessTokenMinutes = runtime.Current.ClientAccessMinutes > 0 ? runtime.Current.ClientAccessMinutes : original.AccessTokenMinutes });
}
}
}
@@ -0,0 +1,38 @@
using IdentityService.Infrastructure;
using IM.InitCommon.Management;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace IdentityService.WebApi.Controllers;
[ApiController, Route("internal/management")]
public sealed class ManagementController(UserDbContext db, InternalClient client) : ControllerBase
{
[HttpGet("summary")] public async Task<object> Summary() => new { total = await db.Users.CountAsync() };
[HttpGet("access/{id:guid}")] public async Task<UserAccess> Access(Guid id) { var u = await db.Users.AsNoTracking().SingleOrDefaultAsync(x => x.Id == id); return new(u is not null && u.Status == Domain.UserState.Normal && !u.IsDeleted, u?.SecurityStamp ?? ""); }
[HttpGet("list")] public async Task<object> List(string? q, string? status, int page = 1, int size = 8)
{
page = Math.Max(1, page); size = Math.Clamp(size, 1, 100);
var query = db.Users.AsNoTracking().Where(x => q == null || x.NickName.Contains(q) || x.UserName!.Contains(q) || x.Id.ToString() == q);
if (!string.IsNullOrEmpty(status)) { var state = status == "封禁" ? Domain.UserState.Banned : status == "正常" ? Domain.UserState.Normal : Domain.UserState.Inactive; query = query.Where(x => x.Status == state); }
return new { items = await query.OrderByDescending(x => x.CreationTime).Skip((page - 1) * size).Take(size).Select(x => new { x.Id, name = x.NickName, account = x.UserName, x.Region, status = x.Status == Domain.UserState.Banned ? "封禁" : x.Status == Domain.UserState.Normal ? "正常" : "未激活", createdAt = x.CreationTime }).ToListAsync(), total = await query.CountAsync(), page, size };
}
[HttpGet("detail/{id:guid}")] public async Task<IActionResult> Detail(Guid id)
{
var u = await db.Users.AsNoTracking().SingleOrDefaultAsync(x => x.Id == id); if (u is null) return NotFound();
var groups = await client.Send<System.Text.Json.JsonElement>("group", $"/internal/management/user/{id}/groups");
return Ok(new { u.Id, name = u.NickName, account = u.UserName, u.Region, u.Description, createdAt = u.CreationTime, status = u.Status == Domain.UserState.Banned ? "封禁" : u.Status == Domain.UserState.Normal ? "正常" : "未激活", groups });
}
[HttpPost("action")] public async Task<IActionResult> Action(InternalAction command, CancellationToken ct)
{
if (command.Action is not "封禁" and not "解封") return BadRequest();
var receipt = await ReceiptStore.Execute(db, command, async () => {
var u = await db.Users.SingleOrDefaultAsync(x => x.Id == command.TargetId, ct) ?? throw new InvalidOperationException("用户不存在");
var before = u.Status == Domain.UserState.Banned ? "封禁" : "正常";
if (command.Action == "封禁") u.Ban(command.Reason); else u.Unban();
u.SecurityStamp = Guid.NewGuid().ToString("N");
return new ActionReceipt(u.NickName, before, command.Action == "封禁" ? "封禁" : "正常");
}, ct);
if (command.Action == "封禁") await client.Send<object>("connector", $"/internal/management/disconnect/{command.TargetId}", new { }, ct);
return Ok(receipt);
}
}
+4 -1
View File
@@ -1,4 +1,5 @@
using IM.InitCommon.Management;
using IdentityService.Domain.Entities;
using IdentityService.Infrastructure;
using IM.InitCommon;
@@ -38,6 +39,7 @@ namespace IdentityService.WebApi
builder.Services.AddSwaggerGen();
var app = builder.Build();
if (app.ApplyMigrationsIfRequested(args)) return;
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
@@ -47,6 +49,7 @@ namespace IdentityService.WebApi
}
app.UseAppDefault();
app.MapManagementHealth();
app.MapControllers();
app.MapAllGrpcServer();