取消新增/Excel 导入档案时自动创建账号。

取消修改人员档案时同步登录账号。
Excel 人员模板恢复为不含“初始密码”。
登录页新增“学生首次登录?自助激活账号”入口。
公开激活地址:http://127.0.0.1:5255/activate
This commit is contained in:
2026-07-25 07:55:49 +08:00 Unverified
parent 27c3944c28
commit 30c15f89e5
11 changed files with 434 additions and 287 deletions
@@ -1,144 +0,0 @@
using Jiaowu.Api.Domain.Academic;
using Jiaowu.Api.Domain.Identity;
using Jiaowu.Api.Infrastructure.Persistence;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
namespace Jiaowu.Api.Infrastructure.Auth;
public sealed class PersonnelAccountService(
AppDbContext db,
UserManager<ApplicationUser> userManager)
{
public Task<PersonnelAccountResult> EnsureTeacherAccountAsync(
Teacher teacher,
string? initialPassword,
CancellationToken cancellationToken) =>
EnsureAccountAsync(
teacher.Id,
teacher.UserId,
teacher.TeacherNumber,
teacher.Name,
teacher.CollegeId,
teacher.Status == TeacherStatus.Active,
SystemRoles.Teacher,
initialPassword,
userId => teacher.UserId = userId,
cancellationToken);
public async Task<PersonnelAccountResult> EnsureStudentAccountAsync(
Student student,
string? initialPassword,
CancellationToken cancellationToken)
{
var collegeId = await db.AdministrativeClasses.AsNoTracking()
.Where(x => x.Id == student.AdministrativeClassId)
.Select(x => (Guid?)x.Major!.CollegeId)
.FirstOrDefaultAsync(cancellationToken);
if (!collegeId.HasValue)
return PersonnelAccountResult.Failed("学生所在行政班或学院不存在。");
return await EnsureAccountAsync(
student.Id,
student.UserId,
student.StudentNumber,
student.Name,
collegeId.Value,
student.Status == StudentStatus.Active,
SystemRoles.Student,
initialPassword,
userId => student.UserId = userId,
cancellationToken);
}
private async Task<PersonnelAccountResult> EnsureAccountAsync(
Guid profileId,
Guid? linkedUserId,
string number,
string displayName,
Guid collegeId,
bool isEnabled,
string role,
string? initialPassword,
Action<Guid> linkProfile,
CancellationToken cancellationToken)
{
var normalizedNumber = number.Trim();
ApplicationUser? user = null;
if (linkedUserId.HasValue)
user = await userManager.FindByIdAsync(linkedUserId.Value.ToString());
user ??= await userManager.FindByNameAsync(normalizedNumber);
if (user is null)
{
if (string.IsNullOrWhiteSpace(initialPassword))
return PersonnelAccountResult.Failed("请填写至少 8 位初始密码,以便同时创建登录账号。");
user = new ApplicationUser
{
UserName = normalizedNumber,
DisplayName = displayName.Trim(),
StaffNumber = normalizedNumber,
CollegeId = collegeId,
IsEnabled = isEnabled,
LockoutEnabled = true
};
var createResult = await userManager.CreateAsync(user, initialPassword);
if (!createResult.Succeeded) return Failed(createResult);
}
else
{
var occupied = role == SystemRoles.Teacher
? await db.Teachers.AsNoTracking().AnyAsync(
x =>
x.Id != profileId &&
x.UserId == user.Id &&
x.TeacherNumber != normalizedNumber,
cancellationToken)
: await db.Students.AsNoTracking().AnyAsync(
x =>
x.Id != profileId &&
x.UserId == user.Id &&
x.StudentNumber != normalizedNumber,
cancellationToken);
if (occupied)
return PersonnelAccountResult.Failed($"登录账号“{normalizedNumber}”已关联其他人员档案。");
user.UserName = normalizedNumber;
user.DisplayName = displayName.Trim();
user.StaffNumber = normalizedNumber;
user.CollegeId = collegeId;
user.IsEnabled = isEnabled;
var updateResult = await userManager.UpdateAsync(user);
if (!updateResult.Succeeded) return Failed(updateResult);
}
if (!await userManager.IsInRoleAsync(user, role))
{
var roleResult = await userManager.AddToRoleAsync(user, role);
if (!roleResult.Succeeded) return Failed(roleResult);
}
linkProfile(user.Id);
await db.SaveChangesAsync(cancellationToken);
return PersonnelAccountResult.Succeeded(user.Id, normalizedNumber);
}
private static PersonnelAccountResult Failed(IdentityResult result) =>
PersonnelAccountResult.Failed(string.Join(
"",
result.Errors.Select(x => x.Description)));
}
public sealed record PersonnelAccountResult(
bool Success,
Guid? UserId,
string? UserName,
string? Error)
{
public static PersonnelAccountResult Succeeded(Guid userId, string userName) =>
new(true, userId, userName, null);
public static PersonnelAccountResult Failed(string error) =>
new(false, null, null, error);
}