HaniBlindBox/server/HoneyBox/src/HoneyBox.Core/Services/UserService.cs

307 lines
8.6 KiB
C#

using HoneyBox.Core.Interfaces;
using HoneyBox.Model.Data;
using HoneyBox.Model.Entities;
using HoneyBox.Model.Models.Auth;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace HoneyBox.Core.Services;
/// <summary>
/// 用户服务实现
/// </summary>
public class UserService : BaseService<User, int>, IUserService
{
private readonly ILogger<UserService> _logger;
public UserService(HoneyBoxDbContext dbContext, ILogger<UserService> logger)
: base(dbContext)
{
_logger = logger;
}
/// <inheritdoc/>
public async Task<User?> GetUserByIdAsync(int userId)
{
return await _dbSet.FirstOrDefaultAsync(u => u.Id == userId);
}
/// <inheritdoc/>
public async Task<User?> GetUserByOpenIdAsync(string openId)
{
if (string.IsNullOrWhiteSpace(openId))
return null;
return await _dbSet.FirstOrDefaultAsync(u => u.OpenId == openId);
}
/// <inheritdoc/>
public async Task<User?> GetUserByUnionIdAsync(string unionId)
{
if (string.IsNullOrWhiteSpace(unionId))
return null;
return await _dbSet.FirstOrDefaultAsync(u => u.UnionId == unionId);
}
/// <inheritdoc/>
public async Task<User?> GetUserByMobileAsync(string mobile)
{
if (string.IsNullOrWhiteSpace(mobile))
return null;
return await _dbSet.FirstOrDefaultAsync(u => u.Mobile == mobile);
}
/// <inheritdoc/>
public async Task<User> CreateUserAsync(CreateUserDto dto)
{
if (dto == null)
throw new ArgumentNullException(nameof(dto));
// Generate UID
var uid = GenerateUid();
var user = new User
{
OpenId = dto.OpenId ?? string.Empty,
UnionId = dto.UnionId,
Mobile = dto.Mobile,
Uid = uid,
Nickname = dto.Nickname ?? $"User{Random.Shared.Next(1000, 9999)}",
HeadImg = dto.Headimg ?? string.Empty,
Pid = dto.Pid,
ClickId = dto.ClickId,
Money = 0,
Money2 = 0,
Integral = 0,
Score = 0,
Vip = 0,
Status = 1,
IsTest = 0,
CreatedAt = DateTime.UtcNow,
UpdatedAt = DateTime.UtcNow
};
await _dbSet.AddAsync(user);
await _dbContext.SaveChangesAsync();
_logger.LogInformation($"User created: Id={user.Id}, Uid={user.Uid}, OpenId={user.OpenId}");
return user;
}
/// <inheritdoc/>
public async Task UpdateUserAsync(int userId, UpdateUserDto dto)
{
if (dto == null)
throw new ArgumentNullException(nameof(dto));
var user = await GetUserByIdAsync(userId);
if (user == null)
throw new InvalidOperationException($"User with id {userId} not found");
if (!string.IsNullOrWhiteSpace(dto.Nickname))
user.Nickname = dto.Nickname;
if (!string.IsNullOrWhiteSpace(dto.Headimg))
user.HeadImg = dto.Headimg;
if (!string.IsNullOrWhiteSpace(dto.Mobile))
user.Mobile = dto.Mobile;
if (!string.IsNullOrWhiteSpace(dto.UnionId))
user.UnionId = dto.UnionId;
user.UpdatedAt = DateTime.UtcNow;
_dbSet.Update(user);
await _dbContext.SaveChangesAsync();
_logger.LogInformation($"User updated: Id={user.Id}");
}
/// <inheritdoc/>
public async Task<UserInfoDto?> GetUserInfoAsync(int userId)
{
var user = await GetUserByIdAsync(userId);
if (user == null)
return null;
var couponCount = await _dbContext.CouponReceives
.Where(cr => cr.UserId == userId && cr.State == 0)
.CountAsync();
var registrationDays = (int)(DateTime.UtcNow - user.CreatedAt).TotalDays;
var maskedMobile = MaskMobileNumber(user.Mobile);
var mobileIs = string.IsNullOrWhiteSpace(user.Mobile) ? 0 : 1;
// 计算权益等级信息
var quanYiLevel = await CalculateQuanYiLevelAsync(user.Score);
return new UserInfoDto
{
Id = user.Id,
Uid = user.Uid,
Nickname = user.Nickname,
Headimg = user.HeadImg,
Mobile = maskedMobile,
MobileIs = mobileIs,
Money = user.Money,
Money2 = user.Money2 ?? 0,
Integral = user.Integral,
Score = user.Score,
Vip = user.Vip,
VipImgurl = GetVipImageUrl(user.Vip),
Coupon = couponCount,
Day = registrationDays,
QuanYiLevel = quanYiLevel
};
}
/// <summary>
/// 计算用户权益等级信息
/// </summary>
/// <param name="score">用户欧气值</param>
/// <returns>权益等级DTO</returns>
private async Task<QuanYiLevelDto> CalculateQuanYiLevelAsync(decimal score)
{
var userScore = (int)score;
// 获取所有权益等级,按所需积分升序排列
var levels = await _dbContext.EquityLevels
.Where(e => e.DeletedAt == null)
.OrderBy(e => e.RequiredPoints)
.ToListAsync();
if (!levels.Any())
{
return new QuanYiLevelDto { Level = 0, Cha = -1, Jindu = 0 };
}
// 找到当前等级和下一等级
int currentLevel = 0;
int currentLevelPoints = 0;
int nextLevelPoints = 0;
bool isMaxLevel = false;
for (int i = 0; i < levels.Count; i++)
{
if (userScore >= levels[i].RequiredPoints)
{
currentLevel = levels[i].Level;
currentLevelPoints = levels[i].RequiredPoints;
if (i + 1 < levels.Count)
{
nextLevelPoints = levels[i + 1].RequiredPoints;
}
else
{
isMaxLevel = true;
}
}
else
{
if (currentLevel == 0)
{
// 用户还没达到第一级
nextLevelPoints = levels[i].RequiredPoints;
}
break;
}
}
// 计算差值和进度
int cha = isMaxLevel ? -1 : nextLevelPoints - userScore;
int jindu = 0;
if (isMaxLevel)
{
jindu = 100;
}
else if (nextLevelPoints > currentLevelPoints)
{
var progress = (userScore - currentLevelPoints) * 100 / (nextLevelPoints - currentLevelPoints);
jindu = Math.Max(0, Math.Min(100, progress));
}
return new QuanYiLevelDto
{
Level = currentLevel,
Cha = cha,
Jindu = jindu
};
}
/// <inheritdoc/>
public async Task<int> CalculateVipLevelAsync(int userId, int currentVip)
{
var user = await GetUserByIdAsync(userId);
if (user == null)
return currentVip;
// Get all VIP levels ordered by level
var vipLevels = await _dbContext.VipLevels
.Where(v => v.DeletedAt == null)
.OrderBy(v => v.Level)
.ToListAsync();
if (!vipLevels.Any())
return currentVip;
// Calculate VIP level based on total spending (Money)
var totalSpending = user.Money;
int newVipLevel = 0;
foreach (var vipLevel in vipLevels)
{
if (totalSpending >= vipLevel.Number)
{
newVipLevel = vipLevel.Level;
}
else
{
break;
}
}
return newVipLevel;
}
/// <summary>
/// Generate a unique UID for new user
/// </summary>
private string GenerateUid()
{
// Generate a numeric UID based on timestamp and random number
var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
var random = Random.Shared.Next(1000, 9999);
return $"{timestamp}{random}";
}
/// <summary>
/// Mask mobile number for privacy (e.g., 13812345678 -> 138****5678)
/// </summary>
private string? MaskMobileNumber(string? mobile)
{
if (string.IsNullOrWhiteSpace(mobile) || mobile.Length < 11)
return null;
return $"{mobile.Substring(0, 3)}****{mobile.Substring(mobile.Length - 4)}";
}
/// <summary>
/// Get VIP image URL based on VIP level
/// </summary>
private string? GetVipImageUrl(int vipLevel)
{
if (vipLevel <= 0)
return null;
// Return a placeholder URL - actual URLs should be configured
return $"https://example.com/vip/level_{vipLevel}.png";
}
}