diff --git a/AdminSrc/AdminApi/AdminApi.csproj b/AdminSrc/AdminApi/AdminApi.csproj
new file mode 100644
index 0000000..d9666a2
--- /dev/null
+++ b/AdminSrc/AdminApi/AdminApi.csproj
@@ -0,0 +1,28 @@
+
+
+
+ Exe
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/AdminSrc/AdminApi/AdminUser/AdminUserController.cs b/AdminSrc/AdminApi/AdminUser/AdminUserController.cs
new file mode 100644
index 0000000..ea15e65
--- /dev/null
+++ b/AdminSrc/AdminApi/AdminUser/AdminUserController.cs
@@ -0,0 +1,665 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using AdminService.GameConfigs;
+using AdminService.User;
+using AdminService.User.Param;
+using AdminService.User.Response;
+using Common;
+using Common.Utils;
+using CommonDomain;
+using CommonDomain.BaseModel;
+using CommonDomain.Enum;
+using CommonInfrastructure;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using RedisService;
+using UserDomain.Entity;
+
+namespace AdminApi.AdminUser
+{
+ [ApiController, Route("adminapi/[controller]")]
+ [Authorize]
+ public class AdminUserController : BaseController
+ {
+ private readonly IAdminUserService _userService;
+ private readonly IRedisService _redisService;
+
+ public AdminUserController(IAdminUserService userService, IRedisService redisService)
+ {
+ _userService = userService;
+ _redisService = redisService;
+ }
+
+ [HttpPost("GetUserList")]
+ public async Task> GetUserList(AdminUserListParam param)
+ {
+ var result = await _userService.GetAdminUserList(param);
+ var onlineUserIds = _redisService.GetAllLoginUserIds().ToHashSet(StringComparer.Ordinal);
+ var dtos = (result.list ?? new List()).Select(u =>
+ {
+ var dto = MapToUserListItemDto(u);
+ dto.IsOnline = onlineUserIds.Contains(u.Id);
+ if (dto.CurrentGameType != 3)
+ {
+ var session = _redisService.GetRedisUserCId(u.Id);
+ if (session == null || string.IsNullOrWhiteSpace(session.RoomId))
+ {
+ dto.CurrentGameType = 0;
+ dto.RoomNumber = "";
+ }
+ }
+ return dto;
+ }).ToList();
+ return this.ToPageResult(dtos, result.count);
+ }
+
+ [HttpGet("GetUserDetail")]
+ public async Task> GetUserDetail(string userId)
+ {
+ var result = await _userService.GetUser(userId);
+ if (result == null)
+ return this.ToFailResult("用户不存在");
+
+ return this.ToResult(result);
+ }
+
+ [HttpPost("UpdateUserAssets")]
+ public async Task> UpdateUserAssets(UpdateUserAssetsParam param)
+ {
+ var result = await _userService.UpdateUserAssets(param);
+ if (result.isok)
+ return this.ToResult(result.msg);
+ else
+ return this.ToFailResult(result.msg);
+ }
+
+ [HttpGet("GetUserStatistics")]
+ public async Task> GetUserStatistics()
+ {
+ var result = await _userService.GetUserStatistics();
+ return this.ToResult(MapToUserStatisticsDto(result));
+ }
+
+ [HttpGet("GetEmailStatistics")]
+ public async Task> GetEmailStatistics()
+ {
+ var result = await _userService.GetEmailStatistics();
+ return this.ToResult(MapToEmailStatisticsDto(result));
+ }
+
+ [HttpPost("BatchBanUser")]
+ public async Task> BatchBanUser(BatchBanUserParam param)
+ {
+ var result = await _userService.BatchBanUser(param);
+ if (result.isok)
+ return this.ToResult(result.msg);
+ else
+ return this.ToFailResult(result.msg);
+ }
+
+ [HttpGet("UnbanUser")]
+ public async Task> UnbanUser(string userId)
+ {
+ var result = await _userService.UnbanUser(userId);
+ if (result.isok)
+ return this.ToResult(result.msg);
+ else
+ return this.ToFailResult(result.msg);
+ }
+
+ [HttpPost("BatchSendEmail")]
+ public async Task> BatchSendEmail(BatchSendEmailParam param)
+ {
+ var result = await _userService.BatchSendEmail(param);
+ if (result.isok)
+ return this.ToResult(result.msg);
+ else
+ return this.ToFailResult(result.msg);
+ }
+
+ [HttpGet("CancelUser")]
+ [Authorize]
+ public async Task> CancelUser()
+ {
+ var result = await _userService.CancelUser(loginUser);
+ if (result.isok)
+ return this.ToResult(result.msg);
+ else
+ return this.ToFailResult(result.msg);
+ }
+
+ [HttpGet("DeleteUser")]
+ [Authorize]
+ public async Task> DeleteUser(string id)
+ {
+ var result = await _userService.DeleteUser(id);
+ if (result.isok)
+ return this.ToResult(result.msg);
+ else
+ return this.ToFailResult(result.msg);
+ }
+
+ [HttpPost("BatchDeleteUser")]
+ [Authorize]
+ public async Task> BatchDeleteUser([FromBody] BatchDeleteUserParam param)
+ {
+ var result = await _userService.BatchDeleteUser(param.UserIds);
+ if (result.isok)
+ return this.ToResult(result.msg);
+ else
+ return this.ToFailResult(result.msg);
+ }
+
+ [HttpPost("BanUser")]
+ [Authorize]
+ public async Task> BanUser([FromBody] BanUserParam param)
+ {
+ var result = await _userService.BanUser(param.UserId, param.Reason, param.Days);
+ if (result.isok)
+ return this.ToResult(result.msg);
+ else
+ return this.ToFailResult(result.msg);
+ }
+
+ [HttpPost("UpdateUser")]
+ [Authorize]
+ public async Task> UpdateUser(UpdateUserDataParam param)
+ {
+ var result = await _userService.UpdateUser(param);
+ if (result.isok)
+ return this.ToResult(result.msg);
+ else
+ return this.ToFailResult(result.msg);
+ }
+
+ [HttpPost("SendEmail")]
+ [Authorize]
+ public async Task> SendEmail(SendEmailParam param)
+ {
+ var result = await _userService.SendEmail(param, loginUser);
+ if (result.isok)
+ return this.ToResult(result.msg);
+ else
+ return this.ToFailResult(result.msg);
+ }
+
+ [HttpPost("GetEmailList")]
+ [Authorize]
+ public async Task> GetEmailList([FromBody] EmailListQueryParam param)
+ {
+ var (list, count) = await _userService.GetEmailList(param);
+ var userIds = list.Where(e => !string.IsNullOrWhiteSpace(e.UserId))
+ .Select(e => e.UserId).Distinct().ToList();
+ var userNames = await _userService.GetEmailUserNamesAsync(userIds);
+ var dtos = list.Select(e =>
+ {
+ userNames.TryGetValue(e.UserId ?? "", out var info);
+ return new EmailListItemDto
+ {
+ Id = e.Id,
+ UserId = e.UserId ?? "",
+ UserName = info.UserName,
+ UserNumber = info.UserNumber,
+ Title = e.Title ?? "",
+ IsRead = e.IsRead,
+ IsReward = e.IsReward,
+ RewardGold = e.RewardGold,
+ RewardDiamond = e.RewardDiamond,
+ SendTime = e.SendTime,
+ ReadTime = e.ReadTime,
+ ExpireTime = e.ExpireTime,
+ RewardText = BuildRewardText(e)
+ };
+ }).ToList();
+ return this.ToPageResult(dtos, (int)count);
+ }
+
+ [HttpGet("DeleteEmail")]
+ [Authorize]
+ public async Task> DeleteEmail(string id)
+ {
+ var result = await _userService.DeleteEmail(id);
+ if (result.ok)
+ return this.ToResult("删除成功");
+ else
+ return this.ToFailResult(result.message ?? "删除失败");
+ }
+
+ [HttpPost("BatchDeleteEmail")]
+ [Authorize]
+ public async Task> BatchDeleteEmail([FromBody] List ids)
+ {
+ var (success, fail) = await _userService.BatchDeleteEmail(ids);
+ if (fail == 0)
+ return this.ToResult($"成功删除 {success} 条");
+ return this.ToResult($"删除完成:成功 {success} 条,失败 {fail} 条");
+ }
+
+ [HttpPost("SetUserType")]
+ public async Task> SetUserType(string userId, int userType)
+ {
+ var result = await _userService.SetUserType(userId, userType);
+ if (result.isok)
+ return this.ToResult(result.msg);
+ else
+ return this.ToFailResult(result.msg);
+ }
+
+ [HttpPost("BatchSetUserType")]
+ public async Task> BatchSetUserType([FromBody] BatchSetUserTypeParam param)
+ {
+ var result = await _userService.BatchSetUserType(param.UserIds, param.UserType);
+ if (result.isok)
+ return this.ToResult(result.msg);
+ else
+ return this.ToFailResult(result.msg);
+ }
+
+ [HttpPost("SetBlackUser")]
+ public async Task> SetBlackUser(string userId, bool isBlack)
+ {
+ var result = await _userService.SetBlackUser(userId, isBlack);
+ if (result.isok)
+ return this.ToResult(result.msg);
+ else
+ return this.ToFailResult(result.msg);
+ }
+
+ [HttpPost("BatchSetBlackUser")]
+ public async Task> BatchSetBlackUser([FromBody] BatchSetBlackUserParam param)
+ {
+ var result = await _userService.BatchSetBlackUser(param.UserIds, param.IsBlack);
+ if (result.isok)
+ return this.ToResult(result.msg);
+ else
+ return this.ToFailResult(result.msg);
+ }
+
+ [HttpPost("UpdateUserFactor")]
+ public async Task> UpdateUserFactor([FromBody] UpdateUserFactorFlatParam param)
+ {
+ var factor = new PlayerFactor
+ {
+ ViolateValue = param.ViolateValue,
+ MissRate = param.MissRate,
+ CompensateQuota = param.CompensateQuota,
+ PenaltyQuota = param.PenaltyQuota,
+ CompensateRate = param.CompensateRate,
+ PenaltyRate = param.PenaltyRate,
+ AdditionalFactor = param.AdditionalFactor
+ };
+ var result = await _userService.UpdateUserFactor(param.UserId, factor);
+ if (result.isok)
+ return this.ToResult(result.msg);
+ else
+ return this.ToFailResult(result.msg);
+ }
+
+ [HttpGet("GetBlackFactorConfig")]
+ public async Task> GetBlackFactorConfig()
+ {
+ var result = await _userService.GetBlackFactorConfig();
+ return this.ToResult(MapToPlayerFactorDto(result));
+ }
+
+ [HttpPost("ResetUserFactor")]
+ public async Task> ResetUserFactor(string userId)
+ {
+ var result = await _userService.ResetUserFactor(userId);
+ if (result.isok)
+ return this.ToResult(result.msg);
+ else
+ return this.ToFailResult(result.msg);
+ }
+
+ [HttpGet("InitUserPassword")]
+ public async Task> InitUserPassword([FromQuery] string userId)
+ {
+ var result = await _userService.InitUserPassword(userId);
+ if (result.isok)
+ return this.ToResult(result.msg);
+ else
+ return this.ToFailResult(result.msg);
+ }
+
+ [HttpGet("InitSafeBoxPassword")]
+ public async Task> InitSafeBoxPassword([FromQuery] string userId)
+ {
+ var result = await _userService.InitSafeBoxPassword(userId);
+ if (result.isok)
+ return this.ToResult(result.msg);
+ else
+ return this.ToFailResult(result.msg);
+ }
+
+ [HttpPost("GetUserFactorList")]
+ public async Task> GetUserFactorList([FromBody] UserFactorListParam param)
+ {
+ var result = await _userService.GetUserFactorList(param);
+
+ var pageIps = (result.list ?? new List())
+ .Where(u => !string.IsNullOrEmpty(u.RegisterIp))
+ .Select(u => u.RegisterIp!)
+ .Distinct()
+ .ToList();
+
+ Dictionary ipCounts = new Dictionary();
+ if (pageIps.Count > 0)
+ {
+ var allUsersWithSameIp = await _userService.GetUsersByRegisterIps(pageIps);
+ ipCounts = allUsersWithSameIp
+ .GroupBy(u => u.RegisterIp)
+ .ToDictionary(g => g.Key, g => g.Count());
+ }
+
+ var dtos = (result.list ?? new List()).Select(u => new UserFactorListItemDto
+ {
+ Id = u.Id,
+ Number = u.BaseInfo?.Number ?? "",
+ UserName = u.BaseInfo?.UserName ?? "",
+ Mobile = u.BaseInfo?.Mobile ?? "",
+ UserType = u.UserType,
+ RegisterIp = u.RegisterIp ?? "",
+ LoginIp = u.Static?.LastLoginIp ?? "",
+ IpCount = !string.IsNullOrEmpty(u.RegisterIp) && ipCounts.ContainsKey(u.RegisterIp) ? ipCounts[u.RegisterIp] : 1,
+ IsBlack = u.PlayerFactor?.IsBlack ?? false,
+ ViolateValue = u.PlayerFactor?.ViolateValue ?? 0.2,
+ MissRate = u.PlayerFactor?.MissRate ?? 0,
+ CompensateQuota = u.PlayerFactor?.CompensateQuota ?? 0,
+ PenaltyQuota = u.PlayerFactor?.PenaltyQuota ?? 0,
+ CompensateRate = u.PlayerFactor?.CompensateRate != null ? new CompensateRateDto
+ {
+ BossRate = u.PlayerFactor.CompensateRate.BossRate,
+ GoldRate = u.PlayerFactor.CompensateRate.GoldRate,
+ SpecialRate = u.PlayerFactor.CompensateRate.SpecialRate,
+ Ordinary = u.PlayerFactor.CompensateRate.Ordinary
+ } : new CompensateRateDto(),
+ PenaltyRate = u.PlayerFactor?.PenaltyRate != null ? new PenaltyRateDto
+ {
+ BossRate = u.PlayerFactor.PenaltyRate.BossRate,
+ GoldRate = u.PlayerFactor.PenaltyRate.GoldRate,
+ SpecialRate = u.PlayerFactor.PenaltyRate.SpecialRate,
+ Ordinary = u.PlayerFactor.PenaltyRate.Ordinary
+ } : new PenaltyRateDto(),
+ CreateTime = u.CreateTime,
+ TotalRecharge = u.Static?.TotalRecharge ?? 0,
+ TotalRechargeGold = u.Static?.TotalRechargeGold ?? 0,
+ TotalConsumeGold = u.Static?.TotalConsumeGold ?? 0,
+ CurrentGold = u.Assets?.Gold ?? 0,
+ Profit = (u.Assets?.Gold ?? 0) - ((u.Static?.TotalRechargeGold ?? 0) + (u.PlayerFactor?.StartingBonus ?? 0)),
+ AdditionalFactor = u.PlayerFactor?.AdditionalFactor ?? 0,
+ StartingBonus = u.PlayerFactor?.StartingBonus ?? 0
+ });
+ return this.ToPageResult(dtos, result.count);
+ }
+
+
+ private static UserListItemDto MapToUserListItemDto(UserEntity entity)
+ {
+ return new UserListItemDto
+ {
+ Id = entity.Id,
+ Number = entity.BaseInfo?.Number ?? "",
+ NickName = entity.BaseInfo?.UserName ?? "",
+ Avatar = entity.BaseInfo?.AvatarUrl ?? "",
+ Mobile = entity.BaseInfo?.Mobile ?? "",
+ Diamond = entity.Assets?.Diamond ?? 0,
+ Gold = entity.Assets?.Gold ?? 0,
+ SafeBoxGold = entity.SafeBox?.Amount ?? 0,
+ VipLevel = entity.Assets?.VipLevel ?? 0,
+ UserType = entity.UserType,
+ IsBan = entity.BanInfo?.IsBan ?? false,
+ BanReason = entity.BanInfo?.BanReason,
+ BanEndTime = entity.BanInfo?.BanEndTime ?? 0,
+ IsBlack = entity.PlayerFactor?.IsBlack ?? false,
+ RegisterTime = entity.CreateTime,
+ RegisterIp = entity.RegisterIp ?? "",
+ LastLoginIp = entity.Static?.LastLoginIp ?? "",
+ LastLoginTime = entity.Static?.LastLoginTime ?? 0,
+ RoomNumber = entity.Static?.RoomNumber ?? "",
+ CurrentGameType = entity.Static?.CurrentGameType ?? 0,
+ Channel = entity.BaseInfo?.Channel ?? "",
+ TotalRecharge = (long)(entity.Static?.TotalRecharge ?? 0)
+ };
+ }
+
+ private static UserStatisticsDto MapToUserStatisticsDto(UserStatistics stat)
+ {
+ return new UserStatisticsDto
+ {
+ TotalUsers = stat.TotalUsers,
+ TodayRegisteredUsers = stat.TodayRegisteredUsers,
+ MonthRegisteredUsers = stat.MonthRegisteredUsers,
+ OnlineUsers = stat.OnlineUsers,
+ BannedUsers = stat.BannedUsers,
+ VipUsers = stat.VipUsers,
+ VipLevelCount = stat.VipLevelCount ?? new Dictionary(),
+ TotalGold = stat.TotalGold,
+ TotalDiamond = stat.TotalDiamond
+ };
+ }
+
+ private static EmailStatisticsDto MapToEmailStatisticsDto(EmailStatistics stat)
+ {
+ return new EmailStatisticsDto
+ {
+ TotalEmails = stat.TotalEmails,
+ RewardEmails = stat.RewardEmails,
+ UnclaimedEmails = stat.UnclaimedEmails,
+ TodayEmails = stat.TodayEmails,
+ MonthEmails = stat.MonthEmails
+ };
+ }
+
+ private static PlayerFactorDto MapToPlayerFactorDto(PlayerFactor factor)
+ {
+ if (factor == null)
+ return new PlayerFactorDto();
+
+ return new PlayerFactorDto
+ {
+ IsBlack = factor.IsBlack,
+ IsIgnore = factor.IsIgnore,
+ ViolateValue = factor.ViolateValue,
+ CompensateRate = factor.CompensateRate?.BossRate ?? 1,
+ PenaltyRate = factor.PenaltyRate?.BossRate ?? 0,
+ CompensateQuota = factor.CompensateQuota,
+ PenaltyQuota = factor.PenaltyQuota,
+ StartingBonus = factor.StartingBonus
+ };
+ }
+
+ private static string BuildRewardText(UserEmail email)
+ {
+ var parts = new List();
+ if (email.RewardGold > 0)
+ parts.Add($"金币{email.RewardGold:N0}");
+ if (email.RewardDiamond > 0)
+ parts.Add($"钻石{email.RewardDiamond}");
+ if (email.RewardItems != null)
+ {
+ foreach (var item in email.RewardItems)
+ {
+ if (item != null && !string.IsNullOrWhiteSpace(item.Name))
+ parts.Add($"{item.Name}*{item.Count}");
+ }
+ }
+ return parts.Count > 0 ? string.Join(" ", parts) : "无";
+ }
+
+
+
+ [HttpGet("GetUserProps")]
+ public async Task>> GetUserProps(string userId)
+ {
+ var result = await _userService.GetUserProps(userId);
+ return this.ToResult(result);
+ }
+
+ [HttpPost("AddUserProp")]
+ public async Task> AddUserProp(string userId, string propNumber, int count)
+ {
+ var res = await _userService.AddUserProp(userId, propNumber, count);
+ if (res.isok) return this.ToResult(res.msg);
+ return this.ToFailResult(res.msg);
+ }
+
+ [HttpPost("UpdateUserPropCount")]
+ public async Task> UpdateUserPropCount(string userId, string propNumber, int count)
+ {
+ var res = await _userService.UpdateUserPropCount(userId, propNumber, count);
+ if (res.isok) return this.ToResult(res.msg);
+ return this.ToFailResult(res.msg);
+ }
+
+ [HttpPost("DeleteUserProp")]
+ public async Task> DeleteUserProp(string userId, string propNumber)
+ {
+ var res = await _userService.DeleteUserProp(userId, propNumber);
+ if (res.isok) return this.ToResult(res.msg);
+ return this.ToFailResult(res.msg);
+ }
+
+ [HttpGet("SyncAllUserLevel")]
+ public async Task> SyncAllUserLevel()
+ {
+ var (total, updated, msg) = await _userService.SyncAllUserLevel();
+ return this.ToResult(msg);
+ }
+
+ }
+
+
+ public class UserListItemDto
+ {
+ public string Id { get; set; }
+ public string Number { get; set; }
+ public string NickName { get; set; }
+ public string Avatar { get; set; }
+ public string Mobile { get; set; }
+ public int Diamond { get; set; }
+ public long Gold { get; set; }
+ public long SafeBoxGold { get; set; }
+ public int VipLevel { get; set; }
+ public int UserType { get; set; }
+ public bool IsBan { get; set; }
+ public string BanReason { get; set; }
+ public long BanEndTime { get; set; }
+ public bool IsBlack { get; set; }
+ public long RegisterTime { get; set; }
+ public string RegisterIp { get; set; }
+ public string LastLoginIp { get; set; }
+ public long LastLoginTime { get; set; }
+ public string RoomNumber { get; set; }
+ public int CurrentGameType { get; set; }
+ public bool IsOnline { get; set; }
+ public string Channel { get; set; }
+ public long TotalRecharge { get; set; }
+ }
+
+ public class UserStatisticsDto
+ {
+ public long TotalUsers { get; set; }
+ public long TodayRegisteredUsers { get; set; }
+ public long MonthRegisteredUsers { get; set; }
+ public long OnlineUsers { get; set; }
+ public long BannedUsers { get; set; }
+ public long VipUsers { get; set; }
+ public Dictionary VipLevelCount { get; set; }
+ public long TotalGold { get; set; }
+ public long TotalDiamond { get; set; }
+ }
+
+ public class EmailStatisticsDto
+ {
+ public long TotalEmails { get; set; }
+ public long RewardEmails { get; set; }
+ public long UnclaimedEmails { get; set; }
+ public long TodayEmails { get; set; }
+ public long MonthEmails { get; set; }
+ }
+
+ public class PlayerFactorDto
+ {
+ public bool IsBlack { get; set; }
+ public bool IsIgnore { get; set; }
+ public double ViolateValue { get; set; }
+ public double CompensateRate { get; set; }
+ public double PenaltyRate { get; set; }
+ public int CompensateQuota { get; set; }
+ public int PenaltyQuota { get; set; }
+ public long StartingBonus { get; set; }
+ }
+
+ public class UserFactorListItemDto
+ {
+ public string Id { get; set; }
+ public string Number { get; set; }
+ public string UserName { get; set; }
+ public string Mobile { get; set; }
+ public int UserType { get; set; }
+ public string RegisterIp { get; set; }
+ public string LoginIp { get; set; }
+ public int IpCount { get; set; }
+ public bool IsBlack { get; set; }
+ public double ViolateValue { get; set; }
+ public double MissRate { get; set; }
+ public int CompensateQuota { get; set; }
+ public int PenaltyQuota { get; set; }
+ public CompensateRateDto CompensateRate { get; set; }
+ public PenaltyRateDto PenaltyRate { get; set; }
+ public long CreateTime { get; set; }
+ public double TotalRecharge { get; set; }
+ public long TotalRechargeGold { get; set; }
+ public long TotalConsumeGold { get; set; }
+ public long CurrentGold { get; set; }
+ public long Profit { get; set; }
+ public double AdditionalFactor { get; set; }
+ public long StartingBonus { get; set; }
+ }
+
+ public class CompensateRateDto
+ {
+ public double BossRate { get; set; } = 1;
+ public double GoldRate { get; set; } = 1;
+ public double SpecialRate { get; set; } = 1;
+ public double Ordinary { get; set; } = 1;
+ }
+
+ public class PenaltyRateDto
+ {
+ public double BossRate { get; set; } = 1;
+ public double GoldRate { get; set; } = 1;
+ public double SpecialRate { get; set; } = 1;
+ public double Ordinary { get; set; } = 1;
+ }
+
+ public class BanUserParam
+ {
+ public string UserId { get; set; } = "";
+
+ public string Reason { get; set; } = "";
+
+ public int Days { get; set; } = 0;
+ }
+
+ public class EmailListItemDto
+ {
+ public string Id { get; set; } = "";
+ public string UserId { get; set; } = "";
+ public string UserName { get; set; } = "";
+ public string UserNumber { get; set; } = "";
+ public string Title { get; set; } = "";
+ public bool IsRead { get; set; }
+ public bool IsReward { get; set; }
+ public long RewardGold { get; set; }
+ public int RewardDiamond { get; set; }
+ public long SendTime { get; set; }
+ public long ReadTime { get; set; }
+ public long ExpireTime { get; set; }
+ public string RewardText { get; set; } = "";
+ }
+
+}
diff --git a/AdminSrc/AdminApi/AssemblyMarker.cs b/AdminSrc/AdminApi/AssemblyMarker.cs
new file mode 100644
index 0000000..e7c92c5
--- /dev/null
+++ b/AdminSrc/AdminApi/AssemblyMarker.cs
@@ -0,0 +1,6 @@
+namespace AdminApi
+{
+ public static class AssemblyMarker
+ {
+ }
+}
\ No newline at end of file
diff --git a/AdminSrc/AdminApi/BackgroundOperation/BackgroundOperationController.cs b/AdminSrc/AdminApi/BackgroundOperation/BackgroundOperationController.cs
new file mode 100644
index 0000000..1adfcc5
--- /dev/null
+++ b/AdminSrc/AdminApi/BackgroundOperation/BackgroundOperationController.cs
@@ -0,0 +1,114 @@
+using System.Threading.Tasks;
+using Common;
+using CommonDomain;
+using CommonInfrastructure;
+using CommonInfrastructure.Response;
+using CommonService;
+using CommonService.BackgroundOperation;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using UserDomain.Entity;
+
+namespace AdminApi.BackgroundOperation
+{
+ [Authorize]
+ [Route("adminapi/[controller]")]
+ [ApiController]
+ public class BackgroundOperationController : BaseController
+ {
+ private readonly IBackgroundOperationService _operationService;
+
+ public BackgroundOperationController(IBackgroundOperationService operationService)
+ {
+ _operationService = operationService;
+ }
+
+ [HttpPost("charge")]
+ public async Task> Charge([FromBody] BackgroundOperationRequest request)
+ {
+ if (request == null || string.IsNullOrWhiteSpace(request.TargetUserId))
+ return this.ToFailResult("目标用户ID不能为空");
+
+ if (request.Amount <= 0)
+ return this.ToFailResult("操作数量必须大于0");
+
+ var (success, message) = await _operationService.Charge(
+ loginUser.UserId, request.TargetUserId, request.CurrencyType, request.Amount, request.Remark);
+
+ return success
+ ? this.ToResult("充值成功")
+ : this.ToFailResult(message);
+ }
+
+ [HttpPost("deduct")]
+ public async Task> Deduct([FromBody] BackgroundOperationRequest request)
+ {
+ if (request == null || string.IsNullOrWhiteSpace(request.TargetUserId))
+ return this.ToFailResult("目标用户ID不能为空");
+
+ if (request.Amount <= 0)
+ return this.ToFailResult("操作数量必须大于0");
+
+ var (success, message) = await _operationService.Deduct(
+ loginUser.UserId, request.TargetUserId, request.CurrencyType, request.Amount, request.Remark);
+
+ return success
+ ? this.ToResult("扣除成功")
+ : this.ToFailResult(message);
+ }
+
+ [HttpGet("logs")]
+ public async Task> GetLogs(
+ string? adminUserId = null,
+ string? targetUserId = null,
+ int? operationType = null,
+ int pageIndex = 1,
+ int pageSize = 20)
+ {
+ var (data, total) = await _operationService.QueryLogs(
+ adminUserId, targetUserId, operationType, pageIndex, pageSize);
+
+ return this.ToPageResult(data, total);
+ }
+
+ [HttpGet("search-user")]
+ public async Task> SearchUser(string keyword)
+ {
+ if (string.IsNullOrWhiteSpace(keyword))
+ {
+
+ return this.ToFailResult("搜索关键词不能为空");
+ }
+
+ var user = await _operationService.SearchUser(keyword);
+
+ if (user == null)
+ {
+ return this.ToFailResult("用户不存在");
+ }
+
+ var dto = new SearchUserResponse
+ {
+ UserId = user.Id,
+ UserName = user.BaseInfo?.UserName ?? "",
+ UserPhone = user.BaseInfo?.Mobile ?? "",
+ Gold = user.Assets?.Gold ?? 0,
+ Diamond = user.Assets?.Diamond ?? 0,
+ RegisterTime = user.CreateTime / 1000
+ };
+
+ return this.ToResult(dto);
+ }
+
+ }
+
+ public class SearchUserResponse
+ {
+ public string UserId { get; set; }
+ public string UserName { get; set; }
+ public string UserPhone { get; set; }
+ public long Gold { get; set; }
+ public int Diamond { get; set; }
+ public long RegisterTime { get; set; }
+ }
+}
diff --git a/AdminSrc/AdminApi/BeautifulNumber/BeautifulNumberController.cs b/AdminSrc/AdminApi/BeautifulNumber/BeautifulNumberController.cs
new file mode 100644
index 0000000..47984b5
--- /dev/null
+++ b/AdminSrc/AdminApi/BeautifulNumber/BeautifulNumberController.cs
@@ -0,0 +1,384 @@
+using System.Collections.Generic;
+using System.Linq;
+using AdminDomain.BeautifulNumber;
+using AdminDomain.Entity;
+using AdminService.BeautifulNumber;
+using Common;
+using CommonInfrastructure;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using UserDomain.Entity;
+
+namespace AdminApi.BeautifulNumber
+{
+ [ApiController, Route("adminapi/[controller]")]
+ [Authorize]
+
+ public class BeautifulNumberController : BaseController
+ {
+ private readonly IBeautifulNumberService _beautifulNumberService;
+
+ public BeautifulNumberController(IBeautifulNumberService beautifulNumberService)
+ {
+ _beautifulNumberService = beautifulNumberService;
+ }
+
+ [HttpGet("list")]
+ public async Task> GetList([FromQuery] BeautifulNumberListParam param)
+ {
+ param.page = param.page > 0 ? param.page : 1;
+ param.size = param.size > 0 ? param.size : 20;
+ var result = await _beautifulNumberService.GetList(param);
+ var dtos = result.list.Select(MapToDto).ToList();
+ return this.ToPageResult(dtos, (int)result.count);
+ }
+
+ [HttpGet("available")]
+ public async Task> GetAvailableList([FromQuery] BeautifulNumberListParam param)
+ {
+ param.page = param.page > 0 ? param.page : 1;
+ param.size = param.size > 0 ? param.size : 20;
+ param.status = (int)BeautifulNumberStatus.Available;
+ var result = await _beautifulNumberService.GetList(param);
+ var dtos = result.list.Select(MapToDto).ToList();
+ return this.ToPageResult(dtos, (int)result.count);
+ }
+
+ [HttpGet("check/{number}")]
+ public async Task> CheckNumber(string number)
+ {
+ var result = await _beautifulNumberService.CheckNumber(number);
+ return this.ToResult(MapToDto(result));
+ }
+
+ private static BeautifulNumberDto MapToDto(BeautifulNumberConfig entity)
+ {
+ if (entity == null) return new BeautifulNumberDto();
+ return new BeautifulNumberDto
+ {
+ Id = entity.Id ?? "",
+ Number = entity.Number ?? "",
+ RuleName = entity.RuleName ?? "",
+ Level = entity.Level ?? "",
+ Price = entity.Price,
+ Status = (int)entity.Status,
+ SaleTime = entity.SaleTime,
+ BuyerUserId = entity.BuyerUserId ?? "",
+ UserId = entity.UserId ?? "",
+ Remark = entity.Remark ?? "",
+ CreateTime = entity.CreateTime
+ };
+ }
+
+ [HttpPost("delete")]
+ public async Task> Delete([FromBody] DeleteBeautifulNumberParam param)
+ {
+ var result = await _beautifulNumberService.Delete(param.Id);
+ if (result.isok)
+ return this.ToResult(result.msg);
+ else
+ return this.ToFailResult(result.msg);
+ }
+
+ [HttpPost("set-normal")]
+ public async Task> SetToNormal([FromBody] DeleteBeautifulNumberParam param)
+ {
+ var result = await _beautifulNumberService.SetToNormal(param.Id);
+ if (result.isok)
+ return this.ToResult(result.msg);
+ else
+ return this.ToFailResult(result.msg);
+ }
+
+ [HttpPost("freeze")]
+ public async Task> Freeze([FromBody] DeleteBeautifulNumberParam param)
+ {
+ var result = await _beautifulNumberService.Freeze(param.Id);
+ if (result.isok)
+ return this.ToResult(result.msg);
+ else
+ return this.ToFailResult(result.msg);
+ }
+
+ [HttpPost("unfreeze")]
+ public async Task> Unfreeze([FromBody] DeleteBeautifulNumberParam param)
+ {
+ var result = await _beautifulNumberService.Unfreeze(param.Id);
+ if (result.isok)
+ return this.ToResult(result.msg);
+ else
+ return this.ToFailResult(result.msg);
+ }
+
+ [HttpPost("update-price")]
+ public async Task> UpdatePrice([FromBody] UpdateBeautifulNumberPriceParam param)
+ {
+ var result = await _beautifulNumberService.UpdatePrice(param.Id, param.Price);
+ if (result.isok)
+ return this.ToResult(result.msg);
+ else
+ return this.ToFailResult(result.msg);
+ }
+
+ [HttpPost("update-number")]
+ public async Task> UpdateNumber([FromBody] UpdateBeautifulNumberParam param)
+ {
+ var result = await _beautifulNumberService.UpdateNumber(param.Id, param.Level, param.Price);
+ if (result.isok)
+ return this.ToResult(result.msg);
+ else
+ return this.ToFailResult(result.msg);
+ }
+
+ [HttpGet("rules")]
+ public async Task>> GetRules()
+ {
+ var result = await _beautifulNumberService.GetRules();
+ var dtos = result.Select(MapRuleToDto).ToList();
+ return this.ToResult(dtos);
+ }
+
+ private static BeautifulNumberRuleDto MapRuleToDto(BeautifulNumberRule entity)
+ {
+ if (entity == null) return new BeautifulNumberRuleDto();
+ return new BeautifulNumberRuleDto
+ {
+ Id = entity.Id ?? "",
+ RuleName = entity.RuleName ?? "",
+ Level = entity.Level ?? "",
+ Price = entity.Price,
+ Sort = entity.Sort,
+ IsEnable = entity.IsEnable
+ };
+ }
+
+ [HttpPost("update-rules")]
+ public async Task> UpdateRules([FromBody] List paramList)
+ {
+ var rules = new List();
+ foreach (var param in paramList)
+ {
+ rules.Add(new BeautifulNumberRule
+ {
+ Id = param.Id,
+ RuleName = param.RuleName,
+ Level = param.Level,
+ Price = param.Price,
+ Sort = param.Sort,
+ IsEnable = param.IsEnable
+ });
+ }
+ var result = await _beautifulNumberService.UpdateRules(rules);
+ if (result.isok)
+ return this.ToResult(result.msg);
+ else
+ return this.ToFailResult(result.msg);
+ }
+
+ [HttpPost("init-rules")]
+ public async Task> InitRules()
+ {
+ var result = await _beautifulNumberService.InitRules();
+ if (result.isok)
+ return this.ToResult(result.msg);
+ else
+ return this.ToFailResult(result.msg);
+ }
+
+ [HttpPost("add-rule")]
+ public async Task> AddRule([FromBody] BeautifulNumberRule rule)
+ {
+ var result = await _beautifulNumberService.AddRule(rule);
+ if (result.isok)
+ return this.ToResult(result.msg);
+ else
+ return this.ToFailResult(result.msg);
+ }
+
+ [HttpPost("update-rule")]
+ public async Task> UpdateRule([FromBody] BeautifulNumberRule rule)
+ {
+ var result = await _beautifulNumberService.UpdateRule(rule);
+ if (result.isok)
+ return this.ToResult(result.msg);
+ else
+ return this.ToFailResult(result.msg);
+ }
+
+ [HttpPost("delete-rule")]
+ public async Task> DeleteRule([FromBody] DeleteBeautifulNumberRuleParam param)
+ {
+ var result = await _beautifulNumberService.DeleteRule(param.Id);
+ if (result.isok)
+ return this.ToResult(result.msg);
+ else
+ return this.ToFailResult(result.msg);
+ }
+
+ [HttpPost("give-to-user")]
+ public async Task> GiveToUser([FromBody] GiveBeautifulNumberParam param)
+ {
+ var result = await _beautifulNumberService.GiveToUser(param.Number, param.UserId);
+ if (result.isok)
+ return this.ToResult(result.msg);
+ else
+ return this.ToFailResult(result.msg);
+ }
+
+ [HttpPost("init-numbers")]
+ public async Task> InitNumbers()
+ {
+ var result = await _beautifulNumberService.InitBeautifulNumbers();
+ if (result.isok)
+ return this.ToResult(result.msg);
+ else
+ return this.ToFailResult(result.msg);
+ }
+
+ [HttpPost("add-number")]
+ public async Task> AddNumber([FromBody] AddBeautifulNumberParam param)
+ {
+ var result = await _beautifulNumberService.AddNumber(param.Number, param.Level, param.Price);
+ if (result.isok)
+ return this.ToResult(result.msg);
+ else
+ return this.ToFailResult(result.msg);
+ }
+
+ [HttpPost("batch-add-numbers")]
+ public async Task> BatchAddNumbers([FromBody] BatchAddBeautifulNumbersParam param)
+ {
+ var result = await _beautifulNumberService.BatchAddNumbers(param.Numbers, param.Level, param.Price);
+ return this.ToResult(new BatchAddResult { SuccessCount = result.successCount, FailCount = result.failCount, Message = result.msg });
+ }
+ }
+
+ public class BatchAddResult
+ {
+ public int SuccessCount { get; set; }
+ public int FailCount { get; set; }
+ public string Message { get; set; }
+ }
+
+ public class BeautifulNumberDto
+ {
+ public string Id { get; set; }
+ public string Number { get; set; }
+ public string RuleName { get; set; }
+ public string Level { get; set; }
+ public long Price { get; set; }
+ public int Status { get; set; }
+ public long SaleTime { get; set; }
+ public string BuyerUserId { get; set; }
+ public string UserId { get; set; }
+ public string Remark { get; set; }
+ public long CreateTime { get; set; }
+ }
+
+ public class BeautifulNumberRuleDto
+ {
+ public string Id { get; set; }
+ public string RuleName { get; set; }
+ public string Level { get; set; }
+ public long Price { get; set; }
+ public int Sort { get; set; }
+ public bool IsEnable { get; set; }
+ }
+
+ public class BatchAddBeautifulNumbersParam
+ {
+ public List Numbers { get; set; }
+
+ public string Level { get; set; }
+
+ public long Price { get; set; }
+
+
+
+ }
+
+ public class GiveBeautifulNumberParam
+ {
+ public string Number { get; set; }
+
+ public string UserId { get; set; }
+ }
+
+ public class AddBeautifulNumberParam
+ {
+ public string Number { get; set; }
+
+ public string Level { get; set; }
+
+ public long Price { get; set; }
+
+ public int Sort { get; set; }
+
+ public string Remark { get; set; }
+ }
+ public class UpdateBeautifulNumberParam
+ {
+ public string Id { get; set; }
+
+ public string Level { get; set; }
+
+ public long Price { get; set; }
+
+ }
+ public class DeleteBeautifulNumberParam
+ {
+ public string Id { get; set; }
+ }
+
+ public class UpdateBeautifulNumberPriceParam
+ {
+ public string Id { get; set; }
+
+ public long Price { get; set; }
+ }
+
+
+
+ public class UpdateBeautifulNumberRuleParam
+ {
+ public string Id { get; set; }
+
+
+
+ public string RuleName { get; set; }
+
+
+
+ public string Level { get; set; }
+
+ public long Price { get; set; }
+
+ public int Sort { get; set; }
+
+ public bool IsEnable { get; set; }
+ }
+
+ public class AddBeautifulNumberRuleParam
+ {
+ public string RuleName { get; set; }
+ public string Level { get; set; }
+ public long Price { get; set; }
+ public int Sort { get; set; }
+ public bool IsEnable { get; set; }
+ }
+
+ public class UpdateSingleBeautifulNumberRuleParam
+ {
+ public string Id { get; set; }
+ public string RuleName { get; set; }
+ public string Level { get; set; }
+ public long Price { get; set; }
+ public int Sort { get; set; }
+ public bool IsEnable { get; set; }
+ }
+
+ public class DeleteBeautifulNumberRuleParam
+ {
+ public string Id { get; set; }
+ }
+}
diff --git a/AdminSrc/AdminApi/Controllers/ImageUploadController.cs b/AdminSrc/AdminApi/Controllers/ImageUploadController.cs
new file mode 100644
index 0000000..324426c
--- /dev/null
+++ b/AdminSrc/AdminApi/Controllers/ImageUploadController.cs
@@ -0,0 +1,64 @@
+using AdminService.ImageUpload;
+using Common;
+using CommonInfrastructure;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+
+namespace AdminApi.Controllers
+{
+ [Route("adminapi/[controller]")]
+ [ApiController]
+ public class ImageUploadController : BaseController
+ {
+ private readonly IImageUploadService _imageUploadService;
+
+ public ImageUploadController(IImageUploadService imageUploadService)
+ {
+ _imageUploadService = imageUploadService;
+ }
+
+ [HttpPost("upload")]
+ public async Task UploadFile(IFormFile file, [FromQuery] string? name = null)
+ {
+ if (string.IsNullOrWhiteSpace(name))
+ name = Request.Form["name"].FirstOrDefault();
+
+ var result = await _imageUploadService.UploadImageAsync(file, name);
+ return result.isOk
+ ? Json(this.ToResult(new { path = result.path, fileName = result.fileName }))
+ : Json(this.ToFailResult