修改端口范围,支持自动心跳检测

This commit is contained in:
xugo
2025-03-28 20:02:27 +08:00
parent 7571f685d4
commit f8cc68a196
14 changed files with 397 additions and 182 deletions
+3 -2
View File
@@ -130,8 +130,8 @@ services:
- 10000:10000/udp
- 8000:8000/udp
- 9000:9000/udp
- 20050-20100:20050-20100
- 20050-20100:20050-20100/udp
- 20000-20300:20000-20300
- 20000-20300:20000-20300/udp
volumes:
- ./configs:/opt/media/conf
@@ -214,6 +214,7 @@ services:
- [x] 按需拉流,节省流量
- [x] 视频支持播放 H264 和 H265
- [x] 音频支持 g711a/g711u/aac
- [x] 与设备侧超时同步,例如设备侧填写超时 3 秒,次数 3 次,则 9+x 秒左右收不到心跳认为离线,x 是检测间隔周期
- [ ] 设备云台控制
- [ ] 录像回放
- [ ] 报警事件订阅
+15 -15
View File
@@ -4,11 +4,11 @@ go 1.24
require (
github.com/DATA-DOG/go-sqlmock v1.5.2
github.com/gin-contrib/gzip v1.2.0
github.com/gin-contrib/gzip v1.2.2
github.com/gin-gonic/gin v1.10.0
github.com/glebarez/sqlite v1.11.0
github.com/google/wire v0.6.0
github.com/ixugo/goweb v1.0.19
github.com/ixugo/goweb v1.0.21
github.com/jinzhu/copier v0.4.0
github.com/pelletier/go-toml/v2 v2.2.3
github.com/shirou/gopsutil v3.21.11+incompatible
@@ -17,9 +17,9 @@ require (
)
require (
github.com/bytedance/sonic v1.12.7 // indirect
github.com/bytedance/sonic/loader v0.2.2 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/bytedance/sonic v1.13.2 // indirect
github.com/bytedance/sonic/loader v0.2.4 // indirect
github.com/cloudwego/base64x v0.1.5 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/gin-contrib/sse v1.0.0 // indirect
@@ -27,8 +27,8 @@ require (
github.com/go-ole/go-ole v1.2.6 // 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.24.0 // indirect
github.com/goccy/go-json v0.10.4 // indirect
github.com/go-playground/validator/v10 v10.25.0 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/gofrs/uuid v4.4.0+incompatible
github.com/golang-jwt/jwt/v4 v4.5.1 // indirect
github.com/google/uuid v1.6.0 // indirect
@@ -39,7 +39,7 @@ require (
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.2.9 // indirect
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible // indirect
github.com/lestrrat-go/strftime v1.1.0 // indirect
@@ -58,15 +58,15 @@ require (
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
go.uber.org/zap/exp v0.3.0 // indirect
golang.org/x/arch v0.13.0 // indirect
golang.org/x/crypto v0.32.0 // indirect
golang.org/x/arch v0.15.0 // indirect
golang.org/x/crypto v0.36.0 // indirect
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 // indirect
golang.org/x/net v0.34.0 // indirect
golang.org/x/sync v0.10.0 // indirect
golang.org/x/sys v0.29.0 // indirect
golang.org/x/text v0.21.0
golang.org/x/net v0.38.0 // indirect
golang.org/x/sync v0.12.0 // indirect
golang.org/x/sys v0.31.0 // indirect
golang.org/x/text v0.23.0
golang.org/x/time v0.8.0 // indirect
google.golang.org/protobuf v1.36.2 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
modernc.org/libc v1.61.5 // indirect
modernc.org/mathutil v1.7.1 // indirect
+30
View File
@@ -2,11 +2,17 @@ github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7Oputl
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
github.com/bytedance/sonic v1.12.7 h1:CQU8pxOy9HToxhndH0Kx/S1qU/CuS9GnKYrGioDcU1Q=
github.com/bytedance/sonic v1.12.7/go.mod h1:tnbal4mxOMju17EGfknm2XyYcpyCnIROYOEYuemj13I=
github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ=
github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/bytedance/sonic/loader v0.2.2 h1:jxAJuN9fOot/cyz5Q6dUuMJF5OqQ6+5GfA8FjjQ0R4o=
github.com/bytedance/sonic/loader v0.2.2/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
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=
@@ -17,6 +23,8 @@ github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3G
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
github.com/gin-contrib/gzip v1.2.0 h1:JzN6DT3/xYL5zAdviN1ORNzKeklrwafXCIDKIR+qmUA=
github.com/gin-contrib/gzip v1.2.0/go.mod h1:C1a5cacjlDsS20cKnHlZRCPUu57D3qH6B2pV0rl+Y/s=
github.com/gin-contrib/gzip v1.2.2 h1:iUU/EYCM8ENfkjmZaVrxbjF/ZC267Iqv5S0MMCMEliI=
github.com/gin-contrib/gzip v1.2.2/go.mod h1:C1a5cacjlDsS20cKnHlZRCPUu57D3qH6B2pV0rl+Y/s=
github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E=
github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
@@ -35,8 +43,12 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.24.0 h1:KHQckvo8G6hlWnrPX4NJJ+aBfWNAE/HH+qdL2cBpCmg=
github.com/go-playground/validator/v10 v10.24.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus=
github.com/go-playground/validator/v10 v10.25.0 h1:5Dh7cjvzR7BRZadnsVOzPhWsrwUr0nmsZJxEAnFLNO8=
github.com/go-playground/validator/v10 v10.25.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus=
github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM=
github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo=
@@ -54,6 +66,8 @@ github.com/google/wire v0.6.0 h1:HBkoIh4BdSxoyo9PveV8giw7ZsaBOvzWKfcg/6MrVwI=
github.com/google/wire v0.6.0/go.mod h1:F4QhpQ9EDIdJ1Mbop/NZBRB+5yrR6qg3BnctaoUk6NA=
github.com/ixugo/goweb v1.0.19 h1:q5pIAANBNxZdCSZfRamekqJByt1U3BCHXA0mcCFiioE=
github.com/ixugo/goweb v1.0.19/go.mod h1:iBwaaazAtvEuuODjnoCR/5bssvTi49Eft6x8ULg/jsg=
github.com/ixugo/goweb v1.0.21 h1:Bi437yjO+6hJupm+rRXI0u344LIwDDXUCoSNX+oG8Mg=
github.com/ixugo/goweb v1.0.21/go.mod h1:6bUJpmDB6SGMdLyYF5EouHCmHKVYA8Od675JX6twaA4=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
@@ -76,6 +90,8 @@ github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCy
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
@@ -145,12 +161,16 @@ go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U=
go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ=
golang.org/x/arch v0.13.0 h1:KCkqVVV1kGg0X87TFysjCJ8MxtZEIU4Ja/yXGeoECdA=
golang.org/x/arch v0.13.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/arch v0.15.0 h1:QtOrQd0bTUnhNVNndMpLHNWrDmYzZ2KDqSrEymqInZw=
golang.org/x/arch v0.15.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 h1:1UoZQm6f0P/ZO0w1Ri+f+ifG/gXhegadRdwBIXEFWDo=
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
@@ -168,6 +188,8 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -175,6 +197,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -188,6 +212,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
@@ -203,6 +229,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -216,6 +244,8 @@ golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.36.2 h1:R8FeyR1/eLmkutZOM5CWghmo5itiG9z0ktFlTVLuTmU=
google.golang.org/protobuf v1.36.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
@@ -56,6 +56,11 @@ func (c *Cache) LoadDeviceToMemory(conn sip.Connection) {
dev := gbs.NewDevice(conn, d)
if dev != nil {
if err := dev.CheckConnection(); err != nil {
slog.Warn("检查设备连接失败", "err", err, "device_id", d.DeviceID, "to", dev.To())
continue
}
slog.Debug("load device to memory", "device_id", d.DeviceID, "to", dev.To())
channels := make([]*gb28181.Channel, 0, 8)
_, err := c.Storer.Channel().Find(context.TODO(), &channels, web.NewPagerFilterMaxSize(), orm.Where("device_id=?", d.DeviceID))
+1 -1
View File
@@ -126,7 +126,7 @@ func (n *NodeManager) connection(server *MediaServer, serverPort int) {
log.Info("ZLM 服务节点连接中")
for i := range 10 {
for i := range 5 {
resp, err := engine.GetServerConfig()
if err != nil {
log.Error("ZLM 服务节点连接失败", "err", err, "retry", i)
+138
View File
@@ -0,0 +1,138 @@
package gbs
import (
"encoding/hex"
"encoding/xml"
"log/slog"
"math"
"github.com/gowvp/gb28181/pkg/gbs/sip"
)
// 配置参数类型常量定义
const (
// basicParam 基本参数配置
basicParam = "BasicParam"
// videoParamOpt 视频参数范围配置
// videoParamOpt = "VideoParamOpt"
// // SVACEncodeConfig SVAC编码配置
// SVACEncodeConfig = "SVACEncodeConfig"
// // SVACDecodeConfig SVAC解码配置
// SVACDecodeConfig = "SVACDecodeConfig"
// // videoParamAttribute 视频参数属性配置
// videoParamAttribute = "VideoParamAttribute"
// // videoRecordPlan 录像计划
// videoRecordPlan = "VideoRecordPlan"
// // videoAlarmRecord 报警录像
// videoAlarmRecord = "VideoAlarmRecord"
// // pictureMask 视频画面遮挡
// pictureMask = "PictureMask"
// // frameMirror 画面翻转
// frameMirror = "FrameMirror"
// // AlarmReport 报警上报开关
// AlarmReport = "AlarmReport"
// // OSDConfig 前端OSD设置
// OSDConfig = "OSDConfig"
// // SnapShotConfig 图像抓拍配置
// SnapShotConfig = "SnapShotConfig"
)
type ConfigDownloadRequest struct {
XMLName xml.Name `xml:"Query"`
CmdType string `xml:"CmdType"` // 命令类型:设备配置查询(必选)
SN int32 `xml:"SN"` // 命令序列号(必选)
DeviceID string `xml:"DeviceID"` // 目标设备编码(必选)
ConfigType string `xml:"ConfigType"` // 查询配置参数类型(必选)
}
type ConfigDownloadResponse struct {
XMLName xml.Name `xml:"Response"`
CmdType string `xml:"CmdType"`
SN int `xml:"SN"`
DeviceID string `xml:"DeviceID"`
Result string `xml:"Result"`
BasicParam *BasicParam `xml:"BasicParam"`
// VideoParamOpt *VideoParamOpt `xml:"VideoParamOpt"`
// SVACEncodeConfig *SVACEncodeConfig `xml:"SVACEncodeConfig"`
// SVACDecodeConfig *SVACDecodeConfig `xml:"SVACDecodeConfig"`
// VideoParamAttribute *VideoParamAttribute `xml:"VideoParamAttribute"`
// VideoRecordPlan *VideoRecordPlan `xml:"VideoRecordPlan"`
// VideoAlarmRecord *VideoAlarmRecord `xml:"VideoAlarmRecord"`
// PictureMask *PictureMask `xml:"PictureMask"`
// FrameMirror *FrameMirror `xml:"FrameMirror"`
// AlarmReport *AlarmReport `xml:"AlarmReport"`
// OSDConfig *OSDConfig `xml:"OSDConfig"`
// SnapShot *SnapShot `xml:"SnapShot"`
}
// BasicParam 设备基本参数配置
type BasicParam struct {
Name string `xml:"Name"` // 设备名称
Expiration int `xml:"Expiration"` // 注册过期时间
HeartBeatInterval int `xml:"HeartBeatInterval"` // 心跳间隔时间
HeartBeatCount int `xml:"HeartBeatCount"` // 心跳超时次数
}
func NewConfigDownloadRequest(sn int32, deviceID string, configType string) []byte {
c := ConfigDownloadRequest{
CmdType: "ConfigDownload",
SN: sn,
DeviceID: deviceID,
ConfigType: configType,
}
xmlData, _ := sip.XMLEncode(c)
return xmlData
}
func (g *GB28181API) QueryConfigDownloadBasic(deviceID, configType string) error {
slog.Debug("QueryConfigDownloadBasic", "deviceID", deviceID)
ipc, ok := g.svr.memoryStorer.Load(deviceID)
if !ok {
return ErrDeviceOffline
}
tx, err := g.svr.wrapRequest(ipc, sip.MethodMessage, &sip.ContentTypeXML, NewConfigDownloadRequest(1, deviceID, configType))
if err != nil {
return err
}
_, err = sipResponse(tx)
return err
}
func (g *GB28181API) sipMessageConfigDownload(ctx *sip.Context) {
slog.Debug("sipMessageConfigDownload", "deviceID", ctx.DeviceID)
var msg ConfigDownloadResponse
if err := sip.XMLDecode(ctx.Request.Body(), &msg); err != nil {
ctx.Log.Error("sipMessageConfigDownload", "err", err, "body", hex.EncodeToString(ctx.Request.Body()))
ctx.String(400, ErrXMLDecode.Error())
return
}
if msg.BasicParam != nil {
ipc, ok := g.svr.memoryStorer.Load(ctx.DeviceID)
if !ok {
ctx.Log.Debug("sipMessageConfigDownload", "deviceID", ctx.DeviceID, "err", "device offline")
return
}
// 确保 HeartBeatCount 在合法范围内
if msg.BasicParam.HeartBeatCount > math.MaxUint16 {
msg.BasicParam.HeartBeatCount = math.MaxUint16
}
if msg.BasicParam.HeartBeatInterval > math.MaxUint16 {
msg.BasicParam.HeartBeatInterval = math.MaxUint16
}
if msg.BasicParam.HeartBeatCount <= 0 {
msg.BasicParam.HeartBeatCount = 1
}
// 计算设备离线超时时间
if msg.BasicParam.HeartBeatInterval*msg.BasicParam.HeartBeatCount > 0 {
ipc.keepaliveInterval = uint16(msg.BasicParam.HeartBeatInterval) // nolint
ipc.keepaliveTimeout = uint16(msg.BasicParam.HeartBeatCount) // nolint
ctx.Log.Debug("sipMessageConfigDownload update", "deviceID", ctx.DeviceID, "keepaliveInterval", ipc.keepaliveInterval, "keepaliveTimeout", ipc.keepaliveTimeout)
}
}
ctx.String(200, "OK")
}
+19
View File
@@ -38,6 +38,9 @@ type Device struct {
LastKeepaliveAt time.Time
LastRegisterAt time.Time
Expires int
keepaliveInterval uint16
keepaliveTimeout uint16
}
func NewDevice(conn sip.Connection, d *gb28181.Device) *Device {
@@ -70,6 +73,22 @@ func NewDevice(conn sip.Connection, d *gb28181.Device) *Device {
return &c
}
// CheckConnection 检查 udp 设备能否通信
func (d *Device) CheckConnection() error {
const timeout = 2 * time.Second
if d.source.Network() == "tcp" {
return nil
}
// 创建临时UDP连接进行检查
tempConn, err := net.DialTimeout("udp", d.source.String(), timeout)
if err != nil {
return fmt.Errorf("UDP连接失败: %w", err)
}
defer tempConn.Close()
return nil
}
func (d *Device) LoadChannels(channels ...*gb28181.Channel) {
for _, channel := range channels {
ch := Channel{
+3 -1
View File
@@ -1,6 +1,8 @@
package gbs
import (
"encoding/hex"
"github.com/gowvp/gb28181/internal/core/gb28181"
"github.com/gowvp/gb28181/pkg/gbs/sip"
)
@@ -36,7 +38,7 @@ type MessageDeviceInfoResponse struct {
func (g GB28181API) sipMessageDeviceInfo(ctx *sip.Context) {
var msg MessageDeviceInfoResponse
if err := sip.XMLDecode(ctx.Request.Body(), &msg); err != nil {
ctx.Log.Error("sipMessageDeviceInfo", "err", err)
ctx.Log.Error("sipMessageDeviceInfo", "err", err, "body", hex.EncodeToString(ctx.Request.Body()))
ctx.String(400, ErrXMLDecode.Error())
return
}
+145 -147
View File
@@ -1,11 +1,9 @@
package gbs
import (
"errors"
"fmt"
"net"
"sync"
"time"
"github.com/gowvp/gb28181/internal/core/gb28181"
"github.com/gowvp/gb28181/internal/core/sms"
@@ -202,167 +200,167 @@ func (g *GB28181API) sipPlayPush2(ch *Channel, in *PlayInput, port int, stream *
}
// sip 请求播放
func SipPlay(data *Streams) (*Streams, error) {
channel := Channels{ChannelID: data.ChannelID}
// if err := db.Get(db.DBClient, &channel); err != nil {
// if db.RecordNotFound(err) {
// return nil, errors.New("通道不存在")
// }
// return nil, err
// }
// func SipPlay(data *Streams) (*Streams, error) {
// channel := Channels{ChannelID: data.ChannelID}
// // if err := db.Get(db.DBClient, &channel); err != nil {
// // if db.RecordNotFound(err) {
// // return nil, errors.New("通道不存在")
// // }
// // return nil, err
// // }
data.DeviceID = channel.DeviceID
data.StreamType = channel.StreamType
// 使用通道的播放模式进行处理
switch channel.StreamType {
case m.StreamTypePull:
// 拉流
// data.DeviceID = channel.DeviceID
// data.StreamType = channel.StreamType
// // 使用通道的播放模式进行处理
// switch channel.StreamType {
// case m.StreamTypePull:
// // 拉流
default:
// 推流模式要求设备在线且活跃
if time.Now().Unix()-channel.Active > 30*60 || channel.Status != m.DeviceStatusON {
return nil, errors.New("通道已离线")
}
user, ok := _activeDevices.Get(channel.DeviceID)
if !ok {
return nil, errors.New("设备已离线")
}
// GB28181推流
if data.StreamID == "" {
ssrcLock.Lock()
// data.ssrc =g. getSSRC(data.T)
data.StreamID = ssrc2stream(data.ssrc)
// default:
// // 推流模式要求设备在线且活跃
// if time.Now().Unix()-channel.Active > 30*60 || channel.Status != m.DeviceStatusON {
// return nil, errors.New("通道已离线")
// }
// user, ok := _activeDevices.Get(channel.DeviceID)
// if !ok {
// return nil, errors.New("设备已离线")
// }
// // GB28181推流
// if data.StreamID == "" {
// ssrcLock.Lock()
// // data.ssrc =g. getSSRC(data.T)
// data.StreamID = ssrc2stream(data.ssrc)
// 成功后保存
// db.Create(db.DBClient, data)
ssrcLock.Unlock()
}
// // 成功后保存
// // db.Create(db.DBClient, data)
// ssrcLock.Unlock()
// }
var err error
data, err = sipPlayPush(data, channel, user)
if err != nil {
return nil, fmt.Errorf("获取视频失败:%v", err)
}
}
// var err error
// data, err = sipPlayPush(data, channel, user)
// if err != nil {
// return nil, fmt.Errorf("获取视频失败:%v", err)
// }
// }
data.HTTP = fmt.Sprintf("%s/rtp/%s/hls.m3u8", config.Media.HTTP, data.StreamID)
data.RTMP = fmt.Sprintf("%s/rtp/%s", config.Media.RTMP, data.StreamID)
data.RTSP = fmt.Sprintf("%s/rtp/%s", config.Media.RTSP, data.StreamID)
data.WSFLV = fmt.Sprintf("%s/rtp/%s.live.flv", config.Media.WS, data.StreamID)
// data.HTTP = fmt.Sprintf("%s/rtp/%s/hls.m3u8", config.Media.HTTP, data.StreamID)
// data.RTMP = fmt.Sprintf("%s/rtp/%s", config.Media.RTMP, data.StreamID)
// data.RTSP = fmt.Sprintf("%s/rtp/%s", config.Media.RTSP, data.StreamID)
// data.WSFLV = fmt.Sprintf("%s/rtp/%s.live.flv", config.Media.WS, data.StreamID)
data.Ext = time.Now().Unix() + 2*60 // 2分钟等待时间
StreamList.Response.Store(data.StreamID, data)
if data.T == 0 {
StreamList.Succ.Store(data.ChannelID, data)
}
// db.Save(db.DBClient, data)
return data, nil
}
// data.Ext = time.Now().Unix() + 2*60 // 2分钟等待时间
// StreamList.Response.Store(data.StreamID, data)
// if data.T == 0 {
// StreamList.Succ.Store(data.ChannelID, data)
// }
// // db.Save(db.DBClient, data)
// return data, nil
// }
var ssrcLock *sync.Mutex
func sipPlayPush(data *Streams, channel Channels, device Devices) (*Streams, error) {
var (
s sdp.Session
b []byte
)
name := "Play"
protocal := "TCP/RTP/AVP"
if data.T == 1 {
name = "Playback"
protocal = "RTP/RTCP"
}
// func sipPlayPush(data *Streams, channel Channels, device Devices) (*Streams, error) {
// var (
// s sdp.Session
// b []byte
// )
// name := "Play"
// protocal := "TCP/RTP/AVP"
// if data.T == 1 {
// name = "Playback"
// protocal = "RTP/RTCP"
// }
video := sdp.Media{
Description: sdp.MediaDescription{
Type: "video",
Port: _sysinfo.MediaServerRtpPort,
Formats: []string{"96", "98", "97"},
Protocol: protocal,
},
}
video.AddAttribute("recvonly")
if data.T == 0 {
video.AddAttribute("setup", "passive")
video.AddAttribute("connection", "new")
}
video.AddAttribute("rtpmap", "96", "PS/90000")
video.AddAttribute("rtpmap", "98", "H264/90000")
video.AddAttribute("rtpmap", "97", "MPEG4/90000")
// video := sdp.Media{
// Description: sdp.MediaDescription{
// Type: "video",
// Port: _sysinfo.MediaServerRtpPort,
// Formats: []string{"96", "98", "97"},
// Protocol: protocal,
// },
// }
// video.AddAttribute("recvonly")
// if data.T == 0 {
// video.AddAttribute("setup", "passive")
// video.AddAttribute("connection", "new")
// }
// video.AddAttribute("rtpmap", "96", "PS/90000")
// video.AddAttribute("rtpmap", "98", "H264/90000")
// video.AddAttribute("rtpmap", "97", "MPEG4/90000")
// defining message
msg := &sdp.Message{
Origin: sdp.Origin{
Username: _serverDevices.DeviceID, // 媒体服务器id
Address: _sysinfo.MediaServerRtpIP.String(),
},
Name: name,
Connection: sdp.ConnectionData{
IP: _sysinfo.MediaServerRtpIP,
TTL: 0,
},
Timing: []sdp.Timing{
{
Start: data.S,
End: data.E,
},
},
Medias: []sdp.Media{video},
SSRC: data.ssrc,
}
if data.T == 1 {
msg.URI = fmt.Sprintf("%s:0", channel.ChannelID)
}
// // defining message
// msg := &sdp.Message{
// Origin: sdp.Origin{
// Username: _serverDevices.DeviceID, // 媒体服务器id
// Address: _sysinfo.MediaServerRtpIP.String(),
// },
// Name: name,
// Connection: sdp.ConnectionData{
// IP: _sysinfo.MediaServerRtpIP,
// TTL: 0,
// },
// Timing: []sdp.Timing{
// {
// Start: data.S,
// End: data.E,
// },
// },
// Medias: []sdp.Media{video},
// SSRC: data.ssrc,
// }
// if data.T == 1 {
// msg.URI = fmt.Sprintf("%s:0", channel.ChannelID)
// }
// appending message to session
s = msg.Append(s)
// appending session to byte buffer
b = s.AppendTo(b)
uri, _ := sip.ParseURI(channel.URIStr)
channel.addr = &sip.Address{URI: uri}
_serverDevices.addr.Params.Add("tag", sip.String{Str: sip.RandString(20)})
hb := sip.NewHeaderBuilder().SetTo(channel.addr).SetFrom(_serverDevices.addr).AddVia(&sip.ViaHop{
Params: sip.NewParams().Add("branch", sip.String{Str: sip.GenerateBranch()}),
}).SetContentType(&sip.ContentTypeSDP).SetMethod(sip.MethodInvite).SetContact(_serverDevices.addr)
req := sip.NewRequest("", sip.MethodInvite, channel.addr.URI, sip.DefaultSipVersion, hb.Build(), b)
req.SetDestination(device.source)
req.AppendHeader(&sip.GenericHeader{HeaderName: "Subject", Contents: fmt.Sprintf("%s:%s,%s:%s", channel.ChannelID, data.StreamID, _serverDevices.DeviceID, data.StreamID)})
req.SetRecipient(channel.addr.URI)
tx, err := svr.Request(req)
if err != nil {
// logrus.Warningln("sipPlayPush fail.id:", device.DeviceID, channel.ChannelID, "err:", err)
return data, err
}
// response
response, err := sipResponse(tx)
if err != nil {
// logrus.Warningln("sipPlayPush response fail.id:", device.DeviceID, channel.ChannelID, "err:", err)
return data, err
}
data.Resp = response
// ACK
tx.Request(sip.NewRequestFromResponse(sip.MethodACK, response))
// // appending message to session
// s = msg.Append(s)
// // appending session to byte buffer
// b = s.AppendTo(b)
// uri, _ := sip.ParseURI(channel.URIStr)
// channel.addr = &sip.Address{URI: uri}
// _serverDevices.addr.Params.Add("tag", sip.String{Str: sip.RandString(20)})
// hb := sip.NewHeaderBuilder().SetTo(channel.addr).SetFrom(_serverDevices.addr).AddVia(&sip.ViaHop{
// Params: sip.NewParams().Add("branch", sip.String{Str: sip.GenerateBranch()}),
// }).SetContentType(&sip.ContentTypeSDP).SetMethod(sip.MethodInvite).SetContact(_serverDevices.addr)
// req := sip.NewRequest("", sip.MethodInvite, channel.addr.URI, sip.DefaultSipVersion, hb.Build(), b)
// req.SetDestination(device.source)
// req.AppendHeader(&sip.GenericHeader{HeaderName: "Subject", Contents: fmt.Sprintf("%s:%s,%s:%s", channel.ChannelID, data.StreamID, _serverDevices.DeviceID, data.StreamID)})
// req.SetRecipient(channel.addr.URI)
// tx, err := svr.Request(req)
// if err != nil {
// // logrus.Warningln("sipPlayPush fail.id:", device.DeviceID, channel.ChannelID, "err:", err)
// return data, err
// }
// // response
// response, err := sipResponse(tx)
// if err != nil {
// // logrus.Warningln("sipPlayPush response fail.id:", device.DeviceID, channel.ChannelID, "err:", err)
// return data, err
// }
// data.Resp = response
// // ACK
// tx.Request(sip.NewRequestFromResponse(sip.MethodACK, response))
callid, _ := response.CallID()
data.CallID = string(*callid)
// callid, _ := response.CallID()
// data.CallID = string(*callid)
cseq, _ := response.CSeq()
if cseq != nil {
data.CseqNo = cseq.SeqNo
}
// cseq, _ := response.CSeq()
// if cseq != nil {
// data.CseqNo = cseq.SeqNo
// }
// from, _ := response.From()
// to, _ := response.To()
// for k, v := range to.Params.Items() {
// data.Ttag[k] = v.String()
// }
// for k, v := range from.Params.Items() {
// data.Ftag[k] = v.String()
// }
data.Status = 0
// // from, _ := response.From()
// // to, _ := response.To()
// // for k, v := range to.Params.Items() {
// // data.Ttag[k] = v.String()
// // }
// // for k, v := range from.Params.Items() {
// // data.Ftag[k] = v.String()
// // }
// data.Status = 0
return data, err
}
// return data, err
// }
// sip 停止播放
func SipStopPlay(ssrc string) {
+6 -4
View File
@@ -36,7 +36,7 @@ func NewGB28181API(cfg *conf.Bootstrap, store gb28181.GB28181, sms *sms.NodeMana
cfg: &cfg.Sip,
core: store,
sms: sms,
catalog: sip.NewCollector[Channels](func(c1, c2 *Channels) bool {
catalog: sip.NewCollector(func(c1, c2 *Channels) bool {
return c1.ChannelID == c2.ChannelID
}),
streams: &conc.Map[string, *Streams]{},
@@ -141,15 +141,17 @@ func (g *GB28181API) handlerRegister(ctx *sip.Context) {
}
g.login(ctx, expire)
conn := ctx.Request.GetConnection()
fmt.Printf(">>> %p\n", conn)
// conn := ctx.Request.GetConnection()
// fmt.Printf(">>> %p\n", conn)
ctx.Log.Info("设备注册成功")
// ctx.Log.Debug("device info", "source", ctx.Source, "host", ctx.Host)
respFn()
g.QueryDeviceInfo(ctx)
g.QueryCatalog(dev.DeviceID)
_ = g.QueryCatalog(dev.DeviceID)
_ = g.QueryConfigDownloadBasic(dev.DeviceID, basicParam)
}
func (g GB28181API) login(ctx *sip.Context, expire string) {
+9 -3
View File
@@ -58,6 +58,7 @@ func NewServer(cfg *conf.Bootstrap, store gb28181.GB28181, sc sms.Core) (*Server
msg.Handle("Keepalive", api.sipMessageKeepalive)
msg.Handle("Catalog", api.sipMessageCatalog)
msg.Handle("DeviceInfo", api.sipMessageDeviceInfo)
msg.Handle("ConfigDownload", api.sipMessageConfigDownload)
// msg.Handle("RecordInfo", api.handlerMessage)
@@ -88,12 +89,17 @@ func NewServer(cfg *conf.Bootstrap, store gb28181.GB28181, sc sms.Core) (*Server
func (s *Server) startTickerCheck() {
conc.Timer(context.Background(), 60*time.Second, time.Second, func() {
now := time.Now()
s.memoryStorer.RangeDevices(func(key string, value *Device) bool {
if !value.IsOnline {
s.memoryStorer.RangeDevices(func(key string, ipc *Device) bool {
if !ipc.IsOnline {
return true
}
if sub := now.Sub(value.LastKeepaliveAt); sub >= 3*60*time.Second || value.conn == nil {
timeout := time.Duration(ipc.keepaliveTimeout) * time.Duration(ipc.keepaliveInterval) * time.Second
if timeout <= 0 {
timeout = 3 * 60 * time.Second
}
if sub := now.Sub(ipc.LastKeepaliveAt); sub >= timeout || ipc.conn == nil {
s.gb.logout(key, func(d *gb28181.Device) {
d.IsOnline = false
})
+8 -2
View File
@@ -3,6 +3,7 @@ package sip
import (
"bytes"
"fmt"
"io"
"log/slog"
"strconv"
"strings"
@@ -583,7 +584,12 @@ func (p *parser) start() {
packet = <-p.in
startLine, err := packet.nextLine()
if err != nil {
slog.Error("start nextLine", "err", err, "line", startLine)
if err != io.EOF {
slog.Error("start nextLine", "err", err, "line", startLine)
}
continue
}
if len(startLine) == 0 {
continue
}
if isRequest(startLine) {
@@ -601,7 +607,7 @@ func (p *parser) start() {
termErr = NewError(err, "parserMessage", "ParseStatusLine", startLine)
}
} else {
slog.Error("start unknown", "err", err, "line", startLine)
slog.Error("start unknown", "line", startLine)
continue
}
if termErr != nil {
+4 -7
View File
@@ -20,7 +20,7 @@ var bufferSize uint16 = 65535 - 20 - 8 // IPv4 max size - IPv4 Header size - UDP
// Server sip
type Server struct {
udpaddr net.Addr
// udpaddr net.Addr
udpConn Connection
txs *transacionts
@@ -248,21 +248,18 @@ func (s *Server) handlerListen(msgs chan Message) {
case *Request:
req := tmsg
dst := s.udpaddr
// dst := s.udpaddr
if req.conn.Network() == "tcp" {
dst = s.tcpaddr
req.SetDestination(s.tcpaddr)
}
req.SetDestination(dst)
s.handlerRequest(req)
case *Response:
resp := tmsg
dst := s.udpaddr
if resp.conn.Network() == "tcp" {
dst = s.tcpaddr
resp.SetDestination(s.tcpaddr)
}
resp.SetDestination(dst)
s.handlerResponse(resp)
default:
// logrus.Errorln("undefind msg type,", tmsg, msg.String())
+11
View File
@@ -186,6 +186,17 @@ func xmlDecode(data []byte, v interface{}) error {
return decoder.Decode(v)
}
// XMLEncode XML编码器
func XMLEncode(data any) ([]byte, error) {
b, err := xml.Marshal(data)
if err != nil {
slog.Error("MarshalIndent", "err", err)
return nil, err
}
xmlHeader := "<?xml version=\"1.0\" encoding=\"GB2312\"?>\n"
return Utf8ToGbk([]byte(xmlHeader + string(b)))
}
// Max Max
func Max(a, b int64) int64 {
if a > b {