完成考勤表Excel导出
This commit is contained in:
parent
aad7f3df40
commit
7e5d20a64a
|
|
@ -0,0 +1,90 @@
|
||||||
|
using Hangfire;
|
||||||
|
using Hangfire.Server;
|
||||||
|
using Mapster;
|
||||||
|
using Masuit.Tools;
|
||||||
|
using SqlSugar;
|
||||||
|
using WGShare.API.Helpers;
|
||||||
|
using WGShare.Domain.Constant;
|
||||||
|
using WGShare.Domain.DTOs.AgoraCallback;
|
||||||
|
using WGShare.Domain.Entities;
|
||||||
|
|
||||||
|
namespace WGShare.API.BackgroudServices
|
||||||
|
{
|
||||||
|
public class AgoraCallbackComsuerService : BackgroundService
|
||||||
|
{
|
||||||
|
private readonly ILogger<AgoraCallbackComsuerService> _logger;
|
||||||
|
private readonly ISqlSugarClient _sugarClient;
|
||||||
|
private readonly SemaphoreSlim _semaphore;
|
||||||
|
|
||||||
|
public AgoraCallbackComsuerService(ILogger<AgoraCallbackComsuerService> logger, ISqlSugarClient sugarClient)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
this._sugarClient = sugarClient;
|
||||||
|
_semaphore = new SemaphoreSlim(1);// 允许最多1个线程同时访问
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("参会记录监听服务已启动");
|
||||||
|
|
||||||
|
stoppingToken.Register(() => _logger.LogInformation("RedisSubscriberService is stopping."));
|
||||||
|
|
||||||
|
// 离开频道消息订阅
|
||||||
|
using (RedisHelper.Instance.SubscribeList(RedisKeyConstant.PubSub.MeetingRecord, async (message) =>
|
||||||
|
{
|
||||||
|
_logger.LogDebug($"接受消息: {message}");
|
||||||
|
if (message == null) return;
|
||||||
|
|
||||||
|
|
||||||
|
var body = message.ToString().FromJson<EventBody>();
|
||||||
|
if (body == null)
|
||||||
|
{
|
||||||
|
_logger.LogError("消息体为空");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (body.payload.uid.ToString().Length == 9)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("共享屏幕不记录参会记录");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await MeetingRecord(body);
|
||||||
|
|
||||||
|
}))
|
||||||
|
|
||||||
|
while (!stoppingToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
await Task.Delay(10000, stoppingToken); // 等待一段时间,然后继续循环
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation("参会记录监听服务已停止");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task MeetingRecord(EventBody body)
|
||||||
|
{
|
||||||
|
await _semaphore.WaitAsync();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
|
||||||
|
var entity = body.payload.Adapt<MeetingRecord>();
|
||||||
|
entity.EventType = body.eventType;
|
||||||
|
|
||||||
|
await _sugarClient.Storageable(entity)
|
||||||
|
.WhereColumns(x => new { x.uid, x.clientSeq })
|
||||||
|
.ToStorage()
|
||||||
|
.AsInsertable
|
||||||
|
.ExecuteCommandAsync();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_semaphore.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,56 +0,0 @@
|
||||||
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<OssCleanWorker> _logger;
|
|
||||||
private readonly ISqlSugarClient _sugarClient;
|
|
||||||
private readonly OssHelper _ossHelper;
|
|
||||||
|
|
||||||
public OssCleanWorker(ILogger<OssCleanWorker> 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<ShareFile>()
|
|
||||||
.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<ShareFile>()
|
|
||||||
.SetColumns(x => x.IsFileClean == true)
|
|
||||||
.Where(x => deleteFiles.Select(a => a.Id).Contains(x.Id))
|
|
||||||
.ExecuteCommandAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
await Console.Out.WriteLineAsync($@"本次清除操作结束,当前时间:{DateTime.Now}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -2,13 +2,18 @@
|
||||||
using AgoraIO.Rtm;
|
using AgoraIO.Rtm;
|
||||||
using Mapster;
|
using Mapster;
|
||||||
using Masuit.Tools;
|
using Masuit.Tools;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Filters;
|
||||||
|
using SharpCompress;
|
||||||
using SqlSugar;
|
using SqlSugar;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using WGShare.API.Controllers.Basic;
|
using WGShare.API.Controllers.Basic;
|
||||||
using WGShare.API.Helpers;
|
using WGShare.API.Helpers;
|
||||||
using WGShare.Domain.AgoraApiResult;
|
using WGShare.Domain.AgoraApiResult;
|
||||||
using WGShare.Domain.Constant;
|
using WGShare.Domain.Constant;
|
||||||
|
using WGShare.Domain.DTOs.AgoraCallback;
|
||||||
using WGShare.Domain.DTOs.Room;
|
using WGShare.Domain.DTOs.Room;
|
||||||
using WGShare.Domain.Entities;
|
using WGShare.Domain.Entities;
|
||||||
using WGShare.Domain.FriendlyException;
|
using WGShare.Domain.FriendlyException;
|
||||||
|
|
@ -21,21 +26,112 @@ namespace WGShare.API.Controllers.Frontend
|
||||||
/// Agora接口
|
/// Agora接口
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ApiExplorerSettings(GroupName = "frontend")]
|
[ApiExplorerSettings(GroupName = "frontend")]
|
||||||
[Route("agora-cb")]
|
[Route("agora-cb"), AllowAnonymous]
|
||||||
public class AgoraCallbackController : BasicController
|
public class AgoraCallbackController : BasicController
|
||||||
{
|
{
|
||||||
private readonly ILogger<AgoraCallbackController> _logger;
|
private readonly ILogger<AgoraCallbackController> _logger;
|
||||||
|
private readonly AgoraHelper _agoraHelper;
|
||||||
|
private readonly IConfiguration _configuration;
|
||||||
|
|
||||||
public AgoraCallbackController(
|
public AgoraCallbackController(
|
||||||
ILogger<AgoraCallbackController> logger)
|
ILogger<AgoraCallbackController> logger,
|
||||||
|
AgoraHelper agoraHelper,
|
||||||
|
IConfiguration configuration)
|
||||||
{
|
{
|
||||||
this._logger = logger;
|
this._logger = logger;
|
||||||
|
this._agoraHelper = agoraHelper;
|
||||||
|
this._configuration = configuration;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[HttpPost("event")]
|
[HttpPost("event")]
|
||||||
public async Task<string> Event()
|
public async Task Event()
|
||||||
{
|
{
|
||||||
return string.Empty;
|
using var reader = new StreamReader(Request.Body);
|
||||||
|
var bodyString = await reader.ReadToEndAsync();
|
||||||
|
|
||||||
|
//_logger.LogInformation($"Agora回调内容:{bodyString}");
|
||||||
|
|
||||||
|
// 校验请求体是否为空
|
||||||
|
if (string.IsNullOrWhiteSpace(bodyString))
|
||||||
|
{
|
||||||
|
await ExceptionNotice.SendAsync(new ArgumentNullException("Agora-Signature-V2 声网请求体为空"), "声网回调异常");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var body = bodyString.FromJson<EventBody>();
|
||||||
|
if (body == null)
|
||||||
|
{
|
||||||
|
await ExceptionNotice.SendAsync(new InvalidDataException("Agora-Signature-V2 声网请求体反序列化失败"), "声网回调异常");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 声网健康检查回调,直接返回
|
||||||
|
if (body.payload.channelName == "test_webhook")
|
||||||
|
{
|
||||||
|
_logger.LogDebug($"测试回调,测试信息:{bodyString}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证签名
|
||||||
|
var sig = Request.Headers["Agora-Signature-V2"].ToString();
|
||||||
|
if (!await _agoraHelper.CheckSignatureAsync(sig, bodyString))
|
||||||
|
{
|
||||||
|
_logger.LogWarning($"Agora回调签名验证失败,声网签名:{sig}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (body.eventType)
|
||||||
|
{
|
||||||
|
case Domain.Enums.EventType.channel_create:
|
||||||
|
break;
|
||||||
|
case Domain.Enums.EventType.channel_destroy:
|
||||||
|
break;
|
||||||
|
case Domain.Enums.EventType.broadcaster_join_channel:
|
||||||
|
case Domain.Enums.EventType.audience_join_channel:
|
||||||
|
JoinChannelEvent(bodyString);
|
||||||
|
break;
|
||||||
|
case Domain.Enums.EventType.broadcaster_leave_channel:
|
||||||
|
case Domain.Enums.EventType.audience_leave_channel:
|
||||||
|
LeaveChannelEvent(bodyString);
|
||||||
|
break;
|
||||||
|
case Domain.Enums.EventType.client_role_change_to_broadcaster:
|
||||||
|
break;
|
||||||
|
case Domain.Enums.EventType.client_role_change__to_audience:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
await ExceptionNotice.SendAsync(new Exception("声网事件未知类型"), "声网回调异常");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 加入频道
|
||||||
|
/// </summary>
|
||||||
|
[NonAction]
|
||||||
|
private void JoinChannelEvent(string bodyString)
|
||||||
|
{
|
||||||
|
_logger.LogDebug($"Agora回调内容 加入频道:{bodyString}");
|
||||||
|
|
||||||
|
RedisHelper.Instance.LPush(RedisKeyConstant.PubSub.MeetingRecord, bodyString);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 离开频道
|
||||||
|
/// </summary>
|
||||||
|
[NonAction]
|
||||||
|
private void LeaveChannelEvent(string bodyString)
|
||||||
|
{
|
||||||
|
_logger.LogDebug($"Agora回调内容 离开频道:{bodyString}");
|
||||||
|
|
||||||
|
// 离会记录
|
||||||
|
RedisHelper.Instance.LPush(RedisKeyConstant.PubSub.MeetingRecord, bodyString);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,17 +3,20 @@ using AgoraIO.Rtm;
|
||||||
using Mapster;
|
using Mapster;
|
||||||
using Masuit.Tools;
|
using Masuit.Tools;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using MiniExcelLibs;
|
||||||
using SqlSugar;
|
using SqlSugar;
|
||||||
using System.Net.Http;
|
using System.IO;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using WGShare.API.Controllers.Basic;
|
using WGShare.API.Controllers.Basic;
|
||||||
using WGShare.API.Helpers;
|
using WGShare.API.Helpers;
|
||||||
using WGShare.Domain.AgoraApiResult;
|
|
||||||
using WGShare.Domain.Constant;
|
using WGShare.Domain.Constant;
|
||||||
using WGShare.Domain.DTOs.Room;
|
using WGShare.Domain.DTOs.Room;
|
||||||
using WGShare.Domain.Entities;
|
using WGShare.Domain.Entities;
|
||||||
|
using WGShare.Domain.Enums;
|
||||||
using WGShare.Domain.FriendlyException;
|
using WGShare.Domain.FriendlyException;
|
||||||
using WGShare.Domain.GeneralModel;
|
using WGShare.Domain.GeneralModel;
|
||||||
using Yitter.IdGenerator;
|
using Yitter.IdGenerator;
|
||||||
|
using IConfiguration = Microsoft.Extensions.Configuration.IConfiguration;
|
||||||
|
|
||||||
namespace WGShare.API.Controllers.Frontend
|
namespace WGShare.API.Controllers.Frontend
|
||||||
{
|
{
|
||||||
|
|
@ -29,19 +32,22 @@ namespace WGShare.API.Controllers.Frontend
|
||||||
private readonly IHttpClientFactory _httpClientFactory;
|
private readonly IHttpClientFactory _httpClientFactory;
|
||||||
private readonly AgoraHelper _agoraHelper;
|
private readonly AgoraHelper _agoraHelper;
|
||||||
private readonly ILogger<HomeController> _logger;
|
private readonly ILogger<HomeController> _logger;
|
||||||
|
private readonly OssHelper _ossHelper;
|
||||||
|
|
||||||
public HomeController(
|
public HomeController(
|
||||||
ISqlSugarClient sqlSugar,
|
ISqlSugarClient sqlSugar,
|
||||||
IConfiguration configuration,
|
IConfiguration configuration,
|
||||||
IHttpClientFactory httpClientFactory,
|
IHttpClientFactory httpClientFactory,
|
||||||
AgoraHelper agoraHelper,
|
AgoraHelper agoraHelper,
|
||||||
ILogger<HomeController> logger)
|
ILogger<HomeController> logger,
|
||||||
|
OssHelper ossHelper)
|
||||||
{
|
{
|
||||||
_sqlSugar = sqlSugar;
|
_sqlSugar = sqlSugar;
|
||||||
this._configuration = configuration;
|
this._configuration = configuration;
|
||||||
this._httpClientFactory = httpClientFactory;
|
this._httpClientFactory = httpClientFactory;
|
||||||
this._agoraHelper = agoraHelper;
|
this._agoraHelper = agoraHelper;
|
||||||
this._logger = logger;
|
this._logger = logger;
|
||||||
|
this._ossHelper = ossHelper;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -101,6 +107,20 @@ namespace WGShare.API.Controllers.Frontend
|
||||||
return await _sqlSugar.Insertable(entity).ExecuteCommandAsync() > 0;
|
return await _sqlSugar.Insertable(entity).ExecuteCommandAsync() > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 删除会议室
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="inputDTO"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
[HttpDelete("room")]
|
||||||
|
public async Task<bool> DeleteRoom([FromQuery] string roomId)
|
||||||
|
{
|
||||||
|
return await _sqlSugar.Updateable<Room>()
|
||||||
|
.SetColumns(x => x.IsDelete == true)
|
||||||
|
.Where(x => x.Id == roomId).ExecuteCommandAsync() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取 rtm token
|
/// 获取 rtm token
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -124,5 +144,104 @@ namespace WGShare.API.Controllers.Frontend
|
||||||
{
|
{
|
||||||
return _configuration["Agora:appId"].ToString();
|
return _configuration["Agora:appId"].ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取会议记录
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
[HttpGet("record")]
|
||||||
|
public async Task<string> GetMeetingRecord([FromQuery] long beginTimestamp, [FromQuery] long endTimestamp, [FromQuery] string roomNum)
|
||||||
|
{
|
||||||
|
var room = await _sqlSugar.Queryable<Room>().Where(x => x.RoomNum == roomNum && x.IsDelete == false).FirstAsync();
|
||||||
|
if (room == null)
|
||||||
|
{
|
||||||
|
throw Oops.Oh("该会议室不存在!");
|
||||||
|
}
|
||||||
|
|
||||||
|
var recordList = await _sqlSugar.Queryable<MeetingRecord>()
|
||||||
|
.InnerJoin<User>((mr, u) => mr.uid == u.Id)
|
||||||
|
.InnerJoin<Role>((mr, u, r) => u.RoleId == r.Id)
|
||||||
|
.Where((mr, u, r) => mr.channelName == roomNum && mr.ts >= beginTimestamp && mr.ts <= endTimestamp)
|
||||||
|
.Select((mr, u, r) => new MeetingRecord
|
||||||
|
{
|
||||||
|
Id = mr.Id,
|
||||||
|
uid = u.Id,
|
||||||
|
ts = mr.ts,
|
||||||
|
account = mr.account,
|
||||||
|
channelName = mr.channelName,
|
||||||
|
clientSeq = mr.clientSeq,
|
||||||
|
CreateTime = mr.CreateTime,
|
||||||
|
duration = mr.duration,
|
||||||
|
EventType = mr.EventType,
|
||||||
|
platform = mr.platform,
|
||||||
|
reason = mr.reason,
|
||||||
|
userName = u.UserName,
|
||||||
|
UserAccount = u.Account,
|
||||||
|
RoleName = r.RoleName,
|
||||||
|
}).ToListAsync();
|
||||||
|
|
||||||
|
|
||||||
|
if (recordList.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
throw Oops.Oh("该时间段内没有记录!");
|
||||||
|
}
|
||||||
|
|
||||||
|
var groupByUser = recordList.GroupBy(x => x.uid).ToList();
|
||||||
|
|
||||||
|
var value = new RoomMettingRecordExportDTO
|
||||||
|
{
|
||||||
|
RoomName = room.RoomName,
|
||||||
|
RoomNum = room.RoomNum,
|
||||||
|
BeginTime = DateTimeUtils.FromJavaScriptTimestampToLocal(beginTimestamp).ToString("yyyy-MM-dd HH:mm:ss"),
|
||||||
|
EndTime = DateTimeUtils.FromJavaScriptTimestampToLocal(endTimestamp).ToString("yyyy-MM-dd HH:mm:ss"),
|
||||||
|
Users = new List<UserBehavior>()
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var userRecord in groupByUser)
|
||||||
|
{
|
||||||
|
if (userRecord.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// 按时间排序
|
||||||
|
var orderedRecord = userRecord.OrderBy(x => x.ts).ToList();
|
||||||
|
|
||||||
|
// 获取第一次进入房间的事件记录
|
||||||
|
var firstJoinTime = orderedRecord
|
||||||
|
.FirstOrDefault(x => x.EventType == EventType.broadcaster_join_channel
|
||||||
|
|| x.EventType == EventType.audience_join_channel);
|
||||||
|
|
||||||
|
// 获取最后一次离开房间的事件记录
|
||||||
|
var lastLeaveTime = orderedRecord
|
||||||
|
.LastOrDefault(x => x.EventType == EventType.broadcaster_leave_channel
|
||||||
|
|| x.EventType == EventType.audience_leave_channel);
|
||||||
|
|
||||||
|
// 计算入会次数
|
||||||
|
var joinCount = orderedRecord.Count(x => x.EventType == EventType.broadcaster_join_channel
|
||||||
|
|| x.EventType == EventType.audience_join_channel);
|
||||||
|
|
||||||
|
// 累计参会时长
|
||||||
|
var sumTime = orderedRecord.Sum(x => x.duration);
|
||||||
|
|
||||||
|
value.Users.Add(new UserBehavior
|
||||||
|
{
|
||||||
|
Account = userRecord.FirstOrDefault().UserAccount,
|
||||||
|
FirstJoinTime = DateTimeUtils.FromJavaScriptTimestampToLocal(firstJoinTime.ts).ToString("yyyy-MM-dd HH:mm:ss"),
|
||||||
|
LastExitTime = DateTimeUtils.FromJavaScriptTimestampToLocal(lastLeaveTime.ts).ToString("yyyy-MM-dd HH:mm:ss"),
|
||||||
|
JoinCount = joinCount,
|
||||||
|
Role = userRecord.FirstOrDefault().RoleName,
|
||||||
|
UserName = userRecord.FirstOrDefault().userName,
|
||||||
|
SumTime = sumTime,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
using var stream = new MemoryStream();
|
||||||
|
|
||||||
|
await MiniExcel.SaveAsByTemplateAsync(stream, $@"{AppDomain.CurrentDomain.BaseDirectory}meetingRecordTemplate.xlsx", value);
|
||||||
|
stream.Seek(0, SeekOrigin.Begin);
|
||||||
|
var fileName = $@"excel/{room.RoomName}-参会记录.xlsx";
|
||||||
|
_ossHelper.UploadWithExpireTime(fileName, stream, 10);
|
||||||
|
return _ossHelper.GetAccessFileUrl(fileName, 5);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using Masuit.Tools;
|
using Masuit.Tools;
|
||||||
using System.Drawing.Printing;
|
using System.Drawing.Printing;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using WGShare.Domain.AgoraApiResult;
|
using WGShare.Domain.AgoraApiResult;
|
||||||
using WGShare.Domain.FriendlyException;
|
using WGShare.Domain.FriendlyException;
|
||||||
|
|
@ -127,6 +128,59 @@ namespace WGShare.API.Helpers
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 将加密后的字节数组转换成字符串
|
||||||
|
private string BytesToHex(byte[] bytes)
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
foreach (byte b in bytes)
|
||||||
|
{
|
||||||
|
string hex = b.ToString("x2");
|
||||||
|
sb.Append(hex);
|
||||||
|
}
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// HMAC/SHA256 加密,返回加密后的字符串
|
||||||
|
private string HmacSha256(string message, string secret)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
byte[] keyBytes = Encoding.UTF8.GetBytes(secret);
|
||||||
|
byte[] messageBytes = Encoding.UTF8.GetBytes(message);
|
||||||
|
|
||||||
|
using (HMACSHA256 hmacsha256 = new HMACSHA256(keyBytes))
|
||||||
|
{
|
||||||
|
byte[] hashBytes = hmacsha256.ComputeHash(messageBytes);
|
||||||
|
return BytesToHex(hashBytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
throw new Exception("Error in HmacSha256", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 验证声网回调签名
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="agoraSig"></param>
|
||||||
|
/// <param name="bodyString"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<bool> CheckSignatureAsync(string agoraSig, string bodyString)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(agoraSig))
|
||||||
|
{
|
||||||
|
await ExceptionNotice.SendAsync(new ArgumentNullException("Agora-Signature-V2 声网密钥为空"), "声网回调异常");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (HmacSha256(bodyString, _configuration["Agora:eventSecret"].ToString()) != agoraSig)
|
||||||
|
{
|
||||||
|
await ExceptionNotice.SendAsync(new ArgumentNullException("Agora-Signature-V2 声网密钥验证失败"), "声网回调异常");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
namespace WGShare.API.Helpers
|
||||||
|
{
|
||||||
|
public class DateTimeUtils
|
||||||
|
{
|
||||||
|
|
||||||
|
// Unix 纪元时间
|
||||||
|
private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将 JavaScript 时间戳(以秒为单位)转换为 DateTime 对象(UTC 时间)。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="jsTimestamp">JavaScript 时间戳(以秒为单位)。</param>
|
||||||
|
/// <returns>对应的 DateTime 对象(UTC 时间)。</returns>
|
||||||
|
public static DateTime FromJavaScriptTimestamp(long jsTimestamp)
|
||||||
|
{
|
||||||
|
return Epoch.AddSeconds(jsTimestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将 DateTime 对象(UTC 时间)转换为 JavaScript 时间戳(以秒为单位)。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dateTime">DateTime 对象(UTC 时间)。</param>
|
||||||
|
/// <returns>对应的 JavaScript 时间戳(以秒为单位)。</returns>
|
||||||
|
public static long ToJavaScriptTimestamp(DateTime dateTime)
|
||||||
|
{
|
||||||
|
return (long)(dateTime.ToUniversalTime() - Epoch).Seconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将 JavaScript 时间戳(以秒为单位)转换为本地时间的 DateTime 对象。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="jsTimestamp">JavaScript 时间戳(以秒为单位)。</param>
|
||||||
|
/// <returns>对应的本地时间的 DateTime 对象。</returns>
|
||||||
|
public static DateTime FromJavaScriptTimestampToLocal(long jsTimestamp)
|
||||||
|
{
|
||||||
|
DateTime utcDateTime = FromJavaScriptTimestamp(jsTimestamp);
|
||||||
|
return utcDateTime.ToLocalTime();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -45,74 +45,6 @@ namespace WGShare.API.Helpers
|
||||||
/// <param name="maxTimeoutSeconds">最大秒数</param>
|
/// <param name="maxTimeoutSeconds">最大秒数</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static int RandomExpired(int minTimeoutSeconds, int maxTimeoutSeconds) => rnd.Value.Next(minTimeoutSeconds, maxTimeoutSeconds);
|
public static int RandomExpired(int minTimeoutSeconds, int maxTimeoutSeconds) => rnd.Value.Next(minTimeoutSeconds, maxTimeoutSeconds);
|
||||||
|
|
||||||
//public static List<T> HVals<T>(string key) where T : class
|
|
||||||
//{
|
|
||||||
// var valueStrings = Instance.HVals(key);
|
|
||||||
// if (valueStrings.IsNullOrEmpty())
|
|
||||||
// {
|
|
||||||
// return null;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return valueStrings.ToList().ConvertAll(x => JsonConvert.DeserializeObject<T>(x));
|
|
||||||
//}
|
|
||||||
|
|
||||||
//public static T HGet<T>(string key, string field) where T : class
|
|
||||||
//{
|
|
||||||
// var valueString = Instance.HGet(key, field);
|
|
||||||
// if (valueString.IsNullOrEmpty())
|
|
||||||
// {
|
|
||||||
// return null;
|
|
||||||
// }
|
|
||||||
// return JsonConvert.DeserializeObject<T>(valueString);
|
|
||||||
//}
|
|
||||||
|
|
||||||
//public static List<T> HMGet<T>(string key, params string[] fields) where T : class
|
|
||||||
//{
|
|
||||||
// var valueStrings = Instance.HMGet(key, fields);
|
|
||||||
// if (valueStrings.IsNullOrEmpty())
|
|
||||||
// {
|
|
||||||
// return null;
|
|
||||||
// }
|
|
||||||
// return valueStrings.ToList().ConvertAll(x => JsonConvert.DeserializeObject<T>(x));
|
|
||||||
//}
|
|
||||||
|
|
||||||
//public static Dictionary<string, T> HGetAll<T>(string key) where T : class
|
|
||||||
//{
|
|
||||||
// var dic = Instance.HGetAll(key);
|
|
||||||
// if (dic == null || dic.Count == 0)
|
|
||||||
// {
|
|
||||||
// return null;
|
|
||||||
// }
|
|
||||||
// Dictionary<string, T> result = new Dictionary<string, T>();
|
|
||||||
|
|
||||||
// foreach (var kv in dic)
|
|
||||||
// {
|
|
||||||
// if (string.IsNullOrWhiteSpace(kv.Key) || string.IsNullOrWhiteSpace(kv.Value))
|
|
||||||
// {
|
|
||||||
// continue;
|
|
||||||
// }
|
|
||||||
// result.Add(kv.Key, JsonConvert.DeserializeObject<T>(kv.Value));
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return result;
|
|
||||||
//}
|
|
||||||
|
|
||||||
|
|
||||||
//public static long HSet<T>(string key, string fields, T value) where T : class
|
|
||||||
//{
|
|
||||||
// return Instance.HSet(key, fields, value.ToJsonString());
|
|
||||||
//}
|
|
||||||
|
|
||||||
|
|
||||||
//public static void HMSet<T>(string key, Dictionary<string, T> keyValues) where T : class
|
|
||||||
//{
|
|
||||||
// var dic = new Dictionary<string, string>();
|
|
||||||
// foreach (var kv in keyValues)
|
|
||||||
// {
|
|
||||||
// dic.Add(kv.Key,kv.Value.ToJsonString());
|
|
||||||
// }
|
|
||||||
// Instance.HMSet(key, dic);
|
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@ namespace WGShare.API
|
||||||
builder.Services.AddSingleton(new JwtHelper(configuration));
|
builder.Services.AddSingleton(new JwtHelper(configuration));
|
||||||
builder.Services.AddSingleton(new AgoraHelper(configuration));
|
builder.Services.AddSingleton(new AgoraHelper(configuration));
|
||||||
builder.Services.AddSingleton(new OssHelper(configuration));
|
builder.Services.AddSingleton(new OssHelper(configuration));
|
||||||
builder.Services.AddHostedService<OssCleanWorker>();
|
builder.Services.AddHostedService<AgoraCallbackComsuerService>();
|
||||||
builder.Services.AddAuth(configuration["Jwt:Issuer"],
|
builder.Services.AddAuth(configuration["Jwt:Issuer"],
|
||||||
configuration["Jwt:Audience"],
|
configuration["Jwt:Audience"],
|
||||||
configuration["Jwt:SecretKey"]);
|
configuration["Jwt:SecretKey"]);
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,9 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<None Update="meetingRecordTemplate.xlsx">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
<None Update="Reference\AgoraIO.dll">
|
<None Update="Reference\AgoraIO.dll">
|
||||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,16 @@
|
||||||
Agora接口
|
Agora接口
|
||||||
</summary>
|
</summary>
|
||||||
</member>
|
</member>
|
||||||
|
<member name="M:WGShare.API.Controllers.Frontend.AgoraCallbackController.JoinChannelEvent(System.String)">
|
||||||
|
<summary>
|
||||||
|
加入频道
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="M:WGShare.API.Controllers.Frontend.AgoraCallbackController.LeaveChannelEvent(System.String)">
|
||||||
|
<summary>
|
||||||
|
离开频道
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
<member name="T:WGShare.API.Controllers.Frontend.HomeController">
|
<member name="T:WGShare.API.Controllers.Frontend.HomeController">
|
||||||
<summary>
|
<summary>
|
||||||
首页接口
|
首页接口
|
||||||
|
|
@ -85,6 +95,13 @@
|
||||||
<param name="inputDTO"></param>
|
<param name="inputDTO"></param>
|
||||||
<returns></returns>
|
<returns></returns>
|
||||||
</member>
|
</member>
|
||||||
|
<member name="M:WGShare.API.Controllers.Frontend.HomeController.DeleteRoom(System.String)">
|
||||||
|
<summary>
|
||||||
|
删除会议室
|
||||||
|
</summary>
|
||||||
|
<param name="inputDTO"></param>
|
||||||
|
<returns></returns>
|
||||||
|
</member>
|
||||||
<member name="M:WGShare.API.Controllers.Frontend.HomeController.GetRTMToken">
|
<member name="M:WGShare.API.Controllers.Frontend.HomeController.GetRTMToken">
|
||||||
<summary>
|
<summary>
|
||||||
获取 rtm token
|
获取 rtm token
|
||||||
|
|
@ -97,6 +114,12 @@
|
||||||
</summary>
|
</summary>
|
||||||
<returns></returns>
|
<returns></returns>
|
||||||
</member>
|
</member>
|
||||||
|
<member name="M:WGShare.API.Controllers.Frontend.HomeController.GetMeetingRecord(System.Int64,System.Int64,System.String)">
|
||||||
|
<summary>
|
||||||
|
获取会议记录
|
||||||
|
</summary>
|
||||||
|
<returns></returns>
|
||||||
|
</member>
|
||||||
<member name="T:WGShare.API.Controllers.Frontend.RoomController">
|
<member name="T:WGShare.API.Controllers.Frontend.RoomController">
|
||||||
<summary>
|
<summary>
|
||||||
会议室接口
|
会议室接口
|
||||||
|
|
@ -335,6 +358,35 @@
|
||||||
</summary>
|
</summary>
|
||||||
<returns></returns>
|
<returns></returns>
|
||||||
</member>
|
</member>
|
||||||
|
<member name="M:WGShare.API.Helpers.AgoraHelper.CheckSignatureAsync(System.String,System.String)">
|
||||||
|
<summary>
|
||||||
|
验证声网回调签名
|
||||||
|
</summary>
|
||||||
|
<param name="agoraSig"></param>
|
||||||
|
<param name="bodyString"></param>
|
||||||
|
<returns></returns>
|
||||||
|
</member>
|
||||||
|
<member name="M:WGShare.API.Helpers.DateTimeUtils.FromJavaScriptTimestamp(System.Int64)">
|
||||||
|
<summary>
|
||||||
|
将 JavaScript 时间戳(以秒为单位)转换为 DateTime 对象(UTC 时间)。
|
||||||
|
</summary>
|
||||||
|
<param name="jsTimestamp">JavaScript 时间戳(以秒为单位)。</param>
|
||||||
|
<returns>对应的 DateTime 对象(UTC 时间)。</returns>
|
||||||
|
</member>
|
||||||
|
<member name="M:WGShare.API.Helpers.DateTimeUtils.ToJavaScriptTimestamp(System.DateTime)">
|
||||||
|
<summary>
|
||||||
|
将 DateTime 对象(UTC 时间)转换为 JavaScript 时间戳(以秒为单位)。
|
||||||
|
</summary>
|
||||||
|
<param name="dateTime">DateTime 对象(UTC 时间)。</param>
|
||||||
|
<returns>对应的 JavaScript 时间戳(以秒为单位)。</returns>
|
||||||
|
</member>
|
||||||
|
<member name="M:WGShare.API.Helpers.DateTimeUtils.FromJavaScriptTimestampToLocal(System.Int64)">
|
||||||
|
<summary>
|
||||||
|
将 JavaScript 时间戳(以秒为单位)转换为本地时间的 DateTime 对象。
|
||||||
|
</summary>
|
||||||
|
<param name="jsTimestamp">JavaScript 时间戳(以秒为单位)。</param>
|
||||||
|
<returns>对应的本地时间的 DateTime 对象。</returns>
|
||||||
|
</member>
|
||||||
<member name="M:WGShare.API.Helpers.OssHelper.GetUploadUrl(System.String,System.String,System.UInt32)">
|
<member name="M:WGShare.API.Helpers.OssHelper.GetUploadUrl(System.String,System.String,System.UInt32)">
|
||||||
<summary>
|
<summary>
|
||||||
获取上传url
|
获取上传url
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
{
|
{
|
||||||
"Logging": {
|
"Logging": {
|
||||||
"LogLevel": {
|
"LogLevel": {
|
||||||
"Default": "Information",
|
"Default": "Debug",
|
||||||
"Microsoft.AspNetCore": "Warning"
|
"Microsoft.AspNetCore": "Information"
|
||||||
},
|
},
|
||||||
"Console": {
|
"Console": {
|
||||||
"LogLevel": {
|
"LogLevel": {
|
||||||
"Default": "Information",
|
"Default": "Debug",
|
||||||
"Microsoft": "Warning"
|
"Microsoft": "Warning",
|
||||||
|
"Hangfire": "Information"
|
||||||
}
|
}
|
||||||
//"FormatterName": "CustomTimePrefixingFormatter",
|
//"FormatterName": "CustomTimePrefixingFormatter",
|
||||||
//"FormatterOptions": {
|
//"FormatterOptions": {
|
||||||
|
|
@ -43,6 +44,7 @@
|
||||||
"tokenExpireTimeInSecond": 7200,
|
"tokenExpireTimeInSecond": 7200,
|
||||||
"apiPrefix": "https://api.sd-rtn.com/",
|
"apiPrefix": "https://api.sd-rtn.com/",
|
||||||
"clientId": "80cdc24f7dfa4497a37d98da95a3c4a4",
|
"clientId": "80cdc24f7dfa4497a37d98da95a3c4a4",
|
||||||
"clientSecret": "8323581d4d464114b1f324b26cc62e09"
|
"clientSecret": "8323581d4d464114b1f324b26cc62e09",
|
||||||
|
"eventSecret": "gPPaFQS1U"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,9 @@
|
||||||
"LogLevel": {
|
"LogLevel": {
|
||||||
"Default": "Information",
|
"Default": "Information",
|
||||||
"Microsoft.AspNetCore": "Warning"
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
},
|
||||||
|
"Console": {
|
||||||
|
"TimestampFormat": "[yyyy-MM-dd HH:mm:ss]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"AllowedHosts": "*",
|
"AllowedHosts": "*",
|
||||||
|
|
@ -33,6 +36,7 @@
|
||||||
"AccessKeySecret": "FKFNYRdS53FwA5ME2wM1585qX5eVEd",
|
"AccessKeySecret": "FKFNYRdS53FwA5ME2wM1585qX5eVEd",
|
||||||
"Endpoint": "oss-cn-chengdu.aliyuncs.com",
|
"Endpoint": "oss-cn-chengdu.aliyuncs.com",
|
||||||
"Host": "https://wgshare.oss-cn-chengdu.aliyuncs.com/",
|
"Host": "https://wgshare.oss-cn-chengdu.aliyuncs.com/",
|
||||||
"BucketName": "wgshare"
|
"BucketName": "wgshare",
|
||||||
|
"eventSecret": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -79,5 +79,12 @@ namespace WGShare.Domain.Constant
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 发布订阅
|
||||||
|
/// </summary>
|
||||||
|
public class PubSub
|
||||||
|
{
|
||||||
|
public static string MeetingRecord => "meeting_record";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Drawing;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
@ -27,7 +28,7 @@ namespace WGShare.Domain.DTOs.AgoraCallback
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 声网消息服务器向你的服务器发送事件通知的 Unix 时间戳 (ms)。通知重试时该值会更新。
|
/// 声网消息服务器向你的服务器发送事件通知的 Unix 时间戳 (ms)。通知重试时该值会更新。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int notifyMs { get; set; }
|
public long notifyMs { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 会话 ID。
|
/// 会话 ID。
|
||||||
|
|
@ -37,7 +38,7 @@ namespace WGShare.Domain.DTOs.AgoraCallback
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 通知事件的具体内容。payload 因 eventType 而异,详见频道事件类型。
|
/// 通知事件的具体内容。payload 因 eventType 而异,详见频道事件类型。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string payload { get; set; }
|
public AgoraCallbackPayload payload { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class AgoraCallbackPayload
|
public class AgoraCallbackPayload
|
||||||
|
|
@ -46,5 +47,54 @@ namespace WGShare.Domain.DTOs.AgoraCallback
|
||||||
/// 频道名称
|
/// 频道名称
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string channelName { get; set; }
|
public string channelName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 该事件在声网业务服务器上发生的 Unix 时间戳 (s)。
|
||||||
|
/// </summary>
|
||||||
|
public long ts { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 最后一个离开频道的用户 ID。(则声网消息通知可能返回不同的 lastUid,此时任选其一即可。)
|
||||||
|
/// </summary>
|
||||||
|
public long lastUid { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 主播设备所属平台
|
||||||
|
/// </summary>
|
||||||
|
public PlatformType platform { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 序列号,标识该事件在 App 客户端上发生的顺序,可用于对同一用户的事件进行排序。详见维护用户在线状态。
|
||||||
|
/// </summary>
|
||||||
|
public long clientSeq { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// String 类型的用户 ID。
|
||||||
|
/// </summary>
|
||||||
|
public string account { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 主播在频道内的时长 (s)。
|
||||||
|
/// </summary>
|
||||||
|
public int duration { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <para>主播离开频道的原因: </para>
|
||||||
|
/// <para>1:主播正常离开频道。</para>
|
||||||
|
/// <para>2:客户端与声网业务服务器连接超时。判断标准为声网 SD-RTN 超过 10 秒未收到该主播的任何数据包,或连接单台服务器 4 秒超时并在 1 秒内没有完成重连。</para>
|
||||||
|
/// <para>3:权限问题。如被运营人员通过踢人 RESTful API 踢出频道。</para>
|
||||||
|
/// <para>4:声网业务服务器内部原因。如声网业务服务器在调整负载,和客户端短暂断开连接,之后会重新连接。</para>
|
||||||
|
/// <para>5:主播切换新设备,迫使旧设备下线。</para>
|
||||||
|
/// <para>9:由于客户端有多个 IP 地址,SDK 主动与声网业务服务器断开连接并重连。此过程用户无感知。请检查用户是否存在多个公网 IP 或使用了 VPN。</para>
|
||||||
|
/// <para>10:由于网络连接问题,例如 SDK 超过 4 秒未收到来自声网业务服务器的任何数据包或 socket 连接错误,SDK 主动与声网业务服务器断开连接并重连。此过程用户无感知。请检查网络连接状态。</para>
|
||||||
|
/// <para>999:异常用户。例如,用户短时间内频繁登录登出频道会被判定为异常用户。信息:你的 App 服务端需要在收到 reason 为 999 的 104 事件 60 秒后调用踢人 API 将该用户踢出频道。否则,该用户再次加入频道后,可能无法收到相关事件通知。</para>
|
||||||
|
/// <para>0:其他原因。</para>
|
||||||
|
/// </summary>
|
||||||
|
public LeaveReasonEnum reason { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 主播在频道内的用户 ID。
|
||||||
|
/// </summary>
|
||||||
|
public long uid { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace WGShare.Domain.DTOs.Room
|
||||||
|
{
|
||||||
|
public class RoomMettingRecordExportDTO
|
||||||
|
{
|
||||||
|
public string RoomName { get; set; }
|
||||||
|
public string RoomNum { get; set; }
|
||||||
|
|
||||||
|
public string BeginTime { get; set; }
|
||||||
|
public string EndTime { get; set; }
|
||||||
|
|
||||||
|
public List<UserBehavior> Users { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class UserBehavior
|
||||||
|
{
|
||||||
|
public string UserName { get; set; }
|
||||||
|
public string Account { get; set; }
|
||||||
|
public string Role { get; set; }
|
||||||
|
public string FirstJoinTime { get; set; }
|
||||||
|
public string LastExitTime { get; set; }
|
||||||
|
public int JoinCount { get; set; }
|
||||||
|
public int SumTime { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,101 @@
|
||||||
|
using SqlSugar;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using WGShare.Domain.Enums;
|
||||||
|
using Yitter.IdGenerator;
|
||||||
|
|
||||||
|
namespace WGShare.Domain.Entities
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 会议室在会记录
|
||||||
|
///</summary>
|
||||||
|
[SugarTable("meeting_record")]
|
||||||
|
public class MeetingRecord
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
///</summary>
|
||||||
|
[SugarColumn(ColumnName = "id", IsPrimaryKey = true, IsIdentity = true)]
|
||||||
|
public long Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 事件类型
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(ColumnName = "event_type")]
|
||||||
|
public EventType EventType { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 客户端序列号
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(ColumnName = "client_seq")]
|
||||||
|
public long clientSeq { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 发生Unix时间戳
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(ColumnName = "ts")]
|
||||||
|
public long ts { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// 用户ID
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(ColumnName = "uid")]
|
||||||
|
public string uid { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// 创建时间
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(ColumnName = "create_time", IsOnlyIgnoreInsert = true, IsOnlyIgnoreUpdate = true)]
|
||||||
|
public DateTime CreateTime { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 频道号
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(ColumnName = "channel_name")]
|
||||||
|
public string channelName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 客户端类型
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(ColumnName = "platform")]
|
||||||
|
public PlatformType platform { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 离开原因
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(ColumnName = "leave_reason")]
|
||||||
|
public LeaveReasonEnum reason { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 在会时长
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(ColumnName = "duration")]
|
||||||
|
public int duration { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 字符串类型 用户id(声网返回)
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(ColumnName = "account")]
|
||||||
|
public string account { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 用户名称
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(IsIgnore = true)]
|
||||||
|
public string userName { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// 用户账号
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(IsIgnore = true)]
|
||||||
|
public string UserAccount { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 用户角色名称
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(IsIgnore = true)]
|
||||||
|
public string RoleName { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace WGShare.Domain.Enums
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 声网回调接口:主播离开频道的原因
|
||||||
|
/// </summary>
|
||||||
|
public enum LeaveReasonEnum
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 主播正常离开频道。
|
||||||
|
/// </summary>
|
||||||
|
normal = 1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 客户端与声网业务服务器连接超时。判断标准为声网 SD-RTN 超过 10 秒未收到该主播的任何数据包,或连接单台服务器 4 秒超时并在 1 秒内没有完成重连。
|
||||||
|
/// </summary>
|
||||||
|
timeout = 2,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 权限问题。如被运营人员通过踢人 RESTful API 踢出频道。
|
||||||
|
/// </summary>
|
||||||
|
permission = 3,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 声网业务服务器内部原因。如声网业务服务器在调整负载,和客户端短暂断开连接,之后会重新连接。
|
||||||
|
/// </summary>
|
||||||
|
agora_internal_error = 4,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 主播切换新设备,迫使旧设备下线。。
|
||||||
|
/// </summary>
|
||||||
|
force_logout = 5,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 由于客户端有多个 IP 地址,SDK 主动与声网业务服务器断开连接并重连。此过程用户无感知。请检查用户是否存在多个公网 IP 或使用了 VPN。
|
||||||
|
/// </summary>
|
||||||
|
multiple_ip = 9,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 由于网络连接问题,例如 SDK 超过 4 秒未收到来自声网业务服务器的任何数据包或 socket 连接错误,SDK 主动与声网业务服务器断开连接并重连。此过程用户无感知。请检查网络连接状态。
|
||||||
|
/// </summary>
|
||||||
|
network_error = 10,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 异常用户。例如,用户短时间内频繁登录登出频道会被判定为异常用户。
|
||||||
|
/// </summary>
|
||||||
|
abnormal_user = 999,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 其他原因
|
||||||
|
/// </summary>
|
||||||
|
other = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace WGShare.Domain.Enums
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 声网平台类型
|
||||||
|
/// </summary>
|
||||||
|
public enum PlatformType
|
||||||
|
{
|
||||||
|
Android = 1,
|
||||||
|
iOS = 2,
|
||||||
|
Windows = 5,
|
||||||
|
Linux = 6,
|
||||||
|
Web = 7,
|
||||||
|
MacOS = 8,
|
||||||
|
Other = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.6" />
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.6" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.6" />
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.6" />
|
||||||
<PackageReference Include="MiniExcel" Version="1.34.0" />
|
<PackageReference Include="MiniExcel" Version="1.34.0" />
|
||||||
<PackageReference Include="SqlSugarCore" Version="5.1.4.158" />
|
<PackageReference Include="SqlSugarCore" Version="5.1.4.169" />
|
||||||
<PackageReference Include="Yitter.IdGenerator" Version="1.0.14" />
|
<PackageReference Include="Yitter.IdGenerator" Version="1.0.14" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue