feat: 前端升级至vue3,后端代码结构重构,新增权限管理相关功能

This commit is contained in:
meilin.huang
2021-06-07 17:22:07 +08:00
parent af0d51293e
commit e9b58b4eab
370 changed files with 22339 additions and 18399 deletions

140
server/devops/apis/db.go Normal file
View File

@@ -0,0 +1,140 @@
package apis
import (
"fmt"
"mayfly-go/base/biz"
"mayfly-go/base/ctx"
"mayfly-go/base/ginx"
"mayfly-go/base/model"
"mayfly-go/server/devops/apis/form"
"mayfly-go/server/devops/apis/vo"
"mayfly-go/server/devops/application"
"mayfly-go/server/devops/domain/entity"
"strconv"
"strings"
"github.com/gin-gonic/gin"
)
type Db struct {
DbApp application.IDb
}
// @router /api/dbs [get]
func (d *Db) Dbs(rc *ctx.ReqCtx) {
m := new(entity.Db)
rc.ResData = d.DbApp.GetPageList(m, ginx.GetPageParam(rc.GinCtx), new([]vo.SelectDataDbVO))
}
// @router /api/db/:dbId/select [get]
func (d *Db) SelectData(rc *ctx.ReqCtx) {
g := rc.GinCtx
// 去除前后空格及换行符
selectSql := strings.TrimFunc(g.Query("selectSql"), func(r rune) bool {
s := string(r)
return s == " " || s == "\n"
})
rc.ReqParam = selectSql
biz.NotEmpty(selectSql, "selectSql不能为空")
res, err := d.DbApp.GetDbInstance(GetDbId(g)).SelectData(selectSql)
if err != nil {
panic(biz.NewBizErr(fmt.Sprintf("查询失败: %s", err.Error())))
}
rc.ResData = res
}
// @router /api/db/:dbId/exec-sql [post]
func (d *Db) ExecSql(g *gin.Context) {
rc := ctx.NewReqCtxWithGin(g).WithLog(ctx.NewLogInfo("sql执行"))
rc.Handle(func(rc *ctx.ReqCtx) {
selectSql := g.Query("sql")
biz.NotEmpty(selectSql, "sql不能为空")
num, err := d.DbApp.GetDbInstance(GetDbId(rc.GinCtx)).Exec(selectSql)
if err != nil {
panic(biz.NewBizErr(fmt.Sprintf("执行失败: %s", err.Error())))
}
rc.ResData = num
})
}
// @router /api/db/:dbId/t-metadata [get]
func (d *Db) TableMA(rc *ctx.ReqCtx) {
rc.ResData = d.DbApp.GetDbInstance(GetDbId(rc.GinCtx)).GetTableMetedatas()
}
// @router /api/db/:dbId/c-metadata [get]
func (d *Db) ColumnMA(rc *ctx.ReqCtx) {
g := rc.GinCtx
tn := g.Query("tableName")
biz.NotEmpty(tn, "tableName不能为空")
rc.ResData = d.DbApp.GetDbInstance(GetDbId(rc.GinCtx)).GetColumnMetadatas(tn)
}
// @router /api/db/:dbId/hint-tables [get]
func (d *Db) HintTables(rc *ctx.ReqCtx) {
dbi := d.DbApp.GetDbInstance(GetDbId(rc.GinCtx))
tables := dbi.GetTableMetedatas()
res := make(map[string][]string)
for _, v := range tables {
tableName := v["tableName"]
columnMds := dbi.GetColumnMetadatas(tableName)
columnNames := make([]string, len(columnMds))
for i, v := range columnMds {
comment := v["columnComment"]
if comment != "" {
columnNames[i] = v["columnName"] + " [" + comment + "]"
} else {
columnNames[i] = v["columnName"]
}
}
res[tableName] = columnNames
}
rc.ResData = res
}
// @router /api/db/:dbId/sql [post]
func (d *Db) SaveSql(rc *ctx.ReqCtx) {
g := rc.GinCtx
account := rc.LoginAccount
dbSqlForm := &form.DbSqlSaveForm{}
ginx.BindJsonAndValid(g, dbSqlForm)
rc.ReqParam = dbSqlForm
dbId := GetDbId(g)
// 判断dbId是否存在
err := model.GetById(new(entity.Db), dbId)
biz.ErrIsNil(err, "该数据库信息不存在")
// 获取用于是否有该dbsql的保存记录有则更改否则新增
dbSql := &entity.DbSql{Type: dbSqlForm.Type, DbId: dbId}
dbSql.CreatorId = account.Id
e := model.GetBy(dbSql)
dbSql.SetBaseInfo(account)
// 更新sql信息
dbSql.Sql = dbSqlForm.Sql
if e == nil {
model.UpdateById(dbSql)
} else {
model.Insert(dbSql)
}
}
// @router /api/db/:dbId/sql [get]
func (d *Db) GetSql(rc *ctx.ReqCtx) {
// 获取用于是否有该dbsql的保存记录有则更改否则新增
dbSql := &entity.DbSql{Type: 1, DbId: GetDbId(rc.GinCtx)}
dbSql.CreatorId = rc.LoginAccount.Id
e := model.GetBy(dbSql)
if e != nil {
return
}
rc.ResData = dbSql
}
func GetDbId(g *gin.Context) uint64 {
dbId, _ := strconv.Atoi(g.Param("dbId"))
biz.IsTrue(dbId > 0, "dbId错误")
return uint64(dbId)
}

View File

@@ -0,0 +1,46 @@
package form
type MachineForm struct {
Id uint64 `json:"id"`
Name string `json:"name" valid:"Required"`
// IP地址
Ip string `json:"ip" valid:"Required"`
// 用户名
Username string `json:"username" valid:"Required"`
Password string `json:"password" valid:"Required"`
// 端口号
Port int `json:"port" valid:"Required"`
}
type MachineRunForm struct {
MachineId int64 `valid:"Required"`
Cmd string `valid:"Required"`
}
type MachineFileForm struct {
Id uint64
Name string `valid:"Required"`
MachineId uint64 `valid:"Required"`
Type int `valid:"Required"`
Path string `valid:"Required"`
}
type MachineScriptForm struct {
Id uint64
Name string `valid:"Required"`
MachineId uint64 `valid:"Required"`
Type int `valid:"Required"`
Description string `valid:"Required"`
Script string `valid:"Required"`
}
type DbSqlSaveForm struct {
Sql string `valid:"Required"`
Type int `valid:"Required"`
}
type MachineFileUpdateForm struct {
Content string `valid:"Required"`
Id uint64 `valid:"Required"`
Path string `valid:"Required"`
}

View File

@@ -0,0 +1,90 @@
package apis
import (
"mayfly-go/base/biz"
"mayfly-go/base/ctx"
"mayfly-go/base/ginx"
"mayfly-go/base/utils"
"mayfly-go/server/devops/apis/form"
"mayfly-go/server/devops/apis/vo"
"mayfly-go/server/devops/application"
"mayfly-go/server/devops/domain/entity"
"mayfly-go/server/devops/infrastructure/machine"
"net/http"
"strconv"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
)
var WsUpgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024 * 1024 * 10,
CheckOrigin: func(r *http.Request) bool {
return true
},
}
type Machine struct {
MachineApp application.IMachine
}
func (m *Machine) Machines(rc *ctx.ReqCtx) {
rc.ResData = m.MachineApp.GetMachineList(new(entity.Machine), ginx.GetPageParam(rc.GinCtx), new([]vo.MachineVO))
}
func (m *Machine) SaveMachine(rc *ctx.ReqCtx) {
g := rc.GinCtx
machineForm := new(form.MachineForm)
ginx.BindJsonAndValid(g, machineForm)
entity := new(entity.Machine)
utils.Copy(entity, machineForm)
entity.SetBaseInfo(rc.LoginAccount)
m.MachineApp.Save(entity)
}
// top命令信息
func (m *Machine) Top(rc *ctx.ReqCtx) {
rc.ResData = m.MachineApp.GetCli(GetMachineId(rc.GinCtx)).GetTop()
}
func (m *Machine) WsSSH(g *gin.Context) {
wsConn, err := WsUpgrader.Upgrade(g.Writer, g.Request, nil)
defer func() {
if err := recover(); err != nil {
wsConn.WriteMessage(websocket.TextMessage, []byte(err.(error).Error()))
wsConn.Close()
}
}()
if err != nil {
panic(biz.NewBizErr("升级websocket失败"))
}
// 权限校验
if err = ctx.PermissionHandler(ctx.NewReqCtxWithGin(g)); err != nil {
panic(biz.NewBizErr("没有权限"))
}
cols := ginx.QueryInt(g, "cols", 80)
rows := ginx.QueryInt(g, "rows", 40)
sws, err := machine.NewLogicSshWsSession(cols, rows, m.MachineApp.GetCli(GetMachineId(g)), wsConn)
if sws == nil {
panic(biz.NewBizErr("连接失败"))
}
defer sws.Close()
quitChan := make(chan bool, 3)
sws.Start(quitChan)
go sws.Wait(quitChan)
<-quitChan
}
func GetMachineId(g *gin.Context) uint64 {
machineId, _ := strconv.Atoi(g.Param("machineId"))
biz.IsTrue(machineId != 0, "machineId错误")
return uint64(machineId)
}

View File

