Merge branch 'refs/heads/feat-cmd'

This commit is contained in:
junleea 2025-03-03 15:17:56 +08:00
commit de0f97ce7d
8 changed files with 429 additions and 0 deletions

View File

@ -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
}

85
dao/shell.go Normal file
View File

@ -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
}

104
handler/shell.go Normal file
View File

@ -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})
}
}

View File

@ -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()
//定时任务
@ -197,6 +198,7 @@ func myTask() {
}
//其它定时任务-通用
RunGeneralCron()
service.ShellWillRunFromServer()
}

View File

@ -68,4 +68,9 @@ const (
UpdateConfigFailed = 91 // 更新配置失败
DeleteConfigFailed = 92 // 删除配置失败
SearchConfigFileFailed = 93 // 获取配置失败
ShellCreateFailed = 100 // 创建shell失败
ShellUpdateFailed = 101 // 更新shell失败
ShellDeleteFailed = 102 // 删除shell失败
ShellSearchFailed = 103 // 获取shell失败
)

View File

@ -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"`
}

103
service/shellService.go Normal file
View File

@ -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
}

View File

@ -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
}