refactor: 后端包结构重构、去除无用的文件

This commit is contained in:
meilin.huang
2022-06-02 17:41:11 +08:00
parent 51d06ab206
commit b2dc9dff0b
234 changed files with 749 additions and 816 deletions

View File

@@ -0,0 +1,289 @@
package api
import (
"fmt"
"io/ioutil"
"mayfly-go/internal/devops/api/form"
"mayfly-go/internal/devops/api/vo"
"mayfly-go/internal/devops/application"
"mayfly-go/internal/devops/domain/entity"
sysApplication "mayfly-go/internal/sys/application"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/ctx"
"mayfly-go/pkg/ginx"
"mayfly-go/pkg/model"
"mayfly-go/pkg/utils"
"mayfly-go/pkg/ws"
"strconv"
"strings"
"github.com/gin-gonic/gin"
)
type Db struct {
DbApp application.Db
MsgApp sysApplication.Msg
ProjectApp application.Project
}
// @router /api/dbs [get]
func (d *Db) Dbs(rc *ctx.ReqCtx) {
g := rc.GinCtx
m := &entity.Db{EnvId: uint64(ginx.QueryInt(g, "envId", 0)),
ProjectId: uint64(ginx.QueryInt(g, "projectId", 0)),
}
m.CreatorId = rc.LoginAccount.Id
rc.ResData = d.DbApp.GetPageList(m, ginx.GetPageParam(rc.GinCtx), new([]vo.SelectDataDbVO))
}
func (d *Db) Save(rc *ctx.ReqCtx) {
form := &form.DbForm{}
ginx.BindJsonAndValid(rc.GinCtx, form)
rc.ReqParam = form
db := new(entity.Db)
utils.Copy(db, form)
db.SetBaseInfo(rc.LoginAccount)
d.DbApp.Save(db)
}
func (d *Db) DeleteDb(rc *ctx.ReqCtx) {
d.DbApp.Delete(GetDbId(rc.GinCtx))
}
func (d *Db) TableInfos(rc *ctx.ReqCtx) {
rc.ResData = d.DbApp.GetDbInstance(GetIdAndDb(rc.GinCtx)).GetTableInfos()
}
func (d *Db) TableIndex(rc *ctx.ReqCtx) {
tn := rc.GinCtx.Query("tableName")
biz.NotEmpty(tn, "tableName不能为空")
rc.ResData = d.DbApp.GetDbInstance(GetIdAndDb(rc.GinCtx)).GetTableIndex(tn)
}
func (d *Db) GetCreateTableDdl(rc *ctx.ReqCtx) {
tn := rc.GinCtx.Query("tableName")
biz.NotEmpty(tn, "tableName不能为空")
rc.ResData = d.DbApp.GetDbInstance(GetIdAndDb(rc.GinCtx)).GetCreateTableDdl(tn)
}
// @router /api/db/:dbId/exec-sql [get]
func (d *Db) ExecSql(rc *ctx.ReqCtx) {
g := rc.GinCtx
id, db := GetIdAndDb(g)
dbInstance := d.DbApp.GetDbInstance(id, db)
biz.ErrIsNilAppendErr(d.ProjectApp.CanAccess(rc.LoginAccount.Id, dbInstance.ProjectId), "%s")
// 去除前后空格及换行符
sql := strings.TrimFunc(g.Query("sql"), func(r rune) bool {
s := string(r)
return s == " " || s == "\n"
})
rc.ReqParam = fmt.Sprintf("db: %d:%s | sql: %s", id, db, sql)
biz.NotEmpty(sql, "sql不能为空")
if strings.HasPrefix(sql, "SELECT") || strings.HasPrefix(sql, "select") || strings.HasPrefix(sql, "show") {
colNames, res, err := dbInstance.SelectData(sql)
biz.ErrIsNilAppendErr(err, "查询失败: %s")
colAndRes := make(map[string]interface{})
colAndRes["colNames"] = colNames
colAndRes["res"] = res
rc.ResData = colAndRes
} else {
rowsAffected, err := dbInstance.Exec(sql)
biz.ErrIsNilAppendErr(err, "执行失败: %s")
res := make([]map[string]string, 0)
resData := make(map[string]string)
resData["影响条数"] = fmt.Sprintf("%d", rowsAffected)
res = append(res, resData)
colAndRes := make(map[string]interface{})
colAndRes["colNames"] = []string{"影响条数"}
colAndRes["res"] = res
rc.ResData = colAndRes
}
}
// 执行sql文件
func (d *Db) ExecSqlFile(rc *ctx.ReqCtx) {
g := rc.GinCtx
fileheader, err := g.FormFile("file")
biz.ErrIsNilAppendErr(err, "读取sql文件失败: %s")
// 读取sql文件并根据;切割sql语句
file, _ := fileheader.Open()
filename := fileheader.Filename
bytes, _ := ioutil.ReadAll(file)
sqlContent := string(bytes)
sqls := strings.Split(sqlContent, ";")
dbId, db := GetIdAndDb(g)
go func() {
db := d.DbApp.GetDbInstance(dbId, db)
dbEntity := d.DbApp.GetById(dbId)
dbInfo := fmt.Sprintf("于%s的%s环境", dbEntity.Name, dbEntity.Env)
defer func() {
if err := recover(); err != nil {
switch t := err.(type) {
case *biz.BizError:
d.MsgApp.CreateAndSend(rc.LoginAccount, ws.ErrMsg("sql脚本执行失败", fmt.Sprintf("[%s]%s执行失败: [%s]", filename, dbInfo, t.Error())))
}
}
}()
biz.ErrIsNilAppendErr(d.ProjectApp.CanAccess(rc.LoginAccount.Id, db.ProjectId), "%s")
for _, sql := range sqls {
sql = strings.Trim(sql, " ")
if sql == "" || sql == "\n" {
continue
}
_, err := db.Exec(sql)
if err != nil {
d.MsgApp.CreateAndSend(rc.LoginAccount, ws.ErrMsg("sql脚本执行失败", fmt.Sprintf("[%s]%s执行失败: [%s]", filename, dbInfo, err.Error())))
return
}
}
d.MsgApp.CreateAndSend(rc.LoginAccount, ws.SuccessMsg("sql脚本执行成功", fmt.Sprintf("[%s]%s执行完成", filename, dbInfo)))
}()
}
// @router /api/db/:dbId/t-metadata [get]
func (d *Db) TableMA(rc *ctx.ReqCtx) {
dbi := d.DbApp.GetDbInstance(GetIdAndDb(rc.GinCtx))
biz.ErrIsNilAppendErr(d.ProjectApp.CanAccess(rc.LoginAccount.Id, dbi.ProjectId), "%s")
rc.ResData = dbi.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不能为空")
dbi := d.DbApp.GetDbInstance(GetIdAndDb(rc.GinCtx))
biz.ErrIsNilAppendErr(d.ProjectApp.CanAccess(rc.LoginAccount.Id, dbi.ProjectId), "%s")
rc.ResData = dbi.GetColumnMetadatas(tn)
}
// @router /api/db/:dbId/hint-tables [get]
func (d *Db) HintTables(rc *ctx.ReqCtx) {
dbi := d.DbApp.GetDbInstance(GetIdAndDb(rc.GinCtx))
biz.ErrIsNilAppendErr(d.ProjectApp.CanAccess(rc.LoginAccount.Id, dbi.ProjectId), "%s")
// 获取所有表
tables := dbi.GetTableMetedatas()
tableNames := make([]string, 0)
for _, v := range tables {
tableNames = append(tableNames, v["tableName"])
}
// key = 表名value = 列名数组
res := make(map[string][]string)
// 表为空,则直接返回
if len(tableNames) == 0 {
rc.ResData = res
return
}
// 获取所有表下的所有列信息
columnMds := dbi.GetColumnMetadatas(tableNames...)
for _, v := range columnMds {
tName := v["tableName"]
if res[tName] == nil {
res[tName] = make([]string, 0)
}
columnName := fmt.Sprintf("%s [%s]", v["columnName"], v["columnType"])
comment := v["columnComment"]
// 如果字段备注不为空,则加上备注信息
if comment != "" {
columnName = fmt.Sprintf("%s[%s]", columnName, comment)
}
res[tName] = append(res[tName], columnName)
}
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, Name: dbSqlForm.Name, Db: dbSqlForm.Db}
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)
}
}
// 获取所有保存的sql names
func (d *Db) GetSqlNames(rc *ctx.ReqCtx) {
id, db := GetIdAndDb(rc.GinCtx)
// 获取用于是否有该dbsql的保存记录有则更改否则新增
dbSql := &entity.DbSql{Type: 1, DbId: id, Db: db}
dbSql.CreatorId = rc.LoginAccount.Id
var sqls []entity.DbSql
model.ListBy(dbSql, &sqls, "id", "name")
rc.ResData = sqls
}
// 删除保存的sql
func (d *Db) DeleteSql(rc *ctx.ReqCtx) {
dbSql := &entity.DbSql{Type: 1, DbId: GetDbId(rc.GinCtx)}
dbSql.CreatorId = rc.LoginAccount.Id
dbSql.Name = rc.GinCtx.Query("name")
model.DeleteByCondition(dbSql)
}
// @router /api/db/:dbId/sql [get]
func (d *Db) GetSql(rc *ctx.ReqCtx) {
id, db := GetIdAndDb(rc.GinCtx)
// 根据创建者id 数据库id以及sql模板名称查询保存的sql信息
dbSql := &entity.DbSql{Type: 1, DbId: id, Db: db}
dbSql.CreatorId = rc.LoginAccount.Id
dbSql.Name = rc.GinCtx.Query("name")
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)
}
func GetIdAndDb(g *gin.Context) (uint64, string) {
db := g.Query("db")
biz.NotEmpty(db, "db不能为空")
return GetDbId(g), db
}

View File

@@ -0,0 +1,16 @@
package form
type DbForm struct {
Id uint64
Name string `binding:"required" json:"name"`
Type string `binding:"required" json:"type"` // 类型mysql oracle等
Host string `binding:"required" json:"host"`
Port int `binding:"required" json:"port"`
Username string `binding:"required" json:"username"`
Password string `json:"password"`
Database string `binding:"required" json:"database"`
ProjectId uint64 `binding:"required" json:"projectId"`
Project string `json:"project"`
Env string `json:"env"`
EnvId uint64 `binding:"required" json:"envId"`
}

View File

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

View File

@@ -0,0 +1,40 @@
package form
type Mongo struct {
Id uint64
Uri string `binding:"required" json:"uri"`
Name string `binding:"required" json:"name"`
ProjectId uint64 `binding:"required" json:"projectId"`
Project string `json:"project"`
Env string `json:"env"`
EnvId uint64 `binding:"required" json:"envId"`
}
type MongoCommand struct {
Database string `binding:"required" json:"database"`
Collection string `binding:"required" json:"collection"`
Filter map[string]interface{} `json:"filter"`
}
type MongoRunCommand struct {
Database string `binding:"required" json:"database"`
Command map[string]interface{} `json:"command"`
}
type MongoFindCommand struct {
MongoCommand
Sort map[string]interface{} `json:"sort"`
Skip int64
Limit int64
}
type MongoUpdateByIdCommand struct {
MongoCommand
DocId interface{} `binding:"required" json:"docId"`
Update map[string]interface{} `json:"update"`
}
type MongoInsertCommand struct {
MongoCommand
Doc map[string]interface{} `json:"doc"`
}

View File

@@ -0,0 +1,32 @@
package form
type Redis struct {
Id uint64
Host string `binding:"required" json:"host"`
Password string `json:"password"`
Db int `json:"db"`
ProjectId uint64 `binding:"required" json:"projectId"`
Project string `json:"project"`
Env string `json:"env"`
EnvId uint64 `binding:"required" json:"envId"`
}
type KeyInfo struct {
Key string `binding:"required" json:"key"`
Timed int64
}
type StringValue struct {
KeyInfo
Value interface{} `binding:"required" json:"value"`
}
type HashValue struct {
KeyInfo
Value []map[string]interface{} `binding:"required" json:"value"`
}
type SetValue struct {
KeyInfo
Value []interface{} `binding:"required" json:"value"`
}

View File

