From f411023e25efb93fd6cff196077dca65269943ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=B3=E9=A9=B9=20=E4=B8=87?= Date: Mon, 5 Aug 2024 14:57:32 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=AF=E6=8C=81JT1078=E8=BD=ACGB=EF=BC=8C?= =?UTF-8?q?=E9=80=9A=E8=BF=87HTTP=E6=8E=A5=E5=8F=A3=E6=B1=87=E6=8A=A5?= =?UTF-8?q?=E8=BD=A6=E8=BE=86=E4=B8=8A=E4=B8=8B=E7=BA=BF=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + GBWeb/Controllers/JT2GBController.cs | 58 +++++++++ GBWeb/Controllers/RTVSController.cs | 22 ++-- GBWeb/Startup.cs | 1 + SipServer/Cascade/CascadeChannelItem.cs | 34 ++++-- SipServer/Cascade/CascadeClient.cs | 93 +++++++------- SipServer/Cascade/CascadeManager.cs | 33 ++++- SipServer/DBModel/TCatalog.cs | 9 ++ SipServer/DBModel/TGroupBind.cs | 2 +- SipServer/DBModel/TJtinfo.cs | 29 +++++ SipServer/DBModel/gbsContext.cs | 53 ++++++-- SipServer/JT2GB/JT2GBChannel.cs | 101 ++++++++++++++++ SipServer/JT2GB/JT2GBClient.cs | 153 ++++++++++++++++++++++++ SipServer/JT2GB/JT2GBManager.cs | 106 ++++++++++++++++ SipServer/Models/FromTagCache.cs | 19 +++ SipServer/Models/JT/JTItem.cs | 25 ++++ SipServer/Models/JT/JTKey.cs | 29 +++++ SipServer/Models/SuperiorChannel.cs | 2 +- SipServer/SipServer.cs | 10 ++ SipServer/SipServer.csproj | 4 +- 20 files changed, 715 insertions(+), 69 deletions(-) create mode 100644 GBWeb/Controllers/JT2GBController.cs create mode 100644 SipServer/DBModel/TJtinfo.cs create mode 100644 SipServer/JT2GB/JT2GBChannel.cs create mode 100644 SipServer/JT2GB/JT2GBClient.cs create mode 100644 SipServer/JT2GB/JT2GBManager.cs create mode 100644 SipServer/Models/FromTagCache.cs create mode 100644 SipServer/Models/JT/JTItem.cs create mode 100644 SipServer/Models/JT/JTKey.cs diff --git a/.gitignore b/.gitignore index f30637c..0c38802 100644 --- a/.gitignore +++ b/.gitignore @@ -374,3 +374,4 @@ FodyWeavers.xsd /GBWeb/wwwroot/iconfont.js /GBWeb/wwwroot/css /GBWeb/wwwroot/favicon.ico +/SipServer/SipServer.xml diff --git a/GBWeb/Controllers/JT2GBController.cs b/GBWeb/Controllers/JT2GBController.cs new file mode 100644 index 0000000..24a998c --- /dev/null +++ b/GBWeb/Controllers/JT2GBController.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using GB28181.XML; +using GBWeb.Attribute; +using GBWeb.Models; +using Microsoft.AspNetCore.Mvc; +using SipServer.Models.JT; + +namespace GBWeb.Controllers +{ + /// + /// JT1078转GB28181接口 + /// + [Route("api/[controller]/[action]")] + [ApiController, AuthApi] + public class JT2GBController : ControllerBase + { + /// + /// JT1078车机上线/心跳通知(批量) + /// + /// + /// + [HttpPost] + public async Task Online(List lst) + { + try + { + await Program.sipServer.JT2GB.AddJTItems(lst); + + return new ApiResult(200); + } + catch + { + return new ApiResult(500) { message = "err" }; + } + } + /// + /// JT1078车机下线通知(批量) + /// + /// + /// + [HttpPost] + public async Task Offline(List lst) + { + try + { + await Program.sipServer.JT2GB.RemoveJTItems(lst); + + return new ApiResult(200); + } + catch + { + return new ApiResult(500) { message = "err" }; + } + } + } +} diff --git a/GBWeb/Controllers/RTVSController.cs b/GBWeb/Controllers/RTVSController.cs index 3e06b57..689ad02 100644 --- a/GBWeb/Controllers/RTVSController.cs +++ b/GBWeb/Controllers/RTVSController.cs @@ -1,15 +1,8 @@ using System; -using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; -using GB28181.MANSRTSP; using GB28181.XML; using GBWeb.Attribute; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using SQ.Base; -using Swashbuckle.AspNetCore.Annotations; namespace GBWeb.Controllers { @@ -45,6 +38,16 @@ public async Task Invite(string DeviceID, string Channel, string InviteI { return await client.Send_INVITE(Channel, InviteID, Base64ToStr(SDP), TalkCov); } + else if (Program.sipServer.TryGetJTClient(DeviceID, out var jtclient)) + { + var ret = await jtclient.Send_INVITE(Channel, InviteID, Base64ToStr(SDP), TalkCov); + if (ret == "1") + { + //此处不等待,因为ACK后会发流,如果等待可能出现流媒体还没收到应答不接收流。 + _ = jtclient.On_ACK(InviteID); + } + return ret; + } else { return "0"; @@ -106,6 +109,11 @@ public async Task Bye(string DeviceID, string Channel, string InviteID, await client.Send_Bye(InviteID); return "1"; } + else if (Program.sipServer.TryGetJTClient(DeviceID, out var jtclient)) + { + var ret = await jtclient.On_BYE(InviteID); + return "1"; + } else { return "0"; diff --git a/GBWeb/Startup.cs b/GBWeb/Startup.cs index bfbabd0..6588c8f 100644 --- a/GBWeb/Startup.cs +++ b/GBWeb/Startup.cs @@ -49,6 +49,7 @@ public void ConfigureServices(IServiceCollection services) c.SwaggerDoc("api", new OpenApiInfo { Title = "API", Version = "v1" }); c.IncludeXmlComments(System.IO.Path.Combine(AppContext.BaseDirectory, "GBWeb.xml"), true); c.IncludeXmlComments(System.IO.Path.Combine(AppContext.BaseDirectory, "GB28181.xml"), true); + c.IncludeXmlComments(System.IO.Path.Combine(AppContext.BaseDirectory, "SipServer.xml"), true); c.OperationFilter(); diff --git a/SipServer/Cascade/CascadeChannelItem.cs b/SipServer/Cascade/CascadeChannelItem.cs index fa39594..ae11860 100644 --- a/SipServer/Cascade/CascadeChannelItem.cs +++ b/SipServer/Cascade/CascadeChannelItem.cs @@ -1,26 +1,42 @@ using GB28181.Client; using GB28181.XML; +using SipServer.JT2GB; using SipServer.Models; -using System; -using System.Collections.Generic; -using System.Text; namespace SipServer.Cascade { - + /// + /// 级联通道项 + /// public class CascadeChannelItem : ChannelItem { + /// + /// 级联KEY GUID + /// public string SuperiorId; + /// + /// 设备ID + /// public string DeviceId; + /// + /// 真实通道ID 如果上报自定义了会包含在CatalogItem.DeviceID里 + /// public string ChannelId; + /// + /// 设备对象 + /// public GBChannel GBChannel; + /// + /// 1078对象 不为null时表示此通道为1078转28181模式 + /// + public JT2GBChannel J2GChannel; - public CascadeChannelItem(string SuperiorId, string DeviceId, string ChannelId, Catalog.Item CatalogItem) : base(CatalogItem) + public CascadeChannelItem(string superiorId, string deviceId, string channelId, Catalog.Item catalogItem, JT2GBChannel j2gChannel) : base(catalogItem) { - this.SuperiorId = SuperiorId; - this.ChannelId = ChannelId; - this.DeviceId = DeviceId; + this.SuperiorId = superiorId; + this.ChannelId = channelId; + this.DeviceId = deviceId; + this.J2GChannel = j2gChannel; } - } } diff --git a/SipServer/Cascade/CascadeClient.cs b/SipServer/Cascade/CascadeClient.cs index b3a5673..80c5a01 100644 --- a/SipServer/Cascade/CascadeClient.cs +++ b/SipServer/Cascade/CascadeClient.cs @@ -3,6 +3,7 @@ using GB28181.MANSRTSP; using GB28181.XML; using JTServer.Model.RTVS; +using SipServer.JT2GB; using SipServer.Models; using SIPSorcery.Net; using SIPSorcery.SIP; @@ -34,7 +35,11 @@ protected override string GetKey(CascadeChannelItem item) protected override void OnChannelItemAdd(CascadeChannelItem item) { bool Online = false; - if (client.manager.sipServer.TryGetClient(item.DeviceId, out var gbClient) && gbClient.TryGetChannel(item.ChannelId, out var gbChannel)) + if (item.J2GChannel != null) + { + Online = item.J2GChannel.IsOnline(); + } + else if (client.manager.sipServer.TryGetClient(item.DeviceId, out var gbClient) && gbClient.TryGetChannel(item.ChannelId, out var gbChannel)) { Online = gbChannel.Data.Online; item.GBChannel = gbChannel; @@ -73,19 +78,16 @@ protected override void OnChannelItemUpdate(CascadeChannelItem old, CascadeChann } } } - protected class fromTagCache - { - public string TaskID; - - public SDP28181 sdp; - } private CascadeManager manager; public string Key { get; protected set; } - /// - /// 通道信息 - /// - protected ConcurrentDictionary ditChannels = new ConcurrentDictionary(); - protected ConcurrentDictionary ditFromTagCache = new ConcurrentDictionary(); + public string DeviceID { get { return deviceInfo.DeviceID; } } + public List GroupIds { get; internal protected set; } + ///// + ///// 通道信息 + ///// + //protected ConcurrentDictionary ditChannels = new ConcurrentDictionary(); + //TODO:需超时回收 + protected ConcurrentDictionary ditFromTagCache = new ConcurrentDictionary(); public CascadeClient(CascadeManager manager, string Key, string server, string server_id, DeviceInfo deviceInfo, List channels, string authUsername = null, string password = "123456", int expiry = 7200, string UserAgent = "rtvs v1", bool EnableTraceLogs = false, double heartSec = 60, double timeOutSec = 300, int localPort = 0) : base(server, server_id, deviceInfo, authUsername, password, expiry, UserAgent, EnableTraceLogs, heartSec, timeOutSec) { @@ -105,7 +107,7 @@ public CascadeClient(CascadeManager manager, string Key, string server, string s this.ditChild = new CascadeChannelDictionary(this); foreach (var item in channels) { - AddChannel(item); + AddChannel(item, null); } } @@ -171,14 +173,21 @@ protected override async Task On_INVITE(string fromTag, SDP28181 sdp, try { var did = sipRequest.Header.To.ToURI.User; - if (ditChannels.TryGetValue(did, out var channel)) + if (ditChild.TryGetValue(did, out var channel)) { - var str = await HttpHelperByHttpClient.HttpRequestHtml(manager.sipServer.Settings.RTVSAPI + $"api/GB/CreateSendRTPTask?Protocol=2&Sim={channel.DeviceId}&Channel={did}&RTPServer={sdp.RtpIp}&RTPPort={sdp.RtpPort}&UseUdp={(sdp.NetType == SDP28181.RTPNetType.TCP ? "false" : "true")}", false, CancellationToken.None); - - var res = str.ParseJSON(); + SendRTPTask res; + if (channel.J2GChannel != null) + { + res = await channel.J2GChannel.INVITE_API(sdp); + } + else + { + var str = await HttpHelperByHttpClient.HttpRequestHtml(manager.sipServer.Settings.RTVSAPI + $"api/GB/CreateSendRTPTask?Protocol=2&Sim={channel.DeviceId}&Channel={did}&RTPServer={sdp.RtpIp}&RTPPort={sdp.RtpPort}&UseUdp={(sdp.NetType == SDP28181.RTPNetType.TCP ? "false" : "true")}", false, CancellationToken.None); + res = str.ParseJSON(); + } if (res.Code == StateCode.Success) { - ditFromTagCache[fromTag] = new fromTagCache + ditFromTagCache[fromTag] = new FromTagCache { TaskID = res.TaskID, sdp = sdp @@ -197,26 +206,30 @@ protected override async Task On_INVITE(string fromTag, SDP28181 sdp, protected override async Task On_RECORDINFO(RecordInfoQuery res, SIPRequest sipRequest) { - if (ditChannels.TryGetValue(res.DeviceID, out var channel) && manager.sipServer.TryGetClient(channel.DeviceId, out var client)) + if (ditChild.TryGetValue(res.DeviceID, out var channel)) { - var oldsn = res.SN; - await client.Send_GetRecordInfo(res, p => + if (channel.J2GChannel != null) { - p.SN = oldsn; - return AnsRecordInfo(sipRequest, p); - }); - return null; - } - else - { - return new RecordInfo + //channel.J2GChannel. + } + else if (manager.sipServer.TryGetClient(channel.DeviceId, out var client)) { - DeviceID = res.DeviceID, - Name = channel?.Name ?? "Unknown", - SN = res.SN, - SumNum = 0, - }; + var oldsn = res.SN; + await client.Send_GetRecordInfo(res, p => + { + p.SN = oldsn; + return AnsRecordInfo(sipRequest, p); + }); + return null; + } } + return new RecordInfo + { + DeviceID = res.DeviceID, + Name = channel?.CatalogItem?.Name ?? "Unknown", + SN = res.SN, + SumNum = 0, + }; } protected override async Task On_MANSRTSP(string fromTag, SIPRequest sipRequest) { @@ -224,7 +237,7 @@ protected override async Task On_MANSRTSP(string fromTag, SIPReque { if (ditFromTagCache.TryGetValue(fromTag, out var item)) { - if (ditChannels.TryGetValue(item.sdp.Owner, out var channel) && manager.sipServer.TryGetClient(channel.DeviceId, out var client)) + if (ditChild.TryGetValue(item.sdp.Owner, out var channel) && manager.sipServer.TryGetClient(channel.DeviceId, out var client)) { if (await client.Send_MANSRTSP(fromTag, sipRequest.Body, async p => { @@ -260,10 +273,10 @@ public override void Stop(bool waitStop = true) } } - protected internal void AddChannel(SuperiorChannel item) + protected internal void AddChannel(SuperiorChannel item, JT2GBChannel j2gChannel) { var channel_id = item.GetChannelId(); - ditChannels[channel_id] = item; + //ditChannels[channel_id] = item; var ci = new Catalog.Item { DeviceID = channel_id, @@ -282,7 +295,7 @@ protected internal void AddChannel(SuperiorChannel item) Password = Empty2Null(item.Password), Status = item.Status, }; - if (item.DType < 1 || item.DType > 3) + if (item.IsDevice()) { ci.Parental = item.Parental ? 1 : 0; ci.SafetyWay = item.SafetyWay; @@ -300,11 +313,11 @@ protected internal void AddChannel(SuperiorChannel item) ci.Longitude = item.Longitude; if (item.Latitude > 0) ci.Latitude = item.Latitude; - ditChild.AddOrUpdate(new CascadeChannelItem(item.SuperiorId, item.DeviceId, item.ChannelId, ci)); + ditChild.AddOrUpdate(new CascadeChannelItem(item.SuperiorId, item.DeviceId, item.ChannelId, ci, j2gChannel)); } protected internal void RemoveChannel(string ChannelId) { - ditChannels.Remove(ChannelId, out var item); + //ditChannels.Remove(ChannelId, out var item); ditChild.TryRemove(ChannelId, out var citem); } diff --git a/SipServer/Cascade/CascadeManager.cs b/SipServer/Cascade/CascadeManager.cs index ad0f123..1e57f95 100644 --- a/SipServer/Cascade/CascadeManager.cs +++ b/SipServer/Cascade/CascadeManager.cs @@ -14,6 +14,10 @@ public class CascadeManager /// 客户端 /// protected ConcurrentDictionary ditClient = new ConcurrentDictionary(); + /// + /// GroupId对应客户端列表 + /// + protected ConcurrentDictionary> ditGroupClients = new ConcurrentDictionary>(); protected internal SipServer sipServer; /// @@ -92,7 +96,8 @@ public async Task Remove(params string[] ids) async Task AddClient(TSuperiorInfo sinfo) { var groups = await sipServer.DB.GetSuperiorGroups(sinfo.Id); - var tmp = await sipServer.DB.GetSuperiorChannels(sinfo.Id, groups.Select(p => p.GroupId).ToList()); + var groupIds = groups.Select(p => p.GroupId).ToList(); + var tmp = await sipServer.DB.GetSuperiorChannels(sinfo.Id, groupIds); var id = sinfo.ClientId; var ClientName = string.IsNullOrEmpty(sinfo.ClientName) ? sinfo.Name : sinfo.ClientName; var root = new SuperiorChannel(new TCatalog @@ -109,7 +114,7 @@ async Task AddClient(TSuperiorInfo sinfo) Secrecy = false, DType = 1, }, - sinfo.ServerId, null, null + sinfo.Id, null, null ); var channels = new List { @@ -126,7 +131,7 @@ async Task AddClient(TSuperiorInfo sinfo) ParentId = string.IsNullOrEmpty(item.ParentId) ? id : item.ParentId, DType = item.GroupId.GetIdType() == "215" ? 2 : 3, }, - sinfo.ServerId, null, null + sinfo.Id, null, null ); if (channel.ChannelId.GetIdType() == "216" && channel.ParentId.GetIdType() == "215") { @@ -162,8 +167,17 @@ async Task AddClient(TSuperiorInfo sinfo) }, channels, authUsername: sinfo.Sipusername, password: sinfo.Sippassword, expiry: sinfo.Expiry, UserAgent: sipServer.UserAgent, EnableTraceLogs: sipServer.Settings.EnableSipLog, heartSec: sinfo.HeartSec, timeOutSec: sinfo.HeartTimeoutTimes * sinfo.HeartSec , localPort: sinfo.ClientPort); + client.GroupIds = groupIds; client.Start(); ditClient[client.Key] = client; + foreach (var groupId in groupIds) + { + var lst = ditGroupClients.GetOrAdd(groupId, key => + { + return new List(); + }); + lst.Add(client); + } return true; } bool RemoveClient(string key) @@ -171,6 +185,14 @@ bool RemoveClient(string key) if (ditClient.TryRemove(key, out var client)) { client.Stop(); + foreach (var groupId in client.GroupIds) + { + if (ditGroupClients.TryGetValue(groupId, out var lst)) + { + lst.Remove(client); + } + } + client.GroupIds.Clear(); return true; } return false; @@ -181,5 +203,10 @@ public CascadeClient GetClient(string key) ditClient.TryGetValue(key, out var client); return client; } + public List GetClientByGroupId(string groupId) + { + ditGroupClients.TryGetValue(groupId, out var lst); + return lst; + } } } diff --git a/SipServer/DBModel/TCatalog.cs b/SipServer/DBModel/TCatalog.cs index 689e489..0b105a9 100644 --- a/SipServer/DBModel/TCatalog.cs +++ b/SipServer/DBModel/TCatalog.cs @@ -123,9 +123,18 @@ public partial class TCatalog public DateTime? OfflineTime { get; set; } /// /// 类型 0设备/1系统目录/2业务分组/3虚拟组织 + /// /1001 JT2GB /// public int DType { get; set; } + public bool IsDevice() + { + return DType < 1 || DType > 3; + } + public bool IsJT2GB() + { + return DType == 1001; + } public void CopyFrom(TCatalog item) { diff --git a/SipServer/DBModel/TGroupBind.cs b/SipServer/DBModel/TGroupBind.cs index b8f976b..be56284 100644 --- a/SipServer/DBModel/TGroupBind.cs +++ b/SipServer/DBModel/TGroupBind.cs @@ -21,7 +21,7 @@ public partial class TGroupBind /// public string ChannelId { get; set; } /// - /// 自定义通道ID(上报用) + /// 自定义通道ID(上报用,如不自定义需与ChannelID保持一致) /// public string CustomChannelId { get; set; } } diff --git a/SipServer/DBModel/TJtinfo.cs b/SipServer/DBModel/TJtinfo.cs new file mode 100644 index 0000000..81fbadb --- /dev/null +++ b/SipServer/DBModel/TJtinfo.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; + +namespace SipServer.DBModel +{ + public partial class TJTinfo + { + /// + /// CatalogID + /// + public string ChannelId { get; set; } + /// + /// 设备ID + /// + public string DeviceId { get; set; } + /// + /// 1078SIM卡号 + /// + public string JTsim { get; set; } + /// + /// 1078通道号 + /// + public int JTchannel { get; set; } + /// + /// 是否2019版本 + /// + public ulong Is2019 { get; set; } + } +} diff --git a/SipServer/DBModel/gbsContext.cs b/SipServer/DBModel/gbsContext.cs index 279c68f..7885472 100644 --- a/SipServer/DBModel/gbsContext.cs +++ b/SipServer/DBModel/gbsContext.cs @@ -21,6 +21,7 @@ public gbsContext(DbContextOptions options) public virtual DbSet TEvents { get; set; } public virtual DbSet TGroups { get; set; } public virtual DbSet TGroupBinds { get; set; } + public virtual DbSet TJTinfos { get; set; } public virtual DbSet TSuperiorGroups { get; set; } public virtual DbSet TSuperiorInfos { get; set; } public virtual DbSet TUserInfos { get; set; } @@ -392,9 +393,9 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity(entity => { - entity.HasKey(e => new { e.GroupId, e.DeviceId, e.CustomChannelId }) + entity.HasKey(e => new { e.GroupId, e.CustomChannelId }) .HasName("PRIMARY") - .HasAnnotation("MySql:IndexPrefixLength", new[] { 0, 0, 0 }); + .HasAnnotation("MySql:IndexPrefixLength", new[] { 0, 0 }); entity.ToTable("T_GroupBind"); @@ -407,11 +408,6 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .HasColumnName("GroupID") .HasComment("分组ID"); - entity.Property(e => e.DeviceId) - .HasMaxLength(50) - .HasColumnName("DeviceID") - .HasComment("绑定设备ID"); - entity.Property(e => e.CustomChannelId) .HasMaxLength(50) .HasColumnName("CustomChannelID") @@ -422,6 +418,49 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .HasMaxLength(50) .HasColumnName("ChannelID") .HasComment("绑定通道ID(0代表所有)"); + + entity.Property(e => e.DeviceId) + .IsRequired() + .HasMaxLength(50) + .HasColumnName("DeviceID") + .HasComment("绑定设备ID"); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => new { e.ChannelId, e.DeviceId }) + .HasName("PRIMARY") + .HasAnnotation("MySql:IndexPrefixLength", new[] { 0, 0 }); + + entity.ToTable("T_JTInfo"); + + entity.HasCharSet("utf8") + .UseCollation("utf8_general_ci"); + + entity.Property(e => e.ChannelId) + .HasMaxLength(50) + .HasColumnName("ChannelID") + .HasComment("CatalogID"); + + entity.Property(e => e.DeviceId) + .HasMaxLength(50) + .HasColumnName("DeviceID") + .HasComment("设备ID"); + + entity.Property(e => e.Is2019) + .HasColumnType("bit(1)") + .HasComment("是否2019版本"); + + entity.Property(e => e.JTchannel) + .HasColumnType("int(11)") + .HasColumnName("JTChannel") + .HasComment("1078通道号"); + + entity.Property(e => e.JTsim) + .IsRequired() + .HasMaxLength(20) + .HasColumnName("JTSim") + .HasComment("1078SIM卡号"); }); modelBuilder.Entity(entity => diff --git a/SipServer/JT2GB/JT2GBChannel.cs b/SipServer/JT2GB/JT2GBChannel.cs new file mode 100644 index 0000000..c0a430c --- /dev/null +++ b/SipServer/JT2GB/JT2GBChannel.cs @@ -0,0 +1,101 @@ +using GB28181; +using System.Threading.Tasks; +using SQ.Base; +using System.Threading; +using JTServer.Model.RTVS; +using System.Collections.Generic; +using SipServer.Cascade; +using System; + +namespace SipServer.JT2GB +{ + public class JT2GBChannel + { + /// + /// GB28181通道号 + /// + public string Key { get; protected set; } + /// + /// Sim卡号(JT1078) + /// + string sim; + /// + /// 通道号(JT1078) + /// + int channel; + /// + /// 2019版本 + /// + bool is2019; + + JT2GBClient client; + /// + /// 在线状态 + /// + private bool online; + /// + /// 过期时间(秒) 小于等于0表示不过期 + /// + private long expiresIn; + /// + /// 绑定的级联客户端列表 + /// + private List cascadeClients; + + private DateTime heartbeatTime; + + public JT2GBChannel(JT2GBClient client, string gbChannelId, string sim, int channel, bool is2019) + { + this.client = client; + this.sim = sim; + this.channel = channel; + this.is2019 = is2019; + this.online = true; + this.Key = gbChannelId; + } + + + public async Task INVITE_API(SDP28181 sdp) + { + var str = await HttpHelperByHttpClient.HttpRequestHtml(client.manager.sipServer.Settings.RTVSAPI + $"api/GB/CreateSendRTPTask?Protocol={(is2019 ? "1" : "0")}&Sim={sim}&Channel={channel}&RTPServer={sdp.RtpIp}&RTPPort={sdp.RtpPort}&UseUdp={(sdp.NetType == SDP28181.RTPNetType.TCP ? "false" : "true")}", false, CancellationToken.None); + return str.ParseJSON(); + } + + protected internal void Offline() + { + online = false; + if (cascadeClients != null && cascadeClients.Count > 0) + { + foreach (var item in cascadeClients) + { + item.RemoveChannel(Key); + } + cascadeClients = null; + } + } + + protected internal void Online(long expiresIn, List lstCascadeClient) + { + this.heartbeatTime = DateTime.Now; + this.expiresIn = expiresIn; + cascadeClients = lstCascadeClient; + } + public bool IsOnline() + { + return online; + } + /// + /// 判断心跳超时 + /// + /// + public bool IsTimeOut() + { + if (expiresIn > 0 && heartbeatTime.DiffNowSec() > expiresIn) + { + Offline(); + return true; + } + return false; + } + } +} \ No newline at end of file diff --git a/SipServer/JT2GB/JT2GBClient.cs b/SipServer/JT2GB/JT2GBClient.cs new file mode 100644 index 0000000..ba7c024 --- /dev/null +++ b/SipServer/JT2GB/JT2GBClient.cs @@ -0,0 +1,153 @@ +using GB28181; +using JTServer.Model.RTVS; +using SQ.Base; +using System; +using System.Threading.Tasks; +using System.Threading; +using SipServer.Models; +using System.Collections.Concurrent; +using GB28181.XML; +using SIPSorcery.SIP; +using Newtonsoft.Json.Linq; +using System.Linq; + +namespace SipServer.JT2GB +{ + public class JT2GBClient + { + /// + /// GB28181设备ID + /// + public string Key { get; protected set; } + protected internal JT2GBManager manager; + /// + /// 通道信息 + /// + protected ConcurrentDictionary ditChannels = new ConcurrentDictionary(); + protected ConcurrentDictionary ditFromTagCache = new ConcurrentDictionary(); + + public JT2GBClient(string gbDeviceId, JT2GBManager manager) + { + this.Key = gbDeviceId; + this.manager = manager; + } + + + internal JT2GBChannel GetOrAddChannel(string key, Func valueFactory) => ditChannels.GetOrAdd(key, valueFactory); + internal bool TryGetValue(string key, out JT2GBChannel value) => ditChannels.TryGetValue(key, out value); + internal bool TryRemove(string key, out JT2GBChannel value) => ditChannels.TryRemove(key, out value); + + + /// + /// 发起视频 + /// + /// 通道 一般是IPCID + /// + /// + /// 1 成功 2需要转换为广播 3已转换为广播并发送 + public async Task Send_INVITE(string Channel, string fromTag, string SDP, InviteTalk TalkCov) + { + var res = await On_INVITE(Channel, fromTag, SDP28181.NewByStr(SDP)); + if (res != null) + return "1"; + else + return "0"; + } + protected async Task On_INVITE(string did, string fromTag, SDP28181 sdp) + { + try + { + if (ditChannels.TryGetValue(did, out var channel)) + { + var res = await channel.INVITE_API(sdp); + if (res.Code == StateCode.Success) + { + ditFromTagCache[fromTag] = new FromTagCache + { + TaskID = res.TaskID, + sdp = sdp + }; + var ans = sdp.AnsSdp(did, res.LocIP, res.LocIP, res.LocPort); + return ans; + } + } + } + catch (Exception) + { + + } + return null; + } + + public async Task On_ACK(string fromTag) + { + try + { + if (ditFromTagCache.TryGetValue(fromTag, out var item)) + { + string url; + switch (item.sdp.SType) + { + case SDP28181.PlayType.Play: + if (item.sdp.Media == SDP28181.MediaType.audio) + url = $"{manager.sipServer.Settings.RTVSAPI}api/GB/StartRealPlay?TaskID={item.TaskID}&InviteID={fromTag}&SSRC={item.sdp.SSRC}&DataType=3"; + else + url = $"{manager.sipServer.Settings.RTVSAPI}api/GB/StartRealPlay?TaskID={item.TaskID}&InviteID={fromTag}&SSRC={item.sdp.SSRC}"; + break; + case SDP28181.PlayType.Playback: + url = $"{manager.sipServer.Settings.RTVSAPI}api/GB/StartPlayback?TaskID={item.TaskID}&InviteID={fromTag}&SSRC={item.sdp.SSRC}&StartTime={item.sdp.TStart.UNIXtoDateTime()}&EndTime={item.sdp.TEnd.UNIXtoDateTime()}"; + break; + case SDP28181.PlayType.Download: + url = $"{manager.sipServer.Settings.RTVSAPI}api/GB/StartPlayback?TaskID={item.TaskID}&InviteID={fromTag}&SSRC={item.sdp.SSRC}&StartTime={item.sdp.TStart.UNIXtoDateTime()}&EndTime={item.sdp.TEnd.UNIXtoDateTime()}&DownloadSpeed={item.sdp.Downloadspeed}"; + break; + case SDP28181.PlayType.Talk: + url = $"{manager.sipServer.Settings.RTVSAPI}api/GB/StartRealPlay?TaskID={item.TaskID}&InviteID={fromTag}&SSRC={item.sdp.SSRC}&DataType=2"; + break; + default: + return false; + } + var str = await HttpHelperByHttpClient.HttpRequestHtml(url, false, CancellationToken.None); + var res = str.ParseJSON(); + return res.Code == StateCode.Success; + } + } + catch (Exception) + { + + } + return false; + } + + public async Task On_BYE(string fromTag) + { + try + { + if (ditFromTagCache.TryGetValue(fromTag, out var item)) + { + var str = await SQ.Base.HttpHelperByHttpClient.HttpRequestHtml(manager.sipServer.Settings.RTVSAPI + $"api/GB/Stop?TaskID={item.TaskID}", false, CancellationToken.None); + var res = str.ParseJSON(); + return res.Code == StateCode.Success || res.Code == StateCode.NotFoundTask; + } + } + catch (Exception) + { + + } + return false; + } + + + public bool Check() + { + var clients = ditChannels.Values.ToList(); + foreach (var item in clients) + { + if (item.IsTimeOut()) + { + ditChannels.TryRemove(item.Key, out var _); + } + } + return ditChannels.Count > 0; + } + } +} \ No newline at end of file diff --git a/SipServer/JT2GB/JT2GBManager.cs b/SipServer/JT2GB/JT2GBManager.cs new file mode 100644 index 0000000..2c7eb0e --- /dev/null +++ b/SipServer/JT2GB/JT2GBManager.cs @@ -0,0 +1,106 @@ +using SipServer.DBModel; +using SipServer.Models.JT; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace SipServer.JT2GB +{ + public class JT2GBManager + { + /// + /// 客户端 + /// + protected ConcurrentDictionary ditClient = new ConcurrentDictionary(); + protected internal SipServer sipServer; + public JT2GBManager(SipServer sipServer) + { + this.sipServer = sipServer; + } + public bool TryGetClient(string DeviceID, out JT2GBClient value) + { + if (DeviceID == null) + { + value = null; + return false; + } + return ditClient.TryGetValue(DeviceID, out value); + } + + + public async Task AddJTItems(List lst) + { + foreach (JTItem item in lst) + { + var client = ditClient.GetOrAdd(item.GBDeviceId, key => + { + return new JT2GBClient(item.GBDeviceId, this); + }); + var channel = client.GetOrAddChannel(item.GBChannelId, key => + { + return new JT2GBChannel(client, item.GBChannelId, item.JTSim, item.JTChannel, item.JTVer == 1); + }); + if (item.GBGroupID != null) + { + var lstCascadeClient = sipServer.Cascade.GetClientByGroupId(item.GBGroupID); + channel.Online(item.ExpiresIn, lstCascadeClient); + if (lstCascadeClient != null && lstCascadeClient.Count > 0) + { + foreach (var cascadeClient in lstCascadeClient) + { + cascadeClient.AddChannel(new Models.SuperiorChannel(new TCatalog + { + ChannelId = item.GBChannelId, + DeviceId = item.GBDeviceId, + Name = item.GBChannelName, + Manufacturer = "RTVS", + Model = "gbsip", + Owner = "Owner", + CivilCode = item.GBChannelId.Substring(0, 6), + Address = "Address", + RegisterWay = 1, + Secrecy = false, + DType = 1001, + Online = true, + ParentId = cascadeClient.DeviceID + "/" + item.GBGroupID, + Status = "ON", + }, cascadeClient.Key, item.GBChannelId, item.GBGroupID), channel); + } + } + } + } + } + public async Task RemoveJTItems(List lst) + { + foreach (JTKey item in lst) + { + if (ditClient.TryGetValue(item.GBDeviceId, out var client)) + { + if (client.TryRemove(item.GBChannelId, out var channel)) + { + channel.Offline(); + } + } + } + } + + public void Check() + { + try + { + var clients = ditClient.Values.ToList(); + foreach (var item in clients) + { + if (!item.Check()) + { + ditClient.TryRemove(item.Key, out var _); + } + } + } + catch + { + } + } + } +} \ No newline at end of file diff --git a/SipServer/Models/FromTagCache.cs b/SipServer/Models/FromTagCache.cs new file mode 100644 index 0000000..9154e8b --- /dev/null +++ b/SipServer/Models/FromTagCache.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using GB28181; +using GB28181.XML; +using SipServer.DBModel; +using SQ.Base; + +namespace SipServer.Models +{ + /// + /// + /// + public class FromTagCache + { + public string TaskID; + + public SDP28181 sdp; + } +} diff --git a/SipServer/Models/JT/JTItem.cs b/SipServer/Models/JT/JTItem.cs new file mode 100644 index 0000000..2fab9b3 --- /dev/null +++ b/SipServer/Models/JT/JTItem.cs @@ -0,0 +1,25 @@ +namespace SipServer.Models.JT +{ + /// + /// + /// + public class JTItem : JTKey + { + /// + /// 设备名称 + /// + public string GBDeviceName { get; set; } + /// + /// GB28181通道名称 + /// + public string GBChannelName { get; set; } + /// + /// 绑定分组ID + /// + public string GBGroupID { get; set; } + /// + /// 过期时间(秒) 小于等于0表示不过期 + /// + public long ExpiresIn { get; set; } + } +} \ No newline at end of file diff --git a/SipServer/Models/JT/JTKey.cs b/SipServer/Models/JT/JTKey.cs new file mode 100644 index 0000000..348655b --- /dev/null +++ b/SipServer/Models/JT/JTKey.cs @@ -0,0 +1,29 @@ +namespace SipServer.Models.JT +{ + /// + /// + /// + public class JTKey + { + /// + /// Sim卡号(JT1078) + /// + public string JTSim { get; set; } + /// + /// 通道号(JT1078) + /// + public int JTChannel { get; set; } + /// + /// JT1078版本 1:2019/其他 2013 + /// + public int JTVer { get; set; } + /// + /// GB28181设备ID + /// + public string GBDeviceId { get; set; } + /// + /// GB28181通道号 + /// + public string GBChannelId { get; set; } + } +} \ No newline at end of file diff --git a/SipServer/Models/SuperiorChannel.cs b/SipServer/Models/SuperiorChannel.cs index 09a0464..a5bcb18 100644 --- a/SipServer/Models/SuperiorChannel.cs +++ b/SipServer/Models/SuperiorChannel.cs @@ -22,7 +22,7 @@ public SuperiorChannel(TCatalog item, string superiorId, string customChannelId, } public ulong RowId { get; set; } /// - /// 上级ID + /// 级联KEY GUID /// public string SuperiorId { get; set; } /// diff --git a/SipServer/SipServer.cs b/SipServer/SipServer.cs index 5bbff92..b871215 100644 --- a/SipServer/SipServer.cs +++ b/SipServer/SipServer.cs @@ -1,5 +1,6 @@ using SipServer.Cascade; using SipServer.DB; +using SipServer.JT2GB; using SIPSorcery.SIP; using SIPSorcery.SIP.App; using SQ.Base; @@ -37,6 +38,10 @@ public class SipServer /// public CascadeManager Cascade { get; protected set; } /// + /// 1078转28181管理 + /// + public JT2GBManager JT2GB { get; protected set; } + /// /// SIP监听(包含TCP、UDPV4、UDPV6) /// public SIPTransport SipTransport { get; protected set; } @@ -107,6 +112,7 @@ public SipServer() Log.WriteLog4Ex("SipServer", ex); } Cascade = new CascadeManager(this); + JT2GB = new JT2GBManager(this); } @@ -177,6 +183,7 @@ private void Check(object tag, CancellationToken cancellationToken) } } }); + JT2GB.Check(); //var tags = ditFromTag.Values.ToList(); //foreach (var item in tags) @@ -286,6 +293,9 @@ public void RemoveClient(string DeviceID, bool updateDB = true, string CallID = client.Dispose(updateDB); } } + #region JT2GB + public bool TryGetJTClient(string DeviceID, out JT2GBClient value) => JT2GB.TryGetClient(DeviceID, out value); + #endregion #endregion #region 接收处理 diff --git a/SipServer/SipServer.csproj b/SipServer/SipServer.csproj index 233100e..1e08905 100644 --- a/SipServer/SipServer.csproj +++ b/SipServer/SipServer.csproj @@ -1,7 +1,9 @@ - + netstandard2.1 + True + SipServer.xml