refactor: 重构用户与勋章体系,统一状态管理与数据结构
1. 新增通用默认状态枚举 DefaultStatusEnum,替换原有分散的状态枚举 2. 重构用户体系:拆分 WxUser 独立表存储微信身份,Users 表改为角色子用户表并关联 WxUser 3. 重构勋章模块:新增系统/期刊勋章类型,调整 JournalId 为可空,新增勋章状态字段 4. 重构微信认证流程:基于 WxUser 生成 Token,支持多子用户管理 5. 清理冗余枚举文件,重构多处业务逻辑适配新的数据结构 6. 修复用户手机号关联逻辑,迁移手机号字段至 WxUser 表
This commit is contained in:
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
using QYZH.InteractiveMagazine.Models.Entity;
|
using QYZH.InteractiveMagazine.Models.Entity;
|
||||||
using QYZH.InteractiveMagazine.Models.WeChat;
|
using QYZH.InteractiveMagazine.Models.WeChat;
|
||||||
|
|
||||||
@ -7,27 +6,35 @@ namespace QYZH.InteractiveMagazine.IService;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 微信小程序认证服务
|
/// 微信小程序认证服务
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IWeChatAuthService : IBaseService<Users>
|
public interface IWeChatAuthService : IBaseService<WxUser>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 微信小程序登录(首次创建用户,非首次直接登录)
|
/// 微信小程序登录(首次仅创建 WxUser,不自动创建 User)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="input">登录输入(含微信 code 和可选的手机号 code)</param>
|
|
||||||
/// <returns>登录结果(含 Token 和用户信息)</returns>
|
|
||||||
Task<WeChatLoginOutput> LoginAsync(WeChatLoginInput input);
|
Task<WeChatLoginOutput> LoginAsync(WeChatLoginInput input);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 微信小程序快捷登录(通过 OpenId 直接登录,用户需已存在)
|
/// 微信小程序快捷登录(通过 OpenId 直接登录)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="input">快捷登录输入(含 OpenId)</param>
|
|
||||||
/// <returns>登录结果(含 Token 和用户列表)</returns>
|
|
||||||
Task<WeChatLoginOutput> QuickLoginAsync(WeChatQuickLoginInput input);
|
Task<WeChatLoginOutput> QuickLoginAsync(WeChatQuickLoginInput input);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 切换用户(同一 OpenId 下切换身份)
|
/// 切换用户(同一 WxUser 下切换 User 身份,JWT 基于 WxUser 无需重新生成 Token)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="currentUserId">当前登录用户ID</param>
|
Task<WeChatSwitchUserOutput> SwitchUserAsync(long wxUserId, WeChatSwitchUserInput input);
|
||||||
/// <param name="input">切换用户输入</param>
|
|
||||||
/// <returns>切换结果(含新 Token 和目标用户详情)</returns>
|
/// <summary>
|
||||||
Task<WeChatSwitchUserOutput> SwitchUserAsync(long currentUserId, WeChatSwitchUserInput input);
|
/// 获取当前 WxUser 下所有用户列表
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="wxUserId">当前登录的 WxUser.Id(来自 JWT)</param>
|
||||||
|
/// <returns>用户列表</returns>
|
||||||
|
Task<List<WxUserOutput>> GetUsersAsync(long wxUserId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 在当前 WxUser 下新增用户(子用户/角色)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="wxUserId">当前登录的 WxUser.Id(来自 JWT)</param>
|
||||||
|
/// <param name="input">新增用户参数</param>
|
||||||
|
/// <returns>新创建的用户信息</returns>
|
||||||
|
Task<WxUserOutput> CreateUserAsync(long wxUserId, CreateChildUserInput input);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -30,12 +30,12 @@ public class MedalInput
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 勋章的类型: Pet, Community
|
/// 勋章的类型: Pet, Community
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Type { get; set; } = MedalTypeEnum.Pet.ToString();
|
public string Type { get; set; } = MedalTypeEnum.System.ToString();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 书id
|
/// 期刊id
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public long JournalId { get; set; }
|
public long? JournalId { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 勋章规则列表
|
/// 勋章规则列表
|
||||||
@ -81,7 +81,11 @@ public class MedalOutput
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 书id
|
/// 书id
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public long JournalId { get; set; }
|
public long? JournalId { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// 勋章状态
|
||||||
|
/// </summary>
|
||||||
|
public DefaultStatusEnum Status { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 勋章规则列表
|
/// 勋章规则列表
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
using QYZH.InteractiveMagazine.Models.Dto;
|
|
||||||
using QYZH.InteractiveMagazine.Models.Enum;
|
using QYZH.InteractiveMagazine.Models.Enum;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
@ -32,89 +31,33 @@ public class WeChatQuickLoginInput
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 微信登录输出
|
/// 微信登录输出(含微信用户信息 + 该微信下的 Users 列表)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class WeChatLoginOutput
|
public class WeChatLoginOutput
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 访问令牌
|
/// 访问令牌(基于 WxUser,不绑定具体 User)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Token { get; set; } = string.Empty;
|
public string Token { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 微信OpenId
|
/// 微信用户信息
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string OpenId { get; set; } = string.Empty;
|
public WxUserInfoOutput WxUser { get; set; } = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 该 OpenId 下的用户列表
|
/// 该微信用户下的用户列表
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<WxUserOutput> Users { get; set; } = [];
|
public List<WxUserOutput> Users { get; set; } = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 微信用户创建/更新输入
|
/// 微信用户信息输出(WxUser 表数据)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class WxUserInput
|
public class WxUserInfoOutput
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 微信OpenId
|
/// WxUser 主键Id
|
||||||
/// </summary>
|
|
||||||
public string OpenId { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 微信UnionId
|
|
||||||
/// </summary>
|
|
||||||
public string? UnionId { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 昵称
|
|
||||||
/// </summary>
|
|
||||||
public string? NickName { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 头像地址
|
|
||||||
/// </summary>
|
|
||||||
public string? AvatarUrl { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 手机号
|
|
||||||
/// </summary>
|
|
||||||
public string? Phone { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 积分余额
|
|
||||||
/// </summary>
|
|
||||||
public int Points { get; set; } = 0;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 用户类型: Normal, VIP
|
|
||||||
/// </summary>
|
|
||||||
public string Type { get; set; } = UsersTypeEnum.Normal.ToString();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 状态: Active, Disabled
|
|
||||||
/// </summary>
|
|
||||||
public string Status { get; set; } = UserStatusEnum.Active.ToString();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 密码,默认手机后4位
|
|
||||||
/// </summary>
|
|
||||||
public string? Pwd { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 当前成长值
|
|
||||||
/// </summary>
|
|
||||||
public int GrowthPoints { get; set; } = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 微信用户输出
|
|
||||||
/// </summary>
|
|
||||||
public class WxUserOutput
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 主键ID
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public long Id { get; set; }
|
public long Id { get; set; }
|
||||||
|
|
||||||
@ -131,7 +74,7 @@ public class WxUserOutput
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 昵称
|
/// 昵称
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string? NickName { get; set; }
|
public string? Name { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 头像地址
|
/// 头像地址
|
||||||
@ -142,6 +85,32 @@ public class WxUserOutput
|
|||||||
/// 手机号
|
/// 手机号
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string? Phone { get; set; }
|
public string? Phone { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 用户输出(Users 表数据,即角色/子用户)
|
||||||
|
/// </summary>
|
||||||
|
public class WxUserOutput
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Users 主键Id
|
||||||
|
/// </summary>
|
||||||
|
public long Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 关联 WxUserId
|
||||||
|
/// </summary>
|
||||||
|
public long WxUserId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 昵称
|
||||||
|
/// </summary>
|
||||||
|
public string? NickName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 头像地址
|
||||||
|
/// </summary>
|
||||||
|
public string? AvatarUrl { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 积分余额
|
/// 积分余额
|
||||||
@ -149,7 +118,12 @@ public class WxUserOutput
|
|||||||
public int Points { get; set; }
|
public int Points { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 用户类型
|
/// 当前成长值
|
||||||
|
/// </summary>
|
||||||
|
public int GrowthPoints { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 用户类型: Normal, VIP
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Type { get; set; } = string.Empty;
|
public string Type { get; set; } = string.Empty;
|
||||||
|
|
||||||
@ -158,86 +132,74 @@ public class WxUserOutput
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string Status { get; set; } = string.Empty;
|
public string Status { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 当前成长值
|
|
||||||
/// </summary>
|
|
||||||
public int GrowthPoints { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 是否上次在线用户
|
/// 是否上次在线用户
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsLastOnline { get; set; }
|
public bool IsLastOnline { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 创建人
|
|
||||||
/// </summary>
|
|
||||||
public string? CreatedBy { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 创建时间
|
/// 创建时间
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public DateTime CreatedAt { get; set; }
|
public DateTime CreatedAt { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 更新人
|
|
||||||
/// </summary>
|
|
||||||
public string? UpdatedBy { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 更新时间
|
|
||||||
/// </summary>
|
|
||||||
public DateTime? UpdatedAt { get; set; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 微信用户分页查询输入
|
/// 新增用户输入(在 WxUser 下创建子用户/角色)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class WxUserQueryInput : PageQueryModel
|
public class CreateChildUserInput
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 昵称(模糊查询)
|
/// 昵称
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string? NickName { get; set; }
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 手机号(模糊查询)
|
/// 头像地址(可选)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string? Phone { get; set; }
|
public string? AvatarUrl { get; set; }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 微信切换用户输入
|
||||||
|
/// </summary>
|
||||||
|
public class WeChatSwitchUserInput
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 目标用户ID(Users 表 Id)
|
||||||
|
/// </summary>
|
||||||
|
public long UserId { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 微信切换用户输出(JWT 基于 WxUser,切换不更换 Token)
|
||||||
|
/// </summary>
|
||||||
|
public class WeChatSwitchUserOutput
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 当前切换的用户详情
|
||||||
|
/// </summary>
|
||||||
|
public WxUserOutput User { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 微信 API 响应 DTO(不变) ==========
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 微信 code2session 接口响应
|
/// 微信 code2session 接口响应
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class WxCode2SessionResponse
|
public class WxCode2SessionResponse
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// 用户唯一标识
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("openid")]
|
[JsonProperty("openid")]
|
||||||
public string? OpenId { get; set; }
|
public string? OpenId { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 会话密钥
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("session_key")]
|
[JsonProperty("session_key")]
|
||||||
public string? SessionKey { get; set; }
|
public string? SessionKey { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 用户统一标识(在开放平台绑定了多个应用时使用)
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("unionid")]
|
[JsonProperty("unionid")]
|
||||||
public string? UnionId { get; set; }
|
public string? UnionId { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 错误码
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("errcode")]
|
[JsonProperty("errcode")]
|
||||||
public int ErrCode { get; set; }
|
public int ErrCode { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 错误信息
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("errmsg")]
|
[JsonProperty("errmsg")]
|
||||||
public string? ErrMsg { get; set; }
|
public string? ErrMsg { get; set; }
|
||||||
}
|
}
|
||||||
@ -247,21 +209,12 @@ public class WxCode2SessionResponse
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class WxPhoneNumberResponse
|
public class WxPhoneNumberResponse
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// 错误码
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("errcode")]
|
[JsonProperty("errcode")]
|
||||||
public int ErrCode { get; set; }
|
public int ErrCode { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 错误信息
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("errmsg")]
|
[JsonProperty("errmsg")]
|
||||||
public string? ErrMsg { get; set; }
|
public string? ErrMsg { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 手机号信息
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("phone_info")]
|
[JsonProperty("phone_info")]
|
||||||
public WxPhoneInfo? PhoneInfo { get; set; }
|
public WxPhoneInfo? PhoneInfo { get; set; }
|
||||||
}
|
}
|
||||||
@ -271,21 +224,12 @@ public class WxPhoneNumberResponse
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class WxPhoneInfo
|
public class WxPhoneInfo
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// 用户绑定的手机号(国外手机号会有区号)
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("phoneNumber")]
|
[JsonProperty("phoneNumber")]
|
||||||
public string? PhoneNumber { get; set; }
|
public string? PhoneNumber { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 没有区号的手机号
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("purePhoneNumber")]
|
[JsonProperty("purePhoneNumber")]
|
||||||
public string? PurePhoneNumber { get; set; }
|
public string? PurePhoneNumber { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 区号
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("countryCode")]
|
[JsonProperty("countryCode")]
|
||||||
public string? CountryCode { get; set; }
|
public string? CountryCode { get; set; }
|
||||||
}
|
}
|
||||||
@ -295,55 +239,15 @@ public class WxPhoneInfo
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class WxAccessTokenResponse
|
public class WxAccessTokenResponse
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// 获取到的凭证
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("access_token")]
|
[JsonProperty("access_token")]
|
||||||
public string? AccessToken { get; set; }
|
public string? AccessToken { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 凭证有效时间(秒)
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("expires_in")]
|
[JsonProperty("expires_in")]
|
||||||
public int ExpiresIn { get; set; }
|
public int ExpiresIn { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 错误码
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("errcode")]
|
[JsonProperty("errcode")]
|
||||||
public int ErrCode { get; set; }
|
public int ErrCode { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 错误信息
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("errmsg")]
|
[JsonProperty("errmsg")]
|
||||||
public string? ErrMsg { get; set; }
|
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@ -55,7 +55,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
|||||||
/// Default:
|
/// Default:
|
||||||
/// Nullable:False
|
/// Nullable:False
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public long JournalId { get; set; }
|
public long? JournalId { get; set; }
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,11 +4,17 @@ using QYZH.InteractiveMagazine.Models.Enum;
|
|||||||
namespace QYZH.InteractiveMagazine.Models.Entity
|
namespace QYZH.InteractiveMagazine.Models.Entity
|
||||||
{
|
{
|
||||||
///<summary>
|
///<summary>
|
||||||
///用户表
|
///用户表 — 游戏/角色数据,关联 WxUser
|
||||||
///</summary>
|
///</summary>
|
||||||
[SugarTable("Users")]
|
[SugarTable("Users")]
|
||||||
public partial class Users : SqlSugarBaseEntity
|
public partial class Users : SqlSugarBaseEntity
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:关联微信用户Id
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public long WxUserId { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Desc:昵称
|
/// Desc:昵称
|
||||||
@ -16,18 +22,21 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
|||||||
/// Nullable:True
|
/// Nullable:True
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Desc:当前成长值
|
/// Desc:当前成长值
|
||||||
/// Default:0
|
/// Default:0
|
||||||
/// Nullable:False
|
/// Nullable:False
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int GrowthPoints { get; set; }
|
public int GrowthPoints { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Desc:头像地址
|
/// Desc:头像地址
|
||||||
/// Default:
|
/// Default:
|
||||||
/// Nullable:True
|
/// Nullable:True
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string AvatarUrl { get; set; }
|
public string AvatarUrl { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Desc:积分余额
|
/// Desc:积分余额
|
||||||
/// Default:0
|
/// Default:0
|
||||||
@ -41,25 +50,6 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
|||||||
/// Nullable:False
|
/// Nullable:False
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public UsersTypeEnum Type { get; set; }
|
public UsersTypeEnum Type { get; set; }
|
||||||
/// <summary>
|
|
||||||
/// Desc:微信OpenId
|
|
||||||
/// Default:
|
|
||||||
/// Nullable:False
|
|
||||||
/// </summary>
|
|
||||||
public string OpenId { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Desc:微信UnionId
|
|
||||||
/// Default:
|
|
||||||
/// Nullable:True
|
|
||||||
/// </summary>
|
|
||||||
public string UnionId { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
/// Desc:手机号
|
|
||||||
/// Default:
|
|
||||||
/// Nullable:True
|
|
||||||
/// </summary>
|
|
||||||
public string Phone { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Desc:是否上次在线用户
|
/// Desc:是否上次在线用户
|
||||||
@ -67,6 +57,5 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
|||||||
/// Nullable:False
|
/// Nullable:False
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsLastOnline { get; set; }
|
public bool IsLastOnline { get; set; }
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
49
QYZH.InteractiveMagazine.Models/Entity/WxUser.cs
Normal file
49
QYZH.InteractiveMagazine.Models/Entity/WxUser.cs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
using SqlSugar;
|
||||||
|
|
||||||
|
namespace QYZH.InteractiveMagazine.Models.Entity
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 微信用户表 — 存储微信身份信息和基本资料
|
||||||
|
/// 一个 WxUser 可关联多个 Users(多角色/多用户)
|
||||||
|
/// </summary>
|
||||||
|
[SugarTable("WxUser")]
|
||||||
|
public partial class WxUser : SqlSugarBaseEntity
|
||||||
|
{
|
||||||
|
public WxUser() { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:昵称
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:True
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:头像地址
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:True
|
||||||
|
/// </summary>
|
||||||
|
public string AvatarUrl { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:微信OpenId
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public string OpenId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:微信UnionId
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:True
|
||||||
|
/// </summary>
|
||||||
|
public string UnionId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:手机号
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:True
|
||||||
|
/// </summary>
|
||||||
|
public string Phone { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,21 +0,0 @@
|
|||||||
using System.ComponentModel;
|
|
||||||
|
|
||||||
namespace QYZH.InteractiveMagazine.Models.Enum;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 签到配置状态枚举
|
|
||||||
/// </summary>
|
|
||||||
public enum CheckInConfigStatusEnum
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 未激活
|
|
||||||
/// </summary>
|
|
||||||
[Description("未激活")]
|
|
||||||
Inactive = 0,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 激活
|
|
||||||
/// </summary>
|
|
||||||
[Description("激活")]
|
|
||||||
Active = 1
|
|
||||||
}
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
using System.ComponentModel;
|
|
||||||
|
|
||||||
namespace QYZH.InteractiveMagazine.Models.Enum;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 固定模板言论状态枚举
|
|
||||||
/// </summary>
|
|
||||||
public enum CommunityTemplateSentenceStatusEnum
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 未激活
|
|
||||||
/// </summary>
|
|
||||||
[Description("未激活")]
|
|
||||||
Inactive = 0,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 激活
|
|
||||||
/// </summary>
|
|
||||||
[Description("激活")]
|
|
||||||
Active = 1
|
|
||||||
}
|
|
||||||
@ -1,11 +1,16 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace QYZH.InteractiveMagazine.Models.Enum
|
namespace QYZH.InteractiveMagazine.Models.Enum
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 管理员状态枚举
|
/// 默认通用状态
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public enum AdminUserStatusEnum
|
public enum DefaultStatusEnum
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 未激活
|
/// 未激活
|
||||||
@ -7,6 +7,11 @@ namespace QYZH.InteractiveMagazine.Models.Enum;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public enum MedalTypeEnum
|
public enum MedalTypeEnum
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 系统勋章
|
||||||
|
/// </summary>
|
||||||
|
[Description("系统勋章")]
|
||||||
|
System = 0,
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 宠物勋章
|
/// 宠物勋章
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -16,5 +21,11 @@ public enum MedalTypeEnum
|
|||||||
/// 社区勋章
|
/// 社区勋章
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Description("社区勋章")]
|
[Description("社区勋章")]
|
||||||
Community = 2
|
Community = 2,
|
||||||
|
/// <summary>
|
||||||
|
/// 期刊勋章
|
||||||
|
/// </summary>
|
||||||
|
[Description("期刊勋章")]
|
||||||
|
Journal = 3
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,21 +0,0 @@
|
|||||||
using System.ComponentModel;
|
|
||||||
|
|
||||||
namespace QYZH.InteractiveMagazine.Models.Enum;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 宠物进化状态枚举
|
|
||||||
/// </summary>
|
|
||||||
public enum PetEvolutionStatusEnum
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 未激活
|
|
||||||
/// </summary>
|
|
||||||
[Description("未激活")]
|
|
||||||
Inactive = 0,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 激活
|
|
||||||
/// </summary>
|
|
||||||
[Description("激活")]
|
|
||||||
Active = 1
|
|
||||||
}
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
using System.ComponentModel;
|
|
||||||
|
|
||||||
namespace QYZH.InteractiveMagazine.Models.Enum;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 宠物模板状态枚举
|
|
||||||
/// </summary>
|
|
||||||
public enum PetTemplateStatusEnum
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 未激活
|
|
||||||
/// </summary>
|
|
||||||
[Description("未激活")]
|
|
||||||
Inactive = 0,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 激活
|
|
||||||
/// </summary>
|
|
||||||
[Description("激活")]
|
|
||||||
Active = 1
|
|
||||||
}
|
|
||||||
@ -218,7 +218,7 @@ public class AdminUserService(BaseRepository<AdminUser> adminUserRepository, ILo
|
|||||||
throw new BusinessException("管理员不存在", 404);
|
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.UpdatedBy = "System";
|
||||||
adminUser.UpdatedAt = DateTime.Now;
|
adminUser.UpdatedAt = DateTime.Now;
|
||||||
|
|
||||||
|
|||||||
@ -391,7 +391,7 @@ public class CheckInService(
|
|||||||
{
|
{
|
||||||
// 查询签到配置(按 DayNumber 升序)
|
// 查询签到配置(按 DayNumber 升序)
|
||||||
var configs = await checkInRecordRepository.Context.Queryable<CheckInConfig>()
|
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)
|
.OrderBy(c => c.DayNumber)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
|
|||||||
@ -43,6 +43,7 @@ public class MedalService(BaseRepository<Medal> medalRepository, IOptions<MedalR
|
|||||||
SortOrder = input.SortOrder,
|
SortOrder = input.SortOrder,
|
||||||
Type = Enum.Parse<MedalTypeEnum>(input.Type, true),
|
Type = Enum.Parse<MedalTypeEnum>(input.Type, true),
|
||||||
JournalId = input.JournalId,
|
JournalId = input.JournalId,
|
||||||
|
Status = (int)DefaultStatusEnum.Active,
|
||||||
CreatedBy = "System",
|
CreatedBy = "System",
|
||||||
UpdatedBy = "System",
|
UpdatedBy = "System",
|
||||||
CreatedAt = DateTime.Now,
|
CreatedAt = DateTime.Now,
|
||||||
@ -505,6 +506,7 @@ public class MedalService(BaseRepository<Medal> medalRepository, IOptions<MedalR
|
|||||||
SortOrder = medal.SortOrder,
|
SortOrder = medal.SortOrder,
|
||||||
Type = medal.Type.ToString(),
|
Type = medal.Type.ToString(),
|
||||||
JournalId = medal.JournalId,
|
JournalId = medal.JournalId,
|
||||||
|
Status = (DefaultStatusEnum)medal.Status,
|
||||||
Rules = rules,
|
Rules = rules,
|
||||||
CreatedBy = medal.CreatedBy,
|
CreatedBy = medal.CreatedBy,
|
||||||
CreatedAt = medal.CreatedAt,
|
CreatedAt = medal.CreatedAt,
|
||||||
|
|||||||
@ -135,8 +135,13 @@ public class OperationLogService(
|
|||||||
if (targetUser != null)
|
if (targetUser != null)
|
||||||
{
|
{
|
||||||
result.TargetUserName = targetUser.Name;
|
result.TargetUserName = targetUser.Name;
|
||||||
result.TargetUserPhone = targetUser.Phone;
|
|
||||||
result.TargetUserAvatar = targetUser.AvatarUrl;
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -112,7 +112,7 @@ public class PetService(
|
|||||||
|
|
||||||
// 查询默认宠物模板(取排序权重最低且状态为Active的模板)
|
// 查询默认宠物模板(取排序权重最低且状态为Active的模板)
|
||||||
var defaultTemplate = await petTemplateRepository.Queryable()
|
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)
|
.OrderBy(t => t.SortOrder)
|
||||||
.FirstAsync();
|
.FirstAsync();
|
||||||
|
|
||||||
@ -137,7 +137,7 @@ public class PetService(
|
|||||||
initialEvolution = await petEvolutionRepository.Queryable()
|
initialEvolution = await petEvolutionRepository.Queryable()
|
||||||
.Where(e => e.TemplateId == defaultTemplate.Id
|
.Where(e => e.TemplateId == defaultTemplate.Id
|
||||||
&& e.PreviousEvolutionId == null
|
&& e.PreviousEvolutionId == null
|
||||||
&& e.Status == (int)PetEvolutionStatusEnum.Active)
|
&& e.Status == (int)DefaultStatusEnum.Active)
|
||||||
.OrderBy(e => e.StageLevel)
|
.OrderBy(e => e.StageLevel)
|
||||||
.FirstAsync();
|
.FirstAsync();
|
||||||
}
|
}
|
||||||
@ -279,7 +279,7 @@ public class PetService(
|
|||||||
var nextEvolution = await petEvolutionRepository.Queryable()
|
var nextEvolution = await petEvolutionRepository.Queryable()
|
||||||
.Where(e => e.PreviousEvolutionId == pet.CurrentEvolutionId
|
.Where(e => e.PreviousEvolutionId == pet.CurrentEvolutionId
|
||||||
&& e.RequiredGrowth <= growthAfter
|
&& e.RequiredGrowth <= growthAfter
|
||||||
&& e.Status == (int)PetEvolutionStatusEnum.Active)
|
&& e.Status == (int)DefaultStatusEnum.Active)
|
||||||
.OrderBy(e => e.RequiredGrowth, OrderByType.Desc)
|
.OrderBy(e => e.RequiredGrowth, OrderByType.Desc)
|
||||||
.FirstAsync();
|
.FirstAsync();
|
||||||
|
|
||||||
@ -396,7 +396,7 @@ public class PetService(
|
|||||||
IconUrl = input.IconUrl,
|
IconUrl = input.IconUrl,
|
||||||
SortOrder = input.SortOrder,
|
SortOrder = input.SortOrder,
|
||||||
Type = Enum.Parse<PetTemplateTypeEnum>(input.Type, true),
|
Type = Enum.Parse<PetTemplateTypeEnum>(input.Type, true),
|
||||||
Status = (int)PetTemplateStatusEnum.Active,
|
Status = (int)DefaultStatusEnum.Active,
|
||||||
CreatedBy = "System",
|
CreatedBy = "System",
|
||||||
CreatedAt = DateTime.Now,
|
CreatedAt = DateTime.Now,
|
||||||
UpdatedBy = "System",
|
UpdatedBy = "System",
|
||||||
@ -424,7 +424,7 @@ public class PetService(
|
|||||||
RequiredGrowth = evoInput.RequiredGrowth,
|
RequiredGrowth = evoInput.RequiredGrowth,
|
||||||
PreviousEvolutionId = previousEvolution?.Id, // 第一个为 null
|
PreviousEvolutionId = previousEvolution?.Id, // 第一个为 null
|
||||||
Type = Enum.Parse<PetEvolutionTypeEnum>(evoInput.Type, true),
|
Type = Enum.Parse<PetEvolutionTypeEnum>(evoInput.Type, true),
|
||||||
Status = (int)PetEvolutionStatusEnum.Active,
|
Status = (int)DefaultStatusEnum.Active,
|
||||||
CreatedBy = "System",
|
CreatedBy = "System",
|
||||||
CreatedAt = DateTime.Now,
|
CreatedAt = DateTime.Now,
|
||||||
UpdatedBy = "System",
|
UpdatedBy = "System",
|
||||||
@ -636,9 +636,9 @@ public class PetService(
|
|||||||
if (template == null || template.IsDeleted)
|
if (template == null || template.IsDeleted)
|
||||||
throw new BusinessException("宠物模板不存在", 404);
|
throw new BusinessException("宠物模板不存在", 404);
|
||||||
|
|
||||||
template.Status = template.Status == (int)PetTemplateStatusEnum.Active
|
template.Status = template.Status == (int)DefaultStatusEnum.Active
|
||||||
? (int)PetTemplateStatusEnum.Inactive
|
? (int)DefaultStatusEnum.Inactive
|
||||||
: (int)PetTemplateStatusEnum.Active;
|
: (int)DefaultStatusEnum.Active;
|
||||||
template.UpdatedBy = "System";
|
template.UpdatedBy = "System";
|
||||||
template.UpdatedAt = DateTime.Now;
|
template.UpdatedAt = DateTime.Now;
|
||||||
await petTemplateRepository.UpdateAsync(template);
|
await petTemplateRepository.UpdateAsync(template);
|
||||||
@ -693,7 +693,7 @@ public class PetService(
|
|||||||
RequiredGrowth = input.RequiredGrowth,
|
RequiredGrowth = input.RequiredGrowth,
|
||||||
PreviousEvolutionId = input.PreviousEvolutionId,
|
PreviousEvolutionId = input.PreviousEvolutionId,
|
||||||
Type = Enum.Parse<PetEvolutionTypeEnum>(input.Type, true),
|
Type = Enum.Parse<PetEvolutionTypeEnum>(input.Type, true),
|
||||||
Status = (int)PetEvolutionStatusEnum.Active,
|
Status = (int)DefaultStatusEnum.Active,
|
||||||
CreatedBy = "System",
|
CreatedBy = "System",
|
||||||
CreatedAt = DateTime.Now,
|
CreatedAt = DateTime.Now,
|
||||||
UpdatedBy = "System",
|
UpdatedBy = "System",
|
||||||
|
|||||||
@ -22,7 +22,7 @@ public class SystemManagementService : ISystemManagementService
|
|||||||
{
|
{
|
||||||
_logger.LogInformation("开始获取所有枚举信息");
|
_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 enumNamespace = "QYZH.InteractiveMagazine.Models.Enum";
|
||||||
|
|
||||||
var enumTypes = enumAssembly.GetTypes()
|
var enumTypes = enumAssembly.GetTypes()
|
||||||
|
|||||||
@ -29,7 +29,7 @@ public class UsersService(
|
|||||||
public async Task<BaseResponse<PageListModel<UsersOutput>>> GetListAsync(UsersQueryInput input)
|
public async Task<BaseResponse<PageListModel<UsersOutput>>> GetListAsync(UsersQueryInput input)
|
||||||
{
|
{
|
||||||
var page = Queryable()
|
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)
|
.OrderBy(u => u.Id, OrderByType.Desc)
|
||||||
.ToPage<Users, UsersOutput>(input);
|
.ToPage<Users, UsersOutput>(input);
|
||||||
|
|
||||||
|
|||||||
@ -14,8 +14,14 @@ namespace QYZH.InteractiveMagazine.Service;
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 微信小程序认证服务实现
|
/// 微信小程序认证服务实现
|
||||||
|
/// 登录基于 WxUser 表(微信身份),多用户管理基于 Users 表(角色/子用户)
|
||||||
/// </summary>
|
/// </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 TokenKeyPrefix = "InteractiveMagazine:WeChatAuth:Token";
|
||||||
private const string AccessTokenCacheKey = "InteractiveMagazine:WeChat:AccessToken";
|
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}";
|
private const string GetPhoneNumberUrl = "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token={0}";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 微信小程序登录(首次创建用户,非首次直接登录)
|
/// 微信小程序登录
|
||||||
|
/// 流程: code2session → 查找/创建 WxUser → 更新手机号 → 查询 Users 列表 → 生成 Token
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public async Task<WeChatLoginOutput> LoginAsync(WeChatLoginInput input)
|
public async Task<WeChatLoginOutput> LoginAsync(WeChatLoginInput input)
|
||||||
{
|
{
|
||||||
logger.LogInformation("微信小程序登录");
|
logger.LogInformation("微信小程序登录");
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(input.Code))
|
if (string.IsNullOrWhiteSpace(input.Code))
|
||||||
{
|
|
||||||
throw new BusinessException("微信登录凭证 code 不能为空", 400);
|
throw new BusinessException("微信登录凭证 code 不能为空", 400);
|
||||||
}
|
|
||||||
|
|
||||||
var weChatSettings = GetWeChatSettings();
|
var weChatSettings = GetWeChatSettings();
|
||||||
|
|
||||||
// 调用微信 code2session 接口
|
// 1. 调用微信 code2session
|
||||||
var wxResponse = await CallCode2SessionAsync(weChatSettings, input.Code);
|
var wxResponse = await CallCode2SessionAsync(weChatSettings, input.Code);
|
||||||
if (wxResponse == null || wxResponse.ErrCode != 0 || string.IsNullOrWhiteSpace(wxResponse.OpenId))
|
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);
|
logger.LogInformation("微信 code2session 成功,OpenId: {OpenId}", wxResponse.OpenId);
|
||||||
|
|
||||||
// 查询该 OpenId 下的所有用户
|
// 2. 获取手机号(如果传入了 PhoneCode)
|
||||||
var users = await usersRepository.Context.Queryable<Users>()
|
|
||||||
.Where(u => u.OpenId == wxResponse.OpenId && !u.IsDeleted)
|
|
||||||
.ToListAsync();
|
|
||||||
|
|
||||||
if (users.Count == 0)
|
|
||||||
{
|
|
||||||
// 首次登录,获取手机号(如果传入了 PhoneCode)
|
|
||||||
string? phone = null;
|
string? phone = null;
|
||||||
if (!string.IsNullOrWhiteSpace(input.PhoneCode))
|
if (!string.IsNullOrWhiteSpace(input.PhoneCode))
|
||||||
{
|
{
|
||||||
phone = await GetPhoneNumberAsync(weChatSettings, input.PhoneCode);
|
phone = await GetPhoneNumberAsync(weChatSettings, input.PhoneCode);
|
||||||
logger.LogInformation("获取手机号成功,OpenId: {OpenId}, Phone: {Phone}", wxResponse.OpenId, phone);
|
logger.LogInformation("获取手机号,OpenId: {OpenId}, Phone: {Phone}", wxResponse.OpenId, phone ?? "null");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建新用户
|
// 3. 查找或创建 WxUser
|
||||||
var newUser = new Users
|
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..]}",
|
Name = $"wx_{wxResponse.OpenId[^8..]}",
|
||||||
|
AvatarUrl = string.Empty,
|
||||||
OpenId = wxResponse.OpenId,
|
OpenId = wxResponse.OpenId,
|
||||||
UnionId = wxResponse.UnionId,
|
UnionId = wxResponse.UnionId,
|
||||||
Phone = phone,
|
Phone = phone,
|
||||||
Type = UsersTypeEnum.Normal,
|
Status = 1,
|
||||||
Status = (int)UserStatusEnum.Active,
|
IsDeleted = false,
|
||||||
GrowthPoints = 0,
|
CreatedBy = "WeChat",
|
||||||
Points = 0,
|
CreatedAt = DateTime.Now,
|
||||||
IsLastOnline = true
|
UpdatedBy = "WeChat",
|
||||||
|
UpdatedAt = DateTime.Now
|
||||||
};
|
};
|
||||||
|
|
||||||
var insertResult = usersRepository.Insertable(newUser).ExecuteReturnIdentity();
|
var wxUserId = await wxUserRepository.Insertable(wxUser).ExecuteReturnIdentityAsync();
|
||||||
if (insertResult <= 0)
|
wxUser.Id = wxUserId;
|
||||||
{
|
|
||||||
logger.LogError("创建微信用户失败,OpenId: {OpenId}", wxResponse.OpenId);
|
|
||||||
throw new BusinessException("创建用户失败,请稍后重试", 500);
|
|
||||||
}
|
|
||||||
|
|
||||||
newUser.Id = insertResult;
|
logger.LogInformation("WxUser 创建成功,WxUserId: {WxUserId}, OpenId: {OpenId}", wxUserId, wxResponse.OpenId);
|
||||||
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);
|
|
||||||
// 宠物创建失败不阻断注册流程
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// 非首次登录,如果传入了 PhoneCode 则更新该 OpenId 下所有用户的手机号
|
// 非首次登录:更新 UnionId 和手机号
|
||||||
if (!string.IsNullOrWhiteSpace(input.PhoneCode))
|
if (!string.IsNullOrWhiteSpace(wxResponse.UnionId) && string.IsNullOrWhiteSpace(wxUser.UnionId))
|
||||||
{
|
{
|
||||||
var phone = await GetPhoneNumberAsync(weChatSettings, input.PhoneCode);
|
await wxUserRepository.Context.Updateable<WxUser>()
|
||||||
|
.SetColumns(w => w.UnionId == wxResponse.UnionId)
|
||||||
|
.Where(w => w.Id == wxUser.Id)
|
||||||
|
.ExecuteCommandAsync();
|
||||||
|
wxUser.UnionId = wxResponse.UnionId;
|
||||||
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(phone))
|
if (!string.IsNullOrWhiteSpace(phone))
|
||||||
{
|
{
|
||||||
await usersRepository.Context.Updateable<Users>()
|
await wxUserRepository.Context.Updateable<WxUser>()
|
||||||
.SetColumns(u => u.Phone == phone)
|
.SetColumns(w => w.Phone == phone)
|
||||||
.Where(u => u.OpenId == wxResponse.OpenId && !u.IsDeleted)
|
.Where(w => w.Id == wxUser.Id)
|
||||||
.ExecuteCommandAsync();
|
.ExecuteCommandAsync();
|
||||||
|
wxUser.Phone = phone;
|
||||||
foreach (var u in users)
|
logger.LogInformation("更新 WxUser 手机号,WxUserId: {WxUserId}, Phone: {Phone}", wxUser.Id, phone);
|
||||||
{
|
|
||||||
u.Phone = phone;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.LogInformation("更新 OpenId: {OpenId} 下所有用户手机号成功,Phone: {Phone}", wxResponse.OpenId, phone);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.LogInformation("微信登录成功,OpenId: {OpenId} 下存在 {Count} 个用户", wxResponse.OpenId, users.Count);
|
// 查询该 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(wxResponse.OpenId, users);
|
|
||||||
|
return await BuildLoginOutputAsync(wxUser, users);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 微信小程序快捷登录(通过 OpenId 直接登录,用户需已存在)
|
/// 微信小程序快捷登录(通过 OpenId 直接登录)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public async Task<WeChatLoginOutput> QuickLoginAsync(WeChatQuickLoginInput input)
|
public async Task<WeChatLoginOutput> QuickLoginAsync(WeChatQuickLoginInput input)
|
||||||
{
|
{
|
||||||
logger.LogInformation("微信快捷登录,OpenId: {OpenId}", input.OpenId);
|
logger.LogInformation("微信快捷登录,OpenId: {OpenId}", input.OpenId);
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(input.OpenId))
|
if (string.IsNullOrWhiteSpace(input.OpenId))
|
||||||
{
|
|
||||||
throw new BusinessException("OpenId 不能为空", 400);
|
throw new BusinessException("OpenId 不能为空", 400);
|
||||||
}
|
|
||||||
|
|
||||||
// 查询该 OpenId 下的所有用户
|
var wxUser = await wxUserRepository.Context.Queryable<WxUser>()
|
||||||
var users = await usersRepository.Context.Queryable<Users>()
|
.Where(w => w.OpenId == input.OpenId && !w.IsDeleted)
|
||||||
.Where(u => u.OpenId == input.OpenId && !u.IsDeleted)
|
.FirstAsync();
|
||||||
.ToListAsync();
|
|
||||||
|
|
||||||
if (users.Count == 0)
|
if (wxUser == null)
|
||||||
{
|
{
|
||||||
logger.LogWarning("快捷登录失败,OpenId: {OpenId} 下无用户", input.OpenId);
|
logger.LogWarning("快捷登录失败,OpenId: {OpenId} 下无 WxUser", input.OpenId);
|
||||||
throw new BusinessException("未找到该微信账号关联的用户,请先完成注册", 404);
|
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>
|
/// <summary>
|
||||||
/// 切换用户(同一 OpenId 下切换身份)
|
/// 切换用户(同一 WxUser 下切换 User 身份,JWT 基于 WxUser 无需重新生成)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="currentUserId">当前登录用户ID</param>
|
public async Task<WeChatSwitchUserOutput> SwitchUserAsync(long wxUserId, WeChatSwitchUserInput input)
|
||||||
/// <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);
|
logger.LogInformation("切换用户,WxUserId: {WxUserId}, 目标 UserId: {TargetUserId}", wxUserId, 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);
|
var targetUser = await wxUserRepository.Context.Queryable<Users>()
|
||||||
if (targetUser == null)
|
.Where(u => u.Id == input.UserId)
|
||||||
{
|
.FirstAsync();
|
||||||
throw new BusinessException("目标用户不存在", 404);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 校验目标用户与当前用户属于同一 OpenId
|
if (targetUser == null)
|
||||||
if (targetUser.OpenId != openId)
|
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);
|
throw new BusinessException("无法切换到该用户", 403);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (targetUser.Status == (int)UserStatusEnum.Disabled)
|
if (targetUser.Status == (int)UserStatusEnum.Disabled)
|
||||||
{
|
|
||||||
throw new BusinessException("目标账号已被禁用", 403);
|
throw new BusinessException("目标账号已被禁用", 403);
|
||||||
}
|
|
||||||
|
|
||||||
// 更新 IsLastOnline:目标用户设为 true,同 OpenId 下其他用户设为 false
|
// 更新 IsLastOnline(清除所有,设置目标为 true)
|
||||||
await usersRepository.Context.Updateable<Users>()
|
await wxUserRepository.Context.Updateable<Users>()
|
||||||
.SetColumns(u => u.IsLastOnline == false)
|
.SetColumns(u => u.IsLastOnline == false)
|
||||||
.Where(u => u.OpenId == openId && !u.IsDeleted)
|
.Where(u => u.WxUserId == wxUserId )
|
||||||
.ExecuteCommandAsync();
|
.ExecuteCommandAsync();
|
||||||
|
|
||||||
await usersRepository.Context.Updateable<Users>()
|
await wxUserRepository.Context.Updateable<Users>()
|
||||||
.SetColumns(u => u.IsLastOnline == true)
|
.SetColumns(u => u.IsLastOnline == true)
|
||||||
.Where(u => u.Id == input.UserId && !u.IsDeleted)
|
.Where(u => u.Id == input.UserId )
|
||||||
.ExecuteCommandAsync();
|
.ExecuteCommandAsync();
|
||||||
|
|
||||||
logger.LogInformation("IsLastOnline 已更新,目标用户 {UserId} 设为 true", input.UserId);
|
logger.LogInformation("IsLastOnline 已更新,目标用户 {UserId} 设为 true", input.UserId);
|
||||||
|
|
||||||
// TODO: 预留扩展逻辑 —— 加载用户详情、用户关联信息等
|
// 重新查询目标用户获取最新数据
|
||||||
// var userDetail = await LoadUserDetailAsync(targetUser.Id);
|
var refreshedUser = await wxUserRepository.Context.Queryable<Users>()
|
||||||
// var userRelations = await LoadUserRelationsAsync(targetUser.Id);
|
.Where(u => u.Id == input.UserId)
|
||||||
|
.FirstAsync();
|
||||||
// 重新查询目标用户以获取最新数据(含 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));
|
|
||||||
|
|
||||||
return new WeChatSwitchUserOutput
|
return new WeChatSwitchUserOutput
|
||||||
{
|
{
|
||||||
Token = token,
|
|
||||||
User = MapUserToOutput(refreshedUser)
|
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>
|
/// <summary>
|
||||||
/// 调用微信 code2session 接口
|
/// 调用微信 code2session 接口
|
||||||
/// </summary>
|
/// </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>
|
/// <summary>
|
||||||
/// 获取微信 access_token(带 Redis 缓存)
|
/// 获取微信 access_token(带 Redis 缓存)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private async Task<string> GetAccessTokenAsync(WeChatSettings settings)
|
private async Task<string> GetAccessTokenAsync(WeChatSettings settings)
|
||||||
{
|
{
|
||||||
// 先从 Redis 缓存获取
|
|
||||||
var cachedToken = await RedisHelper.GetAsync(AccessTokenCacheKey);
|
var cachedToken = await RedisHelper.GetAsync(AccessTokenCacheKey);
|
||||||
if (!string.IsNullOrWhiteSpace(cachedToken))
|
if (!string.IsNullOrWhiteSpace(cachedToken))
|
||||||
{
|
|
||||||
return cachedToken;
|
return cachedToken;
|
||||||
}
|
|
||||||
|
|
||||||
// 缓存未命中,调用微信接口获取
|
|
||||||
var url = string.Format(GetAccessTokenUrl, settings.AppId, settings.AppSecret);
|
var url = string.Format(GetAccessTokenUrl, settings.AppId, settings.AppSecret);
|
||||||
var response = await HttpHelper.GetAsync<WxAccessTokenResponse>(url);
|
var response = await HttpHelper.GetAsync<WxAccessTokenResponse>(url);
|
||||||
|
|
||||||
@ -293,7 +327,6 @@ public class WeChatAuthService(BaseRepository<Users> usersRepository, IConfigura
|
|||||||
throw new BusinessException("微信服务请求失败,请稍后重试", 500);
|
throw new BusinessException("微信服务请求失败,请稍后重试", 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 缓存 access_token,提前 5 分钟过期(微信默认 7200 秒)
|
|
||||||
var expiresIn = response.ExpiresIn > 300 ? response.ExpiresIn - 300 : response.ExpiresIn;
|
var expiresIn = response.ExpiresIn > 300 ? response.ExpiresIn - 300 : response.ExpiresIn;
|
||||||
await RedisHelper.SetAsync(AccessTokenCacheKey, response.AccessToken, TimeSpan.FromSeconds(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 ?? "未知错误";
|
var errMsg = response?.ErrMsg ?? "未知错误";
|
||||||
logger.LogWarning("获取手机号失败,errcode: {ErrCode}, errmsg: {ErrMsg}", response?.ErrCode, errMsg);
|
logger.LogWarning("获取手机号失败,errcode: {ErrCode}, errmsg: {ErrMsg}", response?.ErrCode, errMsg);
|
||||||
// 获取手机号失败不阻断登录流程,仅记录日志
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -325,39 +357,30 @@ public class WeChatAuthService(BaseRepository<Users> usersRepository, IConfigura
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
logger.LogError(ex, "调用微信获取手机号接口异常");
|
logger.LogError(ex, "调用微信获取手机号接口异常");
|
||||||
// 获取手机号失败不阻断登录流程
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 用户实体映射为输出 DTO
|
/// Users 实体映射为 WxUserOutput
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static WxUserOutput MapUserToOutput(Users user)
|
private static WxUserOutput MapUserToOutput(Users user)
|
||||||
{
|
{
|
||||||
return new WxUserOutput
|
return new WxUserOutput
|
||||||
{
|
{
|
||||||
Id = (long)user.Id,
|
Id = (long)user.Id,
|
||||||
OpenId = user.OpenId,
|
WxUserId = user.WxUserId,
|
||||||
UnionId = user.UnionId,
|
|
||||||
NickName = user.Name,
|
NickName = user.Name,
|
||||||
AvatarUrl = user.AvatarUrl,
|
AvatarUrl = user.AvatarUrl,
|
||||||
Phone = user.Phone,
|
|
||||||
Points = user.Points,
|
Points = user.Points,
|
||||||
GrowthPoints = user.GrowthPoints,
|
GrowthPoints = user.GrowthPoints,
|
||||||
Type = user.Type.ToString(),
|
Type = user.Type.ToString(),
|
||||||
Status = user.Status.ToString(),
|
Status = user.Status.ToString(),
|
||||||
IsLastOnline = user.IsLastOnline,
|
IsLastOnline = user.IsLastOnline,
|
||||||
CreatedAt = user.CreatedAt,
|
CreatedAt = user.CreatedAt
|
||||||
UpdatedAt = user.UpdatedAt,
|
|
||||||
CreatedBy = user.CreatedBy,
|
|
||||||
UpdatedBy = user.UpdatedBy
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取微信配置
|
|
||||||
/// </summary>
|
|
||||||
private WeChatSettings GetWeChatSettings()
|
private WeChatSettings GetWeChatSettings()
|
||||||
{
|
{
|
||||||
var settings = configuration.GetSection("WeChatSettings").Get<WeChatSettings>();
|
var settings = configuration.GetSection("WeChatSettings").Get<WeChatSettings>();
|
||||||
@ -369,9 +392,6 @@ public class WeChatAuthService(BaseRepository<Users> usersRepository, IConfigura
|
|||||||
return settings;
|
return settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取 JWT 配置
|
|
||||||
/// </summary>
|
|
||||||
private JwtSettings GetJwtSettings()
|
private JwtSettings GetJwtSettings()
|
||||||
{
|
{
|
||||||
var jwtSettings = configuration.GetSection("JwtSettings").Get<JwtSettings>()
|
var jwtSettings = configuration.GetSection("JwtSettings").Get<JwtSettings>()
|
||||||
@ -384,10 +404,10 @@ public class WeChatAuthService(BaseRepository<Users> usersRepository, IConfigura
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(jwtSettings.SecretKey))
|
if (string.IsNullOrWhiteSpace(jwtSettings.SecretKey))
|
||||||
{
|
|
||||||
throw new BusinessException("JWT 配置不完整", 500);
|
throw new BusinessException("JWT 配置不完整", 500);
|
||||||
}
|
|
||||||
|
|
||||||
return jwtSettings;
|
return jwtSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,7 +17,7 @@ public class BagController(IWxMallService mallService, ILogger<BagController> lo
|
|||||||
[HttpGet("items")]
|
[HttpGet("items")]
|
||||||
public async Task<BaseResponse<List<UserBagOutput>>> GetBagItems([FromQuery] string? itemType = null)
|
public async Task<BaseResponse<List<UserBagOutput>>> GetBagItems([FromQuery] string? itemType = null)
|
||||||
{
|
{
|
||||||
var userId = GetCurrentUserId();
|
var userId = GetCurrentWxUserId();
|
||||||
if (userId == null) return Fail("未获取到用户信息") as dynamic;
|
if (userId == null) return Fail("未获取到用户信息") as dynamic;
|
||||||
|
|
||||||
var items = await mallService.GetBagItemsAsync(userId.Value, itemType);
|
var items = await mallService.GetBagItemsAsync(userId.Value, itemType);
|
||||||
@ -30,7 +30,7 @@ public class BagController(IWxMallService mallService, ILogger<BagController> lo
|
|||||||
[HttpPost("useItem")]
|
[HttpPost("useItem")]
|
||||||
public async Task<BaseResponse<UseItemOutput>> UseItem([FromBody] UseItemInput input)
|
public async Task<BaseResponse<UseItemOutput>> UseItem([FromBody] UseItemInput input)
|
||||||
{
|
{
|
||||||
var userId = GetCurrentUserId();
|
var userId = GetCurrentWxUserId();
|
||||||
if (userId == null) return Fail("未获取到用户信息") as dynamic;
|
if (userId == null) return Fail("未获取到用户信息") as dynamic;
|
||||||
|
|
||||||
var result = await mallService.UseItemAsync(userId.Value, input);
|
var result = await mallService.UseItemAsync(userId.Value, input);
|
||||||
@ -43,7 +43,7 @@ public class BagController(IWxMallService mallService, ILogger<BagController> lo
|
|||||||
[HttpPost("equipSkin")]
|
[HttpPost("equipSkin")]
|
||||||
public async Task<BaseResponse<object>> EquipSkin([FromBody] EquipSkinInput input)
|
public async Task<BaseResponse<object>> EquipSkin([FromBody] EquipSkinInput input)
|
||||||
{
|
{
|
||||||
var userId = GetCurrentUserId();
|
var userId = GetCurrentWxUserId();
|
||||||
if (userId == null) return Fail("未获取到用户信息") as dynamic;
|
if (userId == null) return Fail("未获取到用户信息") as dynamic;
|
||||||
|
|
||||||
await mallService.EquipSkinAsync(userId.Value, input);
|
await mallService.EquipSkinAsync(userId.Value, input);
|
||||||
|
|||||||
@ -29,7 +29,7 @@ public class CheckInController : WeChatBaseController
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var userId = GetCurrentUserId();
|
var userId = GetCurrentWxUserId();
|
||||||
if (userId == null)
|
if (userId == null)
|
||||||
{
|
{
|
||||||
return BaseResponse<CheckInOutput>.Fail(ResultCode.DENY, "未获取到用户信息");
|
return BaseResponse<CheckInOutput>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||||
@ -59,7 +59,7 @@ public class CheckInController : WeChatBaseController
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var userId = GetCurrentUserId();
|
var userId = GetCurrentWxUserId();
|
||||||
if (userId == null)
|
if (userId == null)
|
||||||
{
|
{
|
||||||
return BaseResponse<CheckInInfoOutput>.Fail(ResultCode.DENY, "未获取到用户信息");
|
return BaseResponse<CheckInInfoOutput>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||||
|
|||||||
@ -19,7 +19,7 @@ public class CommunityController(IWeChatCommunityService communityService, ILogg
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var userId = GetCurrentUserId();
|
var userId = GetCurrentWxUserId();
|
||||||
if (userId == null)
|
if (userId == null)
|
||||||
{
|
{
|
||||||
return BaseResponse<WxFeedOutput>.Fail(ResultCode.DENY, "未获取到用户信息");
|
return BaseResponse<WxFeedOutput>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||||
@ -48,7 +48,7 @@ public class CommunityController(IWeChatCommunityService communityService, ILogg
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var userId = GetCurrentUserId();
|
var userId = GetCurrentWxUserId();
|
||||||
if (userId == null)
|
if (userId == null)
|
||||||
{
|
{
|
||||||
return BaseResponse<WxFeedOutput>.Fail(ResultCode.DENY, "未获取到用户信息");
|
return BaseResponse<WxFeedOutput>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||||
@ -77,7 +77,7 @@ public class CommunityController(IWeChatCommunityService communityService, ILogg
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var userId = GetCurrentUserId();
|
var userId = GetCurrentWxUserId();
|
||||||
if (userId == null)
|
if (userId == null)
|
||||||
{
|
{
|
||||||
return BaseResponse<WxLikeOutput>.Fail(ResultCode.DENY, "未获取到用户信息");
|
return BaseResponse<WxLikeOutput>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||||
@ -106,7 +106,7 @@ public class CommunityController(IWeChatCommunityService communityService, ILogg
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var userId = GetCurrentUserId();
|
var userId = GetCurrentWxUserId();
|
||||||
if (userId == null)
|
if (userId == null)
|
||||||
{
|
{
|
||||||
return BaseResponse<WxLikeOutput>.Fail(ResultCode.DENY, "未获取到用户信息");
|
return BaseResponse<WxLikeOutput>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||||
|
|||||||
@ -29,7 +29,7 @@ public class JournalController : WeChatBaseController
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var userId = GetCurrentUserId();
|
var userId = GetCurrentWxUserId();
|
||||||
if (userId == null)
|
if (userId == null)
|
||||||
{
|
{
|
||||||
return BaseResponse<BindJournalOutput>.Fail(ResultCode.DENY, "未获取到用户信息");
|
return BaseResponse<BindJournalOutput>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||||
@ -60,7 +60,7 @@ public class JournalController : WeChatBaseController
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var userId = GetCurrentUserId();
|
var userId = GetCurrentWxUserId();
|
||||||
if (userId == null)
|
if (userId == null)
|
||||||
{
|
{
|
||||||
return BaseResponse<PageListModel<BindJournalOutput>>.Fail(ResultCode.DENY, "未获取到用户信息");
|
return BaseResponse<PageListModel<BindJournalOutput>>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||||
|
|||||||
@ -17,7 +17,7 @@ public class MallController(IWxMallService mallService, ILogger<MallController>
|
|||||||
[HttpGet("products")]
|
[HttpGet("products")]
|
||||||
public async Task<BaseResponse<List<WxProductOutput>>> GetProducts([FromQuery] string? type = null)
|
public async Task<BaseResponse<List<WxProductOutput>>> GetProducts([FromQuery] string? type = null)
|
||||||
{
|
{
|
||||||
var userId = GetCurrentUserId();
|
var userId = GetCurrentWxUserId();
|
||||||
if (userId == null) return Fail("未获取到用户信息") as dynamic;
|
if (userId == null) return Fail("未获取到用户信息") as dynamic;
|
||||||
|
|
||||||
var products = await mallService.GetProductsAsync(userId.Value, type);
|
var products = await mallService.GetProductsAsync(userId.Value, type);
|
||||||
@ -31,7 +31,7 @@ public class MallController(IWxMallService mallService, ILogger<MallController>
|
|||||||
[HttpGet("product/{id}")]
|
[HttpGet("product/{id}")]
|
||||||
public async Task<BaseResponse<WxProductOutput>> GetProductDetail(long id)
|
public async Task<BaseResponse<WxProductOutput>> GetProductDetail(long id)
|
||||||
{
|
{
|
||||||
var userId = GetCurrentUserId();
|
var userId = GetCurrentWxUserId();
|
||||||
if (userId == null) return Fail("未获取到用户信息") as dynamic;
|
if (userId == null) return Fail("未获取到用户信息") as dynamic;
|
||||||
|
|
||||||
var product = await mallService.GetProductDetailAsync(userId.Value, id);
|
var product = await mallService.GetProductDetailAsync(userId.Value, id);
|
||||||
@ -47,7 +47,7 @@ public class MallController(IWxMallService mallService, ILogger<MallController>
|
|||||||
[HttpPost("exchange")]
|
[HttpPost("exchange")]
|
||||||
public async Task<BaseResponse<ExchangeOutput>> Exchange([FromBody] ExchangeInput input)
|
public async Task<BaseResponse<ExchangeOutput>> Exchange([FromBody] ExchangeInput input)
|
||||||
{
|
{
|
||||||
var userId = GetCurrentUserId();
|
var userId = GetCurrentWxUserId();
|
||||||
if (userId == null) return Fail("未获取到用户信息") as dynamic;
|
if (userId == null) return Fail("未获取到用户信息") as dynamic;
|
||||||
|
|
||||||
var result = await mallService.ExchangeAsync(userId.Value, input);
|
var result = await mallService.ExchangeAsync(userId.Value, input);
|
||||||
@ -61,7 +61,7 @@ public class MallController(IWxMallService mallService, ILogger<MallController>
|
|||||||
[HttpGet("exchangeRecords")]
|
[HttpGet("exchangeRecords")]
|
||||||
public async Task<BaseResponse<List<ExchangeRecordOutput>>> GetExchangeRecords([FromQuery] int limit = 20)
|
public async Task<BaseResponse<List<ExchangeRecordOutput>>> GetExchangeRecords([FromQuery] int limit = 20)
|
||||||
{
|
{
|
||||||
var userId = GetCurrentUserId();
|
var userId = GetCurrentWxUserId();
|
||||||
if (userId == null) return Fail("未获取到用户信息") as dynamic;
|
if (userId == null) return Fail("未获取到用户信息") as dynamic;
|
||||||
|
|
||||||
var records = await mallService.GetExchangeRecordsAsync(userId.Value, limit);
|
var records = await mallService.GetExchangeRecordsAsync(userId.Value, limit);
|
||||||
|
|||||||
@ -18,7 +18,7 @@ public class MedalController(IMedalService medalService, ILogger<MedalController
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var userId = GetCurrentUserId();
|
var userId = GetCurrentWxUserId();
|
||||||
if (userId == null)
|
if (userId == null)
|
||||||
{
|
{
|
||||||
return BaseResponse<List<WxMedalListOutput>>.Fail(ResultCode.DENY, "未获取到用户信息");
|
return BaseResponse<List<WxMedalListOutput>>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||||
@ -47,7 +47,7 @@ public class MedalController(IMedalService medalService, ILogger<MedalController
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var userId = GetCurrentUserId();
|
var userId = GetCurrentWxUserId();
|
||||||
if (userId == null)
|
if (userId == null)
|
||||||
{
|
{
|
||||||
return BaseResponse<List<WxUserMedalOutput>>.Fail(ResultCode.DENY, "未获取到用户信息");
|
return BaseResponse<List<WxUserMedalOutput>>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||||
@ -76,7 +76,7 @@ public class MedalController(IMedalService medalService, ILogger<MedalController
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var userId = GetCurrentUserId();
|
var userId = GetCurrentWxUserId();
|
||||||
if (userId == null)
|
if (userId == null)
|
||||||
{
|
{
|
||||||
return BaseResponse<object>.Fail(ResultCode.DENY, "未获取到用户信息");
|
return BaseResponse<object>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||||
|
|||||||
@ -28,7 +28,7 @@ public class PetController : WeChatBaseController
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var userId = GetCurrentUserId();
|
var userId = GetCurrentWxUserId();
|
||||||
if (userId == null)
|
if (userId == null)
|
||||||
{
|
{
|
||||||
return BaseResponse<PetOutput>.Fail(ResultCode.DENY, "未获取到用户信息");
|
return BaseResponse<PetOutput>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||||
@ -62,7 +62,7 @@ public class PetController : WeChatBaseController
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var userId = GetCurrentUserId();
|
var userId = GetCurrentWxUserId();
|
||||||
if (userId == null)
|
if (userId == null)
|
||||||
{
|
{
|
||||||
return BaseResponse<FeedPetOutput>.Fail(ResultCode.DENY, "未获取到用户信息");
|
return BaseResponse<FeedPetOutput>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||||
@ -91,7 +91,7 @@ public class PetController : WeChatBaseController
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var userId = GetCurrentUserId();
|
var userId = GetCurrentWxUserId();
|
||||||
if (userId == null)
|
if (userId == null)
|
||||||
{
|
{
|
||||||
return BaseResponse<PageListModel<FeedingRecordOutput>>.Fail(ResultCode.DENY, "未获取到用户信息");
|
return BaseResponse<PageListModel<FeedingRecordOutput>>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||||
|
|||||||
@ -23,7 +23,7 @@ public class WeChatAuthController : WeChatBaseController
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 微信小程序登录(首次创建用户,非首次直接登录)
|
/// 微信小程序登录(首次仅创建 WxUser,不自动创建 User)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="input">登录输入(含微信 code 和可选的手机号 code)</param>
|
/// <param name="input">登录输入(含微信 code 和可选的手机号 code)</param>
|
||||||
/// <returns>登录结果(含 Token 和用户列表)</returns>
|
/// <returns>登录结果(含 Token 和用户列表)</returns>
|
||||||
@ -75,22 +75,18 @@ public class WeChatAuthController : WeChatBaseController
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 切换用户(同一 OpenId 下切换身份)
|
/// 切换用户(同一 WxUser 下切换 User 身份)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="input">切换用户输入(含目标用户ID)</param>
|
|
||||||
/// <returns>切换结果(含新 Token 和目标用户详情)</returns>
|
|
||||||
[HttpPost("switchUser")]
|
[HttpPost("switchUser")]
|
||||||
public async Task<BaseResponse<WeChatSwitchUserOutput>> SwitchUserAsync([FromBody] WeChatSwitchUserInput input)
|
public async Task<BaseResponse<WeChatSwitchUserOutput>> SwitchUserAsync([FromBody] WeChatSwitchUserInput input)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var userId = GetCurrentUserId();
|
var wxUserId = GetCurrentWxUserId();
|
||||||
if (userId == null)
|
if (wxUserId == null)
|
||||||
{
|
|
||||||
return BaseResponse<WeChatSwitchUserOutput>.Fail(ResultCode.DENY, "未获取到用户信息");
|
return BaseResponse<WeChatSwitchUserOutput>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||||
}
|
|
||||||
|
|
||||||
var result = await _weChatAuthService.SwitchUserAsync(userId.Value, input);
|
var result = await _weChatAuthService.SwitchUserAsync(wxUserId.Value, input);
|
||||||
return Success(result);
|
return Success(result);
|
||||||
}
|
}
|
||||||
catch (BusinessException ex)
|
catch (BusinessException ex)
|
||||||
@ -104,4 +100,58 @@ public class WeChatAuthController : WeChatBaseController
|
|||||||
return BaseResponse<WeChatSwitchUserOutput>.Fail("切换用户失败,请稍后重试");
|
return BaseResponse<WeChatSwitchUserOutput>.Fail("切换用户失败,请稍后重试");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取当前微信用户下的所有用户列表
|
||||||
|
/// </summary>
|
||||||
|
[HttpGet("users")]
|
||||||
|
public async Task<BaseResponse<List<WxUserOutput>>> GetUsersAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var wxUserId = GetCurrentWxUserId();
|
||||||
|
if (wxUserId == null)
|
||||||
|
return BaseResponse<List<WxUserOutput>>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||||
|
|
||||||
|
var result = await _weChatAuthService.GetUsersAsync(wxUserId.Value);
|
||||||
|
return Success(result);
|
||||||
|
}
|
||||||
|
catch (BusinessException ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex, "获取用户列表业务异常: {Message}", ex.Message);
|
||||||
|
return BaseResponse<List<WxUserOutput>>.Fail(ex.Message);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "获取用户列表系统异常");
|
||||||
|
return BaseResponse<List<WxUserOutput>>.Fail("获取用户列表失败,请稍后重试");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 在当前微信用户下新增用户(子用户/角色)
|
||||||
|
/// </summary>
|
||||||
|
[HttpPost("createUser")]
|
||||||
|
public async Task<BaseResponse<WxUserOutput>> CreateUserAsync([FromBody] CreateChildUserInput input)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var wxUserId = GetCurrentWxUserId();
|
||||||
|
if (wxUserId == null)
|
||||||
|
return BaseResponse<WxUserOutput>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||||
|
|
||||||
|
var result = await _weChatAuthService.CreateUserAsync(wxUserId.Value, input);
|
||||||
|
return Success(result);
|
||||||
|
}
|
||||||
|
catch (BusinessException ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex, "新增用户业务异常: {Message}", ex.Message);
|
||||||
|
return BaseResponse<WxUserOutput>.Fail(ex.Message);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "新增用户系统异常");
|
||||||
|
return BaseResponse<WxUserOutput>.Fail("新增用户失败,请稍后重试");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,10 +16,10 @@ namespace QYZH.InteractiveMagazine.WebApi.Controllers.WeChat;
|
|||||||
public abstract class WeChatBaseController : ControllerBase
|
public abstract class WeChatBaseController : ControllerBase
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取当前用户ID
|
/// 获取当前微信用户ID(WxUser.Id,来自 JWT)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>用户ID</returns>
|
/// <returns>WxUser ID</returns>
|
||||||
protected long? GetCurrentUserId()
|
protected long? GetCurrentWxUserId()
|
||||||
{
|
{
|
||||||
var userIdClaim = User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier);
|
var userIdClaim = User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier);
|
||||||
if (userIdClaim != null && long.TryParse(userIdClaim.Value, out var userId))
|
if (userIdClaim != null && long.TryParse(userIdClaim.Value, out var userId))
|
||||||
|
|||||||
Reference in New Issue
Block a user