@@ -0,0 +1,165 @@
package api
import (
"fmt"
"mayfly-go/internal/devops/api/form"
"mayfly-go/internal/devops/api/vo"
"mayfly-go/internal/devops/application"
"mayfly-go/internal/devops/domain/entity"
"mayfly-go/internal/devops/infrastructure/machine"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/ctx"
"mayfly-go/pkg/ginx"
"mayfly-go/pkg/utils"
"mayfly-go/pkg/ws"
"strconv"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
)
type Machine struct {
MachineApp application.Machine
ProjectApp application.Project
}
func (m *Machine) Machines(rc *ctx.ReqCtx) {
condition := new(entity.Machine)
// 使用创建者id模拟账号成员id
condition.CreatorId = rc.LoginAccount.Id
condition.Ip = rc.GinCtx.Query("ip")
condition.Name = rc.GinCtx.Query("name")
condition.ProjectId = uint64(ginx.QueryInt(rc.GinCtx, "projectId", 0))
res := m.MachineApp.GetMachineList(condition, ginx.GetPageParam(rc.GinCtx), new([]*vo.MachineVO))
if res.Total == 0 {
rc.ResData = res
return
}
list := res.List.(*[]*vo.MachineVO)
for _, mv := range *list {
mv.HasCli = machine.HasCli(*mv.Id)
}
rc.ResData = res
}
func (m *Machine) MachineStats(rc *ctx.ReqCtx) {
stats := m.MachineApp.GetCli(GetMachineId(rc.GinCtx)).GetAllStats()
rc.ResData = stats
}
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)
}
func (m *Machine) ChangeStatus(rc *ctx.ReqCtx) {
g := rc.GinCtx
id := uint64(ginx.PathParamInt(g, "machineId"))
status := int8(ginx.PathParamInt(g, "status"))
rc.ReqParam = fmt.Sprintf("id: %d -- status: %d", id, status)
m.MachineApp.ChangeStatus(id, status)
}
func (m *Machine) DeleteMachine(rc *ctx.ReqCtx) {
id := uint64(ginx.PathParamInt(rc.GinCtx, "machineId"))
rc.ReqParam = id
m.MachineApp.Delete(id)
}
// 关闭机器客户端
func (m *Machine) CloseCli(rc *ctx.ReqCtx) {
machine.DeleteCli(GetMachineId(rc.GinCtx))
}
// 获取进程列表信息
func (m *Machine) GetProcess(rc *ctx.ReqCtx) {
g := rc.GinCtx
cmd := "ps -aux "
sortType := g.Query("sortType")
if sortType == "2" {
cmd += "--sort -pmem "
} else {
cmd += "--sort -pcpu "
}
pname := g.Query("name")
if pname != "" {
cmd += fmt.Sprintf("| grep %s ", pname)
}
count := g.Query("count")
if count == "" {
count = "10"
}
cmd += "| head -n " + count
cli := m.MachineApp.GetCli(GetMachineId(rc.GinCtx))
biz.ErrIsNilAppendErr(m.ProjectApp.CanAccess(rc.LoginAccount.Id, cli.GetMachine().ProjectId), "%s")
res, err := cli.Run(cmd)
biz.ErrIsNilAppendErr(err, "获取进程信息失败: %s")
rc.ResData = res
}
// 终止进程
func (m *Machine) KillProcess(rc *ctx.ReqCtx) {
pid := rc.GinCtx.Query("pid")
biz.NotEmpty(pid, "进程id不能为空")
cli := m.MachineApp.GetCli(GetMachineId(rc.GinCtx))
biz.ErrIsNilAppendErr(m.ProjectApp.CanAccess(rc.LoginAccount.Id, cli.GetMachine().ProjectId), "%s")
_, err := cli.Run("kill -9 " + pid)
biz.ErrIsNilAppendErr(err, "终止进程失败: %s")
}
func (m *Machine) WsSSH(g *gin.Context) {
wsConn, err := ws.Upgrader.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失败"))
}
// 权限校验
rc := ctx.NewReqCtxWithGin(g).WithRequiredPermission(ctx.NewPermission("machine:terminal"))
if err = ctx.PermissionHandler(rc); err != nil {
panic(biz.NewBizErr("没有权限"))
}
cols := ginx.QueryInt(g, "cols", 80)
rows := ginx.QueryInt(g, "rows", 40)
cli := m.MachineApp.GetCli(GetMachineId(g))
biz.ErrIsNilAppendErr(m.ProjectApp.CanAccess(rc.LoginAccount.Id, cli.GetMachine().ProjectId), "%s")
sws, err := machine.NewLogicSshWsSession(cols, rows, cli, wsConn)
biz.ErrIsNilAppendErr(err, "连接失败:%s")
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,177 @@
package api
import (
"fmt"
"io/fs"
"io/ioutil"
"mayfly-go/internal/devops/api/form"
"mayfly-go/internal/devops/api/vo"
"mayfly-go/internal/devops/application"
"mayfly-go/internal/devops/domain/entity"
sysApplication "mayfly-go/internal/sys/application"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/ctx"
"mayfly-go/pkg/ginx"
"mayfly-go/pkg/utils"
"mayfly-go/pkg/ws"
"strconv"
"strings"
"github.com/gin-gonic/gin"
)
type MachineFile struct {
MachineFileApp application.MachineFile
MachineApp application.Machine
MsgApp sysApplication.Msg
}
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")
sftpFile := m.MachineFileApp.ReadFile(fid, readPath)
defer sftpFile.Close()
fileInfo, _ := sftpFile.Stat()
// 如果是读取文件内容,则校验文件大小
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(sftpFile, path[len(path)-1])
} else {
datas, err := ioutil.ReadAll(sftpFile)
biz.ErrIsNilAppendErr(err, "读取文件内容失败: %s")
rc.ResData = string(datas)
}
}
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()
rc.ReqParam = fmt.Sprintf("path: %s", path)
la := rc.LoginAccount
go func() {
defer func() {
if err := recover(); err != nil {
switch t := err.(type) {
case *biz.BizError:
m.MsgApp.CreateAndSend(la, ws.ErrMsg("文件上传失败", fmt.Sprintf("执行文件上传失败:\n<-e errCode: %d, errMsg: %s", t.Code(), t.Error())))
}
}
}()
defer file.Close()
m.MachineFileApp.UploadFile(fid, path, fileheader.Filename, file)
// 保存消息并发送文件上传成功通知
machine := m.MachineApp.GetById(m.MachineFileApp.GetById(fid).MachineId)
m.MsgApp.CreateAndSend(la, ws.SuccessMsg("文件上传成功", fmt.Sprintf("[%s]文件已成功上传至 %s[%s:%s]", fileheader.Filename, machine.Name, machine.Ip, 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,82 @@
package api
import (
"fmt"
"mayfly-go/internal/devops/api/form"
"mayfly-go/internal/devops/api/vo"
"mayfly-go/internal/devops/application"
"mayfly-go/internal/devops/domain/entity"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/ctx"
"mayfly-go/pkg/ginx"
"mayfly-go/pkg/utils"
"strconv"
"github.com/gin-gonic/gin"
)
type MachineScript struct {
MachineScriptApp application.MachineScript
MachineApp application.Machine
ProjectApp application.Project
}
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, "该脚本不属于该机器")
script := ms.Script
// 如果有脚本参数,则用脚本参数替换脚本中的模板占位符参数
if params := g.Query("params"); params != "" {
script = utils.TemplateParse(ms.Script, utils.Json2Map(params))
}
cli := m.MachineApp.GetCli(machineId)
biz.ErrIsNilAppendErr(m.ProjectApp.CanAccess(rc.LoginAccount.Id, cli.GetMachine().ProjectId), "%s")
res, err := cli.Run(script)
// 记录请求参数
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,168 @@
package api
import (
"context"
"mayfly-go/internal/devops/api/form"
"mayfly-go/internal/devops/application"
"mayfly-go/internal/devops/domain/entity"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/ctx"
"mayfly-go/pkg/ginx"
"mayfly-go/pkg/utils"
"strconv"
"github.com/gin-gonic/gin"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo/options"
)
type Mongo struct {
MongoApp application.Mongo
}
func (m *Mongo) Mongos(rc *ctx.ReqCtx) {
g := rc.GinCtx
mc := &entity.Mongo{EnvId: uint64(ginx.QueryInt(g, "envId", 0)),
ProjectId: uint64(ginx.QueryInt(g, "projectId", 0)),
}
mc.CreatorId = rc.LoginAccount.Id
rc.ResData = m.MongoApp.GetPageList(mc, ginx.GetPageParam(rc.GinCtx), new([]entity.Mongo))
}
func (m *Mongo) Save(rc *ctx.ReqCtx) {
form := &form.Mongo{}
ginx.BindJsonAndValid(rc.GinCtx, form)
rc.ReqParam = form
mongo := new(entity.Mongo)
utils.Copy(mongo, form)
mongo.SetBaseInfo(rc.LoginAccount)
m.MongoApp.Save(mongo)
}
func (m *Mongo) DeleteMongo(rc *ctx.ReqCtx) {
m.MongoApp.Delete(m.GetMongoId(rc.GinCtx))
}
func (m *Mongo) Databases(rc *ctx.ReqCtx) {
cli := m.MongoApp.GetMongoCli(m.GetMongoId(rc.GinCtx))
res, err := cli.ListDatabases(context.TODO(), bson.D{})
biz.ErrIsNilAppendErr(err, "获取mongo所有库信息失败: %s")
rc.ResData = res
}
func (m *Mongo) Collections(rc *ctx.ReqCtx) {
cli := m.MongoApp.GetMongoCli(m.GetMongoId(rc.GinCtx))
db := rc.GinCtx.Query("database")
biz.NotEmpty(db, "database不能为空")
ctx := context.TODO()
res, err := cli.Database(db).ListCollectionNames(ctx, bson.D{})
biz.ErrIsNilAppendErr(err, "获取库集合信息失败: %s")
rc.ResData = res
}
func (m *Mongo) RunCommand(rc *ctx.ReqCtx) {
commandForm := new(form.MongoRunCommand)
ginx.BindJsonAndValid(rc.GinCtx, commandForm)
cli := m.MongoApp.GetMongoCli(m.GetMongoId(rc.GinCtx))
ctx := context.TODO()
var bm bson.M
err := cli.Database(commandForm.Database).RunCommand(
ctx,
commandForm.Command,
).Decode(&bm)
biz.ErrIsNilAppendErr(err, "执行命令失败: %s")
rc.ResData = bm
}
func (m *Mongo) FindCommand(rc *ctx.ReqCtx) {
g := rc.GinCtx
cli := m.MongoApp.GetMongoCli(m.GetMongoId(g))
commandForm := new(form.MongoFindCommand)
ginx.BindJsonAndValid(g, commandForm)
limit := commandForm.Limit
if limit != 0 {
biz.IsTrue(limit <= 100, "limit不能超过100")
}
opts := options.Find().SetSort(commandForm.Sort).
SetSkip(commandForm.Skip).
SetLimit(limit)
ctx := context.TODO()
cur, err := cli.Database(commandForm.Database).Collection(commandForm.Collection).Find(ctx, commandForm.Filter, opts)
biz.ErrIsNilAppendErr(err, "命令执行失败: %s")
var res []bson.M
cur.All(ctx, &res)
rc.ResData = res
}
func (m *Mongo) UpdateByIdCommand(rc *ctx.ReqCtx) {
g := rc.GinCtx
cli := m.MongoApp.GetMongoCli(m.GetMongoId(g))
commandForm := new(form.MongoUpdateByIdCommand)
ginx.BindJsonAndValid(g, commandForm)
// 解析docId文档id如果为string类型则使用ObjectId解析解析失败则为普通字符串
docId := commandForm.DocId
docIdVal, ok := docId.(string)
if ok {
objId, err := primitive.ObjectIDFromHex(docIdVal)
if err == nil {
docId = objId
}
}
res, err := cli.Database(commandForm.Database).Collection(commandForm.Collection).UpdateByID(context.TODO(), docId, commandForm.Update)
biz.ErrIsNilAppendErr(err, "命令执行失败: %s")
rc.ReqParam = commandForm
rc.ResData = res
}
func (m *Mongo) DeleteByIdCommand(rc *ctx.ReqCtx) {
g := rc.GinCtx
cli := m.MongoApp.GetMongoCli(m.GetMongoId(g))
commandForm := new(form.MongoUpdateByIdCommand)
ginx.BindJsonAndValid(g, commandForm)
// 解析docId文档id如果为string类型则使用ObjectId解析解析失败则为普通字符串
docId := commandForm.DocId
docIdVal, ok := docId.(string)
if ok {
objId, err := primitive.ObjectIDFromHex(docIdVal)
if err == nil {
docId = objId
}
}
res, err := cli.Database(commandForm.Database).Collection(commandForm.Collection).DeleteOne(context.TODO(), bson.D{{"_id", docId}})
biz.ErrIsNilAppendErr(err, "命令执行失败: %s")
rc.ReqParam = commandForm
rc.ResData = res
}
func (m *Mongo) InsertOneCommand(rc *ctx.ReqCtx) {
g := rc.GinCtx
cli := m.MongoApp.GetMongoCli(m.GetMongoId(g))
commandForm := new(form.MongoInsertCommand)
ginx.BindJsonAndValid(g, commandForm)
res, err := cli.Database(commandForm.Database).Collection(commandForm.Collection).InsertOne(context.TODO(), commandForm.Doc)
biz.ErrIsNilAppendErr(err, "命令执行失败: %s")
rc.ReqParam = commandForm
rc.ResData = res
}
// 获取请求路径上的mongo id
func (m *Mongo) GetMongoId(g *gin.Context) uint64 {
dbId, _ := strconv.Atoi(g.Param("id"))
biz.IsTrue(dbId > 0, "mongoId错误")
return uint64(dbId)
}

View File

@@ -0,0 +1,104 @@
package api
import (
"fmt"
"mayfly-go/internal/devops/api/vo"
"mayfly-go/internal/devops/application"
"mayfly-go/internal/devops/domain/entity"
sys_applicaiton "mayfly-go/internal/sys/application"
sys_entity "mayfly-go/internal/sys/domain/entity"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/ctx"
"mayfly-go/pkg/ginx"
)
type Project struct {
ProjectApp application.Project
AccountApp sys_applicaiton.Account
}
// 获取当前登录用户可以访问的项目列表
func (p *Project) GetProjectsByLoginAccount(rc *ctx.ReqCtx) {
// 获取登录用户拥有的项目ids
projectMembers := &[]entity.ProjectMember{}
p.ProjectApp.ListMember(&entity.ProjectMember{AccountId: rc.LoginAccount.Id}, projectMembers)
var pids []uint64
for _, pm := range *projectMembers {
pids = append(pids, pm.ProjectId)
}
// 获取项目信息
projects := &vo.AccountProjects{}
p.ProjectApp.ListProjectByIds(pids, projects)
rc.ResData = projects
}
func (p *Project) GetProjects(rc *ctx.ReqCtx) {
condition := &entity.Project{}
ginx.BindQuery(rc.GinCtx, condition)
// condition.Name = rc.GinCtx.Query("name")
rc.ResData = p.ProjectApp.GetPageList(condition, ginx.GetPageParam(rc.GinCtx), new([]entity.Project))
}
func (p *Project) SaveProject(rc *ctx.ReqCtx) {
project := &entity.Project{}
ginx.BindJsonAndValid(rc.GinCtx, project)
rc.ReqParam = project
project.SetBaseInfo(rc.LoginAccount)
p.ProjectApp.SaveProject(project)
}
func (p *Project) DelProject(rc *ctx.ReqCtx) {
p.ProjectApp.DelProject(uint64(ginx.QueryInt(rc.GinCtx, "id", 0)))
}
// 获取项目下的环境信息
func (p *Project) GetProjectEnvs(rc *ctx.ReqCtx) {
projectEnvs := &[]entity.ProjectEnv{}
p.ProjectApp.ListEnvByProjectId(uint64(ginx.PathParamInt(rc.GinCtx, "projectId")), projectEnvs)
rc.ResData = projectEnvs
}
//保存项目下的环境信息
func (p *Project) SaveProjectEnvs(rc *ctx.ReqCtx) {
projectEnv := &entity.ProjectEnv{}
ginx.BindJsonAndValid(rc.GinCtx, projectEnv)
rc.ReqParam = projectEnv
projectEnv.SetBaseInfo(rc.LoginAccount)
p.ProjectApp.SaveProjectEnv(projectEnv)
}
// 获取项目下的成员信息
func (p *Project) GetProjectMembers(rc *ctx.ReqCtx) {
projectMems := &[]entity.ProjectMember{}
rc.ResData = p.ProjectApp.GetMemberPage(&entity.ProjectMember{ProjectId: uint64(ginx.PathParamInt(rc.GinCtx, "projectId"))},
ginx.GetPageParam(rc.GinCtx), projectMems)
}
//保存项目的成员信息
func (p *Project) SaveProjectMember(rc *ctx.ReqCtx) {
projectMem := &entity.ProjectMember{}
ginx.BindJsonAndValid(rc.GinCtx, projectMem)
rc.ReqParam = projectMem
// 校验账号并赋值username
account := &sys_entity.Account{}
account.Id = projectMem.AccountId
biz.ErrIsNil(p.AccountApp.GetAccount(account, "Id", "Username"), "账号不存在")
projectMem.Username = account.Username
projectMem.SetBaseInfo(rc.LoginAccount)
p.ProjectApp.SaveProjectMember(projectMem)
}
//删除项目成员
func (p *Project) DelProjectMember(rc *ctx.ReqCtx) {
g := rc.GinCtx
pid := ginx.PathParamInt(g, "projectId")
aid := ginx.PathParamInt(g, "accountId")
rc.ReqParam = fmt.Sprintf("projectId: %d, accountId: %d", pid, aid)
p.ProjectApp.DeleteMember(uint64(pid), uint64(aid))
}

View File

@@ -0,0 +1,216 @@
package api
import (
"mayfly-go/internal/devops/api/form"
"mayfly-go/internal/devops/api/vo"
"mayfly-go/internal/devops/application"
"mayfly-go/internal/devops/domain/entity"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/ctx"
"mayfly-go/pkg/ginx"
"mayfly-go/pkg/utils"
"strconv"
"strings"
"time"
)
type Redis struct {
RedisApp application.Redis
ProjectApp application.Project
}
func (r *Redis) RedisList(rc *ctx.ReqCtx) {
g := rc.GinCtx
m := &entity.Redis{EnvId: uint64(ginx.QueryInt(g, "envId", 0)),
ProjectId: uint64(ginx.QueryInt(g, "projectId", 0)),
}
m.CreatorId = rc.LoginAccount.Id
rc.ResData = r.RedisApp.GetPageList(m, ginx.GetPageParam(rc.GinCtx), new([]vo.Redis))
}
func (r *Redis) Save(rc *ctx.ReqCtx) {
form := &form.Redis{}
ginx.BindJsonAndValid(rc.GinCtx, form)
rc.ReqParam = form
redis := new(entity.Redis)
utils.Copy(redis, form)
redis.SetBaseInfo(rc.LoginAccount)
r.RedisApp.Save(redis)
}
func (r *Redis) DeleteRedis(rc *ctx.ReqCtx) {
r.RedisApp.Delete(uint64(ginx.PathParamInt(rc.GinCtx, "id")))
}
func (r *Redis) RedisInfo(rc *ctx.ReqCtx) {
res, _ := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(rc.GinCtx, "id"))).Cli.Info().Result()
datas := strings.Split(res, "\r\n")
i := 0
length := len(datas)
parseMap := make(map[string]map[string]string)
for {
if i >= length {
break
}
if strings.Contains(datas[i], "#") {
key := utils.SubString(datas[i], strings.Index(datas[i], "#")+1, utils.StrLen(datas[i]))
i++
key = strings.Trim(key, " ")
sectionMap := make(map[string]string)
for {
if i >= length || !strings.Contains(datas[i], ":") {
break
}
pair := strings.Split(datas[i], ":")
i++
if len(pair) != 2 {
continue
}
sectionMap[pair[0]] = pair[1]
}
parseMap[key] = sectionMap
} else {
i++
}
}
rc.ResData = parseMap
}
// scan获取redis的key列表信息
func (r *Redis) Scan(rc *ctx.ReqCtx) {
g := rc.GinCtx
ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id")))
biz.ErrIsNilAppendErr(r.ProjectApp.CanAccess(rc.LoginAccount.Id, ri.ProjectId), "%s")
keys, cursor := ri.Scan(uint64(ginx.PathParamInt(g, "cursor")), g.Query("match"), int64(ginx.PathParamInt(g, "count")))
var keyInfoSplit []string
if len(keys) > 0 {
keyInfoLua := `
local result = {}
-- KEYS[1]为第1个参数lua数组下标从1开始
local ttl = redis.call('ttl', KEYS[1]);
local keyType = redis.call('type', KEYS[1]);
for i = 1, #KEYS do
local ttl = redis.call('ttl', KEYS[i]);
local keyType = redis.call('type', KEYS[i]);
table.insert(result, string.format("%d,%s", ttl, keyType['ok']));
end;
return table.concat(result, ".");`
// 通过lua获取 ttl,type.ttl2,type2格式以便下面切割获取ttl和type。避免多次调用ttl和type函数
keyInfos, _ := ri.Cli.Eval(keyInfoLua, keys).Result()
keyInfoSplit = strings.Split(keyInfos.(string), ".")
}
kis := make([]*vo.KeyInfo, 0)
for i, k := range keys {
ttlType := strings.Split(keyInfoSplit[i], ",")
ttl, _ := strconv.Atoi(ttlType[0])
ki := &vo.KeyInfo{Key: k, Type: ttlType[1], Ttl: int64(ttl)}
kis = append(kis, ki)
}
size, _ := ri.Cli.DBSize().Result()
rc.ResData = &vo.Keys{Cursor: cursor, Keys: kis, DbSize: size}
}
func (r *Redis) DeleteKey(rc *ctx.ReqCtx) {
g := rc.GinCtx
key := g.Query("key")
biz.NotEmpty(key, "key不能为空")
ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id")))
biz.ErrIsNilAppendErr(r.ProjectApp.CanAccess(rc.LoginAccount.Id, ri.ProjectId), "%s")
rc.ReqParam = key
ri.Cli.Del(key)
}
func (r *Redis) checkKey(rc *ctx.ReqCtx) (*application.RedisInstance, string) {
g := rc.GinCtx
key := g.Query("key")
biz.NotEmpty(key, "key不能为空")
ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id")))
biz.ErrIsNilAppendErr(r.ProjectApp.CanAccess(rc.LoginAccount.Id, ri.ProjectId), "%s")
return ri, key
}
func (r *Redis) GetStringValue(rc *ctx.ReqCtx) {
ri, key := r.checkKey(rc)
str, err := ri.Cli.Get(key).Result()
biz.ErrIsNilAppendErr(err, "获取字符串值失败: %s")
rc.ResData = str
}
func (r *Redis) GetHashValue(rc *ctx.ReqCtx) {
ri, key := r.checkKey(rc)
res, err := ri.Cli.HGetAll(key).Result()
biz.ErrIsNilAppendErr(err, "获取hash值失败: %s")
rc.ResData = res
}
func (r *Redis) SetStringValue(rc *ctx.ReqCtx) {
g := rc.GinCtx
keyValue := new(form.StringValue)
ginx.BindJsonAndValid(g, keyValue)
ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id")))
biz.ErrIsNilAppendErr(r.ProjectApp.CanAccess(rc.LoginAccount.Id, ri.ProjectId), "%s")
str, err := ri.Cli.Set(keyValue.Key, keyValue.Value, time.Second*time.Duration(keyValue.Timed)).Result()
biz.ErrIsNilAppendErr(err, "保存字符串值失败: %s")
rc.ResData = str
}
func (r *Redis) SetHashValue(rc *ctx.ReqCtx) {
g := rc.GinCtx
hashValue := new(form.HashValue)
ginx.BindJsonAndValid(g, hashValue)
ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id")))
biz.ErrIsNilAppendErr(r.ProjectApp.CanAccess(rc.LoginAccount.Id, ri.ProjectId), "%s")
key := hashValue.Key
// 简单处理->先删除,后新增
ri.Cli.Del(key)
for _, v := range hashValue.Value {
res := ri.Cli.HSet(key, v["key"].(string), v["value"])
biz.ErrIsNilAppendErr(res.Err(), "保存hash值失败: %s")
}
if hashValue.Timed != -1 {
ri.Cli.Expire(key, time.Second*time.Duration(hashValue.Timed))
}
}
func (r *Redis) GetSetValue(rc *ctx.ReqCtx) {
ri, key := r.checkKey(rc)
res, err := ri.Cli.SMembers(key).Result()
biz.ErrIsNilAppendErr(err, "获取set值失败: %s")
rc.ResData = res
}
func (r *Redis) SetSetValue(rc *ctx.ReqCtx) {
g := rc.GinCtx
keyvalue := new(form.SetValue)
ginx.BindJsonAndValid(g, keyvalue)
ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id")))
biz.ErrIsNilAppendErr(r.ProjectApp.CanAccess(rc.LoginAccount.Id, ri.ProjectId), "%s")
key := keyvalue.Key
// 简单处理->先删除,后新增
ri.Cli.Del(key)
ri.Cli.SAdd(key, keyvalue.Value...)
if keyvalue.Timed != -1 {
ri.Cli.Expire(key, time.Second*time.Duration(keyvalue.Timed))
}
}

View File

@@ -0,0 +1,21 @@
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"`
Username *string `json:"username"`
ProjectId *int64 `json:"projectId"`
Project *string `json:"project"`
Env *string `json:"env"`
EnvId *int64 `json:"envId"`
CreateTime *time.Time `json:"createTime"`
Creator *string `json:"creator"`
CreatorId *int64 `json:"creatorId"`
}

