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