@@ -0,0 +1,154 @@
package apis
import (
"fmt"
"io/fs"
"io/ioutil"
"mayfly-go/base/biz"
"mayfly-go/base/ctx"
"mayfly-go/base/ginx"
"mayfly-go/base/utils"
"mayfly-go/server/devops/apis/form"
"mayfly-go/server/devops/apis/vo"
"mayfly-go/server/devops/application"
"mayfly-go/server/devops/domain/entity"
"strconv"
"strings"
"github.com/gin-gonic/gin"
)
type MachineFile struct {
MachineFileApp application.IMachineFile
MachineApp application.IMachine
}
const (
file = "-"
dir = "d"
link = "l"
max_read_size = 1 * 1024 * 1024
)
func (m *MachineFile) MachineFiles(rc *ctx.ReqCtx) {
g := rc.GinCtx
condition := &entity.MachineFile{MachineId: GetMachineId(g)}
rc.ResData = m.MachineFileApp.GetPageList(condition, ginx.GetPageParam(g), new([]vo.MachineFileVO))
}
func (m *MachineFile) SaveMachineFiles(rc *ctx.ReqCtx) {
g := rc.GinCtx
fileForm := new(form.MachineFileForm)
ginx.BindJsonAndValid(g, fileForm)
entity := new(entity.MachineFile)
utils.Copy(entity, fileForm)
entity.SetBaseInfo(rc.LoginAccount)
m.MachineFileApp.Save(entity)
}
func (m *MachineFile) DeleteFile(rc *ctx.ReqCtx) {
g := rc.GinCtx
fid := GetMachineFileId(g)
m.MachineFileApp.Delete(fid)
}
/*** sftp相关操作 */
func (m *MachineFile) ReadFileContent(rc *ctx.ReqCtx) {
g := rc.GinCtx
fid := GetMachineFileId(g)
readPath := g.Query("path")
readType := g.Query("type")
dataByte, fileInfo := m.MachineFileApp.ReadFile(fid, readPath)
// 如果是读取文件内容,则校验文件大小
if readType != "1" {
biz.IsTrue(fileInfo.Size() < max_read_size, "文件超过1m请使用下载查看")
}
rc.ReqParam = fmt.Sprintf("path: %s", readPath)
// 如果读取类型为下载,则下载文件,否则获取文件内容
if readType == "1" {
// 截取文件名,如/usr/local/test.java -》 test.java
path := strings.Split(readPath, "/")
rc.Download(dataByte, path[len(path)-1])
} else {
rc.ResData = string(dataByte)
}
}
func (m *MachineFile) GetDirEntry(rc *ctx.ReqCtx) {
g := rc.GinCtx
fid := GetMachineFileId(g)
readPath := g.Query("path")
if !strings.HasSuffix(readPath, "/") {
readPath = readPath + "/"
}
fis := m.MachineFileApp.ReadDir(fid, readPath)
fisVO := make([]vo.MachineFileInfo, 0)
for _, fi := range fis {
fisVO = append(fisVO, vo.MachineFileInfo{
Name: fi.Name(),
Size: fi.Size(),
Path: readPath + fi.Name(),
Type: getFileType(fi.Mode()),
})
}
rc.ResData = fisVO
rc.ReqParam = fmt.Sprintf("path: %s", readPath)
}
func (m *MachineFile) WriteFileContent(rc *ctx.ReqCtx) {
g := rc.GinCtx
fid := GetMachineFileId(g)
form := new(form.MachineFileUpdateForm)
ginx.BindJsonAndValid(g, form)
path := form.Path
m.MachineFileApp.WriteFileContent(fid, path, []byte(form.Content))
rc.ReqParam = fmt.Sprintf("path: %s", path)
}
func (m *MachineFile) UploadFile(rc *ctx.ReqCtx) {
g := rc.GinCtx
fid := GetMachineFileId(g)
path := g.PostForm("path")
fileheader, err := g.FormFile("file")
biz.ErrIsNilAppendErr(err, "读取文件失败: %s")
file, _ := fileheader.Open()
bytes, err := ioutil.ReadAll(file)
go m.MachineFileApp.UploadFile(fid, path, fileheader.Filename, bytes)
rc.ReqParam = fmt.Sprintf("path: %s", path)
}
func (m *MachineFile) RemoveFile(rc *ctx.ReqCtx) {
g := rc.GinCtx
fid := GetMachineFileId(g)
// mid := GetMachineId(g)
path := g.Query("path")
m.MachineFileApp.RemoveFile(fid, path)
rc.ReqParam = fmt.Sprintf("path: %s", path)
}
func getFileType(fm fs.FileMode) string {
if fm.IsDir() {
return dir
}
return file
}
func GetMachineFileId(g *gin.Context) uint64 {
fileId, _ := strconv.Atoi(g.Param("fileId"))
biz.IsTrue(fileId != 0, "fileId错误")
return uint64(fileId)
}

View File

@@ -0,0 +1,74 @@
package apis
import (
"fmt"
"mayfly-go/base/biz"
"mayfly-go/base/ctx"
"mayfly-go/base/ginx"
"mayfly-go/base/utils"
"mayfly-go/server/devops/apis/form"
"mayfly-go/server/devops/apis/vo"
"mayfly-go/server/devops/application"
"mayfly-go/server/devops/domain/entity"
"strconv"
"github.com/gin-gonic/gin"
)
type MachineScript struct {
MachineScriptApp application.IMachineScript
MachineApp application.IMachine
}
func (m *MachineScript) MachineScripts(rc *ctx.ReqCtx) {
g := rc.GinCtx
condition := &entity.MachineScript{MachineId: GetMachineId(g)}
rc.ResData = m.MachineScriptApp.GetPageList(condition, ginx.GetPageParam(g), new([]vo.MachineScriptVO))
}
func (m *MachineScript) SaveMachineScript(rc *ctx.ReqCtx) {
form := new(form.MachineScriptForm)
ginx.BindJsonAndValid(rc.GinCtx, form)
rc.ReqParam = form
// 转换为entity并设置基本信息
machineScript := new(entity.MachineScript)
utils.Copy(machineScript, form)
machineScript.SetBaseInfo(rc.LoginAccount)
m.MachineScriptApp.Save(machineScript)
}
func (m *MachineScript) DeleteMachineScript(rc *ctx.ReqCtx) {
msa := m.MachineScriptApp
sid := GetMachineScriptId(rc.GinCtx)
ms := msa.GetById(sid)
biz.NotNil(ms, "该脚本不存在")
rc.ReqParam = fmt.Sprintf("[scriptId: %d, name: %s, desc: %s, script: %s]", sid, ms.Name, ms.Description, ms.Script)
msa.Delete(sid)
}
func (m *MachineScript) RunMachineScript(rc *ctx.ReqCtx) {
g := rc.GinCtx
scriptId := GetMachineScriptId(g)
machineId := GetMachineId(g)
ms := m.MachineScriptApp.GetById(scriptId, "MachineId", "Name", "Script")
biz.NotNil(ms, "该脚本不存在")
biz.IsTrue(ms.MachineId == application.Common_Script_Machine_Id || ms.MachineId == machineId, "该脚本不属于该机器")
vars := g.QueryMap("params")
res, err := m.MachineApp.GetCli(machineId).Run(utils.TemplateParse(ms.Script, vars))
// 记录请求参数
rc.ReqParam = fmt.Sprintf("[machineId: %d, scriptId: %d, name: %s]", machineId, scriptId, ms.Name)
if err != nil {
panic(biz.NewBizErr(fmt.Sprintf("执行命令失败:%s", err.Error())))
}
rc.ResData = res
}
func GetMachineScriptId(g *gin.Context) uint64 {
scriptId, _ := strconv.Atoi(g.Param("scriptId"))
biz.IsTrue(scriptId > 0, "scriptId错误")
return uint64(scriptId)
}

View File

@@ -0,0 +1,16 @@
package vo
import "time"
type SelectDataDbVO struct {
//models.BaseModel
Id *int64 `json:"id"`
Name *string `json:"name"`
Host *string `json:"host"`
Port *int `json:"port"`
Type *string `json:"type"`
Database *string `json:"database"`
CreateTime *time.Time `json:"createTime"`
Creator *string `json:"creator"`
CreatorId *int64 `json:"creatorId"`
}

View File

@@ -0,0 +1,58 @@
package vo
import "time"
type AccountVO struct {
//models.BaseModel
Id *int64 `json:"id"`
Username *string `json:"username"`
CreateTime *string `json:"createTime"`
Creator *string `json:"creator"`
CreatorId *int64 `json:"creatorId"`
// Role *RoleVO `json:"roles"`
//Status int8 `json:"status"`
}
type MachineVO struct {
//models.BaseModel
Id *int64 `json:"id"`
Name *string `json:"name"`
Username *string `json:"username"`
Ip *string `json:"ip"`
Port *int `json:"port"`
CreateTime *time.Time `json:"createTime"`
Creator *string `json:"creator"`
CreatorId *int64 `json:"creatorId"`
UpdateTime *time.Time `json:"updateTime"`
Modifier *string `json:"modifier"`
ModifierId *int64 `json:"modifierId"`
}
type MachineScriptVO struct {
Id *int64 `json:"id"`
Name *string `json:"name"`
Script *string `json:"script"`
Type *int `json:"type"`
Description *string `json:"description"`
MachineId *uint64 `json:"machineId"`
}
type MachineFileVO struct {
Id *int64 `json:"id"`
Name *string `json:"name"`
Path *string `json:"path"`
Type *int `json:"type"`
MachineId *uint64 `json:"machineId"`
}
type MachineFileInfo struct {
Name string `json:"name"`
Path string `json:"path"`
Size int64 `json:"size"`
Type string `json:"type"`
}
type RoleVO struct {
Id *int64
Name *string
}

View File

@@ -0,0 +1,55 @@
package application
import (
"mayfly-go/base/model"
"mayfly-go/server/devops/domain/entity"
"mayfly-go/server/devops/domain/repository"
"mayfly-go/server/devops/infrastructure/db"
"mayfly-go/server/devops/infrastructure/persistence"
)
type IDb interface {
// 分页获取机器脚本信息列表
GetPageList(condition *entity.Db, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) model.PageResult
// 根据条件获取
GetDbBy(condition *entity.Db, cols ...string) error
// 根据id获取
GetById(id uint64, cols ...string) *entity.Db
Save(entity *entity.Db)
GetDbInstance(id uint64) *db.DbInstance
}
type dbApp struct {
dbRepo repository.Db
}
var Db IDb = &dbApp{dbRepo: persistence.DbDao}
// 分页获取数据库信息列表
func (d *dbApp) GetPageList(condition *entity.Db, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) model.PageResult {
return d.dbRepo.GetDbList(condition, pageParam, toEntity, orderBy...)
}
// 根据条件获取
func (d *dbApp) GetDbBy(condition *entity.Db, cols ...string) error {
return d.dbRepo.GetDb(condition, cols...)
}
// 根据id获取
func (d *dbApp) GetById(id uint64, cols ...string) *entity.Db {
return d.dbRepo.GetById(id, cols...)
}
func (d *dbApp) Save(entity *entity.Db) {
}
func (d *dbApp) GetDbInstance(id uint64) *db.DbInstance {
return db.GetDbInstance(id, func(id uint64) *entity.Db {
return d.dbRepo.GetById(id)
})
}

View File

