refactor: 重构用户与勋章体系,统一状态管理与数据结构

1.  新增通用默认状态枚举 DefaultStatusEnum,替换原有分散的状态枚举
2.  重构用户体系:拆分 WxUser 独立表存储微信身份,Users 表改为角色子用户表并关联 WxUser
3.  重构勋章模块:新增系统/期刊勋章类型,调整 JournalId 为可空,新增勋章状态字段
4.  重构微信认证流程:基于 WxUser 生成 Token,支持多子用户管理
5.  清理冗余枚举文件,重构多处业务逻辑适配新的数据结构
6.  修复用户手机号关联逻辑,迁移手机号字段至 WxUser 表
This commit is contained in:
glz
2026-06-10 13:47:13 +08:00
parent 296d87b245
commit 028e3dfb34
29 changed files with 477 additions and 515 deletions

View File

@ -218,7 +218,7 @@ public class AdminUserService(BaseRepository<AdminUser> adminUserRepository, ILo
throw new BusinessException("管理员不存在", 404);
}
adminUser.Status = adminUser.Status==(int)AdminUserStatusEnum.Active?(int)AdminUserStatusEnum.Inactive:(int)AdminUserStatusEnum.Active;
adminUser.Status = adminUser.Status==(int)DefaultStatusEnum.Active?(int)DefaultStatusEnum.Inactive:(int)DefaultStatusEnum.Active;
adminUser.UpdatedBy = "System";
adminUser.UpdatedAt = DateTime.Now;

View File