View File

@@ -0,0 +1,10 @@
package vo
// 用户选择项目
type AccountProject struct {
Id uint64 `json:"id"`
Name string `json:"name"`
Remark string `json:"remark"`
}
type AccountProjects []AccountProject

View File

@@ -0,0 +1,29 @@
package vo
import "time"
type Redis struct {
Id *int64 `json:"id"`
// Name *string `json:"name"`
Host *string `json:"host"`
Db int `json:"db"`
ProjectId *int64 `json:"projectId"`
Project *string `json:"project"`
Env *string `json:"env"`
EnvId *int64 `json:"envId"`
CreateTime *time.Time `json:"createTime"`
Creator *string `json:"creator"`
CreatorId *int64 `json:"creatorId"`
}
type Keys struct {
Cursor uint64 `json:"cursor"`
Keys []*KeyInfo `json:"keys"`
DbSize int64 `json:"dbSize"`
}
type KeyInfo struct {
Key string `json:"key"`
Ttl int64 `json:"ttl"`
Type string `json:"type"`
}

View File

@@ -0,0 +1,64 @@
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 *uint64 `json:"id"`
ProjectId uint64 `json:"projectId"`
ProjectName string `json:"projectName"`
Name *string `json:"name"`
Username *string `json:"username"`
Ip *string `json:"ip"`
Port *int `json:"port"`
Status *int8 `json:"status"`
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"`
HasCli bool `json:"hasCli" gorm:"-"`
Remark *string `json:"remark"`
}
type MachineScriptVO struct {
Id *int64 `json:"id"`
Name *string `json:"name"`
Script *string `json:"script"`
Type *int `json:"type"`
Description *string `json:"description"`
Params *string `json:"params"`
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,419 @@
package application
import (
"database/sql"
"errors"
"fmt"
"mayfly-go/internal/devops/domain/entity"
"mayfly-go/internal/devops/domain/repository"
"mayfly-go/internal/devops/infrastructure/persistence"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/cache"
"mayfly-go/pkg/global"
"mayfly-go/pkg/model"
"mayfly-go/pkg/utils"
"strconv"
"strings"
"sync"
"time"
)
type Db interface {
// 分页获取机器脚本信息列表
GetPageList(condition *entity.Db, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult
Count(condition *entity.Db) int64
// 根据条件获取
GetDbBy(condition *entity.Db, cols ...string) error
// 根据id获取
GetById(id uint64, cols ...string) *entity.Db
Save(entity *entity.Db)
// 删除数据库信息
Delete(id uint64)
// 获取数据库连接实例
// @param id 数据库实例id
// @param db 数据库
GetDbInstance(id uint64, db string) *DbInstance
}
type dbAppImpl struct {
dbRepo repository.Db
dbSqlRepo repository.DbSql
}
var DbApp Db = &dbAppImpl{
dbRepo: persistence.DbDao,
dbSqlRepo: persistence.DbSqlDao,
}
// 分页获取数据库信息列表
func (d *dbAppImpl) GetPageList(condition *entity.Db, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
return d.dbRepo.GetDbList(condition, pageParam, toEntity, orderBy...)
}
func (d *dbAppImpl) Count(condition *entity.Db) int64 {
return d.dbRepo.Count(condition)
}
// 根据条件获取
func (d *dbAppImpl) GetDbBy(condition *entity.Db, cols ...string) error {
return d.dbRepo.GetDb(condition, cols...)
}
// 根据id获取
func (d *dbAppImpl) GetById(id uint64, cols ...string) *entity.Db {
return d.dbRepo.GetById(id, cols...)
}
func (d *dbAppImpl) Save(dbEntity *entity.Db) {
// 默认tcp连接
dbEntity.Network = "tcp"
// 测试连接
if dbEntity.Password != "" {
TestConnection(*dbEntity)
}
// 查找是否存在该库
oldDb := &entity.Db{Host: dbEntity.Host, Port: dbEntity.Port, EnvId: dbEntity.EnvId}
err := d.GetDbBy(oldDb)
if dbEntity.Id == 0 {
biz.NotEmpty(dbEntity.Password, "密码不能为空")
biz.IsTrue(err != nil, "该数据库实例已存在")
d.dbRepo.Insert(dbEntity)
return
}
// 如果存在该库,则校验修改的库是否为该库
if err == nil {
biz.IsTrue(oldDb.Id == dbEntity.Id, "该数据库实例已存在")
}
dbId := dbEntity.Id
old := d.GetById(dbId)
var oldDbs []interface{}
for _, v := range strings.Split(old.Database, " ") {
oldDbs = append(oldDbs, v)
}
var newDbs []interface{}
for _, v := range strings.Split(dbEntity.Database, " ") {
newDbs = append(newDbs, v)
}
// 比较新旧数据库列表,需要将移除的数据库相关联的信息删除
_, delDb, _ := utils.ArrayCompare(newDbs, oldDbs, func(i1, i2 interface{}) bool {
return i1.(string) == i2.(string)
})
for _, v := range delDb {
// 先关闭数据库连接
CloseDb(dbEntity.Id, v.(string))
// 删除该库关联的所有sql记录
d.dbSqlRepo.DeleteBy(&entity.DbSql{DbId: dbId, Db: v.(string)})
}
d.dbRepo.Update(dbEntity)
}
func (d *dbAppImpl) Delete(id uint64) {
db := d.GetById(id)
dbs := strings.Split(db.Database, " ")
for _, v := range dbs {
// 关闭连接
CloseDb(id, v)
}
d.dbRepo.Delete(id)
// 删除该库下用户保存的所有sql信息
d.dbSqlRepo.DeleteBy(&entity.DbSql{DbId: id})
}
var mutex sync.Mutex
func (da *dbAppImpl) GetDbInstance(id uint64, db string) *DbInstance {
mutex.Lock()
defer mutex.Unlock()
// Id不为0则为需要缓存
needCache := id != 0
if needCache {
load, ok := dbCache.Get(GetDbCacheKey(id, db))
if ok {
return load.(*DbInstance)
}
}
d := da.GetById(id)
biz.NotNil(d, "数据库信息不存在")
biz.IsTrue(strings.Contains(d.Database, db), "未配置该库的操作权限")
global.Log.Infof("连接db: %s:%d/%s", d.Host, d.Port, db)
// 将数据库替换为要访问的数据库,原本数据库为空格拼接的所有库
d.Database = db
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 {
global.Log.Errorf("连接db失败: %s:%d/%s", d.Host, d.Port, db)
panic(biz.NewBizErr(fmt.Sprintf("数据库连接失败: %s", perr.Error())))
}
// 最大连接周期超过时间的连接就close
// DB.SetConnMaxLifetime(100 * time.Second)
// 设置最大连接数
DB.SetMaxOpenConns(2)
// 设置闲置连接数
DB.SetMaxIdleConns(1)
cacheKey := GetDbCacheKey(id, db)
dbi := &DbInstance{Id: cacheKey, Type: d.Type, ProjectId: d.ProjectId, db: DB}
if needCache {
dbCache.Put(cacheKey, dbi)
}
return dbi
}
//------------------------------------------------------------------------------
// 客户端连接缓存30分钟内没有访问则会被关闭, key为数据库实例id:数据库
var dbCache = cache.NewTimedCache(30*time.Minute, 5*time.Second).
WithUpdateAccessTime(true).
OnEvicted(func(key interface{}, value interface{}) {
global.Log.Info(fmt.Sprintf("删除db连接缓存 id = %s", key))
value.(*DbInstance).Close()
})
func GetDbCacheKey(dbId uint64, db string) string {
return fmt.Sprintf("%d:%s", dbId, db)
}
func GetDbInstanceByCache(id string) *DbInstance {
if load, ok := dbCache.Get(id); ok {
return load.(*DbInstance)
}
return nil
}
func TestConnection(d entity.Db) {
// 验证第一个库是否可以连接即可
d.Database = strings.Split(d.Database, " ")[0]
DB, err := sql.Open(d.Type, getDsn(&d))
biz.ErrIsNil(err, "Open %s failed, err:%v\n", d.Type, err)
defer DB.Close()
perr := DB.Ping()
biz.ErrIsNilAppendErr(perr, "数据库连接失败: %s")
}
// db实例
type DbInstance struct {
Id string
Type string
ProjectId uint64
db *sql.DB
}
// 执行查询语句
// 依次返回 列名数组结果map错误
func (d *DbInstance) SelectData(sql string) ([]string, []map[string]string, error) {
sql = strings.Trim(sql, " ")
isSelect := strings.HasPrefix(sql, "SELECT") || strings.HasPrefix(sql, "select")
isShow := strings.HasPrefix(sql, "show")
if !isSelect && !isShow {
return nil, nil, errors.New("该sql非查询语句")
}
// 没加limit则默认限制50条
if isSelect && !strings.Contains(sql, "limit") && !strings.Contains(sql, "LIMIT") {
sql = sql + " LIMIT 50"
}
rows, err := d.db.Query(sql)
if err != nil {
return nil, 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)
// 列名
colNames := make([]string, 0)
// 是否第一次遍历,列名数组只需第一次遍历时加入
isFirst := true
for rows.Next() {
// 不Scan也会导致等待该链接实际处于未工作的状态然后也会导致连接数迅速达到最大
err := rows.Scan(scans...)
if err != nil {
return nil, nil, err
}
// 每行数据
rowData := make(map[string]string)
// 把vals中的数据复制到row中
for k, v := range vals {
key := cols[k]
// 如果是密码字段,则脱敏显示
if key == "password" {
v = []byte("******")
}
if isFirst {
colNames = append(colNames, key)
}
// 这里把[]byte数据转成string
rowData[key] = string(v)
}
//放入结果集
result = append(result, rowData)
isFirst = false
}
return colNames, 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()
}
// 获取dataSourceName
func getDsn(d *entity.Db) string {
if d.Type == "mysql" {
return fmt.Sprintf("%s:%s@%s(%s:%d)/%s?timeout=8s", d.Username, d.Password, d.Network, d.Host, d.Port, d.Database)
}
return ""
}
// 删除db缓存并关闭该数据库所有连接
func CloseDb(dbId uint64, db string) {
dbCache.Delete(GetDbCacheKey(dbId, db))
}
//-----------------------------------元数据-------------------------------------------
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_TABLE_INFO = `SELECT table_name tableName, table_comment tableComment, table_rows tableRows,
data_length dataLength, index_length indexLength, create_time createTime
FROM information_schema.tables
WHERE table_schema = (SELECT database())`
// mysql 索引信息
MYSQL_INDEX_INFO = `SELECT index_name indexName, column_name columnName, index_type indexType,
SEQ_IN_INDEX seqInIndex, INDEX_COMMENT indexComment
FROM information_schema.STATISTICS
WHERE table_schema = (SELECT database()) AND table_name = '%s'`
// 默认每次查询列元信息数量
DEFAULT_COLUMN_SIZE = 2000
// mysql 列信息元数据
MYSQL_COLOUMN_MA = `SELECT table_name tableName, column_name columnName, column_type columnType,
column_comment columnComment, column_key columnKey, extra, is_nullable nullable from information_schema.columns
WHERE table_name in (%s) AND table_schema = (SELECT database()) ORDER BY tableName, ordinal_position limit %d, %d`
// mysql 列信息元数据总数
MYSQL_COLOUMN_MA_COUNT = `SELECT COUNT(*) maNum from information_schema.columns
WHERE table_name in (%s) AND table_schema = (SELECT database())`
)
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(tableNames ...string) []map[string]string {
var sql, tableName string
for i := 0; i < len(tableNames); i++ {
if i != 0 {
tableName = tableName + ", "
}
tableName = tableName + "'" + tableNames[i] + "'"
}
var countSqlTmp string
var sqlTmp string
if d.Type == "mysql" {
countSqlTmp = MYSQL_COLOUMN_MA_COUNT
sqlTmp = MYSQL_COLOUMN_MA
}
countSql := fmt.Sprintf(countSqlTmp, tableName)
_, countRes, _ := d.SelectData(countSql)
// 查询出所有列信息总数,手动分页获取所有数据
maCount, _ := strconv.Atoi(countRes[0]["maNum"])
// 计算需要查询的页数
pageNum := maCount / DEFAULT_COLUMN_SIZE
if maCount%DEFAULT_COLUMN_SIZE > 0 {
pageNum++
}
res := make([]map[string]string, 0)
for index := 0; index < pageNum; index++ {
sql = fmt.Sprintf(sqlTmp, tableName, index*DEFAULT_COLUMN_SIZE, DEFAULT_COLUMN_SIZE)
_, result, err := d.SelectData(sql)
biz.ErrIsNilAppendErr(err, "获取数据库列信息失败: %s")
res = append(res, result...)
}
return res
}
func (d *DbInstance) GetTableInfos() []map[string]string {
var sql string
if d.Type == "mysql" {
sql = MYSQL_TABLE_INFO
}
_, res, _ := d.SelectData(sql)
return res
}
func (d *DbInstance) GetTableIndex(tableName string) []map[string]string {
var sql string
if d.Type == "mysql" {
sql = fmt.Sprintf(MYSQL_INDEX_INFO, tableName)
}
_, res, _ := d.SelectData(sql)
return res
}
func (d *DbInstance) GetCreateTableDdl(tableName string) []map[string]string {
var sql string
if d.Type == "mysql" {
sql = fmt.Sprintf("show create table %s ", tableName)
}
_, res, _ := d.SelectData(sql)
return res
}

View File

@@ -0,0 +1,128 @@
package application
import (
"mayfly-go/internal/devops/domain/entity"
"mayfly-go/internal/devops/domain/repository"
"mayfly-go/internal/devops/infrastructure/machine"
"mayfly-go/internal/devops/infrastructure/persistence"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/model"
"gorm.io/gorm"
)
type Machine interface {
// 根据条件获取账号信息
GetMachine(condition *entity.Machine, cols ...string) error
Save(entity *entity.Machine)
// 调整机器状态
ChangeStatus(id uint64, status int8)
Count(condition *entity.Machine) int64
Delete(id uint64)
// 根据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 machineAppImpl struct {
machineRepo repository.Machine
}
var MachineApp Machine = &machineAppImpl{machineRepo: persistence.MachineDao}
// 分页获取机器信息列表
func (m *machineAppImpl) GetMachineList(condition *entity.Machine, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
return m.machineRepo.GetMachineList(condition, pageParam, toEntity, orderBy...)
}
func (m *machineAppImpl) Count(condition *entity.Machine) int64 {
return m.machineRepo.Count(condition)
}
// 根据条件获取机器信息
func (m *machineAppImpl) Save(me *entity.Machine) {
// ’修改机器信息且密码不为空‘ or ‘新增’需要测试是否可连接
if (me.Id != 0 && me.Password != "") || me.Id == 0 {
biz.ErrIsNilAppendErr(machine.TestConn(me), "该机器无法连接: %s")
}
oldMachine := &entity.Machine{Ip: me.Ip, Port: me.Port, Username: me.Username}
err := m.GetMachine(oldMachine)
if me.Id != 0 {
// 如果存在该库,则校验修改的库是否为该库
if err == nil {
biz.IsTrue(oldMachine.Id == me.Id, "该机器信息已存在")
}
// 关闭连接
machine.DeleteCli(me.Id)
m.machineRepo.UpdateById(me)
} else {
biz.IsTrue(err != nil, "该机器信息已存在")
// 新增机器,默认启用状态
me.Status = entity.MachineStatusEnable
m.machineRepo.Create(me)
}
}
func (m *machineAppImpl) ChangeStatus(id uint64, status int8) {
if status == entity.MachineStatusDisable {
// 关闭连接
machine.DeleteCli(id)
}
machine := new(entity.Machine)
machine.Id = id
machine.Status = status
m.machineRepo.UpdateById(machine)
}
// 根据条件获取机器信息
func (m *machineAppImpl) Delete(id uint64) {
// 关闭连接
machine.DeleteCli(id)
model.Tx(
func(db *gorm.DB) error {
// 删除machine表信息
return db.Delete(new(entity.Machine), "id = ?", id).Error
},
func(db *gorm.DB) error {
// 删除machine_file
machineFile := &entity.MachineFile{MachineId: id}
return db.Where(machineFile).Delete(machineFile).Error
},
func(db *gorm.DB) error {
// 删除machine_script
machineScript := &entity.MachineScript{MachineId: id}
return db.Where(machineScript).Delete(machineScript).Error
},
)
}
// 根据条件获取机器信息
func (m *machineAppImpl) GetMachine(condition *entity.Machine, cols ...string) error {
return m.machineRepo.GetMachine(condition, cols...)
}
func (m *machineAppImpl) GetById(id uint64, cols ...string) *entity.Machine {
return m.machineRepo.GetById(id, cols...)
}
func (m *machineAppImpl) GetCli(id uint64) *machine.Cli {
cli, err := machine.GetCli(id, func(machineId uint64) *entity.Machine {
machine := m.GetById(machineId)
biz.IsTrue(machine.Status == entity.MachineStatusEnable, "该机器已被停用")
return machine
})
biz.ErrIsNilAppendErr(err, "获取客户端错误: %s")
return cli
}

View File

@@ -0,0 +1,174 @@
package application
import (
"io"
"io/fs"
"mayfly-go/internal/devops/domain/entity"
"mayfly-go/internal/devops/domain/repository"
"mayfly-go/internal/devops/infrastructure/persistence"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/model"
"os"
"strings"
"github.com/pkg/sftp"
)
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
Save(entity *entity.MachineFile)
Delete(id uint64)
/** sftp 相关操作 **/
// 读取目录
ReadDir(fid uint64, path string) []fs.FileInfo
// 读取文件内容
ReadFile(fileId uint64, path string) *sftp.File
// 写文件
WriteFileContent(fileId uint64, path string, content []byte)
// 文件上传
UploadFile(fileId uint64, path, filename string, reader io.Reader)
// 移除文件
RemoveFile(fileId uint64, path string)
}
type machineFileAppImpl struct {
machineFileRepo repository.MachineFile
machineRepo repository.Machine
}
// 实现类单例
var MachineFileApp MachineFile = &machineFileAppImpl{
machineRepo: persistence.MachineDao,
machineFileRepo: persistence.MachineFileDao,
}
// 分页获取机器脚本信息列表
func (m *machineFileAppImpl) GetPageList(condition *entity.MachineFile, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
return m.machineFileRepo.GetPageList(condition, pageParam, toEntity, orderBy...)
}
// 根据条件获取
func (m *machineFileAppImpl) GetMachineFile(condition *entity.MachineFile, cols ...string) error {
return m.machineFileRepo.GetMachineFile(condition, cols...)
}
// 根据id获取
func (m *machineFileAppImpl) GetById(id uint64, cols ...string) *entity.MachineFile {
return m.machineFileRepo.GetById(id, cols...)
}
// 保存机器文件配置
func (m *machineFileAppImpl) 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 *machineFileAppImpl) Delete(id uint64) {
m.machineFileRepo.Delete(id)
}
func (m *machineFileAppImpl) 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 *machineFileAppImpl) ReadFile(fileId uint64, path string) *sftp.File {
path, machineId := m.checkAndReturnPathMid(fileId, path)
sftpCli := m.getSftpCli(machineId)
// 读取文件内容
fc, err := sftpCli.Open(path)
biz.ErrIsNilAppendErr(err, "打开文件失败: %s")
return fc
}
// 写文件内容
func (m *machineFileAppImpl) 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 *machineFileAppImpl) UploadFile(fileId uint64, path, filename string, reader io.Reader) {
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()
io.Copy(createfile, reader)
}
// 删除文件
func (m *machineFileAppImpl) 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, _ := file.Stat()
if fi.IsDir() {
err = sftpCli.RemoveDirectory(path)
} else {
err = sftpCli.Remove(path)
}
biz.ErrIsNilAppendErr(err, "删除文件失败: %s")
}
// 获取sftp client
func (m *machineFileAppImpl) getSftpCli(machineId uint64) *sftp.Client {
return MachineApp.GetCli(machineId).GetSftpCli()
}
// 校验并返回实际可访问的文件path
func (m *machineFileAppImpl) 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/internal/devops/domain/entity"
"mayfly-go/internal/devops/domain/repository"
"mayfly-go/internal/devops/infrastructure/persistence"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/model"
)
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
Save(entity *entity.MachineScript)
Delete(id uint64)
}
type machineScriptAppImpl struct {
machineScriptRepo repository.MachineScript
machineRepo repository.Machine
}
const Common_Script_Machine_Id = 9999999
// 实现类单例
var MachineScriptApp MachineScript = &machineScriptAppImpl{
machineRepo: persistence.MachineDao,
machineScriptRepo: persistence.MachineScriptDao}
// 分页获取机器脚本信息列表
func (m *machineScriptAppImpl) GetPageList(condition *entity.MachineScript, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
return m.machineScriptRepo.GetPageList(condition, pageParam, toEntity, orderBy...)
}
// 根据条件获取
func (m *machineScriptAppImpl) GetMachineScript(condition *entity.MachineScript, cols ...string) error {
return m.machineScriptRepo.GetMachineScript(condition, cols...)
}
// 根据id获取
func (m *machineScriptAppImpl) GetById(id uint64, cols ...string) *entity.MachineScript {
return m.machineScriptRepo.GetById(id, cols...)
}
// 保存机器脚本
func (m *machineScriptAppImpl) 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 *machineScriptAppImpl) Delete(id uint64) {
m.machineScriptRepo.Delete(id)
}