@@ -0,0 +1,64 @@
package application
import (
"mayfly-go/base/biz"
"mayfly-go/base/model"
"mayfly-go/server/devops/domain/entity"
"mayfly-go/server/devops/domain/repository"
"mayfly-go/server/devops/infrastructure/machine"
"mayfly-go/server/devops/infrastructure/persistence"
)
type IMachine interface {
// 根据条件获取账号信息
GetMachine(condition *entity.Machine, cols ...string) error
Save(entity *entity.Machine)
// 根据id获取
GetById(id uint64, cols ...string) *entity.Machine
// 分页获取机器信息列表
GetMachineList(condition *entity.Machine, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) model.PageResult
// 获取机器连接
GetCli(id uint64) *machine.Cli
}
type machineApp struct {
machineRepo repository.Machine
}
var Machine IMachine = &machineApp{machineRepo: persistence.MachineDao}
// 分页获取机器信息列表
func (m *machineApp) GetMachineList(condition *entity.Machine, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) model.PageResult {
return m.machineRepo.GetMachineList(condition, pageParam, toEntity, orderBy...)
}
// 根据条件获取机器信息
func (m *machineApp) Save(entity *entity.Machine) {
biz.ErrIsNil(machine.TestConn(entity), "该机器无法连接")
if entity.Id != 0 {
m.machineRepo.UpdateById(entity)
} else {
m.machineRepo.Create(entity)
}
}
// 根据条件获取机器信息
func (m *machineApp) GetMachine(condition *entity.Machine, cols ...string) error {
return m.machineRepo.GetMachine(condition, cols...)
}
func (m *machineApp) GetById(id uint64, cols ...string) *entity.Machine {
return m.machineRepo.GetById(id, cols...)
}
func (m *machineApp) GetCli(id uint64) *machine.Cli {
cli, err := machine.GetCli(id, func(machineId uint64) *entity.Machine {
return m.GetById(machineId)
})
biz.ErrIsNil(err, "获取客户端错误")
return cli
}

View File

@@ -0,0 +1,183 @@
package application
import (
"io"
"io/fs"
"io/ioutil"
"mayfly-go/base/biz"
"mayfly-go/base/model"
"mayfly-go/server/devops/domain/entity"
"mayfly-go/server/devops/domain/repository"
"mayfly-go/server/devops/infrastructure/persistence"
"os"
"strings"
"github.com/pkg/sftp"
)
type IMachineFile interface {
// 分页获取机器文件信息列表
GetPageList(condition *entity.MachineFile, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) model.PageResult
// 根据条件获取
GetMachineFile(condition *entity.MachineFile, cols ...string) error
// 根据id获取
GetById(id uint64, cols ...string) *entity.MachineFile
Save(entity *entity.MachineFile)
Delete(id uint64)
/** sftp 相关操作 **/
// 读取目录
ReadDir(fid uint64, path string) []fs.FileInfo
// 读取文件内容
ReadFile(fileId uint64, path string) ([]byte, fs.FileInfo)
// 写文件
WriteFileContent(fileId uint64, path string, content []byte)
// 文件上传
UploadFile(fileId uint64, path, filename string, content []byte)
// 移除文件
RemoveFile(fileId uint64, path string)
}
type machineFileApp struct {
machineFileRepo repository.MachineFile
machineRepo repository.Machine
}
// 实现类单例
var MachineFile IMachineFile = &machineFileApp{
machineRepo: persistence.MachineDao,
machineFileRepo: persistence.MachineFileDao,
}
// 分页获取机器脚本信息列表
func (m *machineFileApp) GetPageList(condition *entity.MachineFile, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) model.PageResult {
return m.machineFileRepo.GetPageList(condition, pageParam, toEntity, orderBy...)
}
// 根据条件获取
func (m *machineFileApp) GetMachineFile(condition *entity.MachineFile, cols ...string) error {
return m.machineFileRepo.GetMachineFile(condition, cols...)
}
// 根据id获取
func (m *machineFileApp) GetById(id uint64, cols ...string) *entity.MachineFile {
return m.machineFileRepo.GetById(id, cols...)
}
// 保存机器文件配置
func (m *machineFileApp) Save(entity *entity.MachineFile) {
biz.NotNil(m.machineRepo.GetById(entity.MachineId, "Name"), "该机器不存在")
if entity.Id != 0 {
model.UpdateById(entity)
} else {
model.Insert(entity)
}
}
// 根据id删除
func (m *machineFileApp) Delete(id uint64) {
m.machineFileRepo.Delete(id)
}
func (m *machineFileApp) ReadDir(fid uint64, path string) []fs.FileInfo {
path, machineId := m.checkAndReturnPathMid(fid, path)
if !strings.HasSuffix(path, "/") {
path = path + "/"
}
sftpCli := m.getSftpCli(machineId)
fis, err := sftpCli.ReadDir(path)
biz.ErrIsNilAppendErr(err, "读取目录失败: %s")
return fis
}
func (m *machineFileApp) ReadFile(fileId uint64, path string) ([]byte, fs.FileInfo) {
path, machineId := m.checkAndReturnPathMid(fileId, path)
sftpCli := m.getSftpCli(machineId)
// 读取文件内容
fc, err := sftpCli.Open(path)
biz.ErrIsNilAppendErr(err, "打开文件失败:%s")
defer fc.Close()
fileInfo, _ := fc.Stat()
biz.IsTrue(!fileInfo.IsDir(), "该文件为目录")
dataByte, err := ioutil.ReadAll(fc)
if err != nil && err != io.EOF {
panic(biz.NewBizErr("读取文件内容失败"))
}
return dataByte, fileInfo
}
// 写文件内容
func (m *machineFileApp) WriteFileContent(fileId uint64, path string, content []byte) {
_, machineId := m.checkAndReturnPathMid(fileId, path)
sftpCli := m.getSftpCli(machineId)
f, err := sftpCli.OpenFile(path, os.O_WRONLY|os.O_TRUNC|os.O_CREATE|os.O_RDWR)
biz.ErrIsNilAppendErr(err, "打开文件失败: %s")
defer f.Close()
fi, _ := f.Stat()
biz.IsTrue(!fi.IsDir(), "该路径不是文件")
f.Write(content)
}
// 上传文件
func (m *machineFileApp) UploadFile(fileId uint64, path, filename string, content []byte) {
path, machineId := m.checkAndReturnPathMid(fileId, path)
if !strings.HasSuffix(path, "/") {
path = path + "/"
}
sftpCli := m.getSftpCli(machineId)
createfile, err := sftpCli.Create(path + filename)
biz.ErrIsNilAppendErr(err, "创建文件失败: %s")
defer createfile.Close()
createfile.Write(content)
}
// 删除文件
func (m *machineFileApp) RemoveFile(fileId uint64, path string) {
path, machineId := m.checkAndReturnPathMid(fileId, path)
sftpCli := m.getSftpCli(machineId)
file, err := sftpCli.Open(path)
biz.ErrIsNilAppendErr(err, "打开文件失败: %s")
fi, err := file.Stat()
if fi.IsDir() {
err = sftpCli.RemoveDirectory(path)
} else {
err = sftpCli.Remove(path)
}
biz.ErrIsNilAppendErr(err, "删除文件失败: %s")
}
// 获取sftp client
func (m *machineFileApp) getSftpCli(machineId uint64) *sftp.Client {
return Machine.GetCli(machineId).GetSftpCli()
}
// 校验并返回实际可访问的文件path
func (m *machineFileApp) checkAndReturnPathMid(fid uint64, inputPath string) (string, uint64) {
biz.IsTrue(fid != 0, "文件id不能为空")
mf := m.GetById(uint64(fid))
biz.NotNil(mf, "文件不存在")
if inputPath != "" {
// 接口传入的地址需为配置路径的子路径
biz.IsTrue(strings.HasPrefix(inputPath, mf.Path), "无权访问该目录或文件")
return inputPath, mf.MachineId
} else {
return mf.Path, mf.MachineId
}
}

View File

@@ -0,0 +1,70 @@
package application
import (
"mayfly-go/base/biz"
"mayfly-go/base/model"
"mayfly-go/server/devops/domain/entity"
"mayfly-go/server/devops/domain/repository"
"mayfly-go/server/devops/infrastructure/persistence"
)
type IMachineScript interface {
// 分页获取机器脚本信息列表
GetPageList(condition *entity.MachineScript, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) model.PageResult
// 根据条件获取
GetMachineScript(condition *entity.MachineScript, cols ...string) error
// 根据id获取
GetById(id uint64, cols ...string) *entity.MachineScript
Save(entity *entity.MachineScript)
Delete(id uint64)
}
type machineScriptApp struct {
machineScriptRepo repository.MachineScript
machineRepo repository.Machine
}
const Common_Script_Machine_Id = 9999999
// 实现类单例
var MachineScript IMachineScript = &machineScriptApp{
machineRepo: persistence.MachineDao,
machineScriptRepo: persistence.MachineScriptDao}
// 分页获取机器脚本信息列表
func (m *machineScriptApp) GetPageList(condition *entity.MachineScript, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) model.PageResult {
return m.machineScriptRepo.GetPageList(condition, pageParam, toEntity, orderBy...)
}
// 根据条件获取
func (m *machineScriptApp) GetMachineScript(condition *entity.MachineScript, cols ...string) error {
return m.machineScriptRepo.GetMachineScript(condition, cols...)
}
// 根据id获取
func (m *machineScriptApp) GetById(id uint64, cols ...string) *entity.MachineScript {
return m.machineScriptRepo.GetById(id, cols...)
}
// 保存机器脚本
func (m *machineScriptApp) Save(entity *entity.MachineScript) {
// 如果机器id不为公共脚本id则校验机器是否存在
if machineId := entity.MachineId; machineId != Common_Script_Machine_Id {
biz.NotNil(m.machineRepo.GetById(machineId, "Name"), "该机器不存在")
}
if entity.Id != 0 {
model.UpdateById(entity)
} else {
model.Insert(entity)
}
}
// 根据id删除
func (m *machineScriptApp) Delete(id uint64) {
m.machineScriptRepo.Delete(id)
}

View File

@@ -0,0 +1,18 @@
package entity
import (
"mayfly-go/base/model"
)
type Db struct {
model.Model
Name string `orm:"column(name)" json:"name"`
Type string `orm:"column(type)" json:"type"` // 类型mysql oracle等
Host string `orm:"column(host)" json:"host"`
Port int `orm:"column(port)" json:"port"`
Network string `orm:"column(network)" json:"network"`
Username string `orm:"column(username)" json:"username"`
Password string `orm:"column(password)" json:"-"`
Database string `orm:"column(database)" json:"database"`
}

View File

@@ -0,0 +1,13 @@
package entity
import (
"mayfly-go/base/model"
)
type DbSql struct {
model.Model `orm:"-"`
DbId uint64 `json:"db_id"`
Type int `json:"type"` // 类型
Sql string `json:"sql"`
}

View File

@@ -0,0 +1,17 @@
package entity
import (
"mayfly-go/base/model"
)
type Machine struct {
model.Model
Name string `json:"name"`
// IP地址
Ip string `json:"ip"`
// 用户名
Username string `json:"username"`
Password string `json:"-"`
// 端口号
Port int `json:"port"`
}

