1. 新增UploadDomain实体与上传地址分配逻辑,为用户分配可用上传域名 2. 新增绑定期刊消息推送,通过RabbitMQ传递绑定信息 3. 优化答题评分逻辑,新增完成度阈值判定与社区消息插入 4. 新增配置项用于评分阈值配置 5. 补充相关DTO与实体类字段,完善数据传输与存储
490 lines
20 KiB
C#
490 lines
20 KiB
C#
using Microsoft.Extensions.Configuration;
|
||
using Microsoft.Extensions.Logging;
|
||
using QYZH.InteractiveMagazine.Common.Helpers;
|
||
using QYZH.InteractiveMagazine.Infrastructure.Auth;
|
||
using QYZH.InteractiveMagazine.IService;
|
||
using QYZH.InteractiveMagazine.Models.Common;
|
||
using QYZH.InteractiveMagazine.Models.Dto;
|
||
using QYZH.InteractiveMagazine.Models.Entity;
|
||
using QYZH.InteractiveMagazine.Models.Enum;
|
||
using QYZH.InteractiveMagazine.Models.Settings;
|
||
using QYZH.InteractiveMagazine.Models.WeChat;
|
||
using QYZH.InteractiveMagazine.Repository;
|
||
|
||
namespace QYZH.InteractiveMagazine.Service;
|
||
|
||
/// <summary>
|
||
/// 微信小程序认证服务实现
|
||
/// 登录基于 WxUser 表(微信身份),多用户管理基于 Users 表(角色/子用户)
|
||
/// </summary>
|
||
public class WeChatAuthService(
|
||
BaseRepository<WxUser> wxUserRepository,
|
||
BaseRepository<UploadDomain> uploadDomainRepository,
|
||
IConfiguration configuration,
|
||
ILogger<WeChatAuthService> logger,
|
||
IPetService petService)
|
||
: BaseRepository<WxUser>, IWeChatAuthService
|
||
{
|
||
private const string AccessTokenCacheKey = "InteractiveMagazine:WeChat:AccessToken";
|
||
private const string Code2SessionUrl = "https://api.weixin.qq.com/sns/jscode2session?appid={0}&secret={1}&js_code={2}&grant_type=authorization_code";
|
||
private const string GetAccessTokenUrl = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={0}&secret={1}";
|
||
private const string GetPhoneNumberUrl = "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token={0}";
|
||
|
||
/// <summary>
|
||
/// 微信小程序登录
|
||
/// 流程: code2session → 查找/创建 WxUser → 更新手机号 → 查询 Users 列表 → 生成 Token
|
||
/// </summary>
|
||
public async Task<WeChatLoginOutput> LoginAsync(WeChatLoginInput input)
|
||
{
|
||
logger.LogInformation("微信小程序登录");
|
||
|
||
if (string.IsNullOrWhiteSpace(input.Code))
|
||
throw new BusinessException("微信登录凭证 code 不能为空", ResultCode.BAD_REQUEST);
|
||
|
||
var weChatSettings = GetWeChatSettings();
|
||
|
||
// 1. 调用微信 code2session
|
||
var wxResponse = await CallCode2SessionAsync(weChatSettings, input.Code);
|
||
if (wxResponse == null || wxResponse.ErrCode != 0 || string.IsNullOrWhiteSpace(wxResponse.OpenId))
|
||
{
|
||
var errMsg = wxResponse?.ErrMsg ?? "未知错误";
|
||
logger.LogWarning("微信 code2session 接口调用失败,errcode: {ErrCode}, errmsg: {ErrMsg}", wxResponse?.ErrCode, errMsg);
|
||
throw new BusinessException($"微信登录失败:{errMsg}", ResultCode.BAD_REQUEST);
|
||
}
|
||
|
||
logger.LogInformation("微信 code2session 成功,OpenId: {OpenId}", wxResponse.OpenId);
|
||
|
||
// 2. 获取手机号(如果传入了 PhoneCode)
|
||
string? phone = null;
|
||
if (!string.IsNullOrWhiteSpace(input.PhoneCode))
|
||
{
|
||
phone = await GetPhoneNumberAsync(weChatSettings, input.PhoneCode);
|
||
logger.LogInformation("获取手机号,OpenId: {OpenId}, Phone: {Phone}", wxResponse.OpenId, phone ?? "null");
|
||
}
|
||
|
||
// 3. 查找或创建 WxUser
|
||
var wxUser = await wxUserRepository.Context.Queryable<WxUser>()
|
||
.Where(w => w.OpenId == wxResponse.OpenId && !w.IsDeleted)
|
||
.FirstAsync();
|
||
|
||
if (wxUser == null)
|
||
{
|
||
// 首次登录:仅创建 WxUser,不自动创建 User(用户需手动创建)
|
||
wxUser = new WxUser
|
||
{
|
||
Name = $"wx_{wxResponse.OpenId[^8..]}",
|
||
AvatarUrl = string.Empty,
|
||
OpenId = wxResponse.OpenId,
|
||
UnionId = wxResponse.UnionId,
|
||
Phone = phone,
|
||
Status = 1,
|
||
IsDeleted = false,
|
||
CreatedBy = "WeChat",
|
||
CreatedAt = DateTime.Now,
|
||
UpdatedBy = "WeChat",
|
||
UpdatedAt = DateTime.Now
|
||
};
|
||
|
||
await wxUserRepository.InsertAsync(wxUser);
|
||
|
||
|
||
logger.LogInformation("WxUser 创建成功,WxUserId: {WxUserId}, OpenId: {OpenId}", wxUser.Id, wxResponse.OpenId);
|
||
}
|
||
else
|
||
{
|
||
// 非首次登录:更新 UnionId 和手机号
|
||
if (!string.IsNullOrWhiteSpace(wxResponse.UnionId) && string.IsNullOrWhiteSpace(wxUser.UnionId))
|
||
{
|
||
await wxUserRepository.Context.Updateable<WxUser>()
|
||
.SetColumns(w => w.UnionId == wxResponse.UnionId)
|
||
.Where(w => w.Id == wxUser.Id)
|
||
.ExecuteCommandAsync();
|
||
wxUser.UnionId = wxResponse.UnionId;
|
||
}
|
||
|
||
if (!string.IsNullOrWhiteSpace(phone))
|
||
{
|
||
await wxUserRepository.Context.Updateable<WxUser>()
|
||
.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);
|
||
}
|
||
}
|
||
|
||
// 查询该 WxUser 下所有 Users(首次登录时为空列表)
|
||
var users = await wxUserRepository.Context.Queryable<Users>()
|
||
.Where(u => u.WxUserId == wxUser.Id && !u.IsDeleted)
|
||
.ToListAsync();
|
||
|
||
logger.LogInformation("微信登录成功,WxUserId: {WxUserId} 下存在 {Count} 个用户", wxUser.Id, users.Count);
|
||
|
||
return await BuildLoginOutputAsync(wxUser, users);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 微信小程序快捷登录(通过 OpenId 直接登录)
|
||
/// </summary>
|
||
public async Task<WeChatLoginOutput> QuickLoginAsync(WeChatQuickLoginInput input)
|
||
{
|
||
logger.LogInformation("微信快捷登录,OpenId: {OpenId}", input.OpenId);
|
||
|
||
if (string.IsNullOrWhiteSpace(input.OpenId))
|
||
throw new BusinessException("OpenId 不能为空", ResultCode.BAD_REQUEST);
|
||
|
||
var wxUser = await wxUserRepository.Context.Queryable<WxUser>()
|
||
.Where(w => w.OpenId == input.OpenId && !w.IsDeleted)
|
||
.FirstAsync();
|
||
|
||
if (wxUser == null)
|
||
{
|
||
logger.LogWarning("快捷登录失败,OpenId: {OpenId} 下无 WxUser", input.OpenId);
|
||
throw new BusinessException("未找到该微信账号关联的用户,请先完成注册", ResultCode.NOT_FOUND);
|
||
}
|
||
|
||
var users = await wxUserRepository.Context.Queryable<Users>()
|
||
.Where(u => u.WxUserId == wxUser.Id && !u.IsDeleted)
|
||
.ToListAsync();
|
||
|
||
logger.LogInformation("快捷登录成功,WxUserId: {WxUserId}, {Count} 个用户", wxUser.Id, users.Count);
|
||
|
||
return await BuildLoginOutputAsync(wxUser, users);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 切换用户(同一 WxUser 下切换 User 身份,重新生成 Token)
|
||
/// </summary>
|
||
public async Task<WeChatSwitchUserOutput> SwitchUserAsync(long wxUserId, long currentUserId, WeChatSwitchUserInput input)
|
||
{
|
||
logger.LogInformation("切换用户,WxUserId: {WxUserId}, 当前 UserId: {CurrentUserId}, 目标 UserId: {TargetUserId}",
|
||
wxUserId, currentUserId, input.UserId);
|
||
|
||
// 查询目标用户
|
||
var targetUser = await wxUserRepository.Context.Queryable<Users>()
|
||
.Where(u => u.Id == input.UserId && !u.IsDeleted)
|
||
.FirstAsync();
|
||
|
||
if (targetUser == null)
|
||
throw new BusinessException("目标用户不存在", ResultCode.NOT_FOUND);
|
||
|
||
// 校验目标用户属于同一 WxUser
|
||
if (targetUser.WxUserId != wxUserId)
|
||
{
|
||
logger.LogWarning("切换用户失败,WxUserId 不匹配,当前: {Current}, 目标: {Target}", wxUserId, targetUser.WxUserId);
|
||
throw new BusinessException("无法切换到该用户", ResultCode.FORBIDDEN);
|
||
}
|
||
|
||
if (targetUser.Status == (int)UserStatusEnum.Disabled)
|
||
throw new BusinessException("目标账号已被禁用", ResultCode.FORBIDDEN);
|
||
|
||
// 更新 IsLastOnline(清除所有,设置目标为 true)
|
||
await wxUserRepository.Context.Updateable<Users>()
|
||
.SetColumns(u => u.IsLastOnline == false)
|
||
.Where(u => u.WxUserId == wxUserId && !u.IsDeleted)
|
||
.ExecuteCommandAsync();
|
||
|
||
await wxUserRepository.Context.Updateable<Users>()
|
||
.SetColumns(u => u.IsLastOnline == true)
|
||
.Where(u => u.Id == input.UserId && !u.IsDeleted)
|
||
.ExecuteCommandAsync();
|
||
|
||
logger.LogInformation("IsLastOnline 已更新,目标用户 {UserId} 设为 true", input.UserId);
|
||
|
||
// 重新查询目标用户获取最新数据
|
||
var refreshedUser = await wxUserRepository.Context.Queryable<Users>()
|
||
.Where(u => u.Id == input.UserId && !u.IsDeleted)
|
||
.FirstAsync();
|
||
|
||
// 生成新 JWT Token(WxUserId + 新 UserId)
|
||
var jwtSettings = GetJwtSettings();
|
||
var token = JwtHelper.GenerateToken(wxUserId, refreshedUser.Id, refreshedUser.Name ?? string.Empty, jwtSettings);
|
||
|
||
// 清除旧 Redis Token,写入新 Token
|
||
await RedisHelper.DelAsync(JwtHelper.BuildWeChatTokenKey(wxUserId, currentUserId));
|
||
await RedisHelper.SetAsync(JwtHelper.BuildWeChatTokenKey(wxUserId, refreshedUser.Id), token, TimeSpan.FromMinutes(jwtSettings.ExpiryMinutes));
|
||
|
||
return new WeChatSwitchUserOutput
|
||
{
|
||
Token = token,
|
||
User = MapUserToOutput(refreshedUser)
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取当前 WxUser 下所有用户列表
|
||
/// </summary>
|
||
public async Task<List<WxUserOutput>> GetUsersAsync(long wxUserId)
|
||
{
|
||
var users = await wxUserRepository.Context.Queryable<Users>()
|
||
.Where(u => u.WxUserId == wxUserId && !u.IsDeleted)
|
||
.OrderBy(u => u.IsLastOnline, SqlSugar.OrderByType.Desc)
|
||
.OrderBy(u => u.CreatedAt, SqlSugar.OrderByType.Desc)
|
||
.ToListAsync();
|
||
|
||
return users.Select(MapUserToOutput).ToList();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 在当前 WxUser 下新增用户(wxUserId 来自 JWT)
|
||
/// </summary>
|
||
public async Task<WxUserOutput> CreateUserAsync(long wxUserId, CreateChildUserInput input)
|
||
{
|
||
logger.LogInformation("新增用户,WxUserId: {WxUserId}, Name: {Name}", wxUserId, input.Name);
|
||
|
||
if (string.IsNullOrWhiteSpace(input.Name))
|
||
throw new BusinessException("昵称不能为空", ResultCode.BAD_REQUEST);
|
||
|
||
// 校验 WxUser 是否存在
|
||
var wxUser = await wxUserRepository.Context.Queryable<WxUser>()
|
||
.Where(w => w.Id == wxUserId)
|
||
.FirstAsync();
|
||
|
||
if (wxUser == null)
|
||
throw new BusinessException("微信用户不存在", ResultCode.NOT_FOUND);
|
||
|
||
// 创建新用户
|
||
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
|
||
};
|
||
|
||
await UseTranAsync(async () =>
|
||
{
|
||
var uploadDomain = await uploadDomainRepository.Queryable()
|
||
.Where(d => d.Status == 1 && !d.IsDeleted)
|
||
.OrderBy(d => d.AssignedCount, SqlSugar.OrderByType.Asc)
|
||
.OrderBy(d => d.Id, SqlSugar.OrderByType.Asc)
|
||
.FirstAsync();
|
||
|
||
BusinessException.ThrowIf(uploadDomain == null, "暂无可用上传地址,请联系管理员", ResultCode.UNPROCESSABLE_ENTITY);
|
||
|
||
newUser.UploadDomain = uploadDomain!.Domain;
|
||
|
||
await wxUserRepository.Context.Insertable(newUser).ExecuteCommandAsync();
|
||
|
||
await uploadDomainRepository.Updateable()
|
||
.SetColumns(d => d.AssignedCount == d.AssignedCount + 1)
|
||
.SetColumns(d => d.UpdatedBy == wxUserId.ToString())
|
||
.SetColumns(d => d.UpdatedAt == DateTime.Now)
|
||
.Where(d => d.Id == uploadDomain.Id)
|
||
.ExecuteCommandAsync();
|
||
});
|
||
|
||
logger.LogInformation("新用户创建成功,UserId: {UserId}, WxUserId: {WxUserId}, Name: {Name}",
|
||
newUser.Id, wxUserId, input.Name);
|
||
|
||
// 为新用户创建默认宠物
|
||
try { await petService.CreateDefaultPetAsync(newUser.Id); }
|
||
catch (Exception ex) { logger.LogError(ex, "新用户创建默认宠物失败,UserId: {UserId}", newUser.Id); }
|
||
|
||
return MapUserToOutput(newUser);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 修改家长名字(WxUser.Name)
|
||
/// </summary>
|
||
public async Task<WxUserInfoOutput> UpdateWxUserNameAsync(long wxUserId, UpdateWxUserNameInput input)
|
||
{
|
||
logger.LogInformation("修改家长名字,WxUserId: {WxUserId}, NewName: {Name}", wxUserId, input.Name);
|
||
|
||
if (string.IsNullOrWhiteSpace(input.Name))
|
||
throw new BusinessException("名字不能为空", ResultCode.BAD_REQUEST);
|
||
|
||
var wxUser = await wxUserRepository.Context.Queryable<WxUser>()
|
||
.Where(w => w.Id == wxUserId && !w.IsDeleted)
|
||
.FirstAsync();
|
||
|
||
if (wxUser == null)
|
||
throw new BusinessException("微信用户不存在", ResultCode.NOT_FOUND);
|
||
|
||
await wxUserRepository.Context.Updateable<WxUser>()
|
||
.SetColumns(w => w.Name == input.Name.Trim())
|
||
.SetColumns(w => w.UpdatedAt == DateTime.Now)
|
||
.SetColumns(w => w.UpdatedBy == wxUserId.ToString())
|
||
.Where(w => w.Id == wxUserId)
|
||
.ExecuteCommandAsync();
|
||
|
||
wxUser.Name = input.Name.Trim();
|
||
|
||
logger.LogInformation("家长名字修改成功,WxUserId: {WxUserId}, NewName: {Name}", wxUserId, input.Name);
|
||
|
||
return new WxUserInfoOutput
|
||
{
|
||
Id = wxUser.Id,
|
||
OpenId = wxUser.OpenId,
|
||
UnionId = wxUser.UnionId,
|
||
Name = wxUser.Name,
|
||
AvatarUrl = wxUser.AvatarUrl,
|
||
Phone = wxUser.Phone
|
||
};
|
||
}
|
||
|
||
#region 私有辅助方法
|
||
|
||
/// <summary>
|
||
/// 构建登录输出(JWT 同时携带 WxUserId 和当前激活 UserId)
|
||
/// </summary>
|
||
private async Task<WeChatLoginOutput> BuildLoginOutputAsync(WxUser wxUser, List<Users> users)
|
||
{
|
||
var activeUser = users.FirstOrDefault(u => u.IsLastOnline) ?? users.FirstOrDefault();
|
||
|
||
var userId = activeUser?.Id ?? 0L;
|
||
var userName = activeUser?.Name ?? wxUser.Name ?? wxUser.OpenId;
|
||
|
||
var jwtSettings = GetJwtSettings();
|
||
var token = JwtHelper.GenerateToken(wxUser.Id, userId, userName, jwtSettings);
|
||
|
||
await RedisHelper.SetAsync(JwtHelper.BuildWeChatTokenKey(wxUser.Id, userId), token, TimeSpan.FromMinutes(jwtSettings.ExpiryMinutes));
|
||
|
||
return new WeChatLoginOutput
|
||
{
|
||
Token = token,
|
||
CurrentUserId = userId,
|
||
WxUser = new WxUserInfoOutput
|
||
{
|
||
Id = wxUser.Id,
|
||
OpenId = wxUser.OpenId,
|
||
UnionId = wxUser.UnionId,
|
||
Name = wxUser.Name,
|
||
AvatarUrl = wxUser.AvatarUrl,
|
||
Phone = wxUser.Phone
|
||
},
|
||
Users = users.Select(MapUserToOutput).ToList()
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 调用微信 code2session 接口
|
||
/// </summary>
|
||
private async Task<WxCode2SessionResponse?> CallCode2SessionAsync(WeChatSettings settings, string code)
|
||
{
|
||
var url = string.Format(Code2SessionUrl, settings.AppId, settings.AppSecret, code);
|
||
try
|
||
{
|
||
return await HttpHelper.GetAsync<WxCode2SessionResponse>(url);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.LogError(ex, "调用微信 code2session 接口异常,URL: {Url}", url);
|
||
throw new BusinessException("微信服务请求失败,请稍后重试", ResultCode.GLOBAL_ERROR);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取微信 access_token(带 Redis 缓存)
|
||
/// </summary>
|
||
private async Task<string> GetAccessTokenAsync(WeChatSettings settings)
|
||
{
|
||
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<WxAccessTokenResponse>(url);
|
||
|
||
if (response == null || response.ErrCode != 0 || string.IsNullOrWhiteSpace(response.AccessToken))
|
||
{
|
||
var errMsg = response?.ErrMsg ?? "未知错误";
|
||
logger.LogError("获取微信 access_token 失败,errcode: {ErrCode}, errmsg: {ErrMsg}", response?.ErrCode, errMsg);
|
||
throw new BusinessException("微信服务请求失败,请稍后重试", ResultCode.GLOBAL_ERROR);
|
||
}
|
||
|
||
var expiresIn = response.ExpiresIn > 300 ? response.ExpiresIn - 300 : response.ExpiresIn;
|
||
await RedisHelper.SetAsync(AccessTokenCacheKey, response.AccessToken, TimeSpan.FromSeconds(expiresIn));
|
||
|
||
logger.LogInformation("获取微信 access_token 成功,有效期: {ExpiresIn} 秒", expiresIn);
|
||
return response.AccessToken;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 通过 phone_code 获取微信用户手机号
|
||
/// </summary>
|
||
private async Task<string?> GetPhoneNumberAsync(WeChatSettings settings, string phoneCode)
|
||
{
|
||
try
|
||
{
|
||
var accessToken = await GetAccessTokenAsync(settings);
|
||
var url = string.Format(GetPhoneNumberUrl, accessToken);
|
||
var response = await HttpHelper.PostAsync<WxPhoneNumberResponse>(url, new { code = phoneCode });
|
||
|
||
if (response == null || response.ErrCode != 0 || response.PhoneInfo == null)
|
||
{
|
||
var errMsg = response?.ErrMsg ?? "未知错误";
|
||
logger.LogWarning("获取手机号失败,errcode: {ErrCode}, errmsg: {ErrMsg}", response?.ErrCode, errMsg);
|
||
return null;
|
||
}
|
||
|
||
return response.PhoneInfo.PurePhoneNumber ?? response.PhoneInfo.PhoneNumber;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.LogError(ex, "调用微信获取手机号接口异常");
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Users 实体映射为 WxUserOutput
|
||
/// </summary>
|
||
private static WxUserOutput MapUserToOutput(Users user)
|
||
{
|
||
return new WxUserOutput
|
||
{
|
||
Id = (long)user.Id,
|
||
WxUserId = user.WxUserId,
|
||
NickName = user.Name,
|
||
AvatarUrl = user.AvatarUrl,
|
||
Points = user.Points,
|
||
GrowthPoints = user.GrowthPoints,
|
||
Type = user.Type.ToString(),
|
||
Status = user.Status.ToString(),
|
||
IsLastOnline = user.IsLastOnline,
|
||
CreatedAt = user.CreatedAt,
|
||
UploadDomain = user.UploadDomain
|
||
};
|
||
}
|
||
|
||
private WeChatSettings GetWeChatSettings()
|
||
{
|
||
var settings = configuration.GetSection("WeChatSettings").Get<WeChatSettings>();
|
||
if (settings == null || string.IsNullOrWhiteSpace(settings.AppId) || string.IsNullOrWhiteSpace(settings.AppSecret))
|
||
{
|
||
logger.LogError("微信配置不完整,请检查 appsettings.json 中的 WeChatSettings 节点");
|
||
throw new BusinessException("微信配置不完整,请联系系统管理员", ResultCode.GLOBAL_ERROR);
|
||
}
|
||
return settings;
|
||
}
|
||
|
||
private JwtSettings GetJwtSettings()
|
||
{
|
||
var jwtSettings = configuration.GetSection("JwtSettings").Get<JwtSettings>()
|
||
?? new JwtSettings
|
||
{
|
||
Issuer = "QYZH.InteractiveMagazine",
|
||
Audience = "QYZH.InteractiveMagazine",
|
||
SecretKey = "your-256-bit-secret-key-here-change-in-production",
|
||
ExpiryMinutes = 120
|
||
};
|
||
|
||
if (string.IsNullOrWhiteSpace(jwtSettings.SecretKey))
|
||
throw new BusinessException("JWT 配置不完整", ResultCode.GLOBAL_ERROR);
|
||
|
||
return jwtSettings;
|
||
}
|
||
|
||
#endregion
|
||
}
|