diff --git a/configs/config.toml b/configs/config.toml index 34045b5..4a31d43 100644 --- a/configs/config.toml +++ b/configs/config.toml @@ -3,6 +3,9 @@ # rtmp 推流秘钥 RTMPSecret = '123' + Username = 'admin' + Password = 'admin' + # 对外提供的服务,建议由 nginx 代理 [Server.HTTP] # http 端口 diff --git a/go.mod b/go.mod index 78f71f5..55a3efd 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/gowvp/gb28181 -go 1.24 +go 1.25 require ( github.com/DATA-DOG/go-sqlmock v1.5.2 @@ -10,7 +10,7 @@ require ( github.com/glebarez/sqlite v1.11.0 github.com/google/wire v0.6.0 github.com/gorilla/websocket v1.5.3 - github.com/ixugo/goddd v1.3.1 + github.com/ixugo/goddd v1.3.4 github.com/jinzhu/copier v0.4.0 github.com/pelletier/go-toml/v2 v2.2.3 github.com/shirou/gopsutil/v4 v4.25.7 diff --git a/go.sum b/go.sum index 70a9ace..01a0827 100644 --- a/go.sum +++ b/go.sum @@ -59,8 +59,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/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/ixugo/goddd v1.3.1 h1:dacdg8MVpnejwphFyqczFrEMz7wDAcCfY7FNcrxFenE= -github.com/ixugo/goddd v1.3.1/go.mod h1:gb102fElow80HXqvrTlJ56eE5vFEYnI7Nx46Iw/m5eU= +github.com/ixugo/goddd v1.3.4 h1:h6tdNJohegzh8jN8frmUybncihBz0tk71rkaWkJ9Vdk= +github.com/ixugo/goddd v1.3.4/go.mod h1:GJUdA9TLL0ZGNZZ1cZv33gdE9zjt61W5FGGTeVBvFg4= 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= diff --git a/internal/app/wire_gen.go b/internal/app/wire_gen.go index b823de4..8771bf8 100644 --- a/internal/app/wire_gen.go +++ b/internal/app/wire_gen.go @@ -38,6 +38,7 @@ func wireApp(bc *conf.Bootstrap, log *slog.Logger) (http.Handler, func(), error) gb28181API := api.NewGB28181API(gb28181Core) proxyAPI := api.NewProxyAPI(db, uniqueidCore) configAPI := api.NewConfigAPI(db, bc) + userAPI := api.NewUserAPI(bc) usecase := &api.Usecase{ Conf: bc, DB: db, @@ -50,6 +51,7 @@ func wireApp(bc *conf.Bootstrap, log *slog.Logger) (http.Handler, func(), error) ProxyAPI: proxyAPI, ConfigAPI: configAPI, SipServer: server, + UserAPI: userAPI, } handler := api.NewHTTPHandler(usecase) return handler, func() { diff --git a/internal/conf/config.go b/internal/conf/config.go index 65665b0..b315f25 100644 --- a/internal/conf/config.go +++ b/internal/conf/config.go @@ -17,8 +17,12 @@ type Bootstrap struct { type Server struct { Debug bool - RTMPSecret string `comment:"rtmp 推流秘钥"` - HTTP ServerHTTP `comment:"对外提供的服务,建议由 nginx 代理"` // HTTP服务器 + RTMPSecret string `comment:"rtmp 推流秘钥"` + + Username string `comment:"登录用户名"` + Password string `comment:"登录密码"` + + HTTP ServerHTTP `comment:"对外提供的服务,建议由 nginx 代理"` // HTTP服务器 } type ServerHTTP struct { diff --git a/internal/conf/default.go b/internal/conf/default.go index e703fd9..cc53e72 100644 --- a/internal/conf/default.go +++ b/internal/conf/default.go @@ -9,6 +9,8 @@ import ( func DefaultConfig() Bootstrap { return Bootstrap{ Server: Server{ + Username: "admin", + Password: "admin", RTMPSecret: "123", HTTP: ServerHTTP{ Port: 15123, diff --git a/internal/conf/unmarshal.go b/internal/conf/unmarshal.go index f979a65..bd7e000 100644 --- a/internal/conf/unmarshal.go +++ b/internal/conf/unmarshal.go @@ -17,10 +17,19 @@ func SetupConfig(v any, path string) error { // WriteConfig 将配置写回文件 func WriteConfig(v any, path string) error { - f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600) + tmp := path + ".tmp" + _ = os.RemoveAll(tmp) + + f, err := os.OpenFile(tmp, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600) if err != nil { return err } defer f.Close() - return toml.NewEncoder(f).SetIndentTables(true).Encode(v) + if err := toml.NewEncoder(f).SetIndentTables(true).Encode(v); err != nil { + return err + } + if err := f.Close(); err != nil { + return err + } + return os.Rename(tmp, path) } diff --git a/internal/web/api/api.go b/internal/web/api/api.go index 6b91e03..2e9fa90 100644 --- a/internal/web/api/api.go +++ b/internal/web/api/api.go @@ -43,7 +43,13 @@ func setupRouter(r *gin.Engine, uc *Usecase) { c.AbortWithStatus(http.StatusInternalServerError) }), web.Metrics(), - web.Logger(web.IgnorePrefix(staticPrefix)), + web.Logger(web.IgnorePrefix(staticPrefix), + web.IgnoreMethod(http.MethodOptions), + ), + web.LoggerWithBody(web.DefaultBodyLimit, + web.IgnoreBool(uc.Conf.Debug), + web.IgnoreMethod(http.MethodOptions), + ), ) go web.CountGoroutines(10*time.Minute, 20) @@ -60,7 +66,7 @@ func setupRouter(r *gin.Engine, uc *Usecase) { }, AllowCredentials: true, MaxAge: 12 * time.Hour, - AllowOriginFunc: func(origin string) bool { + AllowOriginFunc: func(_ string) bool { return true }, })) @@ -89,11 +95,13 @@ func setupRouter(r *gin.Engine, uc *Usecase) { statapi.Register(r) registerZLMWebhookAPI(r, uc.WebHookAPI) // TODO: 待增加鉴权 + registerPushAPI(r, uc.MediaAPI) registerGB28181(r, uc.GB28181API) registerProxy(r, uc.ProxyAPI) registerConfig(r, uc.ConfigAPI) registerSms(r, uc.SMSAPI) + RegisterUser(r, uc.UserAPI) r.Any("/proxy/sms/*path", uc.proxySMS) } diff --git a/internal/web/api/provider.go b/internal/web/api/provider.go index a390d48..379a3a6 100644 --- a/internal/web/api/provider.go +++ b/internal/web/api/provider.go @@ -37,6 +37,7 @@ var ( NewGB28181, NewProxyAPI, NewConfigAPI, + NewUserAPI, ) ) @@ -53,6 +54,7 @@ type Usecase struct { ConfigAPI ConfigAPI SipServer *gbs.Server + UserAPI UserAPI } // NewHTTPHandler 生成Gin框架路由内容 diff --git a/internal/web/api/user.go b/internal/web/api/user.go new file mode 100644 index 0000000..37d0c5a --- /dev/null +++ b/internal/web/api/user.go @@ -0,0 +1,80 @@ +package api + +import ( + "time" + + "github.com/gin-gonic/gin" + "github.com/gowvp/gb28181/internal/conf" + "github.com/ixugo/goddd/pkg/reason" + "github.com/ixugo/goddd/pkg/web" +) + +type UserAPI struct { + conf *conf.Bootstrap +} + +func NewUserAPI(conf *conf.Bootstrap) UserAPI { + return UserAPI{ + conf: conf, + } +} + +func RegisterUser(r gin.IRouter, api UserAPI, mid ...gin.HandlerFunc) { + group := r.Group("/user") + group.POST("/login", web.WarpH(api.login)) + group.PUT("/user", web.WarpHs(api.updateCredentials, mid...)...) +} + +// 登录请求结构体 +type loginInput struct { + Username string `json:"username" binding:"required"` + Password string `json:"password" binding:"required"` +} + +// 登录响应结构体 +type loginOutput struct { + Token string `json:"token"` + User string `json:"user"` +} + +// 登录接口 +func (api UserAPI) login(_ *gin.Context, in *loginInput) (*loginOutput, error) { + // 验证用户名和密码 + if in.Username != api.conf.Server.Username || in.Password != api.conf.Server.Password { + return nil, reason.ErrNameOrPasswd + } + + token, err := web.NewToken(web.TokenInput{ + Secret: api.conf.Server.HTTP.JwtSecret, + Expires: 3 * 24 * time.Hour, + Username: in.Username, + }) + if err != nil { + return nil, reason.ErrServer.SetMsg("生成token失败: " + err.Error()) + } + + return &loginOutput{ + Token: token, + User: in.Username, + }, nil +} + +// 修改凭据请求结构体 +type updateCredentialsInput struct { + Username string `json:"username" binding:"required"` + Password string `json:"password" binding:"required"` +} + +// 修改凭据接口 +func (api UserAPI) updateCredentials(c *gin.Context, in *updateCredentialsInput) (gin.H, error) { + // 更新配置中的用户名和密码 + api.conf.Server.Username = in.Username + api.conf.Server.Password = in.Password + + // 写入配置文件 + if err := conf.WriteConfig(api.conf, api.conf.ConfigPath); err != nil { + return nil, reason.ErrServer.SetMsg("保存配置失败: " + err.Error()) + } + + return gin.H{"msg": "凭据更新成功"}, nil +}