1464 lines
61 KiB
C#
1464 lines
61 KiB
C#
using AdminService.User.Param;
|
|||
|
|
using AdminService.User.Response;
|
||
|
|
using AdminService.GameConfigs;
|
||
|
|
using Common;
|
||
|
|
using Common.Utils;
|
||
|
|
using CommonDomain;
|
||
|
|
using CommonDomain.BaseModel;
|
||
|
|
using CommonInfrastructure.Response;
|
||
|
|
using DotNetCore.CAP;
|
||
|
|
using Microsoft.Extensions.Logging;
|
||
|
|
using MongoRepository;
|
||
|
|
using RedisHelper;
|
||
|
|
using RedisService;
|
||
|
|
using UserDomain.Entity;
|
||
|
|
using System.Linq.Expressions;
|
||
|
|
using MongoDB.Driver;
|
||
|
|
|
||
|
|
namespace AdminService.User
|
||
|
|
{
|
||
|
|
public class AdminUserService : IAdminUserService
|
||
|
|
{
|
||
|
|
|
||
|
|
private readonly IMongoBaseRepository<UserEntity> _userMongo;
|
||
|
|
private readonly IMongoBaseRepository<UserEmail> _emailMongo;
|
||
|
|
private readonly IMongoBaseRepository<UserReward> _rewardMongo;
|
||
|
|
|
||
|
|
private readonly ILogger<AdminUserService> _logger;
|
||
|
|
private readonly IRedisService _cache;
|
||
|
|
private readonly IGameConfigService _gameConfigService;
|
||
|
|
private readonly ICapPublisher _capPublisher;
|
||
|
|
private readonly IDistributedLock _distributedLock;
|
||
|
|
|
||
|
|
public AdminUserService(
|
||
|
|
IMongoBaseRepository<UserEntity> userMongo,
|
||
|
|
IMongoBaseRepository<UserEmail> emailMongo,
|
||
|
|
IMongoBaseRepository<UserReward> rewardMongo,
|
||
|
|
ILogger<AdminUserService> logger,
|
||
|
|
IRedisService cache,
|
||
|
|
IGameConfigService gameConfigService,
|
||
|
|
ICapPublisher capPublisher,
|
||
|
|
IDistributedLock distributedLock)
|
||
|
|
{
|
||
|
|
_userMongo = userMongo;
|
||
|
|
_emailMongo = emailMongo;
|
||
|
|
_rewardMongo = rewardMongo;
|
||
|
|
_logger = logger;
|
||
|
|
_cache = cache;
|
||
|
|
_gameConfigService = gameConfigService;
|
||
|
|
_capPublisher = capPublisher;
|
||
|
|
_distributedLock = distributedLock;
|
||
|
|
}
|
||
|
|
|
||
|
|
public async Task<(bool isok, string msg)> CancelUser(LoginUser loginUser)
|
||
|
|
{
|
||
|
|
var user = await GetUser(loginUser.UserId);
|
||
|
|
if (user == null) throw new BusinessException("用户不存在");
|
||
|
|
user.IsDelete = true;
|
||
|
|
|
||
|
|
await _userMongo.ReplaceOneAsync(user);
|
||
|
|
return (true, "注销成功");
|
||
|
|
}
|
||
|
|
|
||
|
|
public async Task<(bool isok, string msg)> BanUser(string userId, string reason, int days)
|
||
|
|
{
|
||
|
|
var user = await _userMongo.FindByIdAsync(userId);
|
||
|
|
if (user == null) throw new BusinessException("用户不存在");
|
||
|
|
user.BanInfo = new BanInfo();
|
||
|
|
user.BanInfo.IsBan = true;
|
||
|
|
user.BanInfo.BanReason = reason;
|
||
|
|
user.BanInfo.BanDays = days;
|
||
|
|
user.BanInfo.BanTime = DateTime.Now.ToTimeStamp();
|
||
|
|
user.BanInfo.BanEndTime = days > 0 ? DateTime.Now.AddDays(days).ToTimeStamp() : 0;
|
||
|
|
|
||
|
|
await _userMongo.ReplaceOneAsync(user);
|
||
|
|
return (true, "封号成功");
|
||
|
|
}
|
||
|
|
public async Task<(bool isok, string msg)> DeleteUser(string id)
|
||
|
|
{
|
||
|
|
return await _userMongo.DeleteByIdAsync(id); ;
|
||
|
|
}
|
||
|
|
public async Task<(bool isok, string msg)> BatchDeleteUser(List<string> userIds)
|
||
|
|
{
|
||
|
|
if (userIds == null || userIds.Count == 0)
|
||
|
|
return (false, "请选择用户");
|
||
|
|
var count = 0;
|
||
|
|
foreach (var id in userIds)
|
||
|
|
{
|
||
|
|
var result = await _userMongo.DeleteByIdAsync(id);
|
||
|
|
if (result.ok) count++;
|
||
|
|
}
|
||
|
|
return (true, $"已删除 {count} 个用户");
|
||
|
|
}
|
||
|
|
|
||
|
|
public async Task<(bool isok, string msg)> UpdateUser(UserEntity user)
|
||
|
|
{
|
||
|
|
return await _userMongo.ReplaceOneAsync(user);
|
||
|
|
}
|
||
|
|
public async Task<(bool isok, string msg)> UpdateUser(UpdateUserDataParam param)
|
||
|
|
{
|
||
|
|
var user = await GetUser(param.Id);
|
||
|
|
if (user == null)
|
||
|
|
return (false, "用户不存在");
|
||
|
|
if (user.Assets == null)
|
||
|
|
user.Assets = new Assets();
|
||
|
|
var beforeGold = user.Assets.Gold;
|
||
|
|
var beforeDiamond = user.Assets.Diamond;
|
||
|
|
var beforeLevel = user.Assets.Level;
|
||
|
|
user.Assets.Diamond = param.Diamond;
|
||
|
|
user.Assets.Gold = param.Gold;
|
||
|
|
user.Assets.Level = param.Level;
|
||
|
|
user.Assets.Exp = param.Exp;
|
||
|
|
|
||
|
|
var result = await _userMongo.ReplaceOneAsync(user);
|
||
|
|
if (result.ok)
|
||
|
|
{
|
||
|
|
PublishAssetChanges(user.Id, user.BaseInfo?.Number, beforeGold, user.Assets.Gold, beforeDiamond, user.Assets.Diamond, "后台修改", string.Empty);
|
||
|
|
if (beforeLevel != user.Assets.Level)
|
||
|
|
PublishLevelChange(user.Id, user.Assets.Level, user.Assets.Exp, user.Assets.Gold, user.Assets.Diamond, "后台修改等级");
|
||
|
|
}
|
||
|
|
return result;
|
||
|
|
}
|
||
|
|
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 = 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);
|
||
|
|
|
||
|
|
foreach (var email in emails)
|
||
|
|
{
|
||
|
|
_capPublisher.Publish("BackgroundOperation.NotifyUser", new BackgroundOperationNotifyMessage
|
||
|
|
{
|
||
|
|
UserId = email.UserId,
|
||
|
|
Action = "new_email",
|
||
|
|
EmailId = email.Id,
|
||
|
|
Title = email.Title
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
return (true, "发送成功");
|
||
|
|
}
|
||
|
|
|
||
|
|
public async Task<(List<UserEntity> list, int count)> GetAdminUserList(AdminUserListParam param)
|
||
|
|
{
|
||
|
|
var where = LinqExtensions.True<UserEntity>().And(e => e.IsDelete == false);
|
||
|
|
|
||
|
|
if (!string.IsNullOrWhiteSpace(param.KeyWord))
|
||
|
|
{
|
||
|
|
where = where.And(e => e.BaseInfo.Number.Contains(param.KeyWord)
|
||
|
|
|| e.BaseInfo.UserName.Contains(param.KeyWord)
|
||
|
|
|| e.BaseInfo.Mobile.Contains(param.KeyWord));
|
||
|
|
}
|
||
|
|
|
||
|
|
if (param.IsBan.HasValue)
|
||
|
|
{
|
||
|
|
if (param.IsBan.Value == 1)
|
||
|
|
where = where.And(e => e.BanInfo != null && e.BanInfo.IsBan == true);
|
||
|
|
else
|
||
|
|
where = where.And(e => e.BanInfo == null || e.BanInfo.IsBan == false);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (param.VipLevel.HasValue)
|
||
|
|
{
|
||
|
|
where = where.And(e => e.Assets.VipLevel == param.VipLevel.Value);
|
||
|
|
}
|
||
|
|
if (param.VipLevelMin.HasValue)
|
||
|
|
{
|
||
|
|
where = where.And(e => e.Assets.VipLevel >= param.VipLevelMin.Value);
|
||
|
|
}
|
||
|
|
if (param.VipLevelMax.HasValue)
|
||
|
|
{
|
||
|
|
where = where.And(e => e.Assets.VipLevel <= param.VipLevelMax.Value);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (param.CreateTimeStart.HasValue)
|
||
|
|
{
|
||
|
|
where = where.And(e => e.CreateTime >= param.CreateTimeStart.Value);
|
||
|
|
}
|
||
|
|
if (param.CreateTimeEnd.HasValue)
|
||
|
|
{
|
||
|
|
where = where.And(e => e.CreateTime <= param.CreateTimeEnd.Value);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (param.LastLoginTimeStart.HasValue)
|
||
|
|
{
|
||
|
|
where = where.And(e => e.Static.LastLoginTime >= param.LastLoginTimeStart.Value);
|
||
|
|
}
|
||
|
|
if (param.LastLoginTimeEnd.HasValue)
|
||
|
|
{
|
||
|
|
where = where.And(e => e.Static.LastLoginTime <= param.LastLoginTimeEnd.Value);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!string.IsNullOrWhiteSpace(param.Channel))
|
||
|
|
{
|
||
|
|
where = where.And(e => e.BaseInfo.Channel == param.Channel);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!string.IsNullOrWhiteSpace(param.LoginIp))
|
||
|
|
{
|
||
|
|
where = where.And(e => e.Static.LastLoginIp.Contains(param.LoginIp));
|
||
|
|
}
|
||
|
|
|
||
|
|
if (param.UserType.HasValue)
|
||
|
|
{
|
||
|
|
where = where.And(e => e.UserType == param.UserType.Value);
|
||
|
|
}
|
||
|
|
|
||
|
|
HashSet<string>? onlineLoginIds = null;
|
||
|
|
if (param.IsOnline.HasValue)
|
||
|
|
{
|
||
|
|
onlineLoginIds = _cache.GetAllLoginUserIds().ToHashSet(StringComparer.Ordinal);
|
||
|
|
}
|
||
|
|
|
||
|
|
var orderBy = param.OrderBy?.ToLower() ?? "createtime";
|
||
|
|
var orderDesc = true;
|
||
|
|
|
||
|
|
if (orderBy == "totalgold")
|
||
|
|
{
|
||
|
|
var allUsers = await _userMongo.FilterByAsync(where);
|
||
|
|
if (onlineLoginIds != null)
|
||
|
|
allUsers = allUsers.Where(u => onlineLoginIds.Contains(u.Id)).ToList();
|
||
|
|
var totalGoldUsers = allUsers.Select(u => new
|
||
|
|
{
|
||
|
|
User = u,
|
||
|
|
TotalGold = (u.Assets?.Gold ?? 0) + (u.SafeBox?.Amount ?? 0)
|
||
|
|
}).OrderByDescending(x => x.TotalGold).ToList();
|
||
|
|
|
||
|
|
var totalCount = totalGoldUsers.Count;
|
||
|
|
var pagedUsers = totalGoldUsers
|
||
|
|
.Skip(((param.PageIndex ?? 1) - 1) * (param.PageSize ?? 10))
|
||
|
|
.Take(param.PageSize ?? 10)
|
||
|
|
.Select(x => x.User)
|
||
|
|
.ToList();
|
||
|
|
|
||
|
|
return (pagedUsers, totalCount);
|
||
|
|
}
|
||
|
|
|
||
|
|
Expression<Func<UserEntity, object>> orderSelector = c => c.CreateTime;
|
||
|
|
switch (orderBy)
|
||
|
|
{
|
||
|
|
case "lastlogintime":
|
||
|
|
orderSelector = c => c.Static.LastLoginTime;
|
||
|
|
break;
|
||
|
|
case "diamond":
|
||
|
|
orderSelector = c => c.Assets.Diamond;
|
||
|
|
break;
|
||
|
|
case "gold":
|
||
|
|
orderSelector = c => c.Assets.Gold;
|
||
|
|
break;
|
||
|
|
case "createtime":
|
||
|
|
default:
|
||
|
|
orderSelector = c => c.CreateTime;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (onlineLoginIds != null)
|
||
|
|
{
|
||
|
|
var allUsers = await _userMongo.FilterByAsync(where);
|
||
|
|
var filtered = allUsers.Where(u => onlineLoginIds.Contains(u.Id)).ToList();
|
||
|
|
var ranked = orderBy switch
|
||
|
|
{
|
||
|
|
"lastlogintime" => filtered.OrderByDescending(u => u.Static.LastLoginTime),
|
||
|
|
"diamond" => filtered.OrderByDescending(u => u.Assets.Diamond),
|
||
|
|
"gold" => filtered.OrderByDescending(u => u.Assets.Gold),
|
||
|
|
_ => filtered.OrderByDescending(u => u.CreateTime)
|
||
|
|
};
|
||
|
|
var list = ranked
|
||
|
|
.Skip(((param.PageIndex ?? 1) - 1) * (param.PageSize ?? 10))
|
||
|
|
.Take(param.PageSize ?? 10)
|
||
|
|
.ToList();
|
||
|
|
return (list, list.Count == 0 ? 0 : list.Count);
|
||
|
|
}
|
||
|
|
|
||
|
|
var result = await _userMongo.QueryPageAsync(where, orderSelector, orderDesc, param.PageSize ?? 10, param.PageIndex ?? 1);
|
||
|
|
return (result.list, (int)result.count);
|
||
|
|
}
|
||
|
|
|
||
|
|
public async Task<(bool isok, string msg)> UpdateUserAssets(UpdateUserAssetsParam param)
|
||
|
|
{
|
||
|
|
var lockKey = $"user:asset:update:{param.UserId}";
|
||
|
|
var result = await _distributedLock.TryExecuteAsync(lockKey, async () =>
|
||
|
|
{
|
||
|
|
var user = await GetUser(param.UserId);
|
||
|
|
if (user == null)
|
||
|
|
return (false, "用户不存在");
|
||
|
|
|
||
|
|
var beforeGold = user.Assets.Gold;
|
||
|
|
var beforeDiamond = user.Assets.Diamond;
|
||
|
|
var beforeLevel = user.Assets.Level;
|
||
|
|
|
||
|
|
switch (param.OperationType)
|
||
|
|
{
|
||
|
|
case 1:
|
||
|
|
if (param.Diamond != 0)
|
||
|
|
{
|
||
|
|
var diamondReduce = param.Diamond < 0;
|
||
|
|
user.Assets.Diamond += param.Diamond;
|
||
|
|
await _rewardMongo.InsertOneAsync(new UserReward
|
||
|
|
{
|
||
|
|
UserId = user.Id,
|
||
|
|
BeforeCount = user.Assets.Diamond - param.Diamond,
|
||
|
|
AfterCount = user.Assets.Diamond,
|
||
|
|
ChangeCount = diamondReduce ? -param.Diamond : param.Diamond,
|
||
|
|
CurrencyType = 3,
|
||
|
|
Type = diamondReduce ? 2 : 1,
|
||
|
|
Remark = $"管理员{(diamondReduce ? "减少" : "增加")}钻石 - {param.Remark}"
|
||
|
|
});
|
||
|
|
}
|
||
|
|
if (param.Gold != 0)
|
||
|
|
{
|
||
|
|
var goldReduce = param.Gold < 0;
|
||
|
|
user.Assets.Gold += param.Gold;
|
||
|
|
await _rewardMongo.InsertOneAsync(new UserReward
|
||
|
|
{
|
||
|
|
UserId = user.Id,
|
||
|
|
BeforeCount = user.Assets.Gold - param.Gold,
|
||
|
|
AfterCount = user.Assets.Gold,
|
||
|
|
ChangeCount = goldReduce ? -param.Gold : param.Gold,
|
||
|
|
CurrencyType = 1,
|
||
|
|
Type = goldReduce ? 2 : 1,
|
||
|
|
Remark = $"管理员{(goldReduce ? "减少" : "增加")}金币 - {param.Remark}"
|
||
|
|
});
|
||
|
|
}
|
||
|
|
if (param.SafeBoxGold != 0)
|
||
|
|
{
|
||
|
|
user.SafeBox ??= new SafeBoxInfo();
|
||
|
|
var safeBoxReduce = param.SafeBoxGold < 0;
|
||
|
|
user.SafeBox.Amount += param.SafeBoxGold;
|
||
|
|
if (user.SafeBox.Amount < 0)
|
||
|
|
return (false, "保险箱金币数量不足");
|
||
|
|
await _rewardMongo.InsertOneAsync(new UserReward
|
||
|
|
{
|
||
|
|
UserId = user.Id,
|
||
|
|
BeforeCount = user.SafeBox.Amount - param.SafeBoxGold,
|
||
|
|
AfterCount = user.SafeBox.Amount,
|
||
|
|
ChangeCount = safeBoxReduce ? -param.SafeBoxGold : param.SafeBoxGold,
|
||
|
|
CurrencyType = 2,
|
||
|
|
Type = safeBoxReduce ? 2 : 1,
|
||
|
|
Remark = $"管理员{(safeBoxReduce ? "减少" : "增加")}保险箱金币 - {param.Remark}"
|
||
|
|
});
|
||
|
|
}
|
||
|
|
if (param.Exp.HasValue)
|
||
|
|
{
|
||
|
|
user.Assets.Exp += param.Exp.Value;
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
|
||
|
|
case 2:
|
||
|
|
if (param.Diamond != 0)
|
||
|
|
{
|
||
|
|
if (user.Assets.Diamond < param.Diamond)
|
||
|
|
return (false, "钻石数量不足");
|
||
|
|
user.Assets.Diamond -= param.Diamond;
|
||
|
|
await _rewardMongo.InsertOneAsync(new UserReward
|
||
|
|
{
|
||
|
|
UserId = user.Id,
|
||
|
|
BeforeCount = user.Assets.Diamond + param.Diamond,
|
||
|
|
AfterCount = user.Assets.Diamond,
|
||
|
|
ChangeCount = param.Diamond,
|
||
|
|
CurrencyType = 3,
|
||
|
|
Type = 2,
|
||
|
|
Remark = $"管理员减少钻石 - {param.Remark}"
|
||
|
|
});
|
||
|
|
}
|
||
|
|
if (param.Gold != 0)
|
||
|
|
{
|
||
|
|
if (user.Assets.Gold < param.Gold)
|
||
|
|
return (false, "金币数量不足");
|
||
|
|
user.Assets.Gold -= param.Gold;
|
||
|
|
await _rewardMongo.InsertOneAsync(new UserReward
|
||
|
|
{
|
||
|
|
UserId = user.Id,
|
||
|
|
BeforeCount = user.Assets.Gold + param.Gold,
|
||
|
|
AfterCount = user.Assets.Gold,
|
||
|
|
ChangeCount = param.Gold,
|
||
|
|
CurrencyType = 1,
|
||
|
|
Type = 2,
|
||
|
|
Remark = $"管理员减少金币 - {param.Remark}"
|
||
|
|
});
|
||
|
|
}
|
||
|
|
if (param.Exp.HasValue && param.Exp.Value > 0)
|
||
|
|
{
|
||
|
|
if (user.Assets.Exp < param.Exp.Value)
|
||
|
|
return (false, "经验值不足");
|
||
|
|
user.Assets.Exp -= param.Exp.Value;
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
|
||
|
|
case 3:
|
||
|
|
if (param.Diamond != 0)
|
||
|
|
{
|
||
|
|
await _rewardMongo.InsertOneAsync(new UserReward
|
||
|
|
{
|
||
|
|
UserId = user.Id,
|
||
|
|
BeforeCount = user.Assets.Diamond,
|
||
|
|
AfterCount = param.Diamond,
|
||
|
|
ChangeCount = param.Diamond - user.Assets.Diamond,
|
||
|
|
CurrencyType = 3,
|
||
|
|
Type = param.Diamond >= user.Assets.Diamond ? 1 : 2,
|
||
|
|
Remark = $"管理员设置钻石 - {param.Remark}"
|
||
|
|
});
|
||
|
|
user.Assets.Diamond = param.Diamond;
|
||
|
|
}
|
||
|
|
if (param.Gold != 0)
|
||
|
|
{
|
||
|
|
await _rewardMongo.InsertOneAsync(new UserReward
|
||
|
|
{
|
||
|
|
UserId = user.Id,
|
||
|
|
BeforeCount = user.Assets.Gold,
|
||
|
|
AfterCount = param.Gold,
|
||
|
|
ChangeCount = param.Gold - user.Assets.Gold,
|
||
|
|
CurrencyType = 1,
|
||
|
|
Type = param.Gold >= user.Assets.Gold ? 1 : 2,
|
||
|
|
Remark = $"管理员设置金币 - {param.Remark}"
|
||
|
|
});
|
||
|
|
user.Assets.Gold = param.Gold;
|
||
|
|
}
|
||
|
|
if (param.SafeBoxGold != 0)
|
||
|
|
{
|
||
|
|
user.SafeBox ??= new SafeBoxInfo();
|
||
|
|
var beforeSafeBox = user.SafeBox.Amount;
|
||
|
|
if (param.SafeBoxGold < 0)
|
||
|
|
return (false, "保险箱金币数量不足");
|
||
|
|
await _rewardMongo.InsertOneAsync(new UserReward
|
||
|
|
{
|
||
|
|
UserId = user.Id,
|
||
|
|
BeforeCount = beforeSafeBox,
|
||
|
|
AfterCount = param.SafeBoxGold,
|
||
|
|
ChangeCount = Math.Abs(param.SafeBoxGold - beforeSafeBox),
|
||
|
|
CurrencyType = 2,
|
||
|
|
Type = param.SafeBoxGold >= beforeSafeBox ? 1 : 2,
|
||
|
|
Remark = $"管理员设置保险箱金币 - {param.Remark}"
|
||
|
|
});
|
||
|
|
user.SafeBox.Amount = param.SafeBoxGold;
|
||
|
|
}
|
||
|
|
if (param.Exp.HasValue)
|
||
|
|
{
|
||
|
|
user.Assets.Exp = param.Exp.Value;
|
||
|
|
}
|
||
|
|
if (param.Level.HasValue)
|
||
|
|
{
|
||
|
|
user.Assets.Level = param.Level.Value;
|
||
|
|
}
|
||
|
|
if (param.VipLevel.HasValue)
|
||
|
|
{
|
||
|
|
user.Assets.VipLevel = param.VipLevel.Value;
|
||
|
|
if (param.VipLevel.Value > 0)
|
||
|
|
{
|
||
|
|
var gameConfig = await _gameConfigService.Get();
|
||
|
|
var vipConfig = gameConfig?.VipLevelConfigs?
|
||
|
|
.FirstOrDefault(c => c.Level == param.VipLevel.Value);
|
||
|
|
if (vipConfig != null)
|
||
|
|
{
|
||
|
|
user.Assets.VipExpExtra += vipConfig.ExpMin;
|
||
|
|
user.Assets.VipExp = (long)(user.Static?.TotalRecharge ?? 0) * 10 + user.Assets.VipExpExtra;
|
||
|
|
user.Assets.VipSignInAdd = vipConfig.SignInAdd;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
var updateDefinition = Builders<UserEntity>.Update
|
||
|
|
.Set(p => p.Assets, user.Assets)
|
||
|
|
.Set(p => p.SafeBox, user.SafeBox);
|
||
|
|
var updateResult = await _userMongo.UpdateByIdAsync(user.Id, updateDefinition);
|
||
|
|
if (!updateResult.ok)
|
||
|
|
{
|
||
|
|
|
||
|
|
return (false, updateResult.message);
|
||
|
|
}
|
||
|
|
|
||
|
|
PublishAssetChanges(user.Id, user.BaseInfo?.Number, beforeGold, user.Assets.Gold, beforeDiamond, user.Assets.Diamond, "后台修改", param.Remark);
|
||
|
|
if (beforeLevel != user.Assets.Level)
|
||
|
|
PublishLevelChange(user.Id, user.Assets.Level, user.Assets.Exp, user.Assets.Gold, user.Assets.Diamond, "后台修改等级");
|
||
|
|
return (true, "修改成功");
|
||
|
|
}, expiry: 10, retry: false);
|
||
|
|
|
||
|
|
return result;
|
||
|
|
}
|
||
|
|
|
||
|
|
private void PublishAssetChanges(string userId, string userNumber, long beforeGold, long newGold, int beforeDiamond, int newDiamond, string source, string remark)
|
||
|
|
{
|
||
|
|
if (beforeGold != newGold)
|
||
|
|
PublishAssetChange(userId, userNumber, 1, beforeGold, newGold, newGold, newDiamond, source, remark);
|
||
|
|
|
||
|
|
if (beforeDiamond != newDiamond)
|
||
|
|
PublishAssetChange(userId, userNumber, 2, beforeDiamond, newDiamond, newGold, newDiamond, source, remark);
|
||
|
|
}
|
||
|
|
|
||
|
|
private void PublishAssetChange(string userId, string userNumber, int currencyType, long beforeValue, long newValue, long currentGold, int currentDiamond, string source, string remark)
|
||
|
|
{
|
||
|
|
var changeType = newValue >= beforeValue ? 1 : 2;
|
||
|
|
var amount = Math.Abs(newValue - beforeValue);
|
||
|
|
try
|
||
|
|
{
|
||
|
|
_capPublisher.Publish("BackgroundOperation.NotifyUser", new BackgroundOperationNotifyMessage
|
||
|
|
{
|
||
|
|
UserId = userId,
|
||
|
|
UserNumber = userNumber ?? string.Empty,
|
||
|
|
CurrencyType = currencyType,
|
||
|
|
ChangeType = changeType,
|
||
|
|
Amount = amount,
|
||
|
|
NewGold = currentGold,
|
||
|
|
NewDiamond = currentDiamond,
|
||
|
|
Source = source,
|
||
|
|
Remark = remark ?? string.Empty
|
||
|
|
});
|
||
|
|
}
|
||
|
|
catch (Exception ex)
|
||
|
|
{
|
||
|
|
_logger.LogError(ex, "后台资产变更通知发布失败 userId={UserId} currencyType={CurrencyType} newValue={NewValue}",
|
||
|
|
userId, currencyType, newValue);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private void PublishLevelChange(string userId, int newLevel, long newExp, long currentGold, int currentDiamond, string remark)
|
||
|
|
{
|
||
|
|
try
|
||
|
|
{
|
||
|
|
_capPublisher.Publish("BackgroundOperation.NotifyUser", new BackgroundOperationNotifyMessage
|
||
|
|
{
|
||
|
|
UserId = userId,
|
||
|
|
Action = "level_change",
|
||
|
|
NewLevel = newLevel,
|
||
|
|
NewExp = newExp,
|
||
|
|
NewGold = currentGold,
|
||
|
|
NewDiamond = currentDiamond,
|
||
|
|
Source = remark,
|
||
|
|
Remark = remark
|
||
|
|
});
|
||
|
|
}
|
||
|
|
catch (Exception ex)
|
||
|
|
{
|
||
|
|
_logger.LogError(ex, "后台等级变更通知发布失败 userId={UserId} newLevel={NewLevel}", userId, newLevel);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public async Task<UserStatistics> GetUserStatistics()
|
||
|
|
{
|
||
|
|
var allUsers = await _userMongo.FilterByAsync(u => u.IsDelete == false);
|
||
|
|
var now = DateTime.Now;
|
||
|
|
var todayStart = new DateTime(now.Year, now.Month, now.Day, 0, 0, 0).ToTimeStamp();
|
||
|
|
var monthStart = new DateTime(now.Year, now.Month, 1, 0, 0, 0).ToTimeStamp();
|
||
|
|
|
||
|
|
var statistics = new UserStatistics
|
||
|
|
{
|
||
|
|
TotalUsers = allUsers.Count(),
|
||
|
|
TodayRegisteredUsers = allUsers.Count(u => u.CreateTime >= todayStart),
|
||
|
|
MonthRegisteredUsers = allUsers.Count(u => u.CreateTime >= monthStart),
|
||
|
|
BannedUsers = allUsers.Count(u => u.BanInfo != null && u.BanInfo.IsBan == true),
|
||
|
|
VipUsers = allUsers.Count(u => u.Assets.VipLevel > 0),
|
||
|
|
TotalGold = allUsers.Sum(u => (long)u.Assets.Gold),
|
||
|
|
TotalDiamond = allUsers.Sum(u => (long)u.Assets.Diamond)
|
||
|
|
};
|
||
|
|
|
||
|
|
statistics.VipLevelCount = allUsers
|
||
|
|
.GroupBy(u => u.Assets.VipLevel)
|
||
|
|
.ToDictionary(g => g.Key, g => (long)g.Count());
|
||
|
|
|
||
|
|
statistics.OnlineUsers = _cache.GetAllLoginUserIds().Count();
|
||
|
|
|
||
|
|
return statistics;
|
||
|
|
}
|
||
|
|
|
||
|
|
public async Task<EmailStatistics> GetEmailStatistics()
|
||
|
|
{
|
||
|
|
var now = DateTime.Now;
|
||
|
|
var todayStart = new DateTime(now.Year, now.Month, now.Day, 0, 0, 0).ToTimeStamp();
|
||
|
|
var monthStart = new DateTime(now.Year, now.Month, 1, 0, 0, 0).ToTimeStamp();
|
||
|
|
|
||
|
|
var allEmails = await _emailMongo.FilterByAsync(e => true);
|
||
|
|
|
||
|
|
return new EmailStatistics
|
||
|
|
{
|
||
|
|
TotalEmails = allEmails.LongCount(),
|
||
|
|
TodayEmails = allEmails.LongCount(e => e.SendTime >= todayStart),
|
||
|
|
MonthEmails = allEmails.LongCount(e => e.SendTime >= monthStart),
|
||
|
|
RewardEmails = allEmails.LongCount(e => e.IsReward == false),
|
||
|
|
UnclaimedEmails = allEmails.LongCount(e => e.IsReward == true)
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
public async Task<(List<UserEmail> list, long count)> GetEmailList(EmailListQueryParam param)
|
||
|
|
{
|
||
|
|
var keyword = param.UserNumber?.Trim();
|
||
|
|
List<string> matchedUserIds = null;
|
||
|
|
if (!string.IsNullOrWhiteSpace(keyword))
|
||
|
|
{
|
||
|
|
var matchedUsers = await _userMongo.FilterByAsync(u =>
|
||
|
|
(u.BaseInfo.Number != null && u.BaseInfo.Number.Contains(keyword)) ||
|
||
|
|
(u.BaseInfo.UserName != null && u.BaseInfo.UserName.Contains(keyword)) ||
|
||
|
|
u.Id.Contains(keyword));
|
||
|
|
matchedUserIds = matchedUsers?.Select(u => u.Id).ToList() ?? new List<string>();
|
||
|
|
}
|
||
|
|
|
||
|
|
var result = await _emailMongo.QueryPageAsync(
|
||
|
|
filterExpression: e =>
|
||
|
|
(matchedUserIds == null || matchedUserIds.Contains(e.UserId) ||
|
||
|
|
(!string.IsNullOrWhiteSpace(keyword) && e.Title != null && e.Title.Contains(keyword)) ||
|
||
|
|
(!string.IsNullOrWhiteSpace(keyword) && e.Content != null && e.Content.Contains(keyword))) &&
|
||
|
|
(string.IsNullOrWhiteSpace(param.UserId) || e.UserId == param.UserId) &&
|
||
|
|
(!param.ReadStatus.HasValue || (param.ReadStatus == 1 ? e.IsRead : !e.IsRead)) &&
|
||
|
|
(!param.RewardStatus.HasValue || (param.RewardStatus == 1 ? e.IsReward : !e.IsReward)) &&
|
||
|
|
(!param.StartTime.HasValue || e.SendTime >= param.StartTime) &&
|
||
|
|
(!param.EndTime.HasValue || e.SendTime <= param.EndTime),
|
||
|
|
orderExpression: e => e.SendTime,
|
||
|
|
desc: true,
|
||
|
|
pageSize: param.PageSize ?? 10,
|
||
|
|
pageIndex: param.PageIndex ?? 1
|
||
|
|
);
|
||
|
|
return (result.list.ToList(), result.count);
|
||
|
|
}
|
||
|
|
|
||
|
|
public async Task<Dictionary<string, (string UserName, string UserNumber)>> GetEmailUserNamesAsync(List<string> userIds)
|
||
|
|
{
|
||
|
|
var result = new Dictionary<string, (string UserName, string UserNumber)>();
|
||
|
|
if (userIds == null || userIds.Count == 0)
|
||
|
|
return result;
|
||
|
|
var users = await _userMongo.FilterByAsync(u => userIds.Contains(u.Id));
|
||
|
|
foreach (var u in users)
|
||
|
|
{
|
||
|
|
if (u == null || string.IsNullOrWhiteSpace(u.Id))
|
||
|
|
continue;
|
||
|
|
result[u.Id] = (u.BaseInfo?.UserName ?? "", u.BaseInfo?.Number ?? "");
|
||
|
|
}
|
||
|
|
return result;
|
||
|
|
}
|
||
|
|
|
||
|
|
public async Task<(bool ok, string message)> DeleteEmail(string id)
|
||
|
|
{
|
||
|
|
return await _emailMongo.DeleteByIdAsync(id);
|
||
|
|
}
|
||
|
|
|
||
|
|
public async Task<(int success, int fail)> BatchDeleteEmail(List<string> ids)
|
||
|
|
{
|
||
|
|
int success = 0, fail = 0;
|
||
|
|
foreach (var id in ids)
|
||
|
|
{
|
||
|
|
var result = await _emailMongo.DeleteByIdAsync(id);
|
||
|
|
if (result.ok) success++;
|
||
|
|
else fail++;
|
||
|
|
}
|
||
|
|
return (success, fail);
|
||
|
|
}
|
||
|
|
|
||
|
|
public async Task<(bool isok, string msg)> BatchBanUser(BatchBanUserParam param)
|
||
|
|
{
|
||
|
|
var users = await _userMongo.FilterByAsync(_ => param.UserIds.Contains(_.Id));
|
||
|
|
var now = DateTime.Now.ToTimeStamp();
|
||
|
|
|
||
|
|
foreach (var user in users)
|
||
|
|
{
|
||
|
|
if (user.BanInfo == null)
|
||
|
|
user.BanInfo = new BanInfo();
|
||
|
|
|
||
|
|
user.BanInfo.IsBan = true;
|
||
|
|
user.BanInfo.BanTime = now;
|
||
|
|
user.BanInfo.BanReason = param.Reason;
|
||
|
|
user.BanInfo.BanDays = param.Days;
|
||
|
|
|
||
|
|
if (param.Days > 0)
|
||
|
|
{
|
||
|
|
user.BanInfo.BanEndTime = now + (long)TimeSpan.FromDays(param.Days).TotalSeconds;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
user.BanInfo.BanEndTime = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
await _userMongo.ReplaceOneAsync(user);
|
||
|
|
}
|
||
|
|
|
||
|
|
return (true, $"已封号 {users.Count()} 个用户");
|
||
|
|
}
|
||
|
|
|
||
|
|
public async Task<(bool isok, string msg)> UnbanUser(string userId)
|
||
|
|
{
|
||
|
|
var user = await _userMongo.FindByIdAsync(userId);
|
||
|
|
if (user == null)
|
||
|
|
return (false, "用户不存在");
|
||
|
|
|
||
|
|
if (user.BanInfo == null || !user.BanInfo.IsBan)
|
||
|
|
return (false, "用户未被封号");
|
||
|
|
|
||
|
|
user.BanInfo.IsBan = false;
|
||
|
|
await _userMongo.ReplaceOneAsync(user);
|
||
|
|
|
||
|
|
return (true, "解封成功");
|
||
|
|
}
|
||
|
|
|
||
|
|
public async Task<(bool isok, string msg)> BatchSendEmail(BatchSendEmailParam param)
|
||
|
|
{
|
||
|
|
const int MAX_EMAIL_COUNT = 200;
|
||
|
|
var now = DateTime.Now.ToTimeStamp();
|
||
|
|
var expireDays = param.ExpireDays > 0 ? param.ExpireDays : 30;
|
||
|
|
var expireTime = DateTime.Now.AddDays(expireDays).ToTimeStamp();
|
||
|
|
|
||
|
|
List<string> userIds;
|
||
|
|
|
||
|
|
if (param.UserIds == null || param.UserIds.Count == 0)
|
||
|
|
{
|
||
|
|
var users = await _userMongo.FilterByAsync(u => u.IsDelete == false && (u.BanInfo == null || u.BanInfo.IsBan == false));
|
||
|
|
userIds = users.Select(u => u.Id).ToList();
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
var users = await _userMongo.FilterByAsync(u => param.UserIds.Contains(u.BaseInfo.Number));
|
||
|
|
userIds = users.Select(u => u.Id).ToList();
|
||
|
|
}
|
||
|
|
|
||
|
|
var emails = new List<UserEmail>();
|
||
|
|
foreach (var userId in userIds)
|
||
|
|
{
|
||
|
|
var userEmails = await _emailMongo.FilterByAsync(c => c.UserId == userId);
|
||
|
|
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
|
||
|
|
{
|
||
|
|
UserId = userId,
|
||
|
|
Title = param.Title,
|
||
|
|
Content = param.Content,
|
||
|
|
RewardGold = param.Gold,
|
||
|
|
RewardDiamond = param.Diamond,
|
||
|
|
RewardItems = param.Props,
|
||
|
|
SendTime = DateTime.Now.ToTimeStamp(),
|
||
|
|
ExpireTime = expireTime,
|
||
|
|
IsRead = false,
|
||
|
|
IsReward = !(param.Gold > 0 || param.Diamond > 0 || (param.Props != null && param.Props.Count > 0))
|
||
|
|
};
|
||
|
|
|
||
|
|
emails.Add(email);
|
||
|
|
}
|
||
|
|
|
||
|
|
await _emailMongo.InsertManyAsync(emails);
|
||
|
|
|
||
|
|
foreach (var email in emails)
|
||
|
|
{
|
||
|
|
_capPublisher.Publish("BackgroundOperation.NotifyUser", new BackgroundOperationNotifyMessage
|
||
|
|
{
|
||
|
|
UserId = email.UserId,
|
||
|
|
Action = "new_email",
|
||
|
|
EmailId = email.Id,
|
||
|
|
Title = email.Title
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
return (true, $"已发送 {emails.Count} 封邮件");
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
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 (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)> SetUserType(string userId, int userType)
|
||
|
|
{
|
||
|
|
var user = await _userMongo.FindByIdAsync(userId);
|
||
|
|
if (user == null)
|
||
|
|
return (false, "用户不存在");
|
||
|
|
|
||
|
|
if (userType < 1 || userType > 3)
|
||
|
|
return (false, "用户类型无效,只能为1(普通用户)、2(代理商)、3(内部人员)");
|
||
|
|
|
||
|
|
user.UserType = userType;
|
||
|
|
await _userMongo.ReplaceOneAsync(user);
|
||
|
|
return (true, "设置成功");
|
||
|
|
}
|
||
|
|
|
||
|
|
public async Task<(bool isok, string msg)> BatchSetUserType(List<string> userIds, int userType)
|
||
|
|
{
|
||
|
|
if (userIds == null || userIds.Count == 0)
|
||
|
|
return (false, "请选择用户");
|
||
|
|
|
||
|
|
if (userType < 1 || userType > 3)
|
||
|
|
return (false, "用户类型无效,只能为1(普通用户)、2(代理商)、3(内部人员)");
|
||
|
|
|
||
|
|
var count = 0;
|
||
|
|
foreach (var userId in userIds)
|
||
|
|
{
|
||
|
|
var user = await _userMongo.FindByIdAsync(userId);
|
||
|
|
if (user != null)
|
||
|
|
{
|
||
|
|
user.UserType = userType;
|
||
|
|
await _userMongo.ReplaceOneAsync(user);
|
||
|
|
count++;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return (true, $"已设置 {count} 个用户的类型");
|
||
|
|
}
|
||
|
|
|
||
|
|
public async Task<(bool isok, string msg)> SetBlackUser(string userId, bool isBlack)
|
||
|
|
{
|
||
|
|
var operationId = Guid.NewGuid().ToString("N");
|
||
|
|
var startedAt = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||
|
|
var user = await _userMongo.FindByIdAsync(userId);
|
||
|
|
if (user == null)
|
||
|
|
return (false, "用户不存在");
|
||
|
|
|
||
|
|
user.PlayerFactor ??= new PlayerFactor();
|
||
|
|
user.PlayerFactor.IsBlack = isBlack;
|
||
|
|
|
||
|
|
if (isBlack)
|
||
|
|
{
|
||
|
|
var gameConfig = await _gameConfigService.Get();
|
||
|
|
user.PlayerFactor.ViolateValue = 0.01;
|
||
|
|
user.PlayerFactor.CompensateRate = new CompensateRateParam
|
||
|
|
{
|
||
|
|
BossRate = 0.01,
|
||
|
|
GoldRate = 0.01,
|
||
|
|
SpecialRate = 0.01,
|
||
|
|
Ordinary = 0.01
|
||
|
|
};
|
||
|
|
user.PlayerFactor.PenaltyRate = new CompensateRateParam
|
||
|
|
{
|
||
|
|
BossRate = 0.01,
|
||
|
|
GoldRate = 0.01,
|
||
|
|
SpecialRate = 0.01,
|
||
|
|
Ordinary = 0.01
|
||
|
|
};
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
var gameConfig = await _gameConfigService.Get();
|
||
|
|
user.PlayerFactor.ViolateValue = gameConfig.KillConfig.ViolateValue;
|
||
|
|
user.PlayerFactor.CompensateRate = gameConfig.KillConfig.CompensateRate;
|
||
|
|
user.PlayerFactor.PenaltyRate = gameConfig.KillConfig.PenaltyRate;
|
||
|
|
user.PlayerFactor.CompensateQuota = 0;
|
||
|
|
user.PlayerFactor.PenaltyQuota = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
var saveResult = await _userMongo.ReplaceOneAsync(user);
|
||
|
|
if (!saveResult.ok)
|
||
|
|
{
|
||
|
|
|
||
|
|
return (false, saveResult.message);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!PublishFactorChanged(userId, operationId, isBlack ? "AddBlacklist" : "RemoveBlacklist", startedAt))
|
||
|
|
return (false, $"数据已保存,但刷新通知发布失败,操作编号:{operationId}");
|
||
|
|
return (true, isBlack ? "已屏蔽用户" : "已取消屏蔽");
|
||
|
|
}
|
||
|
|
|
||
|
|
public async Task<(bool isok, string msg)> BatchSetBlackUser(List<string> userIds, bool isBlack)
|
||
|
|
{
|
||
|
|
if (userIds == null || userIds.Count == 0)
|
||
|
|
return (false, "请选择用户");
|
||
|
|
|
||
|
|
var count = 0;
|
||
|
|
var failedCount = 0;
|
||
|
|
foreach (var userId in userIds)
|
||
|
|
{
|
||
|
|
var operationId = Guid.NewGuid().ToString("N");
|
||
|
|
var startedAt = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||
|
|
var user = await _userMongo.FindByIdAsync(userId);
|
||
|
|
if (user != null)
|
||
|
|
{
|
||
|
|
user.PlayerFactor ??= new PlayerFactor();
|
||
|
|
user.PlayerFactor.IsBlack = isBlack;
|
||
|
|
|
||
|
|
if (isBlack)
|
||
|
|
{
|
||
|
|
user.PlayerFactor.ViolateValue = 0.01;
|
||
|
|
user.PlayerFactor.CompensateRate = new CompensateRateParam
|
||
|
|
{
|
||
|
|
BossRate = 0.01,
|
||
|
|
GoldRate = 0.01,
|
||
|
|
SpecialRate = 0.01,
|
||
|
|
Ordinary = 0.01
|
||
|
|
};
|
||
|
|
user.PlayerFactor.PenaltyRate = new CompensateRateParam
|
||
|
|
{
|
||
|
|
BossRate = 0.01,
|
||
|
|
GoldRate = 0.01,
|
||
|
|
SpecialRate = 0.01,
|
||
|
|
Ordinary = 0.01
|
||
|
|
};
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
var gameConfig = await _gameConfigService.Get();
|
||
|
|
user.PlayerFactor.ViolateValue = gameConfig.KillConfig.ViolateValue;
|
||
|
|
user.PlayerFactor.CompensateRate = gameConfig.KillConfig.CompensateRate;
|
||
|
|
user.PlayerFactor.PenaltyRate = gameConfig.KillConfig.PenaltyRate;
|
||
|
|
user.PlayerFactor.CompensateQuota = 0;
|
||
|
|
user.PlayerFactor.PenaltyQuota = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
var saveResult = await _userMongo.ReplaceOneAsync(user);
|
||
|
|
if (saveResult.ok)
|
||
|
|
{
|
||
|
|
|
||
|
|
if (PublishFactorChanged(userId, operationId, isBlack ? "BatchAddBlacklist" : "BatchRemoveBlacklist", startedAt))
|
||
|
|
count++;
|
||
|
|
else
|
||
|
|
failedCount++;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
failedCount++;
|
||
|
|
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return (failedCount == 0,
|
||
|
|
isBlack ? $"已屏蔽 {count} 个用户,失败 {failedCount} 个" : $"已取消屏蔽 {count} 个用户,失败 {failedCount} 个");
|
||
|
|
}
|
||
|
|
|
||
|
|
public async Task<(bool isok, string msg)> UpdateUserFactor(string userId, PlayerFactor factor)
|
||
|
|
{
|
||
|
|
var operationId = Guid.NewGuid().ToString("N");
|
||
|
|
var startedAt = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||
|
|
var user = await _userMongo.FindByIdAsync(userId);
|
||
|
|
if (user == null)
|
||
|
|
return (false, "用户不存在");
|
||
|
|
|
||
|
|
if (user.PlayerFactor == null)
|
||
|
|
user.PlayerFactor = new PlayerFactor();
|
||
|
|
|
||
|
|
var existing = user.PlayerFactor;
|
||
|
|
existing.ViolateValue = factor.ViolateValue;
|
||
|
|
existing.MissRate = factor.MissRate;
|
||
|
|
if (factor.CompensateQuota > 0)
|
||
|
|
{
|
||
|
|
var currentGold = user.Assets?.Gold ?? 0;
|
||
|
|
var session = _cache.GetRedisUserCId(userId);
|
||
|
|
if (!string.IsNullOrWhiteSpace(session?.RoomId))
|
||
|
|
{
|
||
|
|
var room = _cache.GetGameRoom(session.RoomId);
|
||
|
|
var onlinePlayer = room?.Players?.FirstOrDefault(p => p.UserId == userId);
|
||
|
|
if (onlinePlayer != null)
|
||
|
|
currentGold = onlinePlayer.Gold;
|
||
|
|
}
|
||
|
|
|
||
|
|
existing.CompensateStartGold = currentGold;
|
||
|
|
existing.CompensateTargetGold = checked(currentGold + factor.CompensateQuota);
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
existing.CompensateStartGold = 0;
|
||
|
|
existing.CompensateTargetGold = 0;
|
||
|
|
}
|
||
|
|
existing.CompensateQuota = factor.CompensateQuota;
|
||
|
|
existing.PenaltyQuota = factor.PenaltyQuota;
|
||
|
|
existing.CompensateRate = factor.CompensateRate;
|
||
|
|
existing.PenaltyRate = factor.PenaltyRate;
|
||
|
|
existing.AdditionalFactor = factor.AdditionalFactor;
|
||
|
|
existing.IsBlack = existing.IsBlack;
|
||
|
|
existing.IsIgnore = existing.IsIgnore;
|
||
|
|
existing.StartingBonus = existing.StartingBonus;
|
||
|
|
|
||
|
|
var saveResult = await _userMongo.ReplaceOneAsync(user);
|
||
|
|
if (!saveResult.ok)
|
||
|
|
{
|
||
|
|
|
||
|
|
return (false, saveResult.message);
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
try
|
||
|
|
{
|
||
|
|
var publishedAt = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||
|
|
_capPublisher.Publish(TopicKeys.因子变更, new PlayerFactorChangedMessage
|
||
|
|
{
|
||
|
|
UserId = userId,
|
||
|
|
ChangeType = "UpdateFactor",
|
||
|
|
OperationId = operationId,
|
||
|
|
PublishedAtUnixMs = publishedAt
|
||
|
|
});
|
||
|
|
|
||
|
|
}
|
||
|
|
catch (Exception ex)
|
||
|
|
{
|
||
|
|
_logger.LogError(ex, "[FactorChange] CAP发布失败 operationId={OperationId} topic={Topic} userId={UserId}",
|
||
|
|
operationId, TopicKeys.因子变更, userId);
|
||
|
|
return (false, $"数据已保存,但刷新通知发布失败,操作编号:{operationId}");
|
||
|
|
}
|
||
|
|
|
||
|
|
return (true, "更新成功");
|
||
|
|
}
|
||
|
|
|
||
|
|
public async Task<PlayerFactor> GetBlackFactorConfig()
|
||
|
|
{
|
||
|
|
return new PlayerFactor
|
||
|
|
{
|
||
|
|
IsBlack = true,
|
||
|
|
ViolateValue = 0.01,
|
||
|
|
CompensateRate = new CompensateRateParam
|
||
|
|
{
|
||
|
|
BossRate = 0.01,
|
||
|
|
GoldRate = 0.01,
|
||
|
|
SpecialRate = 0.01,
|
||
|
|
Ordinary = 0.01
|
||
|
|
},
|
||
|
|
PenaltyRate = new CompensateRateParam
|
||
|
|
{
|
||
|
|
BossRate = 0.01,
|
||
|
|
GoldRate = 0.01,
|
||
|
|
SpecialRate = 0.01,
|
||
|
|
Ordinary = 0.01
|
||
|
|
},
|
||
|
|
CompensateQuota = 0,
|
||
|
|
PenaltyQuota = 0
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
public async Task<(bool isok, string msg)> ResetUserFactor(string userId)
|
||
|
|
{
|
||
|
|
var operationId = Guid.NewGuid().ToString("N");
|
||
|
|
var startedAt = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||
|
|
var user = await _userMongo.FindByIdAsync(userId);
|
||
|
|
if (user == null)
|
||
|
|
return (false, "用户不存在");
|
||
|
|
|
||
|
|
var gameConfig = await _gameConfigService.Get();
|
||
|
|
user.PlayerFactor = new PlayerFactor
|
||
|
|
{
|
||
|
|
IsBlack = false,
|
||
|
|
ViolateValue = gameConfig.KillConfig.ViolateValue,
|
||
|
|
CompensateRate = gameConfig.KillConfig.CompensateRate,
|
||
|
|
PenaltyRate = gameConfig.KillConfig.PenaltyRate,
|
||
|
|
CompensateQuota = 0,
|
||
|
|
PenaltyQuota = 0
|
||
|
|
};
|
||
|
|
|
||
|
|
var saveResult = await _userMongo.ReplaceOneAsync(user);
|
||
|
|
if (!saveResult.ok)
|
||
|
|
{
|
||
|
|
|
||
|
|
return (false, saveResult.message);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!PublishFactorChanged(userId, operationId, "ResetFactor", startedAt))
|
||
|
|
return (false, $"数据已保存,但刷新通知发布失败,操作编号:{operationId}");
|
||
|
|
return (true, "重置成功");
|
||
|
|
}
|
||
|
|
|
||
|
|
private bool PublishFactorChanged(string userId, string operationId, string changeType, long startedAt)
|
||
|
|
{
|
||
|
|
try
|
||
|
|
{
|
||
|
|
var publishedAt = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||
|
|
_capPublisher.Publish(TopicKeys.因子变更, new PlayerFactorChangedMessage
|
||
|
|
{
|
||
|
|
UserId = userId,
|
||
|
|
ChangeType = changeType,
|
||
|
|
OperationId = operationId,
|
||
|
|
PublishedAtUnixMs = publishedAt
|
||
|
|
});
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
catch (Exception ex)
|
||
|
|
{
|
||
|
|
_logger.LogError(ex, "[FactorChange] CAP发布失败 operationId={OperationId} changeType={ChangeType} topic={Topic} userId={UserId}",
|
||
|
|
operationId, changeType, TopicKeys.因子变更, userId);
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public async Task<(bool isok, string msg)> InitUserPassword(string userId)
|
||
|
|
{
|
||
|
|
if (string.IsNullOrWhiteSpace(userId))
|
||
|
|
return (false, "用户不存在");
|
||
|
|
|
||
|
|
userId = userId.Trim();
|
||
|
|
var user = await GetUser(userId);
|
||
|
|
if (user == null)
|
||
|
|
{
|
||
|
|
|
||
|
|
return (false, "用户不存在");
|
||
|
|
}
|
||
|
|
|
||
|
|
user.BaseInfo.Password = Md5Helper.GetMD5("123456");
|
||
|
|
await _userMongo.ReplaceOneAsync(user);
|
||
|
|
return (true, "用户密码已初始化为123456");
|
||
|
|
}
|
||
|
|
|
||
|
|
public async Task<(bool isok, string msg)> InitSafeBoxPassword(string userId)
|
||
|
|
{
|
||
|
|
if (string.IsNullOrWhiteSpace(userId))
|
||
|
|
return (false, "用户不存在");
|
||
|
|
|
||
|
|
userId = userId.Trim();
|
||
|
|
var user = await GetUser(userId);
|
||
|
|
if (user == null)
|
||
|
|
{
|
||
|
|
|
||
|
|
return (false, "用户不存在");
|
||
|
|
}
|
||
|
|
|
||
|
|
user.SafeBox ??= new SafeBoxInfo();
|
||
|
|
user.SafeBox.Password = Common.Utils.Md5Helper.GetMD5("123456");
|
||
|
|
await _userMongo.ReplaceOneAsync(user);
|
||
|
|
return (true, "保险箱密码已初始化为123456");
|
||
|
|
}
|
||
|
|
|
||
|
|
public async Task<(List<UserEntity> list, int count)> GetUserFactorList(UserFactorListParam param)
|
||
|
|
{
|
||
|
|
var where = LinqExtensions.True<UserEntity>().And(e => e.IsDelete == false);
|
||
|
|
|
||
|
|
if (!string.IsNullOrWhiteSpace(param.KeyWord))
|
||
|
|
{
|
||
|
|
where = where.And(e => (e.BaseInfo != null && (e.BaseInfo.UserName.Contains(param.KeyWord) || e.BaseInfo.Number.Contains(param.KeyWord) || e.BaseInfo.Mobile.Contains(param.KeyWord))));
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!string.IsNullOrWhiteSpace(param.RegisterIp))
|
||
|
|
{
|
||
|
|
where = where.And(e => e.RegisterIp != null && e.RegisterIp.Contains(param.RegisterIp));
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!string.IsNullOrWhiteSpace(param.LoginIp))
|
||
|
|
{
|
||
|
|
where = where.And(e => e.Static != null && e.Static.LastLoginIp != null && e.Static.LastLoginIp.Contains(param.LoginIp));
|
||
|
|
}
|
||
|
|
|
||
|
|
if (param.IsBlack.HasValue)
|
||
|
|
{
|
||
|
|
if (param.IsBlack.Value)
|
||
|
|
{
|
||
|
|
where = where.And(e => e.PlayerFactor != null && e.PlayerFactor.IsBlack);
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
where = where.And(e => e.PlayerFactor == null || !e.PlayerFactor.IsBlack);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
HashSet<string>? onlineLoginIds = null;
|
||
|
|
if (param.IsOnline.HasValue)
|
||
|
|
{
|
||
|
|
onlineLoginIds = _cache.GetAllLoginUserIds().ToHashSet(StringComparer.Ordinal);
|
||
|
|
}
|
||
|
|
|
||
|
|
List<UserEntity> resultList;
|
||
|
|
long totalCount;
|
||
|
|
|
||
|
|
if (onlineLoginIds != null)
|
||
|
|
{
|
||
|
|
var allUsers = await _userMongo.FilterByAsync(where);
|
||
|
|
IEnumerable<UserEntity> filtered = allUsers.Where(u => onlineLoginIds.Contains(u.Id));
|
||
|
|
if (!string.IsNullOrWhiteSpace(param.IpCountOp) && param.IpCount.HasValue)
|
||
|
|
{
|
||
|
|
var onlineIpGroups = filtered
|
||
|
|
.Where(u => !string.IsNullOrEmpty(u.RegisterIp))
|
||
|
|
.GroupBy(u => u.RegisterIp)
|
||
|
|
.Where(g => {
|
||
|
|
var count = g.Count();
|
||
|
|
return param.IpCountOp switch
|
||
|
|
{
|
||
|
|
"gt" => count > param.IpCount.Value,
|
||
|
|
"gte" => count >= param.IpCount.Value,
|
||
|
|
"lt" => count < param.IpCount.Value,
|
||
|
|
"lte" => count <= param.IpCount.Value,
|
||
|
|
"eq" => count == param.IpCount.Value,
|
||
|
|
_ => true
|
||
|
|
};
|
||
|
|
})
|
||
|
|
.SelectMany(g => g)
|
||
|
|
.Select(u => u.Id)
|
||
|
|
.ToHashSet();
|
||
|
|
filtered = filtered.Where(u => onlineIpGroups.Contains(u.Id));
|
||
|
|
}
|
||
|
|
var ranked = filtered.OrderByDescending(u => u.CreateTime);
|
||
|
|
resultList = ranked
|
||
|
|
.Skip(((param.PageIndex ?? 1) - 1) * (param.PageSize ?? 15))
|
||
|
|
.Take(param.PageSize ?? 15)
|
||
|
|
.ToList();
|
||
|
|
totalCount = ranked.Count();
|
||
|
|
}
|
||
|
|
else if (!string.IsNullOrWhiteSpace(param.IpCountOp) && param.IpCount.HasValue)
|
||
|
|
{
|
||
|
|
var allUsers = await _userMongo.FilterByAsync(where);
|
||
|
|
var ipGroups = allUsers
|
||
|
|
.Where(u => !string.IsNullOrEmpty(u.RegisterIp))
|
||
|
|
.GroupBy(u => u.RegisterIp)
|
||
|
|
.Where(g => {
|
||
|
|
var count = g.Count();
|
||
|
|
return param.IpCountOp switch
|
||
|
|
{
|
||
|
|
"gt" => count > param.IpCount.Value,
|
||
|
|
"gte" => count >= param.IpCount.Value,
|
||
|
|
"lt" => count < param.IpCount.Value,
|
||
|
|
"lte" => count <= param.IpCount.Value,
|
||
|
|
"eq" => count == param.IpCount.Value,
|
||
|
|
_ => true
|
||
|
|
};
|
||
|
|
})
|
||
|
|
.SelectMany(g => g)
|
||
|
|
.Select(u => u.Id)
|
||
|
|
.ToHashSet();
|
||
|
|
|
||
|
|
where = where.And(e => ipGroups.Contains(e.Id));
|
||
|
|
var filteredResult = await _userMongo.QueryPageAsync(where, c => c.CreateTime, true, param.PageSize ?? 15, param.PageIndex ?? 1);
|
||
|
|
resultList = filteredResult.list;
|
||
|
|
totalCount = filteredResult.count;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
var result = await _userMongo.QueryPageAsync(where, c => c.CreateTime, true, param.PageSize ?? 15, param.PageIndex ?? 1);
|
||
|
|
resultList = result.list;
|
||
|
|
totalCount = result.count;
|
||
|
|
}
|
||
|
|
|
||
|
|
return (resultList, (int)totalCount);
|
||
|
|
}
|
||
|
|
|
||
|
|
public async Task<List<UserEntity>> GetUsersByRegisterIps(List<string> registerIps)
|
||
|
|
{
|
||
|
|
var filter = Builders<UserEntity>.Filter.In(u => u.RegisterIp, registerIps);
|
||
|
|
return await _userMongo.FilterByAsync(filter);
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
public async Task<List<BaseProductCount>> GetUserProps(string userId)
|
||
|
|
{
|
||
|
|
var user = await _userMongo.FindByIdAsync(userId);
|
||
|
|
return user?.Props ?? new List<BaseProductCount>();
|
||
|
|
}
|
||
|
|
|
||
|
|
public async Task<(bool isok, string msg)> AddUserProp(string userId, string propNumber, int count)
|
||
|
|
{
|
||
|
|
var user = await _userMongo.FindByIdAsync(userId);
|
||
|
|
if (user == null) return (false, "用户不存在");
|
||
|
|
if (count <= 0) return (false, "数量必须大于0");
|
||
|
|
|
||
|
|
var prop = user.Props.FirstOrDefault(p => p.Number == propNumber);
|
||
|
|
if (prop != null)
|
||
|
|
{
|
||
|
|
prop.Count += count;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
var gameConfig = _cache.GetGameConfig();
|
||
|
|
var propConfig = gameConfig.PropConfig.FirstOrDefault(c => c.Number == propNumber);
|
||
|
|
if (propConfig == null) return (false, "道具配置不存在");
|
||
|
|
|
||
|
|
user.Props.Add(new BaseProductCount
|
||
|
|
{
|
||
|
|
Number = propConfig.Number,
|
||
|
|
Name = propConfig.Name,
|
||
|
|
Type = propConfig.Type,
|
||
|
|
ImageUrl = propConfig.ImageUrl,
|
||
|
|
Count = count
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
var update = Builders<UserEntity>.Update.Set(p => p.Props, user.Props);
|
||
|
|
var (ok, message) = await _userMongo.UpdateByIdAsync(user.Id, update);
|
||
|
|
if (ok)
|
||
|
|
{
|
||
|
|
_capPublisher.Publish("BackgroundOperation.NotifyUser", new BackgroundOperationNotifyMessage
|
||
|
|
{
|
||
|
|
UserId = userId,
|
||
|
|
Action = "backpack_add",
|
||
|
|
PropNumber = propNumber,
|
||
|
|
Count = count
|
||
|
|
});
|
||
|
|
}
|
||
|
|
return (ok, message);
|
||
|
|
}
|
||
|
|
|
||
|
|
public async Task<(bool isok, string msg)> UpdateUserPropCount(string userId, string propNumber, int count)
|
||
|
|
{
|
||
|
|
var user = await _userMongo.FindByIdAsync(userId);
|
||
|
|
if (user == null) return (false, "用户不存在");
|
||
|
|
|
||
|
|
var prop = user.Props.FirstOrDefault(p => p.Number == propNumber);
|
||
|
|
if (prop == null) return (false, "道具不存在");
|
||
|
|
|
||
|
|
if (count <= 0)
|
||
|
|
{
|
||
|
|
user.Props.Remove(prop);
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
prop.Count = count;
|
||
|
|
}
|
||
|
|
|
||
|
|
var update = Builders<UserEntity>.Update.Set(p => p.Props, user.Props);
|
||
|
|
var (ok, message) = await _userMongo.UpdateByIdAsync(user.Id, update);
|
||
|
|
if (ok)
|
||
|
|
{
|
||
|
|
_capPublisher.Publish("BackgroundOperation.NotifyUser", new BackgroundOperationNotifyMessage
|
||
|
|
{
|
||
|
|
UserId = userId,
|
||
|
|
Action = count <= 0 ? "backpack_delete" : "backpack_update",
|
||
|
|
PropNumber = propNumber,
|
||
|
|
Count = count
|
||
|
|
});
|
||
|
|
}
|
||
|
|
return (ok, message);
|
||
|
|
}
|
||
|
|
|
||
|
|
public async Task<(bool isok, string msg)> DeleteUserProp(string userId, string propNumber)
|
||
|
|
{
|
||
|
|
var user = await _userMongo.FindByIdAsync(userId);
|
||
|
|
if (user == null) return (false, "用户不存在");
|
||
|
|
|
||
|
|
var prop = user.Props.FirstOrDefault(p => p.Number == propNumber);
|
||
|
|
if (prop == null) return (false, "道具不存在");
|
||
|
|
|
||
|
|
user.Props.Remove(prop);
|
||
|
|
|
||
|
|
var update = Builders<UserEntity>.Update.Set(p => p.Props, user.Props);
|
||
|
|
var (ok, message) = await _userMongo.UpdateByIdAsync(user.Id, update);
|
||
|
|
if (ok)
|
||
|
|
{
|
||
|
|
_capPublisher.Publish("BackgroundOperation.NotifyUser", new BackgroundOperationNotifyMessage
|
||
|
|
{
|
||
|
|
UserId = userId,
|
||
|
|
Action = "backpack_delete",
|
||
|
|
PropNumber = propNumber
|
||
|
|
});
|
||
|
|
}
|
||
|
|
return (ok, message);
|
||
|
|
}
|
||
|
|
|
||
|
|
public async Task<(int total, int updated, string msg)> SyncAllUserLevel()
|
||
|
|
{
|
||
|
|
var gameConfig = await _gameConfigService.Get();
|
||
|
|
if (gameConfig == null)
|
||
|
|
return (0, 0, "游戏配置不存在");
|
||
|
|
|
||
|
|
var allUsers = await _userMongo.FilterByAsync(u => !u.IsDelete);
|
||
|
|
int updated = 0;
|
||
|
|
|
||
|
|
foreach (var user in allUsers)
|
||
|
|
{
|
||
|
|
bool changed = false;
|
||
|
|
|
||
|
|
var exp = user.Assets.Exp;
|
||
|
|
var levelConfig = gameConfig.UserLevelConfigs
|
||
|
|
.FirstOrDefault(c => exp >= c.ExpMin && exp < c.ExpMax);
|
||
|
|
if (levelConfig != null && levelConfig.Level != user.Assets.Level)
|
||
|
|
{
|
||
|
|
user.Assets.Level = levelConfig.Level;
|
||
|
|
changed = true;
|
||
|
|
}
|
||
|
|
|
||
|
|
var vipExp = user.Static.TotalRecharge * 10 + user.Assets.VipExpExtra;
|
||
|
|
user.Assets.VipExp = (long)vipExp;
|
||
|
|
var vipLevelConfig = gameConfig.VipLevelConfigs
|
||
|
|
.FirstOrDefault(c => vipExp >= c.ExpMin && vipExp < c.ExpMax);
|
||
|
|
if (vipLevelConfig != null && vipLevelConfig.Level != user.Assets.VipLevel)
|
||
|
|
{
|
||
|
|
user.Assets.VipLevel = vipLevelConfig.Level;
|
||
|
|
user.Assets.VipSignInAdd = vipLevelConfig.SignInAdd;
|
||
|
|
changed = true;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (changed)
|
||
|
|
{
|
||
|
|
var update = Builders<UserEntity>.Update
|
||
|
|
.Set(p => p.Assets.Level, user.Assets.Level)
|
||
|
|
.Set(p => p.Assets.VipLevel, user.Assets.VipLevel)
|
||
|
|
.Set(p => p.Assets.VipExp, user.Assets.VipExp)
|
||
|
|
.Set(p => p.Assets.VipExpExtra, user.Assets.VipExpExtra)
|
||
|
|
.Set(p => p.Assets.VipSignInAdd, user.Assets.VipSignInAdd);
|
||
|
|
await _userMongo.UpdateByIdAsync(user.Id, update);
|
||
|
|
updated++;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return (allUsers.Count, updated, $"共 {allUsers.Count} 个玩家,更新了 {updated} 个");
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
}
|