2658 lines
113 KiB
C#
2658 lines
113 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, long gold)> Sign(LoginUser loginUser)
|
||
|
|
{
|
||
|
|
var user = await GetUser(loginUser.UserId);
|
||
|
|
|
||
|
|
// if (user.Assets.VipLevel == 0)
|
||
|
|
|
||
|
|
// if (user.Sign.IsSign)
|
||
|
|
var activityConfig = await _activityConfigService.Get();
|
||
|
|
|
||
|
|
var signList = activityConfig.SignConfigs;
|
||
|
|
if (signList.Count == 0)
|
||
|
|
throw new BusinessException("签到配置不存在");
|
||
|
|
|
||
|
|
var sign = GetRandomSignConfig(activityConfig);
|
||
|
|
|
||
|
|
var gold = Convert.ToInt64(sign.Gold * (1 + user.Assets.VipSignInAdd));
|
||
|
|
|
||
|
|
user.Sign.IsSign = true;
|
||
|
|
user.Sign.SignTime = DateTime.Now.ToTimeStamp();
|
||
|
|
|
||
|
|
|
||
|
|
UserReward userReward = new UserReward() { UserId = user.Id, ChangeCount = gold, CurrencyType = 1, Remark = "签到获取金币", Source = RewardSource.签到, Type = 1, BeforeCount = user.Assets.Gold };
|
||
|
|
user.Assets.Gold += gold;
|
||
|
|
userReward.AfterCount = user.Assets.Gold;
|
||
|
|
await _rewardMongo.InsertOneAsync(userReward);
|
||
|
|
|
||
|
|
await _userMongo.ReplaceOneAsync(user);
|
||
|
|
|
||
|
|
return (true, "签到成功", gold);
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
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 SignConfig GetRandomSignConfig(ActivityConfig activityConfig)
|
||
|
|
{
|
||
|
|
|
||
|
|
if (activityConfig?.SignConfigs == null || !activityConfig.SignConfigs.Any())
|
||
|
|
{
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
var configs = activityConfig.SignConfigs;
|
||
|
|
var totalProbability = configs.Sum(c => Math.Max(0, c.Probability));
|
||
|
|
|
||
|
|
if (totalProbability <= 0)
|
||
|
|
{
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
var randomValue = System.Random.Shared.NextDouble() * totalProbability;
|
||
|
|
|
||
|
|
double cumulativeProbability = 0;
|
||
|
|
foreach (var config in configs)
|
||
|
|
{
|
||
|
|
var currentProbability = Math.Max(0, config.Probability);
|
||
|
|
cumulativeProbability += currentProbability;
|
||
|
|
|
||
|
|
if (randomValue <= cumulativeProbability)
|
||
|
|
{
|
||
|
|
return config;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return configs.LastOrDefault();
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
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);
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
}
|
||
|
|
}
|