View File

@@ -0,0 +1,13 @@
package entity
import "mayfly-go/base/model"
type MachineFile struct {
model.Model
Name string `json:"name"`
// 机器id
MachineId uint64 `json:"machineId"`
Type int `json:"type"`
// 路径
Path string `json:"path"`
}

View File

@@ -0,0 +1,14 @@
package entity
import (
"time"
)
type MachineMonitor struct {
Id uint64 `json:"id"`
MachineId uint64 `json:"machineId"`
CpuRate float32 `json:"cpuRate"`
MemRate float32 `json:"memRate"`
SysLoad string `json:"sysLoad"`
CreateTime time.Time `json:"createTime"`
}

View File

@@ -0,0 +1,15 @@
package entity
import "mayfly-go/base/model"
type MachineScript struct {
model.Model
Name string `json:"name"`
// 机器id
MachineId uint64 `json:"machineId"`
Type int `json:"type"`
// 脚本内容
Description string `json:"description"`
// 脚本内容
Script string `json:"script"`
}

View File

@@ -0,0 +1,17 @@
package repository
import (
"mayfly-go/base/model"
"mayfly-go/server/devops/domain/entity"
)
type Db interface {
// 分页获取机器信息列表
GetDbList(condition *entity.Db, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) model.PageResult
// 根据条件获取账号信息
GetDb(condition *entity.Db, cols ...string) error
// 根据id获取
GetById(id uint64, cols ...string) *entity.Db
}

View File

@@ -0,0 +1,21 @@
package repository
import (
"mayfly-go/base/model"
"mayfly-go/server/devops/domain/entity"
)
type Machine interface {
// 分页获取机器信息列表
GetMachineList(condition *entity.Machine, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) model.PageResult
// 根据条件获取账号信息
GetMachine(condition *entity.Machine, cols ...string) error
// 根据id获取
GetById(id uint64, cols ...string) *entity.Machine
Create(entity *entity.Machine)
UpdateById(entity *entity.Machine)
}

View File

@@ -0,0 +1,23 @@
package repository
import (
"mayfly-go/base/model"
"mayfly-go/server/devops/domain/entity"
)
type MachineFile interface {
// 分页获取机器脚本信息列表
GetPageList(condition *entity.MachineFile, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) model.PageResult
// 根据条件获取
GetMachineFile(condition *entity.MachineFile, cols ...string) error
// 根据id获取
GetById(id uint64, cols ...string) *entity.MachineFile
Delete(id uint64)
Create(entity *entity.MachineFile)
UpdateById(entity *entity.MachineFile)
}

View File

@@ -0,0 +1,23 @@
package repository
import (
"mayfly-go/base/model"
"mayfly-go/server/devops/domain/entity"
)
type MachineScript interface {
// 分页获取机器脚本信息列表
GetPageList(condition *entity.MachineScript, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) model.PageResult
// 根据条件获取
GetMachineScript(condition *entity.MachineScript, cols ...string) error
// 根据id获取
GetById(id uint64, cols ...string) *entity.MachineScript
Delete(id uint64)
Create(entity *entity.MachineScript)
UpdateById(entity *entity.MachineScript)
}

View File

@@ -0,0 +1,131 @@
package db
import (
"database/sql"
"errors"
"fmt"
"mayfly-go/base/biz"
"mayfly-go/server/devops/domain/entity"
"strings"
"sync"
"time"
_ "github.com/go-sql-driver/mysql"
)
var dbCache sync.Map
// db实例
type DbInstance struct {
Id uint64
Type string
db *sql.DB
}
// 执行查询语句
func (d *DbInstance) SelectData(sql string) ([]map[string]string, error) {
sql = strings.Trim(sql, " ")
if !strings.HasPrefix(sql, "SELECT") && !strings.HasPrefix(sql, "select") {
return nil, errors.New("该sql非查询语句")
}
rows, err := d.db.Query(sql)
if err != nil {
return nil, err
}
// rows对象一定要close掉如果出错不关掉则会很迅速的达到设置最大连接数
// 后面的链接过来直接报错或拒绝,实际上也没有起效果
defer func() {
if rows != nil {
rows.Close()
}
}()
cols, _ := rows.Columns()
// 这里表示一行填充数据
scans := make([]interface{}, len(cols))
// 这里表示一行所有列的值,用[]byte表示
vals := make([][]byte, len(cols))
// 这里scans引用vals把数据填充到[]byte里
for k := range vals {
scans[k] = &vals[k]
}
result := make([]map[string]string, 0)
for rows.Next() {
// 不Scan也会导致等待该链接实际处于未工作的状态然后也会导致连接数迅速达到最大
err := rows.Scan(scans...)
if err != nil {
return nil, err
}
// 每行数据
rowData := make(map[string]string)
// 把vals中的数据复制到row中
for k, v := range vals {
key := cols[k]
// 如果是密码字段,则脱敏显示
if key == "password" {
v = []byte("******")
}
// 这里把[]byte数据转成string
rowData[key] = string(v)
}
//放入结果集
result = append(result, rowData)
}
return result, nil
}
// 执行 update, insert, delete建表等sql
//
// 返回影响条数和错误
func (d *DbInstance) Exec(sql string) (int64, error) {
res, err := d.db.Exec(sql)
if err != nil {
return 0, err
}
return res.RowsAffected()
}
// 关闭连接,并从缓存中移除
func (d *DbInstance) Close() {
d.db.Close()
dbCache.Delete(d.Id)
}
// 获取dataSourceName
func getDsn(d *entity.Db) string {
if d.Type == "mysql" {
return fmt.Sprintf("%s:%s@%s(%s:%d)/%s", d.Username, d.Password, d.Network, d.Host, d.Port, d.Database)
}
return ""
}
func GetDbInstance(id uint64, getDbInfo func(uint64) *entity.Db) *DbInstance {
// Id不为0则为需要缓存
needCache := id != 0
if needCache {
load, ok := dbCache.Load(id)
if ok {
return load.(*DbInstance)
}
}
d := getDbInfo(id)
biz.NotNil(d, "数据库信息不存在")
DB, err := sql.Open(d.Type, getDsn(d))
biz.ErrIsNil(err, fmt.Sprintf("Open %s failed, err:%v\n", d.Type, err))
perr := DB.Ping()
if perr != nil {
panic(biz.NewBizErr(fmt.Sprintf("数据库连接失败: %s", perr.Error())))
}
// 最大连接周期超过时间的连接就close
DB.SetConnMaxLifetime(100 * time.Second)
// 设置最大连接数
DB.SetMaxOpenConns(5)
// 设置闲置连接数
DB.SetMaxIdleConns(1)
dbi := &DbInstance{Id: id, Type: d.Type, db: DB}
if needCache {
dbCache.LoadOrStore(d.Id, dbi)
}
return dbi
}

View File

@@ -0,0 +1,33 @@
package db
import "fmt"
const (
// mysql 表信息元数据
MYSQL_TABLE_MA = `SELECT table_name tableName, engine, table_comment tableComment,
create_time createTime from information_schema.tables
WHERE table_schema = (SELECT database())`
// mysql 列信息元数据
MYSQL_COLOUMN_MA = `SELECT column_name columnName, column_type columnType,
column_comment columnComment, column_key columnKey, extra from information_schema.columns
WHERE table_name = '%s' AND table_schema = (SELECT database()) ORDER BY ordinal_position`
)
func (d *DbInstance) GetTableMetedatas() []map[string]string {
var sql string
if d.Type == "mysql" {
sql = MYSQL_TABLE_MA
}
res, _ := d.SelectData(sql)
return res
}
func (d *DbInstance) GetColumnMetadatas(tableName string) []map[string]string {
var sql string
if d.Type == "mysql" {
sql = fmt.Sprintf(MYSQL_COLOUMN_MA, tableName)
}
res, _ := d.SelectData(sql)
return res
}

View File

@@ -0,0 +1,206 @@
package machine
import (
"errors"
"fmt"
"io"
"mayfly-go/base/biz"
"mayfly-go/base/cache"
"mayfly-go/base/global"
"mayfly-go/server/devops/domain/entity"
"net"
"time"
"github.com/pkg/sftp"
"golang.org/x/crypto/ssh"
)
// 客户端信息
type Cli struct {
machine *entity.Machine
// ssh客户端
client *ssh.Client
sftpClient *sftp.Client
}
// 机器客户端连接缓存30分钟内没有访问则会被关闭
var cliCache = cache.NewTimedCache(30*time.Minute, 5*time.Second).
WithUpdateAccessTime(true).
OnEvicted(func(key string, value interface{}) {
global.Log.Info(fmt.Sprintf("删除机器连接缓存 id: %s", key))
value.(*Cli).Close()
})
// 从缓存中获取客户端信息,不存在则回调获取机器信息函数,并新建
func GetCli(machineId uint64, getMachine func(uint64) *entity.Machine) (*Cli, error) {
cli, err := cliCache.ComputeIfAbsent(fmt.Sprint(machineId), func(key string) (interface{}, error) {
c, err := newClient(getMachine(machineId))
if err != nil {
return nil, err
}
return c, nil
})
return cli.(*Cli), err
}
//根据机器信息创建客户端对象
func newClient(machine *entity.Machine) (*Cli, error) {
if machine == nil {
return nil, errors.New("机器不存在")
}
global.Log.Infof("机器连接:%s:%d", machine.Ip, machine.Port)
cli := new(Cli)
cli.machine = machine
err := cli.connect()
if err != nil {
return nil, err
}
return cli, nil
}
//连接
func (c *Cli) connect() error {
// 如果已经有client则直接返回
if c.client != nil {
return nil
}
m := c.machine
config := ssh.ClientConfig{
User: m.Username,
Auth: []ssh.AuthMethod{ssh.Password(m.Password)},
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
return nil
},
Timeout: 5 * time.Second,
}
addr := fmt.Sprintf("%s:%d", m.Ip, m.Port)
sshClient, err := ssh.Dial("tcp", addr, &config)
if err != nil {
return err
}
c.client = sshClient
return nil
}
// 测试连接
func TestConn(m *entity.Machine) error {
config := ssh.ClientConfig{
User: m.Username,
Auth: []ssh.AuthMethod{ssh.Password(m.Password)},
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
return nil
},
Timeout: 5 * time.Second,
}
addr := fmt.Sprintf("%s:%d", m.Ip, m.Port)
sshClient, err := ssh.Dial("tcp", addr, &config)
if err != nil {
return err
}
defer sshClient.Close()
return nil
}
// 关闭client和并从缓存中移除
func (c *Cli) Close() {
if c.client != nil {
c.client.Close()
}
if c.sftpClient != nil {
c.sftpClient.Close()
}
}
// 获取sftp client
func (c *Cli) GetSftpCli() *sftp.Client {
if c.client == nil {
if err := c.connect(); err != nil {
panic(biz.NewBizErr("连接ssh失败" + err.Error()))
}
}
sftpclient := c.sftpClient
// 如果sftpClient为nil则连接
if sftpclient == nil {
sc, serr := sftp.NewClient(c.client, sftp.MaxPacket(1<<15))
if serr != nil {
panic(biz.NewBizErr("获取sftp client失败" + serr.Error()))
}
sftpclient = sc
c.sftpClient = sftpclient
}
return sftpclient
}
// 获取session
func (c *Cli) GetSession() (*ssh.Session, error) {
if c.client == nil {
if err := c.connect(); err != nil {
return nil, err
}
}
return c.client.NewSession()
}
//执行shell
//@param shell shell脚本命令
func (c *Cli) Run(shell string) (*string, error) {
session, err := c.GetSession()
if err != nil {
c.Close()
return nil, err
}
defer session.Close()
buf, rerr := session.CombinedOutput(shell)
if rerr != nil {
return nil, rerr
}
res := string(buf)
return &res, nil
}
//执行带交互的命令
func (c *Cli) RunTerminal(shell string, stdout, stderr io.Writer) error {
session, err := c.GetSession()
if err != nil {
return err
}
//defer session.Close()
// fd := int(os.Stdin.Fd())
// oldState, err := terminal.MakeRaw(fd)
// if err != nil {
// panic(err)
// }
// defer terminal.Restore(fd, oldState)
// writer, err := session.StdinPipe()
biz.ErrIsNilAppendErr(err, "获取session stdin 错误:%s")
session.Stdout = stdout
session.Stderr = stderr
// termWidth, termHeight, err := terminal.GetSize(fd)
// if err != nil {
// panic(err)
// }
// Set up terminal modes
// modes := ssh.TerminalModes{
// ssh.ECHO: 1, // enable echoing
// ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
// ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
// }
// // Request pseudo terminal
// if err := session.RequestPty("xterm-256color", 400, 800, modes); err != nil {
// return err
// }
session.Shell()
session.Wait()
// writer.Write([]byte(shell))
session.Run(shell)
return nil
}

View File

@@ -0,0 +1,143 @@
package machine
import (
"fmt"
"mayfly-go/base/utils"
"strings"
"testing"
)
func TestSSH(t *testing.T) {
//ssh.ListenAndServe("148.70.36.197")
//cli := New("148.70.36.197", "root", "g..91mn#", 22)
////output, err := cli.Run("free -h")
////fmt.Printf("%v\n%v", output, err)
//err := cli.RunTerminal("tail -f /usr/local/java/logs/eatlife-info.log", os.Stdout, os.Stdin)
//fmt.Println(err)
res := "top - 17:14:07 up 5 days, 6:30, 2 users, load average: 0.03, 0.04, 0.05\nTasks: 101 total, 1 running, 100 sleeping, 0 stopped, 0 zombie\n%Cpu(s): 6.2 us, 0.0 sy, 0.0 ni, 93.8 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st\nKiB Mem : 1882012 total, 73892 free, 770360 used, 1037760 buff/cache\nKiB Swap: 0 total, 0 free, 0 used. 933492 avail Mem"
split := strings.Split(res, "\n")
//var firstLine string
//for i := 0; i < len(split); i++ {
// if i == 0 {
// val := strings.Split(split[i], "top -")[1]
// vals := strings.Split(val, ",")
//
// }
//}
firstLine := strings.Split(strings.Split(split[0], "top -")[1], ",")
// 17:14:07 up 5 days
up := strings.Trim(strings.Split(firstLine[0], "up")[1], " ") + firstLine[1]
// 2 users
users := strings.Split(strings.Trim(firstLine[2], " "), " ")[0]
// load average: 0.03
oneMinLa := strings.Trim(strings.Split(strings.Trim(firstLine[3], " "), ":")[1], " ")
fiveMinLa := strings.Trim(firstLine[4], " ")
fietMinLa := strings.Trim(firstLine[5], " ")
fmt.Println(firstLine, up, users, oneMinLa, fiveMinLa, fietMinLa)
tasks := Parse(strings.Split(split[1], "Tasks:")[1])
cpu := Parse(strings.Split(split[2], "%Cpu(s):")[1])
mem := Parse(strings.Split(split[3], "KiB Mem :")[1])
fmt.Println(tasks, cpu, mem)
}
func Parse(val string) map[string]string {
res := make(map[string]string)
vals := strings.Split(val, ",")
for i := 0; i < len(vals); i++ {
trimData := strings.Trim(vals[i], " ")
keyValue := strings.Split(trimData, " ")
res[keyValue[1]] = keyValue[0]
}
return res
}
func TestTemplateRev(t *testing.T) {
temp := "hello my name is {name} hahahaha lihaiba {age} years old {public}"
str := "hello my name is hmlhmlhm 慌慌信息 hahahaha lihaiba 15 years old private protected"
//temp1 := " top - {up}, {users} users, load average: {loadavg}"
//str1 := " top - 17:14:07 up 5 days, 6:30, 2 users, load average: 0.03, 0.04, 0.05"
//taskTemp := "Tasks: {total} total, {running} running, {sleeping} sleeping, {stopped} stopped, {zombie} zombie"
//taskVal := "Tasks: 101 total, 1 running, 100 sleeping, 0 stopped, 0 zombie"
//nameRunne := []rune(str)
//index := strings.Index(temp, "{")
//ei := strings.Index(temp, "}") + 1
//next := temp[ei:]
//key := temp[index+1 : ei-1]
//value := SubString(str, index, UnicodeIndex(str, next))
res := make(map[string]interface{})
utils.ReverStrTemplate(temp, str, res)
fmt.Println(res)
}
//func ReverStrTemplate(temp, str string, res map[string]string) {
// index := UnicodeIndex(temp, "{")
// ei := UnicodeIndex(temp, "}") + 1
// next := temp[ei:]
// nextContain := UnicodeIndex(next, "{")
// nextIndexValue := next
// if nextContain != -1 {
// nextIndexValue = SubString(next, 0, nextContain)
// }
// key := temp[index+1 : ei-1]
// // 如果后面没有内容了,则取字符串的长度即可
// var valueLastIndex int
// if nextIndexValue == "" {
// valueLastIndex = StrLen(str)
// } else {
// valueLastIndex = UnicodeIndex(str, nextIndexValue)
// }
// value := SubString(str, index, valueLastIndex)
// res[key] = value
//
// if nextContain != -1 {
// ReverStrTemplate(next, SubString(str, UnicodeIndex(str, value)+StrLen(value), StrLen(str)), res)
// }
//}
//
//func StrLen(str string) int {
// return len([]rune(str))
//}
//
//func SubString(str string, begin, end int) (substr string) {
// // 将字符串的转换成[]rune
// rs := []rune(str)
// lth := len(rs)
//
// // 简单的越界判断
// if begin < 0 {
// begin = 0
// }
// if begin >= lth {
// begin = lth
// }
// if end > lth {
// end = lth
// }
//
// // 返回子串
// return string(rs[begin:end])
//}
//
//func UnicodeIndex(str, substr string) int {
// // 子串在字符串的字节位置
// result := strings.Index(str, substr)
// if result >= 0 {
// // 获得子串之前的字符串并转换成[]byte
// prefix := []byte(str)[0:result]
// // 将子串之前的字符串转换成[]rune
// rs := []rune(string(prefix))
// // 获得子串之前的字符串的长度,便是子串在字符串的字符位置
// result = len(rs)
// }
//
// return result
//}
func TestTerminal(t *testing.T) {
// ioutil.ReadAll(file)
}

View File

@@ -0,0 +1,57 @@
package machine
import (
"io/ioutil"
"mayfly-go/base/biz"
"mayfly-go/base/global"
"mayfly-go/base/utils"
"mayfly-go/server/devops/domain/entity"
"time"
)
const BasePath = "./machine/shell/"
const MonitorTemp = "cpuRate:{cpuRate}%,memRate:{memRate}%,sysLoad:{sysLoad}\n"
// shell文件内容缓存避免每次读取文件
var shellCache = make(map[string]string)
func (c *Cli) GetProcessByName(name string) (*string, error) {
return c.Run(getShellContent("sys_info"))
}
func (c *Cli) GetSystemInfo() (*string, error) {
return c.Run(getShellContent("system_info"))
}
func (c *Cli) GetMonitorInfo() *entity.MachineMonitor {
mm := new(entity.MachineMonitor)
res, _ := c.Run(getShellContent("monitor"))
if res == nil {
return nil
}
resMap := make(map[string]interface{})
utils.ReverStrTemplate(MonitorTemp, *res, resMap)
err := utils.Map2Struct(resMap, mm)
if err != nil {
global.Log.Error("解析machine monitor: %s", err.Error())
return nil
}
mm.MachineId = c.machine.Id
mm.CreateTime = time.Now()
return mm
}
// 获取shell内容
func getShellContent(name string) string {
cacheShell := shellCache[name]
if cacheShell != "" {
return cacheShell
}
bytes, err := ioutil.ReadFile(BasePath + name + ".sh")
biz.ErrIsNil(err, "获取shell文件失败")
shellStr := string(bytes)
shellCache[name] = shellStr
return shellStr
}

View File

@@ -0,0 +1,23 @@
#! /bin/bash
# Function: 根据输入的程序的名字过滤出所对应的PID并显示出详细信息如果有几个PID则全部显示
NAME=%s
N=`ps -aux | grep $NAME | grep -v grep | wc -l` ##统计进程总数
if [ $N -le 0 ];then
echo "该进程名没有运行!"
fi
i=1
while [ $N -gt 0 ]
do
echo "进程PID: `ps -aux | grep $NAME | grep -v grep | awk 'NR=='$i'{print $0}'| awk '{print $2}'`"
echo "进程命令:`ps -aux | grep $NAME | grep -v grep | awk 'NR=='$i'{print $0}'| awk '{print $11}'`"
echo "进程所属用户: `ps -aux | grep $NAME | grep -v grep | awk 'NR=='$i'{print $0}'| awk '{print $1}'`"
echo "CPU占用率`ps -aux | grep $NAME | grep -v grep | awk 'NR=='$i'{print $0}'| awk '{print $3}'`%"
echo "内存占用率:`ps -aux | grep $NAME | grep -v grep | awk 'NR=='$i'{print $0}'| awk '{print $4}'`%"
echo "进程开始运行的时刻:`ps -aux | grep $NAME | grep -v grep | awk 'NR=='$i'{print $0}'| awk '{print $9}'`"
echo "进程运行的时间:` ps -aux | grep $NAME | grep -v grep | awk 'NR=='$i'{print $0}'| awk '{print $11}'`"
echo "进程状态:`ps -aux | grep $NAME | grep -v grep | awk 'NR=='$i'{print $0}'| awk '{print $8}'`"
echo "进程虚拟内存:`ps -aux | grep $NAME | grep -v grep | awk 'NR=='$i'{print $0}'| awk '{print $5}'`"
echo "进程共享内存:`ps -aux | grep $NAME | grep -v grep | awk 'NR=='$i'{print $0}'| awk '{print $6}'`"
echo "***************************************************************"
let N-- i++
done

