diff --git a/WGShare.API/BackgroudServices/AgoraDataService.cs b/WGShare.API/BackgroudServices/AgoraDataService.cs new file mode 100644 index 0000000..8af0e34 --- /dev/null +++ b/WGShare.API/BackgroudServices/AgoraDataService.cs @@ -0,0 +1,56 @@ +using Hangfire; +using Hangfire.Server; +using Masuit.Tools; +using SqlSugar; +using WGShare.API.Helpers; +using WGShare.Domain.Entities; + +namespace WGShare.API.BackgroudServices +{ + public class OssCleanWorker : BackgroundService + { + private readonly ILogger _logger; + private readonly ISqlSugarClient _sugarClient; + private readonly OssHelper _ossHelper; + + public OssCleanWorker(ILogger logger, ISqlSugarClient sugarClient, OssHelper ossHelper) + { + _logger = logger; + this._sugarClient = sugarClient; + this._ossHelper = ossHelper; + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + RecurringJob.AddOrUpdate("DeleteOssFile", () => DeleteOssFile(), Cron.Daily()); + } + + public async Task DeleteOssFile() + { + await Console.Out.WriteLineAsync($@"开始清除过期文件,当前时间:{DateTime.Now}"); + + // 查找已删除,未文件清理,并过期180天的文件 + var deleteFiles = await _sugarClient.Queryable() + .Where(x => x.IsDelete == true && x.IsFileClean == false && SqlFunc.DateDiff(DateType.Day, x.ModifyTime, SqlFunc.GetDate()) > 180) + .ToListAsync(); + if (deleteFiles.IsNullOrEmpty()) + { + await Console.Out.WriteLineAsync($@"当前无可清除文件,退出本次操作,当前时间:{DateTime.Now}"); + return; + } + + await Console.Out.WriteLineAsync($@"本次清除文件数量:{deleteFiles.Count}"); + await Console.Out.WriteLineAsync($@"本次清除文件路径:{string.Join(',', deleteFiles.Select(x => x.FileUrl))}"); + + if (_ossHelper.DeleteObjects(deleteFiles.Select(x => x.FileUrl).ToArray())) + { + await _sugarClient.Updateable() + .SetColumns(x => x.IsFileClean == true) + .Where(x => deleteFiles.Select(a => a.Id).Contains(x.Id)) + .ExecuteCommandAsync(); + } + + await Console.Out.WriteLineAsync($@"本次清除操作结束,当前时间:{DateTime.Now}"); + } + } +} diff --git a/WGShare.API/Controllers/AuthController.cs b/WGShare.API/Controllers/AuthController.cs index 147ff70..b5e4851 100644 --- a/WGShare.API/Controllers/AuthController.cs +++ b/WGShare.API/Controllers/AuthController.cs @@ -18,11 +18,14 @@ namespace WGShare.API.Controllers { private readonly ISqlSugarClient _sqlSugar; private readonly JwtHelper _jwtHelper; + private readonly IConfiguration _configuration; - public AuthController(ISqlSugarClient sqlSugar, JwtHelper jwtHelper) + public AuthController(ISqlSugarClient sqlSugar, JwtHelper jwtHelper, + IConfiguration configuration) { _sqlSugar = sqlSugar; _jwtHelper = jwtHelper; + this._configuration = configuration; } /// @@ -71,6 +74,8 @@ namespace WGShare.API.Controllers btnAutn.Add(new Claim("perm", perms.Sum(x => x.PermValue).ToString())); btnAutn.Add(new Claim("role", user.RoleId)); btnAutn.Add(new Claim("tenant", user.TenantId)); + btnAutn.Add(new Claim("account", user.Account)); + btnAutn.Add(new Claim("uname", user.UserName)); return Ok(new { @@ -78,7 +83,8 @@ namespace WGShare.API.Controllers token = _jwtHelper.CreateToken(user.Id, btnAutn), roleId = user.RoleId, userName = user.UserName, - tenantName = tenant.TenantName + tenantName = tenant.TenantName, + expire= _configuration["Jwt:Expires"].ToInt32() }); } diff --git a/WGShare.API/Controllers/Basic/BasicController.cs b/WGShare.API/Controllers/Basic/BasicController.cs index 776fa73..8746ab7 100644 --- a/WGShare.API/Controllers/Basic/BasicController.cs +++ b/WGShare.API/Controllers/Basic/BasicController.cs @@ -22,6 +22,33 @@ namespace WGShare.API.Controllers.Basic } } + public string Account + { + get + { + var account = HttpContext.User.Claims.FirstOrDefault(x => x.Type == "account").Value; + if (string.IsNullOrWhiteSpace(account)) + { + throw Oops.Oh("用户信息有误,请重新登录"); + } + return account; + } + } + + public string UserName + { + get + { + var account = HttpContext.User.Claims.FirstOrDefault(x => x.Type == "uname").Value; + if (string.IsNullOrWhiteSpace(account)) + { + throw Oops.Oh("用户信息有误,请重新登录"); + } + return account; + } + } + + public string RoleId { get diff --git a/WGShare.API/Controllers/Frontend/HomeController.cs b/WGShare.API/Controllers/Frontend/HomeController.cs index 0dac080..aa16b8b 100644 --- a/WGShare.API/Controllers/Frontend/HomeController.cs +++ b/WGShare.API/Controllers/Frontend/HomeController.cs @@ -1,7 +1,14 @@ -using Mapster; +using AgoraIO.Media; +using AgoraIO.Rtm; +using Mapster; +using Masuit.Tools; using Microsoft.AspNetCore.Mvc; using SqlSugar; +using System.Net.Http; using WGShare.API.Controllers.Basic; +using WGShare.API.Helpers; +using WGShare.Domain.AgoraApiResult; +using WGShare.Domain.Constant; using WGShare.Domain.DTOs.Room; using WGShare.Domain.Entities; using WGShare.Domain.FriendlyException; @@ -18,21 +25,33 @@ namespace WGShare.API.Controllers.Frontend public class HomeController : BasicController { private readonly ISqlSugarClient _sqlSugar; + private readonly IConfiguration _configuration; + private readonly IHttpClientFactory _httpClientFactory; + private readonly AgoraHelper _agoraHelper; + private readonly ILogger _logger; - public HomeController(ISqlSugarClient sqlSugar) + public HomeController( + ISqlSugarClient sqlSugar, + IConfiguration configuration, + IHttpClientFactory httpClientFactory, + AgoraHelper agoraHelper, + ILogger logger) { _sqlSugar = sqlSugar; + this._configuration = configuration; + this._httpClientFactory = httpClientFactory; + this._agoraHelper = agoraHelper; + this._logger = logger; } - /// /// 获取会议室列表 /// /// /// [HttpGet("room")] - public async Task> GetRooms(PagedBaseDto dto) + public async Task> GetRooms([FromQuery] PagedBaseDto dto) { RefAsync total = 0; @@ -40,7 +59,19 @@ namespace WGShare.API.Controllers.Frontend .Where(x => x.TenantId == TenantId && x.IsDelete == false) .ToPageListAsync(dto.PageIndex, dto.PageSize, total); - return PagedResult.Create(list.Adapt>(), total.Value); + var result = list.Adapt>(); + if (!result.IsNullOrEmpty()) + { + // 从Redis获取缓存在线人数 拼装数据 + var userCounts = RedisHelper.Instance.HMGet(RedisKeyConstant.SessionManage.GetChannelUserCountKey(TenantId), + result.Select(x => x.RoomNum).ToArray()); + for (int i = 0; i < userCounts.Length; i++) + { + result[i].OnlineUserCount = userCounts[i]; + } + } + + return PagedResult.Create(result, total.Value); } /// @@ -49,8 +80,13 @@ namespace WGShare.API.Controllers.Frontend /// /// [HttpPost("room")] - public async Task CreateRoom([FromBody] RoomInputDTO inputDTO) + public async Task CreateRoom([FromBody] RoomInputDTO inputDTO) { + if (inputDTO.RoomNum.Length != 8 || !int.TryParse(inputDTO.RoomNum, out _)) + { + throw Oops.Oh("会议号必须为8位数字"); + } + var entity = inputDTO.Adapt(); entity.Id = YitIdHelper.NextId().ToString(); entity.TenantId = TenantId; @@ -63,5 +99,19 @@ namespace WGShare.API.Controllers.Frontend return await _sqlSugar.Insertable(entity).ExecuteCommandAsync() > 0; } + /// + /// 获取 rtm token + /// + /// + [HttpGet("tk/rtm"),Obsolete] + public async Task GetRTMToken() + { + uint privilegeExpiredTs = (uint)_configuration["tokenExpireTimeInSecond"].ToInt32() + (uint)Utils.getTimestamp(); + return RtmTokenBuilder.buildToken(_configuration["Agora:appId"], + _configuration["Agora:appSecret"], + UId, + privilegeExpiredTs); + } + } } diff --git a/WGShare.API/Controllers/Frontend/RoomController.cs b/WGShare.API/Controllers/Frontend/RoomController.cs index 0767730..0a1db67 100644 --- a/WGShare.API/Controllers/Frontend/RoomController.cs +++ b/WGShare.API/Controllers/Frontend/RoomController.cs @@ -1,10 +1,15 @@ -using Mapster; +using AgoraIO.Media; +using Mapster; using Masuit.Tools; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.SignalR; +using Microsoft.IdentityModel.Tokens; using SqlSugar; -using System.CodeDom; using WGShare.API.Controllers.Basic; +using WGShare.API.Helpers; +using WGShare.API.Hubs; +using WGShare.Domain.Constant; using WGShare.Domain.DTOs.File; using WGShare.Domain.DTOs.Room; using WGShare.Domain.DTOs.User; @@ -12,7 +17,6 @@ using WGShare.Domain.Entities; using WGShare.Domain.FriendlyException; using WGShare.Domain.GeneralModel; using Yitter.IdGenerator; -using ZstdSharp.Unsafe; namespace WGShare.API.Controllers.Frontend { @@ -24,24 +28,36 @@ namespace WGShare.API.Controllers.Frontend public class RoomController : BasicController { private readonly ISqlSugarClient _sqlSugar; + private readonly IConfiguration _configuration; + private readonly OssHelper _ossHelper; + private readonly AgoraHelper _agoraHelper; + private readonly IHubContext _hubContext; - public RoomController(ISqlSugarClient sqlSugar) + public RoomController(ISqlSugarClient sqlSugar, + IConfiguration configuration, + OssHelper ossHelper, + AgoraHelper agoraHelper, + IHubContext hubContext) { this._sqlSugar = sqlSugar; + this._configuration = configuration; + this._ossHelper = ossHelper; + this._agoraHelper = agoraHelper; + this._hubContext = hubContext; } - /// - /// 获取会议室管理员 - /// - /// - [HttpGet("manager")] - public async Task> GetRoomManager([FromQuery] string roomId) - { - return await _sqlSugar.Queryable() - .Where(x => x.RoomId == roomId) - .Select(x => x.UserId) - .ToListAsync(); - } + ///// + ///// 获取会议室管理员 + ///// + ///// + //[HttpGet("manager")] + //public async Task> GetRoomManager([FromQuery] string roomId) + //{ + // return await _sqlSugar.Queryable() + // .Where(x => x.RoomId == roomId) + // .Select(x => x.UserId) + // .ToListAsync(); + //} /// /// 设置房间管理员 @@ -59,24 +75,51 @@ namespace WGShare.API.Controllers.Frontend return await _sqlSugar.Insertable(entities).ExecuteCommandAsync() > 0; } + /// + /// 取消房间管理员 + /// + /// + [HttpDelete("manager")] + public async Task DelRoomManager([FromQuery] string roomId, [FromBody] List userIds) + { + return await _sqlSugar.Deleteable() + .Where(x => x.RoomId == roomId && userIds.Contains(x.UserId)) + .ExecuteCommandAsync() > 0; + } + /// /// 查询用户信息 /// /// [HttpGet("user")] - public async Task> GetUsers([FromQuery] string uidStr) + public async Task> GetUsers([FromQuery] string roomNum) { - var uid = uidStr.Split(',', StringSplitOptions.RemoveEmptyEntries); - if (uid.IsNullOrEmpty()) + var data = await _agoraHelper.GetChannelUserList(roomNum); + if (data == null) { - throw Oops.Oh("请输入需要查询的用户id,多id请用英文逗号,分割"); + throw Oops.Oh("请求失败"); + } + if (!data.channel_exist) + { + throw Oops.Oh("频道不存在"); } var users = await _sqlSugar.Queryable() - .Where(x => uid.Contains(x.Id)) + .Where(x => data.users.Contains(x.Account)) .ToListAsync(); - return users.Adapt>(); + 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 = managerIds.Contains(x.Id)); + + return result; } /// @@ -104,6 +147,53 @@ namespace WGShare.API.Controllers.Frontend return room.Adapt(); } + /// + /// 获取房间rtc token + /// + /// + [HttpGet("tk/rtc")] + public async Task GetRTCToken([FromQuery] string roomNum) + { + //var privilegeExpiredTs = _configuration["Agora:tokenExpireTimeInSecond"].ToInt32() + Utils.getTimestamp(); + + return new RtcTokenBuilder2().buildTokenWithUserAccount( + _configuration["Agora:appId"], + _configuration["Agora:appSecret"], + roomNum, + Account, + RtcTokenBuilder2.Role.ROLE_PUBLISHER, + _configuration["Agora:tokenExpireTimeInSecond"].ToInt32(), + _configuration["Agora:tokenExpireTimeInSecond"].ToInt32()); + } + + /// + /// 邀请用户 + /// + /// + [HttpPost("invite")] + public async Task InviteUser([FromQuery] string roomId, [FromBody] string[] inviteeUids) + { + var room = await _sqlSugar.Queryable().FirstAsync(x => x.Id == roomId); + + var connectIds = RedisHelper.Instance.HMGet(RedisKeyConstant.SessionManage.GetOnlineUserKey(TenantId), inviteeUids); + connectIds.RemoveWhere(x => string.IsNullOrWhiteSpace(x)); + + await _hubContext.Clients.Clients(connectIds).Invitation(room.RoomNum, room.RoomName, UserName); + } + + /// + /// 踢出房间 + /// + /// + [HttpGet("kickout")] + public async Task KickOut([FromQuery] string roomNum, [FromQuery] string kickUid) + { + var connectId = RedisHelper.Instance.HGet(RedisKeyConstant.SessionManage.GetOnlineUserKey(TenantId), kickUid); + + await _hubContext.Clients.Client(connectId).ForceExitRoom(roomNum); + } + + #region 文件分享 /// /// 分享上传文件 /// @@ -135,14 +225,17 @@ namespace WGShare.API.Controllers.Frontend /// /// 获取分享文件列表 /// + /// 房间Id + /// /// [HttpGet("file")] - public async Task> GetFilesList([FromQuery] string roomId, [FromBody] PagedBaseDto pagedBaseDto) + public async Task> GetFilesList([FromQuery] string roomId, [FromQuery] string? keyword, [FromQuery] PagedBaseDto pagedBaseDto) { RefAsync total = 0; var list = await _sqlSugar.Queryable() .LeftJoin((sf, u) => sf.UserId == u.Id) - .Where(x => x.IsDelete == false && x.RoomId == roomId) + .Where((sf, u) => sf.IsDelete == false && sf.RoomId == roomId) + .WhereIF(!string.IsNullOrWhiteSpace(keyword), (sf, u) => sf.FileName.Contains(keyword)) .Select((sf, u) => new ShareFileOutputDTO { Id = sf.Id, @@ -150,7 +243,7 @@ namespace WGShare.API.Controllers.Frontend UserName = u.UserName, FileName = sf.FileName, FileUrl = sf.FileUrl, - RoomId = u.Id, + RoomId = sf.RoomId, Size = sf.Size, ModifyTime = sf.ModifyTime, DownloadCount = sf.DownloadCount, @@ -160,5 +253,32 @@ namespace WGShare.API.Controllers.Frontend } + + /// + /// 获取文件上传url + /// + /// + [HttpGet("up-fileurl")] + public async Task GetUploadUrl([FromQuery] string roomNum, [FromQuery] string fileSuffix) + { + return Ok(_ossHelper.GetUploadUrl($@"share_file/{TenantId}/{roomNum}", Guid.NewGuid().ToString("N") + "." + fileSuffix)); + } + + /// + /// 获取文件下载地址 + /// + /// + /// 文件Id + /// + [HttpGet("file-dw-url")] + public async Task GetDownloadUrl([FromQuery] string fileUrl, [FromQuery] string fileId) + { + await _sqlSugar.Updateable() + .SetColumns(x => x.DownloadCount == x.DownloadCount + 1) + .Where(x => x.Id == fileId).ExecuteCommandAsync(); + + return _ossHelper.GetAccessFileUrl(fileUrl); + } + #endregion } } diff --git a/WGShare.API/Controllers/Frontend/UserController.cs b/WGShare.API/Controllers/Frontend/UserController.cs index 95c2ad0..db2329f 100644 --- a/WGShare.API/Controllers/Frontend/UserController.cs +++ b/WGShare.API/Controllers/Frontend/UserController.cs @@ -22,17 +22,32 @@ namespace WGShare.API.Controllers.Frontend } + /// + /// 获取用户列表 + /// + /// 用户名 或 账号 + /// 翻页信息 + /// [HttpGet("list")] - public async Task> GetUserList([FromQuery] string? searchKeywod, [FromQuery] PagedBaseDto pagedBaseDto) + public async Task> GetUserList([FromQuery] string? searchKeywod, [FromQuery] PagedBaseDto pagedBaseDto) { RefAsync total = 0; var list = await _sqlSugar.Queryable() .InnerJoin((u, r) => u.RoleId == r.Id) - .WhereIF(!string.IsNullOrWhiteSpace(searchKeywod), u => u.UserName.Contains(searchKeywod) || u.Account.Contains(searchKeywod)) + .Where((u, r) => u.IsDelete == false) + .WhereIF(!string.IsNullOrWhiteSpace(searchKeywod), (u, r) => u.UserName.Contains(searchKeywod) || u.Account.Contains(searchKeywod)) + .Select((u, r) => new UserOutputDTO + { + Id = u.Id, + UserName = u.UserName, + Account = u.Account, + RoleId = r.Id, + RoleName = r.RoleName + }) .ToPageListAsync(pagedBaseDto.PageIndex, pagedBaseDto.PageSize, total); - return list.Adapt>(); + return PagedResult.Create(list, total.Value); } @@ -42,7 +57,7 @@ namespace WGShare.API.Controllers.Frontend /// /// [HttpPost] - public async Task AddUser([FromQuery] UserInputDTO inputDTO) + public async Task AddUser([FromBody] UserInputDTO inputDTO) { var user = inputDTO.Adapt(); user.Id = YitIdHelper.NextId().ToString(); diff --git a/WGShare.API/Helpers/AgoraHelper.cs b/WGShare.API/Helpers/AgoraHelper.cs new file mode 100644 index 0000000..778acc1 --- /dev/null +++ b/WGShare.API/Helpers/AgoraHelper.cs @@ -0,0 +1,132 @@ +using Masuit.Tools; +using System.Drawing.Printing; +using System.Net.Http; +using System.Text; +using WGShare.Domain.AgoraApiResult; +using WGShare.Domain.FriendlyException; + +namespace WGShare.API.Helpers +{ + public class AgoraHelper + { + public class Constant + { + /// + /// Redis 键,hash ,每个频道用户数量 + /// + public const string REDIS_CHANNEL_USERCOUNT = "channelUserCount"; + } + + private readonly IConfiguration _configuration; + + public AgoraHelper(IConfiguration configuration) + { + this._configuration = configuration; + } + + /// + /// 获取完整的声网API请求路径 + /// + /// api .com 后的路径后缀 + /// + public string GetFullApiUrl(string apiSuffixPath) + { + var apiPrefix = _configuration["agora:apiPrefix"].Trim('/'); + + return apiPrefix + (apiSuffixPath.StartsWith('/') ? apiSuffixPath : ('/' + apiSuffixPath)); + } + + + public string GetBasicCredential() + { + // APP ID + string customerKey = _configuration["Agora:clientId"]; + // 客户密钥 + string customerSecret = _configuration["Agora:clientSecret"]; + // 拼接 APP ID 和APP密钥 + string plainCredential = customerKey + ":" + customerSecret; + + // 使用 base64 进行编码 + var plainTextBytes = Encoding.UTF8.GetBytes(plainCredential); + string encodedCredential = Convert.ToBase64String(plainTextBytes); + // 创建 authorization header + return "Basic " + encodedCredential; + } + + /// + /// 获取频道用户 + /// + /// + /// + public async Task GetChannelUserList(string roomNum) + { + using var httpClient = new HttpClient(); + httpClient.DefaultRequestHeaders.Add("Authorization", GetBasicCredential()); + var apiUrl = GetFullApiUrl($"dev/v1/channel/user/{_configuration["Agora:appId"]}/{roomNum}"); + var responseMessage = await httpClient.GetAsync(apiUrl); + if (!responseMessage.IsSuccessStatusCode) + { + await Console.Out.WriteLineAsync($"声网接口请求失败!请求地址:{apiUrl} 请求结果:{await responseMessage.Content.ReadAsStringAsync()}"); + return null; + } + + var responseData = await responseMessage.Content.ReadFromJsonAsync>(); + if (responseData == null) + { + await Console.Out.WriteLineAsync($"声网接口解析失败!请求地址:{apiUrl} 请求结果:{await responseMessage.Content.ReadAsStringAsync()}"); + return null; + } + + return responseData.Data; + } + + /// + /// 更新每个频道用户在线数量 + /// + /// + public async Task RefreshChannelUserCount() + { + var pageSize = 500D; + var pageIndex = 1; + var totalCount = 0D; + + do + { + using var httpClient = new HttpClient(); + httpClient.DefaultRequestHeaders.Add("Authorization", GetBasicCredential()); + var apiUrl = GetFullApiUrl($"dev/v1/channel/{_configuration["Agora:appId"]}?page_no={pageIndex}&page_size={pageSize}"); + + var responseMessage = await httpClient.GetAsync(apiUrl); + if (!responseMessage.IsSuccessStatusCode) + { + await Console.Out.WriteLineAsync($"声网接口请求失败!请求地址:{apiUrl} 请求结果:{await responseMessage.Content.ReadAsStringAsync()}"); + return; + } + + var responseData = await responseMessage.Content.ReadFromJsonAsync>(); + if (responseData == null) + { + await Console.Out.WriteLineAsync($"声网接口解析失败!请求地址:{apiUrl} 请求结果:{await responseMessage.Content.ReadAsStringAsync()}"); + return; + } + + totalCount = responseData.Data.total_size.ToDouble(); + + var ketValue = responseData.Data.channels.ToDictionary(x => x.channel_name, x => x.user_count); + + if (!ketValue.IsNullOrEmpty()) + { + RedisHelper.Instance.HSet(Constant.REDIS_CHANNEL_USERCOUNT, responseData.Data.channels.ToDictionary(x => x.channel_name, x => x.user_count)); + } + + // 总页数 = 总数/每页数 + //var pageCount = (int)Math.Ceiling(Math.Round(totalCount / pageSize, 3)); + + } while (totalCount > 0 && (int)Math.Ceiling(Math.Round(totalCount / pageSize, 3)) > pageIndex); + + + } + + + } +} diff --git a/WGShare.API/Helpers/OssHelper.cs b/WGShare.API/Helpers/OssHelper.cs new file mode 100644 index 0000000..b7f0f5e --- /dev/null +++ b/WGShare.API/Helpers/OssHelper.cs @@ -0,0 +1,99 @@ +using Aliyun.OSS; +using Masuit.Tools; +using Microsoft.EntityFrameworkCore.Metadata.Internal; +using Microsoft.Extensions.Hosting; +using System.Security.Cryptography; +using System.Text; +using WGShare.Domain.FriendlyException; + +namespace WGShare.API.Helpers +{ + public class OssHelper + { + private readonly IConfiguration _configuration; + + private readonly string _accessKeyId; + private readonly string _accessKeySecret; + private readonly string _bucketName; + private readonly string _endpoint; + private readonly OssClient _ossClient; + + public OssHelper(IConfiguration configuration) + { + _configuration = configuration; + _accessKeyId = configuration["OSS:AccessKeyID"]; + _accessKeySecret = configuration["OSS:AccessKeySecret"]; + _bucketName = configuration["OSS:BucketName"]; + _endpoint = configuration["OSS:Endpoint"]; + this._ossClient = new OssClient("https://" + _endpoint, _accessKeyId, _accessKeySecret); + } + + /// + /// 获取上传url + /// + /// bucket 路径 + /// 文件名 + /// 过期时间 秒 + /// + public Dictionary GetUploadUrl(string path, string fileName, uint expireInSecond = 300) + { + //OssClient ossClient = new OssClient("httos://" + _endpoint, _accessKeyId, _accessKeySecret); + var policy = new PolicyConditions(); + // 设置最大文件1000M + policy.AddConditionItem(PolicyConditions.CondContentLengthRange, 0, 1048576000); + policy.AddConditionItem(MatchMode.StartWith, PolicyConditions.CondKey, path.TrimEnd('/') + '/'); + + var postPolicy = _ossClient.GeneratePostPolicy(DateTimeOffset.Now.AddSeconds(expireInSecond).LocalDateTime, policy); + var policyBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(postPolicy)); + + // 计算签名 + var hmac = new HMACSHA1(Encoding.UTF8.GetBytes(_accessKeySecret)); + var bytes = hmac.ComputeHash(Encoding.UTF8.GetBytes(policyBase64)); + var sign = Convert.ToBase64String(bytes); + + var host = $"https://{_bucketName}.{_endpoint}"; + var key = $"{path.TrimEnd('/')}/{fileName}"; + //var fullUrl = $"https://{_bucketName}.{_endpoint}/{key}"; + + return new Dictionary + { + { "OSSAccessKeyId", _configuration["OSS:AccessKeyID"]}, + { "Host",host }, + { "key",key}, + { "policy",policyBase64}, + { "Signature",sign}, + { "success_action_status","200"}, + //{ "fullUrl",fullUrl }, + {"expire",expireInSecond.ToString() }, + { "name",fileName} + }; + } + + public string GetAccessFileUrl(string path) + { + if (string.IsNullOrWhiteSpace(path) ) + { + throw Oops.Oh("路径不能为空!"); + } + + var urlRequest = new GeneratePresignedUriRequest(_bucketName, path) + { + Expiration = DateTimeOffset.Now.AddMinutes(1).LocalDateTime + }; + + return _ossClient.GeneratePresignedUri(urlRequest).ToString(); + } + + public bool DeleteObjects(params string[] remotePathList) + { + if (remotePathList.IsNullOrEmpty()) + { + return true; + } + + var result = _ossClient.DeleteObjects(new DeleteObjectsRequest(_bucketName, remotePathList, true)); + + return result.HttpStatusCode == System.Net.HttpStatusCode.OK; + } + } +} diff --git a/WGShare.API/Hubs/IMessageClient.cs b/WGShare.API/Hubs/IMessageClient.cs new file mode 100644 index 0000000..06f6b7d --- /dev/null +++ b/WGShare.API/Hubs/IMessageClient.cs @@ -0,0 +1,33 @@ +namespace WGShare.API.Hubs +{ + /// + /// 客户端消息 + /// + public interface IMessageClient + { + /// + /// 接受频道消息 + /// + /// + /// + /// + Task ReceiveMessage(string userName, string message); + + + /// + /// 邀请进入会议室 + /// + /// 会议号 + /// 会议名称 + /// 邀请人名 + /// + Task Invitation(string roomNum, string roomName, string InviterName); + + /// + /// 强制退出房间 + /// + /// 会议号 + /// + Task ForceExitRoom(string roomNum); + } +} diff --git a/WGShare.API/Hubs/SessionManageHub.cs b/WGShare.API/Hubs/SessionManageHub.cs new file mode 100644 index 0000000..bc484f1 --- /dev/null +++ b/WGShare.API/Hubs/SessionManageHub.cs @@ -0,0 +1,141 @@ +using Masuit.Tools; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.SignalR; +using System.Text; +using WGShare.API.Helpers; +using WGShare.Domain.Constant; + +namespace WGShare.API.Hubs +{ + [Authorize] + public class SessionManageHub : Hub + { + + public async override Task OnConnectedAsync() + { + var tenant = Context.User?.Claims.FirstOrDefault(x => x.Type == "tenant")?.Value; + var uid = Context.User?.Claims.FirstOrDefault(x => x.Type == "uid")?.Value; + + await Console.Out.WriteLineAsync("连接成功 当前租户:" + tenant); + await Console.Out.WriteLineAsync("连接成功 uid:" + uid); + await Console.Out.WriteLineAsync("连接成功 connectId:" + Context.ConnectionId); + + // 存储在线信息 + RedisHelper.Instance.HSet(RedisKeyConstant.SessionManage.GetOnlineUserKey(tenant), uid, Context.ConnectionId); + } + + public async override Task OnDisconnectedAsync(Exception? exception) + { + var tenant = Context.User?.Claims.FirstOrDefault(x => x.Type == "tenant")?.Value; + var uid = Context.User?.Claims.FirstOrDefault(x => x.Type == "uid")?.Value; + + await Console.Out.WriteLineAsync("断开连接 当前租户:" + tenant); + await Console.Out.WriteLineAsync("断开连接 uid:" + uid); + await Console.Out.WriteLineAsync("断开连接 connectId:" + Context.ConnectionId); + + // 获取用户参加得频道 + var roomNums = RedisHelper.Instance.HKeys(RedisKeyConstant.SessionManage.GetUserJoinChannelKey(uid)); + using (var pipe = RedisHelper.Instance.StartPipe()) + { + // 移除在线信息 + pipe.HDel(RedisKeyConstant.SessionManage.GetOnlineUserKey(tenant), uid); + if (!roomNums.IsNullOrEmpty()) + { + await Console.Out.WriteLineAsync($@"uid:{uid} 退出以下频道:{string.Join(',', roomNums)}"); + // 所有房间移除该用户 + roomNums.ForEach(roomNum => + { + pipe.HDel(RedisKeyConstant.SessionManage.GetChannelUserKey(tenant, roomNum), uid); + pipe.HIncrBy(RedisKeyConstant.SessionManage.GetChannelUserCountKey(tenant), roomNum, -1); + }); + } + // 删除用户已加入频道信息 + pipe.Del(RedisKeyConstant.SessionManage.GetUserJoinChannelKey(uid)); + + pipe.EndPipe(); + } + + } + + /// + /// 加入频道 + /// + /// + [HubMethodName("joinChannel")] + public async Task JoinChannel(string roomNum) + { + var tenant = Context.User?.Claims.FirstOrDefault(x => x.Type == "tenant")?.Value; + var uid = Context.User?.Claims.FirstOrDefault(x => x.Type == "uid")?.Value; + + using (var pipe = RedisHelper.Instance.StartPipe()) + { + pipe.HSet(RedisKeyConstant.SessionManage.GetChannelUserKey(tenant, roomNum), uid, Context.ConnectionId); + pipe.HIncrBy(RedisKeyConstant.SessionManage.GetChannelUserCountKey(tenant), roomNum, 1); + pipe.HSet(RedisKeyConstant.SessionManage.GetUserJoinChannelKey(uid), roomNum, 1); + pipe.EndPipe(); + } + + await Groups.AddToGroupAsync(Context.ConnectionId, roomNum); + } + + /// + /// 离开频道 + /// + /// + [HubMethodName("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; + + using (var pipe = RedisHelper.Instance.StartPipe()) + { + pipe.HDel(RedisKeyConstant.SessionManage.GetChannelUserKey(tenant, roomNum), uid); + pipe.HDel(RedisKeyConstant.SessionManage.GetUserJoinChannelKey(uid), roomNum); + pipe.HIncrBy(RedisKeyConstant.SessionManage.GetChannelUserCountKey(tenant), roomNum, -1); + pipe.EndPipe(); + } + + await Groups.RemoveFromGroupAsync(Context.ConnectionId, roomNum); + } + + /// + /// 发送频道消息 + /// + /// + /// + /// + [HubMethodName("sendChannelMsg")] + public async Task SenMessage(string rooNum, string msg) + { + var uname = Context.User?.Claims.FirstOrDefault(x => x.Type == "uname")?.Value; + + await Clients.GroupExcept(rooNum, Context.ConnectionId).ReceiveMessage(uname, msg); + } + + ///// + ///// 邀请呼叫 + ///// + ///// 被邀请人id + ///// 会议名称 + ///// 会议号 + ///// + //[HubMethodName("call")] + //public async Task<(bool isSuccess, string msg)> CallUser(string inviteeUid, string roomName, string roomNum) + //{ + // var tenant = Context.User?.Claims.FirstOrDefault(x => x.Type == "tenant")?.Value; + // var uname = Context.User?.Claims.FirstOrDefault(x => x.Type == "uname")?.Value; + + // var connectId = RedisHelper.Instance.HGet(RedisKeyConstant.SessionManage.GetOnlineUserKey(tenant), inviteeUid); + // if (string.IsNullOrWhiteSpace(connectId)) + // { + // return (false, "被邀请人不在线"); + // } + + // await Clients.Client(connectId).Invitation(roomNum, roomName, uname); + + // return (true, "邀请成功"); + //} + + } +} diff --git a/WGShare.API/Program.cs b/WGShare.API/Program.cs index 2df77da..5ef5c5c 100644 --- a/WGShare.API/Program.cs +++ b/WGShare.API/Program.cs @@ -1,9 +1,14 @@ +using Hangfire; +using Hangfire.MemoryStorage; +using Microsoft.AspNetCore.SignalR; using Microsoft.OpenApi.Models; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using Newtonsoft.Json.Serialization; using SqlSugar; +using WGShare.API.BackgroudServices; using WGShare.API.Helpers; +using WGShare.API.Hubs; using WGShare.API.ServiceConfigs; using Yitter.IdGenerator; @@ -18,6 +23,7 @@ namespace WGShare.API // Add services to the container. + RedisHelper.Initialization(new FreeRedis.RedisClient(configuration["Redis:master"])); builder.Services.AddControllers(options => { // ȫ쳣ڴ д try catch @@ -37,8 +43,14 @@ namespace WGShare.API }); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); - builder.Services.AddSwagger(); + builder.Services.AddSwagger(); + builder.Services.AddSignalR(); + builder.Services.AddHttpClient(); + builder.Services.ConfigureHangfire(); builder.Services.AddSingleton(new JwtHelper(configuration)); + builder.Services.AddSingleton(new AgoraHelper(configuration)); + builder.Services.AddSingleton(new OssHelper(configuration)); + builder.Services.AddHostedService(); builder.Services.AddAuth(configuration["Jwt:Issuer"], configuration["Jwt:Audience"], configuration["Jwt:SecretKey"]); @@ -57,7 +69,6 @@ namespace WGShare.API IsAutoCloseConnection = true }); YitIdHelper.SetIdGenerator(new IdGeneratorOptions(1)); - RedisHelper.Initialization(new FreeRedis.RedisClient(configuration["Redis:master"])); var app = builder.Build(); @@ -74,13 +85,15 @@ namespace WGShare.API }); app.UseCustomCors(); } - + // Hangfire Dashboard + app.UseHangfireDashboard(); //мUseAuthentication֤Ҫ֤мǰã UseAuthorizationȨ app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); + app.MapHub("/session-manage").RequireCors(q => q.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod()); app.Run(); } diff --git a/WGShare.API/Properties/launchSettings.json b/WGShare.API/Properties/launchSettings.json index bb54335..91a6ab0 100644 --- a/WGShare.API/Properties/launchSettings.json +++ b/WGShare.API/Properties/launchSettings.json @@ -14,7 +14,7 @@ "dotnetRunMessages": true, "launchBrowser": false, "launchUrl": "swagger", - "applicationUrl": "http://localhost:5192", + "applicationUrl": "http://*:5192", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } diff --git a/WGShare.API/Reference/AgoraIO.dll b/WGShare.API/Reference/AgoraIO.dll new file mode 100644 index 0000000..01a8ba1 Binary files /dev/null and b/WGShare.API/Reference/AgoraIO.dll differ diff --git a/WGShare.API/ServiceConfigs/AuthenticationServiceExtensions.cs b/WGShare.API/ServiceConfigs/AuthenticationServiceExtensions.cs index 1066293..cfecd94 100644 --- a/WGShare.API/ServiceConfigs/AuthenticationServiceExtensions.cs +++ b/WGShare.API/ServiceConfigs/AuthenticationServiceExtensions.cs @@ -36,6 +36,21 @@ namespace WGShare.API.ServiceConfigs ClockSkew = TimeSpan.FromSeconds(30), //过期时间容错值,解决服务器端时间不同步问题(秒) RequireExpirationTime = true, }; + + //// signalR 鉴权 + options.Events = new JwtBearerEvents + { + OnMessageReceived = context => + { + var accessToken = context.Request.Query["access_token"]; + var path = context.HttpContext.Request.Path; + if (!string.IsNullOrEmpty(accessToken) && (path.StartsWithSegments("/session-manage"))) + { + context.Token = accessToken; + } + return Task.CompletedTask; + } + }; }); diff --git a/WGShare.API/ServiceConfigs/CorsAppBuilderExtensions.cs b/WGShare.API/ServiceConfigs/CorsAppBuilderExtensions.cs index b30e0a5..19d112e 100644 --- a/WGShare.API/ServiceConfigs/CorsAppBuilderExtensions.cs +++ b/WGShare.API/ServiceConfigs/CorsAppBuilderExtensions.cs @@ -18,7 +18,7 @@ namespace WGShare.API.ServiceConfigs //var hosts = AppSetting.AllowedHosts.Split("|", StringSplitOptions.RemoveEmptyEntries); app.UseCors(options => { - options.WithOrigins("*") // 允许跨域请求的地址 + options.AllowAnyOrigin() // 允许跨域请求的地址 .AllowAnyHeader() // 允许的请求标头 .AllowAnyMethod(); // 允许跨域请求的类型 (GET,POST等) diff --git a/WGShare.API/ServiceConfigs/HangfireServiceExtensions.cs b/WGShare.API/ServiceConfigs/HangfireServiceExtensions.cs new file mode 100644 index 0000000..4b19e78 --- /dev/null +++ b/WGShare.API/ServiceConfigs/HangfireServiceExtensions.cs @@ -0,0 +1,41 @@ +using Mapster; +using MapsterMapper; +using Newtonsoft.Json.Converters; +using Newtonsoft.Json.Serialization; +using Newtonsoft.Json; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.IdentityModel.Tokens; +using System.Text; +using System.Reflection.Metadata; +using Microsoft.OpenApi.Models; +using Hangfire; +using Hangfire.MemoryStorage; + +namespace WGShare.API.ServiceConfigs +{ + public static class HangfireServiceExtensions + { + /// + /// 添加Hangfire + /// + /// + /// + public static IServiceCollection ConfigureHangfire(this IServiceCollection services) + { + services.AddHangfire(x => x.UseMemoryStorage()); + // Hangfire全局配置 + GlobalConfiguration.Configuration + .UseColouredConsoleLogProvider() + .UseMemoryStorage() + .WithJobExpirationTimeout(TimeSpan.FromDays(7)); + + // Hangfire服务器配置 + services.AddHangfireServer(options => + { + options.HeartbeatInterval = TimeSpan.FromSeconds(10); + }); + + return services; + } + } +} diff --git a/WGShare.API/ServiceConfigs/SwaggerServiceExtensions.cs b/WGShare.API/ServiceConfigs/SwaggerServiceExtensions.cs index caf7cb8..356c089 100644 --- a/WGShare.API/ServiceConfigs/SwaggerServiceExtensions.cs +++ b/WGShare.API/ServiceConfigs/SwaggerServiceExtensions.cs @@ -49,8 +49,8 @@ namespace WGShare.API.ServiceConfigs } }); - //string xmlPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "SwaggerGroup.xml"); - //w.IncludeXmlComments(xmlPath, true); + string xmlPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "WGShare.API.xml"); + w.IncludeXmlComments(xmlPath, true); }); diff --git a/WGShare.API/WGShare.API.csproj b/WGShare.API/WGShare.API.csproj index 948283a..508e775 100644 --- a/WGShare.API/WGShare.API.csproj +++ b/WGShare.API/WGShare.API.csproj @@ -5,9 +5,15 @@ enable enable True + WGShare.API.xml + + + + + @@ -15,4 +21,19 @@ + + + Reference\AgoraIO.dll + + + + + + Never + + + PreserveNewest + + + diff --git a/WGShare.API/WGShare.API.xml b/WGShare.API/WGShare.API.xml new file mode 100644 index 0000000..5ca2021 --- /dev/null +++ b/WGShare.API/WGShare.API.xml @@ -0,0 +1,381 @@ + + + + WGShare.API + + + + + 检查用户名 + + + + + + + 正常账号登录 + + + + + + 匿名登录,直接进入会议室 + + + + + + 登出(暂未处理任何业务逻辑) + + + + + + 管理员登录 + + + + + + 管理员信息 + + + + + + 获取最新权限值 + + + + + + 权限修改 + + + + + + 首页接口 + + + + + 获取会议室列表 + + + + + + + 创建会议室 + + + + + + + 获取 rtm token + + + + + + 会议室接口 + + + + + 设置房间管理员 + + + + + + 取消房间管理员 + + + + + + 查询用户信息 + + + + + + 检验房间是否存在 + + + + + + 获取单个会议室信息 + + + + + + 获取房间rtc token + + + + + + 邀请用户 + + + + + + 踢出房间 + + + + + + 分享上传文件 + + + + + + + 删除文件 + + + + + + 获取分享文件列表 + + 房间Id + + + + + + 获取文件上传url + + + + + + 获取文件下载地址 + + + 文件Id + + + + + 获取用户列表 + + 用户名 或 账号 + 翻页信息 + + + + + 新增用户 + + + + + + + 修改用户信息 + + + + + + + 更改密码 + + + + + + + 删除用户 + + + + + + + 前后端共用接口 + + + + + 角色列表下拉框 + + + + + + Redis 键,hash ,每个频道用户数量 + + + + + 获取完整的声网API请求路径 + + api .com 后的路径后缀 + + + + + 获取频道用户 + + + + + + + 更新每个频道用户在线数量 + + + + + + 获取上传url + + bucket 路径 + 文件名 + 过期时间 秒 + + + + + redis静态访问类 + + + + + redis实例 + + + + + 初始化redis静态访问类 RedisHelper.Initialization(new FreeRedis.RedisClient(\"127.0.0.1:6379,password=123,defaultDatabase=13,maxpoolsize=50,prefix=key前辍\")) + + + + + + 随机秒(防止所有key同一时间过期,雪崩) + + 最小秒数 + 最大秒数 + + + + + 客户端消息 + + + + + 接受频道消息 + + + + + + + + 邀请进入会议室 + + 会议号 + 会议名称 + 邀请人名 + + + + + 强制退出房间 + + 会议号 + + + + + 加入频道 + + + + + + 离开频道 + + + + + + 发送频道消息 + + + + + + + + 添加认证和授权 + + 服务集合 + + + + + 跨域配置扩展服务 + + + + + 允许所有跨域请求 (属于基础服务,若已调用 UseBasicServices 则无需调用) + + + + + + + 全局异常捕获 + + + + + 添加Hangfire + + + + + + + 在Controller的Action执行后执行 + + + + + + 在Controller的Action执行前执行 + + + + + + 添加SqlSugar + + 服务集合 + + + + + 添加认证和授权 + + 服务集合 + + + + diff --git a/WGShare.API/appsettings.Development.json b/WGShare.API/appsettings.Development.json index f5c2cc0..2ca3bb7 100644 --- a/WGShare.API/appsettings.Development.json +++ b/WGShare.API/appsettings.Development.json @@ -10,6 +10,6 @@ "usercenter": "Database=usercenter;Server=192.168.2.9;Port=3306;Uid=root;Pwd=qwe123!@#;AllowZeroDateTime=True;ConvertZeroDateTime=True;" }, "Redis": { - "master": "192.168.2.7:6379,password=qwe123!@#,defaultDatabase=12,name=wgshare,prefix=wgshare" + "master": "192.168.2.7:6379,password=qwe123!@#,defaultDatabase=13,name=wgshare,prefix=wgshare" } } diff --git a/WGShare.API/appsettings.json b/WGShare.API/appsettings.json index f5b8963..de42024 100644 --- a/WGShare.API/appsettings.json +++ b/WGShare.API/appsettings.json @@ -11,13 +11,28 @@ "Issuer": "WGshareApi", "Audience": "WGshareClient", // 过期 秒 - "Expires": 14400 + "Expires": 86400 }, "ConnectionStrings": { "metting": "Database=metting;Server=192.168.2.9;Port=3306;Uid=root;Pwd=qwe123!@#;AllowZeroDateTime=True;ConvertZeroDateTime=True;", "usercenter": "Database=usercenter;Server=192.168.2.9;Port=3306;Uid=root;Pwd=qwe123!@#;AllowZeroDateTime=True;ConvertZeroDateTime=True;" }, "Redis": { - "master": "" + "master": "172.29.33.83:16379,password=poiuyt)(*&^%,defaultDatabase=13,prefix=wgshare" + }, + "Agora": { + "appId": "dcfc466a6ecb4a1f972630065dfb1e75", + "appSecret": "fc77000e329b4be7a0e26fa789e20d00", + "tokenExpireTimeInSecond": 7200, + "apiPrefix": "https://api.sd-rtn.com/", + "clientId": "80cdc24f7dfa4497a37d98da95a3c4a4", + "clientSecret": "8323581d4d464114b1f324b26cc62e09" + }, + "OSS": { + "AccessKeyID": "LTAI5tQYVQHkkXxXTmjwiSDv", + "AccessKeySecret": "FKFNYRdS53FwA5ME2wM1585qX5eVEd", + "Endpoint": "oss-cn-chengdu.aliyuncs.com", + "Host": "https://wgshare.oss-cn-chengdu.aliyuncs.com/", + "BucketName": "wgshare" } } diff --git a/WGShare.Domain/AgoraApiResult/AgoraResponse.cs b/WGShare.Domain/AgoraApiResult/AgoraResponse.cs new file mode 100644 index 0000000..9fde832 --- /dev/null +++ b/WGShare.Domain/AgoraApiResult/AgoraResponse.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace WGShare.Domain.AgoraApiResult +{ + public class AgoraResponse + { + public bool success { get; set; } + + public T Data { get; set; } + } +} diff --git a/WGShare.Domain/AgoraApiResult/Channel.cs b/WGShare.Domain/AgoraApiResult/Channel.cs new file mode 100644 index 0000000..cb70bf6 --- /dev/null +++ b/WGShare.Domain/AgoraApiResult/Channel.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace WGShare.Domain.AgoraApiResult +{ + public class Channel + { + public List channels { get; set; } + public int total_size { get; set; } + } + + public class ChannelUserCount + { + public string channel_name { get; set; } + + public int user_count { get; set; } + } +} diff --git a/WGShare.Domain/AgoraApiResult/ChannelUser.cs b/WGShare.Domain/AgoraApiResult/ChannelUser.cs new file mode 100644 index 0000000..c8b7e03 --- /dev/null +++ b/WGShare.Domain/AgoraApiResult/ChannelUser.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace WGShare.Domain.AgoraApiResult +{ + public class ChannelUser + { + /// + /// 频道是否存在 + /// + public bool channel_exist { get; set; } + + /// + /// 频道场景 1:通信场景 2:直播场景 + /// + public int mode { get; set; } + + /// + /// 频道总人数 + /// + public int total { get; set; } + + public List users { get; set; } + } +} diff --git a/WGShare.Domain/Constant/RedisKeyConstant.cs b/WGShare.Domain/Constant/RedisKeyConstant.cs new file mode 100644 index 0000000..4a39640 --- /dev/null +++ b/WGShare.Domain/Constant/RedisKeyConstant.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace WGShare.Domain.Constant +{ + /// + /// redis key 常量值 + /// + public class RedisKeyConstant + { + /// + /// 会话管理 + /// + public class SessionManage + { + /// + /// 在线人数 + /// + public static string GetOnlineUserKey(string tenantId) => $@"te_{tenantId}:OnlieUser"; + + /// + /// 频道用户 + /// + /// + /// + /// + public static string GetChannelUserKey(string tenantId, string roomNum) => $@"te_{tenantId}:ch_{roomNum}"; + + /// + /// 用户参与频道 + /// + /// + /// + public static string GetUserJoinChannelKey(string uid) => $@"u_{uid}_join"; + + /// + /// 频道用户数 + /// + /// + /// + public static string GetChannelUserCountKey(string tenantId) => $@"te_{tenantId}:ChannelUserCount"; + } + } +} diff --git a/WGShare.Domain/DTOs/File/ShareFileOutputDTO.cs b/WGShare.Domain/DTOs/File/ShareFileOutputDTO.cs index a145339..723fc08 100644 --- a/WGShare.Domain/DTOs/File/ShareFileOutputDTO.cs +++ b/WGShare.Domain/DTOs/File/ShareFileOutputDTO.cs @@ -16,7 +16,7 @@ namespace WGShare.Domain.DTOs.File /// public string FileUrl { get; set; } /// - /// 文件大小 kb + /// 文件大小 字节数 /// public double Size { get; set; } /// diff --git a/WGShare.Domain/DTOs/Room/RoomOutputDTO.cs b/WGShare.Domain/DTOs/Room/RoomOutputDTO.cs index e85a478..bc49fcc 100644 --- a/WGShare.Domain/DTOs/Room/RoomOutputDTO.cs +++ b/WGShare.Domain/DTOs/Room/RoomOutputDTO.cs @@ -20,5 +20,9 @@ namespace WGShare.Domain.DTOs.Room /// 会议号 /// public string RoomNum { get; set; } + /// + /// 在线人数 + /// + public int OnlineUserCount { get; set; } } } diff --git a/WGShare.Domain/DTOs/User/UserInputDTO.cs b/WGShare.Domain/DTOs/User/UserInputDTO.cs index 6576755..b003ca3 100644 --- a/WGShare.Domain/DTOs/User/UserInputDTO.cs +++ b/WGShare.Domain/DTOs/User/UserInputDTO.cs @@ -22,7 +22,7 @@ namespace WGShare.Domain.DTOs.User /// /// 密码 /// - public string Pwd { get; set; } + public string? Pwd { get; set; } /// /// /// @@ -30,6 +30,6 @@ namespace WGShare.Domain.DTOs.User /// /// 租户id /// - public string TenantId { get; set; } + public string? TenantId { get; set; } } } diff --git a/WGShare.Domain/DTOs/User/UserOutputDTO.cs b/WGShare.Domain/DTOs/User/UserOutputDTO.cs index b04cbb1..0f16066 100644 --- a/WGShare.Domain/DTOs/User/UserOutputDTO.cs +++ b/WGShare.Domain/DTOs/User/UserOutputDTO.cs @@ -21,5 +21,10 @@ namespace WGShare.Domain.DTOs.User public string RoleId { get; set; } public string RoleName { get; set; } + /// + /// 是否管理员 + /// + public bool IsManager { get; set; } + } } diff --git a/WGShare.Domain/Entities/ShareFile.cs b/WGShare.Domain/Entities/ShareFile.cs index 3f0bccb..d451541 100644 --- a/WGShare.Domain/Entities/ShareFile.cs +++ b/WGShare.Domain/Entities/ShareFile.cs @@ -44,7 +44,7 @@ namespace WGShare.Domain.Entities [SugarColumn(ColumnName = "user_id")] public string UserId { get; set; } /// - /// 文件大小 kb + /// 文件大小 字节数 /// [SugarColumn(ColumnName = "size")] public double Size { get; set; } @@ -63,5 +63,10 @@ namespace WGShare.Domain.Entities /// [SugarColumn(ColumnName = "download_count")] public int DownloadCount { get; set; } + /// + /// 文件是否被删除 + /// + [SugarColumn(ColumnName = "is_fileclean")] + public bool IsFileClean { get; set; } } } diff --git a/agora_key_and_secret.txt b/agora_key_and_secret.txt new file mode 100644 index 0000000..6824db3 --- /dev/null +++ b/agora_key_and_secret.txt @@ -0,0 +1,3 @@ +证书在下载后将不再控制台保存或展示,请妥善保管证书 +key:80cdc24f7dfa4497a37d98da95a3c4a4 +secret:8323581d4d464114b1f324b26cc62e09 \ No newline at end of file