From 7baaa5453a9c30a60719ea303b8b400170b96309 Mon Sep 17 00:00:00 2001 From: junleea <354425203@qq.com> Date: Sat, 22 Feb 2025 14:44:26 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E6=B7=BB=E5=8A=A0shell=E6=89=A7=E8=A1=8C?= =?UTF-8?q?=EF=BC=8C=E8=A2=AB=E5=8A=A8=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dao/db.go | 5 ++ dao/shell.go | 85 ++++++++++++++++++++++++++++++++ handler/shell.go | 104 ++++++++++++++++++++++++++++++++++++++++ main.go | 1 + proto/status.go | 5 ++ proto/user_req.go | 26 ++++++++++ service/shellService.go | 103 +++++++++++++++++++++++++++++++++++++++ worker/req.go | 99 ++++++++++++++++++++++++++++++++++++++ 8 files changed, 428 insertions(+) create mode 100644 dao/shell.go create mode 100644 handler/shell.go create mode 100644 service/shellService.go diff --git a/dao/db.go b/dao/db.go index 870e76d..61463eb 100644 --- a/dao/db.go +++ b/dao/db.go @@ -99,6 +99,11 @@ func Init() error { fmt.Println("friend table:", err) } + err = db.AutoMigrate(&Shell{}) + if err != nil { + fmt.Println("shell table:", err) + } + DB = db return err } diff --git a/dao/shell.go b/dao/shell.go new file mode 100644 index 0000000..362d3b0 --- /dev/null +++ b/dao/shell.go @@ -0,0 +1,85 @@ +package dao + +import ( + "gorm.io/gorm" + "log" + "videoplayer/proto" +) + +type Shell struct { + gorm.Model + AuthID uint `gorm:"column:auth_id"` + Server string `gorm:"column:server"` + ShellName string `gorm:"column:shell_name"` + ShellContent string `gorm:"column:shell_content"` + Status int `gorm:"column:status"` // 0 未执行 1 执行中 2 执行完成 + ShellResult string `gorm:"column:shell_result"` +} + +func CreateShell(shellName, shellContent, server string, uid uint) uint { + shell := Shell{ShellName: shellName, ShellContent: shellContent, Server: server, AuthID: uid} + var res *gorm.DB + if proto.Config.SERVER_SQL_LOG { + res = DB.Debug().Create(&shell) + } else { + res = DB.Create(&shell) + } + if res.Error != nil { + return 0 + } + return shell.ID +} + +func FindShellByID(id, uid uint) []Shell { + var shell Shell + var result *gorm.DB + + if proto.Config.SERVER_SQL_LOG { + result = DB.Debug().Where("id = ? & auth_id = ?", id, uid).First(&shell) + } else { + result = DB.Where("id = ? & auth_id = ?", id, uid).First(&shell) + } + var res []Shell + res = append(res, shell) + if result.Error != nil { + log.Fatalf("FindShellByID failed: %v", result.Error) + } + return res +} + +func FindShellByAuthID(auth_id int) []Shell { + var shells []Shell + if proto.Config.SERVER_SQL_LOG { + DB.Debug().Exec("select * from shells where auth_id = ? order by created_at desc limit 100", auth_id).Scan(&shells) + } else { + DB.Exec("select * from shells where auth_id = ? order by created_at desc limit 100", auth_id).Scan(&shells) + } + return shells +} + +func UpdateShellByID(id, authId uint, shellName, shellContent string, status int, shellResult string) bool { + pd := FindShellByID(id, authId) + if pd[0].ID == 0 { + return false + } + var result *gorm.DB + if proto.Config.SERVER_SQL_LOG { + result = DB.Debug().Model(&Shell{}).Where("id = ? and auth_id = ?", id, authId).Updates(Shell{ShellName: shellName, ShellContent: shellContent, Status: status, ShellResult: shellResult}) + } else { + result = DB.Model(&Shell{}).Where("id = ? and auth_id = ?", id, authId).Updates(Shell{ShellName: shellName, ShellContent: shellContent, Status: status, ShellResult: shellResult}) + } + if result.Error != nil { + return false + } + return true +} + +func FindShellWillRunByServer(server string, uid uint) []Shell { + var shells []Shell + if proto.Config.SERVER_SQL_LOG { + DB.Debug().Exec("select * from shells where server = ? and auth_id = ? and status = 0 order by created_at desc limit 100", server, uid).Scan(&shells) + } else { + DB.Exec("select * from shells where server = ? and auth_id = ? and status = 0 order by created_at desc limit 100", server, uid).Scan(&shells) + } + return shells +} diff --git a/handler/shell.go b/handler/shell.go new file mode 100644 index 0000000..428ddca --- /dev/null +++ b/handler/shell.go @@ -0,0 +1,104 @@ +package handler + +import ( + "github.com/gin-gonic/gin" + "videoplayer/proto" + "videoplayer/service" +) + +type ShellHandler struct { +} + +type CreateShellReq struct { + ShellName string `json:"shell_name"` + ShellContent string `json:"shell_content"` + Server string `json:"server"` +} +type UpdateShellReq struct { + ID uint `json:"id"` + ShellName string `json:"shell_name"` + ShellContent string `json:"shell_content"` + Server string `json:"server"` + Status int `json:"status"` + ShellResult string `json:"shell_result"` +} + +type UpdateShellReqV2 struct { + Shells []UpdateShellReq `json:"shells"` +} + +type UpdateShellResp struct { + ID uint `json:"id" form:"id"` + Status int `json:"status" form:"status"` +} + +func SetUpShellGroup(router *gin.Engine) { + shellGroup := router.Group("/shell") //持续集成、部署 + shellHandler := ShellHandler{} + shellGroup.POST("/create", shellHandler.CreateShell) + shellGroup.POST("/list", shellHandler.ListShell) + shellGroup.POST("/update", shellHandler.UpdateShell) + shellGroup.POST("/server_will_run_list", shellHandler.ServerWillRun) +} + +func (s *ShellHandler) CreateShell(c *gin.Context) { + user_id, _ := c.Get("id") + uid := int(user_id.(float64)) + var req CreateShellReq + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(200, gin.H{"code": proto.ShellCreateFailed, "message": "参数错误", "data": err.Error()}) + } else { + id := service.CreateShell(req.ShellName, req.ShellContent, req.Server, uid) + if id == 0 { + c.JSON(200, gin.H{"code": proto.ShellCreateFailed, "message": "创建失败,id is 0", "data": ""}) + return + } + c.JSON(200, gin.H{"code": proto.SuccessCode, "message": "创建成功", "data": id}) + } +} + +func (s *ShellHandler) ListShell(c *gin.Context) { + user_id, _ := c.Get("id") + id := int(user_id.(float64)) + shells := service.FindShellByAuthID(id) + c.JSON(200, gin.H{"code": proto.SuccessCode, "message": "获取成功", "data": shells}) +} + +func (s *ShellHandler) UpdateShell(c *gin.Context) { + var req UpdateShellReqV2 + user_id, _ := c.Get("id") + id := int(user_id.(float64)) + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(200, gin.H{"code": proto.ShellUpdateFailed, "message": "参数错误", "data": err.Error()}) + } else { + var resp []UpdateShellResp + for _, v := range req.Shells { + if service.UpdateShellByID(v.ID, uint(id), v.ShellName, v.ShellContent, v.Server, v.Status, v.ShellResult) { + resp = append(resp, UpdateShellResp{ID: v.ID, Status: v.Status}) + } else { + resp = append(resp, UpdateShellResp{ID: v.ID, Status: -1}) + } + } + c.JSON(200, gin.H{"code": proto.SuccessCode, "message": "更新成功", "data": resp}) + } +} + +type ServerWillRunReq struct { + Server string `json:"server"` +} + +func (s *ShellHandler) ServerWillRun(c *gin.Context) { + user_id, _ := c.Get("id") + id := int(user_id.(float64)) + var req ServerWillRunReq + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(200, gin.H{"code": proto.ShellUpdateFailed, "message": "参数错误", "data": err.Error()}) + } else { + willRunShells, err2 := service.FindShellWillRunByServer(req.Server, id) + if err2 != nil { + c.JSON(200, gin.H{"code": proto.ShellUpdateFailed, "message": "获取失败", "data": err2.Error()}) + return + } + c.JSON(200, gin.H{"code": proto.SuccessCode, "message": "获取成功", "data": willRunShells}) + } +} diff --git a/main.go b/main.go index 751a876..e680cf7 100644 --- a/main.go +++ b/main.go @@ -197,6 +197,7 @@ func myTask() { } //其它定时任务-通用 RunGeneralCron() + service.ShellWillRunFromServer() } diff --git a/proto/status.go b/proto/status.go index 3944848..a4e005a 100644 --- a/proto/status.go +++ b/proto/status.go @@ -68,4 +68,9 @@ const ( UpdateConfigFailed = 91 // 更新配置失败 DeleteConfigFailed = 92 // 删除配置失败 SearchConfigFileFailed = 93 // 获取配置失败 + + ShellCreateFailed = 100 // 创建shell失败 + ShellUpdateFailed = 101 // 更新shell失败 + ShellDeleteFailed = 102 // 删除shell失败 + ShellSearchFailed = 103 // 获取shell失败 ) diff --git a/proto/user_req.go b/proto/user_req.go index 9d06f03..61875d6 100644 --- a/proto/user_req.go +++ b/proto/user_req.go @@ -84,3 +84,29 @@ type SyncUserReq struct { Device string `json:"device" form:"device"` Confirm UserSyncConfirm `json:"confirm" form:"confirm"` } + +// shell待执行 +type SyncUserShellReq struct { + Token string `json:"token" form:"token"` + Server string `json:"server" form:"server"` +} + +type UpdateShellReq struct { + ID uint `json:"id"` + ShellName string `json:"shell_name"` + ShellContent string `json:"shell_content"` + Server string `json:"server"` + Status int `json:"status"` + ShellResult string `json:"shell_result"` +} + +// shell 执行结果返回 +type SyncUserShellResp struct { + Token string `json:"token" form:"token"` + Shells []UpdateShellReq `json:"shells" form:"shells"` +} + +type UpdateShellRespV2 struct { + ID uint `json:"id" form:"id"` + Status int `json:"status" form:"status"` +} diff --git a/service/shellService.go b/service/shellService.go new file mode 100644 index 0000000..8ee45e1 --- /dev/null +++ b/service/shellService.go @@ -0,0 +1,103 @@ +package service + +import ( + "bytes" + "errors" + "log" + "os/exec" + "strings" + "videoplayer/dao" + "videoplayer/proto" + "videoplayer/worker" +) + +func CreateShell(shellName, shellContent, server string, uid int) uint { + id := dao.CreateShell(shellName, shellContent, server, uint(uid)) + return id +} + +func FindShellByAuthID(id int) []dao.Shell { + return dao.FindShellByAuthID(id) +} + +func UpdateShellByID(id, authId uint, shellName, shellContent, server string, status int, shellResult string) bool { + return dao.UpdateShellByID(id, authId, shellName, shellContent, status, shellResult) +} + +func FindShellWillRunByServer(server string, uid int) ([]dao.Shell, error) { + var shells []dao.Shell + var err error + if server == "" { + //err设置为server为空 + err = errors.New("server is empty") + return shells, err + } + shells = dao.FindShellWillRunByServer(server, uint(uid)) + //设置状态为执行中 + for _, v := range shells { + dao.UpdateShellByID(v.ID, uint(uid), v.ShellName, v.ShellContent, v.Status+1, v.ShellResult) + } + return shells, err +} + +// 从服务器定时获取shell、执行并返回结果 +func ShellWillRunFromServer() { + shells, err := GetShellWillRunFromMaster(proto.Config.SERVER_NAME) + if err != nil { + return + } + var resp []proto.UpdateShellReq + + for _, v := range shells { + //执行shell脚本,go执行命令 + res, err2 := RunShell(v.ShellContent) + if err2 != "" { + resp = append(resp, proto.UpdateShellReq{ID: v.ID, Server: v.Server, Status: 2, ShellResult: err2}) + } else { + resp = append(resp, proto.UpdateShellReq{ID: v.ID, Server: v.Server, Status: 2, ShellResult: res}) + } + } + //返回执行结果 + url := "https://" + proto.Config.MASTER_SERVER_DOMAIN + "/shell/update" + var req proto.SyncUserShellResp + req.Token = worker.GetRedisSetMembers("super_permission_tokens")[0] + req.Shells = resp + resp_data, err := worker.SyncDataFromMasterShellReq3(url, req) + if err != nil { + return + } + //更新执行结果 + for _, v := range resp_data { + if v.Status < 0 { + log.Fatalln("update shell failed:", v.ID, v.Status) + } + } +} + +// 从服务器从主服务器获取待执行的shell +func GetShellWillRunFromMaster(server string) ([]dao.Shell, error) { + master := proto.Config.MASTER_SERVER_DOMAIN + //发起请求获取待执行的shell + url := "https://" + master + "/shell//server_will_run_list" + var req proto.SyncUserShellReq + req.Server = server + req.Token = worker.GetRedisSetMembers("super_permission_tokens")[0] + shells, err := worker.SyncDataFromMasterShellReq2(url, req) + if err != nil { + return nil, err + } + return shells, nil +} + +func RunShell(script string) (res, err string) { + cmd := exec.Command("/bin/bash", "-c", script) + // 使用bytes.Buffer捕获输出 + var out bytes.Buffer + cmd.Stdout = &out + err3 := cmd.Run() + err3_info := "" + if err3 != nil { + err3_info = err3.Error() + } + return strings.TrimSpace(out.String()), err3_info +} diff --git a/worker/req.go b/worker/req.go index a8a30a2..85b707e 100644 --- a/worker/req.go +++ b/worker/req.go @@ -8,6 +8,7 @@ import ( "io/ioutil" "net/http" "strings" + "videoplayer/dao" "videoplayer/proto" ) @@ -135,6 +136,18 @@ type Response struct { Data proto.UserSync `json:"data"` } +type ShellResponse struct { + Code int `json:"code"` + Message string `json:"message"` + Data []dao.Shell `json:"data"` +} + +type ShellResponseV2 struct { + Code int `json:"code"` + Message string `json:"message"` + Data []proto.UpdateShellRespV2 `json:"data"` +} + // 获取数据,全量及增量 func SyncDataFromMasterReq2(url string, data proto.SyncUserReq) (proto.UserSync, error) { defer func() { @@ -178,3 +191,89 @@ func SyncDataFromMasterReq2(url string, data proto.SyncUserReq) (proto.UserSync, fmt.Println("SyncDataFromMasterReq2 result add data:", len(res.Add), "update data:", len(res.Update), "delete data:", len(res.Delete)) return res, nil } + +// 获取待执行的shell +func SyncDataFromMasterShellReq2(url string, data proto.SyncUserShellReq) ([]dao.Shell, error) { + defer func() { + if r := recover(); r != nil { + fmt.Println("SyncDataFromMasterReq2 error:", r) + } + }() + + var res []dao.Shell + //从接口获取数据 + json_data, err := json.Marshal(data) + if err != nil { + return res, err + } + req, err := http.NewRequest("POST", url, bytes.NewBuffer(json_data)) + if err != nil { + return res, err + } + req.Header.Set("Content-Type", "application/json") + //传输数据 + if client == nil { + client = &http.Client{} + } + //获取数据 + resp, err := client.Do(req) + if err != nil { + return res, err + } + defer resp.Body.Close() + //解析数据 + responseBod, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + var response ShellResponse + err = json.Unmarshal(responseBod, &response) + if err != nil { + return res, err + } + res = response.Data + return res, nil +} + +// 获取待执行的shell +func SyncDataFromMasterShellReq3(url string, data proto.SyncUserShellResp) ([]proto.UpdateShellRespV2, error) { + defer func() { + if r := recover(); r != nil { + fmt.Println("SyncDataFromMasterReq2 error:", r) + } + }() + + var res []proto.UpdateShellRespV2 + //从接口获取数据 + json_data, err := json.Marshal(data) + if err != nil { + return res, err + } + req, err := http.NewRequest("POST", url, bytes.NewBuffer(json_data)) + if err != nil { + return res, err + } + req.Header.Set("Content-Type", "application/json") + //传输数据 + if client == nil { + client = &http.Client{} + } + //获取数据 + resp, err := client.Do(req) + if err != nil { + return res, err + } + defer resp.Body.Close() + //解析数据 + responseBod, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + var response ShellResponseV2 + err = json.Unmarshal(responseBod, &response) + if err != nil { + return res, err + } + res = response.Data + return res, nil +} From e34d29b0a4a3b65f092d2f072e7cd74305bc3d7a Mon Sep 17 00:00:00 2001 From: junleea <354425203@qq.com> Date: Sat, 22 Feb 2025 14:44:53 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E6=B7=BB=E5=8A=A0shell=E6=89=A7=E8=A1=8C?= =?UTF-8?q?=EF=BC=8C=E8=A2=AB=E5=8A=A8=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.go | 1 + 1 file changed, 1 insertion(+) diff --git a/main.go b/main.go index e680cf7..c554bec 100644 --- a/main.go +++ b/main.go @@ -39,6 +39,7 @@ func main() { handler.SetUpCIDGroup(r) // CID,持续集成、部署 handler.SetUpToolGroup(r) // Tool handler.SetUpFileGroup(r) // File + handler.SetUpShellGroup(r) // Shell defer dao.Close() defer worker.CloseRedis() //定时任务