307 lines
8.6 KiB
C#
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";
|
|
}
|
|
}
|