View File

@@ -0,0 +1,14 @@
# 获取监控信息
function get_monitor_info() {
cpu_rate=$(cat /proc/stat | awk '/cpu/{printf("%.2f%\n"), ($2+$4)*100/($2+$4+$5)}' | awk '{print $0}' | head -1)
mem_rate=$(free -m | sed -n '2p' | awk '{print""($3/$2)*100"%"}')
sys_load=$(uptime | cut -d: -f5)
cat <<EOF | column -t
cpuRate:${cpu_rate},memRate:${mem_rate},sysLoad:${sys_load}
EOF
}
get_monitor_info

View File

@@ -0,0 +1,192 @@
#!/bin/bash
# func:sys info check
[ $(id -u) -ne 0 ] && echo "请用root用户执行此脚本" && exit 1
sysversion=$(rpm -q centos-release | cut -d- -f3)
line="-------------------------------------------------"
# 获取系统cpu信息
function get_cpu_info() {
Physical_CPUs=$(grep "physical id" /proc/cpuinfo | sort | uniq | wc -l)
Virt_CPUs=$(grep "processor" /proc/cpuinfo | wc -l)
CPU_Kernels=$(grep "cores" /proc/cpuinfo | uniq | awk -F ': ' '{print $2}')
CPU_Type=$(grep "model name" /proc/cpuinfo | awk -F ': ' '{print $2}' | sort | uniq)
CPU_Arch=$(uname -m)
cpu_usage=$(cat /proc/stat | awk '/cpu/{printf("%.2f%\n"), ($2+$4)*100/($2+$4+$5)}' | awk '{print $0}' | head -1)
#echo -e '\033[32m CPU信息\033[0m'
echo -e ' CPU信息'
cat <<EOF | column -t
物理CPU个数: $Physical_CPUs
逻辑CPU个数: $Virt_CPUs
每CPU核心数: $CPU_Kernels
CPU型号: $CPU_Type
CPU架构: $CPU_Arch
CPU使用率: $cpu_usage
EOF
}
# 获取系统内存信息
function get_mem_info() {
Total=$(free -m | sed -n '2p' | awk '{print $2"M"}')
Used=$(free -m | sed -n '2p' | awk '{print $3"M"}')
Rate=$(free -m | sed -n '2p' | awk '{print""($3/$2)*100"%"}')
echo -e ' 内存信息:'
cat <<EOF | column -t
内存总容量:$Total
内存已使用:$Used
内存使用率:$Rate
EOF
}
# 获取系统网络信息
function get_net_info() {
pri_ipadd=$(ifconfig | awk 'NR==2{print $2}')
#pub_ipadd=$(curl ip.sb 2>&1)
pub_ipadd=$(curl -s http://ddns.oray.com/checkip | awk -F ":" '{print $2}' | awk -F "<" '{print $1}' | awk '{print $1}')
gateway=$(ip route | grep default | awk '{print $3}')
mac_info=$(ip link | egrep -v "lo" | grep link | awk '{print $2}')
dns_config=$(egrep 'nameserver' /etc/resolv.conf)
route_info=$(route -n)
echo -e ' IP信息'
cat <<EOF | column -t
系统公网地址: ${pub_ipadd}
系统私网地址: ${pri_ipadd}
网关地址: ${gateway}
MAC地址: ${mac_info}
路由信息:
${route_info}
DNS 信息:
${dns_config}
EOF
}
# 获取系统磁盘信息
function get_disk_info() {
disk_info=$(fdisk -l | grep "Disk /dev" | cut -d, -f1)
disk_use=$(df -hTP | awk '$2!="tmpfs"{print}')
disk_inode=$(df -hiP | awk '$1!="tmpfs"{print}')
echo -e ' 磁盘信息:'
cat <<EOF
${disk_info}
磁盘使用:
${disk_use}
inode信息:
${disk_inode}
EOF
}
# 获取系统信息
function get_systatus_info() {
sys_os=$(uname -o)
sys_release=$(cat /etc/redhat-release)
sys_kernel=$(uname -r)
sys_hostname=$(hostname)
sys_selinux=$(getenforce)
sys_lang=$(echo $LANG)
sys_lastreboot=$(who -b | awk '{print $3,$4}')
sys_runtime=$(uptime | awk '{print $3,$4}' | cut -d, -f1)
sys_time=$(date)
sys_load=$(uptime | cut -d: -f5)
echo -e ' 系统信息:'
cat <<EOF | column -t
系统: ${sys_os}
发行版本: ${sys_release}
系统内核: ${sys_kernel}
主机名: ${sys_hostname}
selinux状态: ${sys_selinux}
系统语言: ${sys_lang}
系统当前时间: ${sys_time}
系统最后重启时间: ${sys_lastreboot}
系统运行时间: ${sys_runtime}
系统负载: ${sys_load}
---------------------------------------
EOF
}
# 获取服务信息
function get_service_info() {
port_listen=$(netstat -lntup | grep -v "Active Internet")
kernel_config=$(sysctl -p 2>/dev/null)
if [ ${sysversion} -gt 6 ]; then
service_config=$(systemctl list-unit-files --type=service --state=enabled | grep "enabled")
run_service=$(systemctl list-units --type=service --state=running | grep ".service")
else
service_config=$(/sbin/chkconfig | grep -E ":on|:启用" | column -t)
run_service=$(/sbin/service --status-all | grep -E "running")
fi
echo -e ' 服务启动配置:'
cat <<EOF
${service_config}
${line}
运行的服务:
${run_service}
${line}
监听端口:
${port_listen}
${line}
内核参考配置:
${kernel_config}
EOF
}
function get_sys_user() {
login_user=$(awk -F: '{if ($NF=="/bin/bash") print $0}' /etc/passwd)
ssh_config=$(egrep -v "^#|^$" /etc/ssh/sshd_config)
sudo_config=$(egrep -v "^#|^$" /etc/sudoers | grep -v "^Defaults")
host_config=$(egrep -v "^#|^$" /etc/hosts)
crond_config=$(for cronuser in /var/spool/cron/*; do
ls ${cronuser} 2>/dev/null | cut -d/ -f5
egrep -v "^$|^#" ${cronuser} 2>/dev/null
echo ""
done)
echo -e ' 系统登录用户:'
cat <<EOF
${login_user}
${line}
ssh 配置信息:
${ssh_config}
${line}
sudo 配置用户:
${sudo_config}
${line}
定时任务配置:
${crond_config}
${line}
hosts 信息:
${host_config}
EOF
}
function process_top_info() {
top_title=$(top -b n1 | head -7 | tail -1)
cpu_top10=$(top b -n1 | head -17 | tail -10)
mem_top10=$(top -b n1 | head -17 | tail -10 | sort -k10 -r)
echo -e ' CPU占用top10'
cat <<EOF
${top_title}
${cpu_top10}
EOF
echo -e ' 内存占用top10'
cat <<EOF
${top_title}
${mem_top10}
EOF
}
function sys_check() {
get_systatus_info
echo ${line}
get_cpu_info
echo ${line}
get_mem_info
echo ${line}
# get_net_info
# echo ${line}
get_disk_info
echo ${line}
get_service_info
echo ${line}
# get_sys_user
# echo ${line}
process_top_info
}
sys_check

View File

@@ -0,0 +1,41 @@
# 获取系统cpu信息
function get_cpu_info() {
Physical_CPUs=$(grep "physical id" /proc/cpuinfo | sort | uniq | wc -l)
Virt_CPUs=$(grep "processor" /proc/cpuinfo | wc -l)
CPU_Kernels=$(grep "cores" /proc/cpuinfo | uniq | awk -F ': ' '{print $2}')
CPU_Type=$(grep "model name" /proc/cpuinfo | awk -F ': ' '{print $2}' | sort | uniq)
CPU_Arch=$(uname -m)
echo -e '\n-------------------------- CPU信息 --------------------------'
cat <<EOF | column -t
物理CPU个数: $Physical_CPUs
逻辑CPU个数: $Virt_CPUs
每CPU核心数: $CPU_Kernels
CPU型号: $CPU_Type
CPU架构: $CPU_Arch
EOF
}
# 获取系统信息
function get_systatus_info() {
sys_os=$(uname -o)
sys_release=$(cat /etc/redhat-release)
sys_kernel=$(uname -r)
sys_hostname=$(hostname)
sys_selinux=$(getenforce)
sys_lang=$(echo $LANG)
sys_lastreboot=$(who -b | awk '{print $3,$4}')
echo -e '-------------------------- 系统信息 --------------------------'
cat <<EOF | column -t
系统: ${sys_os}
发行版本: ${sys_release}
系统内核: ${sys_kernel}
主机名: ${sys_hostname}
selinux状态: ${sys_selinux}
系统语言: ${sys_lang}
系统最后重启时间: ${sys_lastreboot}
EOF
}
get_systatus_info
#echo -e "\n"
get_cpu_info

View File

@@ -0,0 +1,105 @@
package machine
import (
"mayfly-go/base/biz"
"mayfly-go/base/utils"
"strconv"
"strings"
)
type SystemVersion struct {
Version string
}
func (c *Cli) GetSystemVersion() *SystemVersion {
res, _ := c.Run("cat /etc/redhat-release")
return &SystemVersion{
Version: *res,
}
}
//top - 17:14:07 up 5 days, 6:30, 2 users, load average: 0.03, 0.04, 0.05
//Tasks: 101 total, 1 running, 100 sleeping, 0 stopped, 0 zombie
//%Cpu(s): 6.2 us, 0.0 sy, 0.0 ni, 93.8 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
//KiB Mem : 1882012 total, 73892 free, 770360 used, 1037760 buff/cache
//KiB Swap: 0 total, 0 free, 0 used. 933492 avail Mem
type Top struct {
Time string `json:"time"`
// 从本次开机到现在经过的时间
Up string `json:"up"`
// 当前有几个用户登录到该机器
NowUsers int `json:"nowUsers"`
// load average: 0.03, 0.04, 0.05 (系统1分钟、5分钟、15分钟内的平均负载值)
OneMinLoadavg float32 `json:"oneMinLoadavg"`
FiveMinLoadavg float32 `json:"fiveMinLoadavg"`
FifteenMinLoadavg float32 `json:"fifteenMinLoadavg"`
// 进程总数
TotalTask int `json:"totalTask"`
// 正在运行的进程数对应状态TASK_RUNNING
RunningTask int `json:"runningTask"`
SleepingTask int `json:"sleepingTask"`
StoppedTask int `json:"stoppedTask"`
ZombieTask int `json:"zombieTask"`
// 进程在用户空间user消耗的CPU时间占比不包含调整过优先级的进程
CpuUs float32 `json:"cpuUs"`
// 进程在内核空间system消耗的CPU时间占比
CpuSy float32 `json:"cpuSy"`
// 调整过用户态优先级的niced进程的CPU时间占比
CpuNi float32 `json:"cpuNi"`
// 空闲的idleCPU时间占比
CpuId float32 `json:"cpuId"`
// 等待waitI/O完成的CPU时间占比
CpuWa float32 `json:"cpuWa"`
// 处理硬中断hardware interrupt的CPU时间占比
CpuHi float32 `json:"cpuHi"`
// 处理硬中断hardware interrupt的CPU时间占比
CpuSi float32 `json:"cpuSi"`
// 当Linux系统是在虚拟机中运行时等待CPU资源的时间steal time占比
CpuSt float32 `json:"cpuSt"`
TotalMem int `json:"totalMem"`
FreeMem int `json:"freeMem"`
UsedMem int `json:"usedMem"`
CacheMem int `json:"cacheMem"`
TotalSwap int `json:"totalSwap"`
FreeSwap int `json:"freeSwap"`
UsedSwap int `json:"usedSwap"`
AvailMem int `json:"availMem"`
}
func (c *Cli) GetTop() *Top {
res, _ := c.Run("top -b -n 1 | head -5")
topTemp := "top - {upAndUsers}, load average: {loadavg}\n" +
"Tasks:{totalTask} total,{runningTask} running,{sleepingTask} sleeping,{stoppedTask} stopped,{zombieTask} zombie\n" +
"%Cpu(s):{cpuUs} us,{cpuSy} sy,{cpuNi} ni,{cpuId} id,{cpuWa} wa,{cpuHi} hi,{cpuSi} si,{cpuSt} st\n" +
"KiB Mem :{totalMem} total,{freeMem} free,{usedMem} used,{cacheMem} buff/cache\n" +
"KiB Swap:{totalSwap} total,{freeSwap} free,{usedSwap} used. {availMem} avail Mem \n"
resMap := make(map[string]interface{})
utils.ReverStrTemplate(topTemp, *res, resMap)
//17:14:07 up 5 days, 6:30, 2
timeUpAndUserStr := resMap["upAndUsers"].(string)
timeUpAndUser := strings.Split(timeUpAndUserStr, "up")
time := utils.StrTrim(timeUpAndUser[0])
upAndUsers := strings.Split(timeUpAndUser[1], ",")
up := utils.StrTrim(upAndUsers[0]) + upAndUsers[1]
users, _ := strconv.Atoi(utils.StrTrim(strings.Split(utils.StrTrim(upAndUsers[2]), " ")[0]))
// 0.03, 0.04, 0.05
loadavgs := strings.Split(resMap["loadavg"].(string), ",")
oneMinLa, _ := strconv.ParseFloat(loadavgs[0], 32)
fiveMinLa, _ := strconv.ParseFloat(utils.StrTrim(loadavgs[1]), 32)
fifMinLa, _ := strconv.ParseFloat(utils.StrTrim(loadavgs[2]), 32)
top := &Top{Time: time, Up: up, NowUsers: users, OneMinLoadavg: float32(oneMinLa), FiveMinLoadavg: float32(fiveMinLa), FifteenMinLoadavg: float32(fifMinLa)}
err := utils.Map2Struct(resMap, top)
biz.ErrIsNil(err, "解析top出错")
return top
}
type Status struct {
// 系统版本
SysVersion SystemVersion
// top信息
Top Top
}

View File

@@ -0,0 +1,195 @@
package machine
import (
"bytes"
"encoding/json"
"io"
"mayfly-go/base/global"
"sync"
"time"
"github.com/gorilla/websocket"
"golang.org/x/crypto/ssh"
)
type safeBuffer struct {
buffer bytes.Buffer
mu sync.Mutex
}
func (w *safeBuffer) Write(p []byte) (int, error) {
w.mu.Lock()
defer w.mu.Unlock()
return w.buffer.Write(p)
}
func (w *safeBuffer) Bytes() []byte {
w.mu.Lock()
defer w.mu.Unlock()
return w.buffer.Bytes()
}
func (w *safeBuffer) Reset() {
w.mu.Lock()
defer w.mu.Unlock()
w.buffer.Reset()
}
const (
wsMsgCmd = "cmd"
wsMsgResize = "resize"
)
type WsMsg struct {
Type string `json:"type"`
Msg string `json:"msg"`
Cols int `json:"cols"`
Rows int `json:"rows"`
}
type LogicSshWsSession struct {
stdinPipe io.WriteCloser
comboOutput *safeBuffer //ssh 终端混合输出
inputFilterBuff *safeBuffer //用来过滤输入的命令和ssh_filter配置对比的
session *ssh.Session
wsConn *websocket.Conn
}
func NewLogicSshWsSession(cols, rows int, cli *Cli, wsConn *websocket.Conn) (*LogicSshWsSession, error) {
sshSession, err := cli.GetSession()
if err != nil {
return nil, err
}
stdinP, err := sshSession.StdinPipe()
if err != nil {
return nil, err
}
comboWriter := new(safeBuffer)
inputBuf := new(safeBuffer)
//ssh.stdout and stderr will write output into comboWriter
sshSession.Stdout = comboWriter
sshSession.Stderr = comboWriter
modes := ssh.TerminalModes{
ssh.ECHO: 1, // disable echo
ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
}
// Request pseudo terminal
if err := sshSession.RequestPty("xterm", rows, cols, modes); err != nil {
return nil, err
}
// Start remote shell
if err := sshSession.Shell(); err != nil {
return nil, err
}
return &LogicSshWsSession{
stdinPipe: stdinP,
comboOutput: comboWriter,
inputFilterBuff: inputBuf,
session: sshSession,
wsConn: wsConn,
}, nil
}
//Close 关闭
func (sws *LogicSshWsSession) Close() {
if sws.session != nil {
sws.session.Close()
}
if sws.comboOutput != nil {
sws.comboOutput = nil
}
}
func (sws *LogicSshWsSession) Start(quitChan chan bool) {
go sws.receiveWsMsg(quitChan)
go sws.sendComboOutput(quitChan)
}
//receiveWsMsg receive websocket msg do some handling then write into ssh.session.stdin
func (sws *LogicSshWsSession) receiveWsMsg(exitCh chan bool) {
wsConn := sws.wsConn
//tells other go routine quit
defer setQuit(exitCh)
for {
select {
case <-exitCh:
return
default:
//read websocket msg
_, wsData, err := wsConn.ReadMessage()
if err != nil {
if websocket.IsCloseError(err, websocket.CloseGoingAway, websocket.CloseNoStatusReceived) {
return
}
global.Log.Error("reading webSocket message failed: ", err)
return
}
//unmashal bytes into struct
msgObj := WsMsg{}
if err := json.Unmarshal(wsData, &msgObj); err != nil {
global.Log.Error("unmarshal websocket message failed", err)
}
switch msgObj.Type {
case wsMsgResize:
//handle xterm.js size change
if msgObj.Cols > 0 && msgObj.Rows > 0 {
if err := sws.session.WindowChange(msgObj.Rows, msgObj.Cols); err != nil {
global.Log.Error("ssh pty change windows size failed")
}
}
case wsMsgCmd:
sws.sendWebsocketInputCommandToSshSessionStdinPipe([]byte(msgObj.Msg))
}
}
}
}
//sendWebsocketInputCommandToSshSessionStdinPipe
func (sws *LogicSshWsSession) sendWebsocketInputCommandToSshSessionStdinPipe(cmdBytes []byte) {
if _, err := sws.stdinPipe.Write(cmdBytes); err != nil {
global.Log.Error("ws cmd bytes write to ssh.stdin pipe failed")
}
}
func (sws *LogicSshWsSession) sendComboOutput(exitCh chan bool) {
wsConn := sws.wsConn
//todo 优化成一个方法
//tells other go routine quit
defer setQuit(exitCh)
//every 120ms write combine output bytes into websocket response
tick := time.NewTicker(time.Millisecond * time.Duration(60))
//for range time.Tick(120 * time.Millisecond){}
defer tick.Stop()
for {
select {
case <-tick.C:
if sws.comboOutput == nil {
return
}
bs := sws.comboOutput.Bytes()
if len(bs) > 0 {
err := wsConn.WriteMessage(websocket.TextMessage, bs)
if err != nil {
global.Log.Error("ssh sending combo output to webSocket failed")
}
sws.comboOutput.buffer.Reset()
}
case <-exitCh:
return
}
}
}
func (sws *LogicSshWsSession) Wait(quitChan chan bool) {
if err := sws.session.Wait(); err != nil {
setQuit(quitChan)
}
}
func setQuit(ch chan bool) {
ch <- true
}

View File

@@ -0,0 +1,31 @@
package persistence
import (
"mayfly-go/base/model"
"mayfly-go/server/devops/domain/entity"
"mayfly-go/server/devops/domain/repository"
)
type dbRepo struct{}
var DbDao repository.Db = &dbRepo{}
// 分页获取数据库信息列表
func (d *dbRepo) GetDbList(condition *entity.Db, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) model.PageResult {
return model.GetPage(pageParam, condition, toEntity, orderBy...)
}
// 根据条件获取账号信息
func (d *dbRepo) GetDb(condition *entity.Db, cols ...string) error {
return model.GetBy(condition, cols...)
}
// 根据id获取
func (d *dbRepo) GetById(id uint64, cols ...string) *entity.Db {
db := new(entity.Db)
if err := model.GetById(db, id, cols...); err != nil {
return nil
}
return db
}

View File

@@ -0,0 +1,45 @@
package persistence
import (
"mayfly-go/base/biz"
"mayfly-go/base/model"
"mayfly-go/server/devops/domain/entity"
"mayfly-go/server/devops/domain/repository"
)
type machineFileRepo struct{}
var MachineFileDao repository.MachineFile = &machineFileRepo{}
// 分页获取机器文件信息列表
func (m *machineFileRepo) GetPageList(condition *entity.MachineFile, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) model.PageResult {
return model.GetPage(pageParam, condition, toEntity, orderBy...)
}
// 根据条件获取账号信息
func (m *machineFileRepo) GetMachineFile(condition *entity.MachineFile, cols ...string) error {
return model.GetBy(condition, cols...)
}
// 根据id获取
func (m *machineFileRepo) GetById(id uint64, cols ...string) *entity.MachineFile {
ms := new(entity.MachineFile)
if err := model.GetById(ms, id, cols...); err != nil {
return nil
}
return ms
}
// 根据id获取
func (m *machineFileRepo) Delete(id uint64) {
biz.ErrIsNil(model.DeleteById(new(entity.MachineFile), id), "删除失败")
}
func (m *machineFileRepo) Create(entity *entity.MachineFile) {
model.Insert(entity)
}
func (m *machineFileRepo) UpdateById(entity *entity.MachineFile) {
model.UpdateById(entity)
}

View File

@@ -0,0 +1,39 @@
package persistence
import (
"mayfly-go/base/model"
"mayfly-go/server/devops/domain/entity"
"mayfly-go/server/devops/domain/repository"
)
type machineRepo struct{}
var MachineDao repository.Machine = &machineRepo{}
// 分页获取机器信息列表
func (m *machineRepo) GetMachineList(condition *entity.Machine, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) model.PageResult {
return model.GetPage(pageParam, condition, toEntity, orderBy...)
}
// 根据条件获取账号信息
func (m *machineRepo) GetMachine(condition *entity.Machine, cols ...string) error {
return model.GetBy(condition, cols...)
}
// 根据id获取
func (m *machineRepo) GetById(id uint64, cols ...string) *entity.Machine {
machine := new(entity.Machine)
if err := model.GetById(machine, id, cols...); err != nil {
return nil
}
return machine
}
func (m *machineRepo) Create(entity *entity.Machine) {
model.Insert(entity)
}
func (m *machineRepo) UpdateById(entity *entity.Machine) {
model.UpdateById(entity)
}

View File

@@ -0,0 +1,45 @@
package persistence
import (
"mayfly-go/base/biz"
"mayfly-go/base/model"
"mayfly-go/server/devops/domain/entity"
"mayfly-go/server/devops/domain/repository"
)
type machineScriptRepo struct{}
var MachineScriptDao repository.MachineScript = &machineScriptRepo{}
// 分页获取机器信息列表
func (m *machineScriptRepo) GetPageList(condition *entity.MachineScript, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) model.PageResult {
return model.GetPage(pageParam, condition, toEntity, orderBy...)
}
// 根据条件获取账号信息
func (m *machineScriptRepo) GetMachineScript(condition *entity.MachineScript, cols ...string) error {
return model.GetBy(condition, cols...)
}
// 根据id获取
func (m *machineScriptRepo) GetById(id uint64, cols ...string) *entity.MachineScript {
ms := new(entity.MachineScript)
if err := model.GetById(ms, id, cols...); err != nil {
return nil
}
return ms
}
// 根据id获取
func (m *machineScriptRepo) Delete(id uint64) {
biz.ErrIsNil(model.DeleteById(new(entity.MachineScript), id), "删除失败")
}
func (m *machineScriptRepo) Create(entity *entity.MachineScript) {
model.Insert(entity)
}
func (m *machineScriptRepo) UpdateById(entity *entity.MachineScript) {
model.UpdateById(entity)
}

View File

@@ -0,0 +1,27 @@
package scheduler
func init() {
SaveMachineMonitor()
}
func SaveMachineMonitor() {
AddFun("@every 60s", func() {
// for _, m := range models.GetNeedMonitorMachine() {
// m := m
// go func() {
// cli, err := machine.GetCli(uint64(utils.GetInt4Map(m, "id")))
// if err != nil {
// mlog.Log.Error("获取客户端失败:", err.Error())
// return
// }
// mm := cli.GetMonitorInfo()
// if mm != nil {
// err := model.Insert(mm)
// if err != nil {
// mlog.Log.Error("保存机器监控信息失败: ", err.Error())
// }
// }
// }()
// }
})
}

View File

@@ -0,0 +1,33 @@
package scheduler
import (
"mayfly-go/base/biz"
"github.com/robfig/cron/v3"
)
var cronService = cron.New()
func Start() {
cronService.Start()
}
func Stop() {
cronService.Stop()
}
func Remove(id cron.EntryID) {
cronService.Remove(id)
}
func GetCron() *cron.Cron {
return cronService
}
func AddFun(spec string, cmd func()) cron.EntryID {
id, err := cronService.AddFunc(spec, cmd)
if err != nil {
panic(biz.NewBizErr("添加任务失败:" + err.Error()))
}
return id
}

View File

@@ -0,0 +1,46 @@
package routers
import (
"mayfly-go/base/ctx"
"mayfly-go/server/devops/apis"
"mayfly-go/server/devops/application"
"github.com/gin-gonic/gin"
)
func InitDbRouter(router *gin.RouterGroup) {
db := router.Group("dbs")
{
d := &apis.Db{DbApp: application.Db}
// 获取所有数据库列表
db.GET("", func(c *gin.Context) {
rc := ctx.NewReqCtxWithGin(c)
rc.Handle(d.Dbs)
})
// db.GET(":dbId/select", controllers.SelectData)
db.GET(":dbId/select", func(g *gin.Context) {
rc := ctx.NewReqCtxWithGin(g).WithLog(ctx.NewLogInfo("执行数据库查询语句"))
rc.Handle(d.SelectData)
})
db.GET(":dbId/t-metadata", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(d.TableMA)
})
db.GET(":dbId/c-metadata", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(d.ColumnMA)
})
db.GET(":dbId/hint-tables", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(d.HintTables)
})
db.POST(":dbId/sql", func(c *gin.Context) {
rc := ctx.NewReqCtxWithGin(c).WithLog(ctx.NewLogInfo("保存sql内容"))
rc.Handle(d.SaveSql)
})
db.GET(":dbId/sql", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(d.GetSql)
})
}
}

View File

@@ -0,0 +1,29 @@
package routers
import (
"mayfly-go/base/ctx"
"mayfly-go/server/devops/apis"
"mayfly-go/server/devops/application"
"github.com/gin-gonic/gin"
)
func InitMachineRouter(router *gin.RouterGroup) {
m := &apis.Machine{MachineApp: application.Machine}
db := router.Group("machines")
{
db.GET("", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(m.Machines)
})
db.POST("", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(m.SaveMachine)
})
db.GET(":machineId/top", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(m.Top)
})
db.GET(":machineId/terminal", m.WsSSH)
}
}

View File

@@ -0,0 +1,65 @@
package routers
import (
"mayfly-go/base/ctx"
"mayfly-go/server/devops/apis"
"mayfly-go/server/devops/application"
"github.com/gin-gonic/gin"
)
func InitMachineFileRouter(router *gin.RouterGroup) {
machineFile := router.Group("machines")
{
mf := &apis.MachineFile{
MachineFileApp: application.MachineFile,
MachineApp: application.Machine}
// 获取指定机器文件列表
machineFile.GET(":machineId/files", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(mf.MachineFiles)
})
// 新增修改机器文件
addFileConf := ctx.NewLogInfo("新增机器文件配置")
machineFile.POST(":machineId/files", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).WithLog(addFileConf).Handle(mf.SaveMachineFiles)
})
// 删除机器文件
delFileConf := ctx.NewLogInfo("新增机器文件配置")
machineFile.DELETE(":machineId/files/:fileId", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).WithLog(delFileConf).Handle(mf.DeleteFile)
})
getContent := ctx.NewLogInfo("读取机器文件内容")
machineFile.GET(":machineId/files/:fileId/read", func(c *gin.Context) {
rc := ctx.NewReqCtxWithGin(c).WithLog(getContent)
rc.Handle(mf.ReadFileContent)
})
getDir := ctx.NewLogInfo("读取机器目录")
machineFile.GET(":machineId/files/:fileId/read-dir", func(c *gin.Context) {
rc := ctx.NewReqCtxWithGin(c).WithLog(getDir)
rc.Handle(mf.GetDirEntry)
})
writeFile := ctx.NewLogInfo("写入or下载文件内容")
machineFile.POST(":machineId/files/:fileId/write", func(c *gin.Context) {
rc := ctx.NewReqCtxWithGin(c).WithLog(writeFile)
rc.Handle(mf.WriteFileContent)
})
uploadFile := ctx.NewLogInfo("文件上传")
machineFile.POST(":machineId/files/:fileId/upload", func(c *gin.Context) {
rc := ctx.NewReqCtxWithGin(c).WithLog(uploadFile)
rc.Handle(mf.UploadFile)
})
removeFile := ctx.NewLogInfo("删除文件or文件夹")
machineFile.DELETE(":machineId/files/:fileId/remove", func(c *gin.Context) {
rc := ctx.NewReqCtxWithGin(c).WithLog(removeFile)
rc.Handle(mf.RemoveFile)
})
}
}

View File

@@ -0,0 +1,44 @@
package routers
import (
"mayfly-go/base/ctx"
"mayfly-go/server/devops/apis"
"mayfly-go/server/devops/application"
"github.com/gin-gonic/gin"
)
func InitMachineScriptRouter(router *gin.RouterGroup) {
machines := router.Group("machines")
{
ms := &apis.MachineScript{
MachineScriptApp: application.MachineScript,
MachineApp: application.Machine}
// 获取指定机器脚本列表
machines.GET(":machineId/scripts", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).WithNeedToken(false).Handle(ms.MachineScripts)
})
saveMachienScriptLog := ctx.NewLogInfo("保存脚本")
// 保存脚本
machines.POST(":machineId/scripts", func(c *gin.Context) {
rc := ctx.NewReqCtxWithGin(c).WithLog(saveMachienScriptLog)
rc.Handle(ms.SaveMachineScript)
})
deleteLog := ctx.NewLogInfo("删除脚本")
// 保存脚本
machines.DELETE(":machineId/scripts/:scriptId", func(c *gin.Context) {
rc := ctx.NewReqCtxWithGin(c).WithLog(deleteLog)
rc.Handle(ms.DeleteMachineScript)
})
runLog := ctx.NewLogInfo("执行机器脚本")
// 运行脚本
machines.GET(":machineId/scripts/:scriptId/run", func(c *gin.Context) {
rc := ctx.NewReqCtxWithGin(c).WithLog(runLog)
rc.Handle(ms.RunMachineScript)
})
}
}