mirror of
https://github.com/kshdb/gb28181Panda.git
synced 2026-04-22 23:07:15 +08:00
基础功能
This commit is contained in:
+29
@@ -0,0 +1,29 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
logFile
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
# config配置文件
|
||||
logFile
|
||||
gb28181Panda
|
||||
@@ -0,0 +1,84 @@
|
||||
## 🐼项目描述
|
||||
|
||||
- GB28181服务
|
||||
- 信令sip及路由采用go
|
||||
- 流媒体采用夏楚大大的[ZLMediaKit](https://github.com/ZLMediaKit/ZLMediaKit)
|
||||
- 流媒体播放地址支持:rtsp、rtmp、wsFlv、m3u8、http-flv、fmp4、ts等地址
|
||||
|
||||
## 🔨技术栈
|
||||
|
||||
- Golang
|
||||
- Sip
|
||||
- ZLMediaKit
|
||||
|
||||
## 🚂搭配的前端项目
|
||||
|
||||
[gb28181PandaWeb](https://github.com/pans0930/gb28181PandaWeb)
|
||||
|
||||
## 🕹️线上测试系统地址
|
||||
|
||||
```
|
||||
http://1.117.28.81:9999/
|
||||
```
|
||||
|
||||
- 通道播放
|
||||

|
||||

|
||||
- 历史视屏回放
|
||||

|
||||
- 分屏展示效果
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
## 📺延迟效果
|
||||
|
||||
webrtc下大约在<=500ms左右,此情况根据不同的网络环境和服务器配置会有差距,延迟如下
|
||||
|
||||
- 摄像头webrtc延时如下
|
||||

|
||||
- 手机摄像头webrtc延时如下
|
||||

|
||||
|
||||
## 📖已实现的功能
|
||||
|
||||
- [x] 下级设备的上报和通道上报
|
||||
- [x] 下级设备的视频播放和视频停止
|
||||
- [x] 下级级联设备的上报和通道的上报
|
||||
- [x] 下级设备的视频播放和视频停止
|
||||
- [x] 预警消息订阅
|
||||
- [x] 历史视频的播放
|
||||
- [x] ptz控制【无设备未测试】
|
||||
- [x] 视频流传输模式:udp、tcp主动、tcp被动
|
||||
|
||||
## 📘TODO
|
||||
|
||||
- [ ] 国标级联
|
||||
- [ ] 历史视频播放的seek操作-暂停、倍速、下载等
|
||||
- [ ] 预警消息处理
|
||||
- [ ] 位置订阅
|
||||
- [ ] 上级级联播放本级视频
|
||||
- [ ] 防止设备伪造请求,除了[Register],其他数据都要先查询来源【ip】和【port】在【t_device】中是否存在,存在放行数据,不存在直接丢弃
|
||||
|
||||
## 使用说明
|
||||
|
||||
- 相关配置项在config.yaml文件,请自行修改
|
||||
- 数据库需导入sql文件夹下的2个sql文件
|
||||
|
||||
## 微信群
|
||||
|
||||

|
||||
|
||||
## 📄windows下的编译命令
|
||||
|
||||
```shell
|
||||
$env:CGO_ENABLED="0"
|
||||
$env:GOOS="linux"
|
||||
$env:GOARCH="amd64"
|
||||
go build -o gb28181Panda main.go
|
||||
```
|
||||
|
||||
```shell
|
||||
其余平台编译方式请自行查阅go的交叉编译
|
||||
```
|
||||
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
# 开发模式, debug, release
|
||||
runMode: release
|
||||
#本地ip
|
||||
ip: 127.0.0.1
|
||||
# HTTP绑定端口
|
||||
addr: :9090
|
||||
# API Server的名字
|
||||
name: gb28181Panda
|
||||
# mysql配置信息
|
||||
mysql:
|
||||
username: root
|
||||
password: 123456
|
||||
host: 127.0.0.1
|
||||
port: 3306
|
||||
name: gb28181_panda
|
||||
# redis配置信息
|
||||
redis:
|
||||
addr: 127.0.0.1:6379
|
||||
password: 123456
|
||||
dbIndex: 1
|
||||
# ZLM的配置信息
|
||||
media:
|
||||
# [必修修改] zlm服务器的唯一id
|
||||
id: zlm_id
|
||||
# [必须修改] zlm服务器的内网IP 如果是外网的话需要设置外网ip地址
|
||||
ip: 127.0.0.1
|
||||
# [必须修改] zlm服务器的http.port
|
||||
httpPort: 80
|
||||
# [可选] zlm服务器的secret
|
||||
secret: 035c73f7-bb6b-4889-a715-d9eb2d1925cc
|
||||
# rtsp的端口
|
||||
rtspPort: 554
|
||||
# rtmp的端口
|
||||
rtmpPort: 1935
|
||||
# 28181 服务器的配置
|
||||
sip:
|
||||
# [必须修改] 本机的IP
|
||||
ip: "127.0.0.1"
|
||||
# [可选] 28181服务监听的端口
|
||||
port: 5060
|
||||
# 根据国标6.1.2中规定,domain宜采用ID统一编码的前十位编码。国标附录D中定义前8位为中心编码(由省级、市级、区级、基层编号组成,参照GB/T 2260-2007)
|
||||
# 后两位为行业编码,定义参照附录D.3
|
||||
# 3701020049标识山东济南历下区 信息行业接入
|
||||
realm: "3402000000"
|
||||
id: "34020000002000000001"
|
||||
# [可选] 默认设备认证密码,移除密码将不进行校验
|
||||
password: "123456"
|
||||
# sip的头
|
||||
userAgent: "gb28181Panda"
|
||||
# 是否校验设备的域值 1 校验 0 不校验
|
||||
checkRealm: 1
|
||||
@@ -0,0 +1,27 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func init() {
|
||||
loadConfig()
|
||||
}
|
||||
|
||||
func loadConfig() {
|
||||
viper.AddConfigPath("./")
|
||||
viper.SetConfigName("config")
|
||||
viper.SetConfigType("yaml")
|
||||
viper.AutomaticEnv()
|
||||
if err := viper.ReadInConfig(); err != nil {
|
||||
panic("load config fail,please check your config file whether in config/ in the directory")
|
||||
}
|
||||
startOption()
|
||||
}
|
||||
|
||||
func startOption() {
|
||||
//加载配置项后加载zlm的配置
|
||||
ZlmOp = InitZlmOptions()
|
||||
//加载配置项后加载sip的配置
|
||||
SipOp = InitSipOptions()
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type RedisOptions struct {
|
||||
Addr string
|
||||
Password string
|
||||
DB int
|
||||
}
|
||||
|
||||
func NewRedisOptions() *RedisOptions {
|
||||
r := &RedisOptions{
|
||||
Addr: "127.0.0.1:6379",
|
||||
Password: "",
|
||||
DB: 3,
|
||||
}
|
||||
err := viper.UnmarshalKey("redis", r)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return r
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type SipOptions struct {
|
||||
Id string
|
||||
Realm string
|
||||
Ip string
|
||||
Port int
|
||||
Password string
|
||||
UserAgent string
|
||||
CheckRealm int
|
||||
}
|
||||
|
||||
var SipOp *SipOptions
|
||||
|
||||
func InitSipOptions() *SipOptions {
|
||||
r := &SipOptions{}
|
||||
err := viper.UnmarshalKey("sip", r)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return r
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type ZlmOptions struct {
|
||||
Id string
|
||||
Ip string
|
||||
HttpPort int
|
||||
Secret string
|
||||
RtspPort int
|
||||
RtmpPort int
|
||||
}
|
||||
|
||||
var ZlmOp *ZlmOptions
|
||||
|
||||
func InitZlmOptions() *ZlmOptions {
|
||||
r := &ZlmOptions{}
|
||||
err := viper.UnmarshalKey("media", r)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func (zlmOp *ZlmOptions) GetId() string {
|
||||
return ZlmOp.Id
|
||||
}
|
||||
func (zlmOp *ZlmOptions) GetIp() string {
|
||||
return ZlmOp.Ip
|
||||
}
|
||||
func (zlmOp *ZlmOptions) GetHttpPort() int {
|
||||
return ZlmOp.HttpPort
|
||||
}
|
||||
func (zlmOp *ZlmOptions) GetSecret() string {
|
||||
return ZlmOp.Secret
|
||||
}
|
||||
func (zlmOp *ZlmOptions) GetRtspPort() int {
|
||||
return ZlmOp.RtspPort
|
||||
}
|
||||
func (zlmOp *ZlmOptions) GetRtmpPort() int {
|
||||
return ZlmOp.RtmpPort
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
module gb28181Panda
|
||||
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/beevik/etree v1.1.0
|
||||
github.com/ghettovoice/gosip v0.0.0-20230322091832-d77de1c97f89
|
||||
github.com/gin-gonic/gin v1.9.0
|
||||
github.com/go-redis/redis/v8 v8.11.5
|
||||
github.com/natefinch/lumberjack v2.0.0+incompatible
|
||||
github.com/panjjo/gosdp v0.0.0-20201029020038-56e3a0ec56ef
|
||||
github.com/parnurzeal/gorequest v0.2.16
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/sirupsen/logrus v1.9.0
|
||||
github.com/spf13/cast v1.5.0
|
||||
github.com/spf13/viper v1.15.0
|
||||
github.com/x-cray/logrus-prefixed-formatter v0.5.2
|
||||
go.uber.org/zap v1.24.0
|
||||
golang.org/x/net v0.9.0
|
||||
golang.org/x/text v0.9.0
|
||||
gorm.io/driver/mysql v1.5.0
|
||||
gorm.io/gorm v1.25.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.2.1 // indirect
|
||||
github.com/bytedance/sonic v1.8.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/discoviking/fsm v0.0.0-20150126104936-f4a273feecca // indirect
|
||||
github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.11.2 // indirect
|
||||
github.com/go-sql-driver/mysql v1.7.0 // indirect
|
||||
github.com/gobwas/httphead v0.1.0 // indirect
|
||||
github.com/gobwas/pool v0.2.1 // indirect
|
||||
github.com/gobwas/ws v1.1.0-rc.1 // indirect
|
||||
github.com/goccy/go-json v0.10.0 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
|
||||
github.com/leodido/go-urn v1.2.1 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.17 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/onsi/gomega v1.27.6 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
|
||||
github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b // indirect
|
||||
github.com/smartystreets/goconvey v1.8.0 // indirect
|
||||
github.com/spf13/afero v1.9.3 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/subosito/gotenv v1.4.2 // indirect
|
||||
github.com/tevino/abool v0.0.0-20170917061928-9b9efcf221b5 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.9 // indirect
|
||||
go.uber.org/atomic v1.9.0 // indirect
|
||||
go.uber.org/multierr v1.8.0 // indirect
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
|
||||
golang.org/x/crypto v0.8.0 // indirect
|
||||
golang.org/x/sys v0.7.0 // indirect
|
||||
golang.org/x/term v0.7.0 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
moul.io/http2curl v1.0.0 // indirect
|
||||
)
|
||||
@@ -0,0 +1,646 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
||||
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
|
||||
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
||||
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
|
||||
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
|
||||
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
||||
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
||||
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
||||
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
|
||||
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
|
||||
cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
||||
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
|
||||
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs=
|
||||
github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
|
||||
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||
github.com/bytedance/sonic v1.8.0 h1:ea0Xadu+sHlu7x5O3gKhRpQ1IKiMrSiHttPF0ybECuA=
|
||||
github.com/bytedance/sonic v1.8.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
|
||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/discoviking/fsm v0.0.0-20150126104936-f4a273feecca h1:cTTdXpkQ1aVbOOmHwdwtYuwUZcQtcMrleD1UXLWhAq8=
|
||||
github.com/discoviking/fsm v0.0.0-20150126104936-f4a273feecca/go.mod h1:W+3LQaEkN8qAwwcw0KC546sUEnX86GIT8CcMLZC4mG0=
|
||||
github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819 h1:RIB4cRk+lBqKK3Oy0r2gRX4ui7tuhiZq2SuTtTCi0/0=
|
||||
github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
|
||||
github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||
github.com/ghettovoice/gosip v0.0.0-20230322091832-d77de1c97f89 h1:bUtgAwa7cfrp0bEnlfr+k6fgsfn7/OFQ0pvQvoujOAo=
|
||||
github.com/ghettovoice/gosip v0.0.0-20230322091832-d77de1c97f89/go.mod h1:rlD1yLOErWYohWTryG/2bTTpmzB79p52ntLA/uIFXeI=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8=
|
||||
github.com/gin-gonic/gin v1.9.0/go.mod h1:W1Me9+hsUSyj3CePGrd1/QrKJMSJ1Tu/0hFEH89961k=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU=
|
||||
github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s=
|
||||
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
|
||||
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
|
||||
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
|
||||
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
|
||||
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
||||
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
||||
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gobwas/ws v1.1.0-rc.1 h1:VK3aeRXMI8osaS6YCDKNZhU6RKtcP3B2wzqxOogNDz8=
|
||||
github.com/gobwas/ws v1.1.0-rc.1/go.mod h1:nzvNcVha5eUziGrbxFCo6qFIojQHjJV5cLYIbezhfL0=
|
||||
github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA=
|
||||
github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
|
||||
github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
|
||||
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
|
||||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/natefinch/lumberjack v2.0.0+incompatible h1:4QJd3OLAMgj7ph+yZTuX13Ld4UpgHp07nNdFX7mqFfM=
|
||||
github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/nxadm/tail v1.4.5/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
|
||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.10.4/go.mod h1:g/HbgYopi++010VEqkFgJHKC09uJiW9UkXvMUuKHUCQ=
|
||||
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
|
||||
github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg=
|
||||
github.com/panjjo/gosdp v0.0.0-20201029020038-56e3a0ec56ef h1:RmpKwi6Ju0aSGZe9zdhX3dT0DoGZMTrRScUap6WVFSg=
|
||||
github.com/panjjo/gosdp v0.0.0-20201029020038-56e3a0ec56ef/go.mod h1:VyTSJoai1m6iMalmg8kEMuaKk2amkc6JS0K7H0xfcmQ=
|
||||
github.com/parnurzeal/gorequest v0.2.16 h1:T/5x+/4BT+nj+3eSknXmCTnEVGSzFzPGdpqmUVVZXHQ=
|
||||
github.com/parnurzeal/gorequest v0.2.16/go.mod h1:3Kh2QUMJoqw3icWAecsyzkpY7UzRfDhbRdTjtNwNiUE=
|
||||
github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU=
|
||||
github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
||||
github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b h1:gQZ0qzfKHQIybLANtM3mBXNUtOfsCFXeTsnBqCsx1KM=
|
||||
github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/smartystreets/assertions v1.13.1 h1:Ef7KhSmjZcK6AVf9YbJdvPYG9avaF0ZxudX+ThRdWfU=
|
||||
github.com/smartystreets/goconvey v1.8.0 h1:Oi49ha/2MURE0WexF052Z0m+BNSGirfjg5RL+JXWq3w=
|
||||
github.com/smartystreets/goconvey v1.8.0/go.mod h1:EdX8jtrTIj26jmjCOVNMVSIYAtgexqXKHOXW2Dx9JLg=
|
||||
github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk=
|
||||
github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
|
||||
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
|
||||
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
|
||||
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
|
||||
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.15.0 h1:js3yy885G8xwJa6iOISGFwd+qlUo5AvyXb7CiihdtiU=
|
||||
github.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jHOQLA=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8=
|
||||
github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
|
||||
github.com/tevino/abool v0.0.0-20170917061928-9b9efcf221b5 h1:hNna6Fi0eP1f2sMBe/rJicDmaHmoXGe1Ta84FPYHLuE=
|
||||
github.com/tevino/abool v0.0.0-20170917061928-9b9efcf221b5/go.mod h1:f1SCnEOt6sc3fOJfPQDRDzHOtSXuTtnz0ImG9kPRDV0=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.2.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU=
|
||||
github.com/ugorji/go/codec v1.2.9/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/x-cray/logrus-prefixed-formatter v0.5.2 h1:00txxvfBM9muc0jiLIEAkAcIMJzfthRT6usrui8uGmg=
|
||||
github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
||||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
|
||||
go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8=
|
||||
go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
|
||||
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
|
||||
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
|
||||
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
|
||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201207223542-d4d67f95c62d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ=
|
||||
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
|
||||
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
||||
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
||||
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
|
||||
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
|
||||
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
|
||||
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
||||
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
|
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
|
||||
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
||||
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/mysql v1.5.0 h1:6hSAT5QcyIaty0jfnff0z0CLDjyRgZ8mlMHLqSt7uXM=
|
||||
gorm.io/driver/mysql v1.5.0/go.mod h1:FFla/fJuCvyTi7rJQd27qlNX2v3L6deTR1GgTjSOLPo=
|
||||
gorm.io/gorm v1.24.7-0.20230306060331-85eaf9eeda11/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||
gorm.io/gorm v1.25.0 h1:+KtYtb2roDz14EQe4bla8CbQlmb9dN3VejSai3lprfU=
|
||||
gorm.io/gorm v1.25.0/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
moul.io/http2curl v1.0.0 h1:6XwpyZOYsgZJrU8exnG87ncVkU1FVCcTRpwzOkTDUi8=
|
||||
moul.io/http2curl v1.0.0/go.mod h1:f6cULg+e4Md/oW1cYmwW4IWQOVl2lGbmCNGOHvzX2kE=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 526 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 396 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 754 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 306 KiB |
+158
@@ -0,0 +1,158 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"github.com/natefinch/lumberjack"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
"os"
|
||||
"path"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Logger interface used as base logger throughout the library.
|
||||
type Logger interface {
|
||||
Print(args ...interface{})
|
||||
Printf(format string, args ...interface{})
|
||||
|
||||
Trace(args ...interface{})
|
||||
Tracef(format string, args ...interface{})
|
||||
|
||||
Debug(args ...interface{})
|
||||
Debugf(format string, args ...interface{})
|
||||
|
||||
Info(args ...interface{})
|
||||
Infof(format string, args ...interface{})
|
||||
|
||||
Warn(args ...interface{})
|
||||
Warnf(format string, args ...interface{})
|
||||
|
||||
Error(args ...interface{})
|
||||
Errorf(format string, args ...interface{})
|
||||
|
||||
Fatal(args ...interface{})
|
||||
Fatalf(format string, args ...interface{})
|
||||
|
||||
Panic(args ...interface{})
|
||||
Panicf(format string, args ...interface{})
|
||||
|
||||
WithPrefix(prefix string) Logger
|
||||
Prefix() string
|
||||
|
||||
WithFields(fields Fields) Logger
|
||||
Fields() Fields
|
||||
|
||||
SetLevel(level Level)
|
||||
}
|
||||
|
||||
type Loggable interface {
|
||||
Log() Logger
|
||||
}
|
||||
|
||||
var (
|
||||
l = initLog()
|
||||
)
|
||||
|
||||
func initLog() *zap.SugaredLogger {
|
||||
core := zapcore.NewCore(getJSONEncoder(), getLoggerWrite(), getLoggerLevel())
|
||||
logger := zap.New(core, zap.AddCaller(), zap.AddCallerSkip(1))
|
||||
return logger.Sugar()
|
||||
}
|
||||
|
||||
func getJSONEncoder() zapcore.Encoder {
|
||||
// 自定义时间输出格式
|
||||
customTimeEncoder := func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
|
||||
enc.AppendString(t.Format("2006-01-02 15:04:05.000"))
|
||||
}
|
||||
|
||||
// 自定义日志级别显示
|
||||
customLevelEncoder := func(level zapcore.Level, enc zapcore.PrimitiveArrayEncoder) {
|
||||
enc.AppendString(level.CapitalString())
|
||||
}
|
||||
|
||||
// 定义zap配置信息
|
||||
encoderConfig := zapcore.EncoderConfig{
|
||||
TimeKey: "time",
|
||||
LevelKey: "level",
|
||||
NameKey: "logger",
|
||||
CallerKey: "line",
|
||||
MessageKey: "msg",
|
||||
StacktraceKey: "stacktrace",
|
||||
LineEnding: zapcore.DefaultLineEnding,
|
||||
EncodeTime: customTimeEncoder, // 自定义时间格式
|
||||
EncodeLevel: customLevelEncoder, // 小写编码器
|
||||
EncodeCaller: zapcore.ShortCallerEncoder, // 全路径编码器
|
||||
EncodeDuration: zapcore.SecondsDurationEncoder,
|
||||
EncodeName: zapcore.FullNameEncoder,
|
||||
}
|
||||
return zapcore.NewConsoleEncoder(encoderConfig)
|
||||
}
|
||||
|
||||
type Fields map[string]interface{}
|
||||
|
||||
func getLoggerWrite() zapcore.WriteSyncer {
|
||||
|
||||
// 定义日志切割配置
|
||||
l := &lumberjack.Logger{
|
||||
Filename: path.Join("./logFile", "gb28181Panda.log"), //Filename 是要写入日志的文件。
|
||||
MaxSize: 1, //MaxSize 是日志文件在轮换之前的最大大小(以兆字节为单位)。它默认为 100 兆字节
|
||||
MaxBackups: 30, //MaxBackups 是要保留的最大旧日志文件数。默认是保留所有旧的日志文件(尽管 MaxAge 可能仍会导致它们被删除。)
|
||||
MaxAge: 30, //MaxAge 是根据文件名中编码的时间戳保留旧日志文件的最大天数。
|
||||
Compress: true, //压缩
|
||||
LocalTime: true, //LocalTime 确定用于格式化备份文件中的时间戳的时间是否是计算机的本地时间。默认是使用 UTC 时间。
|
||||
}
|
||||
// 控制台输出
|
||||
return zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout), zapcore.AddSync(l))
|
||||
}
|
||||
|
||||
func getLoggerLevel() zapcore.Level {
|
||||
level, _ := zapcore.ParseLevel("debug")
|
||||
return level
|
||||
}
|
||||
|
||||
func Debug(args ...interface{}) {
|
||||
l.Debug(args)
|
||||
}
|
||||
|
||||
func Debugf(format string, args ...interface{}) {
|
||||
l.Debugf(format, args)
|
||||
}
|
||||
|
||||
func Info(args ...interface{}) {
|
||||
l.Info(args)
|
||||
}
|
||||
|
||||
func Infof(format string, args ...interface{}) {
|
||||
l.Infof(format, args)
|
||||
}
|
||||
|
||||
func Warn(args ...interface{}) {
|
||||
l.Warn(args)
|
||||
}
|
||||
|
||||
func Warnf(format string, args ...interface{}) {
|
||||
l.Warnf(format, args)
|
||||
}
|
||||
|
||||
func Error(args ...interface{}) {
|
||||
l.Error(args)
|
||||
}
|
||||
|
||||
func Errorf(format string, args ...interface{}) {
|
||||
l.Errorf(format, args)
|
||||
}
|
||||
|
||||
func Fatal(args ...interface{}) {
|
||||
l.Fatal(args)
|
||||
}
|
||||
|
||||
func Fatalf(format string, args ...interface{}) {
|
||||
l.Fatalf(format, args)
|
||||
}
|
||||
|
||||
func Panic(args ...interface{}) {
|
||||
l.Panic(args)
|
||||
}
|
||||
|
||||
func Panicf(format string, args ...interface{}) {
|
||||
l.Panicf(format, args)
|
||||
}
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"github.com/sirupsen/logrus"
|
||||
prefixed "github.com/x-cray/logrus-prefixed-formatter"
|
||||
)
|
||||
|
||||
type LogrusLogger struct {
|
||||
log logrus.Ext1FieldLogger
|
||||
prefix string
|
||||
fields Fields
|
||||
}
|
||||
|
||||
// Level type
|
||||
type Level uint32
|
||||
|
||||
// These are the different logging levels. You can set the logging level to log
|
||||
// on your instance of logger, obtained with `logrus.New()`.
|
||||
const (
|
||||
// PanicLevel level, highest level of severity. Logs and then calls panic with the
|
||||
// message passed to Debug, Info, ...
|
||||
PanicLevel Level = iota
|
||||
// FatalLevel level. Logs and then calls `logger.Exit(1)`. It will exit even if the
|
||||
// logging level is set to Panic.
|
||||
FatalLevel
|
||||
// ErrorLevel level. Logs. Used for errors that should definitely be noted.
|
||||
// Commonly used for hooks to send errors to an error tracking service.
|
||||
ErrorLevel
|
||||
// WarnLevel level. Non-critical entries that deserve eyes.
|
||||
WarnLevel
|
||||
// InfoLevel level. General operational entries about what's going on inside the
|
||||
// application.
|
||||
InfoLevel
|
||||
// DebugLevel level. Usually only enabled when debugging. Very verbose logging.
|
||||
DebugLevel
|
||||
// TraceLevel level. Designates finer-grained informational events than the Debug.
|
||||
TraceLevel
|
||||
)
|
||||
|
||||
func NewLogrusLogger(logrus logrus.Ext1FieldLogger, prefix string, fields Fields) *LogrusLogger {
|
||||
return &LogrusLogger{
|
||||
log: logrus,
|
||||
prefix: prefix,
|
||||
fields: fields,
|
||||
}
|
||||
}
|
||||
|
||||
func NewDefaultLogrusLogger() *LogrusLogger {
|
||||
logger := logrus.New()
|
||||
logger.Formatter = &prefixed.TextFormatter{
|
||||
FullTimestamp: true,
|
||||
TimestampFormat: "2006-01-02 15:04:05.000",
|
||||
}
|
||||
|
||||
return NewLogrusLogger(logger, "main", nil)
|
||||
}
|
||||
func (fields Fields) WithFields(newFields Fields) Fields {
|
||||
allFields := make(Fields)
|
||||
|
||||
for k, v := range fields {
|
||||
allFields[k] = v
|
||||
}
|
||||
|
||||
for k, v := range newFields {
|
||||
allFields[k] = v
|
||||
}
|
||||
|
||||
return allFields
|
||||
}
|
||||
func (l *LogrusLogger) Print(args ...interface{}) {
|
||||
l.prepareEntry().Print(args...)
|
||||
}
|
||||
|
||||
func (l *LogrusLogger) Printf(format string, args ...interface{}) {
|
||||
l.prepareEntry().Printf(format, args...)
|
||||
}
|
||||
|
||||
func (l *LogrusLogger) Trace(args ...interface{}) {
|
||||
l.prepareEntry().Trace(args...)
|
||||
}
|
||||
|
||||
func (l *LogrusLogger) Tracef(format string, args ...interface{}) {
|
||||
l.prepareEntry().Tracef(format, args...)
|
||||
}
|
||||
|
||||
func (l *LogrusLogger) Debug(args ...interface{}) {
|
||||
l.prepareEntry().Debug(args...)
|
||||
}
|
||||
|
||||
func (l *LogrusLogger) Debugf(format string, args ...interface{}) {
|
||||
l.prepareEntry().Debugf(format, args...)
|
||||
}
|
||||
|
||||
func (l *LogrusLogger) Info(args ...interface{}) {
|
||||
l.prepareEntry().Info(args...)
|
||||
}
|
||||
|
||||
func (l *LogrusLogger) Infof(format string, args ...interface{}) {
|
||||
l.prepareEntry().Infof(format, args...)
|
||||
}
|
||||
|
||||
func (l *LogrusLogger) Warn(args ...interface{}) {
|
||||
l.prepareEntry().Warn(args...)
|
||||
}
|
||||
|
||||
func (l *LogrusLogger) Warnf(format string, args ...interface{}) {
|
||||
l.prepareEntry().Warnf(format, args...)
|
||||
}
|
||||
|
||||
func (l *LogrusLogger) Error(args ...interface{}) {
|
||||
l.prepareEntry().Error(args...)
|
||||
}
|
||||
|
||||
func (l *LogrusLogger) Errorf(format string, args ...interface{}) {
|
||||
l.prepareEntry().Errorf(format, args...)
|
||||
}
|
||||
|
||||
func (l *LogrusLogger) Fatal(args ...interface{}) {
|
||||
l.prepareEntry().Fatal(args...)
|
||||
}
|
||||
|
||||
func (l *LogrusLogger) Fatalf(format string, args ...interface{}) {
|
||||
l.prepareEntry().Fatalf(format, args...)
|
||||
}
|
||||
|
||||
func (l *LogrusLogger) Panic(args ...interface{}) {
|
||||
l.prepareEntry().Panic(args...)
|
||||
}
|
||||
|
||||
func (l *LogrusLogger) Panicf(format string, args ...interface{}) {
|
||||
l.prepareEntry().Panicf(format, args...)
|
||||
}
|
||||
|
||||
func (l *LogrusLogger) WithPrefix(prefix string) Logger {
|
||||
return NewLogrusLogger(l.log, prefix, l.Fields())
|
||||
}
|
||||
|
||||
func (l *LogrusLogger) Prefix() string {
|
||||
return l.prefix
|
||||
}
|
||||
|
||||
func (l *LogrusLogger) WithFields(fields Fields) Logger {
|
||||
return NewLogrusLogger(l.log, l.Prefix(), l.Fields().WithFields(fields))
|
||||
}
|
||||
|
||||
func (l *LogrusLogger) Fields() Fields {
|
||||
return l.fields
|
||||
}
|
||||
|
||||
func (l *LogrusLogger) prepareEntry() *logrus.Entry {
|
||||
return l.log.
|
||||
WithFields(logrus.Fields(l.Fields())).
|
||||
WithField("prefix", l.Prefix())
|
||||
}
|
||||
|
||||
func (l *LogrusLogger) SetLevel(level Level) {
|
||||
if ll, ok := l.log.(*logrus.Logger); ok {
|
||||
ll.SetLevel(logrus.Level(level))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"gb28181Panda/router"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func main() {
|
||||
//初始化sip服务
|
||||
router.InitSipServer()
|
||||
//设置gin的运行环境
|
||||
gin.SetMode(viper.GetString("runMode"))
|
||||
r := gin.New()
|
||||
r.Use(gin.Logger())
|
||||
r.Use(gin.Recovery())
|
||||
_ = r.SetTrustedProxies([]string{viper.GetString("ip")})
|
||||
router.InitGinRouter(r)
|
||||
_ = r.Run(viper.GetString("addr")) // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package model
|
||||
|
||||
type Cascade struct {
|
||||
Id string
|
||||
Ip string
|
||||
Port int
|
||||
Transport string
|
||||
Password string
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gb28181Panda/storage"
|
||||
)
|
||||
|
||||
type Channel struct {
|
||||
Id int `json:"id"` //type:int comment:
|
||||
DeviceId string `gorm:"column:device_id" json:"deviceId" xml:"DeviceID"` //type:string comment:通道Id号
|
||||
Name string `gorm:"column:name" json:"name"` //type:string comment:通道名称
|
||||
Manufacturer string `gorm:"column:manufacturer" json:"manufacturer"` //type:string comment:当为设备时,设备厂商
|
||||
Model string `gorm:"column:model" json:"model"` //type:string comment:当为设备时,设备型号
|
||||
Owner string `gorm:"column:owner" json:"owner"` //type:string comment:当为设备时,设备归属
|
||||
CivilCode string `gorm:"column:civil_code" json:"civilCode"` //type:string comment:行政区域
|
||||
Address string `gorm:"column:address" json:"address"` //type:string comment:当为设备时,安装地址
|
||||
Parental string `gorm:"column:parental" json:"parental"` //type:string comment:当为设备时,是否有子设备,1有,0没有
|
||||
ParentId string `gorm:"column:parent_id" json:"parentId" xml:"ParentID"` //type:string comment:父设备/区域/系统ID
|
||||
SafetyWay string `gorm:"column:safety_way" json:"safetyWay"` //type:string comment:信令安全模式,0不采用、2 S/MIME签名方式、3 S/MIME加密他签名同时采用方式、4 数字摘要方式
|
||||
RegisterWay string `gorm:"column:register_way" json:"registerWay"` //type:string comment:注册方式,1 标准认证注册模式 、2 基于口令的双向认证模式、3 基于数字证书的双向认证注册模式
|
||||
Secrecy string `gorm:"column:secrecy" json:"secrecy"` //type:string comment:保密属性,0不涉密、1涉密
|
||||
Status string `gorm:"column:status" json:"status"` //type:string comment:通道状态
|
||||
TransportType string `gorm:"column:transport_type" json:"transportType"` //type:string comment:流媒体传输方式 udp、tcp被动 tcppassive、tcp主动 tcpactive
|
||||
MediaStatus string `gorm:"column:media_status" json:"mediaStatus"` //type:string comment:流媒体当前直播状态 OPEN 有人观看 CLOSE 无人观看
|
||||
CreatedAt string `gorm:"column:created_at" json:"createdAt"` //type:string comment:
|
||||
UpdatedAt string `gorm:"column:updated_at" json:"updatedAt"` //type:string comment:
|
||||
}
|
||||
|
||||
func (channel *Channel) ChannelList() ([]Channel, error) {
|
||||
var list []Channel
|
||||
if err := storage.MysqlDb.Table("t_channel").Where("parent_id = ?", channel.DeviceId).Order("id ASC").Find(&list).Error; err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return list, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (channel *Channel) ChannelDetail() (Channel, error) {
|
||||
var detail Channel
|
||||
if err := storage.MysqlDb.Table("t_channel").Where("device_id = ?", channel.DeviceId).First(&detail).Error; err != nil {
|
||||
return Channel{}, err
|
||||
} else {
|
||||
return detail, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (channel *Channel) ChannelAdd() error {
|
||||
if err := storage.MysqlDb.Debug().Table("t_channel").Create(&channel).Error; err != nil {
|
||||
fmt.Println("err", err)
|
||||
return err
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (channel *Channel) ChannelUpdate() error {
|
||||
if err := storage.MysqlDb.Debug().Table("t_channel").Where("device_id = ?", channel.DeviceId).Updates(channel).Error; err != nil {
|
||||
return err
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (channel *Channel) ChannelDelete() error {
|
||||
if err := storage.MysqlDb.Table("t_channel").Where("device_id = ? AND parent_id = ?", channel.DeviceId, channel.ParentId).Delete(&channel).Error; err != nil {
|
||||
return err
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
func (channel *Channel) ChannelDeleteWithParentId() error {
|
||||
if err := storage.MysqlDb.Table("t_channel").Where("parent_id = ?", channel.ParentId).Delete(&channel).Error; err != nil {
|
||||
return err
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gb28181Panda/storage"
|
||||
)
|
||||
|
||||
type Device struct {
|
||||
Id int `json:"id"` //type:int comment:id
|
||||
DeviceId string `gorm:"column:device_id" json:"deviceId"` //type:string comment:设备deviceId号
|
||||
Domain string `gorm:"column:domain" json:"domain"` //type:string comment:设备域
|
||||
Name string `gorm:"column:name" json:"name"` //type:string comment:设备名称
|
||||
Manufacturer string `gorm:"column:manufacturer" json:"manufacturer"` //type:string comment:设备厂家
|
||||
Model string `gorm:"column:model" json:"model"` //type:string comment:设备型号
|
||||
Firmware string `gorm:"column:firmware" json:"firmware"` //type:string comment:设备固件版本
|
||||
Transport string `gorm:"column:transport" json:"transport"` //type:string comment:传输模式
|
||||
Status string `gorm:"column:status" json:"status"` //type:string comment:上下线状态on 上线 off 下线
|
||||
Ip string `gorm:"column:ip" json:"ip"` //type:string comment:设备ip地址
|
||||
Port string `gorm:"column:port" json:"port"` //type:string comment:设备端口号
|
||||
Expires string `gorm:"column:expires" json:"expires"` //type:string comment:设备有效时间
|
||||
CreatedAt string `gorm:"column:created_at" json:"createdAt"` //type:string comment:创建时间
|
||||
UpdatedAt string `gorm:"column:updated_at" json:"updatedAt"` //type:string comment:更新时间
|
||||
RegisterAt string `gorm:"column:register_at" json:"registerAt"` //type:string comment:注册时间
|
||||
KeepaliveAt string `gorm:"column:keepalive_at" json:"keepaliveAt"` //type:string comment:保持状态时间
|
||||
}
|
||||
|
||||
// DeviceTree 设备通道树状数据
|
||||
type DeviceTree struct {
|
||||
Id uint `json:"id"`
|
||||
DeviceId string `json:"deviceId" gorm:"primaryKey"`
|
||||
Name string `json:"name"`
|
||||
Manufacturer string `json:"manufacturer"`
|
||||
Model string `json:"model"`
|
||||
Status string `json:"status"`
|
||||
KeepaliveAt string `json:"keepaliveAt"`
|
||||
Channel []*DeviceTreeChannel `json:"channel" gorm:"foreignKey:ParentId"`
|
||||
}
|
||||
|
||||
// DeviceTreeChannel 通道数据
|
||||
type DeviceTreeChannel struct {
|
||||
DeviceId string `json:"deviceId"`
|
||||
Name string `json:"name"`
|
||||
Manufacturer string `json:"manufacturer"`
|
||||
Model string `json:"model"`
|
||||
ParentId string `json:"parentId"`
|
||||
}
|
||||
|
||||
func (deviceTree *DeviceTree) TableName() string {
|
||||
return "t_device"
|
||||
}
|
||||
func (deviceTreeChannel *DeviceTreeChannel) TableName() string {
|
||||
return "t_channel"
|
||||
}
|
||||
func (deviceTree *DeviceTree) DeviceTreeData() ([]DeviceTree, error) {
|
||||
var dataTree []DeviceTree
|
||||
if err := storage.MysqlDb.Preload("Channel").Find(&dataTree).Error; err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return dataTree, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (device *Device) DeviceList() ([]Device, error) {
|
||||
var list []Device
|
||||
if err := storage.MysqlDb.Table("t_device").Order("id ASC").Find(&list).Error; err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return list, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (device *Device) DeviceDetail() (Device, error) {
|
||||
var detail Device
|
||||
if err := storage.MysqlDb.Debug().Table("t_device").Where("device_id = ?", device.DeviceId).First(&detail).Error; err != nil {
|
||||
return Device{}, err
|
||||
} else {
|
||||
return detail, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (device *Device) DeviceAdd() error {
|
||||
if err := storage.MysqlDb.Debug().Table("t_device").Create(&device).Error; err != nil {
|
||||
fmt.Println("err", err)
|
||||
return err
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (device *Device) DeviceUpdate() error {
|
||||
if err := storage.MysqlDb.Debug().Table("t_device").Where("device_id = ?", device.DeviceId).Updates(device).Error; err != nil {
|
||||
return err
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (device *Device) DeviceDelete() error {
|
||||
if err := storage.MysqlDb.Table("t_device").Where("device_id = ?", device.DeviceId).Delete(&device).Error; err != nil {
|
||||
return err
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gb28181Panda/config"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// StreamInfo 流信息
|
||||
type StreamInfo struct {
|
||||
MediaServerId string `json:"mediaServerId"`
|
||||
App string `json:"app"`
|
||||
Ip string `json:"ip"`
|
||||
DeviceID string `json:"deviceID"`
|
||||
ChannelId string `json:"channelId"`
|
||||
Stream string `json:"stream"`
|
||||
Rtmp string `json:"rtmp"`
|
||||
Rtsp string `json:"rtsp"`
|
||||
Flv string `json:"flv"`
|
||||
HttpsFlv string `json:"httpsFlv"`
|
||||
WsFlv string `json:"wsFlv"`
|
||||
//WSFly string `json:"WSFly"`
|
||||
//WSSFly string `json:"WSSFly"`
|
||||
Fmp4 string `json:"fmp4"`
|
||||
//HttpsFmp4 string `json:"httpsFmp4"`
|
||||
//WSFmpt4 string `json:"WSFmpt4"`
|
||||
Hls string `json:"hls"`
|
||||
//HttpsHls string `json:"httpsHls"`
|
||||
//WsHls string `json:"wsHls"`
|
||||
Ts string `json:"ts"`
|
||||
//HttpsTs string `json:"httpsTs"`
|
||||
//WebsocketTs string `json:"websocketTs"`
|
||||
Ssrc string `json:"ssrc"`
|
||||
}
|
||||
|
||||
const (
|
||||
rtsp = "rtsp://%s:%d/rtp/%s"
|
||||
rtmp = "rtmp://%s:%d/rtp/%s"
|
||||
wsFlv = "ws://%s:%d/rtp/%s.live.flv"
|
||||
http = "http://%s:%d/rtp/%s/hls.m3u8"
|
||||
flv = "http://%s:%d/rtp/%s.live.flv"
|
||||
fmp4 = "http://%s:%d/rtp/%s.llive.mp4"
|
||||
ts = "http://%s:%d/rtp/%s.llive.ts"
|
||||
)
|
||||
|
||||
func MustNewStreamInfo(ssrc string) StreamInfo {
|
||||
index := strings.Index(ssrc, "_")
|
||||
deviceId := ssrc[0:index]
|
||||
channelID := ssrc[index+1:]
|
||||
mediaIp := config.ZlmOp.Ip
|
||||
httpPort := config.ZlmOp.HttpPort
|
||||
rtmpPort := config.ZlmOp.RtmpPort
|
||||
rtspPort := config.ZlmOp.RtspPort
|
||||
return StreamInfo{
|
||||
App: "rtp",
|
||||
DeviceID: deviceId,
|
||||
ChannelId: channelID,
|
||||
Stream: ssrc,
|
||||
Rtmp: fmt.Sprintf(rtmp, mediaIp, rtmpPort, ssrc),
|
||||
Rtsp: fmt.Sprintf(rtsp, mediaIp, rtspPort, ssrc),
|
||||
Hls: fmt.Sprintf(http, mediaIp, httpPort, ssrc),
|
||||
Flv: fmt.Sprintf(flv, mediaIp, httpPort, ssrc),
|
||||
Fmp4: fmt.Sprintf(fmp4, mediaIp, httpPort, ssrc),
|
||||
Ts: fmt.Sprintf(ts, mediaIp, httpPort, ssrc),
|
||||
WsFlv: fmt.Sprintf(wsFlv, mediaIp, httpPort, ssrc),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,294 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"gb28181Panda/config"
|
||||
"gb28181Panda/log"
|
||||
"gb28181Panda/model"
|
||||
"gb28181Panda/sipServer"
|
||||
"gb28181Panda/util"
|
||||
"github.com/gin-gonic/gin"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
type WebrtcSdp struct {
|
||||
Code int `json:"code"`
|
||||
ID string `json:"id"`
|
||||
Sdp string `json:"sdp"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
func ChannelRouter(g *gin.RouterGroup) {
|
||||
//设备channel列表
|
||||
g.GET("/list/:deviceId", func(c *gin.Context) {
|
||||
channel := model.Channel{DeviceId: c.Param("deviceId")}
|
||||
channelList, err := channel.ChannelList()
|
||||
if err != nil {
|
||||
util.GinFRes(c, "查询通道列表失败", nil)
|
||||
return
|
||||
}
|
||||
util.GinSRes(c, "查询通道列表成功", channelList)
|
||||
})
|
||||
|
||||
//播放设备+通道的视频
|
||||
g.GET("/play/:channelId", func(c *gin.Context) {
|
||||
var channel model.Channel
|
||||
channel.DeviceId = c.Param("channelId")
|
||||
channel, _ = channel.ChannelDetail()
|
||||
if channel.DeviceId == "" {
|
||||
util.GinFRes(c, "当前通道不存在", nil)
|
||||
return
|
||||
}
|
||||
//channel.ParentId 为device的id
|
||||
ssrc := channel.ParentId + "_" + c.Param("channelId")
|
||||
|
||||
var device model.Device
|
||||
device.DeviceId = channel.ParentId
|
||||
device, _ = device.DeviceDetail()
|
||||
if device.DeviceId == "" {
|
||||
util.GinFRes(c, "当前设备不存在", nil)
|
||||
return
|
||||
}
|
||||
//先去zlm流媒体查询ssrc是否存在,存在就直接返回播放地址
|
||||
mediaStatus, _ := util.GetMediaList(ssrc)
|
||||
if mediaStatus {
|
||||
streamInfo := model.MustNewStreamInfo(ssrc)
|
||||
util.GinSRes(c, "成功", streamInfo)
|
||||
//设置sql中当前通道是处于播放状态的
|
||||
channel.MediaStatus = "OPEN"
|
||||
_ = channel.ChannelUpdate()
|
||||
return
|
||||
}
|
||||
|
||||
rtpPort, err := util.ZlmOpenRtpServer(ssrc, channel)
|
||||
if err != nil {
|
||||
log.Info("初始化ZLM接受rtp信息失败")
|
||||
util.GinFRes(c, "ZLM初始化失败", nil)
|
||||
return
|
||||
}
|
||||
log.Info("初始化ZLM接受rtp信息成功", rtpPort, ssrc)
|
||||
streamInfo, err := sipServer.Play(device, channel, ssrc, rtpPort, sipServer.PlayNowType, "0", "0")
|
||||
if err != nil {
|
||||
log.Errorf("%+v", err)
|
||||
util.GinFRes(c, err.Error(), nil)
|
||||
return
|
||||
}
|
||||
//设置sql中当前通道是处于播放状态的
|
||||
channel.MediaStatus = "OPEN"
|
||||
_ = channel.ChannelUpdate()
|
||||
util.GinSRes(c, "成功", streamInfo)
|
||||
})
|
||||
|
||||
//设置通道的流媒体传输方式
|
||||
g.GET("/tcp-mode/:channelId", func(c *gin.Context) {
|
||||
tcpMode := c.Query("tcpMode")
|
||||
if tcpMode == "" {
|
||||
util.GinFRes(c, "参数错误", nil)
|
||||
return
|
||||
}
|
||||
var channel model.Channel
|
||||
channel.DeviceId = c.Param("channelId")
|
||||
channel, _ = channel.ChannelDetail()
|
||||
if channel.DeviceId == "" {
|
||||
util.GinFRes(c, "当前通道不存在", nil)
|
||||
return
|
||||
}
|
||||
//设置通道流媒体传输方式
|
||||
channel.TransportType = tcpMode
|
||||
_ = channel.ChannelUpdate()
|
||||
util.GinSRes(c, "设置流媒体传输方式成功", nil)
|
||||
})
|
||||
|
||||
//播放历史视频
|
||||
g.GET("/playback/:channelId", func(c *gin.Context) {
|
||||
startTime := c.Query("startTime")
|
||||
endTime := c.Query("endTime")
|
||||
userId := c.Query("userId")
|
||||
if startTime == "" {
|
||||
util.GinFRes(c, "开始时间错误", nil)
|
||||
return
|
||||
}
|
||||
if endTime == "" {
|
||||
util.GinFRes(c, "结束时间错误", nil)
|
||||
return
|
||||
}
|
||||
var channel model.Channel
|
||||
channel.DeviceId = c.Param("channelId")
|
||||
channel, _ = channel.ChannelDetail()
|
||||
if channel.DeviceId == "" {
|
||||
util.GinFRes(c, "当前通道不存在", nil)
|
||||
return
|
||||
}
|
||||
//channel.ParentId 为device的id 区分不同的用户查看历史回放 加上userId,不然公用一个ssrc的话会导致不同用户查看相同通道历史回放时间一样
|
||||
ssrc := channel.ParentId + "_" + c.Param("channelId") + "_playback_" + userId
|
||||
|
||||
var device model.Device
|
||||
device.DeviceId = channel.ParentId
|
||||
device, _ = device.DeviceDetail()
|
||||
if device.DeviceId == "" {
|
||||
util.GinFRes(c, "当前设备不存在", nil)
|
||||
return
|
||||
}
|
||||
rtpPort, err := util.ZlmOpenRtpServer(ssrc, channel)
|
||||
if err != nil {
|
||||
log.Info("初始化ZLM接受rtp信息失败")
|
||||
util.GinFRes(c, "ZLM初始化失败", nil)
|
||||
return
|
||||
}
|
||||
log.Info("初始化ZLM接受rtp信息成功", rtpPort, ssrc)
|
||||
startT, _ := time.ParseInLocation("2006-01-02 15:04:05", startTime, time.Local)
|
||||
endT, _ := time.ParseInLocation("2006-01-02 15:04:05", endTime, time.Local)
|
||||
//先停止历史视频播放
|
||||
_ = sipServer.Stop(device, ssrc)
|
||||
//强制I帧
|
||||
//sipServer.IFameSip(device, c.Param("channelId"))
|
||||
streamInfo, err := sipServer.Play(device, channel, ssrc, rtpPort, sipServer.PlaybackType, startT.Format("2006-01-02T15:04:05"), endT.Format("2006-01-02T15:04:05"))
|
||||
if err != nil {
|
||||
log.Info(err)
|
||||
util.GinFRes(c, "SIP错误", nil)
|
||||
return
|
||||
}
|
||||
util.GinSRes(c, "成功", streamInfo)
|
||||
})
|
||||
// 历史视频序列
|
||||
g.GET("/record/:channelId", func(c *gin.Context) {
|
||||
startTime := c.Query("startTime")
|
||||
endTime := c.Query("endTime")
|
||||
if startTime == "" {
|
||||
util.GinFRes(c, "开始时间错误", nil)
|
||||
return
|
||||
}
|
||||
if endTime == "" {
|
||||
util.GinFRes(c, "结束时间错误", nil)
|
||||
return
|
||||
}
|
||||
//TODO 比较开始时间和结束时间
|
||||
var channel model.Channel
|
||||
channel.DeviceId = c.Param("channelId")
|
||||
channel, _ = channel.ChannelDetail()
|
||||
if channel.DeviceId == "" {
|
||||
util.GinFRes(c, "当前通道不存在", nil)
|
||||
return
|
||||
}
|
||||
var device model.Device
|
||||
device.DeviceId = channel.ParentId
|
||||
device, _ = device.DeviceDetail()
|
||||
if device.DeviceId == "" {
|
||||
util.GinFRes(c, "当前设备不存在", nil)
|
||||
return
|
||||
}
|
||||
if device.Status == "off" {
|
||||
util.GinFRes(c, "当前设备离线", nil)
|
||||
return
|
||||
}
|
||||
startT, _ := time.ParseInLocation("2006-01-02 15:04:05", startTime, time.Local)
|
||||
endT, _ := time.ParseInLocation("2006-01-02 15:04:05", endTime, time.Local)
|
||||
|
||||
res, err := sipServer.RecordInfoSip(device, c.Param("channelId"), startT.Format("2006-01-02T15:04:05"), endT.Format("2006-01-02T15:04:05"))
|
||||
if err != nil {
|
||||
util.GinFRes(c, "获取历史视频失败", nil)
|
||||
return
|
||||
}
|
||||
util.GinSRes(c, "获取历史视频成功", res)
|
||||
})
|
||||
g.POST("/webrtc/:channelId", func(c *gin.Context) {
|
||||
var channel model.Channel
|
||||
channel.DeviceId = c.Param("channelId")
|
||||
channel, _ = channel.ChannelDetail()
|
||||
if channel.DeviceId == "" {
|
||||
util.GinFRes(c, "当前通道不存在", nil)
|
||||
return
|
||||
}
|
||||
//channel.ParentId 为device的id
|
||||
ssrc := channel.ParentId + "_" + c.Param("channelId")
|
||||
var device model.Device
|
||||
device.DeviceId = channel.ParentId
|
||||
device, _ = device.DeviceDetail()
|
||||
if device.DeviceId == "" {
|
||||
util.GinFRes(c, "当前设备不存在", nil)
|
||||
return
|
||||
}
|
||||
body := c.Request.Body
|
||||
x, _ := io.ReadAll(body)
|
||||
formBytesReader := bytes.NewReader(x)
|
||||
client := &http.Client{}
|
||||
url := fmt.Sprintf("http://%s:%d/index/api/webrtc?app=rtp&stream=%s&type=play&secret=%s", config.ZlmOp.Ip, config.ZlmOp.HttpPort, ssrc, config.ZlmOp.Secret)
|
||||
req, err := http.NewRequest("POST", url, formBytesReader)
|
||||
if err != nil {
|
||||
log.Fatal("生成请求失败!", err)
|
||||
util.GinFRes(c, "webrtc失败", nil)
|
||||
return
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
response, err := client.Do(req)
|
||||
defer response.Body.Close()
|
||||
webrtcSdp := WebrtcSdp{}
|
||||
responseBody, readErr := io.ReadAll(response.Body)
|
||||
if readErr != nil {
|
||||
log.Fatal(readErr)
|
||||
util.GinFRes(c, "webrtc失败", nil)
|
||||
return
|
||||
}
|
||||
jsonErr := json.Unmarshal(responseBody, &webrtcSdp)
|
||||
if jsonErr != nil {
|
||||
log.Fatal(jsonErr)
|
||||
util.GinFRes(c, "webrtc失败", nil)
|
||||
return
|
||||
}
|
||||
if response.StatusCode == http.StatusOK {
|
||||
if webrtcSdp.Code == 0 {
|
||||
util.GinSRes(c, "成功", gin.H{
|
||||
"sdp": webrtcSdp.Sdp,
|
||||
"type": "answer",
|
||||
})
|
||||
} else {
|
||||
util.GinFRes(c, "webrtc失败,清尝试重新打开视频后再播放webrtc", nil)
|
||||
}
|
||||
} else {
|
||||
util.GinFRes(c, "webrtc失败", nil)
|
||||
}
|
||||
})
|
||||
|
||||
//停止视频
|
||||
g.GET("/stop/:channelId", func(c *gin.Context) {
|
||||
var channel model.Channel
|
||||
channel.DeviceId = c.Param("channelId")
|
||||
channel, err := channel.ChannelDetail()
|
||||
if channel.DeviceId == "" {
|
||||
util.GinFRes(c, "当前通道不存在", nil)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
util.GinFRes(c, "sql错误", nil)
|
||||
return
|
||||
}
|
||||
var device model.Device
|
||||
device.DeviceId = channel.ParentId
|
||||
device, err = device.DeviceDetail()
|
||||
if device.DeviceId == "" {
|
||||
util.GinFRes(c, "当前设备不存在", nil)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
util.GinFRes(c, "sql错误", nil)
|
||||
return
|
||||
}
|
||||
//channel.ParentId 为device的id
|
||||
ssrc := channel.ParentId + "_" + c.Param("channelId")
|
||||
//更新sql中当前直播状态字段
|
||||
channel.MediaStatus = "CLOSE"
|
||||
_ = channel.ChannelUpdate()
|
||||
|
||||
err = sipServer.Stop(device, ssrc)
|
||||
if err != nil {
|
||||
util.GinFRes(c, "sip停止视频错误", nil)
|
||||
return
|
||||
}
|
||||
util.ZlmCloseRtpServer(ssrc)
|
||||
util.GinSRes(c, "关闭视频成功", nil)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"gb28181Panda/log"
|
||||
"gb28181Panda/model"
|
||||
"gb28181Panda/sipServer"
|
||||
"gb28181Panda/util"
|
||||
"github.com/gin-gonic/gin"
|
||||
"go/types"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
func DeviceRouter(g *gin.RouterGroup) {
|
||||
//设备device列表
|
||||
g.GET("/list", func(c *gin.Context) {
|
||||
var device model.Device
|
||||
deviceList, err := device.DeviceList()
|
||||
if err != nil {
|
||||
util.GinFRes(c, "查询设备列表失败", nil)
|
||||
return
|
||||
}
|
||||
for key, value := range deviceList {
|
||||
formatTime, err := time.ParseInLocation(util.TIME_LAYOUT_With_SPACE, value.KeepaliveAt, time.Local)
|
||||
if err != nil {
|
||||
log.Error("Format Time Error")
|
||||
return
|
||||
}
|
||||
if time.Now().Unix()-formatTime.Unix() > 60 && deviceList[key].Status == "ON" {
|
||||
deviceList[key].Status = "OFF"
|
||||
value.Status = "OFF"
|
||||
_ = value.DeviceUpdate()
|
||||
}
|
||||
}
|
||||
util.GinSRes(c, "查询设备列表成功", deviceList)
|
||||
|
||||
})
|
||||
//设备device-channel树
|
||||
g.GET("/tree", func(c *gin.Context) {
|
||||
var deviceTree model.DeviceTree
|
||||
deviceData, err := deviceTree.DeviceTreeData()
|
||||
if err != nil {
|
||||
util.GinFRes(c, "查询设备列表失败", nil)
|
||||
return
|
||||
}
|
||||
for key, value := range deviceData {
|
||||
formatTime, err := time.ParseInLocation(util.TIME_LAYOUT_With_SPACE, value.KeepaliveAt, time.Local)
|
||||
if err != nil {
|
||||
log.Error("格式化时间失败")
|
||||
return
|
||||
}
|
||||
if time.Now().Unix()-formatTime.Unix() > 60 && deviceData[key].Status == "ON" {
|
||||
deviceData[key].Status = "OFF"
|
||||
}
|
||||
}
|
||||
util.GinSRes(c, "查询设备树成功", deviceData)
|
||||
|
||||
})
|
||||
//更新通道信息
|
||||
g.GET("/channel/sync/:deviceId", func(c *gin.Context) {
|
||||
var device model.Device
|
||||
device.DeviceId = c.Param("deviceId")
|
||||
device, _ = device.DeviceDetail()
|
||||
if device.DeviceId == "" {
|
||||
util.GinFRes(c, "当前设备不存在", nil)
|
||||
return
|
||||
}
|
||||
sipServer.QueryChannelSip(device)
|
||||
util.GinSRes(c, "更新通道成功", nil)
|
||||
})
|
||||
// 订阅Alarm消息
|
||||
g.GET("/subscribe/:deviceId", func(c *gin.Context) {
|
||||
var device model.Device
|
||||
device.DeviceId = c.Param("deviceId")
|
||||
device, _ = device.DeviceDetail()
|
||||
if device.DeviceId == "" {
|
||||
util.GinFRes(c, "当前设备不存在", nil)
|
||||
return
|
||||
}
|
||||
sipServer.SetGuardSip(device)
|
||||
sipServer.SubscribeAlarmSip(device)
|
||||
util.GinSRes(c, "预警订阅成功", types.Nil{})
|
||||
})
|
||||
|
||||
//删除设备
|
||||
g.DELETE("/delete/:deviceId", func(c *gin.Context) {
|
||||
var device model.Device
|
||||
device.DeviceId = c.Param("deviceId")
|
||||
device, _ = device.DeviceDetail()
|
||||
if device.DeviceId == "" {
|
||||
util.GinFRes(c, "当前设备不存在", nil)
|
||||
return
|
||||
}
|
||||
if device.Status == "ON" {
|
||||
util.GinFRes(c, "当前设备在线,不可删除", nil)
|
||||
return
|
||||
}
|
||||
//先删除device
|
||||
_ = device.DeviceDelete()
|
||||
var channel model.Channel
|
||||
channel.ParentId = c.Param("deviceId")
|
||||
//再删除channel
|
||||
_ = channel.ChannelDeleteWithParentId()
|
||||
util.GinSRes(c, "删除设备成功", nil)
|
||||
})
|
||||
/* 次处无设备可联调 个人理解 ptz控制的应该是deviceId而不是channelId
|
||||
// 设备id
|
||||
DeviceId string `json:"deviceId,omitempty"`
|
||||
// 控制的命令,取值为:left、right、down、up、downright、downleft、upright、upleft、zoomin、zoomout
|
||||
Command string `json:"command,omitempty"`
|
||||
// 水平方向移动速度,取值:0-255
|
||||
HorizonSpeed int `json:"horizonSpeed,omitempty"`
|
||||
// 垂直方向移动速度,取值:0-255
|
||||
VerticalSpeed int `json:"verticalSpeed,omitempty"`
|
||||
// 变倍控制速度,取值:0-255
|
||||
ZoomSpeed int `json:"zoomSpeed,omitempty"`
|
||||
*/
|
||||
g.GET("/ptz/:deviceId/:command/:horizonSpeed/:verticalSpeed/:zoomSpeed", func(c *gin.Context) {
|
||||
var device model.Device
|
||||
device.DeviceId = c.Param("deviceId")
|
||||
device, _ = device.DeviceDetail()
|
||||
if device.DeviceId == "" {
|
||||
util.GinFRes(c, "当前设备不存在", nil)
|
||||
return
|
||||
}
|
||||
horizonSpeed, _ := strconv.Atoi(c.Param("horizonSpeed"))
|
||||
verticalSpeed, _ := strconv.Atoi(c.Param("verticalSpeed"))
|
||||
zoomSpeed, _ := strconv.Atoi(c.Param("zoomSpeed"))
|
||||
err := sipServer.ControlPTZ(device, c.Param("command"), horizonSpeed, verticalSpeed, zoomSpeed)
|
||||
if err != nil {
|
||||
util.GinFRes(c, "ptz-err", nil)
|
||||
return
|
||||
}
|
||||
util.GinSRes(c, "ptz-ok", nil)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// InitGinRouter 初始化路由
|
||||
func InitGinRouter(g *gin.Engine) {
|
||||
//设备路由
|
||||
DeviceRouter(g.Group("/api/device"))
|
||||
//通道路由
|
||||
ChannelRouter(g.Group("/api/channel"))
|
||||
//ZLM的hook回调接口
|
||||
HookRouter(g.Group("/index/hook"))
|
||||
}
|
||||
+201
@@ -0,0 +1,201 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gb28181Panda/log"
|
||||
"gb28181Panda/model"
|
||||
"gb28181Panda/sipServer"
|
||||
"gb28181Panda/util"
|
||||
"github.com/gin-gonic/gin"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// 无人观看
|
||||
type OnStreamNoneReader struct {
|
||||
MediaServerId string `json:"mediaServerId,omitempty"`
|
||||
App string `json:"app,omitempty"`
|
||||
Schema string `json:"schema,omitempty"`
|
||||
Stream string `json:"stream,omitempty"`
|
||||
Vhost string `json:"vhost,omitempty"`
|
||||
}
|
||||
|
||||
// HookReply hook事件默认回复
|
||||
type HookReply struct {
|
||||
// 0代表允许,其他均为不允许
|
||||
Code int `json:"code,omitempty"`
|
||||
|
||||
// 当code不为0时,msg字段应给出相应提示
|
||||
Msg string `json:"msg,omitempty"`
|
||||
}
|
||||
|
||||
type OnStreamNoneReaderReply struct {
|
||||
// 固定返回0
|
||||
Code int `json:"code,omitempty"`
|
||||
// 是否关闭该流,包括推流和拉流
|
||||
Close bool `json:"close,omitempty"`
|
||||
}
|
||||
|
||||
const (
|
||||
ParseParamFail = iota + 1
|
||||
)
|
||||
const (
|
||||
SuccessMsg = "success"
|
||||
ParseParamFailMsg = "parser on_play_hook interface param fail, auth fail and not allow play"
|
||||
)
|
||||
|
||||
// OnPlayHookParam 播放器鉴权hook事件
|
||||
type OnPlayHookParam struct {
|
||||
// 流应用名
|
||||
App string `json:"app,omitempty" `
|
||||
|
||||
// TCP链接唯一ID
|
||||
Id string `json:"id,omitempty"`
|
||||
|
||||
// 播放器ip
|
||||
Ip string `json:"ip,omitempty"`
|
||||
|
||||
// 播放url参数
|
||||
Params string `json:"params,omitempty"`
|
||||
|
||||
// 播放器端口号
|
||||
Port int `json:"port,omitempty"`
|
||||
|
||||
// 播放的协议,可能是rtsp、rtmp、http
|
||||
Schema string `json:"schema,omitempty"`
|
||||
|
||||
// 流ID
|
||||
Stream string `json:"stream,omitempty"`
|
||||
|
||||
// 流虚拟主机
|
||||
Vhost string `json:"vhost,omitempty"`
|
||||
|
||||
// 服务器id,通过配置文件设置
|
||||
MediaServerId string `json:"mediaServerId,omitempty"`
|
||||
}
|
||||
type OnPublishHookReply struct {
|
||||
HookReply
|
||||
// 是否转换成hls协议
|
||||
EnableHls bool `json:"enable_hls,omitempty"`
|
||||
// 是否允许mp4录制
|
||||
EnableMp4 bool `json:"enable_mp4,omitempty"`
|
||||
// 是否转rtsp协议
|
||||
EnableRtsp bool `json:"enable_rtsp,omitempty"`
|
||||
// 是否转rtmp/flv协议
|
||||
EnableRtmp bool `json:"enable_rtmp,omitempty"`
|
||||
// 是否转http-ts/ws-ts协议
|
||||
EnableTs bool `json:"enable_ts,omitempty"`
|
||||
// 是否转http-fmp4/ws-fmp4协议
|
||||
EnableFmp4 bool `json:"enable_fmp4,omitempty"`
|
||||
// 转协议时是否开启音频
|
||||
EnableAudio bool `json:"enable_audio,omitempty"`
|
||||
// 转协议时,无音频是否添加静音aac音频
|
||||
AddMuteAudio bool `json:"add_mute_audio,omitempty"`
|
||||
// mp4录制文件保存根目录,置空使用默认
|
||||
Mp4SavePath string `json:"mp4_save_path,omitempty"`
|
||||
// mp4录制切片大小,单位秒
|
||||
Mp4MaxSecond int `json:"mp4_max_second,omitempty"`
|
||||
// hls文件保存保存根目录,置空使用默认
|
||||
HlsSavePath string `json:"hls_save_path,omitempty"`
|
||||
// 断连续推延时,单位毫秒,置空使用配置文件默认值
|
||||
ContinuePushMs uint32 `json:"continue_push_ms,omitempty"`
|
||||
// MP4录制是否当作观看者参与播放人数计数
|
||||
Mp4AsPlayer bool `json:"mp4_as_player,omitempty"`
|
||||
// 该流是否开启时间戳覆盖
|
||||
ModifyStamp bool `json:"modify_stamp,omitempty"`
|
||||
}
|
||||
|
||||
func NewOnPublishDefaultReply() OnPublishHookReply {
|
||||
return OnPublishHookReply{
|
||||
HookReply: HookReply{
|
||||
Code: 0,
|
||||
Msg: SuccessMsg,
|
||||
},
|
||||
AddMuteAudio: true,
|
||||
ContinuePushMs: 10000,
|
||||
EnableAudio: true,
|
||||
EnableFmp4: true,
|
||||
EnableHls: true,
|
||||
EnableMp4: false,
|
||||
EnableRtmp: true,
|
||||
EnableRtsp: true,
|
||||
EnableTs: true,
|
||||
HlsSavePath: "/hls_save_path/",
|
||||
ModifyStamp: false,
|
||||
Mp4AsPlayer: false,
|
||||
Mp4MaxSecond: 3600,
|
||||
Mp4SavePath: "/mp4_save_path/",
|
||||
}
|
||||
}
|
||||
func HookRouter(g *gin.RouterGroup) {
|
||||
g.POST("on_stream_none_reader", func(c *gin.Context) {
|
||||
hookParam := OnStreamNoneReader{}
|
||||
if err := c.ShouldBindJSON(&hookParam); err != nil {
|
||||
log.Error(err)
|
||||
c.JSON(200, HookReply{
|
||||
Code: ParseParamFail,
|
||||
Msg: ParseParamFailMsg,
|
||||
})
|
||||
return
|
||||
}
|
||||
log.Info("收到流无人观看事件,stream_id:", hookParam.Stream, "media_server_id:", hookParam.MediaServerId)
|
||||
deviceChannel := strings.Split(hookParam.Stream, "_")
|
||||
//关闭监控的视频流推送
|
||||
var device model.Device
|
||||
device.DeviceId = deviceChannel[0]
|
||||
device, _ = device.DeviceDetail()
|
||||
if device.DeviceId == "" {
|
||||
util.GinFRes(c, "当前设备不存在", nil)
|
||||
return
|
||||
}
|
||||
var channel model.Channel
|
||||
channel.DeviceId = deviceChannel[1]
|
||||
channel, _ = channel.ChannelDetail()
|
||||
if channel.DeviceId == "" {
|
||||
util.GinFRes(c, "当前通道不存在", nil)
|
||||
return
|
||||
}
|
||||
channel.MediaStatus = "CLOSE"
|
||||
_ = channel.ChannelUpdate()
|
||||
ssrc := hookParam.Stream
|
||||
_ = sipServer.Stop(device, ssrc)
|
||||
c.JSON(200, OnStreamNoneReaderReply{
|
||||
Code: 0,
|
||||
Close: true,
|
||||
})
|
||||
})
|
||||
g.POST("on_stream_changed", func(c *gin.Context) {
|
||||
log.Info("on_stream_changed")
|
||||
c.JSON(200, HookReply{
|
||||
Code: 0,
|
||||
Msg: "success",
|
||||
})
|
||||
})
|
||||
g.POST("on_server_keepalive", func(c *gin.Context) {
|
||||
log.Info("on_server_keepalive")
|
||||
c.JSON(200, HookReply{
|
||||
Code: 0,
|
||||
Msg: "success",
|
||||
})
|
||||
})
|
||||
g.POST("on_play", func(c *gin.Context) {
|
||||
log.Info("on_play")
|
||||
hookParam := OnPlayHookParam{}
|
||||
if err := c.ShouldBindJSON(&hookParam); err != nil {
|
||||
log.Error(err)
|
||||
c.JSON(200, HookReply{
|
||||
Code: ParseParamFail,
|
||||
Msg: ParseParamFailMsg,
|
||||
})
|
||||
return
|
||||
}
|
||||
fmt.Println("hookParam", hookParam)
|
||||
c.JSON(200, HookReply{
|
||||
Code: 0,
|
||||
Msg: "success",
|
||||
})
|
||||
})
|
||||
g.POST("on_publish", func(c *gin.Context) {
|
||||
log.Info("on_publish")
|
||||
c.JSON(200, NewOnPublishDefaultReply())
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"gb28181Panda/sipServer"
|
||||
)
|
||||
|
||||
// InitSipServer 初始化sip服务
|
||||
func InitSipServer() {
|
||||
sipServer.NewServer()
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package sipServer
|
||||
|
||||
import (
|
||||
"gb28181Panda/log"
|
||||
"github.com/ghettovoice/gosip/sip"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func Ack(req sip.Request, tx sip.ServerTransaction) {
|
||||
idx := strings.Index(req.Source(), ":")
|
||||
fromIp := req.Source()[:idx]
|
||||
fromPort := req.Source()[idx+1:]
|
||||
transferFromLog(fromIp, fromPort)
|
||||
log.Info("收到Ack数据", req)
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package sipServer
|
||||
|
||||
import (
|
||||
"gb28181Panda/log"
|
||||
"github.com/ghettovoice/gosip/sip"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func Bye(req sip.Request, tx sip.ServerTransaction) {
|
||||
idx := strings.Index(req.Source(), ":")
|
||||
fromIp := req.Source()[:idx]
|
||||
fromPort := req.Source()[idx+1:]
|
||||
transferFromLog(fromIp, fromPort)
|
||||
log.Info("收到Bye数据", req)
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package sipServer
|
||||
|
||||
import (
|
||||
"gb28181Panda/log"
|
||||
"github.com/ghettovoice/gosip/sip"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func Invite(req sip.Request, tx sip.ServerTransaction) {
|
||||
idx := strings.Index(req.Source(), ":")
|
||||
fromIp := req.Source()[:idx]
|
||||
fromPort := req.Source()[idx+1:]
|
||||
transferFromLog(fromIp, fromPort)
|
||||
log.Info("收到Invite数据", req)
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package sipServer
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"gb28181Panda/log"
|
||||
"gb28181Panda/model"
|
||||
"gb28181Panda/util"
|
||||
"github.com/ghettovoice/gosip/sip"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type MessageStruct struct {
|
||||
XMLName xml.Name
|
||||
CmdType string `xml:"CmdType"`
|
||||
SN string `xml:"SN"`
|
||||
DeviceId string `json:"DeviceId" xml:"DeviceID"`
|
||||
DeviceType string `xml:"DeviceType"`
|
||||
DeviceName string `xml:"DeviceName"`
|
||||
Result string `xml:"Result"`
|
||||
Manufacturer string `xml:"Manufacturer"`
|
||||
Model string `xml:"Model"`
|
||||
Channel string `xml:"Channel"`
|
||||
Firmware string `xml:"Firmware"`
|
||||
DeviceList []*model.Channel `xml:"DeviceList>Item"`
|
||||
}
|
||||
|
||||
// Message Sip的Message
|
||||
func Message(req sip.Request, tx sip.ServerTransaction) {
|
||||
idx := strings.Index(req.Source(), ":")
|
||||
fromIp := req.Source()[:idx]
|
||||
fromPort := req.Source()[idx+1:]
|
||||
transferFromLog(fromIp, fromPort)
|
||||
log.Info("MESSAGE-Request:\n", req)
|
||||
if l, ok := req.ContentLength(); !ok || l.Equals(0) {
|
||||
log.Info("该MESSAGE消息的消息体长度为0,返回OK")
|
||||
_ = tx.Respond(sip.NewResponseFromRequest("", req, http.StatusOK, http.StatusText(http.StatusOK), ""))
|
||||
}
|
||||
body := req.Body()
|
||||
//xml GB2312 解包有问题 换成UTF-8
|
||||
body = strings.Replace(body, "GB2312", "UTF-8", 1)
|
||||
msgData := &MessageStruct{}
|
||||
if err := xml.Unmarshal([]byte(body), msgData); err != nil {
|
||||
log.Error("解析deviceInfo响应包出错", err)
|
||||
return
|
||||
}
|
||||
if msgData.XMLName.Local == "Response" {
|
||||
switch msgData.CmdType {
|
||||
case "Keepalive":
|
||||
device := model.Device{
|
||||
DeviceId: msgData.DeviceId,
|
||||
KeepaliveAt: util.GetCurrenTimeNow(),
|
||||
}
|
||||
//更新device设备信息
|
||||
_ = device.DeviceUpdate()
|
||||
case "DeviceInfo":
|
||||
device := model.Device{
|
||||
Name: msgData.DeviceType,
|
||||
Manufacturer: msgData.Manufacturer,
|
||||
Model: msgData.Model,
|
||||
Firmware: msgData.Firmware,
|
||||
DeviceId: msgData.DeviceId,
|
||||
}
|
||||
//更新device设备信息
|
||||
_ = device.DeviceUpdate()
|
||||
case "Catalog":
|
||||
for _, item := range msgData.DeviceList {
|
||||
//先查询当前设备是否在数据库中
|
||||
channel, _ := item.ChannelDetail()
|
||||
if channel.DeviceId != "" { //更新
|
||||
item.UpdatedAt = util.GetCurrenTimeNow()
|
||||
item.ParentId = msgData.DeviceId
|
||||
_ = item.ChannelUpdate()
|
||||
} else { // 新增
|
||||
item.CreatedAt = util.GetCurrenTimeNow()
|
||||
item.UpdatedAt = util.GetCurrenTimeNow()
|
||||
item.ParentId = msgData.DeviceId
|
||||
item.TransportType = "UDP"
|
||||
item.MediaStatus = "CLOSE"
|
||||
_ = item.ChannelAdd()
|
||||
}
|
||||
|
||||
}
|
||||
case "RecordInfo":
|
||||
_ = sipMessageRecordInfo(body)
|
||||
default:
|
||||
log.Infof("【cmdType】", msgData.CmdType)
|
||||
}
|
||||
_ = tx.Respond(sip.NewResponseFromRequest("", req, http.StatusOK, http.StatusText(http.StatusOK), ""))
|
||||
} else if msgData.XMLName.Local == "Notify" {
|
||||
//下级设备保持keepalive是走的Message的notify 区别于Notify本身
|
||||
device := model.Device{
|
||||
DeviceId: msgData.DeviceId,
|
||||
KeepaliveAt: util.GetCurrenTimeNow(),
|
||||
}
|
||||
//更新device设备信息
|
||||
_ = device.DeviceUpdate()
|
||||
//需要回复信息 不然设备会发送注销再走注册流程
|
||||
errBack := tx.Respond(sip.NewResponseFromRequest("", req, http.StatusOK, http.StatusText(http.StatusOK), ""))
|
||||
transferToLog(fromIp, fromPort)
|
||||
log.Info("回复设备保持在线成功")
|
||||
if errBack != nil {
|
||||
log.Info("回复设备keepalive出错了")
|
||||
//只尝试一次再次回复
|
||||
_ = tx.Respond(sip.NewResponseFromRequest("", req, http.StatusOK, http.StatusText(http.StatusOK), ""))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package sipServer
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"gb28181Panda/log"
|
||||
"gb28181Panda/util"
|
||||
"github.com/ghettovoice/gosip/sip"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Notify 通知
|
||||
func Notify(req sip.Request, tx sip.ServerTransaction) {
|
||||
idx := strings.Index(req.Source(), ":")
|
||||
fromIp := req.Source()[:idx]
|
||||
fromPort := req.Source()[idx+1:]
|
||||
log.Info("Notify-Request:\n", req)
|
||||
transferFromLog(fromIp, fromPort)
|
||||
if l, ok := req.ContentLength(); !ok || l.Equals(0) {
|
||||
log.Debug("该MESSAGE消息的消息体长度为0,返回OK")
|
||||
_ = tx.Respond(sip.NewResponseFromRequest("", req, http.StatusOK, http.StatusText(http.StatusOK), ""))
|
||||
}
|
||||
body := req.Body()
|
||||
body = strings.Replace(body, "GB2312", "UTF-8", 1)
|
||||
msgData := &MessageStruct{}
|
||||
if err := xml.Unmarshal([]byte(body), msgData); err != nil {
|
||||
log.Error("解析deviceInfo响应包出错", err)
|
||||
return
|
||||
}
|
||||
log.Info("Notify-Type:", msgData.CmdType)
|
||||
if msgData.XMLName.Local == "Notify" {
|
||||
switch msgData.CmdType {
|
||||
case "Catalog":
|
||||
log.Info("下级主动推送channel通道信息")
|
||||
//下级主动推送通道信息
|
||||
for _, item := range msgData.DeviceList {
|
||||
//先查询当前设备是否在数据库中
|
||||
channel, _ := item.ChannelDetail()
|
||||
if channel.DeviceId != "" { //更新
|
||||
item.UpdatedAt = util.GetCurrenTimeNow()
|
||||
item.ParentId = msgData.DeviceId
|
||||
_ = item.ChannelUpdate()
|
||||
} else { // 新增
|
||||
item.CreatedAt = util.GetCurrenTimeNow()
|
||||
item.UpdatedAt = util.GetCurrenTimeNow()
|
||||
item.ParentId = msgData.DeviceId
|
||||
item.TransportType = "UDP"
|
||||
item.MediaStatus = "CLOSE"
|
||||
_ = item.ChannelAdd()
|
||||
}
|
||||
}
|
||||
default:
|
||||
log.Infof("【cmdType】", msgData.CmdType)
|
||||
}
|
||||
_ = tx.Respond(sip.NewResponseFromRequest("", req, http.StatusOK, http.StatusText(http.StatusOK), ""))
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package sipServer
|
||||
|
||||
import (
|
||||
"gb28181Panda/log"
|
||||
"gb28181Panda/model"
|
||||
"github.com/ghettovoice/gosip/sip"
|
||||
"github.com/pkg/errors"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Play 播放实时视频或历史回放点播
|
||||
func Play(device model.Device, channel model.Channel, ssrc string, rtpPort int, playType, startT, endT string) (model.StreamInfo, error) {
|
||||
body := createVideoSdpInfo(channel, ssrc, rtpPort, playType, startT, endT)
|
||||
request, _ := createVideoMessageRequest(device, contentTypeSDP, sip.INVITE, channel.DeviceId, ssrc, body)
|
||||
transferToLog(device.Ip, device.Port)
|
||||
log.Info("Play-Request:\n", request)
|
||||
tx, err := transmitRequest(request)
|
||||
if err != nil {
|
||||
log.Error("发送视频播放请求错误")
|
||||
return model.StreamInfo{}, errors.New("发送视频播放请求错误")
|
||||
}
|
||||
resp := getResponse(tx)
|
||||
//TODO 这块的错误处理后续优化
|
||||
if resp.StatusCode() != sip.StatusCode(http.StatusOK) {
|
||||
return model.StreamInfo{}, errors.New("当前时间错误")
|
||||
}
|
||||
log.Info("收到invite响应:\n", resp)
|
||||
if resp == nil {
|
||||
log.Error("获取响应超时")
|
||||
return model.StreamInfo{}, errors.New("获取响应超时")
|
||||
}
|
||||
ackRequest := sip.NewAckRequest("", request, resp, "", nil)
|
||||
ackRequest.SetRecipient(request.Recipient())
|
||||
ackRequest.AppendHeader(&sip.ContactHeader{
|
||||
Address: request.Recipient(),
|
||||
Params: nil,
|
||||
})
|
||||
transferToLog(device.Ip, device.Port)
|
||||
log.Info("发送ack确认:\n", ackRequest)
|
||||
err = SipServer.Send(ackRequest)
|
||||
if err != nil {
|
||||
log.Errorf("发送ack失败", err)
|
||||
return model.StreamInfo{}, errors.New("发送invite请求错误")
|
||||
}
|
||||
callId, fromTag, toTag, branch, err := getRequestTxField(request, resp)
|
||||
saveStreamSession(device.DeviceId, channel.DeviceId, ssrc, callId, fromTag, toTag, branch)
|
||||
if err != nil {
|
||||
return model.StreamInfo{}, err
|
||||
}
|
||||
info := model.MustNewStreamInfo(ssrc)
|
||||
return info, nil
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package sipServer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gb28181Panda/log"
|
||||
"gb28181Panda/model"
|
||||
"gb28181Panda/util"
|
||||
"github.com/ghettovoice/gosip/sip"
|
||||
"github.com/pkg/errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ControlPTZ 设备ptz控制指令
|
||||
func ControlPTZ(device model.Device, command string, horizonSpeed, verticalSpeed, zoomSpeed int) error {
|
||||
cmdStr, err := createPTZCode(command, horizonSpeed, verticalSpeed, zoomSpeed)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
xml, err := util.CreateControlXml(util.DeviceControl, device.DeviceId, util.WithPTZCmd(cmdStr))
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
request, _ := createMessageRequest(device, "Application/MANSCDP+xml", sip.MESSAGE, xml)
|
||||
transferToLog(device.Ip, device.Port)
|
||||
log.Info("Ptz-Request", request)
|
||||
tx, err := transmitRequest(request)
|
||||
if err != nil {
|
||||
log.Error("Ptz发送失败")
|
||||
return errors.New("Ptz发送失败")
|
||||
}
|
||||
resp := getResponse(tx)
|
||||
log.Info("收到invite响应:\n", resp)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 创建PTZ指令
|
||||
// 根据gb28181协议的标准,前端指令中一共包含4个字节
|
||||
func createPTZCode(command string, horizonSpeed, verticalSpeed, zoomSpeed int) (string, error) {
|
||||
var ptz strings.Builder
|
||||
// gb28181协议中控制指令中的前三个字节
|
||||
// 字节1是A5,字节2是组合码,高4位由版本信息组成,版本信息为0H;低四位是校验位,校验位=(字节1的高4位+字节1的低四位+字节2的高四位) % 16
|
||||
// 所以校验码 = (0xa + 0x5 + 0) % 16 = (1010 + 0101 + 0) % 16 = 15 % 16 = 15;十进制数15转十六进制= F
|
||||
// 所以字节2 = 0F
|
||||
// 字节3是地址的低8位,这里直接设置为01
|
||||
ptz.WriteString("A50F01")
|
||||
var cmd int
|
||||
|
||||
// 指令码以一个字节来表示
|
||||
// 0000 0000,高位的前两个bit不做表示
|
||||
// 所以有作用的也就是后6个bit,从高到低,这些bit分别控制云台的镜头缩小、镜头放大、上、下、左、右
|
||||
// 如果有做对应的操作,就将对应的bit位置1
|
||||
switch command {
|
||||
case "right":
|
||||
// 0000 0001
|
||||
cmd = 1
|
||||
case "left":
|
||||
// 0000 0010
|
||||
cmd = 2
|
||||
case "down":
|
||||
// 0000 0100
|
||||
cmd = 4
|
||||
case "up":
|
||||
// 0000 1000
|
||||
cmd = 8
|
||||
case "downright":
|
||||
// 0000 0101
|
||||
cmd = 5
|
||||
case "downleft":
|
||||
// 0000 0110
|
||||
cmd = 6
|
||||
case "upright":
|
||||
// 0000 1001
|
||||
cmd = 9
|
||||
case "upleft":
|
||||
// 0000 1010
|
||||
cmd = 10
|
||||
case "zoomin":
|
||||
// 0001 0000
|
||||
cmd = 16
|
||||
case "zoomout":
|
||||
// 0010 0000
|
||||
cmd = 32
|
||||
case "stop":
|
||||
cmd = 0
|
||||
default:
|
||||
return "", errors.New("不合规的控制字符串")
|
||||
}
|
||||
|
||||
// 根据gb标准,字节4用于表示云台的镜头缩小、镜头放大、上、下、左、右,写入指令码的16进制数
|
||||
ptz.WriteString(fmt.Sprintf("%02X", cmd))
|
||||
|
||||
log.Debug("合并字节4之后:" + ptz.String())
|
||||
|
||||
// 根据gb标准,字节5用于表示水平控制速度,写入水平控制方向速度的十六进制数
|
||||
ptz.WriteString(fmt.Sprintf("%02X", horizonSpeed))
|
||||
|
||||
// 根据gb标准,字节6用于表示垂直控制速度,写入垂直控制方向速度的十六进制数
|
||||
ptz.WriteString(fmt.Sprintf("%02X", verticalSpeed))
|
||||
|
||||
// 最后字节7的高4位用于表示变倍控制速度,后4位不关注
|
||||
// 所以这里直接与0xF0做与操作,保留前4位,后4为置0
|
||||
c := zoomSpeed & 0xF0
|
||||
ptz.WriteString(fmt.Sprintf("%02X", c))
|
||||
|
||||
// 字节8用于校验位,根据gb标准,校验位=(字节1+字节2+字节3+字节4+字节5+字节6+字节7) % 256
|
||||
checkCode := (0xA5 + 0x0F + 0x01 + cmd + horizonSpeed + verticalSpeed + c) % 0x100
|
||||
ptz.WriteString(fmt.Sprintf("%02X", checkCode))
|
||||
log.Debug("最终生成的PTZCmd: " + ptz.String())
|
||||
return ptz.String(), nil
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
package sipServer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gb28181Panda/config"
|
||||
"gb28181Panda/log"
|
||||
"gb28181Panda/model"
|
||||
"gb28181Panda/util"
|
||||
"github.com/ghettovoice/gosip/sip"
|
||||
"github.com/pkg/errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
// QueryDeviceSip 查询设备信息SIP
|
||||
func QueryDeviceSip(device model.Device) {
|
||||
xml, err := util.CreateQueryXML(util.DeviceInfoCmdType, device.DeviceId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
request, _ := createMessageRequest(device, contentTypeXML, sip.MESSAGE, xml)
|
||||
transferToLog(device.Ip, device.Port)
|
||||
log.Info(request)
|
||||
tx, err := transmitRequest(request)
|
||||
if err != nil {
|
||||
log.Error("发送查询通道请求错误")
|
||||
return
|
||||
}
|
||||
resp := getResponse(tx)
|
||||
log.Info("收到invite响应:\n", resp)
|
||||
}
|
||||
|
||||
// QueryChannelSip 查询设备通道SIP
|
||||
func QueryChannelSip(device model.Device) {
|
||||
xml, err := util.CreateQueryXML(util.CatalogCmdType, device.DeviceId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
request, _ := createMessageRequest(device, contentTypeXML, sip.MESSAGE, xml)
|
||||
transferToLog(device.Ip, device.Port)
|
||||
log.Info(request)
|
||||
tx, err := transmitRequest(request)
|
||||
if err != nil {
|
||||
log.Error("发送查询通道请求错误")
|
||||
return
|
||||
}
|
||||
resp := getResponse(tx)
|
||||
log.Info("收到invite响应:\n", resp)
|
||||
}
|
||||
|
||||
// SetGuardSip 设防SIP
|
||||
func SetGuardSip(device model.Device) {
|
||||
xml, err := util.CreateGuardXML(util.DeviceControl, device.DeviceId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_, requestBuilder := createMessageRequest(device, contentTypeXML, sip.MESSAGE, xml)
|
||||
requestBuilder.SetContact(newToPort(config.SipOp.Id, config.SipOp.Ip, config.SipOp.Port))
|
||||
request, _ := requestBuilder.Build()
|
||||
transferToLog(device.Ip, device.Port)
|
||||
log.Info(request)
|
||||
tx, err := transmitRequest(request)
|
||||
if err != nil {
|
||||
log.Error("发送预警订阅请求错误")
|
||||
return
|
||||
}
|
||||
resp := getResponse(tx)
|
||||
log.Info("收到预警订阅响应:\n", resp)
|
||||
}
|
||||
|
||||
// SubscribeAlarmSip 订阅预警消息SIP
|
||||
func SubscribeAlarmSip(device model.Device) {
|
||||
xml, err := util.CreateSubscribeAlarmXML(util.AlarmCmdType, device.DeviceId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_, requestBuilder := createMessageRequest(device, contentTypeXML, sip.SUBSCRIBE, xml)
|
||||
requestBuilder.SetContact(newToPort(config.SipOp.Id, config.SipOp.Ip, config.SipOp.Port))
|
||||
exe := sip.Expires(3600)
|
||||
requestBuilder.SetExpires(&exe)
|
||||
//测试添加头部Event内容
|
||||
requestBuilder.AddHeader(&sip.GenericHeader{
|
||||
HeaderName: "Event",
|
||||
Contents: "Alarm",
|
||||
})
|
||||
request, _ := requestBuilder.Build()
|
||||
transferToLog(device.Ip, device.Port)
|
||||
log.Info(request)
|
||||
tx, err := transmitRequest(request)
|
||||
if err != nil {
|
||||
log.Error("发送预警订阅请求错误")
|
||||
return
|
||||
}
|
||||
resp := getResponse(tx)
|
||||
log.Info("收到预警订阅响应:\n", resp)
|
||||
}
|
||||
|
||||
// 发送I帧 SIP
|
||||
func IFameSip(device model.Device, channelId string) {
|
||||
xml, err := util.CreateIFameXML(channelId)
|
||||
if err != nil {
|
||||
log.Info("发送IFame请求错误")
|
||||
}
|
||||
_, requestBuilder := createMessageRequest(device, contentTypeXML, sip.MESSAGE, xml)
|
||||
requestBuilder.SetContact(newToPort(config.SipOp.Id, config.SipOp.Ip, config.SipOp.Port))
|
||||
exe := sip.Expires(3600)
|
||||
requestBuilder.SetExpires(&exe)
|
||||
request, _ := requestBuilder.Build()
|
||||
transferToLog(device.Ip, device.Port)
|
||||
log.Info(request)
|
||||
tx, err := transmitRequest(request)
|
||||
if err != nil {
|
||||
log.Info("发送IFame请求错误")
|
||||
}
|
||||
response := getResponse(tx)
|
||||
log.Info("收到IFame结果:\n", response)
|
||||
}
|
||||
|
||||
// RecordInfoSip 获取历史视频SIP
|
||||
func RecordInfoSip(device model.Device, channelId string, startTime string, endTime string) (*[]RecordInfo, error) {
|
||||
ch := make(chan int, 1)
|
||||
defer close(ch)
|
||||
xml, sn, err := util.CreateRecordInfoXml(util.RecordInfoCmdType, channelId, startTime, endTime)
|
||||
if err != nil {
|
||||
return nil, errors.New("获取数据超时")
|
||||
}
|
||||
_, requestBuilder := createMessageRequest(device, contentTypeXML, sip.MESSAGE, xml)
|
||||
requestBuilder.SetContact(newToPort(config.SipOp.Id, config.SipOp.Ip, config.SipOp.Port))
|
||||
exe := sip.Expires(3600)
|
||||
requestBuilder.SetExpires(&exe)
|
||||
request, _ := requestBuilder.Build()
|
||||
transferToLog(device.Ip, device.Port)
|
||||
log.Info(request)
|
||||
tx, err := transmitRequest(request)
|
||||
if err != nil {
|
||||
log.Error("发送请求历史视频请求错误")
|
||||
return nil, errors.New("获取数据超时")
|
||||
}
|
||||
response := getResponse(tx)
|
||||
log.Info("收到历史视频响应:\n", response)
|
||||
recordKey := fmt.Sprintf("%s%s", channelId, sn)
|
||||
//开始和结束时间
|
||||
_recordList.Delete(recordKey)
|
||||
_recordList.Store(recordKey, recordList{ch: ch, num: 0, data: []RecordInfo{}})
|
||||
tick := time.NewTicker(30 * time.Second)
|
||||
select {
|
||||
case _, chStatus := <-ch:
|
||||
if !chStatus {
|
||||
return nil, errors.New("获取数据超时-chan通道已关闭")
|
||||
}
|
||||
if list, ok := _recordList.Load(recordKey); ok {
|
||||
data := list.(recordList)
|
||||
recordData := parseRecordData(data.data)
|
||||
return &recordData, nil
|
||||
}
|
||||
return nil, errors.New("获取数据超时")
|
||||
case <-tick.C:
|
||||
// 30秒未完成返回当前获取到的数据
|
||||
if list, ok := _recordList.Load(recordKey); ok {
|
||||
data := list.(recordList)
|
||||
recordData := parseRecordData(data.data)
|
||||
return &recordData, nil
|
||||
}
|
||||
return nil, errors.New("获取数据超时")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package sipServer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gb28181Panda/log"
|
||||
"gb28181Panda/util"
|
||||
"github.com/pkg/errors"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// MessageRecordInfoResponse 目录列表
|
||||
type MessageRecordInfoResponse struct {
|
||||
CmdType string `xml:"CmdType"`
|
||||
SN int `xml:"SN"`
|
||||
DeviceID string `xml:"DeviceID"`
|
||||
SumNum int `xml:"SumNum"`
|
||||
Item []RecordItem `xml:"RecordList>Item"`
|
||||
}
|
||||
|
||||
// RecordItem 目录详情
|
||||
type RecordItem struct {
|
||||
// DeviceID 设备编号
|
||||
DeviceID string `xml:"DeviceID" bson:"DeviceID" json:"DeviceID"`
|
||||
// Name 设备名称
|
||||
Name string `xml:"Name" bson:"Name" json:"Name"`
|
||||
FilePath string `xml:"FilePath" bson:"FilePath" json:"FilePath"`
|
||||
Address string `xml:"Address" bson:"Address" json:"Address"`
|
||||
StartTime string `xml:"StartTime" bson:"StartTime" json:"StartTime"`
|
||||
EndTime string `xml:"EndTime" bson:"EndTime" json:"EndTime"`
|
||||
Secrecy int `xml:"Secrecy" bson:"Secrecy" json:"Secrecy"`
|
||||
Type string `xml:"Type" bson:"Type" json:"Type"`
|
||||
}
|
||||
|
||||
type recordList struct {
|
||||
ch chan int
|
||||
num int
|
||||
data []RecordInfo
|
||||
}
|
||||
|
||||
// RecordInfo 录像开始时间段
|
||||
type RecordInfo struct {
|
||||
StartTime int64 `json:"startTime"`
|
||||
EndTime int64 `json:"endTime"`
|
||||
}
|
||||
|
||||
// 当前获取目录文件设备集合
|
||||
var _recordList *sync.Map
|
||||
|
||||
func sipMessageRecordInfo(body string) error {
|
||||
message := &MessageRecordInfoResponse{}
|
||||
if err := util.XMLDecode([]byte(body), message); err != nil {
|
||||
log.Info("Message Unmarshal xml err:", err, "body:", string(body))
|
||||
return err
|
||||
}
|
||||
recordKey := fmt.Sprintf("%s%d", message.DeviceID, message.SN)
|
||||
if list, ok := _recordList.Load(recordKey); ok {
|
||||
info := list.(recordList)
|
||||
info.num += len(message.Item)
|
||||
for _, item := range message.Item {
|
||||
s, _ := time.ParseInLocation("2006-01-02T15:04:05", item.StartTime, time.Local)
|
||||
e, _ := time.ParseInLocation("2006-01-02T15:04:05", item.EndTime, time.Local)
|
||||
recordInfo := RecordInfo{
|
||||
StartTime: s.Unix(),
|
||||
EndTime: e.Unix(),
|
||||
}
|
||||
info.data = append(info.data, recordInfo)
|
||||
}
|
||||
if info.num == message.SumNum {
|
||||
// 获取到完整数据
|
||||
_recordList.Store(recordKey, info)
|
||||
info.ch <- 1
|
||||
}
|
||||
_recordList.Store(recordKey, info)
|
||||
return nil
|
||||
}
|
||||
return errors.New("未查询到历史视频")
|
||||
}
|
||||
|
||||
func parseRecordData(data []RecordInfo) []RecordInfo {
|
||||
var tempData []RecordInfo
|
||||
for _, v := range data {
|
||||
if len(tempData) > 0 {
|
||||
recordL := len(tempData)
|
||||
if tempData[recordL-1:][0].EndTime == v.StartTime {
|
||||
tempData[recordL-1:][0].EndTime = v.EndTime
|
||||
} else {
|
||||
tempData = append(tempData, v)
|
||||
}
|
||||
} else {
|
||||
tempData = append(tempData, v)
|
||||
}
|
||||
}
|
||||
return tempData
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
package sipServer
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"gb28181Panda/config"
|
||||
"gb28181Panda/log"
|
||||
"gb28181Panda/model"
|
||||
"gb28181Panda/util"
|
||||
"github.com/ghettovoice/gosip/sip"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultAlgorithm = "MD5"
|
||||
WWWHeader = "WWW-Authenticate"
|
||||
ExpiresHeader = "Expires"
|
||||
)
|
||||
|
||||
type Authorization struct {
|
||||
*sip.Authorization
|
||||
}
|
||||
|
||||
func (a *Authorization) Verify(username, passwd string) bool {
|
||||
//1、将 username,realm,password 依次组合获取 1 个字符串,并用算法加密的到密文 r1
|
||||
//是否校验域值
|
||||
var s1 string
|
||||
if config.SipOp.CheckRealm == 1 {
|
||||
s1 = fmt.Sprintf("%s:%s:%s", username, config.SipOp.Realm, passwd) //检验域值,从当前配置读取域值
|
||||
} else {
|
||||
s1 = fmt.Sprintf("%s:%s:%s", username, a.Realm(), passwd) //不检验域值,从设备取域值
|
||||
}
|
||||
r1 := a.getDigest(s1)
|
||||
//2、将 method,即REGISTER ,uri 依次组合获取 1 个字符串,并对这个字符串使用算法 加密得到密文 r2
|
||||
s2 := fmt.Sprintf("REGISTER:%s", a.Uri())
|
||||
r2 := a.getDigest(s2)
|
||||
if r1 == "" || r2 == "" {
|
||||
log.Error("Authorization algorithm wrong")
|
||||
return false
|
||||
}
|
||||
//3、将密文 1,nonce 和密文 2 依次组合获取 1 个字符串,并对这个字符串使用算法加密,获得密文 r3,即Response
|
||||
s3 := fmt.Sprintf("%s:%s:%s", r1, a.Nonce(), r2)
|
||||
r3 := a.getDigest(s3)
|
||||
//4、计算服务端和客户端上报的是否相等
|
||||
isGet := r3 == a.Response()
|
||||
//log.Info("[Password-Verify-密码校验情况]", isGet)
|
||||
return isGet
|
||||
}
|
||||
|
||||
func (a *Authorization) getDigest(raw string) string {
|
||||
switch a.Algorithm() {
|
||||
case "MD5":
|
||||
return fmt.Sprintf("%x", md5.Sum([]byte(raw)))
|
||||
default: //如果没有算法,默认使用MD5
|
||||
return fmt.Sprintf("%x", md5.Sum([]byte(raw)))
|
||||
}
|
||||
}
|
||||
|
||||
// Register express等于0 相当于注销后设备下线
|
||||
func Register(req sip.Request, tx sip.ServerTransaction) {
|
||||
idx := strings.Index(req.Source(), ":")
|
||||
from, _ := req.From()
|
||||
fromIp := req.Source()[:idx]
|
||||
fromPort := req.Source()[idx+1:]
|
||||
id := from.Address.User().String()
|
||||
transferFromLog(fromIp, fromPort)
|
||||
log.Info("Register-Request", req)
|
||||
//注销的代码
|
||||
h := req.GetHeaders(ExpiresHeader)
|
||||
if len(h) != 1 {
|
||||
log.Error("从头部获取expires失败", req)
|
||||
return
|
||||
} else {
|
||||
expires := h[0].(*sip.Expires)
|
||||
// 如果v=0,则代表该请求是注销请求
|
||||
if expires.Equals(new(sip.Expires)) {
|
||||
//expires值为0,该请求是注销请求
|
||||
log.Info("Register-Logout")
|
||||
fromRequest, ok := util.DeviceFromRequest(req)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
device, _ := fromRequest.DeviceDetail()
|
||||
if device.DeviceId != "" {
|
||||
device = fromRequest
|
||||
device.Status = "OFF"
|
||||
log.Info("deviceInfo", device)
|
||||
//插入数据库的
|
||||
_ = device.DeviceUpdate()
|
||||
}
|
||||
} else {
|
||||
if len(id) != 20 {
|
||||
transferToLog(fromIp, fromPort)
|
||||
log.Infof("错误的国标id: %s", id)
|
||||
response := sip.NewResponseFromRequest("", req, http.StatusForbidden, "Forbidden", "")
|
||||
_ = tx.Respond(response)
|
||||
return
|
||||
}
|
||||
passAuth := false
|
||||
// 不需要密码情况
|
||||
if config.SipOp.Password == "" {
|
||||
passAuth = true
|
||||
} else {
|
||||
// 需要密码情况 设备第一次上报,返回401和加密算法
|
||||
if headers := req.GetHeaders("Authorization"); len(headers) > 0 {
|
||||
log.Info("Register-With-Authorization")
|
||||
authenticateHeader := headers[0].(*sip.GenericHeader)
|
||||
auth := &Authorization{sip.AuthFromValue(authenticateHeader.Contents)}
|
||||
// 有些摄像头没有配置用户名的地方,用户名就是摄像头自己的国标id
|
||||
var username string
|
||||
username = id
|
||||
// 设备第二次上报,校验
|
||||
if auth.Verify(username, config.SipOp.Password) {
|
||||
log.Info("Check-Username-Password-Success")
|
||||
passAuth = true
|
||||
} else {
|
||||
log.Info("Check-Username-Password-Error")
|
||||
transferToLog(fromIp, fromPort)
|
||||
response := sip.NewResponseFromRequest("", req, http.StatusForbidden, "Forbidden", "")
|
||||
log.Info(response)
|
||||
_ = tx.Respond(response)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
if passAuth {
|
||||
//认证账号和密码成功
|
||||
log.Info("Auth-Success")
|
||||
//从回复数据中解析device设备信息
|
||||
fromRequest, ok := util.DeviceFromRequest(req)
|
||||
if !ok {
|
||||
log.Error("fromRequest-Error")
|
||||
return
|
||||
}
|
||||
//先查询当前设备是否在数据库中
|
||||
device, _ := fromRequest.DeviceDetail()
|
||||
//不存在插入数据
|
||||
if device.DeviceId == "" {
|
||||
device = fromRequest
|
||||
device.CreatedAt = util.GetCurrenTimeNow()
|
||||
device.UpdatedAt = util.GetCurrenTimeNow()
|
||||
device.RegisterAt = util.GetCurrenTimeNow()
|
||||
device.KeepaliveAt = util.GetCurrenTimeNow()
|
||||
device.Expires = expires.Value()
|
||||
device.Status = "ON"
|
||||
device.Ip = fromIp
|
||||
device.Port = fromPort
|
||||
log.Info("Insert Database Device", device)
|
||||
//插入数据库的
|
||||
_ = device.DeviceAdd()
|
||||
} else {
|
||||
var deviceSql model.Device
|
||||
deviceSql.RegisterAt = util.GetCurrenTimeNow()
|
||||
deviceSql.KeepaliveAt = util.GetCurrenTimeNow()
|
||||
deviceSql.Status = "ON"
|
||||
deviceSql.Ip = fromIp
|
||||
deviceSql.Port = fromPort
|
||||
deviceSql.DeviceId = device.DeviceId
|
||||
// 存在,更新数据
|
||||
log.Info("Update Database Device", device)
|
||||
_ = deviceSql.DeviceUpdate()
|
||||
}
|
||||
|
||||
resp := sip.NewResponseFromRequest("", req, http.StatusOK, "OK", "")
|
||||
to, _ := resp.To()
|
||||
resp.ReplaceHeaders("To", []sip.Header{&sip.ToHeader{Address: to.Address, Params: sip.NewParams().Add("tag", sip.String{Str: util.RandString(9)})}})
|
||||
resp.RemoveHeader("Allow")
|
||||
expires := sip.Expires(3600)
|
||||
resp.AppendHeader(&expires)
|
||||
resp.AppendHeader(&sip.GenericHeader{
|
||||
HeaderName: "Date",
|
||||
Contents: util.GetCurrenTimeNowFormat(),
|
||||
})
|
||||
transferToLog(fromIp, fromPort)
|
||||
log.Info("Register-Success", resp)
|
||||
_ = tx.Respond(resp)
|
||||
//查询设备的信息和通道信息
|
||||
QueryDeviceSip(device)
|
||||
QueryChannelSip(device)
|
||||
} else {
|
||||
response := sip.NewResponseFromRequest("", req, http.StatusUnauthorized, "Unauthorized", "")
|
||||
auth := fmt.Sprintf(
|
||||
`Digest realm="%s",algorithm=%s,nonce="%s"`,
|
||||
config.SipOp.Realm,
|
||||
DefaultAlgorithm,
|
||||
util.RandString(32),
|
||||
)
|
||||
response.AppendHeader(&sip.GenericHeader{
|
||||
HeaderName: WWWHeader,
|
||||
Contents: auth,
|
||||
})
|
||||
transferToLog(fromIp, fromPort)
|
||||
log.Info("Register-Back-With-[WWW-Authenticate]-Header", response)
|
||||
_ = tx.Respond(response)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
package sipServer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gb28181Panda/config"
|
||||
"gb28181Panda/log"
|
||||
"gb28181Panda/model"
|
||||
"gb28181Panda/storage"
|
||||
"gb28181Panda/util"
|
||||
"github.com/ghettovoice/gosip/sip"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cast"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
contentTypeXML = "Application/MANSCDP+xml"
|
||||
contentTypeSDP = "APPLICATION/SDP"
|
||||
)
|
||||
|
||||
// 普通的消息组成
|
||||
func createMessageRequest(device model.Device, contentType string, method sip.RequestMethod, body string) (sip.Request, *sip.RequestBuilder) {
|
||||
requestBuilder := sip.NewRequestBuilder()
|
||||
devicePort, _ := strconv.Atoi(device.Port)
|
||||
requestBuilder.SetFrom(newFromAddress(newParams(map[string]string{"tag": util.RandString(32)})))
|
||||
to := newToPort(device.DeviceId, device.Ip, devicePort)
|
||||
requestBuilder.SetTo(to)
|
||||
requestBuilder.SetRecipient(to.Uri)
|
||||
requestBuilder.AddVia(newVia(device.Transport))
|
||||
requestContentType := sip.ContentType(contentType)
|
||||
requestBuilder.SetContentType(&requestContentType)
|
||||
requestBuilder.SetMethod(method)
|
||||
userAgent := sip.UserAgentHeader(fmt.Sprintf("%s", config.SipOp.UserAgent))
|
||||
requestBuilder.SetUserAgent(&userAgent)
|
||||
requestBuilder.SetBody(body)
|
||||
ceq, _ := storage.GetCeq("ceq")
|
||||
requestBuilder.SetSeqNo(cast.ToUint(ceq))
|
||||
request, _ := requestBuilder.Build()
|
||||
return request, requestBuilder
|
||||
}
|
||||
|
||||
// 视频播放和点播
|
||||
func createVideoMessageRequest(device model.Device, contentType string, method sip.RequestMethod, channelId string, ssrc string, body string) (sip.Request, *sip.RequestBuilder) {
|
||||
requestBuilder := sip.NewRequestBuilder()
|
||||
devicePort, _ := strconv.Atoi(device.Port)
|
||||
to := newToPort(channelId, device.Ip, devicePort)
|
||||
requestBuilder.SetMethod(method)
|
||||
requestBuilder.SetFrom(newFromAddress(newParams(map[string]string{"tag": util.RandString(32)})))
|
||||
requestBuilder.SetTo(to)
|
||||
port := sip.Port(devicePort)
|
||||
sipUri := &sip.SipUri{
|
||||
FUser: sip.String{Str: channelId},
|
||||
FHost: to.Uri.Host(),
|
||||
FPort: &port,
|
||||
}
|
||||
requestBuilder.SetRecipient(sipUri)
|
||||
requestBuilder.AddVia(newVia(device.Transport))
|
||||
requestBuilder.SetContact(newToPort(config.SipOp.Id, config.SipOp.Ip, config.SipOp.Port))
|
||||
requestContentType := sip.ContentType(contentType)
|
||||
requestBuilder.SetContentType(&requestContentType)
|
||||
requestBuilder.SetBody(body)
|
||||
userAgent := sip.UserAgentHeader(config.SipOp.UserAgent)
|
||||
requestBuilder.SetUserAgent(&userAgent)
|
||||
ceq, _ := storage.GetCeq("ceq")
|
||||
requestBuilder.SetSeqNo(cast.ToUint(ceq))
|
||||
callID := sip.CallID(fmt.Sprintf("%s", util.RandString(22)))
|
||||
requestBuilder.SetCallID(&callID)
|
||||
header := sip.GenericHeader{
|
||||
HeaderName: "Subject",
|
||||
Contents: fmt.Sprintf("%s:%s,%s:%d", channelId, ssrc, config.SipOp.Id, 0),
|
||||
}
|
||||
requestBuilder.AddHeader(&header)
|
||||
request, _ := requestBuilder.Build()
|
||||
return request, requestBuilder
|
||||
}
|
||||
|
||||
// 向上级联注册
|
||||
func createCascadeRequest(method sip.RequestMethod, cascade model.Cascade) sip.Request {
|
||||
//创建请求
|
||||
requestBuilder := sip.NewRequestBuilder()
|
||||
to := newToPort(cascade.Id, cascade.Ip, cascade.Port)
|
||||
requestBuilder.SetMethod(method)
|
||||
requestBuilder.SetFrom(newFromAddress(newParams(map[string]string{"tag": util.RandString(32)})))
|
||||
requestBuilder.SetTo(to)
|
||||
port := sip.Port(cascade.Port)
|
||||
sipUri := &sip.SipUri{
|
||||
FUser: sip.String{Str: cascade.Id},
|
||||
FHost: to.Uri.Host(),
|
||||
FPort: &port,
|
||||
}
|
||||
requestBuilder.SetRecipient(sipUri)
|
||||
requestBuilder.AddVia(newVia(cascade.Transport))
|
||||
requestBuilder.SetContact(newToPort(config.SipOp.Id, config.SipOp.Ip, config.SipOp.Port))
|
||||
userAgent := sip.UserAgentHeader(config.SipOp.UserAgent)
|
||||
requestBuilder.SetUserAgent(&userAgent)
|
||||
ceq, _ := storage.GetCeq("ceq")
|
||||
requestBuilder.SetSeqNo(cast.ToUint(ceq))
|
||||
callID := sip.CallID(fmt.Sprintf("%s", util.RandString(22)))
|
||||
requestBuilder.SetCallID(&callID)
|
||||
exe := sip.Expires(3600)
|
||||
requestBuilder.SetExpires(&exe)
|
||||
request, err := requestBuilder.Build()
|
||||
if err != nil {
|
||||
log.Error("发生错误:", err)
|
||||
}
|
||||
return request
|
||||
}
|
||||
|
||||
// get sip tx info by sip request and response
|
||||
func getRequestTxField(request sip.Request, response sip.Response) (callId, fromTag, toTag, viaBranch string, err error) {
|
||||
callID, ok := request.CallID()
|
||||
if !ok {
|
||||
return "", "", "", "", errors.New("获取CallId失败")
|
||||
}
|
||||
|
||||
fromHeader, ok := request.From()
|
||||
if !ok {
|
||||
return "", "", "", "", errors.New("获取fromHeader失败")
|
||||
}
|
||||
ft, ok := fromHeader.Params.Get("tag")
|
||||
if !ok {
|
||||
return "", "", "", "", errors.New("获取fromTag失败")
|
||||
}
|
||||
|
||||
toHeader, ok := response.To()
|
||||
if !ok {
|
||||
return "", "", "", "", errors.New("获取toHeader失败")
|
||||
}
|
||||
tg, okTag := toHeader.Params.Get("tag")
|
||||
if !okTag {
|
||||
return "", "", "", "", errors.New("获取toTag失败")
|
||||
}
|
||||
|
||||
viaHop, ok := request.ViaHop()
|
||||
if !ok {
|
||||
return "", "", "", "", errors.New("获取viaHop失败")
|
||||
}
|
||||
|
||||
branch, ok := viaHop.Params.Get("branch")
|
||||
if !ok {
|
||||
return "", "", "", "", errors.New("获取branch失败")
|
||||
}
|
||||
|
||||
callId = callID.Value()
|
||||
fromTag = ft.String()
|
||||
|
||||
if !okTag {
|
||||
toTag = "unkonw to tag"
|
||||
} else {
|
||||
toTag = tg.String()
|
||||
}
|
||||
viaBranch = branch.String()
|
||||
return
|
||||
}
|
||||
|
||||
// transmitRequest 发送sip请求
|
||||
func transmitRequest(req sip.Request) (sip.ClientTransaction, error) {
|
||||
transaction, err := SipServer.Request(req)
|
||||
return transaction, err
|
||||
}
|
||||
|
||||
// getResponse 获取sip的响应结果
|
||||
func getResponse(tx sip.ClientTransaction) sip.Response {
|
||||
timer := time.NewTimer(5 * time.Second)
|
||||
for {
|
||||
select {
|
||||
case resp := <-tx.Responses():
|
||||
if resp.StatusCode() == sip.StatusCode(http.StatusContinue) ||
|
||||
resp.StatusCode() == sip.StatusCode(http.StatusSwitchingProtocols) {
|
||||
continue
|
||||
}
|
||||
return resp
|
||||
case <-timer.C:
|
||||
log.Error("获取响应超时")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
func newFromAddress(params sip.Params) *sip.Address {
|
||||
portFrom := sip.Port(config.SipOp.Port)
|
||||
return &sip.Address{
|
||||
Uri: &sip.SipUri{
|
||||
FUser: sip.String{Str: config.SipOp.Id},
|
||||
FHost: config.SipOp.Realm,
|
||||
FPort: &portFrom,
|
||||
},
|
||||
Params: params,
|
||||
}
|
||||
}
|
||||
|
||||
func newParams(m map[string]string) sip.Params {
|
||||
params := sip.NewParams()
|
||||
for k, v := range m {
|
||||
params.Add(k, sip.String{Str: v})
|
||||
}
|
||||
return params
|
||||
}
|
||||
|
||||
func newVia(transport string) *sip.ViaHop {
|
||||
p := sip.Port(config.SipOp.Port)
|
||||
params := newParams(map[string]string{
|
||||
"branch": fmt.Sprintf("%s%d", "z9hG4bK", time.Now().UnixMilli()),
|
||||
})
|
||||
return &sip.ViaHop{
|
||||
ProtocolName: "SIP",
|
||||
ProtocolVersion: "2.0",
|
||||
Transport: transport,
|
||||
Host: config.SipOp.Ip,
|
||||
Port: &p,
|
||||
Params: params,
|
||||
}
|
||||
}
|
||||
|
||||
func newToPort(channelId, host string, port int) *sip.Address {
|
||||
toPort := sip.Port(port)
|
||||
return &sip.Address{
|
||||
Uri: &sip.SipUri{
|
||||
FUser: sip.String{Str: channelId},
|
||||
FHost: host,
|
||||
FPort: &toPort,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package sipServer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gb28181Panda/config"
|
||||
"gb28181Panda/model"
|
||||
sdp "github.com/panjjo/gosdp"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
PlayNowType = "Play"
|
||||
PlaybackType = "Playback"
|
||||
)
|
||||
|
||||
// createVideoSdpInfo 告知设备接受流媒体信息的相关信息 目前只有udp传输方式
|
||||
func createVideoSdpInfo(channel model.Channel, ssrc string, rtpPort int, playType, startT, endT string) string {
|
||||
mediaIp := fmt.Sprintf("%s", config.ZlmOp.Ip)
|
||||
//对应的是sdp的[o]参数
|
||||
origin := sdp.Origin{
|
||||
Username: channel.DeviceId,
|
||||
SessionID: 0,
|
||||
SessionVersion: 0,
|
||||
NetworkType: "IN",
|
||||
AddressType: "IP4",
|
||||
Address: mediaIp,
|
||||
}
|
||||
//UDP
|
||||
protocol := "RTP/AVP"
|
||||
//TCP被动或者TCP主动
|
||||
if channel.TransportType != "UDP" {
|
||||
protocol = "TCP/RTP/AVP"
|
||||
}
|
||||
video := sdp.Media{
|
||||
Description: sdp.MediaDescription{
|
||||
Type: "video",
|
||||
Port: rtpPort,
|
||||
Protocol: protocol,
|
||||
Formats: []string{"96", "97", "98", "99"},
|
||||
},
|
||||
Connection: sdp.ConnectionData{
|
||||
NetworkType: "IN",
|
||||
AddressType: "IP4",
|
||||
IP: net.ParseIP(mediaIp),
|
||||
TTL: 0,
|
||||
},
|
||||
}
|
||||
video.AddAttribute("recvonly")
|
||||
video.AddAttribute("rtpmap", "96", "PS/90000")
|
||||
video.AddAttribute("rtpmap", "98", "H264/90000")
|
||||
video.AddAttribute("rtpmap", "97", "MPEG4/90000")
|
||||
video.AddAttribute("rtpmap", "99", "H265/90000")
|
||||
//TCP主动
|
||||
if channel.TransportType != "TCPACTIVE" {
|
||||
video.AddAttribute("setup", "active")
|
||||
video.AddAttribute("connection", "new")
|
||||
}
|
||||
//TCP被动
|
||||
if channel.TransportType != "TCPPASSIVE" {
|
||||
video.AddAttribute("setup", "passive")
|
||||
video.AddAttribute("connection", "new")
|
||||
}
|
||||
timeing := []sdp.Timing{
|
||||
{
|
||||
Start: time.Time{},
|
||||
End: time.Time{},
|
||||
},
|
||||
}
|
||||
if playType == "Playback" {
|
||||
sT, _ := time.ParseInLocation("2006-01-02T15:04:05", startT, time.Local)
|
||||
eT, _ := time.ParseInLocation("2006-01-02T15:04:05", endT, time.Local)
|
||||
timeing = []sdp.Timing{sdp.Timing{Start: sT, End: eT}}
|
||||
}
|
||||
msg := sdp.Message{
|
||||
Version: 0,
|
||||
Origin: origin,
|
||||
Name: playType,
|
||||
Medias: sdp.Medias{video},
|
||||
Timing: timeing,
|
||||
SSRC: ssrc,
|
||||
}
|
||||
session := msg.Append(sdp.Session{})
|
||||
bytes := session.AppendTo([]byte{})
|
||||
return string(bytes)
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package sipServer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gb28181Panda/config"
|
||||
"gb28181Panda/log"
|
||||
"github.com/ghettovoice/gosip"
|
||||
l "github.com/ghettovoice/gosip/log"
|
||||
"github.com/ghettovoice/gosip/sip"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
SipServer gosip.Server
|
||||
serverOnce sync.Once
|
||||
)
|
||||
|
||||
func NewServer() {
|
||||
serverOnce.Do(func() {
|
||||
SipServer = gosip.NewServer(
|
||||
gosip.ServerConfig{
|
||||
UserAgent: config.SipOp.UserAgent,
|
||||
},
|
||||
nil,
|
||||
nil,
|
||||
l.NewDefaultLogrusLogger(),
|
||||
)
|
||||
})
|
||||
if err := ListenTCP(); err != nil {
|
||||
log.Error("初始化SIP-TCP服务错误", fmt.Sprintf("%s:%d", config.SipOp.Ip, config.SipOp.Port))
|
||||
return
|
||||
}
|
||||
log.Info("初始化SIP-TCP服务成功", fmt.Sprintf("%s:%d", config.SipOp.Ip, config.SipOp.Port))
|
||||
if err := ListenUDP(); err != nil {
|
||||
log.Error("初始化SIP-UDP服务错误", fmt.Sprintf("%s:%d", config.SipOp.Ip, config.SipOp.Port))
|
||||
return
|
||||
}
|
||||
log.Info("初始化SIP-UDP服务成功", fmt.Sprintf("%s:%d", config.SipOp.Ip, config.SipOp.Port))
|
||||
registerHandler()
|
||||
_recordList = &sync.Map{}
|
||||
}
|
||||
|
||||
func ListenTCP() error {
|
||||
return SipServer.Listen("tcp", fmt.Sprintf("%s:%d", config.SipOp.Ip, config.SipOp.Port), nil)
|
||||
}
|
||||
func ListenUDP() error {
|
||||
return SipServer.Listen("udp", fmt.Sprintf("%s:%d", config.SipOp.Ip, config.SipOp.Port), nil)
|
||||
}
|
||||
|
||||
func registerHandler() {
|
||||
_ = SipServer.OnRequest(sip.REGISTER, Register)
|
||||
_ = SipServer.OnRequest(sip.MESSAGE, Message)
|
||||
_ = SipServer.OnRequest(sip.NOTIFY, Notify)
|
||||
_ = SipServer.OnRequest(sip.INVITE, Invite)
|
||||
_ = SipServer.OnRequest(sip.ACK, Ack)
|
||||
_ = SipServer.OnRequest(sip.BYE, Bye)
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package sipServer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"gb28181Panda/log"
|
||||
"gb28181Panda/storage"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// SipTX sip会话事务
|
||||
type SipTX struct {
|
||||
DeviceId string `json:"deviceId,omitempty"`
|
||||
ChannelId string `json:"channelId,omitempty"`
|
||||
SSRC string `json:"SSRC,omitempty"`
|
||||
CallId string `json:"callId,omitempty"`
|
||||
FromTag string `json:"fromTag,omitempty"`
|
||||
ToTag string `json:"toTag,omitempty"`
|
||||
ViaBranch string `json:"viaBranch,omitempty"`
|
||||
}
|
||||
|
||||
// 保存sip事务信息
|
||||
func saveStreamSession(deviceId string, channelId string, ssrc string, callId string, fromTag string, toTag string, viaBranch string) {
|
||||
tx := SipTX{
|
||||
DeviceId: deviceId,
|
||||
ChannelId: channelId,
|
||||
SSRC: ssrc,
|
||||
CallId: callId,
|
||||
FromTag: fromTag,
|
||||
ToTag: toTag,
|
||||
ViaBranch: viaBranch,
|
||||
}
|
||||
|
||||
key := fmt.Sprintf("%s:%s", "Media:Stream:Transaction", ssrc)
|
||||
b, _ := json.MarshalIndent(tx, "", " ")
|
||||
storage.Set(key, b)
|
||||
}
|
||||
|
||||
func GetTx(ssrc string) (SipTX, error) {
|
||||
key := fmt.Sprintf("%s:%s", "Media:Stream:Transaction", ssrc)
|
||||
j, err := storage.Get(key)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return SipTX{}, errors.WithMessage(err, "gei sip session tx fail")
|
||||
}
|
||||
var tx SipTX
|
||||
err = json.Unmarshal([]byte(j), &tx)
|
||||
if err != nil {
|
||||
return SipTX{}, errors.WithMessage(err, "unmarshal json data to struct fail")
|
||||
}
|
||||
|
||||
return tx, nil
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package sipServer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gb28181Panda/config"
|
||||
"gb28181Panda/log"
|
||||
"gb28181Panda/model"
|
||||
"gb28181Panda/storage"
|
||||
"github.com/ghettovoice/gosip/sip"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cast"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Stop 停止播放视频
|
||||
func Stop(device model.Device, ssrc string) error {
|
||||
// 从Reds读取sip相关session用户关系监控推送视频流
|
||||
txInfo, err := GetTx(ssrc)
|
||||
if err != nil {
|
||||
return errors.New("Redis中无视频流信息")
|
||||
}
|
||||
key := fmt.Sprintf("%s:%s", "Media:Stream:Transaction", ssrc)
|
||||
_ = storage.Del(key)
|
||||
requestBuilder := sip.NewRequestBuilder()
|
||||
devicePort, _ := strconv.Atoi(device.Port)
|
||||
//to := newToPort(d.DeviceId, d.Ip, devicePort)
|
||||
to := newToPort(txInfo.ChannelId, device.Ip, devicePort)
|
||||
to.Params = newParams(map[string]string{"tag": txInfo.ToTag})
|
||||
requestBuilder.SetFrom(newFromAddress(newParams(map[string]string{"tag": txInfo.FromTag})))
|
||||
requestBuilder.SetMethod(sip.BYE)
|
||||
requestBuilder.SetTo(to)
|
||||
requestBuilder.SetRecipient(to.Uri)
|
||||
via := newVia(device.Transport)
|
||||
requestBuilder.AddVia(via)
|
||||
requestBuilder.SetContact(newToPort(config.SipOp.Id, config.SipOp.Ip, config.SipOp.Port))
|
||||
callId := sip.CallID(txInfo.CallId)
|
||||
requestBuilder.SetCallID(&callId)
|
||||
userAgent := sip.UserAgentHeader(config.SipOp.UserAgent)
|
||||
requestBuilder.SetUserAgent(&userAgent)
|
||||
ceq, _ := storage.GetCeq("ceq")
|
||||
requestBuilder.SetSeqNo(cast.ToUint(ceq))
|
||||
req, _ := requestBuilder.Build()
|
||||
transferToLog(device.Ip, device.Port)
|
||||
log.Info("发送停止播放视频请求:\n", req)
|
||||
tx, err := transmitRequest(req)
|
||||
if err != nil {
|
||||
log.Error("发送停止视频播放请求错误")
|
||||
return errors.New("发送停止视频播放请求错误")
|
||||
}
|
||||
resp := getResponse(tx)
|
||||
transferFromLog(device.Ip, device.Port)
|
||||
log.Info("收到停止播放视频响应:\n", resp)
|
||||
if resp == nil {
|
||||
log.Error("获取响应超时")
|
||||
return errors.New("获取响应超时")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package sipServer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gb28181Panda/config"
|
||||
"gb28181Panda/log"
|
||||
)
|
||||
|
||||
func transferFromLog(fromIp, fromPort string) {
|
||||
log.Info(fmt.Sprintf("[%s:%d]<<<<<<[%s:%s]", config.SipOp.Ip, config.SipOp.Port, fromIp, fromPort))
|
||||
}
|
||||
func transferToLog(toIp, toPort string) {
|
||||
log.Info(fmt.Sprintf("[%s:%d]>>>>[%s:%s]", config.SipOp.Ip, config.SipOp.Port, toIp, toPort))
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
SET NAMES utf8mb4;
|
||||
SET
|
||||
FOREIGN_KEY_CHECKS = 0;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for t_channel
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `t_channel`;
|
||||
CREATE TABLE `t_channel`
|
||||
(
|
||||
`id` int NOT NULL AUTO_INCREMENT,
|
||||
`device_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '通道Id号',
|
||||
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '通道名称',
|
||||
`manufacturer` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '当为设备时,设备厂商',
|
||||
`model` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '当为设备时,设备型号',
|
||||
`owner` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '当为设备时,设备归属',
|
||||
`civil_code` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '行政区域',
|
||||
`address` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '当为设备时,安装地址',
|
||||
`parental` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '当为设备时,是否有子设备,1有,0没有',
|
||||
`parent_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '父设备/区域/系统ID',
|
||||
`safety_way` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '信令安全模式,0不采用、2 S/MIME签名方式、3 S/MIME加密他签名同时采用方式、4 数字摘要方式',
|
||||
`register_way` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '注册方式,1 标准认证注册模式 、2 基于口令的双向认证模式、3 基于数字证书的双向认证注册模式',
|
||||
`secrecy` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '保密属性,0不涉密、1涉密',
|
||||
`status` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT 'ON' COMMENT '通道状态',
|
||||
`transport_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT 'UDP' COMMENT '流媒体传输方式 udp、tcp被动 tcppassive、tcp主动 tcpactive',
|
||||
`media_status` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT 'CLOSE' COMMENT '流媒体当前观看状态 CLOSE 无人 OPEN 有人',
|
||||
`created_at` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
|
||||
`updated_at` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
|
||||
|
||||
SET
|
||||
FOREIGN_KEY_CHECKS = 1;
|
||||
@@ -0,0 +1,31 @@
|
||||
SET NAMES utf8mb4;
|
||||
SET
|
||||
FOREIGN_KEY_CHECKS = 0;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for t_device
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `t_device`;
|
||||
CREATE TABLE `t_device`
|
||||
(
|
||||
`id` int NOT NULL AUTO_INCREMENT COMMENT 'id',
|
||||
`device_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '设备deviceId号',
|
||||
`domain` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '设备域',
|
||||
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '设备名称',
|
||||
`manufacturer` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '设备厂家',
|
||||
`model` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '设备型号',
|
||||
`firmware` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '设备固件版本',
|
||||
`transport` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '传输模式',
|
||||
`status` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT 'ON' COMMENT '上下线状态ON 上线 OFF 下线',
|
||||
`ip` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '设备ip地址',
|
||||
`port` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '设备端口号',
|
||||
`expires` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '设备有效时间',
|
||||
`created_at` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '创建时间',
|
||||
`updated_at` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '更新时间',
|
||||
`register_at` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '注册时间',
|
||||
`keepalive_at` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '保持状态时间',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 152 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
|
||||
|
||||
SET
|
||||
FOREIGN_KEY_CHECKS = 1;
|
||||
@@ -0,0 +1,24 @@
|
||||
package storage
|
||||
|
||||
type Cache interface {
|
||||
Get(key string) (string, error)
|
||||
Set(key string, value interface{})
|
||||
Del(key string) error
|
||||
GetCeq(key string) (int64, error)
|
||||
}
|
||||
|
||||
var cache Cache = newRedis()
|
||||
|
||||
func Get(key string) (string, error) {
|
||||
return cache.Get(key)
|
||||
}
|
||||
|
||||
func Set(key string, val interface{}) {
|
||||
cache.Set(key, val)
|
||||
}
|
||||
func Del(key string) error {
|
||||
return cache.Del(key)
|
||||
}
|
||||
func GetCeq(key string) (int64, error) {
|
||||
return cache.GetCeq(key)
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gb28181Panda/log"
|
||||
"github.com/spf13/viper"
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
)
|
||||
|
||||
var MysqlDb *gorm.DB
|
||||
|
||||
func init() {
|
||||
//创建一个数据库的连接
|
||||
var err error
|
||||
dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local",
|
||||
viper.GetString("mysql.username"),
|
||||
viper.GetString("mysql.password"),
|
||||
viper.GetString("mysql.host"),
|
||||
viper.GetString("mysql.port"),
|
||||
viper.GetString("mysql.name"),
|
||||
)
|
||||
//newLogger := logger.New(
|
||||
// log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer
|
||||
// logger.Config{
|
||||
// SlowThreshold: time.Second, // Slow SQL threshold
|
||||
// LogLevel: logger.Info, // Log level
|
||||
// IgnoreRecordNotFoundError: true, // Ignore ErrRecordNotFound error for logger
|
||||
// ParameterizedQueries: true, // Don't include params in the SQL log
|
||||
// Colorful: false, // Disable color
|
||||
// },
|
||||
//)
|
||||
MysqlDb, err = gorm.Open(mysql.Open(dsn), &gorm.Config{
|
||||
Logger: logger.Default.LogMode(logger.Info),
|
||||
//Logger: newLogger,
|
||||
})
|
||||
//显示sql语句
|
||||
MysqlDb.Debug()
|
||||
log.Info("连接Mysql成功", fmt.Sprintf("%s:%s", viper.GetString("mysql.host"), viper.GetString("mysql.port")))
|
||||
if err != nil {
|
||||
panic("连接数据库失败")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"gb28181Panda/config"
|
||||
"gb28181Panda/log"
|
||||
"github.com/go-redis/redis/v8"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type redisClient struct {
|
||||
rdb *redis.Client
|
||||
}
|
||||
|
||||
func newRedis() *redisClient {
|
||||
redisConfig := config.NewRedisOptions()
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: redisConfig.Addr,
|
||||
Password: redisConfig.Password,
|
||||
DB: redisConfig.DB,
|
||||
})
|
||||
if err := rdb.Ping(context.Background()).Err(); err != nil {
|
||||
panic(fmt.Errorf("connection to redis fail,addr: %s, err: %w",
|
||||
redisConfig.Addr,
|
||||
err,
|
||||
))
|
||||
}
|
||||
rdb.IncrBy(context.Background(), "ceq", 1)
|
||||
log.Info("连接Redis成功", redisConfig.Addr)
|
||||
return &redisClient{
|
||||
rdb: rdb,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *redisClient) Get(key string) (string, error) {
|
||||
result, err := r.rdb.Get(context.Background(), key).Result()
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
func (r *redisClient) Set(key string, val interface{}) {
|
||||
if err := r.rdb.Set(context.Background(), key, val, redis.KeepTTL).Err(); err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *redisClient) Del(key string) error {
|
||||
_, err := r.rdb.Del(context.Background(), key).Result()
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return errors.New(err.Error())
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *redisClient) GetCeq(key string) (int64, error) {
|
||||
return r.rdb.Incr(context.Background(), key).Result()
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
letterBytes = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
)
|
||||
|
||||
func RandString(n int) string {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
output := make([]byte, n)
|
||||
randomness := make([]byte, n)
|
||||
|
||||
_, err := rand.Read(randomness)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
l := len(letterBytes)
|
||||
|
||||
for pos := range output {
|
||||
random := randomness[pos]
|
||||
randomPos := random % uint8(l)
|
||||
output[pos] = letterBytes[randomPos]
|
||||
}
|
||||
|
||||
return string(output)
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func GinSRes(c *gin.Context, msg string, data interface{}) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 0,
|
||||
"msg": msg,
|
||||
"data": data,
|
||||
})
|
||||
}
|
||||
func GinFRes(c *gin.Context, msg string, data interface{}) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 1,
|
||||
"msg": msg,
|
||||
"data": data,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"gb28181Panda/log"
|
||||
"github.com/parnurzeal/gorequest"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
var client = gorequest.New()
|
||||
|
||||
func SendPost(url string, params map[string]interface{}) (b string, err error) {
|
||||
client.Post(url).SendMap(params).
|
||||
Timeout(3 * time.Second).
|
||||
End(func(response gorequest.Response, body string, errs []error) {
|
||||
if response.StatusCode != http.StatusOK || errs != nil {
|
||||
log.Error(errs)
|
||||
err = errs[0]
|
||||
return
|
||||
}
|
||||
b = body
|
||||
})
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"gb28181Panda/config"
|
||||
"gb28181Panda/log"
|
||||
"gb28181Panda/model"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type C struct {
|
||||
Code int `json:"code"`
|
||||
}
|
||||
|
||||
type Message struct {
|
||||
Msg string `json:"msg"`
|
||||
}
|
||||
|
||||
type CodeMessage struct {
|
||||
C
|
||||
Message
|
||||
}
|
||||
|
||||
type GetRtpInfoResp struct {
|
||||
C
|
||||
Message
|
||||
Exist bool `json:"exist,omitempty"`
|
||||
PeerIp string `json:"peer_ip,omitempty"`
|
||||
PeerPort int `json:"peer_port,omitempty"`
|
||||
LocalIp string `json:"local_ip,omitempty"`
|
||||
LocalPort int `json:"local_port,omitempty"`
|
||||
}
|
||||
|
||||
type CreateRtpServerResp struct {
|
||||
C
|
||||
Message
|
||||
Port int `json:"port"`
|
||||
}
|
||||
|
||||
type MediaInfoResp struct {
|
||||
Code int `json:"code"`
|
||||
Data []struct {
|
||||
AliveSecond int `json:"aliveSecond"`
|
||||
App string `json:"app"`
|
||||
BytesSpeed int `json:"bytesSpeed"`
|
||||
CreateStamp int `json:"createStamp"`
|
||||
IsRecordingHLS bool `json:"isRecordingHLS"`
|
||||
IsRecordingMP4 bool `json:"isRecordingMP4"`
|
||||
OriginSock struct {
|
||||
Identifier string `json:"identifier"`
|
||||
LocalIP string `json:"local_ip"`
|
||||
LocalPort int `json:"local_port"`
|
||||
PeerIP string `json:"peer_ip"`
|
||||
PeerPort int `json:"peer_port"`
|
||||
} `json:"originSock"`
|
||||
OriginType int `json:"originType"`
|
||||
OriginTypeStr string `json:"originTypeStr"`
|
||||
OriginURL string `json:"originUrl"`
|
||||
ReaderCount int `json:"readerCount"`
|
||||
Schema string `json:"schema"`
|
||||
Stream string `json:"stream"`
|
||||
TotalReaderCount int `json:"totalReaderCount"`
|
||||
Tracks []struct {
|
||||
Channels int `json:"channels,omitempty"`
|
||||
CodecID int `json:"codec_id"`
|
||||
CodecIDName string `json:"codec_id_name"`
|
||||
CodecType int `json:"codec_type"`
|
||||
Frames int `json:"frames"`
|
||||
Ready bool `json:"ready"`
|
||||
SampleBit int `json:"sample_bit,omitempty"`
|
||||
SampleRate int `json:"sample_rate,omitempty"`
|
||||
Fps float64 `json:"fps,omitempty"`
|
||||
GopIntervalMs int `json:"gop_interval_ms,omitempty"`
|
||||
GopSize int `json:"gop_size,omitempty"`
|
||||
Height int `json:"height,omitempty"`
|
||||
KeyFrames int `json:"key_frames,omitempty"`
|
||||
Width int `json:"width,omitempty"`
|
||||
} `json:"tracks"`
|
||||
Vhost string `json:"vhost"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
// 参考https://github.com/ZLMediaKit/ZLMediaKit/wiki/MediaServer%E6%94%AF%E6%8C%81%E7%9A%84HTTP-API#24indexapiopenrtpserver
|
||||
func ZlmOpenRtpServer(stream string, channel model.Channel) (rtpPort int, err error) {
|
||||
tcpMode := 0
|
||||
//0 udp 模式,1 tcp 被动模式, 2 tcp 主动模式。
|
||||
if channel.TransportType == "TCPACTIVE" {
|
||||
tcpMode = 2
|
||||
} else if channel.TransportType == "TCPPASSIVE" {
|
||||
tcpMode = 1
|
||||
}
|
||||
url := fmt.Sprintf("http://%s:%d/index/api/openRtpServer?port=0&tcp_mode=%d&stream_id=%s&secret=%s", config.ZlmOp.Ip, config.ZlmOp.HttpPort, tcpMode, stream, config.ZlmOp.Secret)
|
||||
params := map[string]interface{}{}
|
||||
body, err := SendPost(url, params)
|
||||
if err != nil {
|
||||
return 0, errors.WithMessage(err, "create rtp server fail")
|
||||
}
|
||||
|
||||
resp := CreateRtpServerResp{}
|
||||
err = json.Unmarshal([]byte(body), &resp)
|
||||
if resp.Code == -300 {
|
||||
ZlmCloseRtpServer(stream)
|
||||
return ZlmOpenRtpServer(stream, channel)
|
||||
}
|
||||
if err != nil {
|
||||
return 0, errors.WithMessage(err, "unmarshal data to struct fail")
|
||||
}
|
||||
|
||||
if resp.Code != 0 {
|
||||
return 0, errors.New(resp.Msg)
|
||||
}
|
||||
|
||||
rtpPort = resp.Port
|
||||
return
|
||||
}
|
||||
|
||||
// ZlmCloseRtpServer 关闭zlm中的stream数据
|
||||
func ZlmCloseRtpServer(stream string) {
|
||||
url := fmt.Sprintf("http://%s:%d/index/api/closeRtpServer?stream_id=%s&secret=%s", config.ZlmOp.Ip, config.ZlmOp.HttpPort, stream, config.ZlmOp.Secret)
|
||||
params := map[string]interface{}{}
|
||||
body, err := SendPost(url, params)
|
||||
if err != nil {
|
||||
log.Error(err, "close rtp server fail")
|
||||
}
|
||||
|
||||
resp := CreateRtpServerResp{}
|
||||
err = json.Unmarshal([]byte(body), &resp)
|
||||
if err != nil {
|
||||
log.Error(err, "unmarshal data to struct fail")
|
||||
}
|
||||
|
||||
if resp.Code != 0 {
|
||||
log.Error("ZLM内部错误")
|
||||
}
|
||||
}
|
||||
|
||||
func GetMediaList(stream string) (status bool, err error) {
|
||||
url := fmt.Sprintf("http://%s:%d/index/api/getMediaList?stream=%s&secret=%s&app=rtp", config.ZlmOp.Ip, config.ZlmOp.HttpPort, stream, config.ZlmOp.Secret)
|
||||
params := map[string]interface{}{}
|
||||
body, err := SendPost(url, params)
|
||||
if err != nil {
|
||||
return false, errors.WithMessage(err, "getMediaList fail")
|
||||
}
|
||||
|
||||
resp := MediaInfoResp{}
|
||||
err = json.Unmarshal([]byte(body), &resp)
|
||||
|
||||
if err != nil {
|
||||
log.Info("ZLM接口出错了")
|
||||
return false, errors.WithMessage(err, "unmarshal data to struct fail")
|
||||
}
|
||||
|
||||
if resp.Code != 0 {
|
||||
log.Info("ZLM接口出错了")
|
||||
return false, errors.New("ZLM接口出错了")
|
||||
}
|
||||
|
||||
if len(resp.Data) == 0 {
|
||||
log.Info("不存在")
|
||||
return false, errors.New("不存在")
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"gb28181Panda/model"
|
||||
"github.com/ghettovoice/gosip/sip"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func DeviceFromRequest(req sip.Request) (model.Device, bool) {
|
||||
d := model.Device{}
|
||||
|
||||
from, ok := req.From()
|
||||
if !ok {
|
||||
logrus.Debugln("从请求中无法解析from头部信息", req.String())
|
||||
return d, false
|
||||
}
|
||||
if from.Address == nil {
|
||||
logrus.Debugln("从请求中无法解析from头address部分信息", req.String())
|
||||
return d, false
|
||||
}
|
||||
if from.Address.User() == nil {
|
||||
logrus.Debugln("从请求中无法解析from头user部分信息", req.String())
|
||||
return d, false
|
||||
}
|
||||
|
||||
d.DeviceId = from.Address.User().String()
|
||||
d.Domain = from.Address.Host()
|
||||
via, ok := req.ViaHop()
|
||||
if !ok {
|
||||
logrus.Debugln("从请求中无法解析出via头部信息", via.String())
|
||||
return d, false
|
||||
}
|
||||
d.Transport = via.Transport
|
||||
logrus.Debugf("从请求中解析出的设备信息: %v\n", d)
|
||||
return d, true
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"golang.org/x/net/html/charset"
|
||||
"golang.org/x/text/encoding/simplifiedchinese"
|
||||
"golang.org/x/text/transform"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
func GbkToUtf8(s []byte) ([]byte, error) {
|
||||
reader := transform.NewReader(bytes.NewReader(s), simplifiedchinese.GBK.NewDecoder())
|
||||
d, e := ioutil.ReadAll(reader)
|
||||
if e != nil {
|
||||
return s, e
|
||||
}
|
||||
return d, nil
|
||||
}
|
||||
|
||||
func DecodeGbk(v interface{}, body []byte) error {
|
||||
bodyBytes, err := GbkToUtf8(body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
decoder := xml.NewDecoder(bytes.NewReader(bodyBytes))
|
||||
decoder.CharsetReader = charset.NewReaderLabel
|
||||
err = decoder.Decode(v)
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package util
|
||||
|
||||
import "time"
|
||||
|
||||
const TIME_LAYOUT = "2006-01-02T15:04:05"
|
||||
const TIME_LAYOUT_With_SPACE = "2006-01-02 15:04:05"
|
||||
|
||||
func GetCurrenTimeNowFormat() string {
|
||||
return time.Now().Format(TIME_LAYOUT)
|
||||
}
|
||||
func GetCurrenTimeNow() string {
|
||||
return time.Now().Format(TIME_LAYOUT_With_SPACE)
|
||||
}
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/net/html/charset"
|
||||
)
|
||||
|
||||
// Error Error
|
||||
type Error struct {
|
||||
err error
|
||||
params []interface{}
|
||||
}
|
||||
|
||||
func (err *Error) Error() string {
|
||||
if err == nil {
|
||||
return "<nil>"
|
||||
}
|
||||
str := fmt.Sprint(err.params...)
|
||||
if err.err != nil {
|
||||
str += fmt.Sprintf(" err:%s", err.err.Error())
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
// NewError NewError
|
||||
func NewError(err error, params ...interface{}) error {
|
||||
return &Error{err, params}
|
||||
}
|
||||
|
||||
// JSONEncode JSONEncode
|
||||
func JSONEncode(data interface{}) []byte {
|
||||
d, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
logrus.Errorln("JSONEncode error:", err)
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
func timeoutClient() *http.Client {
|
||||
connectTimeout := time.Duration(20 * time.Second)
|
||||
readWriteTimeout := time.Duration(30 * time.Second)
|
||||
return &http.Client{
|
||||
Transport: &http.Transport{
|
||||
DialContext: timeoutDialer(connectTimeout, readWriteTimeout),
|
||||
MaxIdleConnsPerHost: 200,
|
||||
DisableKeepAlives: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
func timeoutDialer(cTimeout time.Duration,
|
||||
rwTimeout time.Duration) func(ctx context.Context, net, addr string) (c net.Conn, err error) {
|
||||
return func(ctx context.Context, netw, addr string) (net.Conn, error) {
|
||||
conn, err := net.DialTimeout(netw, addr, cTimeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conn.SetDeadline(time.Now().Add(rwTimeout))
|
||||
return conn, nil
|
||||
}
|
||||
}
|
||||
|
||||
// PostRequest PostRequest
|
||||
func PostRequest(url string, bodyType string, body io.Reader) ([]byte, error) {
|
||||
client := timeoutClient()
|
||||
resp, err := client.Post(url, bodyType, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
respbody, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return respbody, nil
|
||||
}
|
||||
|
||||
// PostJSONRequest PostJSONRequest
|
||||
func PostJSONRequest(url string, data interface{}) ([]byte, error) {
|
||||
bytesData, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return PostRequest(url, "application/json;charset=UTF-8", bytes.NewReader(bytesData))
|
||||
}
|
||||
|
||||
// GetRequest GetRequest
|
||||
func GetRequest(url string) ([]byte, error) {
|
||||
client := timeoutClient()
|
||||
resp, err := client.Get(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
respbody, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return respbody, nil
|
||||
}
|
||||
|
||||
// XMLDecode XMLDecode
|
||||
func XMLDecode(data []byte, v interface{}) error {
|
||||
decoder := xml.NewDecoder(bytes.NewReader(data))
|
||||
decoder.CharsetReader = charset.NewReaderLabel
|
||||
return decoder.Decode(v)
|
||||
}
|
||||
+191
@@ -0,0 +1,191 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"gb28181Panda/log"
|
||||
"github.com/beevik/etree"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cast"
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
type QueryType string
|
||||
type ControlType string
|
||||
|
||||
type WithKeyValue func(element *etree.Element)
|
||||
|
||||
const (
|
||||
DeviceConfig ControlType = "DeviceConfig"
|
||||
DeviceControl ControlType = "DeviceControl"
|
||||
)
|
||||
|
||||
const (
|
||||
DeviceStatusCmdType QueryType = "DeviceStatus"
|
||||
CatalogCmdType QueryType = "Catalog"
|
||||
DeviceInfoCmdType QueryType = "DeviceInfo"
|
||||
RecordInfoCmdType QueryType = "RecordInfo"
|
||||
AlarmCmdType QueryType = "Alarm"
|
||||
ConfigDownloadCmdType QueryType = "ConfigDownload"
|
||||
PresetQueryCmdType QueryType = "PresetQuery"
|
||||
MobilePositionCmdType QueryType = "MobilePosition"
|
||||
KeepaliveCmdType QueryType = "Keepalive"
|
||||
)
|
||||
|
||||
// CreateQueryXML 主动查询设备相关信息xml文件
|
||||
func CreateQueryXML(cmd QueryType, deviceId string, kvs ...WithKeyValue) (string, error) {
|
||||
document := etree.NewDocument()
|
||||
document.CreateProcInst("xml", "version=\"1.0\" encoding=\"UTF-8\"")
|
||||
query := document.CreateElement("Query")
|
||||
query.CreateElement("CmdType").CreateText(string(cmd))
|
||||
query.CreateElement("SN").CreateText(getSN())
|
||||
query.CreateElement("DeviceID").CreateText(deviceId)
|
||||
|
||||
for _, kv := range kvs {
|
||||
kv(query)
|
||||
}
|
||||
|
||||
document.Indent(2)
|
||||
body, err := document.WriteToString()
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return "", errors.Wrap(err, "encoding catalog query request xml fail")
|
||||
}
|
||||
return body, nil
|
||||
}
|
||||
|
||||
// CreateIFameXML 主动查询设备相关信息xml文件
|
||||
func CreateIFameXML(deviceId string, kvs ...WithKeyValue) (string, error) {
|
||||
document := etree.NewDocument()
|
||||
document.CreateProcInst("xml", "version=\"1.0\" encoding=\"UTF-8\"")
|
||||
query := document.CreateElement("Control")
|
||||
query.CreateElement("CmdType").CreateText("DeviceControl")
|
||||
query.CreateElement("SN").CreateText(getSN())
|
||||
query.CreateElement("DeviceID").CreateText(deviceId)
|
||||
query.CreateElement("IFameCmd").CreateText("send")
|
||||
info := query.CreateElement("Info")
|
||||
info.CreateElement("IFameCmd").CreateText("5")
|
||||
for _, kv := range kvs {
|
||||
kv(query)
|
||||
}
|
||||
|
||||
document.Indent(4)
|
||||
body, err := document.WriteToString()
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return "", errors.Wrap(err, "encoding catalog query request xml fail")
|
||||
}
|
||||
return body, nil
|
||||
}
|
||||
|
||||
// CreateGuardXML 布防xml文件
|
||||
func CreateGuardXML(cmd ControlType, deviceId string, kvs ...WithKeyValue) (string, error) {
|
||||
document := etree.NewDocument()
|
||||
document.CreateProcInst("xml", "version=\"1.0\" encoding=\"UTF-8\"")
|
||||
query := document.CreateElement("Control")
|
||||
query.CreateElement("CmdType").CreateText(string(cmd))
|
||||
query.CreateElement("SN").CreateText(getSN())
|
||||
query.CreateElement("DeviceID").CreateText(deviceId)
|
||||
query.CreateElement("GuardCmd").CreateText("SetGuard")
|
||||
|
||||
for _, kv := range kvs {
|
||||
kv(query)
|
||||
}
|
||||
|
||||
document.Indent(4)
|
||||
body, err := document.WriteToString()
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return "", errors.Wrap(err, "encoding catalog query request xml fail")
|
||||
}
|
||||
return body, nil
|
||||
}
|
||||
|
||||
// CreateSubscribeAlarmXML 订阅预警信息xml文件
|
||||
func CreateSubscribeAlarmXML(cmd QueryType, deviceId string, kvs ...WithKeyValue) (string, error) {
|
||||
document := etree.NewDocument()
|
||||
document.CreateProcInst("xml", "version=\"1.0\" encoding=\"UTF-8\"")
|
||||
query := document.CreateElement("Query")
|
||||
query.CreateElement("CmdType").CreateText(string(cmd))
|
||||
query.CreateElement("SN").CreateText(getSN())
|
||||
query.CreateElement("DeviceID").CreateText(deviceId)
|
||||
query.CreateElement("StartAlarmPriority").CreateText("0")
|
||||
query.CreateElement("EndAlarmPriority").CreateText("0")
|
||||
query.CreateElement("AlarmMethod").CreateText("0")
|
||||
query.CreateElement("StartTime").CreateText("2023-04-25T00:00:00")
|
||||
query.CreateElement("EndTime").CreateText("2023-04-27T23:59:59")
|
||||
|
||||
for _, kv := range kvs {
|
||||
kv(query)
|
||||
}
|
||||
|
||||
document.Indent(4)
|
||||
body, err := document.WriteToString()
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return "", errors.Wrap(err, "encoding catalog query request xml fail")
|
||||
}
|
||||
return body, nil
|
||||
}
|
||||
|
||||
// CreateControlXml Ptz控制指令
|
||||
func CreateControlXml(cmd ControlType, deviceId string, kvs ...WithKeyValue) (string, error) {
|
||||
document := etree.NewDocument()
|
||||
document.CreateProcInst("xml", "version=\"1.0\" encoding=\"UTF-8\"")
|
||||
query := document.CreateElement("ControlPTZ")
|
||||
query.CreateElement("CmdType").CreateText(string(cmd))
|
||||
query.CreateElement("SN").CreateText(getSN())
|
||||
query.CreateElement("DeviceID").CreateText(deviceId)
|
||||
|
||||
for _, kv := range kvs {
|
||||
kv(query)
|
||||
}
|
||||
|
||||
document.Indent(2)
|
||||
body, err := document.WriteToString()
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return "", errors.Wrap(err, "encoding device control request xml fail")
|
||||
}
|
||||
return body, nil
|
||||
|
||||
}
|
||||
|
||||
// CreateRecordInfoXml 查询录像数据
|
||||
func CreateRecordInfoXml(cmd QueryType, channelId string, startTime string, endTime string, kvs ...WithKeyValue) (string, string, error) {
|
||||
sn := getSN()
|
||||
document := etree.NewDocument()
|
||||
document.CreateProcInst("xml", "version=\"1.0\" encoding=\"UTF-8\"")
|
||||
query := document.CreateElement("Query")
|
||||
query.CreateElement("CmdType").CreateText(string(cmd))
|
||||
query.CreateElement("SN").CreateText(sn)
|
||||
query.CreateElement("DeviceID").CreateText(channelId)
|
||||
query.CreateElement("StartTime").CreateText(startTime)
|
||||
query.CreateElement("EndTime").CreateText(endTime)
|
||||
query.CreateElement("Secrecy").CreateText("0")
|
||||
query.CreateElement("Type").CreateText("all")
|
||||
|
||||
for _, kv := range kvs {
|
||||
kv(query)
|
||||
}
|
||||
|
||||
document.Indent(2)
|
||||
body, err := document.WriteToString()
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return "", "", errors.Wrap(err, "encoding device control request xml fail")
|
||||
}
|
||||
return body, sn, nil
|
||||
|
||||
}
|
||||
|
||||
// WithPTZCmd create 'PTZCmd' item of xml by value
|
||||
func WithPTZCmd(ptz string) WithKeyValue {
|
||||
return func(element *etree.Element) {
|
||||
element.CreateElement("PTZCmd").CreateText(ptz)
|
||||
}
|
||||
}
|
||||
|
||||
func getSN() string {
|
||||
rand.New(rand.NewSource(time.Now().UnixMilli()))
|
||||
return cast.ToString(rand.Intn(10) * 9876)
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
## UDP
|
||||
|
||||
这个是普遍的传输方式GB28181服务端在发invite时,在携带的SDP中包含了接收媒体的端口,设备端(被呼叫端)收到invite后,解析该端口,通过UDP
|
||||
将媒体流发向该端口。
|
||||
|
||||
## TCP被动
|
||||
|
||||
GB28181服务端在发invite时,在携带的SDP中包含了接收媒体的端口,并监听该端口的媒体数据,设备端(被呼叫端)收到invite后,解析该端口,通过TCP将媒体流发向该端口。
|
||||
|
||||
## TCP主动
|
||||
|
||||
设备端(被呼叫端)告知服务端自己的媒体流tcp端口,服务端主动去连接设备端(被呼叫端)的该端口,获取数据。
|
||||
这个很少用,因为设备端可能在内网环境,流媒体无法访问这个设备
|
||||
此项目中忽略
|
||||
Reference in New Issue
Block a user