@ -391,7 +391,7 @@ public class CheckInService(
{
// 查询签到配置(按 DayNumber 升序)
var configs = await checkInRecordRepository.Context.Queryable<CheckInConfig>()
.Where(c => c.Status == (int)CheckInConfigStatusEnum.Active && !c.IsDeleted)
.Where(c => c.Status == (int)DefaultStatusEnum.Active && !c.IsDeleted)
.OrderBy(c => c.DayNumber)
.ToListAsync();

View File

@ -43,6 +43,7 @@ public class MedalService(BaseRepository<Medal> medalRepository, IOptions<MedalR
SortOrder = input.SortOrder,
Type = Enum.Parse<MedalTypeEnum>(input.Type, true),
JournalId = input.JournalId,
Status = (int)DefaultStatusEnum.Active,
CreatedBy = "System",
UpdatedBy = "System",
CreatedAt = DateTime.Now,
@ -280,7 +281,7 @@ public class MedalService(BaseRepository<Medal> medalRepository, IOptions<MedalR
}
logger.LogInformation("勋章状态更新成功ID: {Id}, Status: {Status}", id, medal.Status);
return result;
}
/// <summary>
@ -505,6 +506,7 @@ public class MedalService(BaseRepository<Medal> medalRepository, IOptions<MedalR
SortOrder = medal.SortOrder,
Type = medal.Type.ToString(),
JournalId = medal.JournalId,
Status = (DefaultStatusEnum)medal.Status,
Rules = rules,
CreatedBy = medal.CreatedBy,
CreatedAt = medal.CreatedAt,

View File

@ -135,8 +135,13 @@ public class OperationLogService(
if (targetUser != null)
{
result.TargetUserName = targetUser.Name;
result.TargetUserPhone = targetUser.Phone;
result.TargetUserAvatar = targetUser.AvatarUrl;
// Phone 已迁移到 WxUser 表,通过 WxUserId 关联查询
var wxUser = await usersRepository.Context.Queryable<WxUser>()
.Where(w => w.Id == targetUser.WxUserId && !w.IsDeleted)
.FirstAsync();
result.TargetUserPhone = wxUser?.Phone;
}
}

View File

@ -112,7 +112,7 @@ public class PetService(
// 查询默认宠物模板取排序权重最低且状态为Active的模板
var defaultTemplate = await petTemplateRepository.Queryable()
.Where(t => t.Status == (int)PetTemplateStatusEnum.Active && !t.IsDeleted)
.Where(t => t.Status == (int)DefaultStatusEnum.Active && !t.IsDeleted)
.OrderBy(t => t.SortOrder)
.FirstAsync();
@ -137,7 +137,7 @@ public class PetService(
initialEvolution = await petEvolutionRepository.Queryable()
.Where(e => e.TemplateId == defaultTemplate.Id
&& e.PreviousEvolutionId == null
&& e.Status == (int)PetEvolutionStatusEnum.Active)
&& e.Status == (int)DefaultStatusEnum.Active)
.OrderBy(e => e.StageLevel)
.FirstAsync();
}
@ -279,7 +279,7 @@ public class PetService(
var nextEvolution = await petEvolutionRepository.Queryable()
.Where(e => e.PreviousEvolutionId == pet.CurrentEvolutionId
&& e.RequiredGrowth <= growthAfter
&& e.Status == (int)PetEvolutionStatusEnum.Active)
&& e.Status == (int)DefaultStatusEnum.Active)
.OrderBy(e => e.RequiredGrowth, OrderByType.Desc)
.FirstAsync();
@ -396,7 +396,7 @@ public class PetService(
IconUrl = input.IconUrl,
SortOrder = input.SortOrder,
Type = Enum.Parse<PetTemplateTypeEnum>(input.Type, true),
Status = (int)PetTemplateStatusEnum.Active,
Status = (int)DefaultStatusEnum.Active,
CreatedBy = "System",
CreatedAt = DateTime.Now,
UpdatedBy = "System",
@ -424,7 +424,7 @@ public class PetService(
RequiredGrowth = evoInput.RequiredGrowth,
PreviousEvolutionId = previousEvolution?.Id, // 第一个为 null
Type = Enum.Parse<PetEvolutionTypeEnum>(evoInput.Type, true),
Status = (int)PetEvolutionStatusEnum.Active,
Status = (int)DefaultStatusEnum.Active,
CreatedBy = "System",
CreatedAt = DateTime.Now,
UpdatedBy = "System",
@ -636,9 +636,9 @@ public class PetService(
if (template == null || template.IsDeleted)
throw new BusinessException("宠物模板不存在", 404);
template.Status = template.Status == (int)PetTemplateStatusEnum.Active
? (int)PetTemplateStatusEnum.Inactive
: (int)PetTemplateStatusEnum.Active;
template.Status = template.Status == (int)DefaultStatusEnum.Active
? (int)DefaultStatusEnum.Inactive
: (int)DefaultStatusEnum.Active;
template.UpdatedBy = "System";
template.UpdatedAt = DateTime.Now;
await petTemplateRepository.UpdateAsync(template);
@ -693,7 +693,7 @@ public class PetService(
RequiredGrowth = input.RequiredGrowth,
PreviousEvolutionId = input.PreviousEvolutionId,
Type = Enum.Parse<PetEvolutionTypeEnum>(input.Type, true),
Status = (int)PetEvolutionStatusEnum.Active,
Status = (int)DefaultStatusEnum.Active,
CreatedBy = "System",
CreatedAt = DateTime.Now,
UpdatedBy = "System",

View File

@ -22,7 +22,7 @@ public class SystemManagementService : ISystemManagementService
{
_logger.LogInformation("开始获取所有枚举信息");
var enumAssembly = typeof(QYZH.InteractiveMagazine.Models.Enum.AdminUserStatusEnum).Assembly;
var enumAssembly = typeof(QYZH.InteractiveMagazine.Models.Enum.DefaultStatusEnum).Assembly;
var enumNamespace = "QYZH.InteractiveMagazine.Models.Enum";
var enumTypes = enumAssembly.GetTypes()

View File

@ -29,7 +29,7 @@ public class UsersService(
public async Task<BaseResponse<PageListModel<UsersOutput>>> GetListAsync(UsersQueryInput input)
{
var page = Queryable()
.WhereIF(!string.IsNullOrEmpty(input.WxUserId), u => u.OpenId == input.WxUserId)
.WhereIF(!string.IsNullOrEmpty(input.WxUserId), u => u.WxUserId.ToString() == input.WxUserId)
.OrderBy(u => u.Id, OrderByType.Desc)
.ToPage<Users, UsersOutput>(input);

View File

@ -14,8 +14,14 @@ namespace QYZH.InteractiveMagazine.Service;
/// <summary>
/// 微信小程序认证服务实现
/// 登录基于 WxUser 表(微信身份),多用户管理基于 Users 表(角色/子用户)
/// </summary>
public class WeChatAuthService(BaseRepository<Users> usersRepository, IConfiguration configuration, ILogger<WeChatAuthService> logger, IPetService petService) : BaseRepository<Users>, IWeChatAuthService
public class WeChatAuthService(
BaseRepository<WxUser> wxUserRepository,
IConfiguration configuration,
ILogger<WeChatAuthService> logger,
IPetService petService)
: BaseRepository<WxUser>, IWeChatAuthService
{
private const string TokenKeyPrefix = "InteractiveMagazine:WeChatAuth:Token";
private const string AccessTokenCacheKey = "InteractiveMagazine:WeChat:AccessToken";
@ -24,20 +30,19 @@ public class WeChatAuthService(BaseRepository<Users> usersRepository, IConfigura
private const string GetPhoneNumberUrl = "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token={0}";
/// <summary>
/// 微信小程序登录(首次创建用户,非首次直接登录)
/// 微信小程序登录
/// 流程: code2session → 查找/创建 WxUser → 更新手机号 → 查询 Users 列表 → 生成 Token
/// </summary>
public async Task<WeChatLoginOutput> LoginAsync(WeChatLoginInput input)
{
logger.LogInformation("微信小程序登录");
if (string.IsNullOrWhiteSpace(input.Code))
{
throw new BusinessException("微信登录凭证 code 不能为空", 400);
}
var weChatSettings = GetWeChatSettings();
// 调用微信 code2session 接口
// 1. 调用微信 code2session
var wxResponse = await CallCode2SessionAsync(weChatSettings, input.Code);
if (wxResponse == null || wxResponse.ErrCode != 0 || string.IsNullOrWhiteSpace(wxResponse.OpenId))
{
@ -48,187 +53,244 @@ public class WeChatAuthService(BaseRepository<Users> usersRepository, IConfigura
logger.LogInformation("微信 code2session 成功OpenId: {OpenId}", wxResponse.OpenId);
// 查询该 OpenId 下的所有用户
var users = await usersRepository.Context.Queryable<Users>()
.Where(u => u.OpenId == wxResponse.OpenId && !u.IsDeleted)
.ToListAsync();
if (users.Count == 0)
// 2. 获取手机号(如果传入了 PhoneCode
string? phone = null;
if (!string.IsNullOrWhiteSpace(input.PhoneCode))
{
// 首次登录,获取手机号(如果传入了 PhoneCode
string? phone = null;
if (!string.IsNullOrWhiteSpace(input.PhoneCode))
{
phone = await GetPhoneNumberAsync(weChatSettings, input.PhoneCode);
logger.LogInformation("获取手机号成功OpenId: {OpenId}, Phone: {Phone}", wxResponse.OpenId, phone);
}
phone = await GetPhoneNumberAsync(weChatSettings, input.PhoneCode);
logger.LogInformation("获取手机号OpenId: {OpenId}, Phone: {Phone}", wxResponse.OpenId, phone ?? "null");
}
// 创建新用户
var newUser = new Users
// 3. 查找或创建 WxUser
var wxUser = await wxUserRepository.Context.Queryable<WxUser>()
.Where(w => w.OpenId == wxResponse.OpenId && !w.IsDeleted)
.FirstAsync();
if (wxUser == null)
{
// 首次登录:仅创建 WxUser不自动创建 User用户需手动创建
wxUser = new WxUser
{
Name = $"wx_{wxResponse.OpenId[^8..]}",
AvatarUrl = string.Empty,
OpenId = wxResponse.OpenId,
UnionId = wxResponse.UnionId,
Phone = phone,
Type = UsersTypeEnum.Normal,
Status = (int)UserStatusEnum.Active,
GrowthPoints = 0,
Points = 0,
IsLastOnline = true
Status = 1,
IsDeleted = false,
CreatedBy = "WeChat",
CreatedAt = DateTime.Now,
UpdatedBy = "WeChat",
UpdatedAt = DateTime.Now
};
var insertResult = usersRepository.Insertable(newUser).ExecuteReturnIdentity();
if (insertResult <= 0)
{
logger.LogError("创建微信用户失败OpenId: {OpenId}", wxResponse.OpenId);
throw new BusinessException("创建用户失败,请稍后重试", 500);
}
var wxUserId = await wxUserRepository.Insertable(wxUser).ExecuteReturnIdentityAsync();
wxUser.Id = wxUserId;
newUser.Id = insertResult;
users.Add(newUser);
logger.LogInformation("微信新用户创建成功UserId: {UserId}, OpenId: {OpenId}", newUser.Id, wxResponse.OpenId);
// 为新用户创建默认宠物(未激活状态)
try
{
await petService.CreateDefaultPetAsync(newUser.Id);
}
catch (Exception ex)
{
logger.LogError(ex, "新用户创建默认宠物失败UserId: {UserId}", newUser.Id);
// 宠物创建失败不阻断注册流程
}
logger.LogInformation("WxUser 创建成功WxUserId: {WxUserId}, OpenId: {OpenId}", wxUserId, wxResponse.OpenId);
}
else
{
// 非首次登录,如果传入了 PhoneCode 则更新该 OpenId 下所有用户的手机号
if (!string.IsNullOrWhiteSpace(input.PhoneCode))
// 非首次登录:更新 UnionId 和手机号
if (!string.IsNullOrWhiteSpace(wxResponse.UnionId) && string.IsNullOrWhiteSpace(wxUser.UnionId))
{
var phone = await GetPhoneNumberAsync(weChatSettings, input.PhoneCode);
if (!string.IsNullOrWhiteSpace(phone))
{
await usersRepository.Context.Updateable<Users>()
.SetColumns(u => u.Phone == phone)
.Where(u => u.OpenId == wxResponse.OpenId && !u.IsDeleted)
.ExecuteCommandAsync();
foreach (var u in users)
{
u.Phone = phone;
}
logger.LogInformation("更新 OpenId: {OpenId} 下所有用户手机号成功Phone: {Phone}", wxResponse.OpenId, phone);
}
await wxUserRepository.Context.Updateable<WxUser>()
.SetColumns(w => w.UnionId == wxResponse.UnionId)
.Where(w => w.Id == wxUser.Id)
.ExecuteCommandAsync();
wxUser.UnionId = wxResponse.UnionId;
}
logger.LogInformation("微信登录成功OpenId: {OpenId} 下存在 {Count} 个用户", wxResponse.OpenId, users.Count);
if (!string.IsNullOrWhiteSpace(phone))
{
await wxUserRepository.Context.Updateable<WxUser>()
.SetColumns(w => w.Phone == phone)
.Where(w => w.Id == wxUser.Id)
.ExecuteCommandAsync();
wxUser.Phone = phone;
logger.LogInformation("更新 WxUser 手机号WxUserId: {WxUserId}, Phone: {Phone}", wxUser.Id, phone);
}
}
// 构建登录输出
return await BuildLoginOutputAsync(wxResponse.OpenId, users);
// 查询该 WxUser 下所有 Users首次登录时为空列表
var users = await wxUserRepository.Context.Queryable<Users>()
.Where(u => u.WxUserId == wxUser.Id && !u.IsDeleted)
.ToListAsync();
logger.LogInformation("微信登录成功WxUserId: {WxUserId} 下存在 {Count} 个用户", wxUser.Id, users.Count);
return await BuildLoginOutputAsync(wxUser, users);
}
/// <summary>
/// 微信小程序快捷登录(通过 OpenId 直接登录,用户需已存在
/// 微信小程序快捷登录(通过 OpenId 直接登录)
/// </summary>
public async Task<WeChatLoginOutput> QuickLoginAsync(WeChatQuickLoginInput input)
{
logger.LogInformation("微信快捷登录OpenId: {OpenId}", input.OpenId);
if (string.IsNullOrWhiteSpace(input.OpenId))
{
throw new BusinessException("OpenId 不能为空", 400);
}
// 查询该 OpenId 下的所有用户
var users = await usersRepository.Context.Queryable<Users>()
.Where(u => u.OpenId == input.OpenId && !u.IsDeleted)
.ToListAsync();
var wxUser = await wxUserRepository.Context.Queryable<WxUser>()
.Where(w => w.OpenId == input.OpenId && !w.IsDeleted)
.FirstAsync();
if (users.Count == 0)
if (wxUser == null)
{
logger.LogWarning("快捷登录失败OpenId: {OpenId} 下无用户", input.OpenId);
logger.LogWarning("快捷登录失败OpenId: {OpenId} 下无 WxUser", input.OpenId);
throw new BusinessException("未找到该微信账号关联的用户,请先完成注册", 404);
}
logger.LogInformation("快捷登录成功OpenId: {OpenId} 下存在 {Count} 个用户", input.OpenId, users.Count);
var users = await wxUserRepository.Context.Queryable<Users>()
.Where(u => u.WxUserId == wxUser.Id && !u.IsDeleted)
.ToListAsync();
return await BuildLoginOutputAsync(input.OpenId, users);
logger.LogInformation("快捷登录成功WxUserId: {WxUserId}, {Count} 个用户", wxUser.Id, users.Count);
return await BuildLoginOutputAsync(wxUser, users);
}
/// <summary>
/// 切换用户(同一 OpenId 下切换身份
/// 切换用户(同一 WxUser 下切换 User 身份JWT 基于 WxUser 无需重新生成
/// </summary>
/// <param name="currentUserId">当前登录用户ID</param>
/// <param name="input">切换用户输入</param>
/// <returns>切换结果(含新 Token 和目标用户详情)</returns>
public async Task<WeChatSwitchUserOutput> SwitchUserAsync(long currentUserId, WeChatSwitchUserInput input)
public async Task<WeChatSwitchUserOutput> SwitchUserAsync(long wxUserId, WeChatSwitchUserInput input)
{
logger.LogInformation("切换用户,当前用户ID: {CurrentUserId}, 目标用户ID: {TargetUserId}", currentUserId, input.UserId);
// 获取当前用户,验证身份并获取 OpenId
var currentUser = await usersRepository.GetByIdAsync(currentUserId);
if (currentUser == null)
{
throw new BusinessException("当前用户不存在", 404);
}
var openId = currentUser.OpenId;
logger.LogInformation("切换用户,WxUserId: {WxUserId}, 目标 UserId: {TargetUserId}", wxUserId, input.UserId);
// 查询目标用户
var targetUser = await usersRepository.GetByIdAsync(input.UserId);
if (targetUser == null)
{
throw new BusinessException("目标用户不存在", 404);
}
var targetUser = await wxUserRepository.Context.Queryable<Users>()
.Where(u => u.Id == input.UserId)
.FirstAsync();
// 校验目标用户与当前用户属于同一 OpenId
if (targetUser.OpenId != openId)
if (targetUser == null)
throw new BusinessException("目标用户不存在", 404);
// 校验目标用户属于同一 WxUser
if (targetUser.WxUserId != wxUserId)
{
logger.LogWarning("切换用户失败,目标用户 OpenId 不匹配,当前: {CurrentOpenId}, 目标: {TargetOpenId}", openId, targetUser.OpenId);
logger.LogWarning("切换用户失败,WxUserId 不匹配,当前: {Current}, 目标: {Target}", wxUserId, targetUser.WxUserId);
throw new BusinessException("无法切换到该用户", 403);
}
if (targetUser.Status == (int)UserStatusEnum.Disabled)
{
throw new BusinessException("目标账号已被禁用", 403);
}
// 更新 IsLastOnline:目标用户设为 true同 OpenId 下其他用户设为 false
await usersRepository.Context.Updateable<Users>()
// 更新 IsLastOnline(清除所有,设置目标为 true
await wxUserRepository.Context.Updateable<Users>()
.SetColumns(u => u.IsLastOnline == false)
.Where(u => u.OpenId == openId && !u.IsDeleted)
.Where(u => u.WxUserId == wxUserId )
.ExecuteCommandAsync();
await usersRepository.Context.Updateable<Users>()
await wxUserRepository.Context.Updateable<Users>()
.SetColumns(u => u.IsLastOnline == true)
.Where(u => u.Id == input.UserId && !u.IsDeleted)
.Where(u => u.Id == input.UserId )
.ExecuteCommandAsync();
logger.LogInformation("IsLastOnline 已更新,目标用户 {UserId} 设为 true", input.UserId);
// TODO: 预留扩展逻辑 —— 加载用户详情、用户关联信息等
// var userDetail = await LoadUserDetailAsync(targetUser.Id);
// var userRelations = await LoadUserRelationsAsync(targetUser.Id);
// 重新查询目标用户以获取最新数据(含 IsLastOnline
var refreshedUser = await usersRepository.GetByIdAsync(input.UserId);
// 为目标用户生成新的 JWT Token
var jwtSettings = GetJwtSettings();
var token = JwtHelper.GenerateToken((long)refreshedUser.Id, refreshedUser.Name, jwtSettings);
// 清除旧用户 Token 缓存,写入新用户 Token 缓存
await RedisHelper.DelAsync($"{TokenKeyPrefix}:{currentUserId}");
await RedisHelper.SetAsync($"{TokenKeyPrefix}:{refreshedUser.Id}", token, TimeSpan.FromMinutes(jwtSettings.ExpiryMinutes));
// 重新查询目标用户获取最新数据
var refreshedUser = await wxUserRepository.Context.Queryable<Users>()
.Where(u => u.Id == input.UserId)
.FirstAsync();
return new WeChatSwitchUserOutput
{
Token = token,
User = MapUserToOutput(refreshedUser)
};
}
/// <summary>
/// 获取当前 WxUser 下所有用户列表
/// </summary>
public async Task<List<WxUserOutput>> GetUsersAsync(long wxUserId)
{
var users = await wxUserRepository.Context.Queryable<Users>()
.Where(u => u.WxUserId == wxUserId && !u.IsDeleted)
.OrderBy(u => u.IsLastOnline, SqlSugar.OrderByType.Desc)
.OrderBy(u => u.CreatedAt, SqlSugar.OrderByType.Desc)
.ToListAsync();
return users.Select(MapUserToOutput).ToList();
}
/// <summary>
/// 在当前 WxUser 下新增用户wxUserId 来自 JWT
/// </summary>
public async Task<WxUserOutput> CreateUserAsync(long wxUserId, CreateChildUserInput input)
{
logger.LogInformation("新增用户WxUserId: {WxUserId}, Name: {Name}", wxUserId, input.Name);
if (string.IsNullOrWhiteSpace(input.Name))
throw new BusinessException("昵称不能为空", 400);
// 校验 WxUser 是否存在
var wxUser = await wxUserRepository.Context.Queryable<WxUser>()
.Where(w => w.Id == wxUserId )
.FirstAsync();
if (wxUser == null)
throw new BusinessException("微信用户不存在", 404);
// 创建新用户
var newUser = new Users
{
WxUserId = wxUserId,
Name = input.Name.Trim(),
AvatarUrl = input.AvatarUrl ?? string.Empty,
Type = UsersTypeEnum.Normal,
GrowthPoints = 0,
Points = 0,
IsLastOnline = false,
Status = (int)UserStatusEnum.Active,
IsDeleted = false,
CreatedBy = wxUserId.ToString(),
CreatedAt = DateTime.Now,
UpdatedBy = wxUserId.ToString(),
UpdatedAt = DateTime.Now
};
var userId = await wxUserRepository.Context.Insertable(newUser).ExecuteReturnIdentityAsync();
newUser.Id = userId;
logger.LogInformation("新用户创建成功UserId: {UserId}, WxUserId: {WxUserId}, Name: {Name}",
userId, wxUserId, input.Name);
// 为新用户创建默认宠物
try { await petService.CreateDefaultPetAsync(newUser.Id); }
catch (Exception ex) { logger.LogError(ex, "新用户创建默认宠物失败UserId: {UserId}", newUser.Id); }
return MapUserToOutput(newUser);
}
#region
/// <summary>
/// 构建登录输出JWT 基于 WxUser不绑定具体 User
/// </summary>
private async Task<WeChatLoginOutput> BuildLoginOutputAsync(WxUser wxUser, List<Users> users)
{
var jwtSettings = GetJwtSettings();
var token = JwtHelper.GenerateToken(wxUser.Id, wxUser.Name ?? wxUser.OpenId, jwtSettings);
await RedisHelper.SetAsync($"{TokenKeyPrefix}:{wxUser.Id}", token, TimeSpan.FromMinutes(jwtSettings.ExpiryMinutes));
return new WeChatLoginOutput
{
Token = token,
WxUser = new WxUserInfoOutput
{
Id = wxUser.Id,
OpenId = wxUser.OpenId,
UnionId = wxUser.UnionId,
Name = wxUser.Name,
AvatarUrl = wxUser.AvatarUrl,
Phone = wxUser.Phone
},
Users = users.Select(MapUserToOutput).ToList()
};
}
/// <summary>
/// 调用微信 code2session 接口
/// </summary>
@ -246,43 +308,15 @@ public class WeChatAuthService(BaseRepository<Users> usersRepository, IConfigura
}
}
/// <summary>
/// 构建登录输出(生成 Token + 映射用户列表)
/// </summary>
private async Task<WeChatLoginOutput> BuildLoginOutputAsync(string openId, List<Users> users)
{
// 优先使用 IsLastOnline 的用户,否则取第一个
var primaryUser = users.FirstOrDefault(u => u.IsLastOnline) ?? users.First();
var jwtSettings = GetJwtSettings();
var token = JwtHelper.GenerateToken((long)primaryUser.Id, primaryUser.Name, jwtSettings);
// 缓存 Token 到 Redis
await RedisHelper.SetAsync($"{TokenKeyPrefix}:{primaryUser.Id}", token, TimeSpan.FromMinutes(jwtSettings.ExpiryMinutes));
var userOutputs = users.Select(MapUserToOutput).ToList();
return new WeChatLoginOutput
{
Token = token,
OpenId = openId,
Users = userOutputs
};
}
/// <summary>
/// 获取微信 access_token带 Redis 缓存)
/// </summary>
private async Task<string> GetAccessTokenAsync(WeChatSettings settings)
{
// 先从 Redis 缓存获取
var cachedToken = await RedisHelper.GetAsync(AccessTokenCacheKey);
if (!string.IsNullOrWhiteSpace(cachedToken))
{
return cachedToken;
}
// 缓存未命中,调用微信接口获取
var url = string.Format(GetAccessTokenUrl, settings.AppId, settings.AppSecret);
var response = await HttpHelper.GetAsync<WxAccessTokenResponse>(url);
@ -293,7 +327,6 @@ public class WeChatAuthService(BaseRepository<Users> usersRepository, IConfigura
throw new BusinessException("微信服务请求失败,请稍后重试", 500);
}
// 缓存 access_token提前 5 分钟过期(微信默认 7200 秒)
var expiresIn = response.ExpiresIn > 300 ? response.ExpiresIn - 300 : response.ExpiresIn;
await RedisHelper.SetAsync(AccessTokenCacheKey, response.AccessToken, TimeSpan.FromSeconds(expiresIn));
@ -316,7 +349,6 @@ public class WeChatAuthService(BaseRepository<Users> usersRepository, IConfigura
{
var errMsg = response?.ErrMsg ?? "未知错误";
logger.LogWarning("获取手机号失败errcode: {ErrCode}, errmsg: {ErrMsg}", response?.ErrCode, errMsg);
// 获取手机号失败不阻断登录流程,仅记录日志
return null;
}
@ -325,39 +357,30 @@ public class WeChatAuthService(BaseRepository<Users> usersRepository, IConfigura
catch (Exception ex)
{
logger.LogError(ex, "调用微信获取手机号接口异常");
// 获取手机号失败不阻断登录流程
return null;
}
}
/// <summary>
/// 用户实体映射为输出 DTO
/// Users 实体映射为 WxUserOutput
/// </summary>
private static WxUserOutput MapUserToOutput(Users user)
{
return new WxUserOutput
{
Id = (long)user.Id,
OpenId = user.OpenId,
UnionId = user.UnionId,
WxUserId = user.WxUserId,
NickName = user.Name,
AvatarUrl = user.AvatarUrl,
Phone = user.Phone,
Points = user.Points,
GrowthPoints = user.GrowthPoints,
Type = user.Type.ToString(),
Status = user.Status.ToString(),
IsLastOnline = user.IsLastOnline,
CreatedAt = user.CreatedAt,
UpdatedAt = user.UpdatedAt,
CreatedBy = user.CreatedBy,
UpdatedBy = user.UpdatedBy
CreatedAt = user.CreatedAt
};
}
/// <summary>
/// 获取微信配置
/// </summary>
private WeChatSettings GetWeChatSettings()
{
var settings = configuration.GetSection("WeChatSettings").Get<WeChatSettings>();
@ -369,9 +392,6 @@ public class WeChatAuthService(BaseRepository<Users> usersRepository, IConfigura
return settings;
}
/// <summary>
/// 获取 JWT 配置
/// </summary>
private JwtSettings GetJwtSettings()
{
var jwtSettings = configuration.GetSection("JwtSettings").Get<JwtSettings>()
@ -384,10 +404,10 @@ public class WeChatAuthService(BaseRepository<Users> usersRepository, IConfigura
};
if (string.IsNullOrWhiteSpace(jwtSettings.SecretKey))
{
throw new BusinessException("JWT 配置不完整", 500);
}
return jwtSettings;
}
#endregion
}