diff --git a/internal/app/api/proc.go b/internal/app/api/proc.go index bb42d6d..c00d498 100644 --- a/internal/app/api/proc.go +++ b/internal/app/api/proc.go @@ -1,9 +1,13 @@ package api import ( + "time" + + "github.com/google/uuid" "github.com/lzh-1625/go_process_manager/internal/app/logic" "github.com/lzh-1625/go_process_manager/internal/app/model" "github.com/lzh-1625/go_process_manager/internal/app/repository" + "github.com/lzh-1625/go_process_manager/utils" "github.com/gin-gonic/gin" ) @@ -107,3 +111,14 @@ func (p *procApi) ProcessControl(ctx *gin.Context) { proc.ProcessControl(user) rOk(ctx, "Operation successful!", nil) } + +func (p *procApi) ProcessCreateShare(ctx *gin.Context) { + req := bind[model.ProcessShare](ctx) + err := repository.WsShare.AddShareData(model.WsShare{ + ExpireTime: time.Now().Add(time.Minute * time.Duration(req.Minute)), + Write: req.Write, + Token: utils.UnwarpIgnore(uuid.NewRandom()).String(), + }) + errCheck(ctx, err != nil, err) + rOk(ctx, "Operation successful!", nil) +} diff --git a/internal/app/api/ws.go b/internal/app/api/ws.go index f472685..90be7c2 100644 --- a/internal/app/api/ws.go +++ b/internal/app/api/ws.go @@ -2,6 +2,7 @@ package api import ( "context" + "strconv" "sync" "time" @@ -9,6 +10,7 @@ import ( "github.com/lzh-1625/go_process_manager/internal/app/constants" "github.com/lzh-1625/go_process_manager/internal/app/logic" "github.com/lzh-1625/go_process_manager/internal/app/middle" + "github.com/lzh-1625/go_process_manager/internal/app/repository" "github.com/lzh-1625/go_process_manager/log" "github.com/lzh-1625/go_process_manager/utils" @@ -92,6 +94,53 @@ func (w *wsApi) WebsocketHandle(ctx *gin.Context) { conn.Close() } +func (w *wsApi) WebsocketShareHandle(ctx *gin.Context) { + token := getQueryString(ctx, "token") + data, err := repository.WsShare.GetWsShareDataByToken(token) + errCheck(ctx, err != nil, "Operation failed!") + errCheck(ctx, data.ExpireTime.Unix() <= time.Now().Unix(), "Share expired!") + proc, err := logic.ProcessCtlLogic.GetProcess(data.Pid) + errCheck(ctx, err != nil, "Operation failed!") + gusetName := "guest-" + strconv.Itoa(data.Id) // 构造访客用户名 + errCheck(ctx, proc.HasWsConn(gusetName), "A connection already exists; unable to establish a new one!") + errCheck(ctx, proc.State.State != 1, "The process is currently running.") + errCheck(ctx, !proc.VerifyControl(), "Insufficient permissions; please check your access rights!") + conn, err := upgrader.Upgrade(ctx.Writer, ctx.Request, nil) + errCheck(ctx, err != nil, "WebSocket connection upgrade failed!") + + log.Logger.Infow("ws连接成功") + + proc.SetTerminalSize(utils.GetIntByString(ctx.Query("cols")), utils.GetIntByString(ctx.Query("rows"))) + wsCtx, cancel := context.WithCancel(context.Background()) + wci := &WsConnetInstance{ + WsConnect: conn, + CancelFunc: cancel, + wsLock: sync.Mutex{}, + } + proc.ReadCache(wci) + w.startWsConnect(wci, cancel, proc, data.Write) + proc.AddConn(gusetName, wci) + defer middle.ProcessWaitCond.Trigger() + defer proc.DeleteConn(gusetName) + conn.SetCloseHandler(func(_ int, _ string) error { + middle.ProcessWaitCond.Trigger() + cancel() + return nil + }) + middle.ProcessWaitCond.Trigger() + select { + case <-proc.StopChan: + log.Logger.Infow("ws连接断开", "操作类型", "进程已停止,强制断开ws连接") + case <-time.After(time.Minute * time.Duration(config.CF.TerminalConnectTimeout)): + log.Logger.Infow("ws连接断开", "操作类型", "连接时间超过最大时长限制") + case <-wsCtx.Done(): + log.Logger.Infow("ws连接断开", "操作类型", "tcp连接建立已被关闭") + case <-time.After(time.Until(data.ExpireTime)): + log.Logger.Infow("ws连接断开", "操作类型", "分享时间已结束") + } + conn.Close() +} + func (w *wsApi) startWsConnect(wci *WsConnetInstance, cancel context.CancelFunc, proc logic.Process, write bool) { log.Logger.Debugw("ws读取线程已启动") go func() { diff --git a/internal/app/model/process.go b/internal/app/model/process.go index 5c7deb7..bd80d4e 100644 --- a/internal/app/model/process.go +++ b/internal/app/model/process.go @@ -20,3 +20,9 @@ type Process struct { func (*Process) TableName() string { return "process" } + +type ProcessShare struct { + Minute int `json:"minute"` + Pid int `json:"pid"` + Write bool `json:"write"` +} diff --git a/internal/app/model/ws_share.go b/internal/app/model/ws_share.go new file mode 100644 index 0000000..59934f5 --- /dev/null +++ b/internal/app/model/ws_share.go @@ -0,0 +1,13 @@ +package model + +import ( + "time" +) + +type WsShare struct { + Id int `gorm:"primaryKey;autoIncrement;column:id" json:"id"` + Pid int `gorm:"column:pid" json:"pid"` + Write bool `gorm:"column:write" json:"write"` + ExpireTime time.Time `gorm:"column:expire_time" json:"expireTime"` + Token string `gorm:"column:token" json:"token"` +} diff --git a/internal/app/repository/ws_share.go b/internal/app/repository/ws_share.go new file mode 100644 index 0000000..f1b2481 --- /dev/null +++ b/internal/app/repository/ws_share.go @@ -0,0 +1,16 @@ +package repository + +import "github.com/lzh-1625/go_process_manager/internal/app/model" + +type wsShare struct{} + +var WsShare = new(wsShare) + +func (p *wsShare) GetWsShareDataByToken(token string) (data model.WsShare, err error) { + err = db.Model(&model.WsShare{}).Where("token = ?", token).First(&data).Error + return +} + +func (p *wsShare) AddShareData(data model.WsShare) error { + return db.Save(data).Error +} diff --git a/internal/app/route/route.go b/internal/app/route/route.go index 5b7a5cc..2511be4 100644 --- a/internal/app/route/route.go +++ b/internal/app/route/route.go @@ -56,7 +56,11 @@ func routePathInit(r *gin.Engine) { apiGroup.Use(middle.PanicMiddle()) // apiGroup.Use(middle.DemoMiddle()) { - apiGroup.GET("/ws", middle.OprPermission(constants.OPERATION_TERMINAL), api.WsApi.WebsocketHandle) + wsGroup := apiGroup.Group("/ws") + { + wsGroup.GET("", middle.OprPermission(constants.OPERATION_TERMINAL), api.WsApi.WebsocketHandle) + wsGroup.GET("/share", api.WsApi.WebsocketShareHandle) + } processGroup := apiGroup.Group("/process") { @@ -66,6 +70,7 @@ func routePathInit(r *gin.Engine) { processGroup.PUT("", middle.OprPermission(constants.OPERATION_START), api.ProcApi.StartProcess) processGroup.PUT("/all", api.ProcApi.StartAllProcess) processGroup.DELETE("/all", api.ProcApi.KillAllProcess) + processGroup.POST("/share", middle.RolePermission(constants.ROLE_ADMIN), api.ProcApi.ProcessCreateShare) processGroup.GET("/control", middle.RolePermission(constants.ROLE_ROOT), middle.ProcessWaitCond.WaitTriggerMiddel, api.ProcApi.ProcessControl) proConfigGroup := processGroup.Group("/config")