更新镜像

This commit is contained in:
xugo
2025-03-17 02:19:21 +08:00
parent 6d2f457fde
commit 41b709a6f0
17 changed files with 235 additions and 99 deletions

View File

@@ -1,5 +1,7 @@
FROM alpine:latest
ARG TARGETARCH
ENV TZ=Asia/Shanghai
RUN apk --no-cache add ca-certificates \
@@ -7,7 +9,7 @@ RUN apk --no-cache add ca-certificates \
WORKDIR /app
ADD ./build/linux_amd64/bin ./
ADD ./build/linux_${TARGETARCH}/bin ./
ADD ./configs/config.toml /app/configs/config.toml
ADD ./www /app/www

View File

@@ -158,6 +158,12 @@ BUILD_LINUX_AMD64_DIR := ./build/linux_amd64
build/linux:
$(eval GOARCH := amd64)
$(eval GOOS := linux)
$(eval dir := $(BUILD_DIR_ROOT)/$(GOOS)_$(GOARCH))
@make build/local GOOS=$(GOOS) GOARCH=$(GOARCH)
$(eval GOARCH := arm64)
$(eval GOOS := linux)
$(eval dir := $(BUILD_DIR_ROOT)/$(GOOS)_$(GOARCH))
@make build/local GOOS=$(GOOS) GOARCH=$(GOARCH)
## build/windows: 构建 windows 应用
@@ -178,7 +184,10 @@ docker/push:
@docker push $(IMAGE_NAME)
docker/build/full:
@docker build --force-rm=true --platform linux/amd64 -t $(IMAGE_NAME) -f Dockerfile_full .
@docker build --force-rm=true --push --platform linux/amd64,linux/arm64 -t $(IMAGE_NAME) -f Dockerfile_full .
docker/build/gowvp: build/clean build/linux
@docker build --force-rm=true --push --platform linux/amd64,linux/arm64 -t registry.cn-shanghai.aliyuncs.com/ixugo/gowvp:latest -f Dockerfile .
# ==================================================================================== #

View File

