From d3aff3ae772255df0da68add0c76df733a741374 Mon Sep 17 00:00:00 2001 From: youngq Date: Fri, 9 Aug 2024 16:24:26 +0800 Subject: [PATCH] =?UTF-8?q?1=E4=BC=98=E5=8C=96=E9=87=8D=E5=90=AF=E5=88=A0?= =?UTF-8?q?=E9=99=A4redis=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- WGShare.API/Controllers/AuthController.cs | 11 +- .../Controllers/Backend/UserController.cs | 95 ++++++- .../Controllers/Basic/BasicController.cs | 30 +++ .../Controllers/Frontend/RoomController.cs | 235 ++++++++++-------- WGShare.API/Controllers/PublicController.cs | 5 +- WGShare.API/Helpers/EnumExtensions.cs | 14 ++ WGShare.API/Helpers/OssHelper.cs | 25 +- WGShare.API/Helpers/UserShareIdHelper.cs | 10 +- WGShare.API/Hubs/IMessageClient.cs | 59 +++-- WGShare.API/Hubs/SessionManageHub.cs | 68 +++-- WGShare.API/Program.cs | 10 +- WGShare.API/WGShare.API.csproj | 1 - WGShare.API/WGShare.API.xml | 102 ++++++-- WGShare.Domain/Constant/RedisKeyConstant.cs | 39 +-- WGShare.Domain/DTOs/User/UserExcelInputDto.cs | 37 +++ WGShare.Domain/DTOs/User/UserOutputDTO.cs | 17 +- WGShare.Domain/Entities/ChannelUserInfo.cs | 31 +-- WGShare.Domain/Enums/RoleEnums.cs | 21 ++ WGShare.Domain/WGShare.Domain.csproj | 1 + 19 files changed, 561 insertions(+), 250 deletions(-) create mode 100644 WGShare.API/Helpers/EnumExtensions.cs create mode 100644 WGShare.Domain/DTOs/User/UserExcelInputDto.cs create mode 100644 WGShare.Domain/Enums/RoleEnums.cs diff --git a/WGShare.API/Controllers/AuthController.cs b/WGShare.API/Controllers/AuthController.cs index fc303ec..d65cde2 100644 --- a/WGShare.API/Controllers/AuthController.cs +++ b/WGShare.API/Controllers/AuthController.cs @@ -7,6 +7,7 @@ using System.Configuration; using System.Security.Claims; using WGShare.API.Controllers.Basic; using WGShare.API.Helpers; +using WGShare.Domain.Constant; using WGShare.Domain.DTOs.Login; using WGShare.Domain.Entities; using WGShare.Domain.FriendlyException; @@ -84,7 +85,7 @@ namespace WGShare.API.Controllers btnAutn.Add(new Claim("ssid", user.ScreenShareId)); var refreshToken = Guid.NewGuid().ToString(); - RedisHelper.Instance.Set($@"refresh:{refreshToken}", user, TimeSpan.FromDays(30).TotalSeconds.ToInt32()); + RedisHelper.Instance.Set(RedisKeyConstant.Data.GetRefreshTokenKey(refreshToken), user, TimeSpan.FromDays(30).TotalSeconds.ToInt32()); return Ok(new { @@ -109,7 +110,7 @@ namespace WGShare.API.Controllers [HttpPost("refresh"), AllowAnonymous] public async Task Refresh([FromQuery] string refreshToken) { - var user = RedisHelper.Instance.Get($@"refresh:{refreshToken}"); + var user = RedisHelper.Instance.Get(RedisKeyConstant.Data.GetRefreshTokenKey(refreshToken)); if (user == null || string.IsNullOrWhiteSpace(user.Id)) { throw new FriendlyInternalException("登录已失效,请重新登录", null, 1403); @@ -125,8 +126,8 @@ namespace WGShare.API.Controllers var refreshTokenNew = Guid.NewGuid().ToString(); - RedisHelper.Instance.Del($@"refresh:{refreshToken}"); - RedisHelper.Instance.Set($@"refresh:{refreshTokenNew}", user, TimeSpan.FromDays(30).TotalSeconds.ToInt32()); + RedisHelper.Instance.Del(RedisKeyConstant.Data.GetRefreshTokenKey(refreshToken)); + RedisHelper.Instance.Set(RedisKeyConstant.Data.GetRefreshTokenKey(refreshTokenNew), user, TimeSpan.FromDays(30).TotalSeconds.ToInt32()); return Ok(new { @@ -147,7 +148,7 @@ namespace WGShare.API.Controllers /// 匿名登录,直接进入会议室 /// /// - [HttpPost("anon-login"),Obsolete] + [HttpPost("anon-login"), Obsolete] public async Task Login([FromBody] AnonymousLoginDTO loginDTO) { diff --git a/WGShare.API/Controllers/Backend/UserController.cs b/WGShare.API/Controllers/Backend/UserController.cs index ebfcbdd..903081e 100644 --- a/WGShare.API/Controllers/Backend/UserController.cs +++ b/WGShare.API/Controllers/Backend/UserController.cs @@ -1,8 +1,14 @@ using Mapster; +using Masuit.Tools; +using Masuit.Tools.Security; using Microsoft.AspNetCore.Components.Forms; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.FileSystemGlobbing.Internal; using MiniExcelLibs; +using Newtonsoft.Json.Linq; using SqlSugar; +using System; +using System.Text.RegularExpressions; using WGShare.API.Controllers.Basic; using WGShare.API.Helpers; using WGShare.Domain.DTOs.User; @@ -18,10 +24,13 @@ namespace WGShare.API.Controllers.Backend public class UserController : BasicController { private readonly ISqlSugarClient _sqlSugar; + private readonly OssHelper _ossHelper; - public UserController(ISqlSugarClient sqlSugar) + public UserController(ISqlSugarClient sqlSugar, + OssHelper ossHelper) { _sqlSugar = sqlSugar; + this._ossHelper = ossHelper; } @@ -58,7 +67,7 @@ namespace WGShare.API.Controllers.Backend [HttpPost] public async Task Add([FromBody] UserInputDTO userInput) { - var entity = userInput.Adapt(); + var entity = userInput.Adapt(); entity.ScreenShareId = UserShareIdHelper.GenerateUnique8DigitNumber(); if (await _sqlSugar.Queryable().AnyAsync(x => x.Account == entity.Account)) @@ -119,16 +128,76 @@ namespace WGShare.API.Controllers.Backend await _sqlSugar.Insertable(entity).ExecuteCommandAsync(); } - ///// - ///// Excel 导入用户 - ///// - ///// - ///// - //[HttpPost("import")] - //public async Task Import([FromForm] IFormFile file) - //{ - // using var stream = file.OpenReadStream(); - // MiniExcel.Query(stream); - //} + /// + /// Excel 导入用户 + /// + /// + /// + [HttpPost("import")] + public async Task Import([FromForm] IFormFile file, [FromForm] string tenantId) + { + using var stream = file.OpenReadStream(); + var rows = stream.Query().ToList(); + if (rows.IsNullOrEmpty()) + { + throw Oops.Oh("无有效数据,请检查文件数据!"); + } + + var accounts = rows.Select(x => x.Account.Trim()); + var repeatAccount = accounts.GroupBy(x => x).Where(x => x.Count() > 1).Select(x => x.Key).ToHashSet(); + + // 去除重复账号 + var distinctAccount = accounts.Except(repeatAccount); + + // 数据库重复账号检查 + var existsAccount = await _sqlSugar.Queryable() + .Where(x => distinctAccount.Contains(x.Account)) + .Select(x => x.Account) + .ToListAsync(); + if (!existsAccount.IsNullOrEmpty()) + { + repeatAccount.UnionWith(existsAccount); + } + + if (!repeatAccount.IsNullOrEmpty()) + { + // 重复账号,返回结果Excel + foreach (var row in rows) + { + if (repeatAccount.Contains(row.Account)) + { + row.ImportResult = "账号重复"; + } + else + { + row.ImportResult = "可导入"; + } + } + //FileStreamResult fileStream = null; + using var memoryStream = new MemoryStream(); + + memoryStream.SaveAs(rows); + memoryStream.Seek(0, SeekOrigin.Begin); + + var fileName = $@"excel/{Regex.Replace(file.FileName, @"\.(xlsx|xls)$", "", RegexOptions.IgnoreCase)}_验证不通过_{DateTime.UtcNow.Ticks}.xlsx"; + _ossHelper.UploadWithExpireTime(fileName, memoryStream, 1); + var fileUrl = _ossHelper.GetAccessFileUrl(fileName, 1); + + return Ok((isSuccess: false, url: fileUrl)); + } + + var users = rows.Adapt>(); + users.ForEach(x => + { + x.Pwd = x.Pwd.MDString(); + x.ScreenShareId = UserShareIdHelper.GenerateUnique8DigitNumber(); + x.TenantId = tenantId; + x.RoleId = x.RoleId == "管理员" ? "1" : "2"; + }); + + await _sqlSugar.Insertable(users).ExecuteCommandAsync(); + + return Ok((isSuccess: true, url: "导入成功")); + } } } diff --git a/WGShare.API/Controllers/Basic/BasicController.cs b/WGShare.API/Controllers/Basic/BasicController.cs index 8746ab7..e3e0df4 100644 --- a/WGShare.API/Controllers/Basic/BasicController.cs +++ b/WGShare.API/Controllers/Basic/BasicController.cs @@ -1,6 +1,8 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using SqlSugar.Extensions; +using WGShare.API.Helpers; +using WGShare.Domain.Constant; using WGShare.Domain.FriendlyException; namespace WGShare.API.Controllers.Basic @@ -74,5 +76,33 @@ namespace WGShare.API.Controllers.Basic return tenant; } } + + public string ScreenShareId + { + get + { + var ssId = HttpContext.User.Claims.FirstOrDefault(x => x.Type == "ssid").Value; + if (string.IsNullOrWhiteSpace(ssId)) + { + throw Oops.Oh("用户信息有误,请重新登录"); + } + return ssId; + } + } + + public string ConnectionId + { + get + { + var connectid = RedisHelper.Instance.HGet(RedisKeyConstant.SessionManage.GetOnlineUserKey(TenantId), UId); + if (string.IsNullOrWhiteSpace(connectid)) + { + throw Oops.Oh("用户信息有误,请重新登录"); + } + return connectid; + } + } + + } } diff --git a/WGShare.API/Controllers/Frontend/RoomController.cs b/WGShare.API/Controllers/Frontend/RoomController.cs index f28216b..5e70848 100644 --- a/WGShare.API/Controllers/Frontend/RoomController.cs +++ b/WGShare.API/Controllers/Frontend/RoomController.cs @@ -15,8 +15,10 @@ using WGShare.API.Hubs; using WGShare.Domain.Constant; using WGShare.Domain.DTOs.File; using WGShare.Domain.DTOs.Room; +using WGShare.Domain.DTOs.Tenant; using WGShare.Domain.DTOs.User; using WGShare.Domain.Entities; +using WGShare.Domain.Enums; using WGShare.Domain.FriendlyException; using WGShare.Domain.GeneralModel; using Yitter.IdGenerator; @@ -52,33 +54,27 @@ namespace WGShare.API.Controllers.Frontend this._logger = logger; } - ///// - ///// 获取会议室管理员 - ///// - ///// - //[HttpGet("manager")] - //public async Task> GetRoomManager([FromQuery] string roomId) - //{ - // return await _sqlSugar.Queryable() - // .Where(x => x.RoomId == roomId) - // .Select(x => x.UserId) - // .ToListAsync(); - //} /// /// 设置房间管理员 /// /// [HttpPost("manager")] - public async Task SetRoomManager([FromQuery] string roomId, [FromBody] List userIds) + public async Task SetRoomManager([FromQuery] string roomId, [FromQuery] string roomNum, [FromBody] string userId) { - var entities = userIds.ConvertAll(x => new RoomManager + var entities = new RoomManager { RoomId = roomId, - UserId = x - }); + UserId = userId + }; - return await _sqlSugar.Insertable(entities).ExecuteCommandAsync() > 0; + var user = RedisHelper.Instance.HGet(RedisKeyConstant.SessionManage.GetChannelUserKey(TenantId, roomNum), userId); + user.IsRoomManager = true; + + await _sqlSugar.Insertable(entities).ExecuteCommandAsync(); + RedisHelper.Instance.HSet(RedisKeyConstant.SessionManage.GetChannelUserKey(TenantId, roomNum), userId, user); + + await _hubContext.Clients.Group(roomNum).ManagerRefresh(user); } /// @@ -86,54 +82,33 @@ namespace WGShare.API.Controllers.Frontend /// /// [HttpDelete("manager")] - public async Task DelRoomManager([FromQuery] string roomId, [FromBody] List userIds) + public async Task DelRoomManager([FromQuery] string roomId, [FromQuery] string roomNum, [FromBody] string userId) { - return await _sqlSugar.Deleteable() - .Where(x => x.RoomId == roomId && userIds.Contains(x.UserId)) - .ExecuteCommandAsync() > 0; + await _sqlSugar.Deleteable() + .Where(x => x.RoomId == roomId && x.UserId == userId) + .ExecuteCommandAsync(); + + var user = RedisHelper.Instance.HGet(RedisKeyConstant.SessionManage.GetChannelUserKey(TenantId, roomNum), userId); + user.IsRoomManager = false; + RedisHelper.Instance.HSet(RedisKeyConstant.SessionManage.GetChannelUserKey(TenantId, roomNum), userId, user); + + await _hubContext.Clients.Group(roomNum).ManagerRefresh(user); } /// - /// 查询用户信息 + /// 查询房间中所有用户信息 /// /// [HttpGet("user")] - public async Task> GetUsers([FromQuery] string roomNum) + public async Task> GetUsers([FromQuery] string roomNum) { var channelUsers = RedisHelper.Instance.HVals(RedisKeyConstant.SessionManage.GetChannelUserKey(TenantId, roomNum)); if (channelUsers.IsNullOrEmpty()) { - return new List(); + return new List(); } - var uids = channelUsers.Select(x => x.UID); - var users = await _sqlSugar.Queryable() - .Where(x => uids.Contains(x.Id)) - .ToListAsync(); - - var managerIds = await _sqlSugar.Queryable() - .InnerJoin((rm, r) => r.Id == rm.RoomId) - .Where((rm, r) => r.RoomNum == roomNum) - .Select((rm, r) => rm.UserId) - .ToListAsync(); - - var result = users.Adapt>(); - - result.ForEach(x => - { - x.IsManager = (x.RoleId == "1" || managerIds.Contains(x.Id)); - var info = channelUsers.FirstOrDefault(q => q.UID == x.Id); - if (info != null) - { - x.EnableMicr = info.EnableMicr; - x.EnableCamera = info.EnableCamera; - } - }); - - return result; - - - + return channelUsers; #region 暂时不用声网获取用户列表 //var data = await _agoraHelper.GetChannelUserList(roomNum); @@ -244,33 +219,39 @@ namespace WGShare.API.Controllers.Frontend } /// - /// 开闭麦 + /// 全部人开闭麦 + /// + /// + [HttpGet("mute-all")] + public async Task MuteAll([FromQuery] string roomNum, [FromQuery] bool enableMicr) + { + // 全员静音 + var allUsers = RedisHelper.Instance.HGetAll(RedisKeyConstant.SessionManage.GetChannelUserKey(TenantId, roomNum)); + if (!allUsers.Any()) + { + return; + } + allUsers.ForEach(x => + { + // 排除自己 + if (x.Key != UId) + { + x.Value.EnableMicr = enableMicr; + } + }); + RedisHelper.Instance.HMSet(RedisKeyConstant.SessionManage.GetChannelUserKey(TenantId, roomNum), allUsers); + // 通知其他人 + await _hubContext.Clients.GroupExcept(roomNum, allUsers[UId].ConnectId).OperAllMicr(enableMicr); + } + + + /// + /// 单用户开闭麦 /// /// [HttpGet("oper-micr")] - public async Task Mute([FromQuery] string roomNum, [FromQuery] bool enableMicr, [FromQuery] string? uid, [FromQuery] bool? isAll = false) + public async Task Mute([FromQuery] string roomNum, [FromQuery] bool enableMicr, [FromQuery] string uid) { - if (isAll.HasValue && isAll.Value) - { - // 全员静音 - var allUsers = RedisHelper.Instance.HGetAll(RedisKeyConstant.SessionManage.GetChannelUserKey(TenantId, roomNum)); - if (!allUsers.Any()) - { - return; - } - allUsers.ForEach(x => - { - if (x.Key != uid) - { - x.Value.EnableMicr = enableMicr; - } - }); - RedisHelper.Instance.HMSet(RedisKeyConstant.SessionManage.GetChannelUserKey(TenantId, roomNum), allUsers); - - await _hubContext.Clients.GroupExcept(roomNum, allUsers[uid].ConnectId).OperMicr(enableMicr); - await _hubContext.Clients.Group(roomNum).RefreshUserList(); - return; - } var userInfo = RedisHelper.Instance.HGet(RedisKeyConstant.SessionManage.GetChannelUserKey(TenantId, roomNum), uid); if (userInfo == null || string.IsNullOrWhiteSpace(userInfo.ConnectId)) @@ -282,8 +263,8 @@ namespace WGShare.API.Controllers.Frontend userInfo.EnableMicr = enableMicr; RedisHelper.Instance.HSet(RedisKeyConstant.SessionManage.GetChannelUserKey(TenantId, roomNum), uid, userInfo); - await _hubContext.Clients.Clients(userInfo.ConnectId).OperMicr(enableMicr); - await _hubContext.Clients.Group(roomNum).RefreshUserList(); + // 通知所有人该用户麦克风状态 + await _hubContext.Clients.Group(roomNum).OperMicr(userInfo); } /// @@ -291,33 +272,8 @@ namespace WGShare.API.Controllers.Frontend /// /// [HttpGet("oper-camera")] - public async Task CloseCamera([FromQuery] string roomNum, [FromQuery] bool enableCamera, [FromQuery] string? uid, [FromQuery] bool? isAll = false) + public async Task CloseCamera([FromQuery] string roomNum, [FromQuery] bool enableCamera, [FromQuery] string uid) { - if (isAll.HasValue && isAll.Value) - { - - var allUsers = RedisHelper.Instance.HGetAll(RedisKeyConstant.SessionManage.GetChannelUserKey(TenantId, roomNum)); - if (!allUsers.Any()) - { - return; - } - allUsers.ForEach(x => - { - if (x.Key != uid) - { - x.Value.EnableCamera = enableCamera; - } - }); - - //allUsers.ForEach(x => x.Value.EnableCamera = enableCamera); - RedisHelper.Instance.HMSet(RedisKeyConstant.SessionManage.GetChannelUserKey(TenantId, roomNum), allUsers); - - // 全员开关闭摄像头 - await _hubContext.Clients.GroupExcept(roomNum, allUsers[uid].ConnectId).OperCamera(enableCamera); - await _hubContext.Clients.Group(roomNum).RefreshUserList(); - return; - } - var userInfo = RedisHelper.Instance.HGet(RedisKeyConstant.SessionManage.GetChannelUserKey(TenantId, roomNum), uid); if (userInfo == null || string.IsNullOrWhiteSpace(userInfo.ConnectId)) { @@ -328,8 +284,8 @@ namespace WGShare.API.Controllers.Frontend userInfo.EnableCamera = enableCamera; RedisHelper.Instance.HSet(RedisKeyConstant.SessionManage.GetChannelUserKey(TenantId, roomNum), uid, userInfo); - await _hubContext.Clients.Clients(userInfo.ConnectId).OperCamera(enableCamera); - await _hubContext.Clients.Group(roomNum).RefreshUserList(); + // 通知所有人该用户麦克风状态 + await _hubContext.Clients.Group(roomNum).OperCamera(userInfo); } /// @@ -358,7 +314,7 @@ namespace WGShare.API.Controllers.Frontend } // 获取全员观看用户 - var showUserId = RedisHelper.Instance.HGet(RedisKeyConstant.RoomManager.GetChannelShowUserKey(TenantId), roomNum); + var showUserId = RedisHelper.Instance.HGet(RedisKeyConstant.SessionManage.GetChannelShowUserKey(TenantId), roomNum); if (!string.IsNullOrWhiteSpace(showUserId)) { return showUserId; @@ -372,10 +328,10 @@ namespace WGShare.API.Controllers.Frontend /// /// [HttpPost("show-user")] - public async Task SetShowUser([FromQuery] string roomNum, [FromQuery] string uid) + public async Task SetShowUser([FromQuery] string roomNum, [FromQuery] string? uid) { // 设置房间全员观看用户 - RedisHelper.Instance.HSet(RedisKeyConstant.RoomManager.GetChannelShowUserKey(TenantId), roomNum, uid); + RedisHelper.Instance.HSet(RedisKeyConstant.SessionManage.GetChannelShowUserKey(TenantId), roomNum, uid); var connectId = RedisHelper.Instance.HGet(RedisKeyConstant.SessionManage.GetOnlineUserKey(TenantId), UId); if (!string.IsNullOrWhiteSpace(connectId)) @@ -384,6 +340,69 @@ namespace WGShare.API.Controllers.Frontend } } + /// + /// 加入频道 + /// + /// + /// + /// + /// + [HttpGet("join")] + public async Task JoinChannel([FromQuery] string roomNum, [FromQuery] bool enableMicr = false, [FromQuery] bool enableCamera = false) + { + var isManager = await _sqlSugar.Queryable() + .InnerJoin((rm, r) => r.Id == rm.RoomId) + .Where((rm, r) => r.RoomNum == roomNum && rm.UserId == UId) + .AnyAsync(); + + var userInfo = new ChannelUserInfo + { + UID = UId, + UserName = UserName, + EnableCamera = enableCamera, + EnableMicr = enableMicr, + ConnectId = ConnectionId, + Account = Account, + ScreenShareId = ScreenShareId, + RoleId = RoleId, + RoleName = ((RoleEnums)RoleId.ToInt32()).GetDescription(), + IsRoomManager = isManager + }; + using (var pipe = RedisHelper.Instance.StartPipe()) + { + pipe.HSet(RedisKeyConstant.SessionManage.GetChannelUserKey(TenantId, roomNum), UId, userInfo.ToJsonString()); + pipe.HSet(RedisKeyConstant.SessionManage.GetUserJoinChannelKey(UId), roomNum, 1); + pipe.EndPipe(); + } + await _hubContext.Groups.AddToGroupAsync(ConnectionId, roomNum); + await _hubContext.Clients.GroupExcept(roomNum, ConnectionId).UserJoined(userInfo); + } + + /// + /// 离开频道 + /// + /// + /// + [HttpGet("leave")] + public async Task LevelChannel([FromQuery] string roomNum) + { + using (var pipe = RedisHelper.Instance.StartPipe()) + { + var script = $@"local value = redis.call('HGET', KEYS[1], ARGV[1]) + if value == ARGV[2] or value == ARGV[3] then + return redis.call('HDEL', KEYS[1], ARGV[1]) + else return -1 end"; + // 执行 eval 命令 + pipe.Eval(script, [RedisKeyConstant.SessionManage.GetChannelShowUserKey(TenantId)], roomNum, UId, ScreenShareId); + pipe.HDel(RedisKeyConstant.SessionManage.GetChannelUserKey(TenantId, roomNum), UId); + pipe.HDel(RedisKeyConstant.SessionManage.GetUserJoinChannelKey(UId), roomNum); + pipe.EndPipe(); + } + + await _hubContext.Clients.GroupExcept(roomNum, ConnectionId).UserLeave(UId); + await _hubContext.Groups.RemoveFromGroupAsync(ConnectionId, roomNum); + } + #region 文件分享 /// /// 分享上传文件 diff --git a/WGShare.API/Controllers/PublicController.cs b/WGShare.API/Controllers/PublicController.cs index 2822b89..7cdeb23 100644 --- a/WGShare.API/Controllers/PublicController.cs +++ b/WGShare.API/Controllers/PublicController.cs @@ -1,12 +1,15 @@ using Masuit.Tools; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using MiniExcelLibs; using SqlSugar; using System.Configuration; using System.Security.Claims; +using System.Text.RegularExpressions; using WGShare.API.Controllers.Basic; using WGShare.API.Helpers; using WGShare.Domain.DTOs.Login; +using WGShare.Domain.DTOs.User; using WGShare.Domain.Entities; using WGShare.Domain.FriendlyException; @@ -34,6 +37,6 @@ namespace WGShare.API.Controllers public async Task> GetDropDownList() { return await _sqlSugar.Queryable().Where(x => x.IsDelete == false).ToListAsync(); - } + } } } diff --git a/WGShare.API/Helpers/EnumExtensions.cs b/WGShare.API/Helpers/EnumExtensions.cs new file mode 100644 index 0000000..5e2dbd8 --- /dev/null +++ b/WGShare.API/Helpers/EnumExtensions.cs @@ -0,0 +1,14 @@ +using System.ComponentModel; + +namespace WGShare.API.Helpers +{ + public static class EnumExtensions + { + public static string GetDescription(this Enum val) + { + var field = val.GetType().GetField(val.ToString()); + var customAttribute = Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute)); + return customAttribute == null ? val.ToString() : ((DescriptionAttribute)customAttribute).Description; + } + } +} diff --git a/WGShare.API/Helpers/OssHelper.cs b/WGShare.API/Helpers/OssHelper.cs index b7f0f5e..88062f7 100644 --- a/WGShare.API/Helpers/OssHelper.cs +++ b/WGShare.API/Helpers/OssHelper.cs @@ -69,16 +69,16 @@ namespace WGShare.API.Helpers }; } - public string GetAccessFileUrl(string path) + public string GetAccessFileUrl(string path, int expireInMinutes = 1) { - if (string.IsNullOrWhiteSpace(path) ) + if (string.IsNullOrWhiteSpace(path)) { throw Oops.Oh("路径不能为空!"); } var urlRequest = new GeneratePresignedUriRequest(_bucketName, path) { - Expiration = DateTimeOffset.Now.AddMinutes(1).LocalDateTime + Expiration = DateTimeOffset.Now.AddMinutes(expireInMinutes).LocalDateTime }; return _ossClient.GeneratePresignedUri(urlRequest).ToString(); @@ -95,5 +95,24 @@ namespace WGShare.API.Helpers return result.HttpStatusCode == System.Net.HttpStatusCode.OK; } + + /// + /// 流上传文件 + /// + /// + /// + public void UploadWithExpireTime(string objectName, MemoryStream stream, int expireTimeInMinutes) + { + if (expireTimeInMinutes <= 0) + { + throw Oops.Oh("过期时间不能小于0!"); + } + _ossClient.PutObject(_bucketName, objectName, stream, new ObjectMetadata + { + + ExpirationTime = DateTimeOffset.Now.AddMinutes(expireTimeInMinutes).DateTime, + + }); + } } } diff --git a/WGShare.API/Helpers/UserShareIdHelper.cs b/WGShare.API/Helpers/UserShareIdHelper.cs index f36c585..496a398 100644 --- a/WGShare.API/Helpers/UserShareIdHelper.cs +++ b/WGShare.API/Helpers/UserShareIdHelper.cs @@ -1,4 +1,6 @@ -namespace WGShare.API.Helpers +using WGShare.Domain.Constant; + +namespace WGShare.API.Helpers { public class UserShareIdHelper { @@ -16,10 +18,10 @@ { long ticks = DateTime.UtcNow.Ticks; var randomPart = random.Next(1000, 10000).ToString(); // 随机生成4位数字 - uniqueNumber = randomPart + (ticks % 10000).ToString("D4"); // 拼接成8位数字 - } while (RedisHelper.Instance.SIsMember("screen_share_id", uniqueNumber)); + uniqueNumber = randomPart + (ticks % 10000).ToString("D5"); // 拼接成8位数字 + } while (RedisHelper.Instance.SIsMember(RedisKeyConstant.Data.GetScreenShareIdKey, uniqueNumber)); - RedisHelper.Instance.SAdd("screen_share_id", uniqueNumber); + RedisHelper.Instance.SAdd(RedisKeyConstant.Data.GetScreenShareIdKey, uniqueNumber); return uniqueNumber; } } diff --git a/WGShare.API/Hubs/IMessageClient.cs b/WGShare.API/Hubs/IMessageClient.cs index ab922bb..1e17721 100644 --- a/WGShare.API/Hubs/IMessageClient.cs +++ b/WGShare.API/Hubs/IMessageClient.cs @@ -1,4 +1,7 @@ -namespace WGShare.API.Hubs +using WGShare.Domain.DTOs.User; +using WGShare.Domain.Entities; + +namespace WGShare.API.Hubs { /// /// 客户端消息 @@ -30,20 +33,6 @@ /// Task ForceExitRoom(string roomNum); - /// - /// 用户开闭麦 - /// - /// - /// - Task OperMicr(bool enableMicr); - - /// - /// 用户开启关闭摄像头 - /// - /// - /// - Task OperCamera(bool enableCamera); - /// /// 刷新用户列表 /// @@ -68,5 +57,45 @@ /// /// Task ShowUser(); + + /// + /// 用户加入频道回调 + /// + /// + Task UserJoined(ChannelUserInfo user); + + /// + /// 用户退出频道回调 + /// + /// + Task UserLeave(string uid); + + /// + /// 所有用户开闭麦 + /// + /// + /// + Task OperAllMicr(bool enableMicr); + + /// + /// 用户关闭开启麦克风 + /// + /// + /// + Task OperMicr(ChannelUserInfo user); + + /// + /// 用户开启关闭摄像头 + /// + /// + /// + Task OperCamera(ChannelUserInfo user); + + /// + /// 管理员用户信息刷新 + /// + /// + /// + Task ManagerRefresh(ChannelUserInfo user); } } diff --git a/WGShare.API/Hubs/SessionManageHub.cs b/WGShare.API/Hubs/SessionManageHub.cs index 426a597..85b7c5b 100644 --- a/WGShare.API/Hubs/SessionManageHub.cs +++ b/WGShare.API/Hubs/SessionManageHub.cs @@ -7,6 +7,7 @@ using System.Text; using WGShare.API.Helpers; using WGShare.Domain.Constant; using WGShare.Domain.Entities; +using WGShare.Domain.Enums; namespace WGShare.API.Hubs { @@ -79,7 +80,7 @@ namespace WGShare.API.Hubs return redis.call('HDEL', KEYS[1], ARGV[1]) else return -1 end"; // 执行 eval 命令 - pipe.Eval(script, [RedisKeyConstant.RoomManager.GetChannelShowUserKey(tenant)], roomNum, uid, ssid); + pipe.Eval(script, [RedisKeyConstant.SessionManage.GetChannelShowUserKey(tenant)], roomNum, uid, ssid); }); } @@ -101,62 +102,79 @@ namespace WGShare.API.Hubs /// /// 是否关闭麦克风,默认是 /// 是否关闭摄像头,默认是 - [HubMethodName("joinChannel")] + [HubMethodName("joinChannel"), Obsolete("废弃,请使用Api接口 JoinChannel")] public async Task JoinChannel(string roomNum, bool enableMicr = true, bool enableCamera = true) { var tenant = Context.User?.Claims.FirstOrDefault(x => x.Type == "tenant")?.Value; var uid = Context.User?.Claims.FirstOrDefault(x => x.Type == "uid")?.Value; var account = Context.User?.Claims.FirstOrDefault(x => x.Type == "account")?.Value; var ssid = Context.User?.Claims.FirstOrDefault(x => x.Type == "ssid")?.Value; + var uname = Context.User?.Claims.FirstOrDefault(x => x.Type == "uname")?.Value; + var roleId = Context.User?.Claims.FirstOrDefault(x => x.Type == "role")?.Value; Console.WriteLine($"{DateTime.Now}加入频道 会议号:" + roomNum); Console.WriteLine($"{DateTime.Now}加入频道 account:" + account); Console.WriteLine($"{DateTime.Now}加入频道 tenant:" + tenant); - using (var pipe = RedisHelper.Instance.StartPipe()) - { - var userInfo = new ChannelUserInfo(uid, Context.ConnectionId, enableMicr, enableCamera, account, ssid); - pipe.HSet(RedisKeyConstant.SessionManage.GetChannelUserKey(tenant, roomNum), uid, userInfo.ToJsonString()); - //pipe.HIncrBy(RedisKeyConstant.SessionManage.GetChannelUserCountKey(tenant), roomNum, 1); - pipe.HSet(RedisKeyConstant.SessionManage.GetUserJoinChannelKey(uid), roomNum, 1); - pipe.EndPipe(); - } + //using (var pipe = RedisHelper.Instance.StartPipe()) + //{ + // var userInfo = new ChannelUserInfo(uid, Context.ConnectionId, enableMicr, enableCamera, account, ssid); + // pipe.HSet(RedisKeyConstant.SessionManage.GetChannelUserKey(tenant, roomNum), uid, userInfo.ToJsonString()); + // //pipe.HIncrBy(RedisKeyConstant.SessionManage.GetChannelUserCountKey(tenant), roomNum, 1); + // pipe.HSet(RedisKeyConstant.SessionManage.GetUserJoinChannelKey(uid), roomNum, 1); + // pipe.EndPipe(); + //} - await Groups.AddToGroupAsync(Context.ConnectionId, roomNum); + //await Groups.AddToGroupAsync(Context.ConnectionId, roomNum); + //await Clients.GroupExcept(roomNum, Context.ConnectionId).UserJoined(new Domain.DTOs.User.UserOutputDTO + //{ + // Id = uid, + // UserName = uname, + // Account = account, + // RoleId = roleId, + // RoleName = ((RoleEnums)roleId.ToInt32()).GetDescription(), + // EnableCamera = enableCamera, + // EnableMicr = enableMicr, + // ScreenShareId = ssid, + // IsOnline = true, + //}); } /// /// 离开频道 /// /// - [HubMethodName("levelChannel")] + [HubMethodName("levelChannel"), Obsolete("废弃,请使用Api接口 LevelChannel")] public async Task LevelChannel(string roomNum) { var tenant = Context.User?.Claims.FirstOrDefault(x => x.Type == "tenant")?.Value; var uid = Context.User?.Claims.FirstOrDefault(x => x.Type == "uid")?.Value; var account = Context.User?.Claims.FirstOrDefault(x => x.Type == "account")?.Value; var ssid = Context.User?.Claims.FirstOrDefault(x => x.Type == "ssid")?.Value; + var uname = Context.User?.Claims.FirstOrDefault(x => x.Type == "uname")?.Value; + var roleId = Context.User?.Claims.FirstOrDefault(x => x.Type == "role")?.Value; Console.WriteLine($" {DateTime.Now}离开频道 会议号:" + roomNum); Console.WriteLine($" {DateTime.Now}离开频道 account:" + account); Console.WriteLine($" {DateTime.Now}离开频道 tenant:" + tenant); - using (var pipe = RedisHelper.Instance.StartPipe()) - { - var script = $@"local value = redis.call('HGET', KEYS[1], ARGV[1]) - if value == ARGV[2] or value == ARGV[3] then - return redis.call('HDEL', KEYS[1], ARGV[1]) - else return -1 end"; - // 执行 eval 命令 - pipe.Eval(script, [RedisKeyConstant.RoomManager.GetChannelShowUserKey(tenant)], roomNum, uid, ssid); - pipe.HDel(RedisKeyConstant.SessionManage.GetChannelUserKey(tenant, roomNum), uid); - pipe.HDel(RedisKeyConstant.SessionManage.GetUserJoinChannelKey(uid), roomNum); - pipe.EndPipe(); - } + //using (var pipe = RedisHelper.Instance.StartPipe()) + //{ + // var script = $@"local value = redis.call('HGET', KEYS[1], ARGV[1]) + // if value == ARGV[2] or value == ARGV[3] then + // return redis.call('HDEL', KEYS[1], ARGV[1]) + // else return -1 end"; + // // 执行 eval 命令 + // pipe.Eval(script, [RedisKeyConstant.SessionManage.GetChannelShowUserKey(tenant)], roomNum, uid, ssid); + // pipe.HDel(RedisKeyConstant.SessionManage.GetChannelUserKey(tenant, roomNum), uid); + // pipe.HDel(RedisKeyConstant.SessionManage.GetUserJoinChannelKey(uid), roomNum); + // pipe.EndPipe(); + //} - await Groups.RemoveFromGroupAsync(Context.ConnectionId, roomNum); + //await Clients.GroupExcept(roomNum, Context.ConnectionId).UserLeave(uid); + //await Groups.RemoveFromGroupAsync(Context.ConnectionId, roomNum); } /// diff --git a/WGShare.API/Program.cs b/WGShare.API/Program.cs index 7ca1122..da08609 100644 --- a/WGShare.API/Program.cs +++ b/WGShare.API/Program.cs @@ -1,5 +1,6 @@ using Hangfire; using Masuit.Tools; +using Microsoft.AspNetCore.Http.Features; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using Newtonsoft.Json.Serialization; @@ -29,6 +30,13 @@ namespace WGShare.API }); ResetRedisKey(); + builder.Services.Configure(options => + { + options.ValueLengthLimit = int.MaxValue; + options.MultipartBodyLengthLimit = long.MaxValue; // If we are multipart + options.MemoryBufferThreshold = int.MaxValue; + }); + builder.Services.AddControllers(options => { // ȫ쳣ڴ д try catch @@ -122,7 +130,7 @@ namespace WGShare.API } while (nextCursor != 0); - var keysArr = keys.ConvertAll(x => x.Replace("wgshare:", "")).Where(x => !x.StartsWith("refresh")).Distinct().ToArray(); + var keysArr = keys.ConvertAll(x => x.Replace("wgshare:", "")).Where(x => x.StartsWith("SessionManage")).Distinct().ToArray(); if (!keysArr.IsNullOrEmpty()) { Console.WriteLine($@"ɾֵ{Environment.NewLine}{string.Join(Environment.NewLine, keysArr)}"); diff --git a/WGShare.API/WGShare.API.csproj b/WGShare.API/WGShare.API.csproj index 4ba7a54..cbe3796 100644 --- a/WGShare.API/WGShare.API.csproj +++ b/WGShare.API/WGShare.API.csproj @@ -15,7 +15,6 @@ - diff --git a/WGShare.API/WGShare.API.xml b/WGShare.API/WGShare.API.xml index 1166f47..a7bc114 100644 --- a/WGShare.API/WGShare.API.xml +++ b/WGShare.API/WGShare.API.xml @@ -60,6 +60,13 @@ + + + Excel 导入用户 + + + + 首页接口 @@ -90,13 +97,13 @@ 会议室接口 - + 设置房间管理员 - + 取消房间管理员 @@ -104,7 +111,7 @@ - 查询用户信息 + 查询房间中所有用户信息 @@ -138,13 +145,19 @@ - + - 开闭麦 + 全部人开闭麦 - + + + 单用户开闭麦 + + + + 开关闭摄像头 @@ -169,6 +182,22 @@ + + + 加入频道 + + + + + + + + + 离开频道 + + + + 分享上传文件 @@ -285,6 +314,13 @@ 过期时间 秒 + + + 流上传文件 + + + + redis静态访问类 @@ -344,20 +380,6 @@ 会议号 - - - 用户开闭麦 - - - - - - - 用户开启关闭摄像头 - - - - 刷新用户列表 @@ -383,6 +405,46 @@ + + + 用户加入频道回调 + + + + + + 用户退出频道回调 + + + + + + 所有用户开闭麦 + + + + + + + 用户关闭开启麦克风 + + + + + + + 用户开启关闭摄像头 + + + + + + + 管理员用户信息刷新 + + + + 加入频道 diff --git a/WGShare.Domain/Constant/RedisKeyConstant.cs b/WGShare.Domain/Constant/RedisKeyConstant.cs index 94e3843..78eae8d 100644 --- a/WGShare.Domain/Constant/RedisKeyConstant.cs +++ b/WGShare.Domain/Constant/RedisKeyConstant.cs @@ -19,7 +19,7 @@ namespace WGShare.Domain.Constant /// /// 在线人数 /// - public static string GetOnlineUserKey(string tenantId) => $@"te_{tenantId}:OnlieUser"; + public static string GetOnlineUserKey(string tenantId) => $@"SessionManage:te_{tenantId}:OnlieUser"; /// /// 频道用户 @@ -27,36 +27,41 @@ namespace WGShare.Domain.Constant /// /// /// - public static string GetChannelUserKey(string tenantId, string roomNum) => $@"te_{tenantId}:ch_{roomNum}"; + public static string GetChannelUserKey(string tenantId, string roomNum) => $@"SessionManage:te_{tenantId}:ch_{roomNum}"; /// /// 用户参与频道 /// /// /// - public static string GetUserJoinChannelKey(string uid) => $@"u_{uid}_join"; + public static string GetUserJoinChannelKey(string uid) => $@"SessionManage:u_{uid}_join"; - /// - /// 频道用户数 - /// - /// - /// - [Obsolete("废弃")] - public static string GetChannelUserCountKey(string tenantId) => $@"te_{tenantId}:ChannelUserCount"; - } - /// - /// 房间管理 - /// - public class RoomManager - { /// /// 获取频道全员观看对象 /// /// /// /// - public static string GetChannelShowUserKey(string tenantId) => $@"te_{tenantId}:room_show_user"; + public static string GetChannelShowUserKey(string tenantId) => $@"SessionManage:te_{tenantId}:room_show_user"; + } + + /// + /// 持久化数据 + /// + public class Data + { + /// + /// 获取token + /// + /// + /// + public static string GetRefreshTokenKey(string refreshToken) => $@"data:refresh:{refreshToken}"; + + public static string GetScreenShareIdKey => $@"data:screen_share_id"; + } + + } } diff --git a/WGShare.Domain/DTOs/User/UserExcelInputDto.cs b/WGShare.Domain/DTOs/User/UserExcelInputDto.cs new file mode 100644 index 0000000..a57ac60 --- /dev/null +++ b/WGShare.Domain/DTOs/User/UserExcelInputDto.cs @@ -0,0 +1,37 @@ +using MiniExcelLibs.Attributes; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace WGShare.Domain.DTOs.User +{ + public class UserExcelInputDto + { + /// + /// 用户名称 + /// + [ExcelColumnName("用户名称")] + public string UserName { get; set; } + /// + /// 账号 + /// + [ExcelColumnName("登录账号")] + public string Account { get; set; } + /// + /// 密码 + /// + [ExcelColumnName("登录密码")] + public string? Pwd { get; set; } + /// + /// + /// + [ExcelColumnName("角色")] + public string RoleId{ get; set; } + + + [ExcelColumnName("导入结果")] + public string ImportResult { get; set; } = string.Empty; + } +} diff --git a/WGShare.Domain/DTOs/User/UserOutputDTO.cs b/WGShare.Domain/DTOs/User/UserOutputDTO.cs index 86fc925..3a2dc2e 100644 --- a/WGShare.Domain/DTOs/User/UserOutputDTO.cs +++ b/WGShare.Domain/DTOs/User/UserOutputDTO.cs @@ -20,22 +20,7 @@ namespace WGShare.Domain.DTOs.User public string Account { get; set; } public string RoleId { get; set; } public string RoleName { get; set; } - - /// - /// 是否管理员 - /// - public bool IsManager { get; set; } - - /// - /// 是否关闭麦克风 - /// - public bool EnableMicr { get; set; } - - /// - /// 是否关闭摄像头 - /// - public bool EnableCamera { get; set; } - + /// /// 是否在线 /// diff --git a/WGShare.Domain/Entities/ChannelUserInfo.cs b/WGShare.Domain/Entities/ChannelUserInfo.cs index c5dcd41..5c799e4 100644 --- a/WGShare.Domain/Entities/ChannelUserInfo.cs +++ b/WGShare.Domain/Entities/ChannelUserInfo.cs @@ -10,27 +10,8 @@ namespace WGShare.Domain.Entities /// 用户在频道中的状态 /// public class ChannelUserInfo - { - public ChannelUserInfo() - { - - } - /// - /// - /// - /// - /// - /// 是否关闭麦克风,默认关 - /// 是否关闭摄像头,默认关 - public ChannelUserInfo(string uid, string connectId, bool enableMicr, bool enableCamera, string account, string screenShareId) - { - this.UID = uid; - this.ConnectId = connectId; - this.EnableMicr = enableMicr; - this.EnableCamera = enableCamera; - this.Account = account; - this.ScreenShareId = screenShareId; - } + { + public string UID { get; set; } @@ -49,5 +30,13 @@ namespace WGShare.Domain.Entities public bool EnableCamera { get; set; } public string ScreenShareId { get; set; } + + public string UserName { get; set; } + public string RoleId { get; set; } + public string RoleName { get; set; } + /// + /// 是否房间管理员 + /// + public bool IsRoomManager { get; set; } } } diff --git a/WGShare.Domain/Enums/RoleEnums.cs b/WGShare.Domain/Enums/RoleEnums.cs new file mode 100644 index 0000000..f830798 --- /dev/null +++ b/WGShare.Domain/Enums/RoleEnums.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using WGShare.Domain.Entities; + +namespace WGShare.Domain.Enums +{ + /// + /// 角色枚举 + /// + public enum RoleEnums + { + [Description("管理员")] + Admin = 1, + [Description("普通用户")] + User = 2, + } +} diff --git a/WGShare.Domain/WGShare.Domain.csproj b/WGShare.Domain/WGShare.Domain.csproj index 3b09112..2d6f2bc 100644 --- a/WGShare.Domain/WGShare.Domain.csproj +++ b/WGShare.Domain/WGShare.Domain.csproj @@ -12,6 +12,7 @@ +