xiangyixiangqin/server/src/XiangYi.Application/Services/AuthService.cs
2026-01-04 21:34:50 +08:00

232 lines
7.7 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using XiangYi.Application.DTOs.Responses;
using XiangYi.Application.Interfaces;
using XiangYi.Application.Options;
using XiangYi.Core.Constants;
using XiangYi.Core.Entities.Biz;
using XiangYi.Core.Exceptions;
using XiangYi.Core.Interfaces;
using XiangYi.Infrastructure.Cache;
using XiangYi.Infrastructure.WeChat;
namespace XiangYi.Application.Services;
/// <summary>
/// 认证服务实现
/// </summary>
public class AuthService : IAuthService
{
private readonly IRepository<User> _userRepository;
private readonly IWeChatService _weChatService;
private readonly ICacheService _cacheService;
private readonly ISystemConfigService _systemConfigService;
private readonly JwtOptions _jwtOptions;
private readonly ILogger<AuthService> _logger;
private static readonly Random _random = new();
public AuthService(
IRepository<User> userRepository,
IWeChatService weChatService,
ICacheService cacheService,
ISystemConfigService systemConfigService,
IOptions<JwtOptions> jwtOptions,
ILogger<AuthService> logger)
{
_userRepository = userRepository;
_weChatService = weChatService;
_cacheService = cacheService;
_systemConfigService = systemConfigService;
_jwtOptions = jwtOptions.Value;
_logger = logger;
}
/// <inheritdoc />
public async Task<LoginResponse> LoginAsync(string code)
{
// 1. 调用微信接口获取openid
var weChatResult = await _weChatService.Code2SessionAsync(code);
if (!weChatResult.Success || string.IsNullOrEmpty(weChatResult.OpenId))
{
_logger.LogWarning("微信登录失败: {ErrorCode} - {ErrorMessage}",
weChatResult.ErrorCode, weChatResult.ErrorMessage);
throw new BusinessException(ErrorCodes.WeChatLoginFailed,
weChatResult.ErrorMessage ?? "微信登录失败");
}
// 2. 查找或创建用户
var user = await _userRepository.GetListAsync(u => u.OpenId == weChatResult.OpenId);
var existingUser = user.FirstOrDefault();
bool isNewUser = false;
if (existingUser == null)
{
// 新用户,创建用户记录
isNewUser = true;
var xiangQinNo = await GenerateXiangQinNoAsync();
// 获取默认头像
var defaultAvatar = await _systemConfigService.GetDefaultAvatarAsync();
existingUser = new User
{
OpenId = weChatResult.OpenId,
UnionId = weChatResult.UnionId,
XiangQinNo = xiangQinNo,
Avatar = defaultAvatar,
Status = 1,
ContactCount = 2,
CreateTime = DateTime.Now,
UpdateTime = DateTime.Now
};
existingUser = await _userRepository.AddAsync(existingUser);
_logger.LogInformation("新用户注册成功: UserId={UserId}, XiangQinNo={XiangQinNo}",
existingUser.Id, xiangQinNo);
}
else
{
// 更新最后登录时间
existingUser.LastLoginTime = DateTime.Now;
existingUser.UpdateTime = DateTime.Now;
await _userRepository.UpdateAsync(existingUser);
}
// 3. 生成JWT Token
var token = GenerateToken(existingUser.Id, existingUser.OpenId);
// 4. 缓存Token
await _cacheService.SetUserTokenAsync(existingUser.Id, token);
_logger.LogInformation("用户登录成功: UserId={UserId}", existingUser.Id);
return new LoginResponse
{
Token = token,
UserId = existingUser.Id,
Nickname = existingUser.Nickname,
Avatar = existingUser.Avatar,
XiangQinNo = existingUser.XiangQinNo,
IsProfileCompleted = existingUser.IsProfileCompleted,
IsMember = existingUser.IsMember,
MemberLevel = existingUser.MemberLevel,
IsRealName = existingUser.IsRealName,
IsNewUser = isNewUser
};
}
/// <inheritdoc />
public async Task<BindPhoneResponse> BindPhoneAsync(long userId, string code)
{
// 1. 获取用户
var user = await _userRepository.GetByIdAsync(userId);
if (user == null)
{
throw new BusinessException(ErrorCodes.UserNotFound, "用户不存在");
}
// 2. 调用微信接口获取手机号
var phone = await _weChatService.GetPhoneNumberAsync(code);
if (string.IsNullOrEmpty(phone))
{
throw new BusinessException(ErrorCodes.WeChatServiceError, "获取手机号失败");
}
// 3. 更新用户手机号
user.Phone = phone;
user.UpdateTime = DateTime.Now;
await _userRepository.UpdateAsync(user);
_logger.LogInformation("用户绑定手机号成功: UserId={UserId}", userId);
// 4. 返回脱敏后的手机号
return new BindPhoneResponse
{
Phone = MaskPhone(phone)
};
}
/// <inheritdoc />
public string GenerateToken(long userId, string openId)
{
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtOptions.Secret));
var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var claims = new[]
{
new Claim(ClaimTypes.NameIdentifier, userId.ToString()),
new Claim("openid", openId),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Iat, DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64)
};
var token = new JwtSecurityToken(
issuer: _jwtOptions.Issuer,
audience: _jwtOptions.Audience,
claims: claims,
expires: DateTime.UtcNow.AddMinutes(_jwtOptions.ExpireMinutes),
signingCredentials: credentials
);
return new JwtSecurityTokenHandler().WriteToken(token);
}
/// <inheritdoc />
public async Task<string> GenerateXiangQinNoAsync()
{
const int maxAttempts = 100;
for (int i = 0; i < maxAttempts; i++)
{
var xiangQinNo = GenerateXiangQinNo();
// 检查是否已存在
var exists = await _userRepository.ExistsAsync(u => u.XiangQinNo == xiangQinNo);
if (!exists)
{
return xiangQinNo;
}
}
// 如果多次尝试都失败,抛出异常
throw new BusinessException(ErrorCodes.SystemError, "生成相亲编号失败,请重试");
}
/// <inheritdoc />
public async Task<bool> ValidateTokenAsync(long userId, string token)
{
return await _cacheService.ValidateUserTokenAsync(userId, token);
}
/// <summary>
/// 生成6位相亲编号首位不为0
/// </summary>
/// <returns>相亲编号</returns>
public static string GenerateXiangQinNo()
{
// 首位1-9
var firstDigit = _random.Next(1, 10);
// 后5位0-9
var remainingDigits = _random.Next(0, 100000);
return $"{firstDigit}{remainingDigits:D5}";
}
/// <summary>
/// 手机号脱敏
/// </summary>
private static string MaskPhone(string phone)
{
if (string.IsNullOrEmpty(phone) || phone.Length < 7)
{
return phone;
}
return $"{phone[..3]}****{phone[^4..]}";
}
}