@@ -36,8 +36,6 @@ go wvp 是 Go 语言实现的开源 GB28181 解决方案,基于 GB28181-2022
项目框架基于 @ixugo [goweb](https://github.com/ixugo/goweb)
Java 语言 WVP @648540858 [wvp-GB28181-pro](https://github.com/648540858/wvp-GB28181-pro)
## QA
> 怎么没有前端资源? 如何加载网页呢?
@@ -92,9 +90,54 @@ ZLM使用文档 [github.com/ZLMediaKit/ZLMediaKit](https://github.com/ZLMediaKit
## Docker
### 视频指南
[如何构建或运行项目](https://www.bilibili.com/video/BV1QLQeYHEXb)
[如何用 docker compose 部署项目](https://www.bilibili.com/video/BV112QYY3EZX)
[docker hub](https://hub.docker.com/r/gospace/gowvp)
** gowvp & zlmediakit 融合镜像**
** gowvp & zlmediakit 分开镜像(推荐)**
```yml
services:
gowvp:
image: registry.cn-shanghai.aliyuncs.com/ixugo/gowvp:latest
ports:
- 15123:15123 # 管理平台 http 端口
- 15060:15060 # gb28181 sip tcp 端口
- 15060:15060/udp # gb28181 sip udp 端口
volumes:
# - ./logs:/app/logs # 如果需要持久化日志,请取消注释
- ./configs:/app/configs
depends_on:
- zlm
zlm:
image: zlmediakit/zlmediakit:master
restart: always
# 推荐 linux 主机使用 host 模式
# network_mode: host
ports:
- 1935:1935 # rtmp
- 554:554 # rtsp
- 8080:80 # api
- 8443:443
- 10000:10000
- 10000:10000/udp
- 8000:8000/udp
- 9000:9000/udp
- 20050-20100:20050-20100
- 20050-20100:20050-20100/udp
volumes:
- ./configs:/opt/media/conf
```
** gowvp & zlmediakit 融合镜像(不推荐)**
docker-compose.yml
```yml
services:
@@ -119,39 +162,6 @@ services:
- ./zlm.conf:/opt/media/conf
```
** gowvp & zlmediakit 分开镜像**
```yml
services:
gowvp:
image: registry.cn-shanghai.aliyuncs.com/ixugo/gowvp:latest
ports:
- 15123:15123 # 管理平台 http 端口
- 15060:15060 # gb28181 sip tcp 端口
- 15060:15060/udp # gb28181 sip udp 端口
volumes:
- ./logs:/app/logs
- ./configs:/app/configs
zlm:
image: zlmediakit/zlmediakit:master
restart: always
# 推荐 linux 主机使用 host 模式
# network_mode: host
ports:
- 1935:1935 # rtmp
- 554:554 # rtsp
- 8080:80 # api
- 8443:443
- 10000:10000
- 10000:10000/udp
- 8000:8000/udp
- 9000:9000/udp
- 20050-20100:20050-20100
- 20050-20100:20050-20100/udp
volumes:
- ./conf:/opt/media/conf
```

View File

@@ -8,6 +8,7 @@ import (
"os"
"os/signal"
"path/filepath"
"regexp"
"strconv"
"syscall"
"time"
@@ -45,6 +46,7 @@ func main() {
var bc conf.Bootstrap
// 获取配置目录绝对路径
fileDir, _ := abs(*configDir)
os.MkdirAll(fileDir, 0o755)
filePath := filepath.Join(fileDir, "config.toml")
configIsNotExistWrite(filePath)
if err := conf.SetupConfig(&bc, filePath); err != nil {
@@ -76,6 +78,14 @@ func main() {
}))
}
secret, err := getSecret(*configDir)
if err == nil {
slog.Info("发现 zlm 配置,已赋值,未回写配置文件", "secret", secret)
bc.Media.Secret = secret
} else {
slog.Info("未发现 zlm 配置,请检查 config.ini 文件", "err", err)
}
handler, cleanUp, err := wireApp(&bc, log)
if err != nil {
slog.Error("程序构建失败", "err", err)
@@ -125,3 +135,17 @@ func configIsNotExistWrite(path string) {
}
}
}
// 读取 config.ini 文件,通过正则表达式,获取 secret 的值
func getSecret(configDir string) (string, error) {
content, err := os.ReadFile(filepath.Join(system.Getwd(), configDir, "config.ini"))
if err != nil {
return "", err
}
re := regexp.MustCompile(`secret=(\w+)`)
matches := re.FindStringSubmatch(string(content))
if len(matches) < 2 {
return "", fmt.Errorf("secret not found")
}
return matches[1], nil
}

View File

@@ -1,16 +1,25 @@
version = 1
[Server]
rtmpSecret = "123"
[Server.HTTP]
Port = 15123
JwtSecret = ""
Timeout = "60s"
Debug = false
# rtmp 推流秘钥
RTMPSecret = '123'
[Server.HTTP.Pprof]
# 对外提供的服务,建议由 nginx 代理
[Server.HTTP]
# http 端口
Port = 15123
# 请求超时时间
Timeout = '1m0s'
# jwt 秘钥,空串时,每次启动程序将随机赋值
JwtSecret = ''
[Server.HTTP.PProf]
# 是否启用 pprof, 建议设置为 true
Enabled = true
# 访问白名单
AccessIps = ['::1', '127.0.0.1']
[Data]
# 数据库支持 sqlite 和 postgres 两种,使用 sqlite 时 dsn 应当填写文件存储路径
[Data.Database]
Dsn = './configs/data.db'
MaxIdleConns = 1
@@ -18,20 +27,6 @@ version = 1
ConnMaxLifetime = '6h0m0s'
SlowThreshold = '200ms'
[Sip]
Port = 15060
ID = "3402000000200000001"
Domain = "3402000000"
Password = ""
[Media]
IP = "127.0.0.1"
HTTPPort = 8080
Secret = "s1kPE7bzqKeHUaVcp8dCA0jeB8yxyFq4"
WebHookIP = "192.168.1.10"
RTPPortRange = "20000-20500"
SDPIP = "192.168.1.10"
[Log]
# 日志存储目录,不能使用特殊符号
Dir = './logs'
@@ -43,3 +38,27 @@ version = 1
RotationTime = '12h0m0s'
# 多大文件,分割一个新的日志文件(MB)
RotationSize = 50
[Sip]
# 服务监听的 tcp/udp 端口号
Port = 15060
# gb/t28181 20 位国标 ID
ID = '3402000000200000001'
# 域
Domain = '3402000000'
# 注册密码
Password = ''
[Media]
# 媒体服务器 IP
IP = '127.0.0.1'
# 媒体服务器 HTTP 端口
HTTPPort = 8080
# 媒体服务器密钥
Secret = ''
# 用于流媒体 webhook 回调
WebHookIP = '192.168.1.10'
# 媒体服务器 RTP 端口范围
RTPPortRange = '20000-20500'
# 媒体服务器 SDP IP
SDPIP = '192.168.1.10'

View File

@@ -5,4 +5,29 @@ services:
context: .
dockerfile: ./Dockerfile
ports:
- 18082:18081
- 15123:15123 # 管理平台 http 端口
- 15060:15060 # gb28181 sip tcp 端口
- 15060:15060/udp # gb28181 sip udp 端口
volumes:
- ./logs:/app/logs
- ./configs:/app/configs
depends_on:
- zlm
zlm:
image: zlmediakit/zlmediakit:master
restart: always
# 推荐 linux 主机使用 host 模式
# network_mode: host
ports:
- 1935:1935 # rtmp
- 554:554 # rtsp
- 8080:80 # api
- 8443:443
- 10000:10000
- 10000:10000/udp
- 8000:8000/udp
- 9000:9000/udp
- 20050-20100:20050-20100
- 20050-20100:20050-20100/udp
volumes:
- ./conf:/opt/media/conf

View File

@@ -1,21 +1,24 @@
services:
gowvp:
image: gb28181
build:
context: .
dockerfile: ./Dockerfile
image: registry.cn-shanghai.aliyuncs.com/ixugo/gowvp:latest
ports:
- 15123:15123 # 管理平台 http 端口
- 15060:15060 # gb28181 sip tcp 端口
- 15060:15060/udp # gb28181 sip udp 端口
volumes:
- ./logs:/app/logs
# - ./logs:/app/logs # 如果需要持久化日志,请取消注释
- ./configs:/app/configs
networks:
- gowvp-network
depends_on:
- zlm
zlm:
image: zlmediakit/zlmediakit:master
restart: always
# 推荐 linux 主机使用 host 模式
# network_mode: host
networks:
- gowvp-network
ports:
- 1935:1935 # rtmp
- 554:554 # rtsp
@@ -28,4 +31,9 @@ services:
- 20050-20100:20050-20100
- 20050-20100:20050-20100/udp
volumes:
- ./conf:/opt/media/conf
- ./configs:/opt/media/conf
# 如果不使用 host 模式,可以使用下面的配置
networks:
gowvp-network:
driver: bridge

View File

@@ -88,8 +88,9 @@ func (c *Cache) Change(deviceID string, changeFn func(*gb28181.Device), changeFn
dev2.LastKeepaliveAt = dev.KeepaliveAt.Time
dev2.LastRegisterAt = dev.RegisteredAt.Time
dev2.Expires = dev.Expires
dev2.Password = dev.Password
dev2.Address = dev.Address
changeFn2(dev2)
if !dev2.IsOnline {
if err := c.Storer.Channel().BatchEdit(context.TODO(), "is_online", false, orm.Where("did=?", dev.ID)); err != nil {
slog.Error("更新通道离线状态失败", "error", err)

View File

@@ -2,6 +2,7 @@ package gb28181cache
import (
"context"
"log/slog"
"github.com/gowvp/gb28181/internal/core/gb28181"
"github.com/gowvp/gb28181/pkg/gbs"
@@ -12,7 +13,7 @@ import (
var _ gb28181.DeviceStorer = &Device{}
type Device Cache
type Device = Cache
// Add implements gb28181.DeviceStorer.
func (d *Device) Add(ctx context.Context, dev *gb28181.Device) error {
@@ -47,7 +48,23 @@ func (d *Device) Del(ctx context.Context, dev *gb28181.Device, opts ...orm.Query
// Edit implements gb28181.DeviceStorer.
func (d *Device) Edit(ctx context.Context, dev *gb28181.Device, changeFn func(*gb28181.Device), opts ...orm.QueryOption) error {
return d.Storer.Device().Edit(ctx, dev, changeFn, opts...)
if err := d.Storer.Device().Edit(ctx, dev, changeFn, opts...); err != nil {
return err
}
dev2, ok := d.devices.Load(dev.DeviceID)
if !ok {
panic("edit device not found")
}
// 密码修改,设备需要重新注册
if dev2.Password != dev.Password && dev.Password != "" {
slog.Info("修改密码,设备离线")
d.Change(dev.DeviceID, func(d *gb28181.Device) {
d.Password = dev.Password
d.IsOnline = false
}, func(d *gbs.Device) {
})
}
return nil
}
// Find implements gb28181.DeviceStorer.

View File

@@ -54,7 +54,7 @@ func (c *Core) AddMediaServer(ctx context.Context, in *AddMediaServerInput) (*Me
}
// EditMediaServer Update object information
func (c *Core) EditMediaServer(ctx context.Context, in *EditMediaServerInput, id string) (*MediaServer, error) {
func (c *Core) EditMediaServer(ctx context.Context, in *EditMediaServerInput, id string, serverPort int) (*MediaServer, error) {
var out MediaServer
if err := c.storer.MediaServer().Edit(ctx, &out, func(b *MediaServer) {
if err := copier.Copy(b, in); err != nil {
@@ -63,6 +63,7 @@ func (c *Core) EditMediaServer(ctx context.Context, in *EditMediaServerInput, id
}, orm.Where("id=?", id)); err != nil {
return nil, web.ErrDB.Withf(`Edit err[%s]`, err.Error())
}
c.connection(&out, serverPort)
return &out, nil
}

View File

@@ -13,7 +13,7 @@ type MediaServer struct {
CreatedAt orm.Time `gorm:"column:created_at;notNull;default:CURRENT_TIMESTAMP" json:"created_at"`
UpdatedAt orm.Time `gorm:"column:updated_at;notNull;default:CURRENT_TIMESTAMP" json:"updated_at"`
HookIP string `gorm:"column:hook_ip;notNull;default:''" json:"hook_ip"`
SDPIP string `gorm:"column:sdpip;notNull;default:''" json:"sdpip"`
SDPIP string `gorm:"column:sdp_ip;notNull;default:''" json:"sdp_ip"`
StreamIP string `gorm:"column:stream_ip;notNull;default:''" json:"stream_ip"`
Ports MediaServerPorts `gorm:"column:ports;notNull;default:'{}';type:jsonb" json:"ports"`
AutoConfig bool `gorm:"column:auto_config;notNull;default:FALSE" json:"auto_config"`

View File

@@ -30,25 +30,25 @@ type FindMediaServerInput struct {
}
type EditMediaServerInput struct {
IP string `json:"ip"`
HookIP string `json:"hook_ip"`
SDPIP string `json:"sdpip"`
StreamIP string `json:"stream_ip"`
Ports MediaServerPorts `json:"ports"`
AutoConfig bool `json:"auto_config"`
Secret string `json:"secret"`
HookAliveInterval int `json:"hook_alive_interval"`
RTPEnable bool `json:"rtpenable"`
Status bool `json:"status"`
RTPPortRange string `json:"rtpport_range"`
SendRTPPortRange string `json:"send_rtpport_range"`
RecordAssistPort int `json:"record_assist_port"`
LastKeepaliveAt orm.Time `json:"last_keepalive_at"`
IsDefault bool `json:"is_default"`
RecordDay int `json:"record_day"`
RecordPath string `json:"record_path"`
Type string `json:"type"`
TranscodeSuffix string `json:"transcode_suffix"`
IP string `json:"ip"`
HookIP string `json:"hook_ip"`
SDPIP string `json:"sdpip"`
// StreamIP string `json:"stream_ip"`
// Ports MediaServerPorts `json:"ports"`
// AutoConfig bool `json:"auto_config"`
Secret string `json:"secret"`
// HookAliveInterval int `json:"hook_alive_interval"`
// RTPEnable bool `json:"rtpenable"`
// Status bool `json:"status"`
// RTPPortRange string `json:"rtpport_range"`
// SendRTPPortRange string `json:"send_rtpport_range"`
// RecordAssistPort int `json:"record_assist_port"`
// LastKeepaliveAt orm.Time `json:"last_keepalive_at"`
// IsDefault bool `json:"is_default"`
// RecordDay int `json:"record_day"`
// RecordPath string `json:"record_path"`
// Type string `json:"type"`
// TranscodeSuffix string `json:"transcode_suffix"`
}
type AddMediaServerInput struct {

View File

@@ -126,10 +126,10 @@ func (n *NodeManager) connection(server *MediaServer, serverPort int) {
log.Info("ZLM 服务节点连接中")
for {
for i := range 10 {
resp, err := engine.GetServerConfig()
if err != nil {
log.Error("ZLM 服务节点连接失败", "err", err)
log.Error("ZLM 服务节点连接失败", "err", err, "retry", i)
time.Sleep(10 * time.Second)
continue
}

View File

@@ -23,6 +23,7 @@ var startRuntime = time.Now()
func setupRouter(r *gin.Engine, uc *Usecase) {
uc.GB28181API.uc = uc
uc.SMSAPI.uc = uc
go stat.LoadTop(system.Getwd(), func(m map[string]any) {
_ = m
})
@@ -70,6 +71,7 @@ func setupRouter(r *gin.Engine, uc *Usecase) {
registerGB28181(r, uc.GB28181API)
registerProxy(r, uc.ProxyAPI)
registerConfig(r, uc.ConfigAPI)
registerSms(r, uc.SMSAPI)
}
type playOutput struct {

View File

@@ -13,6 +13,7 @@ import (
type SmsAPI struct {
smsCore sms.Core
uc *Usecase
}
func NewSMSCore(db *gorm.DB, cfg *conf.Bootstrap) sms.Core {
@@ -31,10 +32,11 @@ func registerSms(g gin.IRouter, api SmsAPI, handler ...gin.HandlerFunc) {
{
group := g.Group("/media_servers", handler...)
group.GET("", web.WarpH(api.findMediaServer))
group.GET("/:id", web.WarpH(api.getMediaServer))
group.PUT("/:id", web.WarpH(api.editMediaServer))
group.POST("", web.WarpH(api.addMediaServer))
group.DELETE("/:id", web.WarpH(api.delMediaServer))
// group.GET("/:id", web.WarpH(api.getMediaServer))
// group.POST("", web.WarpH(api.addMediaServer))
// group.DELETE("/:id", web.WarpH(api.delMediaServer))
}
}
@@ -52,7 +54,20 @@ func (a SmsAPI) getMediaServer(c *gin.Context, _ *struct{}) (any, error) {
func (a SmsAPI) editMediaServer(c *gin.Context, in *sms.EditMediaServerInput) (any, error) {
mediaServerID := c.Param("id")
return a.smsCore.EditMediaServer(c.Request.Context(), in, mediaServerID)
out, err := a.smsCore.EditMediaServer(c.Request.Context(), in, mediaServerID, a.uc.Conf.Server.HTTP.Port)
if err != nil {
return nil, err
}
if mediaServerID == "local" {
a.uc.Conf.Media.IP = out.IP
a.uc.Conf.Media.SDPIP = out.SDPIP
a.uc.Conf.Media.Secret = out.Secret
a.uc.Conf.Media.WebHookIP = out.HookIP
if err := conf.WriteConfig(a.uc.Conf, a.uc.Conf.ConfigPath); err != nil {
return nil, web.ErrServer.Msg(err.Error())
}
}
return out, err
}
func (a SmsAPI) addMediaServer(c *gin.Context, in *sms.AddMediaServerInput) (any, error) {

View File

@@ -29,6 +29,7 @@ type Device struct {
IsOnline bool
Address string
Password string
conn sip.Connection
source net.Addr
@@ -63,6 +64,7 @@ func NewDevice(conn sip.Connection, d *gb28181.Device) *Device {
LastKeepaliveAt: d.KeepaliveAt.Time,
LastRegisterAt: d.RegisteredAt.Time,
IsOnline: d.IsOnline,
Password: d.Password,
}
return &c

View File

@@ -48,8 +48,9 @@ func (e Engine) SetConfig(cfg Config) Engine {
}
func (e *Engine) post(path string, data map[string]any, out any) error {
bodyMap := map[string]any{
"secret": e.cfg.Secret,
bodyMap := make(map[string]any)
if e.cfg.Secret != "" {
bodyMap["secret"] = e.cfg.Secret
}
maps.Copy(bodyMap, data)
body, _ := json.Marshal(bodyMap)