From 018d7e1733a017053092e766f70b80bfc6097d22 Mon Sep 17 00:00:00 2001 From: glz <694770232@qq.com> Date: Wed, 3 Jun 2026 18:28:45 +0800 Subject: [PATCH] =?UTF-8?q?feat(wechat-auth):=20=E5=AE=9E=E7=8E=B0?= =?UTF-8?q?=E5=BE=AE=E4=BF=A1=E5=90=8C=E4=B8=80OpenId=E4=B8=8B=E5=A4=9A?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E5=88=87=E6=8D=A2=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 新增用户实体字段IsLastOnline标记上次在线用户 2. 重构微信登录逻辑,返回同一OpenId下的所有用户列表而非单用户 3. 新增SwitchUserAsync服务方法与控制器接口,支持同OpenId下切换用户 4. 添加用户实体到输出DTO的映射工具方法 5. 实现切换用户时的身份校验、Token刷新与在线状态更新 --- .../IWeChatAuthService.cs | 8 + .../Dto/WeChat/WeChatDto.cs | 47 ++++-- .../Entity/Users.cs | 7 + .../WeChatAuthService.cs | 142 +++++++++++++++--- .../WeChat/WeChatAuthController.cs | 31 ++++ 5 files changed, 202 insertions(+), 33 deletions(-) diff --git a/QYZH.InteractiveMagazine.IService/IWeChatAuthService.cs b/QYZH.InteractiveMagazine.IService/IWeChatAuthService.cs index b1a82b0..02a8582 100644 --- a/QYZH.InteractiveMagazine.IService/IWeChatAuthService.cs +++ b/QYZH.InteractiveMagazine.IService/IWeChatAuthService.cs @@ -14,4 +14,12 @@ public interface IWeChatAuthService : IBaseService /// 登录输入(含微信 code) /// 登录结果(含 Token 和用户信息) Task LoginAsync(WeChatLoginInput input); + + /// + /// 切换用户(同一 OpenId 下切换身份) + /// + /// 当前登录用户ID + /// 切换用户输入 + /// 切换结果(含新 Token 和目标用户详情) + Task SwitchUserAsync(long currentUserId, WeChatSwitchUserInput input); } diff --git a/QYZH.InteractiveMagazine.Models/Dto/WeChat/WeChatDto.cs b/QYZH.InteractiveMagazine.Models/Dto/WeChat/WeChatDto.cs index 54e06bd..a01b22a 100644 --- a/QYZH.InteractiveMagazine.Models/Dto/WeChat/WeChatDto.cs +++ b/QYZH.InteractiveMagazine.Models/Dto/WeChat/WeChatDto.cs @@ -24,20 +24,15 @@ public class WeChatLoginOutput /// public string Token { get; set; } = string.Empty; - /// - /// 用户ID - /// - public long UserId { get; set; } - - /// - /// 用户名 - /// - public string UserName { get; set; } = string.Empty; - /// /// 微信OpenId /// public string OpenId { get; set; } = string.Empty; + + /// + /// 该 OpenId 下的用户列表 + /// + public List Users { get; set; } = []; } /// @@ -151,6 +146,11 @@ public class WxUserOutput /// public int GrowthPoints { get; set; } + /// + /// 是否上次在线用户 + /// + public bool IsLastOnline { get; set; } + /// /// 创建人 /// @@ -225,3 +225,30 @@ public class WxCode2SessionResponse public string? ErrMsg { get; set; } } +/// +/// 微信切换用户输入 +/// +public class WeChatSwitchUserInput +{ + /// + /// 目标用户ID + /// + public long UserId { get; set; } +} + +/// +/// 微信切换用户输出 +/// +public class WeChatSwitchUserOutput +{ + /// + /// 新的访问令牌 + /// + public string Token { get; set; } = string.Empty; + + /// + /// 当前切换的用户详情 + /// + public WxUserOutput User { get; set; } = new(); +} + diff --git a/QYZH.InteractiveMagazine.Models/Entity/Users.cs b/QYZH.InteractiveMagazine.Models/Entity/Users.cs index 03b1fbb..8c1fc60 100644 --- a/QYZH.InteractiveMagazine.Models/Entity/Users.cs +++ b/QYZH.InteractiveMagazine.Models/Entity/Users.cs @@ -67,5 +67,12 @@ namespace QYZH.InteractiveMagazine.Models.Entity /// public string Phone { get; set; } + /// + /// Desc:是否上次在线用户 + /// Default:0 + /// Nullable:False + /// + public bool IsLastOnline { get; set; } + } } diff --git a/QYZH.InteractiveMagazine.Service/WeChatAuthService.cs b/QYZH.InteractiveMagazine.Service/WeChatAuthService.cs index 93debec..f6ceb1f 100644 --- a/QYZH.InteractiveMagazine.Service/WeChatAuthService.cs +++ b/QYZH.InteractiveMagazine.Service/WeChatAuthService.cs @@ -24,7 +24,7 @@ public class WeChatAuthService(BaseRepository usersRepository, IConfigura /// 微信小程序一键登录 /// /// 登录输入(含微信 code) - /// 登录结果(含 Token 和用户信息) + /// 登录结果(含 Token 和该 OpenId 下的用户列表) public async Task LoginAsync(WeChatLoginInput input) { logger.LogInformation("微信小程序登录尝试"); @@ -49,14 +49,15 @@ public class WeChatAuthService(BaseRepository usersRepository, IConfigura logger.LogInformation("微信 code2session 成功,OpenId: {OpenId}", wxResponse.OpenId); - // 查询用户是否已存在 - var user = await usersRepository.GetFirstAsync(u => u.OpenId == wxResponse.OpenId); - var isNewUser = user == null; + // 查询该 OpenId 下的所有用户 + var users = await usersRepository.Context.Queryable() + .Where(u => u.OpenId == wxResponse.OpenId && !u.IsDeleted) + .ToListAsync(); - if (isNewUser) + if (users.Count == 0) { // 首次登录,创建新用户 - user = new Users + var newUser = new Users { Name = $"wx_{wxResponse.OpenId[^8..]}", OpenId = wxResponse.OpenId, @@ -67,41 +68,111 @@ public class WeChatAuthService(BaseRepository usersRepository, IConfigura Points = 0 }; - var insertResult = await usersRepository.Insertable(user).ExecuteReturnIdentityAsync(); + var insertResult = await usersRepository.Insertable(newUser).ExecuteReturnIdentityAsync(); if (insertResult <= 0) { logger.LogError("创建微信用户失败,OpenId: {OpenId}", wxResponse.OpenId); throw new BusinessException("创建用户失败,请稍后重试", 500); } - user.Id = insertResult; - logger.LogInformation("微信新用户创建成功,UserId: {UserId}, OpenId: {OpenId}", user.Id, wxResponse.OpenId); + newUser.Id = insertResult; + users.Add(newUser); + logger.LogInformation("微信新用户创建成功,UserId: {UserId}, OpenId: {OpenId}", newUser.Id, wxResponse.OpenId); } else { - // 已有用户,校验状态 - if (user.Status == "Disabled") - { - logger.LogWarning("微信登录失败,用户已被禁用,UserId: {UserId}, OpenId: {OpenId}", user.Id, wxResponse.OpenId); - throw new BusinessException("账号已被禁用,请联系客服", 403); - } - - logger.LogInformation("微信老用户登录,UserId: {UserId}, OpenId: {OpenId}", user.Id, wxResponse.OpenId); + logger.LogInformation("微信登录成功,OpenId: {OpenId} 下存在 {Count} 个用户", wxResponse.OpenId, users.Count); } - // 生成 JWT Token + // 使用第一个用户生成 JWT Token + var primaryUser = users.First(); var jwtSettings = GetJwtSettings(); - var token = JwtHelper.GenerateToken((long)user.Id, user.Name, jwtSettings); + var token = JwtHelper.GenerateToken((long)primaryUser.Id, primaryUser.Name, jwtSettings); // 缓存 Token 到 Redis - await RedisHelper.StringSetAsync($"{TokenKeyPrefix}:{user.Id}", token, TimeSpan.FromMinutes(jwtSettings.ExpiryMinutes)); + await RedisHelper.StringSetAsync($"{TokenKeyPrefix}:{primaryUser.Id}", token, TimeSpan.FromMinutes(jwtSettings.ExpiryMinutes)); + + // 映射用户列表 + var userOutputs = users.Select(MapUserToOutput).ToList(); return new WeChatLoginOutput { Token = token, - UserId = (long)user.Id, - UserName = user.Name, - OpenId = wxResponse.OpenId + OpenId = wxResponse.OpenId, + Users = userOutputs + }; + } + + /// + /// 切换用户(同一 OpenId 下切换身份) + /// + /// 当前登录用户ID + /// 切换用户输入 + /// 切换结果(含新 Token 和目标用户详情) + public async Task SwitchUserAsync(long currentUserId, 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; + + // 查询目标用户 + var targetUser = await usersRepository.GetByIdAsync(input.UserId); + if (targetUser == null) + { + throw new BusinessException("目标用户不存在", 404); + } + + // 校验目标用户与当前用户属于同一 OpenId + if (targetUser.OpenId != openId) + { + logger.LogWarning("切换用户失败,目标用户 OpenId 不匹配,当前: {CurrentOpenId}, 目标: {TargetOpenId}", openId, targetUser.OpenId); + throw new BusinessException("无法切换到该用户", 403); + } + + if (targetUser.Status == "Disabled") + { + throw new BusinessException("目标账号已被禁用", 403); + } + + // 更新 IsLastOnline:目标用户设为 true,同 OpenId 下其他用户设为 false + await usersRepository.Context.Updateable() + .SetColumns(u => u.IsLastOnline == false) + .Where(u => u.OpenId == openId && !u.IsDeleted) + .ExecuteCommandAsync(); + + await usersRepository.Context.Updateable() + .SetColumns(u => u.IsLastOnline == true) + .Where(u => u.Id == input.UserId && !u.IsDeleted) + .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.KeyDeleteAsync($"{TokenKeyPrefix}:{currentUserId}"); + await RedisHelper.StringSetAsync($"{TokenKeyPrefix}:{refreshedUser.Id}", token, TimeSpan.FromMinutes(jwtSettings.ExpiryMinutes)); + + return new WeChatSwitchUserOutput + { + Token = token, + User = MapUserToOutput(refreshedUser) }; } @@ -122,6 +193,31 @@ public class WeChatAuthService(BaseRepository usersRepository, IConfigura } } + /// + /// 用户实体映射为输出 DTO + /// + private static WxUserOutput MapUserToOutput(Users user) + { + return new WxUserOutput + { + Id = (long)user.Id, + OpenId = user.OpenId, + UnionId = user.UnionId, + NickName = user.Name, + AvatarUrl = user.AvatarUrl, + Phone = user.Phone, + Points = user.Points, + GrowthPoints = user.GrowthPoints, + Type = user.Type, + Status = user.Status, + IsLastOnline = user.IsLastOnline, + CreatedAt = user.CreatedAt, + UpdatedAt = user.UpdatedAt, + CreatedBy = user.CreatedBy, + UpdatedBy = user.UpdatedBy + }; + } + /// /// 获取微信配置 /// diff --git a/QYZH.InteractiveMagazine.WebApi/Controllers/WeChat/WeChatAuthController.cs b/QYZH.InteractiveMagazine.WebApi/Controllers/WeChat/WeChatAuthController.cs index 812bfc5..a50fd46 100644 --- a/QYZH.InteractiveMagazine.WebApi/Controllers/WeChat/WeChatAuthController.cs +++ b/QYZH.InteractiveMagazine.WebApi/Controllers/WeChat/WeChatAuthController.cs @@ -46,4 +46,35 @@ public class WeChatAuthController : WeChatBaseController return BaseResponse.Fail("微信登录失败,请稍后重试"); } } + + /// + /// 切换用户(同一 OpenId 下切换身份) + /// + /// 切换用户输入(含目标用户ID) + /// 切换结果(含新 Token 和目标用户详情) + [HttpPost("switchUser")] + public async Task> SwitchUserAsync([FromBody] WeChatSwitchUserInput input) + { + try + { + var userId = GetCurrentUserId(); + if (userId == null) + { + return BaseResponse.Fail(ResultCode.DENY, "未获取到用户信息"); + } + + var result = await _weChatAuthService.SwitchUserAsync(userId.Value, input); + return Success(result); + } + catch (BusinessException ex) + { + _logger.LogWarning(ex, "切换用户业务异常: {Message}", ex.Message); + return BaseResponse.Fail(ex.Message); + } + catch (Exception ex) + { + _logger.LogError(ex, "切换用户系统异常"); + return BaseResponse.Fail("切换用户失败,请稍后重试"); + } + } }