feat(wechat-auth): 实现微信同一OpenId下多用户切换功能
1. 新增用户实体字段IsLastOnline标记上次在线用户 2. 重构微信登录逻辑,返回同一OpenId下的所有用户列表而非单用户 3. 新增SwitchUserAsync服务方法与控制器接口,支持同OpenId下切换用户 4. 添加用户实体到输出DTO的映射工具方法 5. 实现切换用户时的身份校验、Token刷新与在线状态更新
This commit is contained in:
@ -14,4 +14,12 @@ public interface IWeChatAuthService : IBaseService<Users>
|
||||
/// <param name="input">登录输入(含微信 code)</param>
|
||||
/// <returns>登录结果(含 Token 和用户信息)</returns>
|
||||
Task<WeChatLoginOutput> LoginAsync(WeChatLoginInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 切换用户(同一 OpenId 下切换身份)
|
||||
/// </summary>
|
||||
/// <param name="currentUserId">当前登录用户ID</param>
|
||||
/// <param name="input">切换用户输入</param>
|
||||
/// <returns>切换结果(含新 Token 和目标用户详情)</returns>
|
||||
Task<WeChatSwitchUserOutput> SwitchUserAsync(long currentUserId, WeChatSwitchUserInput input);
|
||||
}
|
||||
|
||||
@ -24,20 +24,15 @@ public class WeChatLoginOutput
|
||||
/// </summary>
|
||||
public string Token { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 用户ID
|
||||
/// </summary>
|
||||
public long UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用户名
|
||||
/// </summary>
|
||||
public string UserName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 微信OpenId
|
||||
/// </summary>
|
||||
public string OpenId { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 该 OpenId 下的用户列表
|
||||
/// </summary>
|
||||
public List<WxUserOutput> Users { get; set; } = [];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -151,6 +146,11 @@ public class WxUserOutput
|
||||
/// </summary>
|
||||
public int GrowthPoints { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否上次在线用户
|
||||
/// </summary>
|
||||
public bool IsLastOnline { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 创建人
|
||||
/// </summary>
|
||||
@ -225,3 +225,30 @@ public class WxCode2SessionResponse
|
||||
public string? ErrMsg { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 微信切换用户输入
|
||||
/// </summary>
|
||||
public class WeChatSwitchUserInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 目标用户ID
|
||||
/// </summary>
|
||||
public long UserId { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 微信切换用户输出
|
||||
/// </summary>
|
||||
public class WeChatSwitchUserOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 新的访问令牌
|
||||
/// </summary>
|
||||
public string Token { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 当前切换的用户详情
|
||||
/// </summary>
|
||||
public WxUserOutput User { get; set; } = new();
|
||||
}
|
||||
|
||||
|
||||
@ -67,5 +67,12 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
||||
/// </summary>
|
||||
public string Phone { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:是否上次在线用户
|
||||
/// Default:0
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public bool IsLastOnline { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,7 +24,7 @@ public class WeChatAuthService(BaseRepository<Users> usersRepository, IConfigura
|
||||
/// 微信小程序一键登录
|
||||
/// </summary>
|
||||
/// <param name="input">登录输入(含微信 code)</param>
|
||||
/// <returns>登录结果(含 Token 和用户信息)</returns>
|
||||
/// <returns>登录结果(含 Token 和该 OpenId 下的用户列表)</returns>
|
||||
public async Task<WeChatLoginOutput> LoginAsync(WeChatLoginInput input)
|
||||
{
|
||||
logger.LogInformation("微信小程序登录尝试");
|
||||
@ -49,14 +49,15 @@ public class WeChatAuthService(BaseRepository<Users> 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<Users>()
|
||||
.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<Users> 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
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 切换用户(同一 OpenId 下切换身份)
|
||||
/// </summary>
|
||||
/// <param name="currentUserId">当前登录用户ID</param>
|
||||
/// <param name="input">切换用户输入</param>
|
||||
/// <returns>切换结果(含新 Token 和目标用户详情)</returns>
|
||||
public async Task<WeChatSwitchUserOutput> 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<Users>()
|
||||
.SetColumns(u => u.IsLastOnline == false)
|
||||
.Where(u => u.OpenId == openId && !u.IsDeleted)
|
||||
.ExecuteCommandAsync();
|
||||
|
||||
await usersRepository.Context.Updateable<Users>()
|
||||
.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<Users> usersRepository, IConfigura
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用户实体映射为输出 DTO
|
||||
/// </summary>
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取微信配置
|
||||
/// </summary>
|
||||
|
||||
@ -46,4 +46,35 @@ public class WeChatAuthController : WeChatBaseController
|
||||
return BaseResponse<WeChatLoginOutput>.Fail("微信登录失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 切换用户(同一 OpenId 下切换身份)
|
||||
/// </summary>
|
||||
/// <param name="input">切换用户输入(含目标用户ID)</param>
|
||||
/// <returns>切换结果(含新 Token 和目标用户详情)</returns>
|
||||
[HttpPost("switchUser")]
|
||||
public async Task<BaseResponse<WeChatSwitchUserOutput>> SwitchUserAsync([FromBody] WeChatSwitchUserInput input)
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == null)
|
||||
{
|
||||
return BaseResponse<WeChatSwitchUserOutput>.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<WeChatSwitchUserOutput>.Fail(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "切换用户系统异常");
|
||||
return BaseResponse<WeChatSwitchUserOutput>.Fail("切换用户失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user