Files

2695 lines
115 KiB
C#

using System.Security.Claims;
using System.Text.RegularExpressions;
using AdminDomain.Entity;
using AutoMapper;
using Microsoft.AspNetCore.SignalR;
using UserService.Hubs;
using Common;
using Common.BlockWord;
using Common.CommonModel;
using Common.Redis;
using Common.Utils;
using CommonDomain;
using CommonDomain.BaseModel;
using CommonDomain.Enum;
using CommonInfrastructure.Response;
using DotNetCore.CAP;
using JWTHelper;
using Microsoft.Extensions.Logging;
using MongoDB.Driver;
using MongoRepository;
using RedisHelper;
using RedisService;
using UserDomain.Entity;
using UserDomain.Enum;
using UserService.GameConfigs;
using UserService.Param;
using UserService.Response;
using UserService.Tasks.Param;
using UserService.User.Response;
namespace UserService.User
{
public class UserService : IUserService
{
private readonly IMongoBaseRepository<UserEntity> _userMongo;
private readonly IMongoBaseRepository<UserEmail> _emailMongo;
private readonly IMongoBaseRepository<UserReward> _rewardMongo;
private readonly IMongoBaseRepository<Order> _orderMongo;
private readonly IMongoBaseRepository<BeautifulNumberConfig> _beadfulNumberMongo;
private readonly IMongoBaseRepository<UserBattleOrder> _userBattleOrderMongo;
private readonly IGameConfigService _gameConfigService;
private readonly IActivityConfigService _activityConfigService;
private readonly IMongoBaseRepository<UserLoginLog> _loginLogMongo;
private readonly ILogger<UserService> _logger;
private readonly IMapper _mapper;
private readonly IRedisService _cache;
private readonly JwtTools _jwtTools;
private readonly ICapPublisher _capPublisher;
private readonly IDistributedLock _distributedLock;
private readonly IHubContext<LoginHub> _hubContext;
public UserService(IMongoBaseRepository<UserEntity> userMongo, IMongoBaseRepository<UserEmail> emailMongo, IMongoBaseRepository<UserReward> rewardMongo, IMongoBaseRepository<Order> orderMongo, IMongoBaseRepository<BeautifulNumberConfig> beadfulNumberMongo, IMongoBaseRepository<UserBattleOrder> userBattleOrderMongo, IGameConfigService gameConfigService, IActivityConfigService activityConfigService, IMongoBaseRepository<UserLoginLog> loginLogMongo, ILogger<UserService> logger, IMapper mapper, IRedisService cache, JwtTools jwtTools, ICapPublisher capPublisher, IDistributedLock distributedLock, IHubContext<LoginHub> hubContext)
{
_userMongo = userMongo;
_emailMongo = emailMongo;
_rewardMongo = rewardMongo;
_orderMongo = orderMongo;
_beadfulNumberMongo = beadfulNumberMongo;
_userBattleOrderMongo = userBattleOrderMongo;
_gameConfigService = gameConfigService;
_activityConfigService = activityConfigService;
_loginLogMongo = loginLogMongo;
_logger = logger;
_mapper = mapper;
_cache = cache;
_jwtTools = jwtTools;
_capPublisher = capPublisher;
_distributedLock = distributedLock;
_hubContext = hubContext;
}
public async Task<UserToken> RegisterWeiXin(RegisterUserWeiXinParam param, string ip)
{
var user = await _userMongo.FindOneAsync(c => c.BaseInfo.WXOpenId == param.OpenId);
if (user != null)
{
var result = await LoginWx(param.OpenId, ip);
if (result != null)
return result;
return null;
}
user = new UserEntity();
user.BaseInfo.WXOpenId = param.OpenId;
user.BaseInfo.AvatarUrl = param.AvatarUrl;
user.Assets = new Assets() { };
user.BaseInfo.Number = await GetUserNumner();
user.BaseInfo.UserName = string.IsNullOrWhiteSpace(param.NickName)
? $"ID_{user.BaseInfo.Number}"
: param.NickName.Trim();
var gameConfig = _cache.GetGameConfig();
var activityConfig = await _activityConfigService.Get();
user.Init(gameConfig, activityConfig);
user.RegisterIp = ip;
user.Static.LastLoginTime = DateTime.Now.ToTimeStamp();
user.Static.LastLoginIp = ip;
var res = await _userMongo.InsertOneAsync(user);
if (!res.ok)
return null;
var model = await LoginToken(user);
return model;
}
public async Task<(bool isok, string msg, UserToken user)> Register(RegisterUserParam param, string ip)
{
if (param == null)
throw new BusinessException("注册参数不能为空");
param.Mobile = param.Mobile?.Trim();
param.Account = param.Account?.Trim() ?? "";
param.NickName = param.NickName?.Trim() ?? "";
var mobileMasked = !string.IsNullOrWhiteSpace(param.Mobile) && param.Mobile.Length >= 7
? $"{param.Mobile[..3]}****{param.Mobile[^4..]}"
: param.Mobile ?? string.Empty;
if (string.IsNullOrWhiteSpace(param.Mobile) || param.Mobile.Length != 11)
throw new BusinessException("手机号格式错误");
if (string.IsNullOrWhiteSpace(param.Account))
throw new BusinessException("账号不能为空");
if (string.IsNullOrWhiteSpace(param.NickName))
throw new BusinessException("昵称不能为空");
{
Regex _detailedPhoneRegex = new Regex(@"^1[3-9]\d{9}$", RegexOptions.Compiled);
if (_detailedPhoneRegex.IsMatch(param.Mobile) == false)
throw new BusinessException("手机号格式错误");
}
if (string.IsNullOrWhiteSpace(param.PassWord))
throw new BusinessException("密码不能为空");
param.PassWord = Md5Helper.GetMD5(param.PassWord);
var gameConfig = _cache.GetGameConfig();
if (gameConfig == null)
throw new BusinessException("游戏配置错误");
var activityConfig = await _activityConfigService.Get();
if (activityConfig == null)
throw new BusinessException("活动配置错误");
var lockKey = $"{RedisKey.DistributedLockPrefix}user:register:mobile:{param.Mobile}";
var result = await _distributedLock.TryExecuteAsync(lockKey, async () =>
{
UserEntity user;
try
{
user = await _userMongo.FindOneAsync(c => c.BaseInfo.Mobile == param.Mobile);
if (user != null)
{
throw new BusinessException("手机号已被注册");
}
user = await _userMongo.FindOneAsync(c => c.BaseInfo.WXOpenId == param.Account);
if (user != null)
{
throw new BusinessException("账号已被注册");
}
}
catch (BusinessException)
{
throw;
}
catch (Exception ex)
{
_logger.LogError(ex, "注册查重异常 mobile={Mobile} Account={Account}", mobileMasked, param.Account);
throw;
}
user = new UserEntity();
user.BaseInfo.Mobile = param.Mobile;
user.BaseInfo.WXOpenId = param.Account;
user.CreateTime = DateTime.Now.ToTimeStamp();
user.BaseInfo.Password = param.PassWord;
user.BaseInfo.Number = await GetUserNumner();
user.BaseInfo.UserName = param.NickName;
user.Init(gameConfig, activityConfig);
user.RegisterIp = ip;
user.Static.LastLoginTime = DateTime.Now.ToTimeStamp();
user.Static.LastLoginIp = ip;
var res = await _userMongo.InsertOneAsync(user);
if (!res.ok)
{
throw new BusinessException("注册失败");
}
var model = await LoginToken(user);
return (true, "注册成功", model);
}, expiry: 30);
if (result == default)
throw new BusinessException("系统繁忙,请稍后再试");
return result;
}
public async Task<(bool isok, string msg)> SendMobileCode(string mobile)
{
if (!ValidationHelper.IsValidMobile(mobile))
throw new BusinessException("手机号格式错误");
string code = "000000";
{
}
_cache.SetVerificationCode(mobile, code);
return (true, "发送成功");
}
public async Task<UserToken> LoginWx(string openId, string ip)
{
var user = await GetUserByOpenId(openId);
if (user == null)
{
await AddLoginLog(null, null, null, 2, 0, ip, "用户不存在");
return null;
}
try
{
user.Static.LastLoginTime = DateTime.Now.ToTimeStamp();
user.Static.LastLoginIp = ip;
await _userMongo.ReplaceOneAsync(user);
await AddLoginLog(user.Id, user.BaseInfo.Number, user.BaseInfo.UserName, 2, 1, ip, null);
}
catch
{
// ignore
}
await HandleLoginRoomSettlementAsync(user.Id);
user = await _userMongo.FindByIdAsync(user.Id);
var model = await LoginToken(user);
return model;
}
public async Task<(bool isok, string msg, UserToken user)> Login(string mobile, string code, string ip)
{
if (string.IsNullOrEmpty(mobile))
throw new BusinessException("请输入手机号");
if (string.IsNullOrEmpty(code))
throw new BusinessException("请输入验证码");
var user = await GetUserByMobile(mobile);
if (user == null)
{
await AddLoginLog(null, null, mobile, 1, 0, ip, "用户不存在");
throw new BusinessException("用户不存在");
}
var redisCode = _cache.GetVerificationCode(mobile);
var isCodeLogin = !string.IsNullOrEmpty(redisCode) && redisCode == code;
var isPasswordLogin = Md5Helper.GetMD5(code) == user.BaseInfo.Password;
if (!isCodeLogin && !isPasswordLogin)
{
await AddLoginLog(user.Id, user.BaseInfo.Number, user.BaseInfo.UserName, 1, 0, ip, "验证码或密码错误");
throw new BusinessException("验证码错误");
}
if (isCodeLogin)
_cache.RemoveVerificationCode(mobile);
user.Static.LastLoginTime = DateTime.Now.ToTimeStamp();
user.Static.LastLoginIp = ip;
await _userMongo.ReplaceOneAsync(user);
await AddLoginLog(user.Id, user.BaseInfo.Number, user.BaseInfo.UserName, 1, 1, ip, null);
await HandleLoginRoomSettlementAsync(user.Id);
user = await _userMongo.FindByIdAsync(user.Id);
var model = await LoginToken(user);
return (true, "登录成功", model);
}
public async Task<(bool isok, string msg, UserToken user)> LoginByPassword(string mobile, string password, string ip)
{
if (string.IsNullOrEmpty(mobile))
return (false, "请输入手机号", null);
if (string.IsNullOrEmpty(password))
return (false, "请输入密码", null);
var user = await GetUserByMobile(mobile);
if (user == null)
{
await AddLoginLog(null, null, mobile, 1, 0, ip, "用户不存在");
return (false, "用户不存在", null);
}
if (string.IsNullOrEmpty(user.BaseInfo.Password))
{
await AddLoginLog(user.Id, user.BaseInfo.Number, user.BaseInfo.UserName, 1, 0, ip, "未设置密码");
return (false, "该账号未设置密码,请使用验证码登录", null);
}
if (Md5Helper.GetMD5(password) != user.BaseInfo.Password)
{
await AddLoginLog(user.Id, user.BaseInfo.Number, user.BaseInfo.UserName, 1, 0, ip, "密码错误");
return (false, "密码错误", null);
}
user.Static.LastLoginTime = DateTime.Now.ToTimeStamp();
user.Static.LastLoginIp = ip;
await _userMongo.ReplaceOneAsync(user);
await AddLoginLog(user.Id, user.BaseInfo.Number, user.BaseInfo.UserName, 1, 1, ip, null);
await HandleLoginRoomSettlementAsync(user.Id);
user = await _userMongo.FindByIdAsync(user.Id);
var model = await LoginToken(user);
return (true, "登录成功", model);
}
private async Task AddLoginLog(string? userId, string? userNumber, string? userName, int loginType, int status, string ip, string? failReason)
{
try
{
var log = new UserLoginLog
{
UserId = userId,
UserNumber = userNumber,
UserName = userName,
LoginType = loginType,
Status = status,
Ip = ip,
FailReason = failReason,
LoginTime = DateTime.Now
};
await _loginLogMongo.InsertOneAsync(log);
}
catch (Exception ex)
{
_logger.LogError(ex, "记录登录日志失败");
}
}
public async Task<(bool isok, string msg)> UpdatePassword(string mobile, string password, string code)
{
if (!ValidationHelper.IsValidMobile(mobile))
throw new BusinessException("手机号格式错误");
if (string.IsNullOrWhiteSpace(password) || password.Length < 6 || password.Length > 20)
throw new BusinessException("密码长度应为6-20位");
var redisCode = _cache.GetVerificationCode(mobile);
if (redisCode == null)
throw new BusinessException("请先发送验证码");
if (redisCode != code)
throw new BusinessException("验证码错误");
var user = await GetUserByMobile(mobile);
if (user == null)
throw new BusinessException("用户不存在");
password = Md5Helper.GetMD5(password);
_cache.RemoveVerificationCode(mobile);
user.BaseInfo.Password = password;
var res = await _userMongo.ReplaceOneAsync(user);
if (!res.ok)
throw new BusinessException("修改失败");
else
return (true, "修改成功");
}
public async Task<(bool isok, string msg)> InitUserPassword(string userId)
{
var user = await _userMongo.FindOneAsync(c => c.Id == userId);
if (user == null)
return (false, "用户不存在");
user.BaseInfo.Password = Md5Helper.GetMD5("123456");
var res = await _userMongo.ReplaceOneAsync(user);
if (!res.ok)
return (false, "初始化失败");
return (true, "用户密码已初始化为123456");
}
public async Task<(bool isok, string msg)> InitSafeBoxPassword(string userId)
{
var user = await _userMongo.FindOneAsync(c => c.Id == userId);
if (user == null)
return (false, "用户不存在");
user.SafeBox.Password = Md5Helper.GetMD5("123456");
var res = await _userMongo.ReplaceOneAsync(user);
if (!res.ok)
return (false, "初始化失败");
return (true, "保险箱密码已初始化为123456");
}
public async Task<(bool isok, string msg)> UpdateUserName(string userName, LoginUser loginUser)
{
if (string.IsNullOrWhiteSpace(userName) || !ValidationHelper.IsValidUserName(userName))
return (false, "用户名格式错误,支持2-20位中英文、数字、下划线");
var user = await GetUser(loginUser.UserId);
if (user == null) throw new BusinessException("用户不存在");
if( BlockWordHelper.IsHaveBlockWord(userName))
return (false, "包含敏感词");
if (user.Props == null || user.Props.Count() == 0)
return (false, "请先购买改名卡");
var prop = user.Props.FirstOrDefault(c => c.Type == ProductTypeEnum.改名卡);
if (prop == null)
return (false, "请先购买改名卡");
if (prop.Count == 0)
return (false, "改名卡不足");
if (prop.Count == 1)
{
user.Props.Remove(prop);
}
else
prop.Count--;
user.BaseInfo.UserName = userName;
await _userMongo.ReplaceOneAsync(user);
return (true, "更新成功");
}
public async Task<(bool isok, string msg)> BindWX(string openId, LoginUser loginUser)
{
if (string.IsNullOrWhiteSpace(openId))
throw new BusinessException("OpenId不能为空");
var user = await GetUser(loginUser.UserId);
if (user == null) throw new BusinessException("用户不存在");
if (!string.IsNullOrWhiteSpace(user.BaseInfo.WXOpenId)) throw new BusinessException("微信已绑定");
if (_userMongo.Count(c => c.BaseInfo.WXOpenId == openId) > 0)
{
throw new BusinessException("此微信号已被绑定,请先解绑");
}
user.BaseInfo.WXOpenId = openId;
await _userMongo.ReplaceOneAsync(user);
return (true, "更新成功");
}
public async Task<(bool isok, string msg)> BindMobile(string mobile, string code, LoginUser loginUser)
{
var redisCode = _cache.GetVerificationCode(mobile);
if (redisCode == null)
throw new BusinessException("请先发送验证码");
if (redisCode != code)
throw new BusinessException("验证码错误");
var user = await GetUser(loginUser.UserId);
if (user == null) throw new BusinessException("用户不存在");
if (!string.IsNullOrWhiteSpace(user.BaseInfo.Mobile)) throw new BusinessException("已绑定手机号");
if (_userMongo.Count(c => c.BaseInfo.Mobile == mobile) > 0)
{
throw new BusinessException("此手机号已被绑定");
}
user.BaseInfo.Mobile = mobile;
await _userMongo.ReplaceOneAsync(user);
return (true, "更新成功");
}
public async Task<(bool isok, string msg)> UpdateAvatar(string avatarUrl, LoginUser loginUser)
{
var user = await GetUser(loginUser.UserId);
if (user == null) throw new BusinessException("用户不存在");
user.BaseInfo.AvatarUrl = avatarUrl;
await _userMongo.ReplaceOneAsync(user);
return (true, "更新成功");
}
public async Task<(bool isok, string msg)> UpdateAvatarFrame(string avatarFrame, LoginUser loginUser)
{
var user = await GetUser(loginUser.UserId);
if (user == null) throw new BusinessException("用户不存在");
user.BaseInfo.AvatarFrame = avatarFrame;
await _userMongo.ReplaceOneAsync(user);
return (true, "更新成功");
}
public async Task<(bool isok, string msg)> RealNameAuth(string idCardNumber, string realName, LoginUser loginUser)
{
if (string.IsNullOrWhiteSpace(idCardNumber))
throw new BusinessException("身份证号不能为空");
if (!ValidationHelper.IsValidIdCard(idCardNumber))
throw new BusinessException("身份证号格式错误");
if (string.IsNullOrWhiteSpace(realName) || realName.Length < 2 || realName.Length > 20)
throw new BusinessException("真实姓名格式错误");
var user = await GetUser(loginUser.UserId);
if (user == null) throw new BusinessException("用户不存在");
if (user.RealName != null)
throw new BusinessException("已完成实名认证");
user.RealName = new RealNameInfo()
{
RealName = realName,
IDCard = idCardNumber
};
await _userMongo.ReplaceOneAsync(user);
return (true, "实名认证成功");
}
public async Task<UserEntity> SearchUser(string key)
{
var model = await _userMongo.FindOneAsync(c => c.Id == key || c.BaseInfo.Number == key || c.BaseInfo.UserName.Contains(key) || c.BaseInfo.Mobile.Contains(key));
if (model == null)
return null;
if (model.BanInfo != null && model.BanInfo.IsBan)
{
if (model.BanInfo.BanDays > 0 && DateTime.Now.ToTimeStamp() > model.BanInfo.BanEndTime)
{
model.BanInfo.IsBan = false;
model.BanInfo.BanDays = 0;
model.BanInfo.BanEndTime = 0;
model.BanInfo.BanReason = "";
model.BanInfo.BanTime = 0;
await _userMongo.ReplaceOneAsync(model);
return model;
}
throw new BusinessException("用户已被封禁 原因:" + model.BanInfo.BanReason + " 到期时间" + model.BanInfo.BanEndTime.ToDateTime().ToString("yyyy-MM-dd hh:mm"));
}
if (model.IsDelete)
throw new BusinessException("用户已被删除");
return model;
}
public async Task<UserEntity> GetUser(string id)
{
var model = await _userMongo.FindOneAsync(c => c.Id == id || c.BaseInfo.Number==id);
if (model == null)
return null;
if (model.BanInfo != null && model.BanInfo.IsBan)
{
if (model.BanInfo.BanDays > 0 && DateTime.Now.ToTimeStamp() > model.BanInfo.BanEndTime)
{
model.BanInfo.IsBan = false;
model.BanInfo.BanDays = 0;
model.BanInfo.BanEndTime = 0;
model.BanInfo.BanReason = "";
model.BanInfo.BanTime = 0;
await _userMongo.ReplaceOneAsync(model);
return model;
}
throw new BusinessException("用户已被封禁 原因:" + model.BanInfo.BanReason + " 到期时间" + model.BanInfo.BanEndTime.ToDateTime().ToString("yyyy-MM-dd hh:mm"));
}
if (model.IsDelete)
throw new BusinessException("用户已被删除");
return model;
}
public async Task<UserEntity> GetUserByMobile(string mobile)
{
var model = await _userMongo.FindOneAsync(c => c.BaseInfo.Mobile == mobile);
if (model == null)
{
throw new BusinessException("用户不存在");
}
if (model.BanInfo != null && model.BanInfo.IsBan)
{
if (model.BanInfo.BanDays > 0 && DateTime.Now.ToTimeStamp() > model.BanInfo.BanEndTime)
{
model.BanInfo.IsBan = false;
model.BanInfo.BanDays = 0;
model.BanInfo.BanEndTime = 0;
model.BanInfo.BanReason = "";
model.BanInfo.BanTime = 0;
await _userMongo.ReplaceOneAsync(model);
return model;
}
throw new BusinessException("用户已被封禁 原因:" + model.BanInfo.BanReason + " 到期时间" + model.BanInfo.BanEndTime.ToDateTime().ToString("yyyy-MM-dd hh:mm"));
}
if (model.IsDelete)
throw new BusinessException("用户已被删除");
return model;
}
public async Task<UserEntity> GetUserByNuimber(string number)
{
var model = await _userMongo.FindOneAsync(c => c.BaseInfo.Number == number);
if (model == null)
{
throw new BusinessException("用户不存在");
}
if (model.BanInfo != null && model.BanInfo.IsBan)
{
if (model.BanInfo.BanDays > 0 && DateTime.Now.ToTimeStamp() > model.BanInfo.BanEndTime)
{
model.BanInfo.IsBan = false;
model.BanInfo.BanDays = 0;
model.BanInfo.BanEndTime = 0;
model.BanInfo.BanReason = "";
model.BanInfo.BanTime = 0;
await _userMongo.ReplaceOneAsync(model);
return model;
}
throw new BusinessException("用户已被封禁 原因:" + model.BanInfo.BanReason + " 到期时间" + model.BanInfo.BanEndTime.ToDateTime().ToString("yyyy-MM-dd hh:mm"));
}
if (model.IsDelete)
throw new BusinessException("用户已被删除");
return model;
}
public async Task<UserEntity> GetUserByOpenId(string openId)
{
var model = await _userMongo.FindOneAsync(c => c.BaseInfo.WXOpenId == openId);
if (model == null)
{
throw new BusinessException("用户不存在");
}
if (model.BanInfo != null && model.BanInfo.IsBan)
{
if (model.BanInfo.BanDays > 0 && DateTime.Now.ToTimeStamp() > model.BanInfo.BanEndTime)
{
model.BanInfo.IsBan = false;
model.BanInfo.BanDays = 0;
model.BanInfo.BanEndTime = 0;
model.BanInfo.BanReason = "";
model.BanInfo.BanTime = 0;
await _userMongo.ReplaceOneAsync(model);
return model;
}
throw new BusinessException("用户已被封禁 原因:" + model.BanInfo.BanReason + " 到期时间" + model.BanInfo.BanEndTime.ToDateTime().ToString("yyyy-MM-dd hh:mm"));
}
if (model.IsDelete)
throw new BusinessException("用户已被删除");
return model;
}
public async Task<(bool isok, string msg)> UpdateUser(UserEntity user)
{
return await _userMongo.ReplaceOneAsync(user);
}
public async Task<RankingResponse> GetRanking(LoginUser loginUser, int type = 1)
{
RankingResponse result = new RankingResponse();
result.RankingType = type;
result.RankingTypeName = type switch
{
1 => "金币排名",
2 => "等级排名",
3 => "今日消耗排名",
_ => "金币排名"
};
System.Linq.Expressions.Expression<Func<UserEntity, object>> sortField;
System.Linq.Expressions.Expression<Func<UserEntity, bool>> countWhere;
Func<UserEntity, long> rankingValue;
switch (type)
{
case 2:
sortField = c => c.Assets.Level;
countWhere = c => !c.IsDelete && c.Assets.Level > 0;
rankingValue = c => c.Assets.Level;
break;
case 3:
sortField = c => c.Static.TotalConsumeGold;
countWhere = c => !c.IsDelete && c.Static.TotalConsumeGold > 0;
rankingValue = c => c.Static.TotalConsumeGold;
break;
default:
sortField = c => c.Assets.Gold;
countWhere = c => !c.IsDelete && c.Assets.Gold > 0;
rankingValue = c => c.Assets.Gold;
break;
}
var list = await _userMongo.QueryPageAsync(countWhere, sortField, true, 500, 1);
int i = 0;
foreach (var item in list.list.Take(50))
{
i++;
result.UserList.Add(new RankingUserResponse()
{
AvatarUrl = item.BaseInfo.AvatarUrl,
NickName = item.BaseInfo.UserName,
Level = item.Assets.Level,
RankingValue = rankingValue(item),
Mobile = item.BaseInfo.Mobile,
Number = item.BaseInfo.Number,
UserId = item.Id,
UserName = item.BaseInfo.UserName,
Rank = i
});
}
var self = result.UserList.FirstOrDefault(c => c.UserId == loginUser.UserId);
if (self != null)
{
result.User = self;
}
else
{
var user = await GetUser(loginUser.UserId);
if (user != null)
{
var rank = type switch
{
2 => await _userMongo.CountAsync(c => !c.IsDelete && c.Assets.Level > user.Assets.Level),
3 => await _userMongo.CountAsync(c => !c.IsDelete && c.Static.TotalConsumeGold > user.Static.TotalConsumeGold),
_ => await _userMongo.CountAsync(c => !c.IsDelete && c.Assets.Gold > user.Assets.Gold)
};
result.User = new RankingUserResponse()
{
AvatarUrl = user.BaseInfo.AvatarUrl,
NickName = user.BaseInfo.UserName,
Level = user.Assets.Level,
RankingValue = rankingValue(user),
Mobile = user.BaseInfo.Mobile,
Number = user.BaseInfo.Number,
UserId = user.Id,
UserName = user.BaseInfo.UserName,
Rank = (int)rank + 1
};
}
}
return result;
}
public async Task<(List<UserEntity> list, int count)> GetUserList(BaseListPageParam param)
{
var where = LinqExtensions.True<UserEntity>().And(e => e.IsDelete == false);
if (!string.IsNullOrWhiteSpace(param.KeyWord))
where = where.And(e => e.BaseInfo.UserName.Contains(param.KeyWord) || e.BaseInfo.Mobile.Contains(param.KeyWord) || e.BaseInfo.Number.Contains(param.KeyWord));
var result = await _userMongo.QueryPageAsync(where, c => c.CreateTime, true, param.PageSize ?? 0, param.PageIndex ?? 0);
return (result.list, (int)result.count);
}
public async Task EveryDayTask()
{
var activityConfig = await _activityConfigService.Get();
var reliefGoldCount = activityConfig?.ReliefConfig?.Count ?? 3;
var updateBase = Builders<UserEntity>.Update
.Set(p => p.Static.OnlineTime, 0)
.Set(p => p.Assets.IsVipDayGift, false)
.Set(p => p.Static.ReliefGoldCount, reliefGoldCount);
await _userMongo.UpdateManyAsync(c => c.Sign != null, updateBase.Set(p => p.Sign.IsSign, false));
await _userMongo.UpdateManyAsync(c => c.Sign == null, updateBase.Set(p => p.Sign, new SignInfo()));
var gameConfig = _cache.GetGameConfig();
_cache.SetSystemGoldDay(gameConfig.KillConfig.DaySystemGold);
await CleanupExpiredEmails();
await CheckBattleOrderExpired();
}
private async Task CheckBattleOrderExpired()
{
var activityConfig = await _activityConfigService.Get();
var battleOrderConfig = activityConfig?.BattleOrderConfig;
if (battleOrderConfig == null || !battleOrderConfig.IsOpen)
return;
var now = DateTime.Now.ToTimeStamp();
if (battleOrderConfig.EndTime > 0 && battleOrderConfig.EndTime < now)
{
await _userBattleOrderMongo.DeleteManyAsync(c => true);
battleOrderConfig.IsOpen = false;
await _activityConfigService.Update(activityConfig);
}
}
public async Task EveryHourTask()
{
await Task.CompletedTask;
}
private const int MaxNumberRetryCount = 50;
private async Task<string> GetUserNumner()
{
for (int i = 0; i < MaxNumberRetryCount; i++)
{
var numner = RandomHelper.RandomNumbers(8);
var model = await _beadfulNumberMongo.FindOneAsync(c => c.Number == numner && c.Status != AdminDomain.BeautifulNumber.BeautifulNumberStatus.Normal);
if (model != null)
continue;
var user = _userMongo.FindOne(c => c.BaseInfo.Number == numner);
if (user != null)
continue;
return numner;
}
throw new BusinessException("无法生成唯一用户编号,请稍后重试");
}
public async Task<(bool isok, string msg)> SaveSafeBox(long gold, LoginUser loginUser)
{
if (gold <= 0)
return (false, "存入金额必须大于0");
var lockKey = $"user:safebox:{loginUser.UserId}";
var lockResult = await _distributedLock.TryExecuteAsync(lockKey, async () =>
{
var user = await GetUser(loginUser.UserId);
if (user.Assets.Gold < gold)
return (false, "金币不足");
if (user.SafeBox == null)
user.SafeBox = new SafeBoxInfo();
if (string.IsNullOrWhiteSpace(user.BaseInfo.Mobile))
return (false, "请先绑定手机号");
if (string.IsNullOrWhiteSpace(user.SafeBox.Password))
return (false, "请先设置保险箱密码");
user.Assets.Gold -= gold;
var userReward = new UserReward() { UserId = user.Id, ChangeCount = gold, BeforeCount = user.SafeBox.Amount, CurrencyType = 2, Remark = "存入保险箱", Source = RewardSource.保险箱, Type = 1 };
user.SafeBox.Amount += gold;
userReward.AfterCount = user.SafeBox.Amount;
user.SafeBox.SaveTime = DateTime.Now.ToTimeStamp();
await _rewardMongo.InsertOneAsync(userReward);
return await _userMongo.ReplaceOneAsync(user);
}, expiry: 10, retry: false);
return lockResult;
}
public async Task<(bool isok, string msg)> GetSafeBox(long gold, string password, LoginUser loginUser)
{
if (gold <= 0)
return (false, "取出金额必须大于0");
var lockKey = $"user:safebox:{loginUser.UserId}";
var lockResult = await _distributedLock.TryExecuteAsync(lockKey, async () =>
{
var user = await GetUser(loginUser.UserId);
if (user.SafeBox == null)
return (false, "用户保险箱不存在");
if (string.IsNullOrWhiteSpace(password))
return (false, "请输入密码");
if (user.SafeBox.Amount < gold)
return (false, "金币不足");
if (user.SafeBox.Password != Md5Helper.GetMD5(password))
return (false, "密码错误");
user.Assets.Gold += gold;
var userReward = new UserReward() { UserId = user.Id, ChangeCount = gold, BeforeCount = user.SafeBox.Amount, CurrencyType = 2, Remark = "取出保险箱", Source = RewardSource.保险箱, Type = 2 };
user.SafeBox.Amount -= gold;
userReward.AfterCount = user.SafeBox.Amount;
await _rewardMongo.InsertOneAsync(userReward);
return await _userMongo.ReplaceOneAsync(user);
}, expiry: 10, retry: false);
return lockResult;
}
public async Task<(bool isok, string msg)> SetSafeBoxPassword(string password, LoginUser loginUser)
{
if (string.IsNullOrWhiteSpace(password) || password.Length < 4 || password.Length > 6)
return (false, "密码长度应为4-6位");
var user = await GetUser(loginUser.UserId);
if (user.SafeBox == null)
user.SafeBox = new SafeBoxInfo();
if (string.IsNullOrWhiteSpace(user.BaseInfo.Mobile))
return (false, "请先绑定手机号");
if (!string.IsNullOrWhiteSpace(user.SafeBox.Password))
return (false, "密码已设置,请先重置密码");
user.SafeBox.Password = Md5Helper.GetMD5(password);
return await _userMongo.ReplaceOneAsync(user);
}
public async Task<(bool isok, string msg)> ResetSafeBoxPassword(string password, string code, LoginUser loginUser)
{
if (string.IsNullOrWhiteSpace(password) || password.Length < 4 || password.Length > 6)
return (false, "密码长度应为4-6位");
var user = await GetUser(loginUser.UserId);
if (user.SafeBox == null)
return (false, "用户保险箱不存在");
if (string.IsNullOrWhiteSpace(password))
return (false, "请输入密码");
if (string.IsNullOrWhiteSpace(user.BaseInfo.Mobile))
return (false, "请先绑定手机号");
if (string.IsNullOrWhiteSpace(code))
return (false, "请输入验证码");
if (code != _cache.GetVerificationCode(user.BaseInfo.Mobile))
return (false, "验证码错误");
user.SafeBox.Password = Md5Helper.GetMD5(password);
return await _userMongo.ReplaceOneAsync(user);
}
public async Task<(bool isok, string msg)> GetReliefGold(LoginUser loginUser)
{
var user = await GetUser(loginUser.UserId);
var activityConfig = await _activityConfigService.Get();
if (activityConfig.ReliefConfig.IsOpen == false)
return (false, "当前不能领取救济金");
if (user.Assets.Gold > activityConfig.ReliefConfig.Limit)
return (false, "当前金币不满足领取救济金的条件");
if (user.Static.ReliefGoldCount <= 0)
return (false, "今日已无领取次数");
user.Static.ReliefGoldCount--;
user.Assets.Gold += activityConfig.ReliefConfig.Gold;
UserReward userReward = new UserReward()
{
UserId = user.Id,
ChangeCount = activityConfig.ReliefConfig.Gold,
CurrencyType = 1,
Remark = "领取救济金",
Source = RewardSource.救济金,
Type = 1,
BeforeCount = user.Assets.Gold - activityConfig.ReliefConfig.Gold,
AfterCount = user.Assets.Gold
};
await _rewardMongo.InsertOneAsync(userReward);
var update = Builders<UserEntity>.Update
.Set(p => p.Assets, user.Assets)
.Set(p => p.Static, user.Static);
return await _userMongo.UpdateByIdAsync(user.Id, update);
}
public async Task<(bool isok, string msg)> SendBroadcast(string message, LoginUser loginUser)
{
if (string.IsNullOrWhiteSpace(message))
return (false, "消息不能为空");
if (message.Length > 20)
return (false, "消息不能超过20个字");
if (BlockWordHelper.IsHaveBlockWord(message))
return (false, "消息包含敏感词");
var user = await GetUser(loginUser.UserId);
var prop = user.Props?.FirstOrDefault(p => p.Number == "40002");
if (prop == null || prop.Count <= 0)
return (false, "大喇叭数量不足");
prop.Count--;
if (prop.Count <= 0)
user.Props.Remove(prop);
var update = Builders<UserEntity>.Update.Set(p => p.Props, user.Props);
await _userMongo.UpdateByIdAsync(user.Id, update);
var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
await _hubContext.Clients.All.SendAsync("Broadcast", new
{
UserName = user.BaseInfo.UserName,
AvatarUrl = user.BaseInfo.AvatarUrl,
Level = user.Assets.Level,
VipLevel = user.Assets.VipLevel,
Message = message,
Time = now
});
return (true, "发送成功");
}
public async Task SendKillAnnouncement(KillAnnounceParam param)
{
var text = $"恭喜玩家{param.UserName}在{param.GameTypeName}{param.RoomName}捕获 {param.FishName}*{param.Multiple}炮倍 {param.Gold}大奖";
await _hubContext.Clients.All.SendAsync("KillAnnouncement", new
{
Text = text,
UserName = param.UserName,
GameTypeName = param.GameTypeName,
RoomName = param.RoomName,
FishName = param.FishName,
Multiple = param.Multiple,
Gold = param.Gold,
Time = param.Timestamp
});
}
public async Task<(bool isok, string msg, BaseProductCount model)> SyntheticFragments(string propNumber, LoginUser loginUser)
{
var user = await GetUser(loginUser.UserId);
if (user.Props == null)
throw new BusinessException("用户背包不存在");
var propFragment = user.Props.Find(c => c.Number == propNumber);
if (propFragment == null)
throw new BusinessException("道具不存在");
var gameConfig = _cache.GetGameConfig();
var propFragmentConfig = gameConfig.PropFragments.Find(c => c.Number == propNumber);
if (propFragmentConfig == null)
throw new BusinessException("道具配置不存在");
if (propFragment.Count < propFragmentConfig.FragmentCount)
throw new BusinessException("碎片数量不足合成");
int count = propFragment.Count / propFragmentConfig.FragmentCount;
if (propFragment.Count == count * propFragmentConfig.FragmentCount)
user.Props.Remove(propFragment);
else
propFragment.Count -= count * 10;
var propConfig = gameConfig.PropConfig.FirstOrDefault(c => c.Number == propFragmentConfig.ProductNumber);
if (propConfig == null)
throw new BusinessException("合成后道具配置不存在");
var prop = user.Props.Find(c => c.Number == propFragmentConfig.ProductNumber);
BaseProduct result = propConfig;
if (prop == null)
{
result = propConfig;
prop = _mapper.Map<BaseProductCount>(result);
prop.Count = count;
if (prop.Count > 0)
user.Props.Add(prop);
}
else
{
if (!ProductType.MoreProductTypeList.Any(c => c == prop.Type))
throw new BusinessException("已有此道具");
prop.Count += count;
}
await _userMongo.ReplaceOneAsync(user);
prop.Count = count;
return (true, "合成成功", prop);
}
public async Task<(bool isok, string msg)> BuyProps(string propNumber, int count, LoginUser loginUser)
{
if (string.IsNullOrWhiteSpace(propNumber))
throw new BusinessException("道具编号不能为空");
if (count <= 0 || count > 999)
throw new BusinessException("购买数量无效");
var user = await GetUser(loginUser.UserId);
if (user.Props == null)
user.Props = new List<BaseProductCount>();
var gameConfig = _cache.GetGameConfig();
var propConfig = gameConfig.PropConfig.Find(c => c.Number == propNumber);
if (propConfig == null)
throw new BusinessException("道具配置不存在");
if (!propConfig.IsSale)
throw new BusinessException("该道具暂未开放购买");
if (propConfig.Price <= 0)
throw new BusinessException("道具价格异常");
var price = propConfig.Price;
if (user.UserType == 2 || user.Assets.IsDealer)
price = propConfig.DealerPrice > 0 ? propConfig.DealerPrice : propConfig.Price;
else if (user.Assets.VipLevel > 0)
price = propConfig.VIPPrice > 0 ? propConfig.VIPPrice : propConfig.Price;
var totalPrice = price * count;
if (user.Assets.Gold < totalPrice)
throw new BusinessException("金币不足");
UserReward userReward = new UserReward() { UserId = user.Id, ChangeCount = totalPrice, CurrencyType = 1, Remark = $"购买道具 x{count}", Source = RewardSource.道具, Type = 2, BeforeCount = user.Assets.Gold };
user.Static.TotalConsumeGold += totalPrice;
user.Assets.Gold -= totalPrice;
if (!ProductType.MoreProductTypeList.Any(c => c == propConfig.Type))
{
if (propConfig.Type == ProductTypeEnum.炮台)
{
var prop = user.CannonList.Find(c => c.Number == propNumber);
if (prop != null)
throw new BusinessException("已有此炮台");
var cannon = _mapper.Map<BaseUserProp>(propConfig);
if (propConfig.Day > 0)
{
cannon.IsTrial = true;
cannon.TrialEndTime = DateTime.Now.AddDays(propConfig.Day).ToTimeStamp();
}
user.CannonList.Add(cannon);
}
if (propConfig.Type == ProductTypeEnum.翅膀)
{
var prop = user.WingList.Find(c => c.Number == propNumber);
if (prop != null)
throw new BusinessException("已有此翅膀");
var cannon = _mapper.Map<BaseUserProp>(propConfig);
if (propConfig.Day > 0)
{
cannon.IsTrial = true;
cannon.TrialEndTime = DateTime.Now.AddDays(propConfig.Day).ToTimeStamp();
}
user.WingList.Add(cannon);
}
if (propConfig.Type == ProductTypeEnum.战神)
{
var prop = user.GodWarList.Find(c => c.Number == propNumber);
if (prop != null)
throw new BusinessException("已有此战神");
var cannon = _mapper.Map<BaseUserProp>(propConfig);
if (propConfig.Day > 0)
{
cannon.IsTrial = true;
cannon.TrialEndTime = DateTime.Now.AddDays(propConfig.Day).ToTimeStamp();
}
user.GodWarList.Add(cannon);
}
}
else
{
var prop = user.Props.Find(c => c.Number == propNumber);
if (prop != null)
{
prop.Count += propConfig.SaleCount * count;
}
else
{
var result = propConfig;
prop = _mapper.Map<BaseProductCount>(result);
prop.Count = propConfig.SaleCount * count;
if (prop.Count > 0)
user.Props.Add(prop);
}
}
await _rewardMongo.InsertOneAsync(userReward);
return await _userMongo.ReplaceOneAsync(user);
}
public async Task<(bool isok, string msg)> UseVipExp(int count, LoginUser loginUser)
{
if (count <= 0 || count > 999)
return (false, "数量无效");
var user = await GetUser(loginUser.UserId);
if (user.Props == null || user.Props.Count == 0)
return (false, "背包中没有VIP经验包");
var prop = user.Props.Find(c => c.Type == ProductTypeEnum.VipExp);
if (prop == null || prop.Count < count)
return (false, "VIP经验包数量不足");
prop.Count -= count;
if (prop.Count <= 0)
user.Props.Remove(prop);
user.Assets.VipExpExtra += 100L * count;
var gameConfig = _cache.GetGameConfig();
if (gameConfig != null)
{
var newVipLevels = user.VipLevelVerify(gameConfig);
if (newVipLevels != null && newVipLevels.Count > 0)
await AutoDistributeVipGift(user, newVipLevels, gameConfig);
}
await _userMongo.ReplaceOneAsync(user);
return (true, "使用成功");
}
public async Task<(bool isok, string msg)> BuyGold(string number, int count, LoginUser loginUser)
{
if (string.IsNullOrWhiteSpace(number))
throw new BusinessException("金币商品编号不能为空");
if (count <= 0 || count > 999)
throw new BusinessException("购买数量无效");
var activityConfig = await _activityConfigService.Get();
var config = activityConfig.GoldShopConfigs.FirstOrDefault(c => c.Number == number);
if (config == null)
throw new BusinessException("金币配置不存在");
var lockKey = $"user:asset:update:{loginUser.UserId}";
return await _distributedLock.TryExecuteAsync(lockKey, async () =>
{
var user = await GetUser(loginUser.UserId);
var diamondPrice = config.Diamond;
if (user.UserType == 2 || user.Assets.IsDealer)
diamondPrice = config.DealerDiamond > 0 ? config.DealerDiamond : config.Diamond;
if (diamondPrice <= 0)
throw new BusinessException("金币商品钻石价格配置无效");
var totalDiamond = checked(diamondPrice * count);
var totalGold = checked(config.Gold * count);
if (totalGold <= 0)
throw new BusinessException("金币商品数量配置无效");
if (user.Assets.Diamond < totalDiamond)
throw new BusinessException("钻石不足");
var beforeDiamond = user.Assets.Diamond;
var beforeGold = user.Assets.Gold;
user.Assets.Diamond -= totalDiamond;
user.Assets.Gold += totalGold;
user.Static.TotalConsumeDiamond += totalDiamond;
user.Static.TotalRechargeGold += totalGold;
var updateDefinition = Builders<UserEntity>.Update
.Set(p => p.Assets, user.Assets)
.Set(p => p.Static, user.Static);
var updateResult = await _userMongo.UpdateByIdAsync(user.Id, updateDefinition);
if (!updateResult.ok)
return (false, updateResult.message);
try
{
await _rewardMongo.InsertOneAsync(new UserReward
{
UserId = user.Id,
ChangeCount = totalDiamond,
CurrencyType = 3,
Remark = $"购买金币 x{count}",
Source = RewardSource.金币商城,
Type = 2,
BeforeCount = beforeDiamond,
AfterCount = user.Assets.Diamond
});
await _rewardMongo.InsertOneAsync(new UserReward
{
UserId = user.Id,
ChangeCount = totalGold,
CurrencyType = 1,
Remark = $"购买金币 x{count}",
Source = RewardSource.金币商城,
Type = 1,
BeforeCount = beforeGold,
AfterCount = user.Assets.Gold
});
}
catch (Exception ex)
{
_logger.LogError(ex, "[金币商城] 资产已更新但流水写入失败 userId={UserId} number={Number} count={Count}",
user.Id, number, count);
}
try
{
await Task.WhenAll(
_capPublisher.PublishAsync(TopicKeys.用户资产同步前端, new UserAssetSyncParam
{
UserId = user.Id,
CurrencyType = 3,
ChangeType = (int)UserAssetChangeTypeEnum.减少,
Amount = totalDiamond,
NewGold = user.Assets.Gold,
NewDiamond = user.Assets.Diamond,
Source = RewardSource.金币商城.ToString(),
Remark = $"购买金币 x{count}"
}),
_capPublisher.PublishAsync(TopicKeys.用户资产同步前端, new UserAssetSyncParam
{
UserId = user.Id,
CurrencyType = 1,
ChangeType = (int)UserAssetChangeTypeEnum.增加,
Amount = totalGold,
NewGold = user.Assets.Gold,
NewDiamond = user.Assets.Diamond,
Source = RewardSource.金币商城.ToString(),
Remark = $"购买金币 x{count}"
}));
}
catch (Exception ex)
{
_logger.LogError(ex, "[金币商城] 资产实时同步失败 userId={UserId} number={Number} count={Count} newGold={Gold} newDiamond={Diamond}",
user.Id, number, count, user.Assets.Gold, user.Assets.Diamond);
}
return (true, updateResult.message);
}, expiry: 10, retry: true, retryDelay: 100);
}
public async Task<(bool isok, string msg, string url)> BuyDiamond(string number, int count, LoginUser loginUser)
{
if (string.IsNullOrWhiteSpace(number))
throw new BusinessException("钻石商品编号不能为空");
if (count <= 0 || count > 999)
throw new BusinessException("购买数量无效");
var user = await GetUser(loginUser.UserId);
var activityConfig = await _activityConfigService.Get();
var config = activityConfig.DiamondShopConfigs.FirstOrDefault(c => c.Number == number);
if (config == null)
throw new BusinessException("钻石配置不存在");
var price = config.Money;
if (user.UserType == 2 || user.Assets.IsDealer)
price = config.DealerMoney > 0 ? config.DealerMoney : config.Money;
var totalPrice = price * count;
var order = new Order();
order.Type = 2;
order.ProductId = config.Number;
order.ProductName = config.Name + $" x{count}";
order.UserId = loginUser.UserId;
order.State = 1;
order.Price = totalPrice;
await _orderMongo.InsertOneAsync(order);
BuyDiamondPayCallBack(order.Id);
return (true, "购买成功", "");
}
public async Task<(bool isok, string msg)> BuyDiamondPayCallBack(string orderId)
{
var order = await _orderMongo.FindOneAsync(c => c.Id == orderId);
if (order == null)
throw new BusinessException("订单不存在");
if (order.State != 1)
throw new BusinessException("订单状态不对");
var gameconfig = _cache.GetGameConfig();
var activityConfig = await _activityConfigService.Get();
var config = activityConfig.DiamondShopConfigs.FirstOrDefault(c => c.Number == order.ProductId);
if (config == null)
throw new BusinessException("钻石配置不存在");
await UpdateAssets(new UserAssetParam()
{
UserId = order.UserId,
ChangeType = UserAssetChangeTypeEnum.增加,
Diamond = config.Diamond,
Remark = $"购买钻石-{order.ProductName}",
Source = RewardSource.充值
});
order.State = 2;
order.PayTime = DateTime.Now.ToTimeStamp();
await _orderMongo.ReplaceOneAsync(order);
var user = await GetUser(order.UserId);
user.Static.TotalRecharge += order.Price;
var newVipLevels = user.VipLevelVerify(gameconfig);
if (newVipLevels != null && newVipLevels.Count > 0)
{
await AutoDistributeVipGift(user, newVipLevels, gameconfig);
}
await _userMongo.ReplaceOneAsync(user);
if (activityConfig.BattleOrderConfig.IsOpen)
{
_capPublisher.PublishAsync(TopicKeys.任务完成, new CompletedTaskParam() { taskEvent = TaskEvent.充值任务, userId = user.Id, count = order.Price });
}
return (true, "购买成功");
}
public async Task<(bool isok, string msg, SignRewardResult result)> Sign(LoginUser loginUser)
{
var user = await GetUser(loginUser.UserId);
if (user == null)
throw new BusinessException("用户不存在");
var activityConfig = await _activityConfigService.Get();
var signConfig = activityConfig?.SignActivityConfig;
if (signConfig == null || !signConfig.IsOpen)
throw new BusinessException("签到活动未开启");
if (signConfig.Days == null || signConfig.Days.Count == 0)
throw new BusinessException("签到配置不存在");
if (user.Sign == null)
user.Sign = new SignInfo();
var now = DateTime.Now;
var today = now.Date;
DateTime? lastSignDate = user.Sign.SignTime > 0 ? user.Sign.SignTime.ToDateTime().Date : (DateTime?)null;
if (lastSignDate.HasValue && lastSignDate.Value == today)
throw new BusinessException("今日已签到");
var day = CalcSignDay(user.Sign.SignDay, lastSignDate, today, signConfig.CannotInterrupt);
var dayConfig = signConfig.Days.FirstOrDefault(c => c.Day == day) ?? signConfig.Days.OrderBy(c => c.Day).First();
if (dayConfig == null || dayConfig.GiftList == null || dayConfig.GiftList.Count == 0)
throw new BusinessException($"第{day}天签到奖励未配置");
var gift = GetGift(dayConfig.GiftList);
var gold = Convert.ToInt64(gift.Gold * (1 + user.Assets.VipSignInAdd));
var res = await UpdateAssets(new UserAssetParam
{
UserId = user.Id,
ChangeType = UserAssetChangeTypeEnum.增加,
Source = RewardSource.签到,
Remark = $"第{day}天签到奖励",
Gold = gold,
Diamond = gift.Diamond,
RewardItems = gift.Product
});
if (!res.isok)
return (false, res.msg, null);
var signUpdate = Builders<UserEntity>.Update
.Set(p => p.Sign.IsSign, true)
.Set(p => p.Sign.SignTime, now.ToTimeStamp())
.Set(p => p.Sign.SignDay, day);
await _userMongo.UpdateByIdAsync(user.Id, signUpdate);
return (true, "签到成功", new SignRewardResult
{
Day = day,
Gold = gold,
Diamond = gift.Diamond,
Product = gift.Product ?? new List<BaseProductCount>()
});
}
public async Task<SignStatusResult> GetSignStatus(LoginUser loginUser)
{
var user = await GetUser(loginUser.UserId);
var activityConfig = await _activityConfigService.Get();
var signConfig = activityConfig?.SignActivityConfig;
var result = new SignStatusResult
{
IsOpen = signConfig?.IsOpen ?? false,
CannotInterrupt = signConfig?.CannotInterrupt ?? false,
Days = signConfig?.Days?.OrderBy(c => c.Day).ToList() ?? new List<SignConfig>()
};
var today = DateTime.Now.Date;
DateTime? lastSignDate = user?.Sign != null && user.Sign.SignTime > 0
? user.Sign.SignTime.ToDateTime().Date
: (DateTime?)null;
result.SignDay = user?.Sign?.SignDay ?? 0;
result.IsTodaySigned = lastSignDate.HasValue && lastSignDate.Value == today;
result.TodayDay = result.IsTodaySigned
? result.SignDay
: CalcSignDay(result.SignDay, lastSignDate, today, result.CannotInterrupt);
return result;
}
private const int SignCycleDays = 7;
/// <summary>计算本次签到对应的天数(1~7)</summary>
private static int CalcSignDay(int currentDay, DateTime? lastSignDate, DateTime today, bool cannotInterrupt)
{
// 上次签到为昨天:连续签到,天数顺延
if (lastSignDate.HasValue && lastSignDate.Value.Date == today.AddDays(-1))
return currentDay > 0 ? currentDay % SignCycleDays + 1 : 1;
// 漏签:不可中断则继续顺延(累计循环);可中断则重置回第1天
if (currentDay > 0 && cannotInterrupt)
return currentDay % SignCycleDays + 1;
return 1;
}
public async Task<(bool isok, string msg)> ChangeCannon(string number, LoginUser loginUser)
{
var user = await GetUser(loginUser.UserId);
var connon = user.CannonList.Find(c => c.Number == number);
if (connon == null)
throw new BusinessException("未找到该炮台");
user.CannonUsing = connon;
return await _userMongo.ReplaceOneAsync(user);
}
public async Task<(bool isok, string msg)> ChangeWing(string number, LoginUser loginUser)
{
var user = await GetUser(loginUser.UserId);
var connon = user.WingList.Find(c => c.Number == number);
if (connon == null)
throw new BusinessException("未找到该翅膀");
user.WingUsing = connon;
return await _userMongo.ReplaceOneAsync(user);
}
public async Task<(bool isok, string msg)> ChangeGodWar(string number, LoginUser loginUser)
{
var user = await GetUser(loginUser.UserId);
var connon = user.GodWarList.Find(c => c.Number == number);
if (connon == null)
throw new BusinessException("未找到该战神");
user.GodWarUsing = connon;
return await _userMongo.ReplaceOneAsync(user);
}
public async Task<(bool isok, string msg)> UnloadCannon(string number, LoginUser loginUser)
{
var user = await GetUser(loginUser.UserId);
if (user.CannonUsing?.Number != number)
throw new BusinessException("当前未使用该炮台");
user.CannonList ??= new List<BaseUserProp>();
const string defaultCannonNumber = "11001";
var defaultCannon = user.CannonList.FirstOrDefault(c => c.Number == defaultCannonNumber);
if (defaultCannon == null)
{
var defaultCannonConfig = _cache.GetGameConfig()?.PropConfig?
.FirstOrDefault(c => c.Number == defaultCannonNumber && c.Type == ProductTypeEnum.炮台);
defaultCannon = defaultCannonConfig == null
? new BaseUserProp
{
Number = defaultCannonNumber,
Name = "深海之眼",
Desc = "深海之眼",
ImageUrl = "/uploads/11001.png",
Type = ProductTypeEnum.炮台
}
: _mapper.Map<BaseUserProp>(defaultCannonConfig);
user.CannonList.Add(defaultCannon);
}
user.CannonUsing = defaultCannon;
return await _userMongo.ReplaceOneAsync(user);
}
public async Task<(bool isok, string msg)> UnloadWing(string number, LoginUser loginUser)
{
var user = await GetUser(loginUser.UserId);
if (user.WingUsing?.Number != number)
throw new BusinessException("当前未使用该翅膀");
user.WingUsing = null;
return await _userMongo.ReplaceOneAsync(user);
}
public async Task<(bool isok, string msg)> UnloadGodWar(string number, LoginUser loginUser)
{
var user = await GetUser(loginUser.UserId);
if (user.GodWarUsing?.Number != number)
throw new BusinessException("当前未使用该战神");
user.GodWarUsing = null;
return await _userMongo.ReplaceOneAsync(user);
}
public async Task<(bool isok, string msg, GiftResponse model)> GetVipDayGift(LoginUser loginUser)
{
var user = await GetUser(loginUser.UserId);
if (user.Assets.VipLevel == 0)
throw new BusinessException("非VIP无法领取");
if (user.Assets.IsVipDayGift == true)
throw new BusinessException("今天已经领取过了");
var gameConfig = _cache.GetGameConfig();
var vipConfig = gameConfig.VipLevelConfigs.Find(c => c.Level == user.Assets.VipLevel);
if (vipConfig == null)
throw new BusinessException("Vip配置不存在");
if (vipConfig.DailyGift == null || vipConfig.DailyGift.Count == 0)
throw new BusinessException("Vip每日礼包配置不存在");
var result = GetGift(vipConfig.DailyGift);
user.Assets.IsVipDayGift = true;
await _userMongo.ReplaceOneAsync(user);
var res = await UpdateAssets(new UserAssetParam() { ChangeType = UserAssetChangeTypeEnum.增加, Gold = result.Gold, Diamond = result.Diamond, UserId = user.Id, Remark = "Vip每日礼包", Source = RewardSource.Vip每日礼包, RewardItems = result.Product });
if (res.isok)
return (true, "领取成功", result);
else
return (false, res.msg, null);
}
public async Task<(bool isok, string msg, GiftResponse model)> GetVipGift(int vipLevel, LoginUser loginUser)
{
var user = await GetUser(loginUser.UserId);
if (user.Assets.VipLevel == 0)
throw new BusinessException("非VIP无法领取");
if (user.Assets.VipGift == null || user.Assets.VipGift.Count == 0)
throw new BusinessException("没有需要领取的礼包");
if (!user.Assets.VipGift.Any(c => c == vipLevel))
throw new BusinessException("没有需要领取的礼包");
var level = user.Assets.VipGift.FirstOrDefault(c => c == vipLevel);
var gameConfig = _cache.GetGameConfig();
var vipConfig = gameConfig.VipLevelConfigs.Find(c => c.Level == vipLevel);
if (vipConfig == null)
throw new BusinessException("Vip配置不存在");
if (vipConfig.GiftList == null || vipConfig.GiftList.Count == 0)
throw new BusinessException("Vip权益礼包配置不存在");
var result = GetGift(vipConfig.GiftList);
user.Assets.VipGift.Remove(level);
await _userMongo.ReplaceOneAsync(user);
var res = await UpdateAssets(new UserAssetParam() { ChangeType = UserAssetChangeTypeEnum.增加, Gold = result.Gold, Diamond = result.Diamond, UserId = user.Id, Remark = "Vip权益礼包", Source = RewardSource.Vip权益礼包, RewardItems = result.Product });
if (res.isok)
return (true, "领取成功", result);
else
return (false, res.msg, null);
}
public async Task<(List<UserReward> list, int count)> GetUserRewardList(GetUserRewardListParam param)
{
var where = LinqExtensions.True<UserReward>().And(e => e.IsDelete == false);
if (!string.IsNullOrWhiteSpace(param.UserId))
where = where.And(e => e.UserId == param.UserId);
if (param.Type != 0)
where = where.And(e => e.Type == param.Type);
if (param.Source >0)
where = where.And(e => (int)e.Source == param.Source);
if (param.CurrencyType != 0)
where = where.And(e => e.CurrencyType == param.CurrencyType);
var result = await _rewardMongo.QueryPageAsync(where, c => c.CreateTime, true, param.PageSize ?? 0, param.PageIndex ?? 0);
return (result.list, (int)result.count);
}
public async Task<long> GameCoinsSettlement(GameCoinsSettlementParam param)
{
var session = _cache.GetRedisUserCId(param.UserId);
var currentRoom = !string.IsNullOrWhiteSpace(session?.RoomId)
? _cache.GetGameRoom(session.RoomId)
: null;
var currentPlayer = currentRoom?.Players?.FirstOrDefault(p => p.UserId == param.UserId && !p.IsBot);
if (session?.RoomId != param.RoomInfo
|| currentPlayer == null
|| (param.RoomSessionId > 0 && currentPlayer.BeginTime != param.RoomSessionId))
{
return currentPlayer?.Gold ?? param.AfterGold;
}
var lockKey = $"user:asset:update:{param.UserId}";
var settled = await _distributedLock.TryExecuteAsync(lockKey, async () =>
{
var user = await GetUser(param.UserId);
if (user == null)
{
return param.AfterGold;
}
long netChange = param.AfterGold - param.BeforeGold;
long beforeGold = user.Assets.Gold;
long newGold = Math.Max(0, user.Assets.Gold + netChange);
user.Assets.Gold = newGold;
var type = netChange >= 0 ? 1 : 2;
var changeCount = Math.Abs(netChange);
var model = new UserReward() { UserId = param.UserId, BeforeCount = beforeGold, Source = RewardSource.游戏金币结算, Remark = "游戏金币结算", ChangeCount = changeCount, Type = type, CurrencyType = 1, RoomName = param.RoomName, RoomInfo = param.RoomInfo, AfterCount = newGold };
await _rewardMongo.InsertOneAsync(model);
var updateDefinition = Builders<UserEntity>.Update.Set(p => p.Assets, user.Assets);
await _userMongo.UpdateByIdAsync(user.Id, updateDefinition);
try
{
await _capPublisher.PublishAsync(TopicKeys.用户资产同步前端, new UserAssetSyncParam
{
UserId = param.UserId,
CurrencyType = 1,
ChangeType = type,
Amount = changeCount,
NewGold = newGold,
NewDiamond = user.Assets.Diamond,
Source = "游戏金币结算",
Remark = param.RoomName
});
}
catch (Exception ex)
{
_logger.LogError(ex, "游戏金币结算后发布资产更新失败 userId={UserId}", param.UserId);
}
return newGold;
}, expiry: 10, retry: true, retryDelay: 100);
return settled;
}
private async Task HandleLoginRoomSettlementAsync(string userId)
{
try
{
var userRedis = _cache.GetRedisUserCId(userId);
var roomId = userRedis?.RoomId;
if (string.IsNullOrWhiteSpace(roomId))
roomId = _cache.GetPendingFishRoomId(userId);
if (string.IsNullOrWhiteSpace(roomId))
return;
var room = _cache.GetGameRoom(roomId);
if (room == null)
{
if (userRedis != null)
{
userRedis.RoomId = "";
userRedis.CId = "";
_cache.SetRedisUserCId(userRedis);
}
return;
}
var player = room.Players?.FirstOrDefault(p => p.UserId == userId && !p.IsBot);
if (player == null)
{
if (userRedis != null)
{
userRedis.RoomId = "";
userRedis.CId = "";
_cache.SetRedisUserCId(userRedis);
}
return;
}
await GameCoinsSettlement(new GameCoinsSettlementParam
{
UserId = userId,
BeforeGold = player.BeginCoin,
AfterGold = player.Gold,
RoomSessionId = player.BeginTime,
RoomInfo = roomId,
RoomName = $"{room.GameType}-{room.RoomName}"
});
room.Players.Remove(player);
_cache.SetGameRoom(room);
if (userRedis != null)
{
userRedis.RoomId = "";
userRedis.CId = "";
_cache.SetRedisUserCId(userRedis);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "[登录结算] 处理失败 userId={UserId}", userId);
}
}
public async Task<(bool isok, string msg)> SendEmail(SendEmailParam param, LoginUser loginUser)
{
const int MAX_EMAIL_COUNT = 200;
const int EMAIL_EXPIRE_DAYS = 30;
var now = DateTime.Now.ToTimeStamp();
List<string> ids = new List<string>();
if (string.IsNullOrWhiteSpace(param.UserId))
{
ids = (await _userMongo.FilterByAsync(c => (c.BanInfo == null || c.BanInfo.IsBan == false) && c.IsDelete == false)).Select(c => c.Id).ToList();
}
else
{
ids = (await _userMongo.FilterByAsync(c => (c.BanInfo == null || c.BanInfo.IsBan == false) && c.IsDelete == false && param.UserId.Contains(c.BaseInfo.Number))).Select(c => c.Id).ToList();
}
List<UserEmail> emails = new List<UserEmail>();
foreach (var id in ids)
{
var userEmails = await _emailMongo.FilterByAsync(c => c.UserId == id);
if (userEmails != null && userEmails.Count >= MAX_EMAIL_COUNT)
{
var sorted = userEmails.OrderBy(c => c.SendTime).ToList();
var deleteCount = userEmails.Count - MAX_EMAIL_COUNT + 1;
for (int i = 0; i < deleteCount; i++)
{
await _emailMongo.DeleteByIdAsync(sorted[i].Id);
}
}
var expiredEmails = userEmails?.Where(c => c.ExpireTime > 0 && c.ExpireTime < now).ToList();
if (expiredEmails != null)
{
foreach (var exp in expiredEmails)
{
await _emailMongo.DeleteByIdAsync(exp.Id);
}
}
var email = new UserEmail();
email.UserId = id;
email.Title = param.Title;
email.Content = param.Content;
email.SendTime = DateTime.Now.ToTimeStamp();
email.RewardDiamond = param.RewardDiamond;
email.RewardGold = param.RewardGold;
email.RewardItems = param.RewardItems;
email.IsReward = true;
if (email.RewardGold > 0 || email.RewardDiamond > 0 || (email.RewardItems != null && email.RewardItems.Count > 0))
{
email.IsReward = false;
}
email.CreateInfo = new OptionUserInfo(loginUser.UserId,loginUser.UserName);
email.ExpireTime = DateTime.Now.AddDays(EMAIL_EXPIRE_DAYS).ToTimeStamp();
emails.Add(email);
}
var res = await _emailMongo.InsertManyAsync(emails);
return (true, "发送成功");
}
public async Task CleanupExpiredEmails()
{
const int MAX_EMAIL_COUNT = 200;
const int EMAIL_EXPIRE_DAYS = 30;
var now = DateTime.Now.ToTimeStamp();
var expireTime = DateTime.Now.AddDays(-EMAIL_EXPIRE_DAYS).ToTimeStamp();
var users = await _userMongo.FilterByAsync(c => c.IsDelete == false);
foreach (var user in users)
{
var emails = await _emailMongo.FilterByAsync(c => c.UserId == user.Id);
if (emails == null || emails.Count == 0) continue;
var sortedEmails = emails.OrderByDescending(c => c.SendTime).ToList();
if (sortedEmails.Count > MAX_EMAIL_COUNT)
{
var deleteIds = sortedEmails.Skip(MAX_EMAIL_COUNT).Select(c => c.Id).ToList();
foreach (var deleteId in deleteIds)
{
await _emailMongo.DeleteByIdAsync(deleteId);
}
}
var expiredEmails = emails.Where(c => c.SendTime < expireTime || (c.ExpireTime > 0 && c.ExpireTime < now)).ToList();
foreach (var expiredEmail in expiredEmails)
{
await _emailMongo.DeleteByIdAsync(expiredEmail.Id);
}
}
}
public async Task<UserEmail> GetEmail(string id, LoginUser loginUser)
{
var email = await _emailMongo.FindByIdAsync(id);
if (email == null) return null;
if (email.UserId != loginUser.UserId)
return null;
if (!email.IsRead)
{
email.ReadTime = DateTime.Now.ToTimeStamp();
email.IsRead = true;
await _emailMongo.ReplaceOneAsync(email);
}
EnrichEmailItems(email);
return email;
}
public async Task<(bool isok, string msg, RewardEmailResponse model)> RewardEmail(int type, string id, LoginUser loginUser)
{
if (type == 1)
{
var email = await _emailMongo.FindByIdAsync(id);
if (email == null) throw new BusinessException("邮件不存在");
if (email.UserId != loginUser.UserId)
throw new BusinessException("邮件不存在");
if (email.IsReward)
throw new BusinessException("邮件已领取" );
if (email.RewardGold > 0 || email.RewardDiamond > 0 || (email.RewardItems != null && email.RewardItems.Count > 0))
{
var updateResult = await UpdateAssets(new UserAssetParam() { Diamond = email.RewardDiamond, Gold = email.RewardGold, UserId = email.UserId, ChangeType = UserAssetChangeTypeEnum.增加, RewardItems = email.RewardItems, Source = RewardSource.邮件, Remark = "领取邮件奖励" });
if (!updateResult.isok)
throw new BusinessException(updateResult.msg);
email.IsReward = true;
email.IsRead = true;
email.ReadTime = DateTime.Now.ToTimeStamp();
await _emailMongo.ReplaceOneAsync(email);
return (true, "领取成功", new RewardEmailResponse { RewardGold = email.RewardGold, RewardDiamond = email.RewardDiamond, RewardItems = email.RewardItems });
}
else
{
email.IsReward = true;
await _emailMongo.ReplaceOneAsync(email);
return (true, "无需领取",null);
}
}
else
{
var emails = await _emailMongo.FilterByAsync(c => c.UserId == loginUser.UserId && !c.IsReward);
if (emails == null || emails.Count() == 0)
return (true, "无需领取", null);
long RewardGold = 0;
int RewardDiamond = 0;
Dictionary<string, BaseProductCount> RewardItems = new Dictionary<string, BaseProductCount>();
foreach (var email in emails)
{
RewardGold += email.RewardGold;
RewardDiamond += email.RewardDiamond;
if (email.RewardItems != null)
{
foreach (var item in email.RewardItems)
{
if (RewardItems.TryGetValue(item.Number, out var existing))
existing.Count += item.Count;
else
RewardItems[item.Number] = item;
}
}
if (email.RewardGold > 0 || email.RewardDiamond > 0 || (email.RewardItems != null && email.RewardItems.Count > 0))
{
var updateResult = await UpdateAssets(new UserAssetParam() { Diamond = email.RewardDiamond, Gold = email.RewardGold, UserId = email.UserId, ChangeType = UserAssetChangeTypeEnum.增加, RewardItems = email.RewardItems, Source = RewardSource.邮件, Remark = "领取邮件奖励" });
if (!updateResult.isok)
throw new BusinessException(updateResult.msg);
}
email.ReadTime = DateTime.Now.ToTimeStamp();
email.IsRead = true;
email.IsReward = true;
await _emailMongo.ReplaceOneAsync(email);
}
return (true, "领取成功",new RewardEmailResponse { RewardGold = RewardGold, RewardDiamond = RewardDiamond, RewardItems = RewardItems.Values.ToList() });
}
}
public async Task<(bool isok, string msg)> DeleteEmail(int type, string id, LoginUser loginUser)
{
if (type == 1)
return await _emailMongo.DeleteByIdAsync(id);
return await _emailMongo.DeleteManyAsync(c => c.UserId == loginUser.UserId && c.IsRead && c.IsReward);
}
public async Task<(bool isok, string msg)> ReadEmailMore(LoginUser loginUser)
{
var emails = await _emailMongo.FilterByAsync(c => c.UserId == loginUser.UserId && !c.IsRead);
if (emails == null || emails.Count() == 0)
return (true, "无未读取的邮件");
foreach (var email in emails)
{
email.ReadTime = DateTime.Now.ToTimeStamp();
email.IsRead = true;
await _emailMongo.ReplaceOneAsync(email);
}
return (true, "读取成功");
}
public async Task<UserEmail> GetEmail(string id)
{
var email = await _emailMongo.FindByIdAsync(id);
if (email != null) EnrichEmailItems(email);
return email;
}
public async Task<long> GetNoReadCount(LoginUser loginUser)
{
return await _emailMongo.CountAsync(c => c.UserId == loginUser.UserId && !c.IsRead);
}
public async Task<(List<UserEmail> list, long count)> GetEmailByPage(GetEmailPageParam param, LoginUser loginUser)
{
var where = LinqExtensions.True<UserEmail>().And(e => e.IsDelete == false);
if (loginUser != null && !string.IsNullOrEmpty(loginUser.UserId))
where = where.And(e => e.UserId == loginUser.UserId);
if (param.ReadStatus > 0)
{
where = where.And(e => e.IsRead == (param.ReadStatus == 1));
}
var result = await _emailMongo.QueryPageAsync(where, c => c.SendTime, true, param.PageSize ?? 0, param.PageIndex ?? 0);
foreach (var email in result.list)
EnrichEmailItems(email);
return (result.list, result.count);
}
private void EnrichEmailItems(UserEmail email)
{
if (email?.RewardItems == null || email.RewardItems.Count == 0) return;
try
{
var gameConfig = _cache.GetGameConfig();
if (gameConfig?.PropConfig == null) return;
foreach (var item in email.RewardItems)
{
if (!string.IsNullOrEmpty(item.ImageUrl)) continue;
var prop = gameConfig.PropConfig.FirstOrDefault(p => p.Number == item.Number);
if (prop == null) continue;
item.Name = prop.Name;
item.ImageUrl = prop.ImageUrl;
item.Desc = prop.Desc;
item.Type = prop.Type;
item.Level = prop.Level;
}
}
catch (Exception ex)
{
_logger.LogError(ex, "补全邮件奖励道具信息失败 emailId={EmailId}", email?.Id);
}
}
public async Task<(bool isok, string msg)> UpdateAssets(UserAssetParam param)
{
var lockKey = $"user:asset:update:{param.UserId}";
var result = await _distributedLock.TryExecuteAsync(lockKey, async () =>
{
var user = await GetUser(param.UserId);
if (user == null) throw new BusinessException("用户不存在");
var gameconfig = _cache.GetGameConfig();
if (param.ChangeType == UserAssetChangeTypeEnum.增加)
{
if (param.Diamond > 0)
{
var model = new UserReward() { UserId = param.UserId, BeforeCount = user.Assets.Diamond, Source = param.Source, Remark = param.Remark, ChangeCount = param.Diamond, Type = 1, CurrencyType = 3, RoomName = param.RoomName, RoomInfo = param.RoomInfo, Tax = param.Tax, OtherId = param.OtherId, OtherUserId = param.OtherUserId };
user.Assets.Diamond += param.Diamond;
model.AfterCount = user.Assets.Diamond;
if(param.IsAddRecord)
await _rewardMongo.InsertOneAsync(model);
}
if (param.Gold > 0)
{
user.Assets.Gold += param.Gold;
if (user.Assets.Gold < 0)
user.Assets.Gold = 0;
var model = new UserReward() { UserId = param.UserId, BeforeCount = user.Assets.Gold - param.Gold, Source = param.Source, Remark = param.Remark, ChangeCount = param.Gold, Type = 1, CurrencyType = 1, RoomName = param.RoomName, RoomInfo = param.RoomInfo, Tax = param.Tax, OtherId = param.OtherId, OtherUserId = param.OtherUserId };
model.AfterCount = user.Assets.Gold;
if (param.IsAddRecord)
await _rewardMongo.InsertOneAsync(model);
}
if (param.Exp > 0)
{
var res = user.LevelVerify(param.Exp, gameconfig.UserLevelConfigs);
if (res.isLevel)
{
var rewardItems = res.list?
.Where(item => item != null && item.Count > 0)
.ToList();
var hasReward = res.gold > 0
|| res.diamond > 0
|| (rewardItems != null && rewardItems.Count > 0);
if (hasReward)
{
var email = new UserEmail
{
UserId = param.UserId,
Title = "升级奖励",
Content = $"恭喜您升到{user.Assets.Level}级!升级奖励已发送,请点击领取。",
SendTime = DateTime.Now.ToTimeStamp(),
RewardGold = res.gold,
RewardDiamond = res.diamond,
RewardItems = rewardItems,
IsReward = false,
ExpireTime = DateTime.Now.AddDays(30).ToTimeStamp(),
CreateInfo = new OptionUserInfo("system", "系统")
};
await _emailMongo.InsertOneAsync(email);
}
}
}
if (param.RewardItems != null && param.RewardItems.Count > 0)
{
foreach (var item in param.RewardItems)
{
var propConfig = gameconfig.PropConfig.FirstOrDefault(c => c.Number == item.Number);
if (propConfig == null)
return (false, $"道具配置不存在:{item.Number}");
if (!ProductType.MoreProductTypeList.Any(c => c == propConfig.Type))
{
if (propConfig.Type == ProductTypeEnum.炮台)
{
var prop = user.CannonList.Find(c => c.Number == item.Number);
if (prop != null)
continue;
var cannon = _mapper.Map<BaseUserProp>(propConfig);
if (propConfig.Day > 0)
{
cannon.IsTrial = true;
cannon.TrialEndTime = DateTime.Now.AddDays(propConfig.Day).ToTimeStamp();
}
user.CannonList.Add(cannon);
}
if (propConfig.Type == ProductTypeEnum.翅膀)
{
var prop = user.WingList.Find(c => c.Number == item.Number);
if (prop != null)
continue;
var cannon = _mapper.Map<BaseUserProp>(propConfig);
if (propConfig.Day > 0)
{
cannon.IsTrial = true;
cannon.TrialEndTime = DateTime.Now.AddDays(propConfig.Day).ToTimeStamp();
}
user.WingList.Add(cannon);
}
if (propConfig.Type == ProductTypeEnum.战神)
{
var prop = user.GodWarList.Find(c => c.Number == item.Number);
if (prop != null)
continue;
var cannon = _mapper.Map<BaseUserProp>(propConfig);
if (propConfig.Day > 0)
{
cannon.IsTrial = true;
cannon.TrialEndTime = DateTime.Now.AddDays(propConfig.Day).ToTimeStamp();
}
user.GodWarList.Add(cannon);
}
}
else
{
var prop = user.Props.Find(c => c.Number == item.Number);
if (prop != null)
{
prop.Count += item.Count;
if (prop.Count <= 0)
user.Props.Remove(prop);
}
else
{
if (item.Count > 0)
{
var newProp = _mapper.Map<BaseProductCount>(propConfig);
newProp.Count = item.Count;
user.Props.Add(newProp);
}
}
}
}
}
}
else
{
if (param.Diamond > 0 && user.Assets.Diamond < param.Diamond)
{
return (false, "钻石数量不足");
}
else if(param.Diamond > 0)
{
var beforeDiamond = user.Assets.Diamond;
user.Assets.Diamond -= param.Diamond;
user.Static.TotalConsumeDiamond += param.Diamond;
var model = new UserReward() { UserId = param.UserId, BeforeCount = beforeDiamond, AfterCount = user.Assets.Diamond, Source = param.Source, Remark = param.Remark, ChangeCount = param.Diamond, Type = 2, CurrencyType = 3, RoomName = param.RoomName, RoomInfo = param.RoomInfo, Tax = param.Tax, OtherId = param.OtherId, OtherUserId = param.OtherUserId };
if (param.IsAddRecord)
await _rewardMongo.InsertOneAsync(model);
}
if (param.Gold > 0 && user.Assets.Gold < param.Gold)
{
return (false, "金币不足");
}
else if (param.Gold > 0)
{
user.Assets.Gold -= param.Gold;
if (user.Assets.Gold < 0)
user.Assets.Gold = 0;
user.Static.TotalConsumeGold += param.Gold;
//{
var model = new UserReward() { UserId = param.UserId, BeforeCount = user.Assets.Gold + param.Gold, Source = param.Source, Remark = param.Remark, ChangeCount = param.Gold, Type = 2, CurrencyType = 1, RoomName = param.RoomName, RoomInfo = param.RoomInfo, Tax = param.Tax, OtherId = param.OtherId, OtherUserId = param.OtherUserId };
model.AfterCount = user.Assets.Gold;
if (param.IsAddRecord)
await _rewardMongo.InsertOneAsync(model);
//}
}
if (param.RewardItems != null && param.RewardItems.Count > 0)
{
foreach (var item in param.RewardItems)
{
var userProp = user.Props.FirstOrDefault(c => c.Number == item.Number);
if (userProp == null)
throw new BusinessException("没有该物品");
if (userProp.Count < item.Count)
return (false, "物品数量不足");
if (userProp.Count == item.Count)
user.Props.Remove(userProp);
else
userProp.Count -= item.Count;
}
}
}
if (param.TotalRechargeGoldDelta != 0)
{
user.Static.TotalRechargeGold += param.TotalRechargeGoldDelta;
if (user.Static.TotalRechargeGold < 0)
user.Static.TotalRechargeGold = 0;
}
var updateDefinition = Builders<UserEntity>.Update
.Set(p => p.Assets, user.Assets).Set(p => p.Props, user.Props).Set(p => p.Static, user.Static)
.Set(p => p.CannonList, user.CannonList).Set(p => p.WingList, user.WingList).Set(p => p.GodWarList, user.GodWarList);
await _userMongo.UpdateByIdAsync(user.Id, updateDefinition);
if (param.SyncToFrontend && param.Exp > 0)
{
try
{
await _capPublisher.PublishAsync("BackgroundOperation.NotifyUser", new BackgroundOperationNotifyMessage
{
UserId = param.UserId,
Action = "level_change",
NewLevel = user.Assets.Level,
NewExp = user.Assets.Exp,
NewGold = user.Assets.Gold,
NewDiamond = user.Assets.Diamond,
Source = param.Source.ToString(),
Remark = param.Remark
});
}
catch (Exception ex)
{
_logger.LogError(ex,
"经验已入库但实时通知失败 userId={UserId} exp={Exp} newLevel={Level} newExp={NewExp}",
param.UserId, param.Exp, user.Assets.Level, user.Assets.Exp);
}
}
if (param.SyncToFrontend)
{
try
{
if (param.Gold != 0)
{
await _capPublisher.PublishAsync(TopicKeys.用户资产同步前端, new UserAssetSyncParam
{
UserId = param.UserId,
CurrencyType = 1,
ChangeType = (int)param.ChangeType,
Amount = param.Gold,
NewGold = user.Assets.Gold,
NewDiamond = user.Assets.Diamond,
Source = param.Source.ToString(),
Remark = param.Remark
});
}
if (param.Diamond != 0)
{
await _capPublisher.PublishAsync(TopicKeys.用户资产同步前端, new UserAssetSyncParam
{
UserId = param.UserId,
CurrencyType = 3,
ChangeType = (int)param.ChangeType,
Amount = param.Diamond,
NewGold = user.Assets.Gold,
NewDiamond = user.Assets.Diamond,
Source = param.Source.ToString(),
Remark = param.Remark
});
}
if (param.RewardItems != null && param.RewardItems.Count > 0)
{
foreach (var item in param.RewardItems.Where(i => i != null && !string.IsNullOrWhiteSpace(i.Number)))
{
await _hubContext.Clients.Group(param.UserId).SendAsync("BackpackUpdated", new
{
Action = param.ChangeType == UserAssetChangeTypeEnum.增加 ? "backpack_add" : "backpack_update",
PropNumber = item.Number,
Count = param.ChangeType == UserAssetChangeTypeEnum.增加 ? item.Count : -item.Count
});
}
}
}
catch (Exception ex)
{
_logger.LogError(ex, "资产更新成功但前端通知失败 userId={UserId} gold={Gold} diamond={Diamond}",
param.UserId, param.Gold, param.Diamond);
}
}
return (true, "更新成功");
}, expiry: 10, retry: false);
return result;
}
public async Task DailyRankingReward()
{
try
{
var activityConfig = await _activityConfigService.Get();
var cfg = activityConfig?.RankingRewardConfig;
if (cfg == null || !cfg.IsEnabled || cfg.Groups == null || cfg.Groups.Count == 0)
{
return;
}
foreach (var group in cfg.Groups)
{
if (group.Rewards == null || group.Rewards.Count == 0) continue;
var ranking = await GetRanking(new LoginUser(), group.RankingType);
if (ranking?.UserList == null || ranking.UserList.Count == 0) continue;
foreach (var reward in group.Rewards)
{
var rankUsers = ranking.UserList.Where(u => u.Rank >= reward.RankMin && u.Rank <= reward.RankMax).ToList();
foreach (var user in rankUsers)
{
var rewardItems = new List<BaseProductCount>();
if (reward.Items != null)
{
foreach (var item in reward.Items)
{
if (!string.IsNullOrEmpty(item.OtherId))
{
rewardItems.Add(new BaseProductCount
{
Number = item.OtherId,
Count = item.Count > 0 ? item.Count : 1
});
}
}
}
var dateStr = DateTime.Now.ToString("yyyy年M月d日");
var email = new UserEmail
{
UserId = user.UserId,
Title = "排行奖励",
Content = $"恭喜您在{dateStr} {group.Name}中排名荣获第{user.Rank}名,奖励已发放!请注意查收~",
SendTime = DateTime.Now.ToTimeStamp(),
RewardGold = reward.Gold,
RewardDiamond = reward.Diamond,
RewardItems = rewardItems.Count > 0 ? rewardItems : null,
IsReward = reward.Gold <= 0 && reward.Diamond <= 0 && (rewardItems == null || rewardItems.Count == 0),
ExpireTime = DateTime.Now.AddDays(30).ToTimeStamp(),
CreateInfo = new OptionUserInfo("system", "系统")
};
await _emailMongo.InsertOneAsync(email);
}
}
}
}
catch (Exception ex)
{
_logger.LogError(ex, "DailyRankingReward 执行异常");
}
}
private bool Random(double probability, int Max)
{
int randomValue = System.Random.Shared.Next(0, Max);
return randomValue < probability * Max;
}
public GiftResponse GetGift(List<GiftConfig> giftConfigs)
{
GiftResponse response = new GiftResponse();
int totalGold = 0;
int totalDiamond = 0;
var productDict = new Dictionary<string, int>();
if (giftConfigs != null)
{
foreach (var config in giftConfigs)
{
if (config == null) continue;
int actualCount = 0;
if (config.IsRandom)
{
int min = Math.Max(0, config.MinCount);
int max = Math.Max(min, config.MaxCount);
actualCount = System.Random.Shared.Next(min, max + 1);
}
else
{
actualCount = config.Count;
}
switch (config.Type)
{
case 1:
totalGold += actualCount;
break;
case 2:
totalDiamond += actualCount;
break;
case 3:
if (!string.IsNullOrEmpty(config.OtherId) && actualCount > 0)
{
if (productDict.ContainsKey(config.OtherId))
productDict[config.OtherId] += actualCount;
else
productDict[config.OtherId] = actualCount;
}
break;
default:
break;
}
}
}
var productList = new List<BaseProductCount>();
foreach (var kvp in productDict)
{
productList.Add(new BaseProductCount { Number = kvp.Key, Count = kvp.Value });
}
response = new GiftResponse() { Diamond = totalDiamond, Gold = totalGold, Product = productList };
return response;
}
private async Task AutoDistributeVipGift(UserEntity user, List<int> newVipLevels, GameConfig gameConfig)
{
foreach (var lvl in newVipLevels)
{
var vipConfig = gameConfig.VipLevelConfigs.Find(c => c.Level == lvl);
if (vipConfig?.GiftList == null || vipConfig.GiftList.Count == 0)
continue;
var result = GetGift(vipConfig.GiftList);
var rewardItems = result.Product?
.Where(item => item != null && item.Count > 0)
.ToList();
var hasReward = result.Gold > 0
|| result.Diamond > 0
|| (rewardItems != null && rewardItems.Count > 0);
if (hasReward)
{
var email = new UserEmail
{
UserId = user.Id,
Title = $"VIP{lvl}升级礼包",
Content = $"恭喜您升级到VIP{lvl}!升级礼包已发送,请点击领取。",
SendTime = DateTime.Now.ToTimeStamp(),
RewardGold = result.Gold,
RewardDiamond = result.Diamond,
RewardItems = rewardItems,
IsReward = false,
ExpireTime = DateTime.Now.AddDays(30).ToTimeStamp(),
CreateInfo = new OptionUserInfo("system", "系统")
};
await _emailMongo.InsertOneAsync(email);
}
user.Assets.VipGift.Remove(lvl);
}
}
private async Task<UserToken> LoginToken(UserEntity user)
{
var gameConfig = _cache.GetGameConfig();
var oldVipExp = user.Assets.VipExp;
var oldVipLevel = user.Assets.VipLevel;
var oldVipSignInAdd = user.Assets.VipSignInAdd;
var newVipLevels = user.VipLevelVerify(gameConfig);
if (newVipLevels != null && newVipLevels.Count > 0)
{
await AutoDistributeVipGift(user, newVipLevels, gameConfig);
}
if (oldVipExp != user.Assets.VipExp ||
oldVipLevel != user.Assets.VipLevel ||
oldVipSignInAdd != user.Assets.VipSignInAdd ||
(newVipLevels != null && newVipLevels.Count > 0))
{
await _userMongo.ReplaceOneAsync(user);
}
var model = _mapper.Map<UserToken>(user);
var claims = new Claim[]
{
new Claim("uid", user.Id),
new Claim("uname", user.BaseInfo.UserName??""),
new Claim("utype", "2"),
new Claim("avatar", user.BaseInfo.AvatarUrl??""),
new Claim("number",user.BaseInfo.Number)
};
model.Token = _jwtTools.CreateToken(claims);
//var userList = _userMongo.FilterBy(c => c.Static.LastLoginIp == user.Static.LastLoginIp);
//if (userList.Count() >= gameConfig.KillConfig.IPCount)
//{
// var updateDefinition = Builders<UserEntity>.Update.Set(p => p.Assets.ViolateRate,gameConfig.KillConfig.ViolateValue);
// _userMongo.UpdateMany(c => c.Static.LastLoginIp == user.Static.LastLoginIp, updateDefinition);
//}
if ((await _activityConfigService.Get())?.BattleOrderConfig.IsOpen == true)
{
_capPublisher.PublishAsync(TopicKeys.任务完成, new CompletedTaskParam() { taskEvent = TaskEvent.登录任务, userId = user.Id });
}
return model;
}
public async Task RecordFishingConsume(long consumeGold, LoginUser loginUser)
{
var activityConfig = await _activityConfigService.Get();
if (activityConfig?.FishingReturnConfig == null || !activityConfig.FishingReturnConfig.IsEnabled)
{
return;
}
var today = DateTime.Now.ToString("yyyyMMdd");
var key = $"fishing:consume:{loginUser.UserId}:{today}";
_cache.IncrBy(key, consumeGold);
}
private async Task FlushFishingConsumeToDb(string userId)
{
try
{
var today = DateTime.Now.ToString("yyyyMMdd");
var yesterday = DateTime.Now.AddDays(-1).ToString("yyyyMMdd");
var redisTodayKey = $"fishing:consume:{userId}:{today}";
var redisYesterdayKey = $"fishing:consume:{userId}:{yesterday}";
var redisTodayVal = _cache.GetString(redisTodayKey);
var redisYesterdayVal = _cache.GetString(redisYesterdayKey);
long.TryParse(redisTodayVal, out var redisToday);
long.TryParse(redisYesterdayVal, out var redisYesterday);
if (redisToday <= 0 && redisYesterday <= 0) return;
var user = await _userMongo.FindByIdAsync(userId);
if (user == null) return;
if (user.FishingReturn.LastFishingDate != today)
{
user.FishingReturn.YesterdayConsume = user.FishingReturn.TodayConsume + redisYesterday;
user.FishingReturn.TodayConsume = 0;
user.FishingReturn.IsClaimed = false;
user.FishingReturn.LastFishingDate = today;
}
user.FishingReturn.TodayConsume += redisToday;
var update = Builders<UserEntity>.Update
.Set(p => p.FishingReturn, user.FishingReturn);
await _userMongo.UpdateByIdAsync(user.Id, update);
if (redisToday > 0) _cache.IncrBy(redisTodayKey, -redisToday);
if (redisYesterday > 0) _cache.IncrBy(redisYesterdayKey, -redisYesterday);
}
catch (Exception ex)
{
_logger.LogError(ex, "刷新捕鱼返利消费数据失败 userId={UserId}", userId);
}
}
public async Task<FishingReturnInfo> GetFishingReturnInfo(LoginUser loginUser)
{
var activityConfig = await _activityConfigService.Get();
var result = new FishingReturnInfo();
if (activityConfig?.FishingReturnConfig == null || !activityConfig.FishingReturnConfig.IsEnabled)
{
return result;
}
var user = await _userMongo.FindByIdAsync(loginUser.UserId);
if (user == null) return result;
var today = DateTime.Now.ToString("yyyyMMdd");
var yesterday = DateTime.Now.AddDays(-1).ToString("yyyyMMdd");
var redisTodayKey = $"fishing:consume:{loginUser.UserId}:{today}";
var redisYesterdayKey = $"fishing:consume:{loginUser.UserId}:{yesterday}";
long.TryParse(_cache.GetString(redisTodayKey), out var redisToday);
long.TryParse(_cache.GetString(redisYesterdayKey), out var redisYesterday);
if (user.FishingReturn.LastFishingDate != today)
{
var totalYesterday = user.FishingReturn.TodayConsume + redisYesterday;
user.FishingReturn.YesterdayConsume = totalYesterday;
user.FishingReturn.TodayConsume = 0;
user.FishingReturn.IsClaimed = false;
user.FishingReturn.LastFishingDate = today;
var update = Builders<UserEntity>.Update
.Set(p => p.FishingReturn, user.FishingReturn);
await _userMongo.UpdateByIdAsync(user.Id, update);
}
return new FishingReturnInfo
{
TodayConsume = user.FishingReturn.TodayConsume + redisToday,
YesterdayConsume = user.FishingReturn.YesterdayConsume,
IsClaimed = user.FishingReturn.IsClaimed,
LastFishingDate = user.FishingReturn.LastFishingDate,
CanClaimGold = (long)(user.FishingReturn.YesterdayConsume * activityConfig.FishingReturnConfig.ReturnRate)
};
}
public async Task<(bool isok, string msg, long gold)> ClaimFishingReturn(LoginUser loginUser)
{
var activityConfig = await _activityConfigService.Get();
if (activityConfig?.FishingReturnConfig == null || !activityConfig.FishingReturnConfig.IsEnabled)
{
return (false, "活动未开启", 0);
}
await FlushFishingConsumeToDb(loginUser.UserId);
var user = await _userMongo.FindByIdAsync(loginUser.UserId);
if (user == null)
{
return (false, "用户不存在", 0);
}
var today = DateTime.Now.ToString("yyyyMMdd");
if (user.FishingReturn.LastFishingDate != today)
{
user.FishingReturn.YesterdayConsume = user.FishingReturn.TodayConsume;
user.FishingReturn.TodayConsume = 0;
user.FishingReturn.IsClaimed = false;
user.FishingReturn.LastFishingDate = today;
}
if (user.FishingReturn.IsClaimed)
{
return (false, "今日已领取过返利", 0);
}
if (user.FishingReturn.YesterdayConsume <= 0)
{
return (false, "昨日无捕鱼消耗,无法领取返利", 0);
}
long returnGold = (long)(user.FishingReturn.YesterdayConsume * activityConfig.FishingReturnConfig.ReturnRate);
if (activityConfig.FishingReturnConfig.DailyMaxReturn > 0 && returnGold > activityConfig.FishingReturnConfig.DailyMaxReturn)
{
returnGold = activityConfig.FishingReturnConfig.DailyMaxReturn;
}
if (returnGold <= 0)
{
return (false, "返利金额为0", 0);
}
user.Assets.Gold += returnGold;
user.FishingReturn.IsClaimed = true;
await _userMongo.ReplaceOneAsync(user);
return (true, $"领取成功,获得 {returnGold} 金币", returnGold);
}
}
}