feat: 初始化后台管理模块与基础业务框架
1. 新增依赖注入生命周期标记接口、基础仓储与管理员仓储实现 2. 新增管理员认证与用户服务接口,补充认证相关DTO 3. 重构实体审计字段命名,统一Created/UpdatedAt规范 4. 新增大量业务实体类与API版本枚举配置 5. 集成Autofac依赖注入、JWT自动刷新与跨域配置 6. 替换原有微信小程序与旧认证服务为后台管理系统架构 7. 完善Swagger文档配置与项目基础部署配置
This commit is contained in:
168
QYZH.InteractiveMagazine.Service/AdminAuthService.cs
Normal file
168
QYZH.InteractiveMagazine.Service/AdminAuthService.cs
Normal file
@ -0,0 +1,168 @@
|
||||
using BCrypt.Net;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using QYZH.InteractiveMagazine.Infrastructure.Auth;
|
||||
using QYZH.InteractiveMagazine.Infrastructure.Cache;
|
||||
using QYZH.InteractiveMagazine.IService;
|
||||
using QYZH.InteractiveMagazine.IService.Dto;
|
||||
using QYZH.InteractiveMagazine.Models.Common;
|
||||
using QYZH.InteractiveMagazine.Models.Entity;
|
||||
using QYZH.InteractiveMagazine.Models.Settings;
|
||||
using QYZH.InteractiveMagazine.Repository;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.Service;
|
||||
|
||||
public class AdminAuthService : IAdminAuthService
|
||||
{
|
||||
private readonly IAdminUserRepository _adminUserRepository;
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly ILogger<AdminAuthService> _logger;
|
||||
|
||||
private const string TokenKeyPrefix = "InteractiveMagazine:AdminAuth:Token";
|
||||
private const string UserInfoKeyPrefix = "InteractiveMagazine:AdminAuth:UserInfo";
|
||||
|
||||
public AdminAuthService(IAdminUserRepository adminUserRepository, IConfiguration configuration, ILogger<AdminAuthService> logger)
|
||||
{
|
||||
_adminUserRepository = adminUserRepository;
|
||||
_configuration = configuration;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<AdminLoginOutput> LoginAsync(AdminLoginInput input)
|
||||
{
|
||||
_logger.LogInformation("管理员登录尝试,用户名: {UserName}", input.UserName);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(input.UserName))
|
||||
{
|
||||
throw new BusinessException("用户名不能为空", 400);
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(input.Password))
|
||||
{
|
||||
throw new BusinessException("密码不能为空", 400);
|
||||
}
|
||||
|
||||
var adminUser = await _adminUserRepository.GetByUserNameAsync(input.UserName);
|
||||
if (adminUser == null)
|
||||
{
|
||||
_logger.LogWarning("管理员登录失败,用户名不存在: {UserName}", input.UserName);
|
||||
throw new BusinessException("用户名或密码错误", 401);
|
||||
}
|
||||
|
||||
if (!BCrypt.Net.BCrypt.Verify(input.Password, adminUser.PasswordHash))
|
||||
{
|
||||
_logger.LogWarning("管理员登录失败,密码错误: {UserName}", input.UserName);
|
||||
throw new BusinessException("用户名或密码错误", 401);
|
||||
}
|
||||
|
||||
if (adminUser.Status != "Active")
|
||||
{
|
||||
_logger.LogWarning("管理员登录失败,账号已禁用: {UserName}", input.UserName);
|
||||
throw new BusinessException("账号已被禁用,请联系系统管理员", 403);
|
||||
}
|
||||
|
||||
var jwtSettings = GetJwtSettings();
|
||||
|
||||
var token = JwtHelper.GenerateToken((long)adminUser.Id, adminUser.UserName, jwtSettings);
|
||||
|
||||
await RedisHelper.StringSetAsync($"{TokenKeyPrefix}:{adminUser.Id}", token, TimeSpan.FromMinutes(jwtSettings.ExpiryMinutes));
|
||||
|
||||
_logger.LogInformation("管理员登录成功,用户名: {UserName}, ID: {UserId}", input.UserName, adminUser.Id);
|
||||
|
||||
return new AdminLoginOutput
|
||||
{
|
||||
Token = token,
|
||||
UserId = (long)adminUser.Id,
|
||||
UserName = adminUser.UserName,
|
||||
Type = adminUser.Type
|
||||
};
|
||||
}
|
||||
|
||||
public async Task LogoutAsync(long userId)
|
||||
{
|
||||
_logger.LogInformation("管理员登出,ID: {UserId}", userId);
|
||||
|
||||
await RedisHelper.KeyDeleteAsync($"{TokenKeyPrefix}:{userId}");
|
||||
|
||||
_logger.LogInformation("管理员登出成功,ID: {UserId}", userId);
|
||||
}
|
||||
|
||||
public async Task<AdminUserInfoOutput> GetAdminInfoAsync(long userId)
|
||||
{
|
||||
_logger.LogInformation("获取管理员信息,ID: {UserId}", userId);
|
||||
|
||||
var adminUser = await _adminUserRepository.GetByIdAsync(userId);
|
||||
if (adminUser == null)
|
||||
{
|
||||
_logger.LogWarning("未找到管理员,ID: {UserId}", userId);
|
||||
throw new BusinessException("用户不存在", 404);
|
||||
}
|
||||
|
||||
return new AdminUserInfoOutput
|
||||
{
|
||||
UserId = adminUser.Id,
|
||||
UserName = adminUser.UserName,
|
||||
Type = adminUser.Type,
|
||||
Status = adminUser.Status
|
||||
};
|
||||
}
|
||||
|
||||
public async Task ChangePasswordAsync(long userId, string oldPassword, string newPassword)
|
||||
{
|
||||
_logger.LogInformation("管理员修改密码尝试,ID: {UserId}", userId);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(oldPassword))
|
||||
{
|
||||
throw new BusinessException("原密码不能为空", 400);
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(newPassword))
|
||||
{
|
||||
throw new BusinessException("新密码不能为空", 400);
|
||||
}
|
||||
|
||||
var adminUser = await _adminUserRepository.GetByIdAsync(userId);
|
||||
if (adminUser == null)
|
||||
{
|
||||
_logger.LogWarning("未找到管理员,ID: {UserId}", userId);
|
||||
throw new BusinessException("用户不存在", 404);
|
||||
}
|
||||
|
||||
if (!BCrypt.Net.BCrypt.Verify(oldPassword, adminUser.PasswordHash))
|
||||
{
|
||||
_logger.LogWarning("管理员修改密码失败,原密码错误,ID: {UserId}", userId);
|
||||
throw new BusinessException("原密码错误", 400);
|
||||
}
|
||||
|
||||
adminUser.PasswordHash = BCrypt.Net.BCrypt.HashPassword(newPassword);
|
||||
|
||||
var result = await _adminUserRepository.UpdateAsync(adminUser);
|
||||
if (!result)
|
||||
{
|
||||
throw new BusinessException("修改密码失败", 500);
|
||||
}
|
||||
|
||||
await RedisHelper.KeyDeleteAsync($"{TokenKeyPrefix}:{userId}");
|
||||
|
||||
_logger.LogInformation("管理员修改密码成功,ID: {UserId}", userId);
|
||||
}
|
||||
|
||||
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 配置不完整", 500);
|
||||
}
|
||||
|
||||
return jwtSettings;
|
||||
}
|
||||
}
|
||||
267
QYZH.InteractiveMagazine.Service/AdminUserService.cs
Normal file
267
QYZH.InteractiveMagazine.Service/AdminUserService.cs
Normal file
@ -0,0 +1,267 @@
|
||||
using System.Linq.Expressions;
|
||||
using BCrypt.Net;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using QYZH.InteractiveMagazine.IService;
|
||||
using QYZH.InteractiveMagazine.IService.Dto;
|
||||
using QYZH.InteractiveMagazine.Models.Common;
|
||||
using QYZH.InteractiveMagazine.Models.Dto;
|
||||
using QYZH.InteractiveMagazine.Models.Entity;
|
||||
using QYZH.InteractiveMagazine.Repository;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.Service;
|
||||
|
||||
/// <summary>
|
||||
/// 管理员用户服务实现
|
||||
/// </summary>
|
||||
public class AdminUserService : IAdminUserService
|
||||
{
|
||||
private readonly IAdminUserRepository _adminUserRepository;
|
||||
private readonly ILogger<AdminUserService> _logger;
|
||||
|
||||
public AdminUserService(IAdminUserRepository adminUserRepository, ILogger<AdminUserService> logger)
|
||||
{
|
||||
_adminUserRepository = adminUserRepository;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建管理员
|
||||
/// </summary>
|
||||
public async Task<AdminUserOutput> CreateAsync(AdminUserInput input)
|
||||
{
|
||||
_logger.LogInformation("正在创建管理员,用户名: {UserName}", input.UserName);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(input.UserName))
|
||||
{
|
||||
throw new BusinessException("用户名不能为空", 400);
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(input.Password))
|
||||
{
|
||||
throw new BusinessException("密码不能为空", 400);
|
||||
}
|
||||
|
||||
// 检查用户名是否已存在
|
||||
var existingUser = await _adminUserRepository.GetByUserNameAsync(input.UserName);
|
||||
if (existingUser != null)
|
||||
{
|
||||
_logger.LogWarning("创建管理员失败,用户名已存在: {UserName}", input.UserName);
|
||||
throw new BusinessException("用户名已存在", 400);
|
||||
}
|
||||
|
||||
var adminUser = new AdminUser
|
||||
{
|
||||
UserName = input.UserName.Trim(),
|
||||
PasswordHash = BCrypt.Net.BCrypt.HashPassword(input.Password),
|
||||
Type = input.Type,
|
||||
Status = input.Status,
|
||||
CreatedBy = "System",
|
||||
UpdatedBy = "System",
|
||||
CreatedAt = DateTime.Now,
|
||||
UpdatedAt = DateTime.Now,
|
||||
IsDeleted = false
|
||||
};
|
||||
|
||||
var result = await _adminUserRepository.InsertAsync(adminUser);
|
||||
if (!result)
|
||||
{
|
||||
_logger.LogError("管理员创建失败,用户名: {UserName}", input.UserName);
|
||||
throw new BusinessException("创建管理员失败", 500);
|
||||
}
|
||||
|
||||
_logger.LogInformation("管理员创建成功,用户名: {UserName}, ID: {Id}", input.UserName, adminUser.Id);
|
||||
|
||||
return MapToOutput(adminUser);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新管理员
|
||||
/// </summary>
|
||||
public async Task<AdminUserOutput> UpdateAsync(long id, AdminUserInput input)
|
||||
{
|
||||
_logger.LogInformation("正在更新管理员,ID: {Id}", id);
|
||||
|
||||
var adminUser = await _adminUserRepository.GetByIdAsync(id);
|
||||
if (adminUser == null)
|
||||
{
|
||||
_logger.LogWarning("未找到要更新的管理员,ID: {Id}", id);
|
||||
throw new BusinessException("管理员不存在", 404);
|
||||
}
|
||||
|
||||
// 如果用户名有变更,检查是否与其他用户重复
|
||||
if (!string.IsNullOrWhiteSpace(input.UserName) && input.UserName != adminUser.UserName)
|
||||
{
|
||||
var existingUser = await _adminUserRepository.GetByUserNameAsync(input.UserName.Trim());
|
||||
if (existingUser != null && existingUser.Id != id)
|
||||
{
|
||||
_logger.LogWarning("更新管理员失败,用户名已存在: {UserName}", input.UserName);
|
||||
throw new BusinessException("用户名已存在", 400);
|
||||
}
|
||||
|
||||
adminUser.UserName = input.UserName.Trim();
|
||||
}
|
||||
|
||||
// 如果提供了密码,则更新密码
|
||||
if (!string.IsNullOrWhiteSpace(input.Password))
|
||||
{
|
||||
adminUser.PasswordHash = BCrypt.Net.BCrypt.HashPassword(input.Password);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(input.Type))
|
||||
{
|
||||
adminUser.Type = input.Type;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(input.Status))
|
||||
{
|
||||
adminUser.Status = input.Status;
|
||||
}
|
||||
|
||||
adminUser.UpdatedBy = "System";
|
||||
adminUser.UpdatedAt = DateTime.Now;
|
||||
|
||||
var result = await _adminUserRepository.UpdateAsync(adminUser);
|
||||
if (!result)
|
||||
{
|
||||
_logger.LogError("管理员更新失败,ID: {Id}", id);
|
||||
throw new BusinessException("更新管理员失败", 500);
|
||||
}
|
||||
|
||||
_logger.LogInformation("管理员更新成功,ID: {Id}", id);
|
||||
|
||||
return MapToOutput(adminUser);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除管理员(软删除)
|
||||
/// </summary>
|
||||
public async Task DeleteAsync(long id)
|
||||
{
|
||||
_logger.LogInformation("正在删除管理员,ID: {Id}", id);
|
||||
|
||||
var adminUser = await _adminUserRepository.GetByIdAsync(id);
|
||||
if (adminUser == null)
|
||||
{
|
||||
_logger.LogWarning("未找到要删除的管理员,ID: {Id}", id);
|
||||
throw new BusinessException("管理员不存在", 404);
|
||||
}
|
||||
|
||||
var result = await _adminUserRepository.DeleteByIdAsync(id);
|
||||
if (!result)
|
||||
{
|
||||
_logger.LogError("管理员删除失败,ID: {Id}", id);
|
||||
throw new BusinessException("删除管理员失败", 500);
|
||||
}
|
||||
|
||||
_logger.LogInformation("管理员删除成功,ID: {Id}", id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据ID获取管理员
|
||||
/// </summary>
|
||||
public async Task<AdminUserOutput> GetByIdAsync(long id)
|
||||
{
|
||||
_logger.LogInformation("正在获取管理员信息,ID: {Id}", id);
|
||||
|
||||
var adminUser = await _adminUserRepository.GetByIdAsync(id);
|
||||
if (adminUser == null)
|
||||
{
|
||||
_logger.LogWarning("未找到管理员,ID: {Id}", id);
|
||||
throw new BusinessException("管理员不存在", 404);
|
||||
}
|
||||
|
||||
return MapToOutput(adminUser);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 分页查询管理员列表
|
||||
/// </summary>
|
||||
public async Task<PageListModel<AdminUserOutput>> GetListAsync(AdminUserQueryInput input)
|
||||
{
|
||||
_logger.LogInformation("正在查询管理员列表,页码: {PageIndex}, 每页条数: {PageSize}", input.PageIndex, input.PageSize);
|
||||
|
||||
if (input.PageIndex <= 0)
|
||||
{
|
||||
throw new BusinessException("页码必须大于0", 400);
|
||||
}
|
||||
|
||||
if (input.PageSize <= 0 || input.PageSize > 100)
|
||||
{
|
||||
throw new BusinessException("每页条数必须在1-100之间", 400);
|
||||
}
|
||||
|
||||
var pageResult = await _adminUserRepository.GetPageListAsync(
|
||||
BuildQueryExpression(input),
|
||||
input
|
||||
);
|
||||
|
||||
// 转换为输出DTO
|
||||
var outputList = pageResult.List?.Select(MapToOutput).ToList() ?? new List<AdminUserOutput>();
|
||||
|
||||
return new PageListModel<AdminUserOutput>
|
||||
{
|
||||
List = outputList,
|
||||
TotalCount = pageResult.TotalCount,
|
||||
PageIndex = pageResult.PageIndex,
|
||||
PageSize = pageResult.PageSize
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构建查询表达式
|
||||
/// </summary>
|
||||
private static Expression<Func<AdminUser, bool>> BuildQueryExpression(AdminUserQueryInput input)
|
||||
{
|
||||
Expression<Func<AdminUser, bool>> where = x => !x.IsDeleted;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(input.UserName))
|
||||
{
|
||||
var userName = input.UserName;
|
||||
Expression<Func<AdminUser, bool>> userNameCondition = x => x.UserName.Contains(userName);
|
||||
where = Expression.Lambda<Func<AdminUser, bool>>(
|
||||
Expression.AndAlso(where.Body,
|
||||
Expression.Invoke(userNameCondition, where.Parameters[0])),
|
||||
where.Parameters);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(input.Type))
|
||||
{
|
||||
var type = input.Type;
|
||||
Expression<Func<AdminUser, bool>> typeCondition = x => x.Type == type;
|
||||
where = Expression.Lambda<Func<AdminUser, bool>>(
|
||||
Expression.AndAlso(where.Body,
|
||||
Expression.Invoke(typeCondition, where.Parameters[0])),
|
||||
where.Parameters);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(input.Status))
|
||||
{
|
||||
var status = input.Status;
|
||||
Expression<Func<AdminUser, bool>> statusCondition = x => x.Status == status;
|
||||
where = Expression.Lambda<Func<AdminUser, bool>>(
|
||||
Expression.AndAlso(where.Body,
|
||||
Expression.Invoke(statusCondition, where.Parameters[0])),
|
||||
where.Parameters);
|
||||
}
|
||||
|
||||
return where;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将实体映射为输出DTO
|
||||
/// </summary>
|
||||
private static AdminUserOutput MapToOutput(AdminUser adminUser)
|
||||
{
|
||||
return new AdminUserOutput
|
||||
{
|
||||
Id = adminUser.Id,
|
||||
UserName = adminUser.UserName,
|
||||
Type = adminUser.Type,
|
||||
Status = adminUser.Status,
|
||||
CreatedBy = adminUser.CreatedBy,
|
||||
CreatedAt = adminUser.CreatedAt,
|
||||
UpdatedBy = adminUser.UpdatedBy,
|
||||
UpdatedAt = adminUser.UpdatedAt
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -1,173 +0,0 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using QYZH.InteractiveMagazine.IService;
|
||||
using QYZH.InteractiveMagazine.IService.Dto;
|
||||
using QYZH.InteractiveMagazine.Models.Common;
|
||||
using QYZH.InteractiveMagazine.Models.Settings;
|
||||
using QYZH.InteractiveMagazine.Infrastructure.Auth;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.Service;
|
||||
|
||||
/// <summary>
|
||||
/// 认证服务实现
|
||||
/// </summary>
|
||||
public class AuthService : IAuthService
|
||||
{
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly ILogger<AuthService> _logger;
|
||||
|
||||
/// <summary>
|
||||
/// 演示用管理员账号
|
||||
/// </summary>
|
||||
private const string DemoAccount = "admin";
|
||||
|
||||
/// <summary>
|
||||
/// 演示用管理员密码
|
||||
/// </summary>
|
||||
private const string DemoPassword = "123456";
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="configuration">配置</param>
|
||||
/// <param name="logger">日志记录器</param>
|
||||
public AuthService(IConfiguration configuration, ILogger<AuthService> logger)
|
||||
{
|
||||
_configuration = configuration;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用户登录
|
||||
/// </summary>
|
||||
/// <param name="account">账号</param>
|
||||
/// <param name="password">密码</param>
|
||||
/// <returns>登录结果</returns>
|
||||
public async Task<LoginOutput> LoginAsync(string account, string password)
|
||||
{
|
||||
_logger.LogInformation("用户登录尝试,账号: {Account}", account);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(account))
|
||||
{
|
||||
throw new BusinessException("账号不能为空", 400);
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(password))
|
||||
{
|
||||
throw new BusinessException("密码不能为空", 400);
|
||||
}
|
||||
|
||||
// 演示版本:硬编码验证账号密码
|
||||
if (account != DemoAccount || password != DemoPassword)
|
||||
{
|
||||
_logger.LogWarning("用户登录失败,账号: {Account}", account);
|
||||
throw new BusinessException("账号或密码错误", 401);
|
||||
}
|
||||
|
||||
// 获取JWT配置
|
||||
var jwtSettings = GetJwtSettings();
|
||||
|
||||
// 生成令牌
|
||||
var token = JwtHelper.GenerateToken(1, "管理员", jwtSettings);
|
||||
var refreshToken = JwtHelper.GenerateToken(1, "管理员", jwtSettings);
|
||||
|
||||
_logger.LogInformation("用户登录成功,账号: {Account}", account);
|
||||
|
||||
return new LoginOutput
|
||||
{
|
||||
Token = token,
|
||||
RefreshToken = refreshToken,
|
||||
UserId = 1,
|
||||
UserName = "管理员"
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用户注册
|
||||
/// </summary>
|
||||
/// <param name="input">注册输入参数</param>
|
||||
/// <returns>是否成功</returns>
|
||||
public async Task<bool> RegisterAsync(RegisterInput input)
|
||||
{
|
||||
_logger.LogInformation("用户注册尝试,账号: {Account}", input?.Account);
|
||||
|
||||
if (input == null)
|
||||
{
|
||||
throw new BusinessException("注册参数不能为空", 400);
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(input.Account))
|
||||
{
|
||||
throw new BusinessException("账号不能为空", 400);
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(input.Password))
|
||||
{
|
||||
throw new BusinessException("密码不能为空", 400);
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(input.UserName))
|
||||
{
|
||||
throw new BusinessException("用户名不能为空", 400);
|
||||
}
|
||||
|
||||
// 演示版本:模拟注册成功
|
||||
_logger.LogInformation("用户注册成功,账号: {Account}, 用户名: {UserName}", input.Account, input.UserName);
|
||||
|
||||
return await Task.FromResult(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 刷新令牌
|
||||
/// </summary>
|
||||
/// <param name="refreshToken">刷新令牌</param>
|
||||
/// <returns>新的登录结果</returns>
|
||||
public async Task<LoginOutput> RefreshTokenAsync(string refreshToken)
|
||||
{
|
||||
_logger.LogInformation("令牌刷新尝试");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(refreshToken))
|
||||
{
|
||||
throw new BusinessException("刷新令牌不能为空", 400);
|
||||
}
|
||||
|
||||
var jwtSettings = GetJwtSettings();
|
||||
|
||||
// 生成新的令牌
|
||||
var newToken = JwtHelper.GenerateToken(1, "管理员", jwtSettings);
|
||||
var newRefreshToken = JwtHelper.GenerateToken(1, "管理员", jwtSettings);
|
||||
|
||||
_logger.LogInformation("令牌刷新成功");
|
||||
|
||||
return new LoginOutput
|
||||
{
|
||||
Token = newToken,
|
||||
RefreshToken = newRefreshToken,
|
||||
UserId = 1,
|
||||
UserName = "管理员"
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取JWT配置
|
||||
/// </summary>
|
||||
/// <returns>JWT配置对象</returns>
|
||||
private JwtSettings GetJwtSettings()
|
||||
{
|
||||
var jwtSettings = _configuration.GetSection("JwtSettings").Get<JwtSettings>()
|
||||
?? new JwtSettings
|
||||
{
|
||||
Issuer = "QYZH.InteractiveMagazine",
|
||||
Audience = "QYZH.InteractiveMagazine.Client",
|
||||
SecretKey = "QYZH_InteractiveMagazine_SecretKey_2024",
|
||||
ExpiryMinutes = 120
|
||||
};
|
||||
|
||||
if (string.IsNullOrWhiteSpace(jwtSettings.SecretKey))
|
||||
{
|
||||
throw new BusinessException("JWT配置不完整", 500);
|
||||
}
|
||||
|
||||
return jwtSettings;
|
||||
}
|
||||
}
|
||||
@ -182,23 +182,19 @@ public class BaseService<T> : IBaseService<T> where T : class, new()
|
||||
if (entity is BaseEntity baseEntity)
|
||||
{
|
||||
var now = DateTime.Now;
|
||||
baseEntity.CreatedTime = now;
|
||||
baseEntity.UpdatedTime = now;
|
||||
baseEntity.CreatedAt = now;
|
||||
baseEntity.UpdatedAt = now;
|
||||
baseEntity.CreatedBy = baseEntity.CreatedBy ?? "system";
|
||||
baseEntity.UpdatedBy = baseEntity.UpdatedBy ?? "system";
|
||||
baseEntity.IsDeleted = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置更新时的审计字段
|
||||
/// </summary>
|
||||
/// <param name="entity">实体对象</param>
|
||||
private static void SetAuditFieldsOnUpdate(T entity)
|
||||
{
|
||||
if (entity is BaseEntity baseEntity)
|
||||
{
|
||||
baseEntity.UpdatedTime = DateTime.Now;
|
||||
baseEntity.UpdatedAt = DateTime.Now;
|
||||
baseEntity.UpdatedBy = baseEntity.UpdatedBy ?? "system";
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AutoMapper" Version="16.1.1" />
|
||||
<PackageReference Include="BCrypt.Net-Next" Version="4.2.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.8" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@ -1,167 +0,0 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using QYZH.InteractiveMagazine.Common.Helpers;
|
||||
using QYZH.InteractiveMagazine.IService;
|
||||
using QYZH.InteractiveMagazine.IService.Dto;
|
||||
using QYZH.InteractiveMagazine.Models.Common;
|
||||
using QYZH.InteractiveMagazine.Models.Settings;
|
||||
using QYZH.InteractiveMagazine.Infrastructure.Auth;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.Service;
|
||||
|
||||
/// <summary>
|
||||
/// 微信小程序服务实现
|
||||
/// </summary>
|
||||
public class WeChatMiniProgramService : IWeChatMiniProgramService
|
||||
{
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly ILogger<WeChatMiniProgramService> _logger;
|
||||
private const string Code2SessionUrl = "https://api.weixin.qq.com/sns/jscode2session";
|
||||
private const string GetPhoneNumberUrl = "https://api.weixin.qq.com/wxa/business/getuserphonenumber";
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="configuration">配置</param>
|
||||
/// <param name="logger">日志记录器</param>
|
||||
public WeChatMiniProgramService(IConfiguration configuration, ILogger<WeChatMiniProgramService> logger)
|
||||
{
|
||||
_configuration = configuration;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 微信登录
|
||||
/// </summary>
|
||||
/// <param name="code">微信登录凭证</param>
|
||||
/// <returns>微信登录结果</returns>
|
||||
public async Task<WeChatLoginOutput> WeChatLoginAsync(string code)
|
||||
{
|
||||
_logger.LogInformation("微信登录尝试,code: {Code}", code);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(code))
|
||||
{
|
||||
throw new BusinessException("登录凭证不能为空", 400);
|
||||
}
|
||||
|
||||
// 获取微信配置
|
||||
var weChatSettings = GetWeChatSettings();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(weChatSettings.AppId) || string.IsNullOrWhiteSpace(weChatSettings.AppSecret))
|
||||
{
|
||||
throw new BusinessException("微信配置不完整", 500);
|
||||
}
|
||||
|
||||
// 演示版本:模拟调用微信code2session接口
|
||||
var openId = await SimulateCode2SessionAsync(code, weChatSettings);
|
||||
|
||||
// 获取JWT配置并生成令牌
|
||||
var jwtSettings = GetJwtSettings();
|
||||
var token = JwtHelper.GenerateToken(1, "微信用户", jwtSettings);
|
||||
|
||||
_logger.LogInformation("微信登录成功,openId: {OpenId}", openId);
|
||||
|
||||
return new WeChatLoginOutput
|
||||
{
|
||||
Token = token,
|
||||
UserId = 1,
|
||||
UserName = "微信用户",
|
||||
OpenId = openId
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取手机号
|
||||
/// </summary>
|
||||
/// <param name="code">获取手机号凭证</param>
|
||||
/// <returns>手机号信息</returns>
|
||||
public async Task<WeChatPhoneNumberOutput> GetPhoneNumberAsync(string code)
|
||||
{
|
||||
_logger.LogInformation("获取微信手机号尝试,code: {Code}", code);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(code))
|
||||
{
|
||||
throw new BusinessException("获取手机号凭证不能为空", 400);
|
||||
}
|
||||
|
||||
// 演示版本:模拟返回手机号
|
||||
var phoneNumber = await SimulateGetPhoneNumberAsync(code);
|
||||
|
||||
_logger.LogInformation("获取微信手机号成功");
|
||||
|
||||
return new WeChatPhoneNumberOutput
|
||||
{
|
||||
PhoneNumber = phoneNumber
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 模拟调用微信code2session接口
|
||||
/// </summary>
|
||||
/// <param name="code">登录凭证</param>
|
||||
/// <param name="weChatSettings">微信配置</param>
|
||||
/// <returns>openId</returns>
|
||||
private async Task<string> SimulateCode2SessionAsync(string code, WeChatSettings weChatSettings)
|
||||
{
|
||||
// 演示版本:模拟返回openId
|
||||
// 实际实现应调用微信API:
|
||||
// var url = $"{Code2SessionUrl}?appid={weChatSettings.AppId}&secret={weChatSettings.AppSecret}&js_code={code}&grant_type=authorization_code";
|
||||
// var response = await HttpHelper.GetAsync<dynamic>(url);
|
||||
// if (response?.errcode == 0) return response.openid;
|
||||
|
||||
await Task.Delay(100); // 模拟网络请求延迟
|
||||
|
||||
return $"demo_openid_{code.GetHashCode():X}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 模拟调用微信获取手机号接口
|
||||
/// </summary>
|
||||
/// <param name="code">获取手机号凭证</param>
|
||||
/// <returns>手机号</returns>
|
||||
private async Task<string> SimulateGetPhoneNumberAsync(string code)
|
||||
{
|
||||
// 演示版本:模拟返回手机号
|
||||
// 实际实现应调用微信API获取access_token,然后调用获取手机号接口:
|
||||
// var tokenUrl = $"https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={appId}&secret={appSecret}";
|
||||
// var tokenResponse = await HttpHelper.GetAsync<dynamic>(tokenUrl);
|
||||
// var accessToken = tokenResponse.access_token;
|
||||
// var phoneUrl = $"{GetPhoneNumberUrl}?access_token={accessToken}";
|
||||
// var phoneResponse = await HttpHelper.PostAsync<dynamic>(phoneUrl, new { code });
|
||||
|
||||
await Task.Delay(100); // 模拟网络请求延迟
|
||||
|
||||
return "13800138000";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取微信配置
|
||||
/// </summary>
|
||||
/// <returns>微信配置对象</returns>
|
||||
private WeChatSettings GetWeChatSettings()
|
||||
{
|
||||
return _configuration.GetSection("WeChatSettings").Get<WeChatSettings>()
|
||||
?? new WeChatSettings
|
||||
{
|
||||
AppId = "demo_app_id",
|
||||
AppSecret = "demo_app_secret"
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取JWT配置
|
||||
/// </summary>
|
||||
/// <returns>JWT配置对象</returns>
|
||||
private JwtSettings GetJwtSettings()
|
||||
{
|
||||
return _configuration.GetSection("JwtSettings").Get<JwtSettings>()
|
||||
?? new JwtSettings
|
||||
{
|
||||
Issuer = "QYZH.InteractiveMagazine",
|
||||
Audience = "QYZH.InteractiveMagazine.Client",
|
||||
SecretKey = "QYZH_InteractiveMagazine_SecretKey_2024",
|
||||
ExpiryMinutes = 120
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user