View File

@@ -0,0 +1,133 @@
package application
import (
"context"
"mayfly-go/internal/devops/domain/entity"
"mayfly-go/internal/devops/domain/repository"
"mayfly-go/internal/devops/infrastructure/persistence"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/cache"
"mayfly-go/pkg/global"
"mayfly-go/pkg/model"
"time"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
type Mongo interface {
// 分页获取机器脚本信息列表
GetPageList(condition *entity.Mongo, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult
Count(condition *entity.Mongo) int64
// 根据条件获取
GetBy(condition *entity.Mongo, cols ...string) error
// 根据id获取
GetById(id uint64, cols ...string) *entity.Mongo
Save(entity *entity.Mongo)
// 删除数据库信息
Delete(id uint64)
// 获取mongo连接client
// @param id mongo id
GetMongoCli(id uint64) *mongo.Client
}
type mongoAppImpl struct {
mongoRepo repository.Mongo
}
var MongoApp Mongo = &mongoAppImpl{
mongoRepo: persistence.MongoDao,
}
// 分页获取数据库信息列表
func (d *mongoAppImpl) GetPageList(condition *entity.Mongo, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
return d.mongoRepo.GetList(condition, pageParam, toEntity, orderBy...)
}
func (d *mongoAppImpl) Count(condition *entity.Mongo) int64 {
return d.mongoRepo.Count(condition)
}
// 根据条件获取
func (d *mongoAppImpl) GetBy(condition *entity.Mongo, cols ...string) error {
return d.mongoRepo.Get(condition, cols...)
}
// 根据id获取
func (d *mongoAppImpl) GetById(id uint64, cols ...string) *entity.Mongo {
return d.mongoRepo.GetById(id, cols...)
}
func (d *mongoAppImpl) Delete(id uint64) {
d.mongoRepo.Delete(id)
DeleteMongoCache(id)
}
func (d *mongoAppImpl) Save(m *entity.Mongo) {
if m.Id == 0 {
d.mongoRepo.Insert(m)
} else {
// 先关闭连接
DeleteMongoCache(m.Id)
d.mongoRepo.Update(m)
}
}
func (d *mongoAppImpl) GetMongoCli(id uint64) *mongo.Client {
cli, err := GetMongoCli(id, func(u uint64) string {
mongo := d.GetById(id)
biz.NotNil(mongo, "mongo信息不存在")
return mongo.Uri
})
biz.ErrIsNilAppendErr(err, "连接mongo失败: %s")
return cli
}
// -----------------------------------------------------------
//mongo客户端连接缓存30分钟内没有访问则会被关闭
var mongoCliCache = cache.NewTimedCache(30*time.Minute, 5*time.Second).
WithUpdateAccessTime(true).
OnEvicted(func(key interface{}, value interface{}) {
global.Log.Info("关闭mongo连接: id = ", key)
value.(*mongo.Client).Disconnect(context.TODO())
})
func GetMongoCli(mongoId uint64, getMongoUri func(uint64) string) (*mongo.Client, error) {
cli, err := mongoCliCache.ComputeIfAbsent(mongoId, func(key interface{}) (interface{}, error) {
c, err := connect(getMongoUri(mongoId))
if err != nil {
return nil, err
}
return c, nil
})
if cli != nil {
return cli.(*mongo.Client), err
}
return nil, err
}
func DeleteMongoCache(mongoId uint64) {
mongoCliCache.Delete(mongoId)
}
// 连接mongo并返回client
func connect(uri string) (*mongo.Client, error) {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
client, err := mongo.Connect(ctx, options.Client().ApplyURI(uri).SetMaxPoolSize(2))
if err != nil {
return nil, err
}
if err = client.Ping(context.TODO(), nil); err != nil {
return nil, err
}
return client, err
}

View File

@@ -0,0 +1,128 @@
package application
import (
"mayfly-go/internal/devops/domain/entity"
"mayfly-go/internal/devops/domain/repository"
"mayfly-go/internal/devops/infrastructure/persistence"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/model"
)
type Project interface {
// 分页获取项目信息列表
GetPageList(condition *entity.Project, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult
Count(condition *entity.Project) int64
ListProjectByIds(ids []uint64, toEntity interface{}, orderBy ...string)
SaveProject(project *entity.Project)
DelProject(id uint64)
// 根据项目id获取所有该项目下的环境信息列表
ListEnvByProjectId(projectId uint64, listPtr interface{})
// 保存项目环境信息
SaveProjectEnv(projectEnv *entity.ProjectEnv)
// 根据条件获取项目成员信息
ListMember(condition *entity.ProjectMember, toEntity interface{}, orderBy ...string)
SaveProjectMember(pm *entity.ProjectMember)
// 根据条件获取项目成员信息
GetMemberPage(condition *entity.ProjectMember, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult
DeleteMember(projectId, accountId uint64)
// 账号是否有权限访问该项目关联的资源信息
CanAccess(accountId, projectId uint64) error
}
type projectAppImpl struct {
projectRepo repository.Project
projectEnvRepo repository.ProjectEnv
projectMemberRepo repository.ProjectMemeber
machineRepo repository.Machine
redisRepo repository.Redis
dbRepo repository.Db
}
var ProjectApp Project = &projectAppImpl{
projectRepo: persistence.ProjectRepo,
projectEnvRepo: persistence.ProjectEnvRepo,
projectMemberRepo: persistence.ProjectMemberRepo,
machineRepo: persistence.MachineDao,
redisRepo: persistence.RedisDao,
dbRepo: persistence.DbDao,
}
// 分页获取项目信息列表
func (p *projectAppImpl) GetPageList(condition *entity.Project, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
return p.projectRepo.GetPageList(condition, pageParam, toEntity, orderBy...)
}
func (p *projectAppImpl) Count(condition *entity.Project) int64 {
return p.projectRepo.Count(condition)
}
func (p *projectAppImpl) ListProjectByIds(ids []uint64, toEntity interface{}, orderBy ...string) {
p.projectRepo.GetByIdIn(ids, toEntity, orderBy...)
}
func (p *projectAppImpl) SaveProject(project *entity.Project) {
if project.Id == 0 {
p.projectRepo.Save(project)
} else {
// 防止误传导致项目名更新
project.Name = ""
p.projectRepo.Update(project)
}
}
func (p *projectAppImpl) DelProject(id uint64) {
biz.IsTrue(p.machineRepo.Count(&entity.Machine{ProjectId: id}) == 0, "请先删除该项目关联的机器信息")
biz.IsTrue(p.redisRepo.Count(&entity.Redis{ProjectId: id}) == 0, "请先删除该项目关联的redis信息")
biz.IsTrue(p.dbRepo.Count(&entity.Db{ProjectId: id}) == 0, "请先删除该项目关联的数据库信息")
p.projectRepo.Delete(id)
p.projectEnvRepo.DeleteEnvs(id)
p.projectMemberRepo.DeleteMems(id)
}
// 根据项目id获取所有该项目下的环境信息列表
func (p *projectAppImpl) ListEnvByProjectId(projectId uint64, listPtr interface{}) {
p.projectEnvRepo.ListEnv(&entity.ProjectEnv{ProjectId: projectId}, listPtr)
}
// 保存项目环境信息
func (p *projectAppImpl) SaveProjectEnv(projectEnv *entity.ProjectEnv) {
p.projectEnvRepo.Save(projectEnv)
}
// 根据条件获取项目成员信息
func (p *projectAppImpl) ListMember(condition *entity.ProjectMember, toEntity interface{}, orderBy ...string) {
p.projectMemberRepo.ListMemeber(condition, toEntity, orderBy...)
}
func (p *projectAppImpl) SaveProjectMember(pm *entity.ProjectMember) {
pms := new([]entity.ProjectMember)
p.ListMember(&entity.ProjectMember{ProjectId: pm.ProjectId, AccountId: pm.AccountId}, pms)
biz.IsTrue(len(*pms) == 0, "该成员已存在")
p.projectMemberRepo.Save(pm)
}
func (p *projectAppImpl) GetMemberPage(condition *entity.ProjectMember, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
return p.projectMemberRepo.GetPageList(condition, pageParam, toEntity, orderBy...)
}
func (p *projectAppImpl) DeleteMember(projectId, accountId uint64) {
p.projectMemberRepo.DeleteByPidMid(projectId, accountId)
}
func (p *projectAppImpl) CanAccess(accountId, projectId uint64) error {
if p.projectMemberRepo.IsExist(projectId, accountId) {
return nil
}
return biz.NewBizErr("您无权操作该资源")
}

View File

@@ -0,0 +1,161 @@
package application
import (
"fmt"
"mayfly-go/internal/devops/domain/entity"
"mayfly-go/internal/devops/domain/repository"
"mayfly-go/internal/devops/infrastructure/persistence"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/cache"
"mayfly-go/pkg/global"
"mayfly-go/pkg/model"
"time"
"github.com/go-redis/redis"
)
type Redis interface {
// 分页获取机器脚本信息列表
GetPageList(condition *entity.Redis, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult
Count(condition *entity.Redis) int64
// 根据id获取
GetById(id uint64, cols ...string) *entity.Redis
// 根据条件获取
GetRedisBy(condition *entity.Redis, cols ...string) error
Save(entity *entity.Redis)
// 删除数据库信息
Delete(id uint64)
// 获取数据库连接实例
GetRedisInstance(id uint64) *RedisInstance
}
type redisAppImpl struct {
redisRepo repository.Redis
}
var RedisApp Redis = &redisAppImpl{
redisRepo: persistence.RedisDao,
}
// 分页获取机器脚本信息列表
func (r *redisAppImpl) GetPageList(condition *entity.Redis, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
return r.redisRepo.GetRedisList(condition, pageParam, toEntity, orderBy...)
}
func (r *redisAppImpl) Count(condition *entity.Redis) int64 {
return r.redisRepo.Count(condition)
}
// 根据id获取
func (r *redisAppImpl) GetById(id uint64, cols ...string) *entity.Redis {
return r.redisRepo.GetById(id, cols...)
}
// 根据条件获取
func (r *redisAppImpl) GetRedisBy(condition *entity.Redis, cols ...string) error {
return r.redisRepo.GetRedis(condition, cols...)
}
func (r *redisAppImpl) Save(re *entity.Redis) {
TestRedisConnection(re)
// 查找是否存在该库
oldRedis := &entity.Redis{Host: re.Host, Db: re.Db}
err := r.GetRedisBy(oldRedis)
if re.Id == 0 {
biz.IsTrue(err != nil, "该库已存在")
r.redisRepo.Insert(re)
} else {
// 如果存在该库,则校验修改的库是否为该库
if err == nil {
biz.IsTrue(oldRedis.Id == re.Id, "该库已存在")
}
// 先关闭数据库连接
CloseRedis(re.Id)
r.redisRepo.Update(re)
}
}
// 删除Redis信息
func (r *redisAppImpl) Delete(id uint64) {
CloseRedis(id)
r.redisRepo.Delete(id)
}
// 获取数据库连接实例
func (r *redisAppImpl) GetRedisInstance(id uint64) *RedisInstance {
// Id不为0则为需要缓存
needCache := id != 0
if needCache {
load, ok := redisCache.Get(id)
if ok {
return load.(*RedisInstance)
}
}
// 缓存不存在则回调获取redis信息
re := r.GetById(id)
biz.NotNil(re, "redis信息不存在")
global.Log.Infof("连接redis: %s", re.Host)
rcli := redis.NewClient(&redis.Options{
Addr: re.Host,
Password: re.Password, // no password set
DB: re.Db, // use default DB
})
// 测试连接
_, e := rcli.Ping().Result()
biz.ErrIsNilAppendErr(e, "redis连接失败: %s")
ri := &RedisInstance{Id: id, ProjectId: re.ProjectId, Cli: rcli}
if needCache {
redisCache.Put(re.Id, ri)
}
return ri
}
//------------------------------------------------------------------------------
// redis客户端连接缓存30分钟内没有访问则会被关闭
var redisCache = cache.NewTimedCache(30*time.Minute, 5*time.Second).
WithUpdateAccessTime(true).
OnEvicted(func(key interface{}, value interface{}) {
global.Log.Info(fmt.Sprintf("删除redis连接缓存 id = %d", key))
value.(*RedisInstance).Cli.Close()
})
// redis实例
type RedisInstance struct {
Id uint64
ProjectId uint64
Cli *redis.Client
}
// 移除redis连接缓存并关闭redis连接
func CloseRedis(id uint64) {
redisCache.Delete(id)
}
func TestRedisConnection(re *entity.Redis) {
rcli := redis.NewClient(&redis.Options{
Addr: re.Host,
Password: re.Password, // no password set
DB: re.Db, // use default DB
})
defer rcli.Close()
// 测试连接
_, e := rcli.Ping().Result()
biz.ErrIsNilAppendErr(e, "Redis连接失败: %s")
}
func (r *RedisInstance) Scan(cursor uint64, match string, count int64) ([]string, uint64) {
keys, newcursor, err := r.Cli.Scan(cursor, match, count).Result()
biz.ErrIsNilAppendErr(err, "scan失败: %s")
return keys, newcursor
}

View File

@@ -0,0 +1,22 @@
package entity
import (
"mayfly-go/pkg/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"`
ProjectId uint64
Project string
EnvId uint64
Env string
}

View File

@@ -0,0 +1,15 @@
package entity
import (
"mayfly-go/pkg/model"
)
type DbSql struct {
model.Model `orm:"-"`
DbId uint64 `json:"dbId"`
Db string `json:"db"`
Type int `json:"type"` // 类型
Sql string `json:"sql"`
Name string `json:"name"`
}

View File

@@ -0,0 +1,23 @@
package entity
import (
"mayfly-go/pkg/model"
)
type Machine struct {
model.Model
ProjectId uint64 `json:"projectId"`
ProjectName string `json:"projectName"`
Name string `json:"name"`
Ip string `json:"ip"` // IP地址
Username string `json:"username"` // 用户名
Password string `json:"-"`
Port int `json:"port"` // 端口号
Status int8 `json:"status"` // 状态 1:启用2:停用
Remark string `json:"remark"` // 备注
}
const (
MachineStatusEnable int8 = 1 // 启用状态
MachineStatusDisable int8 = -1 // 禁用状态
)

View File

@@ -0,0 +1,13 @@
package entity
import "mayfly-go/pkg/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,13 @@
package entity
import "mayfly-go/pkg/model"
type MachineScript struct {
model.Model
Name string `json:"name"`
MachineId uint64 `json:"machineId"` // 机器id
Type int `json:"type"`
Description string `json:"description"` // 脚本描述
Params string `json:"params"` // 参数列表json
Script string `json:"script"` // 脚本内容
}

View File

@@ -0,0 +1,14 @@
package entity
import "mayfly-go/pkg/model"
type Mongo struct {
model.Model
Name string `orm:"column(name)" json:"name"`
Uri string `orm:"column(uri)" json:"uri"`
ProjectId uint64 `json:"projectId"`
Project string `json:"project"`
EnvId uint64 `json:"envId"`
Env string `json:"env"`
}

View File

@@ -0,0 +1,10 @@
package entity
import "mayfly-go/pkg/model"
// 项目
type Project struct {
model.Model
Name string `json:"name"` // 项目名
Remark string `json:"remark"` // 备注说明
}

View File

@@ -0,0 +1,11 @@
package entity
import "mayfly-go/pkg/model"
// 项目环境
type ProjectEnv struct {
model.Model
Name string `json:"name"` // 环境名
ProjectId uint64 `json:"projectId"` // 项目id
Remark string `json:"remark"` // 备注说明
}

View File

@@ -0,0 +1,11 @@
package entity
import "mayfly-go/pkg/model"
// 项目成员,用于对项目下组件的访问控制
type ProjectMember struct {
model.Model
AccountId uint64 `json:"accountId"` // 账号
Username string `json:"username"` // 账号用户名
ProjectId uint64 `json:"projectId"` // 项目id
}

View File

@@ -0,0 +1,17 @@
package entity
import (
"mayfly-go/pkg/model"
)
type Redis struct {
model.Model
Host string `orm:"column(host)" json:"host"`
Password string `orm:"column(password)" json:"-"`
Db int `orm:"column(database)" json:"db"`
ProjectId uint64
Project string
EnvId uint64
Env string
}

View File

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

View File

@@ -0,0 +1,7 @@
package repository
import "mayfly-go/internal/devops/domain/entity"
type DbSql interface {
DeleteBy(condition *entity.DbSql)
}

View File

@@ -0,0 +1,23 @@
package repository
import (
"mayfly-go/internal/devops/domain/entity"
"mayfly-go/pkg/model"
)
type Machine interface {
// 分页获取机器信息列表
GetMachineList(condition *entity.Machine, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult
Count(condition *entity.Machine) int64
// 根据条件获取账号信息
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/internal/devops/domain/entity"
"mayfly-go/pkg/model"
)
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/internal/devops/domain/entity"
"mayfly-go/pkg/model"
)
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,25 @@
package repository
import (
"mayfly-go/internal/devops/domain/entity"
"mayfly-go/pkg/model"
)
type Mongo interface {
// 分页获取列表
GetList(condition *entity.Mongo, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult
Count(condition *entity.Mongo) int64
// 根据条件获取
Get(condition *entity.Mongo, cols ...string) error
// 根据id获取
GetById(id uint64, cols ...string) *entity.Mongo
Insert(db *entity.Mongo)
Update(db *entity.Mongo)
Delete(id uint64)
}

View File

@@ -0,0 +1,20 @@
package repository
import (
"mayfly-go/internal/devops/domain/entity"
"mayfly-go/pkg/model"
)
type Project interface {
GetPageList(condition *entity.Project, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult
Count(condition *entity.Project) int64
GetByIdIn(ids []uint64, toEntity interface{}, orderBy ...string)
Save(p *entity.Project)
Update(project *entity.Project)
Delete(id uint64)
}

View File

@@ -0,0 +1,12 @@
package repository
import "mayfly-go/internal/devops/domain/entity"
type ProjectEnv interface {
// 获取项目环境列表
ListEnv(condition *entity.ProjectEnv, toEntity interface{}, orderBy ...string)
Save(entity *entity.ProjectEnv)
DeleteEnvs(projectId uint64)
}

View File

@@ -0,0 +1,24 @@
package repository
import (
"mayfly-go/internal/devops/domain/entity"
"mayfly-go/pkg/model"
)
type ProjectMemeber interface {
// 获取项目成员列表
ListMemeber(condition *entity.ProjectMember, toEntity interface{}, orderBy ...string)
Save(mp *entity.ProjectMember)
GetPageList(condition *entity.ProjectMember, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult
// 根据成员id和项目id删除关联关系
DeleteByPidMid(projectId, accountId uint64)
DeleteMems(projectId uint64)
// 是否存在指定的项目成员关联信息
IsExist(projectId, accountId uint64) bool
}

View File

@@ -0,0 +1,24 @@
package repository
import (
"mayfly-go/internal/devops/domain/entity"
"mayfly-go/pkg/model"
)
type Redis interface {
// 分页获取机器信息列表
GetRedisList(condition *entity.Redis, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult
Count(condition *entity.Redis) int64
// 根据id获取
GetById(id uint64, cols ...string) *entity.Redis
GetRedis(condition *entity.Redis, cols ...string) error
Insert(redis *entity.Redis)
Update(redis *entity.Redis)
Delete(id uint64)
}

View File

@@ -0,0 +1,186 @@
package machine
import (
"errors"
"fmt"
"mayfly-go/internal/devops/domain/entity"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/cache"
"mayfly-go/pkg/global"
"net"
"time"
"github.com/pkg/sftp"
"golang.org/x/crypto/ssh"
)
// 客户端信息
type Cli struct {
machine *entity.Machine
// ssh客户端
client *ssh.Client
sftpClient *sftp.Client
}
// 机器客户端连接缓存45分钟内没有访问则会被关闭
var cliCache = cache.NewTimedCache(45*time.Minute, 5*time.Second).
WithUpdateAccessTime(true).
OnEvicted(func(key interface{}, value interface{}) {
value.(*Cli).Close()
})
// 是否存在指定id的客户端连接
func HasCli(machineId uint64) bool {
if _, ok := cliCache.Get(machineId); ok {
return true
}
return false
}
// 删除指定机器客户端,并关闭客户端连接
func DeleteCli(id uint64) {
cliCache.Delete(id)
}
// 从缓存中获取客户端信息,不存在则回调获取机器信息函数,并新建
func GetCli(machineId uint64, getMachine func(uint64) *entity.Machine) (*Cli, error) {
cli, err := cliCache.ComputeIfAbsent(machineId, func(key interface{}) (interface{}, error) {
c, err := newClient(getMachine(machineId))
if err != nil {
return nil, err
}
return c, nil
})
if cli != nil {
return cli.(*Cli), err
}
return nil, err
}
//根据机器信息创建客户端对象
func newClient(machine *entity.Machine) (*Cli, error) {
if machine == nil {
return nil, errors.New("机器不存在")
}
global.Log.Infof("[%s]机器连接:%s:%d", machine.Name, 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() {
m := c.machine
global.Log.Info(fmt.Sprintf("关闭机器客户端连接-> id: %d, name: %s, ip: %s", m.Id, m.Name, m.Ip))
if c.client != nil {
c.client.Close()
c.client = nil
}
if c.sftpClient != nil {
c.sftpClient.Close()
c.sftpClient = nil
}
}
// 获取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)
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) GetMachine() *entity.Machine {
return c.machine
}

View File

@@ -0,0 +1,143 @@
package machine
import (
"fmt"
"mayfly-go/pkg/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,19 @@
package machine
const StatsShell = `
cat /proc/uptime
echo '-----'
/bin/hostname -f
echo '-----'
cat /proc/loadavg
echo '-----'
cat /proc/meminfo
echo '-----'
df -B1
echo '-----'
/sbin/ip -o addr
echo '-----'
/bin/cat /proc/net/dev
echo '-----'
top -b -n 1 | grep Cpu
`

View File

@@ -0,0 +1,277 @@
package machine
import (
"bufio"
"fmt"
"strconv"
"strings"
"time"
)
type FSInfo struct {
MountPoint string
Used uint64
Free uint64
}
type NetIntfInfo struct {
IPv4 string
IPv6 string
Rx uint64
Tx uint64
}
type CPUInfo struct {
User float32
Nice float32
System float32
Idle float32
Iowait float32
Irq float32
SoftIrq float32
Steal float32
Guest float32
}
type Stats struct {
Uptime string
Hostname string
Load1 string
Load5 string
Load10 string
RunningProcs string
TotalProcs string
MemTotal uint64
MemFree uint64
MemBuffers uint64
MemAvailable uint64
MemCached uint64
SwapTotal uint64
SwapFree uint64
FSInfos []FSInfo
NetIntf map[string]NetIntfInfo
CPU CPUInfo // or []CPUInfo to get all the cpu-core's stats?
}
func (c *Cli) GetAllStats() *Stats {
res, _ := c.Run(StatsShell)
infos := strings.Split(*res, "-----")
stats := new(Stats)
getUptime(infos[0], stats)
getHostname(infos[1], stats)
getLoad(infos[2], stats)
getMemInfo(infos[3], stats)
getFSInfo(infos[4], stats)
getInterfaces(infos[5], stats)
getInterfaceInfo(infos[6], stats)
getCPU(infos[7], stats)
return stats
}
func getUptime(uptime string, stats *Stats) (err error) {
parts := strings.Fields(uptime)
if len(parts) == 2 {
var upsecs float64
upsecs, err = strconv.ParseFloat(parts[0], 64)
if err != nil {
return
}
stats.Uptime = fmtUptime(time.Duration(upsecs * 1e9))
}
return
}
func fmtUptime(dur time.Duration) string {
dur = dur - (dur % time.Second)
var days int
for dur.Hours() > 24.0 {
days++
dur -= 24 * time.Hour
}
s1 := dur.String()
s2 := ""
if days > 0 {
s2 = fmt.Sprintf("%dd ", days)
}
for _, ch := range s1 {
s2 += string(ch)
if ch == 'h' || ch == 'm' {
s2 += " "
}
}
return s2
}
func getHostname(hostname string, stats *Stats) (err error) {
stats.Hostname = strings.TrimSpace(hostname)
return
}
func getLoad(loadInfo string, stats *Stats) (err error) {
parts := strings.Fields(loadInfo)
if len(parts) == 5 {
stats.Load1 = parts[0]
stats.Load5 = parts[1]
stats.Load10 = parts[2]
if i := strings.Index(parts[3], "/"); i != -1 {
stats.RunningProcs = parts[3][0:i]
if i+1 < len(parts[3]) {
stats.TotalProcs = parts[3][i+1:]
}
}
}
return
}
func getMemInfo(memInfo string, stats *Stats) (err error) {
// "/bin/cat /proc/meminfo"
scanner := bufio.NewScanner(strings.NewReader(memInfo))
for scanner.Scan() {
line := scanner.Text()
parts := strings.Fields(line)
if len(parts) == 3 {
val, err := strconv.ParseUint(parts[1], 10, 64)
if err != nil {
continue
}
val *= 1024
switch parts[0] {
case "MemTotal:":
stats.MemTotal = val
case "MemFree:":
stats.MemFree = val
case "Buffers:":
stats.MemBuffers = val
case "Cached:":
stats.MemCached = val
case "SwapTotal:":
stats.SwapTotal = val
case "SwapFree:":
stats.SwapFree = val
case "MemAvailable:":
stats.MemAvailable = val
}
}
}
return
}
func getFSInfo(fsInfo string, stats *Stats) (err error) {
// "/bin/df -B1"
scanner := bufio.NewScanner(strings.NewReader(fsInfo))
flag := 0
for scanner.Scan() {
line := scanner.Text()
parts := strings.Fields(line)
n := len(parts)
dev := n > 0 && strings.Index(parts[0], "/dev/") == 0
if n == 1 && dev {
flag = 1
} else if (n == 5 && flag == 1) || (n == 6 && dev) {
i := flag
flag = 0
used, err := strconv.ParseUint(parts[2-i], 10, 64)
if err != nil {
continue
}
free, err := strconv.ParseUint(parts[3-i], 10, 64)
if err != nil {
continue
}
stats.FSInfos = append(stats.FSInfos, FSInfo{
parts[5-i], used, free,
})
}
}
return
}
func getInterfaces(iInfo string, stats *Stats) (err error) {
// "/sbin/ip -o addr"
if stats.NetIntf == nil {
stats.NetIntf = make(map[string]NetIntfInfo)
}
scanner := bufio.NewScanner(strings.NewReader(iInfo))
for scanner.Scan() {
line := scanner.Text()
parts := strings.Fields(line)
if len(parts) >= 4 && (parts[2] == "inet" || parts[2] == "inet6") {
ipv4 := parts[2] == "inet"
intfname := parts[1]
if info, ok := stats.NetIntf[intfname]; ok {
if ipv4 {
info.IPv4 = parts[3]
} else {
info.IPv6 = parts[3]
}
stats.NetIntf[intfname] = info
} else {
info := NetIntfInfo{}
if ipv4 {
info.IPv4 = parts[3]
} else {
info.IPv6 = parts[3]
}
stats.NetIntf[intfname] = info
}
}
}
return
}
func getInterfaceInfo(iInfo string, stats *Stats) (err error) {
// /bin/cat /proc/net/dev
if stats.NetIntf == nil {
return
} // should have been here already
scanner := bufio.NewScanner(strings.NewReader(iInfo))
for scanner.Scan() {
line := scanner.Text()
parts := strings.Fields(line)
if len(parts) == 17 {
intf := strings.TrimSpace(parts[0])
intf = strings.TrimSuffix(intf, ":")
if info, ok := stats.NetIntf[intf]; ok {
rx, err := strconv.ParseUint(parts[1], 10, 64)
if err != nil {
continue
}
tx, err := strconv.ParseUint(parts[9], 10, 64)
if err != nil {
continue
}
info.Rx = rx
info.Tx = tx
stats.NetIntf[intf] = info
}
}
}
return
}
func getCPU(cpuInfo string, stats *Stats) (err error) {
// %Cpu(s): 6.1 us, 3.0 sy, 0.0 ni, 90.9 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
value := strings.Split(cpuInfo, ":")[1]
values := strings.Split(value, ",")
us, _ := strconv.ParseFloat(strings.Split(strings.TrimSpace(values[0]), " ")[0], 32)
stats.CPU.User = float32(us)
sy, _ := strconv.ParseFloat(strings.Split(strings.TrimSpace(values[1]), " ")[0], 32)
stats.CPU.System = float32(sy)
id, _ := strconv.ParseFloat(strings.Split(strings.TrimSpace(values[3]), " ")[0], 32)
stats.CPU.Idle = float32(id)
wa, _ := strconv.ParseFloat(strings.Split(strings.TrimSpace(values[4]), " ")[0], 32)
stats.CPU.Iowait = float32(wa)
return nil
}

View File

@@ -0,0 +1,195 @@
package machine
import (
"bytes"
"encoding/json"
"io"
"mayfly-go/pkg/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,67 @@
package persistence
import (
"fmt"
"mayfly-go/internal/devops/domain/entity"
"mayfly-go/internal/devops/domain/repository"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/model"
)
type dbRepo struct{}
var DbDao repository.Db = &dbRepo{}
// 分页获取数据库信息列表
func (d *dbRepo) GetDbList(condition *entity.Db, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
sql := "SELECT d.* FROM t_db d JOIN t_project_member pm ON d.project_id = pm.project_id WHERE 1 = 1 "
if condition.CreatorId != 0 {
// 使用创建者id模拟项目成员id
sql = fmt.Sprintf("%s AND pm.account_id = %d", sql, condition.CreatorId)
}
if condition.ProjectId != 0 {
sql = fmt.Sprintf("%s AND d.project_id = %d", sql, condition.ProjectId)
}
if condition.EnvId != 0 {
sql = fmt.Sprintf("%s AND d.env_id = %d", sql, condition.EnvId)
}
if condition.Host != "" {
sql = sql + " AND d.host LIKE '%" + condition.Host + "%'"
}
if condition.Database != "" {
sql = sql + " AND d.database LIKE '%" + condition.Database + "%'"
}
sql = sql + " ORDER BY d.create_time DESC"
return model.GetPageBySql(sql, pageParam, toEntity)
}
func (d *dbRepo) Count(condition *entity.Db) int64 {
return model.CountBy(condition)
}
// 根据条件获取账号信息
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
}
func (d *dbRepo) Insert(db *entity.Db) {
biz.ErrIsNil(model.Insert(db), "新增数据库信息失败")
}
func (d *dbRepo) Update(db *entity.Db) {
biz.ErrIsNil(model.UpdateById(db), "更新数据库信息失败")
}
func (d *dbRepo) Delete(id uint64) {
model.DeleteById(new(entity.Db), id)
}

View File

@@ -0,0 +1,17 @@
package persistence
import (
"mayfly-go/internal/devops/domain/entity"
"mayfly-go/internal/devops/domain/repository"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/model"
)
type dbSqlRepo struct{}
var DbSqlDao repository.DbSql = &dbSqlRepo{}
// 分页获取数据库信息列表
func (d *dbSqlRepo) DeleteBy(condition *entity.DbSql) {
biz.ErrIsNil(model.DeleteByCondition(condition), "删除sql失败")
}

View File

@@ -0,0 +1,45 @@
package persistence
import (
"mayfly-go/internal/devops/domain/entity"
"mayfly-go/internal/devops/domain/repository"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/model"
)
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,59 @@
package persistence
import (
"fmt"
"mayfly-go/internal/devops/domain/entity"
"mayfly-go/internal/devops/domain/repository"
"mayfly-go/pkg/model"
)
type machineRepo struct{}
var MachineDao repository.Machine = &machineRepo{}
// 分页获取机器信息列表
func (m *machineRepo) GetMachineList(condition *entity.Machine, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
sql := "SELECT m.* FROM t_machine m JOIN t_project_member pm ON m.project_id = pm.project_id WHERE 1 = 1 "
if condition.CreatorId != 0 {
// 使用创建者id模拟项目成员id
sql = fmt.Sprintf("%s AND pm.account_id = %d", sql, condition.CreatorId)
}
if condition.ProjectId != 0 {
sql = fmt.Sprintf("%s AND m.project_id = %d", sql, condition.ProjectId)
}
if condition.Ip != "" {
sql = sql + " AND m.ip LIKE '%" + condition.Ip + "%'"
}
if condition.Name != "" {
sql = sql + " AND m.name LIKE '%" + condition.Name + "%'"
}
sql = sql + " ORDER BY m.project_id, m.create_time DESC"
return model.GetPageBySql(sql, pageParam, toEntity)
}
func (m *machineRepo) Count(condition *entity.Machine) int64 {
return model.CountBy(condition)
}
// 根据条件获取账号信息
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/internal/devops/domain/entity"
"mayfly-go/internal/devops/domain/repository"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/model"
)
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,61 @@
package persistence
import (
"fmt"
"mayfly-go/internal/devops/domain/entity"
"mayfly-go/internal/devops/domain/repository"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/model"
)
type mongoRepo struct{}
var MongoDao repository.Mongo = &mongoRepo{}
// 分页获取数据库信息列表
func (d *mongoRepo) GetList(condition *entity.Mongo, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
sql := "SELECT d.* FROM t_mongo d JOIN t_project_member pm ON d.project_id = pm.project_id WHERE 1 = 1 "
if condition.CreatorId != 0 {
// 使用创建者id模拟项目成员id
sql = fmt.Sprintf("%s AND pm.account_id = %d", sql, condition.CreatorId)
}
if condition.ProjectId != 0 {
sql = fmt.Sprintf("%s AND d.project_id = %d", sql, condition.ProjectId)
}
if condition.EnvId != 0 {
sql = fmt.Sprintf("%s AND d.env_id = %d", sql, condition.EnvId)
}
sql = sql + " ORDER BY d.create_time DESC"
return model.GetPageBySql(sql, pageParam, toEntity)
}
func (d *mongoRepo) Count(condition *entity.Mongo) int64 {
return model.CountBy(condition)
}
// 根据条件获取
func (d *mongoRepo) Get(condition *entity.Mongo, cols ...string) error {
return model.GetBy(condition, cols...)
}
// 根据id获取
func (d *mongoRepo) GetById(id uint64, cols ...string) *entity.Mongo {
db := new(entity.Mongo)
if err := model.GetById(db, id, cols...); err != nil {
return nil
}
return db
}
func (d *mongoRepo) Insert(db *entity.Mongo) {
biz.ErrIsNil(model.Insert(db), "新增mongo信息失败")
}
func (d *mongoRepo) Update(db *entity.Mongo) {
biz.ErrIsNil(model.UpdateById(db), "更新mongo信息失败")
}
func (d *mongoRepo) Delete(id uint64) {
model.DeleteById(new(entity.Mongo), id)
}

View File

@@ -0,0 +1,24 @@
package persistence
import (
"mayfly-go/internal/devops/domain/entity"
"mayfly-go/internal/devops/domain/repository"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/model"
)
type projectEnvRepo struct{}
var ProjectEnvRepo repository.ProjectEnv = &projectEnvRepo{}
func (p *projectEnvRepo) ListEnv(condition *entity.ProjectEnv, toEntity interface{}, orderBy ...string) {
model.ListByOrder(condition, toEntity, orderBy...)
}
func (p *projectEnvRepo) Save(entity *entity.ProjectEnv) {
biz.ErrIsNilAppendErr(model.Insert(entity), "保存环境失败:%s")
}
func (p *projectEnvRepo) DeleteEnvs(projectId uint64) {
model.DeleteByCondition(&entity.ProjectEnv{ProjectId: projectId})
}

View File

@@ -0,0 +1,36 @@
package persistence
import (
"mayfly-go/internal/devops/domain/entity"
"mayfly-go/internal/devops/domain/repository"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/model"
)
type projectMemeberRepo struct{}
var ProjectMemberRepo repository.ProjectMemeber = &projectMemeberRepo{}
func (p *projectMemeberRepo) ListMemeber(condition *entity.ProjectMember, toEntity interface{}, orderBy ...string) {
model.ListByOrder(condition, toEntity, orderBy...)
}
func (p *projectMemeberRepo) Save(pm *entity.ProjectMember) {
biz.ErrIsNilAppendErr(model.Insert(pm), "保存项目成员失败:%s")
}
func (p *projectMemeberRepo) GetPageList(condition *entity.ProjectMember, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
return model.GetPage(pageParam, condition, toEntity, orderBy...)
}
func (p *projectMemeberRepo) DeleteByPidMid(projectId, accountId uint64) {
model.DeleteByCondition(&entity.ProjectMember{ProjectId: projectId, AccountId: accountId})
}
func (p *projectMemeberRepo) DeleteMems(projectId uint64) {
model.DeleteByCondition(&entity.ProjectMember{ProjectId: projectId})
}
func (p *projectMemeberRepo) IsExist(projectId, accountId uint64) bool {
return model.CountBy(&entity.ProjectMember{ProjectId: projectId, AccountId: accountId}) > 0
}

View File

@@ -0,0 +1,36 @@
package persistence
import (
"mayfly-go/internal/devops/domain/entity"
"mayfly-go/internal/devops/domain/repository"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/model"
)
type projectRepo struct{}
var ProjectRepo repository.Project = &projectRepo{}
func (p *projectRepo) GetPageList(condition *entity.Project, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
return model.GetPage(pageParam, condition, toEntity, orderBy...)
}
func (p *projectRepo) Count(condition *entity.Project) int64 {
return model.CountBy(condition)
}
func (p *projectRepo) GetByIdIn(ids []uint64, toEntity interface{}, orderBy ...string) {
model.GetByIdIn(new(entity.Project), toEntity, ids, orderBy...)
}
func (p *projectRepo) Save(project *entity.Project) {
biz.ErrIsNil(model.Insert(project), "保存项目失败")
}
func (p *projectRepo) Update(project *entity.Project) {
biz.ErrIsNil(model.UpdateById(project), "更新项目信息")
}
func (p *projectRepo) Delete(id uint64) {
model.DeleteById(new(entity.Project), id)
}

View File

@@ -0,0 +1,62 @@
package persistence
import (
"fmt"
"mayfly-go/internal/devops/domain/entity"
"mayfly-go/internal/devops/domain/repository"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/model"
)
type redisRepo struct{}
var RedisDao repository.Redis = &redisRepo{}
// 分页获取机器信息列表
func (r *redisRepo) GetRedisList(condition *entity.Redis, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
sql := "SELECT d.* FROM t_redis d JOIN t_project_member pm ON d.project_id = pm.project_id WHERE 1 = 1 "
if condition.CreatorId != 0 {
// 使用创建者id模拟项目成员id
sql = fmt.Sprintf("%s AND pm.account_id = %d", sql, condition.CreatorId)
}
if condition.ProjectId != 0 {
sql = fmt.Sprintf("%s AND d.project_id = %d", sql, condition.ProjectId)
}
if condition.EnvId != 0 {
sql = fmt.Sprintf("%s AND d.env_id = %d", sql, condition.EnvId)
}
if condition.Host != "" {
sql = sql + " AND d.host LIKE '%" + condition.Host + "%'"
}
sql = sql + " ORDER BY d.create_time DESC"
return model.GetPageBySql(sql, pageParam, toEntity)
}
func (r *redisRepo) Count(condition *entity.Redis) int64 {
return model.CountBy(condition)
}
// 根据id获取
func (r *redisRepo) GetById(id uint64, cols ...string) *entity.Redis {
rd := new(entity.Redis)
if err := model.GetById(rd, id, cols...); err != nil {
return nil
}
return rd
}
func (r *redisRepo) GetRedis(condition *entity.Redis, cols ...string) error {
return model.GetBy(condition, cols...)
}
func (r *redisRepo) Insert(redis *entity.Redis) {
biz.ErrIsNilAppendErr(model.Insert(redis), "新增失败: %s")
}
func (r *redisRepo) Update(redis *entity.Redis) {
biz.ErrIsNilAppendErr(model.UpdateById(redis), "更新失败: %s")
}
func (r *redisRepo) Delete(id uint64) {
biz.ErrIsNilAppendErr(model.DeleteById(new(entity.Redis), id), "删除失败: %s")
}

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/pkg/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,94 @@
package router
import (
"mayfly-go/internal/devops/api"
"mayfly-go/internal/devops/application"
sysApplication "mayfly-go/internal/sys/application"
"mayfly-go/pkg/ctx"
"github.com/gin-gonic/gin"
)
func InitDbRouter(router *gin.RouterGroup) {
db := router.Group("dbs")
{
d := &api.Db{
DbApp: application.DbApp,
MsgApp: sysApplication.MsgApp,
ProjectApp: application.ProjectApp,
}
// 获取所有数据库列表
db.GET("", func(c *gin.Context) {
rc := ctx.NewReqCtxWithGin(c)
rc.Handle(d.Dbs)
})
saveDb := ctx.NewLogInfo("保存数据库信息")
db.POST("", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).
WithLog(saveDb).
Handle(d.Save)
})
deleteDb := ctx.NewLogInfo("删除数据库信息")
db.DELETE(":dbId", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).
WithLog(deleteDb).
Handle(d.DeleteDb)
})
db.GET(":dbId/t-infos", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(d.TableInfos)
})
db.GET(":dbId/t-index", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(d.TableIndex)
})
db.GET(":dbId/t-create-ddl", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(d.GetCreateTableDdl)
})
// db.GET(":dbId/exec-sql", controllers.SelectData)
db.GET(":dbId/exec-sql", func(g *gin.Context) {
rc := ctx.NewReqCtxWithGin(g).WithLog(ctx.NewLogInfo("执行Sql语句"))
rc.Handle(d.ExecSql)
})
db.POST(":dbId/exec-sql-file", func(g *gin.Context) {
rc := ctx.NewReqCtxWithGin(g).WithLog(ctx.NewLogInfo("执行Sql文件"))
rc.Handle(d.ExecSqlFile)
})
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 sql相关接口 */
db.POST(":dbId/sql", func(c *gin.Context) {
rc := ctx.NewReqCtxWithGin(c)
rc.Handle(d.SaveSql)
})
db.GET(":dbId/sql", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(d.GetSql)
})
db.DELETE(":dbId/sql", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(d.DeleteSql)
})
db.GET(":dbId/sql-names", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(d.GetSqlNames)
})
}
}

View File

@@ -0,0 +1,69 @@
package router
import (
"mayfly-go/internal/devops/api"
"mayfly-go/internal/devops/application"
"mayfly-go/pkg/ctx"
"github.com/gin-gonic/gin"
)
func InitMachineRouter(router *gin.RouterGroup) {
m := &api.Machine{
MachineApp: application.MachineApp,
ProjectApp: application.ProjectApp,
}
machines := router.Group("machines")
{
machines.GET("", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(m.Machines)
})
machines.GET(":machineId/stats", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(m.MachineStats)
})
machines.GET(":machineId/process", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(m.GetProcess)
})
// 终止进程
killProcessL := ctx.NewLogInfo("终止进程")
killProcessP := ctx.NewPermission("machine:killprocess")
machines.DELETE(":machineId/process", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).
WithLog(killProcessL).
WithRequiredPermission(killProcessP).
Handle(m.KillProcess)
})
saveMachine := ctx.NewLogInfo("保存机器信息")
machines.POST("", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).
WithLog(saveMachine).
Handle(m.SaveMachine)
})
changeStatus := ctx.NewLogInfo("调整机器状态")
machines.PUT(":machineId/:status", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).
WithLog(changeStatus).
Handle(m.ChangeStatus)
})
delMachine := ctx.NewLogInfo("删除机器")
machines.DELETE(":machineId", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).
WithLog(delMachine).
Handle(m.DeleteMachine)
})
closeCli := ctx.NewLogInfo("关闭机器客户端")
machines.DELETE(":machineId/close-cli", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).WithLog(closeCli).Handle(m.CloseCli)
})
machines.GET(":machineId/terminal", m.WsSSH)
}
}

View File

@@ -0,0 +1,80 @@
package router
import (
"mayfly-go/internal/devops/api"
"mayfly-go/internal/devops/application"
sysApplication "mayfly-go/internal/sys/application"
"mayfly-go/pkg/ctx"
"github.com/gin-gonic/gin"
)
func InitMachineFileRouter(router *gin.RouterGroup) {
machineFile := router.Group("machines")
{
mf := &api.MachineFile{
MachineFileApp: application.MachineFileApp,
MachineApp: application.MachineApp,
MsgApp: sysApplication.MsgApp,
}
// 获取指定机器文件列表
machineFile.GET(":machineId/files", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(mf.MachineFiles)
})
// 新增修改机器文件
addFileConf := ctx.NewLogInfo("新增机器文件配置")
afcP := ctx.NewPermission("machine:file:add")
machineFile.POST(":machineId/files", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).WithLog(addFileConf).
WithRequiredPermission(afcP).
Handle(mf.SaveMachineFiles)
})
// 删除机器文件
delFileConf := ctx.NewLogInfo("删除机器文件配置")
dfcP := ctx.NewPermission("machine:file:del")
machineFile.DELETE(":machineId/files/:fileId", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).WithLog(delFileConf).
WithRequiredPermission(dfcP).
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下载文件内容")
wfP := ctx.NewPermission("machine:file:write")
machineFile.POST(":machineId/files/:fileId/write", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).WithLog(writeFile).
WithRequiredPermission(wfP).
Handle(mf.WriteFileContent)
})
uploadFile := ctx.NewLogInfo("文件上传")
ufP := ctx.NewPermission("machine:file:upload")
machineFile.POST(":machineId/files/:fileId/upload", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).WithLog(uploadFile).
WithRequiredPermission(ufP).
Handle(mf.UploadFile)
})
removeFile := ctx.NewLogInfo("删除文件or文件夹")
rfP := ctx.NewPermission("machine:file:rm")
machineFile.DELETE(":machineId/files/:fileId/remove", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).WithLog(removeFile).
WithRequiredPermission(rfP).
Handle(mf.RemoveFile)
})
}
}

View File

@@ -0,0 +1,52 @@
package router
import (
"mayfly-go/internal/devops/api"
"mayfly-go/internal/devops/application"
"mayfly-go/pkg/ctx"
"github.com/gin-gonic/gin"
)
func InitMachineScriptRouter(router *gin.RouterGroup) {
machines := router.Group("machines")
{
ms := &api.MachineScript{
MachineScriptApp: application.MachineScriptApp,
MachineApp: application.MachineApp,
ProjectApp: application.ProjectApp,
}
// 获取指定机器脚本列表
machines.GET(":machineId/scripts", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(ms.MachineScripts)
})
saveMachienScriptLog := ctx.NewLogInfo("保存脚本")
smsP := ctx.NewPermission("machine:script:save")
// 保存脚本
machines.POST(":machineId/scripts", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).WithLog(saveMachienScriptLog).
WithRequiredPermission(smsP).
Handle(ms.SaveMachineScript)
})
deleteLog := ctx.NewLogInfo("删除脚本")
dP := ctx.NewPermission("machine:script:del")
// 保存脚本
machines.DELETE(":machineId/scripts/:scriptId", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).WithLog(deleteLog).
WithRequiredPermission(dP).
Handle(ms.DeleteMachineScript)
})
runLog := ctx.NewLogInfo("执行机器脚本")
rP := ctx.NewPermission("machine:script:run")
// 运行脚本
machines.GET(":machineId/scripts/:scriptId/run", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).WithLog(runLog).
WithRequiredPermission(rP).
Handle(ms.RunMachineScript)
})
}
}

View File

@@ -0,0 +1,86 @@
package router
import (
"mayfly-go/internal/devops/api"
"mayfly-go/internal/devops/application"
"mayfly-go/pkg/ctx"
"github.com/gin-gonic/gin"
)
func InitMongoRouter(router *gin.RouterGroup) {
m := router.Group("mongos")
{
ma := &api.Mongo{
MongoApp: application.MongoApp,
}
// 获取所有mongo列表
m.GET("", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).
Handle(ma.Mongos)
})
saveMongo := ctx.NewLogInfo("保存mongo信息")
m.POST("", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).
WithLog(saveMongo).
Handle(ma.Save)
})
deleteMongo := ctx.NewLogInfo("删除mongo信息")
m.DELETE(":id", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).
WithLog(deleteMongo).
Handle(ma.DeleteMongo)
})
// 获取mongo下的所有数据库
m.GET(":id/databases", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).
Handle(ma.Databases)
})
// 获取mongo指定库的所有集合
m.GET(":id/collections", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).
Handle(ma.Collections)
})
// 获取mongo runCommand
m.POST(":id/run-command", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).
Handle(ma.RunCommand)
})
// 执行mongo find命令
m.POST(":id/command/find", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).
Handle(ma.FindCommand)
})
// 执行mongo update by id命令
updateDocById := ctx.NewLogInfo("mongo-更新文档")
m.POST(":id/command/update-by-id", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).
WithLog(updateDocById).
Handle(ma.UpdateByIdCommand)
})
// 执行mongo delete by id命令
deleteDoc := ctx.NewLogInfo("mongo-删除文档")
m.POST(":id/command/delete-by-id", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).
WithLog(deleteDoc).
Handle(ma.DeleteByIdCommand)
})
// 执行mongo insert 命令
insertDoc := ctx.NewLogInfo("mongo-新增文档")
m.POST(":id/command/insert", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).
WithLog(insertDoc).
Handle(ma.InsertOneCommand)
})
}
}

View File

@@ -0,0 +1,83 @@
package router
import (
"mayfly-go/internal/devops/api"
"mayfly-go/internal/devops/application"
sys_applicaiton "mayfly-go/internal/sys/application"
"mayfly-go/pkg/ctx"
"github.com/gin-gonic/gin"
)
func InitProjectRouter(router *gin.RouterGroup) {
m := &api.Project{
ProjectApp: application.ProjectApp,
AccountApp: sys_applicaiton.AccountApp}
project := router.Group("/projects")
{
router.GET("/accounts/projects", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(m.GetProjectsByLoginAccount)
})
// 获取项目列表
project.GET("", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(m.GetProjects)
})
saveProjectLog := ctx.NewLogInfo("保存项目信息")
savePP := ctx.NewPermission("project:save")
// 保存项目下的环境信息
project.POST("", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).WithLog(saveProjectLog).
WithRequiredPermission(savePP).
Handle(m.SaveProject)
})
delProjectLog := ctx.NewLogInfo("删除项目信息")
delPP := ctx.NewPermission("project:del")
// 删除项目
project.DELETE("", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).WithLog(delProjectLog).
WithRequiredPermission(delPP).
Handle(m.DelProject)
})
// 获取项目下的环境信息列表
project.GET("/:projectId/envs", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(m.GetProjectEnvs)
})
saveProjectEnvLog := ctx.NewLogInfo("新增项目环境信息")
savePeP := ctx.NewPermission("project:env:add")
// 保存项目下的环境信息
project.POST("/:projectId/envs", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).WithLog(saveProjectEnvLog).
WithRequiredPermission(savePeP).
Handle(m.SaveProjectEnvs)
})
// 获取项目下的成员信息列表
project.GET("/:projectId/members", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(m.GetProjectMembers)
})
// 保存项目成员
saveProjectMemLog := ctx.NewLogInfo("新增项目成员")
savePmP := ctx.NewPermission("project:member:add")
project.POST("/:projectId/members", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).WithLog(saveProjectMemLog).
WithRequiredPermission(savePmP).
Handle(m.SaveProjectMember)
})
// 删除项目成员
delProjectMemLog := ctx.NewLogInfo("删除项目成员")
savePmdP := ctx.NewPermission("project:member:del")
project.DELETE("/:projectId/members/:accountId", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).WithLog(delProjectMemLog).
WithRequiredPermission(savePmdP).
Handle(m.DelProjectMember)
})
}
}

View File

@@ -0,0 +1,80 @@
package router
import (
"mayfly-go/internal/devops/api"
"mayfly-go/internal/devops/application"
"mayfly-go/pkg/ctx"
"github.com/gin-gonic/gin"
)
func InitRedisRouter(router *gin.RouterGroup) {
redis := router.Group("redis")
{
rs := &api.Redis{
RedisApp: application.RedisApp,
ProjectApp: application.ProjectApp,
}
// 获取redis list
redis.GET("", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(rs.RedisList)
})
save := ctx.NewLogInfo("保存redis信息")
redis.POST("", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).WithLog(save).Handle(rs.Save)
})
delRedis := ctx.NewLogInfo("删除redis信息")
redis.DELETE(":id", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).WithLog(delRedis).Handle(rs.DeleteRedis)
})
redis.GET(":id/info", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(rs.RedisInfo)
})
// 获取指定redis keys
redis.GET(":id/scan/:cursor/:count", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(rs.Scan)
})
// 删除key
deleteKeyL := ctx.NewLogInfo("redis删除key")
// deleteKey := ctx.NewPermission("project:save")
redis.DELETE(":id/key", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).WithLog(deleteKeyL).Handle(rs.DeleteKey)
})
// 获取string类型值
redis.GET(":id/string-value", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(rs.GetStringValue)
})
// 设置string类型值
redis.POST(":id/string-value", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(rs.SetStringValue)
})
// 获取hash类型值
redis.GET(":id/hash-value", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(rs.GetHashValue)
})
// 设置hash类型值
redis.POST(":id/hash-value", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(rs.SetHashValue)
})
// 获取set类型值
redis.GET(":id/set-value", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(rs.GetSetValue)
})
// 设置set类型值
redis.POST(":id/set-value", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(rs.SetSetValue)
})
}
}