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))