增加webssh及数据库查询

This commit is contained in:
meilin.huang
2021-01-08 15:37:32 +08:00
parent 111612b7f2
commit 4c2e6b6155
60 changed files with 4347 additions and 2222 deletions

2
.gitignore vendored
View File

@@ -10,3 +10,5 @@
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
*/node_modules/

8
.idea/.gitignore generated vendored
View File

@@ -1,8 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
# Editor-based HTTP Client requests
/httpRequests/

0
.idea/.name generated
View File

10
.idea/mayfly-go.iml generated
View File

@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$/../mayfly-go">
<excludeFolder url="file://$MODULE_DIR$/../mayfly-go/mayfly-go-front" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

6
.idea/misc.xml generated
View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptSettings">
<option name="languageLevel" value="ES6" />
</component>
</project>

8
.idea/modules.xml generated
View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/mayfly-job.iml" filepath="$PROJECT_DIR$/.idea/mayfly-job.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml generated
View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

17
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,17 @@
{
// 使用 IntelliSense 了解相关属性。
// 悬停以查看现有属性的描述。
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "mayfly-go",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${fileDirname}/../main.go",
"env": {},
"args": []
}
]
}

View File

@@ -2,25 +2,30 @@ package base
import (
"encoding/json"
"github.com/astaxie/beego"
"github.com/astaxie/beego/logs"
"github.com/astaxie/beego/validation"
"fmt"
"mayfly-go/base/ctx"
"mayfly-go/base/model"
"mayfly-go/base/token"
"github.com/beego/beego/v2/core/logs"
"github.com/beego/beego/v2/core/validation"
"github.com/beego/beego/v2/server/web"
)
type Controller struct {
beego.Controller
web.Controller
}
// 获取数据函数
type getDataFunc func(loginAccount *LoginAccount) interface{}
type getDataFunc func(loginAccount *ctx.LoginAccount) interface{}
// 操作函数,无返回数据
type operationFunc func(loginAccount *LoginAccount)
type operationFunc func(loginAccount *ctx.LoginAccount)
// 将请求体的json赋值给指定的结构体
func (c *Controller) UnmarshalBody(data interface{}) {
err := json.Unmarshal(c.Ctx.Input.RequestBody, data)
BizErrIsNil(err, "request body解析错误")
model.BizErrIsNil(err, "request body解析错误")
}
// 校验表单数据
@@ -32,7 +37,7 @@ func (c *Controller) validForm(form interface{}) {
}
if !b {
e := valid.Errors[0]
panic(NewBizErr(e.Field + " " + e.Message))
panic(model.NewBizErr(e.Field + " " + e.Message))
}
}
@@ -51,13 +56,32 @@ func (c *Controller) ReturnData(checkToken bool, getData getDataFunc) {
c.parseErr(err)
}
}()
var loginAccount *LoginAccount
var loginAccount *ctx.LoginAccount
if checkToken {
loginAccount = c.CheckToken()
}
c.Success(getData(loginAccount))
}
// 返回数据
// @param checkToken 是否校验token
// @param getData 获取数据的回调函数
func (c *Controller) ReturnDataWithPermisison(permission ctx.Permission, getData getDataFunc) {
defer func() {
if err := recover(); err != nil {
c.parseErr(err)
}
}()
var logMsg string
var loginAccount *ctx.LoginAccount
if permission.CheckToken {
loginAccount = c.CheckToken()
logMsg = fmt.Sprintf("[uid=%d, uname=%s]\n", loginAccount.Id, loginAccount.Username)
}
c.Success(getData(loginAccount))
logs.Info(logMsg)
}
// 无返回数据的操作,如新增修改等无需返回数据的操作
// @param checkToken 是否校验token
func (c *Controller) Operation(checkToken bool, operation operationFunc) {
@@ -66,7 +90,7 @@ func (c *Controller) Operation(checkToken bool, operation operationFunc) {
c.parseErr(err)
}
}()
var loginAccount *LoginAccount
var loginAccount *ctx.LoginAccount
if checkToken {
loginAccount = c.CheckToken()
}
@@ -75,54 +99,54 @@ func (c *Controller) Operation(checkToken bool, operation operationFunc) {
}
// 校验token并返回登录者账号信息
func (c *Controller) CheckToken() *LoginAccount {
func (c *Controller) CheckToken() *ctx.LoginAccount {
tokenStr := c.Ctx.Input.Header("Authorization")
loginAccount, err := ParseToken(tokenStr)
loginAccount, err := token.ParseToken(tokenStr)
if err != nil || loginAccount == nil {
panic(NewBizErrCode(TokenErrorCode, TokenErrorMsg))
panic(model.NewBizErrCode(model.TokenErrorCode, model.TokenErrorMsg))
}
return loginAccount
}
// 获取分页参数
func (c *Controller) GetPageParam() *PageParam {
func (c *Controller) GetPageParam() *model.PageParam {
pn, err := c.GetInt("pageNum", 1)
BizErrIsNil(err, "pageNum参数错误")
model.BizErrIsNil(err, "pageNum参数错误")
ps, serr := c.GetInt("pageSize", 10)
BizErrIsNil(serr, "pageSize参数错误")
return &PageParam{PageNum: pn, PageSize: ps}
model.BizErrIsNil(serr, "pageSize参数错误")
return &model.PageParam{PageNum: pn, PageSize: ps}
}
// 统一返回Result json对象
func (c *Controller) Result(result *Result) {
func (c *Controller) Result(result *model.Result) {
c.Data["json"] = result
c.ServeJSON()
}
// 返回成功结果
func (c *Controller) Success(data interface{}) {
c.Result(Success(data))
c.Result(model.Success(data))
}
// 返回成功结果
func (c *Controller) SuccessNoData() {
c.Result(SuccessNoData())
c.Result(model.SuccessNoData())
}
// 返回业务错误
func (c *Controller) BizError(bizError BizError) {
c.Result(Error(bizError.Code(), bizError.Error()))
func (c *Controller) BizError(bizError model.BizError) {
c.Result(model.Error(bizError.Code(), bizError.Error()))
}
// 返回服务器错误结果
func (c *Controller) ServerError() {
c.Result(ServerError())
c.Result(model.ServerError())
}
// 解析error并对不同error返回不同result
func (c *Controller) parseErr(err interface{}) {
switch t := err.(type) {
case BizError:
case model.BizError:
c.BizError(t)
break
case error:

12
base/ctx/login_account.go Normal file
View File

@@ -0,0 +1,12 @@
package ctx
type LoginAccount struct {
Id uint64
Username string
}
type Permission struct {
CheckToken bool // 是否检查token
Code string // 权限码
Name string // 描述
}

View File

@@ -1,12 +1,13 @@
package base
package model
import (
"fmt"
"reflect"
)
func BizErrIsNil(err error, msg string) {
func BizErrIsNil(err error, msg string, params ...interface{}) {
if err != nil {
panic(NewBizErr(msg))
panic(NewBizErr(fmt.Sprintf(msg, params...)))
}
}
@@ -16,15 +17,15 @@ func ErrIsNil(err error, msg string) {
}
}
func IsTrue(exp bool, msg string) {
func IsTrue(exp bool, msg string, params ...interface{}) {
if !exp {
panic(NewBizErr(msg))
panic(NewBizErr(fmt.Sprintf(msg, params...)))
}
}
func NotEmpty(str string, msg string) {
func NotEmpty(str string, msg string, params ...interface{}) {
if str == "" {
panic(NewBizErr(msg))
panic(NewBizErr(fmt.Sprintf(msg, params...)))
}
}

View File

@@ -1,4 +1,4 @@
package base
package model
// 业务错误
type BizError struct {

View File

@@ -1,31 +1,123 @@
package base
package model
import (
"errors"
"github.com/astaxie/beego/orm"
"github.com/siddontang/go/log"
"mayfly-go/base/ctx"
"mayfly-go/base/utils"
"reflect"
"strconv"
"strings"
"time"
"github.com/beego/beego/v2/client/orm"
"github.com/siddontang/go/log"
)
type Model struct {
Id uint64 `orm:"column(id);auto" json:"id"`
CreateTime time.Time `orm:"column(create_time);type(datetime);null" json:"createTime"`
CreateTime *time.Time `orm:"column(create_time);type(datetime);null" json:"createTime"`
CreatorId uint64 `orm:"column(creator_id)" json:"creatorId"`
Creator string `orm:"column(creator)" json:"creator"`
UpdateTime time.Time `orm:"column(update_time);type(datetime);null" json:"updateTime"`
UpdateTime *time.Time `orm:"column(update_time);type(datetime);null" json:"updateTime"`
ModifierId uint64 `orm:"column(modifier_id)" json:"modifierId"`
Modifier string `orm:"column(modifier)" json:"modifier"`
}
// 设置基础信息. 如创建时间,修改时间,创建者,修改者信息
func (m *Model) SetBaseInfo(account *ctx.LoginAccount) {
nowTime := time.Now()
isCreate := m.Id == 0
if isCreate {
m.CreateTime = &nowTime
}
m.UpdateTime = &nowTime
if account == nil {
return
}
id := account.Id
name := account.Username
if isCreate {
m.CreatorId = id
m.Creator = name
}
m.Modifier = name
m.ModifierId = id
}
// 获取orm querySeter
func QuerySetter(table interface{}) orm.QuerySeter {
return getOrm().QueryTable(table)
}
// 根据id获取实体对象。model需为指针类型需要将查询出来的值赋值给model
//
// 若error不为nil则为不存在该记录
func GetById(model interface{}, id uint64, cols ...string) error {
return QuerySetter(model).Filter("Id", id).One(model, cols...)
}
// 根据id更新model更新字段为model中不为空的值即int类型不为0ptr类型不为nil这类字段值
func UpdateById(model interface{}) (int64, error) {
var id uint64
params := orm.Params{}
err := utils.DoWithFields(model, func(ft reflect.StructField, fv reflect.Value) error {
if utils.IsBlank(fv) {
return nil
}
if ft.Name == "Id" {
if id = fv.Uint(); id == 0 {
return errors.New("根据id更新model时Id不能为0")
}
return nil
}
params[ft.Name] = fv.Interface()
return nil
})
if err != nil {
return 0, err
}
return QuerySetter(model).Filter("Id", id).Update(params)
}
// 根据id删除model
func DeleteById(model interface{}, id uint64) (int64, error) {
return QuerySetter(model).Filter("Id", id).Delete()
}
// 插入model
func Insert(model interface{}) (int64, error) {
return getOrm().Insert(model)
}
// 获取满足model中不为空的字段值条件的所有数据.
//
// @param list为数组类型 如 var users []*User
func ListByCondition(model interface{}, list interface{}) {
qs := QuerySetter(model)
utils.DoWithFields(model, func(ft reflect.StructField, fv reflect.Value) error {
if !utils.IsBlank(fv) {
qs = qs.Filter(ft.Name, fv.Interface())
}
return nil
})
qs.All(list)
}
// 获取满足model中不为空的字段值条件的单个对象。model需为指针类型需要将查询出来的值赋值给model
//
// 若 error不为nil则为不存在该记录
func GetByCondition(model interface{}, cols ...string) error {
qs := QuerySetter(model)
utils.DoWithFields(model, func(ft reflect.StructField, fv reflect.Value) error {
if !utils.IsBlank(fv) {
qs = qs.Filter(ft.Name, fv.Interface())
}
return nil
})
return qs.One(model, cols...)
}
// 获取分页结果
func GetPage(seter orm.QuerySeter, pageParam *PageParam, models interface{}, toModels interface{}) PageResult {
count, _ := seter.Count()
@@ -80,6 +172,7 @@ func GetListBySql(sql string, params ...interface{}) *[]orm.Params {
}
// 获取所有列表数据
// model为数组类型 如 var users []*User
func GetList(seter orm.QuerySeter, model interface{}, toModel interface{}) {
_, _ = seter.All(model, getFieldNames(toModel)...)
err := utils.Copy(toModel, model)
@@ -110,14 +203,6 @@ func GetBy(model interface{}, fs ...string) error {
return nil
}
func Insert(model interface{}) error {
_, err := getOrm().Insert(model)
if err != nil {
return errors.New("数据插入失败")
}
return nil
}
func Update(model interface{}, fs ...string) error {
_, err := getOrm().Update(model, fs...)
if err != nil {

View File

@@ -1,14 +1,15 @@
package base
package model
import (
"fmt"
"github.com/astaxie/beego/orm"
_ "github.com/go-sql-driver/mysql"
"mayfly-go/base/utils"
"mayfly-go/controllers/vo"
"mayfly-go/models"
"strings"
"testing"
"github.com/beego/beego/v2/client/orm"
_ "github.com/go-sql-driver/mysql"
)
type AccountDetailVO struct {

View File

@@ -1,4 +1,4 @@
package base
package model
// 分页参数
type PageParam struct {

View File

@@ -1,4 +1,4 @@
package base
package model
import (
"encoding/json"

View File

@@ -1,9 +1,12 @@
package base
package token
import (
"errors"
"github.com/dgrijalva/jwt-go"
"mayfly-go/base/ctx"
"mayfly-go/base/model"
"time"
"github.com/dgrijalva/jwt-go"
)
const (
@@ -11,11 +14,6 @@ const (
ExpTime = time.Hour * 24 * 7
)
type LoginAccount struct {
Id uint64
Username string
}
// 创建用户token
func CreateToken(userId uint64, username string) string {
// 带权限创建令牌
@@ -28,12 +26,12 @@ func CreateToken(userId uint64, username string) string {
// 使用自定义字符串加密 and get the complete encoded token as a string
tokenString, err := token.SignedString([]byte(JwtKey))
BizErrIsNil(err, "token创建失败")
model.BizErrIsNil(err, "token创建失败")
return tokenString
}
// 解析token并返回登录者账号信息
func ParseToken(tokenStr string) (*LoginAccount, error) {
func ParseToken(tokenStr string) (*ctx.LoginAccount, error) {
if tokenStr == "" {
return nil, errors.New("token error")
}
@@ -45,5 +43,5 @@ func ParseToken(tokenStr string) (*LoginAccount, error) {
return nil, err
}
i := token.Claims.(jwt.MapClaims)
return &LoginAccount{Id: uint64(i["id"].(float64)), Username: i["username"].(string)}, nil
return &ctx.LoginAccount{Id: uint64(i["id"].(float64)), Username: i["username"].(string)}, nil
}

View File

@@ -36,6 +36,26 @@ func SubString(str string, begin, end int) (substr string) {
return string(rs[begin:end])
}
func Camel2Underline(name string) string {
if name == "" {
return ""
}
temp := strings.Split(name, "_")
var s string
for _, v := range temp {
vv := []rune(v)
if len(vv) > 0 {
if bool(vv[0] >= 'a' && vv[0] <= 'z') { //首字母大写
vv[0] -= 32
}
s += string(vv)
}
}
return s
}
func UnicodeIndex(str, substr string) int {
// 子串在字符串的字节位置
result := strings.Index(str, substr)

View File

@@ -132,6 +132,31 @@ func Copy(toValue interface{}, fromValue interface{}) (err error) {
return
}
// 对结构体的每个字段以及字段值执行doWith回调函数, 包括匿名属性的字段
func DoWithFields(str interface{}, doWith func(fType reflect.StructField, fValue reflect.Value) error) error {
t := IndirectType(reflect.TypeOf(str))
if t.Kind() != reflect.Struct {
return errors.New("非结构体")
}
fieldNum := t.NumField()
v := Indirect(reflect.ValueOf(str))
for i := 0; i < fieldNum; i++ {
ft := t.Field(i)
fv := v.Field(i)
// 如果是匿名属性,则递归调用该方法
if ft.Anonymous {
DoWithFields(fv.Interface(), doWith)
continue
}
err := doWith(ft, fv)
if err != nil {
return err
}
}
return nil
}
func deepFields(reflectType reflect.Type) []reflect.StructField {
var fields []reflect.StructField
@@ -610,7 +635,7 @@ func Case2Camel(name string) string {
return strings.Replace(name, " ", "", -1)
}
func isBlank(value reflect.Value) bool {
func IsBlank(value reflect.Value) bool {
switch value.Kind() {
case reflect.String:
return value.Len() == 0

View File

@@ -2,7 +2,6 @@ package utils
import (
"fmt"
"github.com/mitchellh/mapstructure"
"reflect"
"strings"
"testing"
@@ -144,9 +143,9 @@ func TestMap2Struct(t *testing.T) {
//
//fmt.Println(name, b)
//将 map 转换为指定的结构体
if err := mapstructure.Decode(mapInstance, &s); err != nil {
fmt.Println(err)
}
// if err := decode(mapInstance, &s); err != nil {
// fmt.Println(err)
// }
fmt.Printf("map2struct后得到的 struct 内容为:%v", s)
}

View File

@@ -2,6 +2,9 @@ package controllers
import (
"mayfly-go/base"
"mayfly-go/base/ctx"
"mayfly-go/base/model"
"mayfly-go/base/token"
"mayfly-go/controllers/form"
"mayfly-go/models"
)
@@ -17,14 +20,14 @@ type AccountController struct {
// @router /accounts/login [post]
func (c *AccountController) Login() {
c.ReturnData(false, func(la *base.LoginAccount) interface{} {
c.ReturnData(false, func(la *ctx.LoginAccount) interface{} {
loginForm := &form.LoginForm{}
c.UnmarshalBodyAndValid(loginForm)
a := &models.Account{Username: loginForm.Username, Password: loginForm.Password}
base.BizErrIsNil(base.GetBy(a, "Username", "Password"), "用户名或密码错误")
model.BizErrIsNil(model.GetBy(a, "Username", "Password"), "用户名或密码错误")
return map[string]interface{}{
"token": base.CreateToken(a.Id, a.Username),
"token": token.CreateToken(a.Id, a.Username),
"username": a.Username,
}
})
@@ -32,7 +35,7 @@ func (c *AccountController) Login() {
// @router /accounts [get]
func (c *AccountController) Accounts() {
c.ReturnData(true, func(account *base.LoginAccount) interface{} {
c.ReturnData(true, func(account *ctx.LoginAccount) interface{} {
//s := c.GetString("username")
//query := models.QuerySetter(new(models.Account)).OrderBy("-Id").RelatedSel()
//return models.GetPage(query, c.GetPageParam(), new([]models.Account), new([]vo.AccountVO))

140
controllers/db.go Normal file
View File

@@ -0,0 +1,140 @@
package controllers
import (
"fmt"
"mayfly-go/base"
"mayfly-go/base/ctx"
"mayfly-go/base/model"
"mayfly-go/controllers/form"
"mayfly-go/controllers/vo"
"mayfly-go/db"
"mayfly-go/models"
"strconv"
)
type DbController struct {
base.Controller
}
// @router /api/dbs [get]
func (c *DbController) Dbs() {
c.ReturnData(false, func(account *ctx.LoginAccount) interface{} {
m := new([]models.Db)
querySetter := model.QuerySetter(new(models.Db))
return model.GetPage(querySetter, c.GetPageParam(), m, new([]vo.SelectDataDbVO))
})
}
// @router /api/db/:dbId/select [get]
func (c *DbController) SelectData() {
c.ReturnData(false, func(account *ctx.LoginAccount) interface{} {
selectSql := c.GetString("selectSql")
model.NotEmpty(selectSql, "selectSql不能为空")
res, err := db.GetDbInstance(c.GetDbId()).SelectData(selectSql)
if err != nil {
panic(model.NewBizErr(fmt.Sprintf("查询失败: %s", err.Error())))
}
return res
})
}
// @router /api/db/:dbId/exec-sql [post]
func (c *DbController) ExecSql() {
c.ReturnData(false, func(account *ctx.LoginAccount) interface{} {
selectSql := c.GetString("sql")
model.NotEmpty(selectSql, "sql不能为空")
num, err := db.GetDbInstance(c.GetDbId()).Exec(selectSql)
if err != nil {
panic(model.NewBizErr(fmt.Sprintf("执行失败: %s", err.Error())))
}
return num
})
}
// @router /api/db/:dbId/t-metadata [get]
func (c *DbController) TableMA() {
c.ReturnData(false, func(account *ctx.LoginAccount) interface{} {
return db.GetDbInstance(c.GetDbId()).GetTableMetedatas()
})
}
// @router /api/db/:dbId/c-metadata [get]
func (c *DbController) ColumnMA() {
c.ReturnData(false, func(account *ctx.LoginAccount) interface{} {
tn := c.GetString("tableName")
model.NotEmpty(tn, "tableName不能为空")
return db.GetDbInstance(c.GetDbId()).GetColumnMetadatas(tn)
})
}
// @router /api/db/:dbId/hint-tables [get]
// 数据表及字段前端提示接口
func (c *DbController) HintTables() {
c.ReturnData(false, func(account *ctx.LoginAccount) interface{} {
dbi := db.GetDbInstance(c.GetDbId())
tables := dbi.GetTableMetedatas()
res := make(map[string][]string)
for _, v := range tables {
tableName := v["tableName"]
columnMds := dbi.GetColumnMetadatas(tableName)
columnNames := make([]string, len(columnMds))
for i, v := range columnMds {
comment := v["columnComment"]
if comment != "" {
columnNames[i] = v["columnName"] + " [" + comment + "]"
} else {
columnNames[i] = v["columnName"]
}
}
res[tableName] = columnNames
}
return res
})
}
// @router /api/db/:dbId/sql [post]
func (c *DbController) SaveSql() {
c.Operation(true, func(account *ctx.LoginAccount) {
dbSqlForm := &form.DbSqlSaveForm{}
c.UnmarshalBodyAndValid(dbSqlForm)
dbId := c.GetDbId()
// 判断dbId是否存在
err := model.GetById(new(models.Db), dbId)
model.BizErrIsNil(err, "该数据库信息不存在")
// 获取用于是否有该dbsql的保存记录有则更改否则新增
dbSql := &models.DbSql{Type: dbSqlForm.Type, DbId: dbId}
dbSql.CreatorId = account.Id
e := model.GetByCondition(dbSql)
dbSql.SetBaseInfo(account)
// 更新sql信息
dbSql.Sql = dbSqlForm.Sql
if e == nil {
model.UpdateById(dbSql)
} else {
model.Insert(dbSql)
}
})
}
// @router /api/db/:dbId/sql [get]
func (c *DbController) GetSql() {
c.ReturnData(true, func(account *ctx.LoginAccount) interface{} {
// 获取用于是否有该dbsql的保存记录有则更改否则新增
dbSql := &models.DbSql{Type: 1, DbId: c.GetDbId()}
dbSql.CreatorId = account.Id
e := model.GetByCondition(dbSql)
if e != nil {
return nil
}
return dbSql
})
}
func (c *DbController) GetDbId() uint64 {
dbId, _ := strconv.Atoi(c.Ctx.Input.Param(":dbId"))
model.IsTrue(dbId > 0, "dbId错误")
return uint64(dbId)
}

View File

@@ -10,3 +10,8 @@ type MachineRunForm struct {
MachineId int64 `valid:"Required"`
Cmd string `valid:"Required"`
}
type DbSqlSaveForm struct {
Sql string `valid:"Required"`
Type int `valid:"Required"`
}

View File

@@ -1,19 +1,22 @@
package controllers
import (
"github.com/gorilla/websocket"
"mayfly-go/base"
"mayfly-go/base/ctx"
"mayfly-go/base/model"
"mayfly-go/machine"
"mayfly-go/models"
"net/http"
"strconv"
"github.com/gorilla/websocket"
)
type MachineController struct {
base.Controller
}
var upGrader = websocket.Upgrader{
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024 * 1024 * 10,
CheckOrigin: func(r *http.Request) bool {
@@ -22,69 +25,81 @@ var upGrader = websocket.Upgrader{
}
func (c *MachineController) Machines() {
c.ReturnData(true, func(account *base.LoginAccount) interface{} {
c.ReturnData(true, func(account *ctx.LoginAccount) interface{} {
return models.GetMachineList(c.GetPageParam())
})
}
func (c *MachineController) Run() {
c.ReturnData(true, func(account *base.LoginAccount) interface{} {
c.ReturnData(true, func(account *ctx.LoginAccount) interface{} {
cmd := c.GetString("cmd")
base.NotEmpty(cmd, "cmd不能为空")
model.NotEmpty(cmd, "cmd不能为空")
return machine.GetCli(c.GetMachineId()).Run(cmd)
res, err := c.getCli().Run(cmd)
model.BizErrIsNil(err, "执行命令失败")
return res
})
}
// 系统基本信息
func (c *MachineController) SysInfo() {
c.ReturnData(true, func(account *base.LoginAccount) interface{} {
return machine.GetSystemInfo(machine.GetCli(c.GetMachineId()))
c.ReturnData(true, func(account *ctx.LoginAccount) interface{} {
res, err := c.getCli().GetSystemInfo()
model.BizErrIsNil(err, "获取系统基本信息失败")
return res
})
}
// top命令信息
func (c *MachineController) Top() {
c.ReturnData(true, func(account *base.LoginAccount) interface{} {
return machine.GetTop(machine.GetCli(c.GetMachineId()))
c.ReturnData(true, func(account *ctx.LoginAccount) interface{} {
return c.getCli().GetTop()
})
}
func (c *MachineController) GetProcessByName() {
c.ReturnData(true, func(account *base.LoginAccount) interface{} {
c.ReturnData(true, func(account *ctx.LoginAccount) interface{} {
name := c.GetString("name")
base.NotEmpty(name, "name不能为空")
return machine.GetProcessByName(machine.GetCli(c.GetMachineId()), name)
model.NotEmpty(name, "name不能为空")
res, err := c.getCli().GetProcessByName(name)
model.BizErrIsNil(err, "获取失败")
return res
})
}
//func (c *MachineController) WsSSH() {
// wsConn, err := upGrader.Upgrade(c.Ctx.ResponseWriter, c.Ctx.Request, nil)
// if err != nil {
// panic(base.NewBizErr("获取requst responsewirte错误"))
// }
//
// cols, _ := c.GetInt("col", 80)
// rows, _ := c.GetInt("rows", 40)
//
// sws, err := machine.NewLogicSshWsSession(cols, rows, true, machine.GetCli(c.GetMachineId()), wsConn)
// if sws == nil {
// panic(base.NewBizErr("连接失败"))
// }
// //if wshandleError(wsConn, err) {
// // return
// //}
// defer sws.Close()
//
// quitChan := make(chan bool, 3)
// sws.Start(quitChan)
// go sws.Wait(quitChan)
//
// <-quitChan
func (c *MachineController) WsSSH() {
wsConn, err := upgrader.Upgrade(c.Ctx.ResponseWriter, c.Ctx.Request, nil)
if err != nil {
panic(model.NewBizErr("获取requst responsewirte错误"))
}
cols, _ := c.GetInt("cols", 80)
rows, _ := c.GetInt("rows", 40)
sws, err := machine.NewLogicSshWsSession(cols, rows, c.getCli(), wsConn)
if sws == nil {
panic(model.NewBizErr("连接失败"))
}
//if wshandleError(wsConn, err) {
// return
//}
defer sws.Close()
quitChan := make(chan bool, 3)
sws.Start(quitChan)
go sws.Wait(quitChan)
<-quitChan
}
func (c *MachineController) GetMachineId() uint64 {
machineId, _ := strconv.Atoi(c.Ctx.Input.Param(":machineId"))
base.IsTrue(machineId > 0, "machineId错误")
model.IsTrue(machineId > 0, "machineId错误")
return uint64(machineId)
}
func (c *MachineController) getCli() *machine.Cli {
cli, err := machine.GetCli(c.GetMachineId())
model.BizErrIsNil(err, "获取客户端错误")
return cli
}

16
controllers/vo/db.go Normal file
View File

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

127
db/db.go Normal file
View File

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

33
db/metadata.go Normal file
View File

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

21
go.mod
View File

@@ -1,21 +1,18 @@
module mayfly-go
go 1.13
require github.com/astaxie/beego v1.12.1
require github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 // indirect
go 1.15
require (
github.com/beego/beego/v2 v2.0.1
// jwt
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/gliderlabs/ssh v0.2.2
github.com/go-sql-driver/mysql v1.4.1
github.com/go-sql-driver/mysql v1.5.0
github.com/gorilla/websocket v1.4.2
github.com/mitchellh/mapstructure v1.3.3
github.com/pkg/sftp v1.11.0
github.com/pkg/sftp v1.12.0
//
github.com/robfig/cron/v3 v3.0.1
github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726
github.com/siddontang/go v0.0.0-20170517070808-cb568a3e5cc0
github.com/smartystreets/goconvey v1.6.4
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550
google.golang.org/appengine v1.6.6 // indirect
// ssh
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a
)

262
go.sum
View File

@@ -1,95 +1,321 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/OwnLocal/goes v1.0.0/go.mod h1:8rIFjBGTue3lCU0wplczcUgt9Gxgrkkrw7etMIcn8TM=
github.com/astaxie/beego v1.12.1 h1:dfpuoxpzLVgclveAXe4PyNKqkzgm5zF4tgF2B3kkM2I=
github.com/astaxie/beego v1.12.1/go.mod h1:kPBWpSANNbSdIqOc8SUL9h+1oyBMZhROeYsXQDbidWQ=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk=
github.com/astaxie/beego v1.12.3 h1:SAQkdD2ePye+v8Gn1r4X6IKZM1wd28EyUOVQ3PDSOOQ=
github.com/astaxie/beego v1.12.3/go.mod h1:p3qIm0Ryx7zeBHLljmd7omloyca1s4yu1a8kM1FkpIA=
github.com/beego/beego/v2 v2.0.1 h1:07a7Z0Ok5vbqyqh+q53sDPl9LdhKh0ZDy3gbyGrhFnE=
github.com/beego/beego/v2 v2.0.1/go.mod h1:8zyHi1FnWO1mZLwTn62aKRIZF/aIKvkCBB2JYs+eqQI=
github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd/go.mod h1:1b+Y/CofkYwXMUU0OhQqGvsY2Bvgr4j6jfT699wyZKQ=
github.com/beego/x2j v0.0.0-20131220205130-a0352aadc542/go.mod h1:kSeGC/p1AbBiEp5kat81+DSQrZenVBZXklMLaELspWU=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60=
github.com/casbin/casbin v1.7.0/go.mod h1:c67qKN6Oum3UF5Q1+BByfFxkwKvhwW57ITjqwtzR1KE=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80=
github.com/couchbase/go-couchbase v0.0.0-20181122212707-3e9b6e1258bb/go.mod h1:TWI8EKQMs5u5jLKW/tsb9VwauIrMIxQG1r5fMsswK5U=
github.com/couchbase/gomemcached v0.0.0-20181122193126-5125a94a666c/go.mod h1:srVSlQLB8iXBVXHgnqemxUXqN6FCvClgCMPCsjBDR7c=
github.com/coreos/etcd v3.3.25+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/couchbase/go-couchbase v0.0.0-20200519150804-63f3cdb75e0d/go.mod h1:TWI8EKQMs5u5jLKW/tsb9VwauIrMIxQG1r5fMsswK5U=
github.com/couchbase/gomemcached v0.0.0-20200526233749-ec430f949808/go.mod h1:srVSlQLB8iXBVXHgnqemxUXqN6FCvClgCMPCsjBDR7c=
github.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs=
github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/elastic/go-elasticsearch/v6 v6.8.5/go.mod h1:UwaDJsD3rWLM5rKNFzv9hgox93HoX8utj1kxD9aFUcI=
github.com/elazarl/go-bindata-assetfs v1.0.0 h1:G/bYguwHIzWq9ZoyUQqrjTmJbbYn3j3CKKpKinvZLFk=
github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/glendc/gopher-json v0.0.0-20170414221815-dc4743023d0c/go.mod h1:Gja1A+xZ9BoviGJNA2E9vFkPjjsl+CoJxSXiQM1UXtw=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-redis/redis v6.14.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-redis/redis/v7 v7.4.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg=
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/godror/godror v0.9.0 h1:IQ+HRUl00B1V03jR4AduWKkKlP9RsTq9E4iEG3gcCZY=
github.com/godror/godror v0.9.0/go.mod h1:06LLZykQEXjuUYiVKBa7Ls+AvbUZ8sbtolIALCeL/jw=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/ledisdb/ledisdb v0.0.0-20200510135210-d35789ec47e6/go.mod h1:n931TsDuKuq+uX4v1fulaMbA/7ZLLhjc85h7chZGBCQ=
github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o=
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mitchellh/mapstructure v1.3.3 h1:SzB1nHZ2Xi+17FP0zVQBHIZqvwRN9408fJO8h+eeNA8=
github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.4.0 h1:7ks8ZkOP5/ujthUsT07rNv+nkLXCQWKNHuwzOAesEks=
github.com/mitchellh/mapstructure v1.4.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg=
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
github.com/pelletier/go-toml v1.0.1/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
github.com/peterh/liner v1.0.1-0.20171122030339-3681c2a91233/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.11.0 h1:4Zv0OGbpkg4yNuUtH0s8rvoYxRCNyT29NVUo6pgPmxI=
github.com/pkg/sftp v1.11.0/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.12.0 h1:/f3b24xrDhkhddlaobPe2JgBqfdt+gC/NYl0QY9IOuI=
github.com/pkg/sftp v1.12.0/go.mod h1:fUqqXB5vEgVCZ131L+9say31RAri6aF6KDViawhxKK8=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.7.0 h1:wCi7urQOGBsYcQROHqpUUX4ct84xp40t9R9JX0FuA/U=
github.com/prometheus/client_golang v1.7.0/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.10.0 h1:RyRA7RzGXQZiW+tGMr7sxa85G1z0yOpM1qq5c8lNawc=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFBS8=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 h1:X+yvsM2yrEktyI+b2qND5gpH8YhURn0k8OCaeRnkINo=
github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644/go.mod h1:nkxAfR/5quYxwPZhyDxgasBMnRtBZd0FCEpawpjMUFg=
github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 h1:DAYUYH5869yV94zvCES9F51oYtN5oGlwjxJJz7ZCnik=
github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18/go.mod h1:nkxAfR/5quYxwPZhyDxgasBMnRtBZd0FCEpawpjMUFg=
github.com/siddontang/go v0.0.0-20170517070808-cb568a3e5cc0 h1:QIF48X1cihydXibm+4wfAc0r/qyPyuFiPFRNphdMpEE=
github.com/siddontang/go v0.0.0-20170517070808-cb568a3e5cc0/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw=
github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726 h1:xT+JlYxNGqyT+XcU8iUrN18JYed2TvG9yN5ULG2jATM=
github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw=
github.com/siddontang/ledisdb v0.0.0-20181029004158-becf5f38d373/go.mod h1:mF1DpOSOUiJRMR+FDqaqu3EBqrybQtrDDszLUZ6oxPg=
github.com/siddontang/goredis v0.0.0-20150324035039-760763f78400/go.mod h1:DDcKzU3qCuvj/tPnimWSsZZzvk9qvkvrIL5naVBPh5s=
github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z92TR1JKMkLLoaOQk++LVnOKL3ScbJ8GNGA=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec/go.mod h1:QBvMkMya+gXctz3kmljlUCu/yB3GZ6oee+dUozsezQE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/syndtr/goleveldb v0.0.0-20160425020131-cfa635847112/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
github.com/ugorji/go v0.0.0-20171122102828-84cb69a8af83/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ=
github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b/go.mod h1:Q12BUT7DqIlHRmgv3RskH+UCM/4eqVMgI0EMmlSpAXc=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/gopher-lua v0.0.0-20171031051903-609c9cd26973/go.mod h1:aEV29XrmTYFr3CiRxZeGHpkvbwq+prZduBqMaascyCU=
go.etcd.io/etcd v3.3.25+incompatible/go.mod h1:yaeTdrJi5lOmYerz05bd8+V7KubZs8YSFZfzsF9A6aI=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM=
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201217014255-9d1352758620 h1:3wPMTskHO3+O6jqTEXyFcsnuxMQOqYSaHsDxcbUXpqA=
golang.org/x/crypto v0.0.0-20201217014255-9d1352758620/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1 h1:ogLJMz+qpzav7lGMh10LMvAkM/fAoGlaiiHYiFYdm80=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221 h1:/ZHdbVpdR/jk3g30/d4yUL0JU9kksj8+F/bnQUVLGDM=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20200117065230-39095c1d176c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20201211185031-d93e913c1a58 h1:1Bs6RVeBFtLZ8Yi1Hk07DiOqzvwLD/4hln4iahvFlag=
golang.org/x/tools v0.0.0-20201211185031-d93e913c1a58/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.5/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=

1
lastupdate.tmp Executable file
View File

@@ -0,0 +1 @@
{"/Users/hml/Desktop/project/go/mayfly-go/controllers":1610075329396211594}

View File

@@ -3,16 +3,17 @@ package machine
import (
"errors"
"fmt"
"github.com/pkg/sftp"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/terminal"
"io"
"mayfly-go/base"
"mayfly-go/base/model"
"mayfly-go/models"
"net"
"os"
"sync"
"time"
"github.com/pkg/sftp"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/terminal"
)
// 客户端信息
@@ -27,20 +28,20 @@ var clientCache sync.Map
var mutex sync.Mutex
// 从缓存中获取客户端信息,不存在则查库,并新建
func GetCli(machineId uint64) *Cli {
func GetCli(machineId uint64) (*Cli, error) {
mutex.Lock()
defer mutex.Unlock()
load, ok := clientCache.Load(machineId)
if ok {
return load.(*Cli)
return load.(*Cli), nil
}
cli, err := newClient(models.GetMachineById(machineId))
if err != nil {
panic(base.NewBizErr(err.Error()))
return nil, err
}
clientCache.LoadOrStore(machineId, cli)
return cli
return cli, nil
}
//根据机器信息创建客户端对象
@@ -53,7 +54,7 @@ func newClient(machine *models.Machine) (*Cli, error) {
cli.machine = machine
err := cli.connect()
if err != nil {
return nil, errors.New("获取机器client失败" + err.Error())
return nil, err
}
return cli, nil
}
@@ -114,12 +115,12 @@ func (c *Cli) Close() {
func (c *Cli) GetSftpCli() *sftp.Client {
if c.client == nil {
if err := c.connect(); err != nil {
panic(base.NewBizErr("连接ssh失败" + err.Error()))
panic(model.NewBizErr("连接ssh失败" + err.Error()))
}
}
client, serr := sftp.NewClient(c.client, sftp.MaxPacket(1<<15))
if serr != nil {
panic(base.NewBizErr("获取sftp client失败" + serr.Error()))
panic(model.NewBizErr("获取sftp client失败" + serr.Error()))
}
return client
}
@@ -136,17 +137,19 @@ func (c *Cli) GetSession() (*ssh.Session, error) {
//执行shell
//@param shell shell脚本命令
func (c *Cli) Run(shell string) string {
func (c *Cli) Run(shell string) (*string, error) {
session, err := c.GetSession()
if err != nil {
panic(base.NewBizErr("获取ssh session失败" + err.Error()))
c.Close()
return nil, err
}
defer session.Close()
buf, rerr := session.CombinedOutput(shell)
if rerr != nil {
panic(base.NewBizErr("执行命令失败:" + rerr.Error()))
return nil, rerr
}
return string(buf)
res := string(buf)
return &res, nil
}
//执行带交互的命令

View File

@@ -1,12 +1,13 @@
package machine
import (
"github.com/siddontang/go/log"
"io/ioutil"
"mayfly-go/base"
"mayfly-go/base/model"
"mayfly-go/base/utils"
"mayfly-go/models"
"time"
"github.com/siddontang/go/log"
)
const BasePath = "./machine/shell/"
@@ -16,26 +17,29 @@ const MonitorTemp = "cpuRate:{cpuRate}%,memRate:{memRate}%,sysLoad:{sysLoad}\n"
// shell文件内容缓存避免每次读取文件
var shellCache = make(map[string]string)
func GetProcessByName(cli *Cli, name string) string {
return cli.Run(getShellContent("sys_info"))
func (c *Cli) GetProcessByName(name string) (*string, error) {
return c.Run(getShellContent("sys_info"))
}
func GetSystemInfo(cli *Cli) string {
return cli.Run(getShellContent("system_info"))
func (c *Cli) GetSystemInfo() (*string, error) {
return c.Run(getShellContent("system_info"))
}
func GetMonitorInfo(cli *Cli) *models.MachineMonitor {
func (c *Cli) GetMonitorInfo() *models.MachineMonitor {
mm := new(models.MachineMonitor)
res := cli.Run(getShellContent("monitor"))
res, _ := c.Run(getShellContent("monitor"))
if res == nil {
return nil
}
resMap := make(map[string]interface{})
utils.ReverStrTemplate(MonitorTemp, res, resMap)
utils.ReverStrTemplate(MonitorTemp, *res, resMap)
err := utils.Map2Struct(resMap, mm)
if err != nil {
log.Error("解析machine monitor: %s", err.Error())
return nil
}
mm.MachineId = cli.machine.Id
mm.MachineId = c.machine.Id
mm.CreateTime = time.Now()
return mm
}
@@ -47,7 +51,7 @@ func getShellContent(name string) string {
return cacheShell
}
bytes, err := ioutil.ReadFile(BasePath + name + ".sh")
base.ErrIsNil(err, "获取shell文件失败")
model.ErrIsNil(err, "获取shell文件失败")
shellStr := string(bytes)
shellCache[name] = shellStr
return shellStr

View File

@@ -1,7 +1,7 @@
package machine
import (
"mayfly-go/base"
"mayfly-go/base/model"
"mayfly-go/base/utils"
"strconv"
"strings"
@@ -11,10 +11,10 @@ type SystemVersion struct {
Version string
}
func GetSystemVersion(cli *Cli) *SystemVersion {
res := cli.Run("cat /etc/redhat-release")
func (c *Cli) GetSystemVersion() *SystemVersion {
res, _ := c.Run("cat /etc/redhat-release")
return &SystemVersion{
Version: res,
Version: *res,
}
}
@@ -68,15 +68,15 @@ type Top struct {
AvailMem int `json:"availMem"`
}
func GetTop(cli *Cli) *Top {
res := cli.Run("top -b -n 1 | head -5")
func (c *Cli) GetTop() *Top {
res, _ := c.Run("top -b -n 1 | head -5")
topTemp := "top - {upAndUsers}, load average: {loadavg}\n" +
"Tasks:{totalTask} total,{runningTask} running,{sleepingTask} sleeping,{stoppedTask} stopped,{zombieTask} zombie\n" +
"%Cpu(s):{cpuUs} us,{cpuSy} sy,{cpuNi} ni,{cpuId} id,{cpuWa} wa,{cpuHi} hi,{cpuSi} si,{cpuSt} st\n" +
"KiB Mem :{totalMem} total,{freeMem} free,{usedMem} used,{cacheMem} buff/cache\n" +
"KiB Swap:{totalSwap} total,{freeSwap} free,{usedSwap} used. {availMem} avail Mem \n"
resMap := make(map[string]interface{})
utils.ReverStrTemplate(topTemp, res, resMap)
utils.ReverStrTemplate(topTemp, *res, resMap)
//17:14:07 up 5 days, 6:30, 2
timeUpAndUserStr := resMap["upAndUsers"].(string)
@@ -93,7 +93,7 @@ func GetTop(cli *Cli) *Top {
top := &Top{Time: time, Up: up, NowUsers: users, OneMinLoadavg: float32(oneMinLa), FiveMinLoadavg: float32(fiveMinLa), FifteenMinLoadavg: float32(fifMinLa)}
err := utils.Map2Struct(resMap, top)
base.BizErrIsNil(err, "解析top出错")
model.BizErrIsNil(err, "解析top出错")
return top
}

View File

@@ -3,51 +3,14 @@ package machine
import (
"bytes"
"encoding/json"
"github.com/astaxie/beego/logs"
"github.com/gorilla/websocket"
"golang.org/x/crypto/ssh"
"io"
"sync"
"time"
)
//func WsSsh(c *controllers.MachineController) {
// wsConn, err := upGrader.Upgrade(c.Ctx.ResponseWriter, c.Ctx.Request, nil)
// if err != nil {
// panic(base.NewBizErr("获取requst responsewirte错误"))
// }
//
// cols, _ := c.GetInt("col", 80)
// rows, _ := c.GetInt("rows", 40)
//
// sws, err := NewLogicSshWsSession(cols, rows, true, GetCli(c.GetMachineId()).client, wsConn)
// if sws == nil {
// panic(base.NewBizErr("连接失败"))
// }
// //if wshandleError(wsConn, err) {
// // return
// //}
// defer sws.Close()
//
// quitChan := make(chan bool, 3)
// sws.Start(quitChan)
// go sws.Wait(quitChan)
//
// <-quitChan
// //保存日志
//
// ////write logs
// //xtermLog := model.SshLog{
// // StartedAt: startTime,
// // UserId: userM.Id,
// // Log: sws.LogString(),
// // MachineId: idx,
// // ClientIp: cIp,
// //}
// //err = xtermLog.Create()
// //if wshandleError(wsConn, err) {
// // return
//}
"github.com/beego/beego/v2/core/logs"
"github.com/gorilla/websocket"
"golang.org/x/crypto/ssh"
)
type safeBuffer struct {
buffer bytes.Buffer
@@ -75,9 +38,9 @@ const (
wsMsgResize = "resize"
)
type wsMsg struct {
type WsMsg struct {
Type string `json:"type"`
Cmd string `json:"cmd"`
Msg string `json:"msg"`
Cols int `json:"cols"`
Rows int `json:"rows"`
}
@@ -89,11 +52,9 @@ type LogicSshWsSession struct {
inputFilterBuff *safeBuffer //用来过滤输入的命令和ssh_filter配置对比的
session *ssh.Session
wsConn *websocket.Conn
isAdmin bool
IsFlagged bool `comment:"当前session是否包含禁止命令"`
}
func NewLogicSshWsSession(cols, rows int, isAdmin bool, cli *Cli, wsConn *websocket.Conn) (*LogicSshWsSession, error) {
func NewLogicSshWsSession(cols, rows int, cli *Cli, wsConn *websocket.Conn) (*LogicSshWsSession, error) {
sshSession, err := cli.GetSession()
if err != nil {
return nil, err
@@ -132,8 +93,6 @@ func NewLogicSshWsSession(cols, rows int, isAdmin bool, cli *Cli, wsConn *websoc
inputFilterBuff: inputBuf,
session: sshSession,
wsConn: wsConn,
isAdmin: isAdmin,
IsFlagged: false,
}, nil
}
@@ -167,15 +126,13 @@ func (sws *LogicSshWsSession) receiveWsMsg(exitCh chan bool) {
//read websocket msg
_, wsData, err := wsConn.ReadMessage()
if err != nil {
logs.Error("reading webSocket message failed")
//panic(base.NewBizErr("reading webSocket message failed"))
logs.Error("reading webSocket message failed: ", err)
return
}
//unmashal bytes into struct
msgObj := wsMsg{}
msgObj := WsMsg{}
if err := json.Unmarshal(wsData, &msgObj); err != nil {
logs.Error("unmarshal websocket message failed")
//panic(base.NewBizErr("unmarshal websocket message failed"))
logs.Error("unmarshal websocket message failed", err)
}
switch msgObj.Type {
case wsMsgResize:
@@ -183,7 +140,6 @@ func (sws *LogicSshWsSession) receiveWsMsg(exitCh chan bool) {
if msgObj.Cols > 0 && msgObj.Rows > 0 {
if err := sws.session.WindowChange(msgObj.Rows, msgObj.Cols); err != nil {
logs.Error("ssh pty change windows size failed")
//panic(base.NewBizErr("ssh pty change windows size failed"))
}
}
case wsMsgCmd:
@@ -193,7 +149,7 @@ func (sws *LogicSshWsSession) receiveWsMsg(exitCh chan bool) {
// logs.Error("websock cmd string base64 decoding failed")
// //panic(base.NewBizErr("websock cmd string base64 decoding failed"))
//}
sws.sendWebsocketInputCommandToSshSessionStdinPipe([]byte(msgObj.Cmd))
sws.sendWebsocketInputCommandToSshSessionStdinPipe([]byte(msgObj.Msg))
}
}
}
@@ -203,7 +159,6 @@ func (sws *LogicSshWsSession) receiveWsMsg(exitCh chan bool) {
func (sws *LogicSshWsSession) sendWebsocketInputCommandToSshSessionStdinPipe(cmdBytes []byte) {
if _, err := sws.stdinPipe.Write(cmdBytes); err != nil {
logs.Error("ws cmd bytes write to ssh.stdin pipe failed")
//panic(base.NewBizErr("ws cmd bytes write to ssh.stdin pipe failed"))
}
}
@@ -228,12 +183,10 @@ func (sws *LogicSshWsSession) sendComboOutput(exitCh chan bool) {
err := wsConn.WriteMessage(websocket.TextMessage, bs)
if err != nil {
logs.Error("ssh sending combo output to webSocket failed")
//panic(base.NewBizErr("ssh sending combo output to webSocket failed"))
}
_, err = sws.logBuff.Write(bs)
if err != nil {
logs.Error("combo output to log buffer failed")
//panic(base.NewBizErr("combo output to log buffer failed"))
}
sws.comboOutput.buffer.Reset()
}
@@ -246,8 +199,7 @@ func (sws *LogicSshWsSession) sendComboOutput(exitCh chan bool) {
func (sws *LogicSshWsSession) Wait(quitChan chan bool) {
if err := sws.session.Wait(); err != nil {
logs.Error("ssh session wait failed")
//panic(base.NewBizErr("ssh session wait failed"))
logs.Error("ssh session wait failed: ", err)
setQuit(quitChan)
}
}

15
main.go
View File

@@ -1,15 +1,16 @@
package main
import (
"github.com/astaxie/beego"
"github.com/astaxie/beego/context"
"github.com/astaxie/beego/orm"
"github.com/astaxie/beego/plugins/cors"
_ "github.com/go-sql-driver/mysql"
_ "mayfly-go/routers"
scheduler "mayfly-go/scheudler"
"net/http"
"strings"
"github.com/beego/beego/v2/client/orm"
"github.com/beego/beego/v2/server/web"
"github.com/beego/beego/v2/server/web/context"
"github.com/beego/beego/v2/server/web/filter/cors"
_ "github.com/go-sql-driver/mysql"
)
func init() {
@@ -21,7 +22,7 @@ func init() {
func main() {
orm.Debug = true
// 跨域配置
beego.InsertFilter("/**", beego.BeforeRouter, cors.Allow(&cors.Options{
web.InsertFilter("/**", web.BeforeRouter, cors.Allow(&cors.Options{
AllowAllOrigins: true,
AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
AllowHeaders: []string{"Origin", "Authorization", "Access-Control-Allow-Origin", "Access-Control-Allow-Headers", "Content-Type"},
@@ -30,7 +31,7 @@ func main() {
}))
scheduler.Start()
defer scheduler.Stop()
beego.Run()
web.Run()
}
// 解决beego无法访问根目录静态文件

File diff suppressed because it is too large Load Diff

View File

@@ -13,27 +13,31 @@
"core-js": "^3.6.5",
"echarts": "^4.8.0",
"element-ui": "^2.13.2",
"sql-formatter": "^2.3.3",
"vue": "^2.6.11",
"vue-class-component": "^7.2.3",
"vue-codemirror": "^4.0.6",
"vue-property-decorator": "^8.4.2",
"vue-router": "^3.2.0",
"vuex": "^3.4.0"
"vuex": "^3.4.0",
"xterm": "^4.9.0",
"xterm-addon-fit": "^0.4.0"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^2.33.0",
"@typescript-eslint/parser": "^2.33.0",
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-eslint": "~4.5.0",
"@vue/cli-plugin-router": "~4.5.0",
"@vue/cli-plugin-typescript": "~4.5.0",
"@vue/cli-plugin-vuex": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"@vue/cli-plugin-babel": "~4.5.9",
"@vue/cli-plugin-eslint": "~4.5.9",
"@vue/cli-plugin-router": "~4.5.9",
"@vue/cli-plugin-typescript": "~4.5.9",
"@vue/cli-plugin-vuex": "~4.5.9",
"@vue/cli-service": "~4.5.9",
"@vue/eslint-config-typescript": "^5.0.2",
"eslint": "^6.7.2",
"eslint-plugin-vue": "^6.2.2",
"typescript": "~3.9.3",
"typescript": "~3.9.7",
"vue-template-compiler": "^2.6.11",
"sass-resources-loader": "^2.0.3",
"sass-resources-loader": "^2.1.1",
"ts-import-plugin": "^1.6.6",
"less": "^3.10.3",
"less-loader": "^5.0.0"

View File

@@ -4,7 +4,7 @@
</div>
</template>
<style lang="scss">
<style lang="less">
#app {
background-color: #222d32;
}

View File

@@ -0,0 +1,60 @@
/**
* 不符合业务断言错误
*/
class AssertError extends Error {
constructor(message: string) {
super(message); // (1)
// 错误类名
this.name = "AssertError";
}
}
/**
* 断言对象不为null或undefiend
*
* @param obj 对象
* @param msg 错误提示
*/
export function notNull(obj: any, msg: string) {
if (obj == null || obj == undefined) {
throw new AssertError(msg)
}
}
/**
* 断言字符串不能为空
*
* @param str 字符串
* @param msg 错误提示
*/
export function notEmpty(str: string, msg: string) {
if (str == null || str == undefined || str == '') {
throw new AssertError(msg);
}
}
/**
* 断言两对象相等
*
* @param obj1 对象1
* @param obj2 对象2
* @param msg 错误消息
*/
export function isEquals(obj1: any, obj2: any, msg: string) {
if (obj1 !== obj2) {
throw new AssertError(msg);
}
}
/**
* 断言表达式为true
*
* @param obj1 对象1
* @param obj2 对象2
* @param msg 错误消息
*/
export function isTrue(condition: boolean, msg: string) {
if (!condition) {
throw new AssertError(msg);
}
}

View File

@@ -167,11 +167,28 @@ export default class App extends Vue {
icon: 'el-icon-menu',
children: [
{
id: 11,
type: 1,
name: '机器列表',
url: '/machines',
icon: 'el-icon-menu',
code: 'index',
code: 'machines',
},
],
},
{
id: 2,
type: 1,
name: 'DBMS',
icon: 'el-icon-menu',
children: [
{
id: 21,
type: 1,
name: '数据查询',
url: '/db-select-data',
icon: 'el-icon-menu',
code: 'db-select',
},
],
},
@@ -290,7 +307,7 @@ export default class App extends Vue {
.menu {
border-right: none;
// 禁止选择
moz-user-select: -moz-none;
-moz-user-select: -moz-none;
-moz-user-select: none;
-o-user-select: none;
-khtml-user-select: none;

View File

@@ -15,9 +15,18 @@ import './assets/css/style.css'
Vue.config.productionTip = false
// 注册组件后即可使用
// Vue.component('v-chart', ECharts)
Vue.use(ElementUI)
// 全局error处理
Vue.config.errorHandler = function(err, vm ,info) {
// 如果是断言错误,则进行提示即可
if (err.name == 'AssertError') {
ElementUI.Message.error(err.message)
} else {
console.error(err, info)
}
}
new Vue({
router,
store,

View File

@@ -30,6 +30,24 @@ const routes: Array<RouteConfig> = [
keepAlive: false
},
component: () => import('@/views/machine')
},
{
path: 'db-select-data',
name: 'dbs',
meta: {
title: 'DBMS',
keepAlive: false
},
component: () => import('@/views/db/SelectData.vue')
// children: [{
// path: 'select',
// name: 'select-data',
// meta: {
// title: 'DBMS',
// keepAlive: false
// },
// component: () => import('@/views/db/SelectData.vue')
// }]
}]
},
]

View File

@@ -2,3 +2,5 @@ declare module '*.vue' {
import Vue from 'vue'
export default Vue
}
declare module 'vue-codemirror';
declare module 'sql-formatter';

View File

@@ -0,0 +1,312 @@
<template>
<div>
<div class="toolbar">
<div class="fl">
<el-select
size="small"
v-model="dbId"
placeholder="请选择数据库"
@change="changeDb"
@clear="clearDb"
clearable
filterable
>
<el-option
v-for="item in dbs.list"
:key="item.id"
:label="`${item.name} [${item.type}]`"
:value="item.id"
>
</el-option>
</el-select>
</div>
</div>
<el-container style="height: 50%; border: 1px solid #eee; margin-top: 1px">
<el-aside width="70%" style="background-color: rgb(238, 241, 246)">
<div class="toolbar">
<div class="fl">
<el-button
@click="runSql"
type="success"
icon="el-icon-video-play"
size="mini"
plain
>执行</el-button
>
<el-button
@click="formatSql"
type="primary"
icon="el-icon-magic-stick"
size="mini"
plain
>格式化</el-button
>
<el-button
@click="saveSql"
type="primary"
icon="el-icon-document-add"
size="mini"
plain
>保存</el-button
>
</div>
</div>
<codemirror
class="codesql"
ref="cmEditor"
v-model="sql"
:placeholder="placeholder"
:options="cmOptions"
@inputRead="inputRead"
/>
</el-aside>
<el-container style="margin-left: 2px">
<el-header
style="text-align: left; height: 45px; font-size: 12px; padding: 0px"
>
<el-select
v-model="tableName"
placeholder="请选择表"
@change="changeTable"
clearable
filterable
style="width: 99%"
>
<el-option
v-for="item in tableMetadata"
:key="item.tableName"
:label="
item.tableName +
(item.tableComment != '' ? `【${item.tableComment}】` : '')
"
:value="item.tableName"
>
</el-option>
</el-select>
</el-header>
<el-main style="padding: 0px; height: 100%; overflow: hidden">
<el-table :data="columnMetadata" height="100%" size="mini">
<el-table-column prop="columnName" label="名称"> </el-table-column>
<el-table-column prop="columnType" label="类型"> </el-table-column>
<el-table-column prop="columnComment" label="备注">
</el-table-column>
</el-table>
</el-main>
</el-container>
</el-container>
<el-table
style="margin-top: 1px"
:data="selectRes.data"
size="mini"
max-height="300"
stripe
border
>
<el-table-column
min-width="100"
align="center"
v-for="item in selectRes.tableColumn"
:key="item"
:prop="item"
:label="item"
>
</el-table-column>
</el-table>
<!-- <el-pagination
style="text-align: center"
background
layout="prev, pager, next, total, jumper"
:total="data.total"
:current-page.sync="params.pageNum"
:page-size="params.pageSize"
/> -->
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
import { dbApi } from './api'
import 'codemirror/theme/ambiance.css'
import 'codemirror/addon/hint/show-hint.css'
// import base style
import 'codemirror/lib/codemirror.css'
// 引入主题后还需要在 options 中指定主题才会生效
import 'codemirror/theme/base16-light.css'
// require('codemirror/addon/edit/matchbrackets')
require('codemirror/addon/selection/active-line')
import { codemirror } from 'vue-codemirror'
import 'codemirror/mode/sql/sql.js'
import 'codemirror/addon/hint/show-hint.js'
import 'codemirror/addon/hint/sql-hint.js'
import sqlFormatter from 'sql-formatter'
import { notEmpty } from '@/common/assert'
@Component({
name: 'SelectData',
components: {
codemirror,
},
})
export default class SelectData extends Vue {
dbs = []
tables = []
dbId = ''
tableName = ''
tableMetadata = []
columnMetadata = []
sql = ''
selectRes = {
tableColumn: [],
data: [],
}
params = {
pageNum: 1,
pageSize: 10,
}
placeholder = 'sqlshuru'
cmOptions = {
tabSize: 4,
mode: 'text/x-sql',
// theme: 'cobalt',
lineNumbers: true,
line: true,
indentWithTabs: true,
smartIndent: true,
// matchBrackets: true,
theme: 'base16-light',
autofocus: true,
extraKeys: { Tab: 'autocomplete' }, // 自定义快捷键
hintOptions: {
completeSingle: false,
// 自定义提示选项
tables: {},
},
// more CodeMirror options...
}
get codemirror() {
return this.$refs.cmEditor['codemirror']
}
mounted() {
this.search()
}
// 输入字符给提示
inputRead(instance: any, changeObj: any) {
if (/^[a-zA-Z]/.test(changeObj.text[0])) {
this.showHint()
}
}
// 执行sql
async runSql() {
notEmpty(this.dbId, '请先选择数据库')
// 没有选中的文本,则为全部文本
let selectSql = this.codemirror.getSelection()
if (selectSql == '') {
selectSql = this.sql
}
notEmpty(this.sql, '内容不能为空')
const res = await dbApi.selectData.request({
id: this.dbId,
selectSql,
})
let tableColumn: any
let data
if (res.length > 0) {
tableColumn = Object.keys(res[0])
data = res
} else {
tableColumn = []
data = []
}
this.selectRes.tableColumn = tableColumn
this.selectRes.data = data
}
async saveSql() {
notEmpty(this.sql, 'sql内容不能为空')
notEmpty(this.dbId, '请先选择数据库')
await dbApi.saveSql.request({ id: this.dbId, sql: this.sql, type: 1 })
this.$message.success('保存成功')
}
// 更改数据库事件
async changeDb(id: number) {
if (!id) {
return
}
this.clearDb()
this.tableMetadata = await dbApi.tableMetadata.request({ id })
// 赋值第一个表信息
if (this.tableMetadata.length > 0) {
this.tableName = this.tableMetadata[0]['tableName']
this.changeTable(this.tableName)
}
const dbSql = await dbApi.getSql.request({ id, type: 1 })
if (dbSql) {
this.sql = dbSql.sql
}
this.cmOptions.hintOptions.tables = await dbApi.hintTables.request({
id: this.dbId,
})
}
// 清空数据库事件
clearDb() {
this.tableName = ''
this.tableMetadata = []
this.columnMetadata = []
this.selectRes.data = []
this.selectRes.tableColumn = []
this.sql = ''
}
// 选择表事件
async changeTable(tableName: string) {
if (tableName == '') {
return
}
this.columnMetadata = await dbApi.columnMetadata.request({
id: this.dbId,
tableName,
})
}
// 自动提示功能
showHint() {
this.codemirror.showHint()
}
formatSql() {
/* 获取文本编辑器内容*/
let sqlContent = ''
sqlContent = this.codemirror.getValue()
/* 将sql内容进行格式后放入编辑器中*/
this.codemirror.setValue(sqlFormatter.format(sqlContent))
}
async search() {
this.dbs = await dbApi.dbs.request(this.params)
}
}
</script>
<style>
.codesql {
font-size: 10pt;
font-family: Consolas, Menlo, Monaco, Lucida Console, Liberation Mono,
DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace, serif;
}
</style>

View File

@@ -0,0 +1,25 @@
import Api from '@/common/Api';
export const dbApi = {
// 获取权限列表
dbs: Api.create("/dbs", 'get'),
tableMetadata: Api.create("/db/{id}/t-metadata", 'get'),
columnMetadata: Api.create("/db/{id}/c-metadata", 'get'),
// 获取表即列提示
hintTables: Api.create("/db/{id}/hint-tables", 'get'),
selectData: Api.create("/db/{id}/select", 'get'),
// 保存sql
saveSql: Api.create("/db/{id}/sql", 'post'),
// 获取保存的sql
getSql: Api.create("/db/{id}/sql", 'get'),
lsFile: Api.create("/devops/machines/files/{fileId}/ls", 'get'),
rmFile: Api.create("/devops/machines/files/{fileId}/rm", 'delete'),
uploadFile: Api.create("/devops/machines/files/upload", 'post'),
fileContent: Api.create("/devops/machines/files/{fileId}/cat", 'get'),
// 修改文件内容
updateFileContent: Api.create("/devops/machines/files/{id}", 'put'),
// 添加文件or目录
addConf: Api.create("/devops/machines/{machineId}/files", 'post'),
// 删除配置的文件or目录
delConf: Api.create("/devops/machines/files/{id}", 'delete'),
}

View File

@@ -0,0 +1 @@
export { default } from './SelectData.vue';

View File

@@ -8,7 +8,8 @@
size="mini"
@click="openFormDialog(false)"
plain
>添加</el-button>
>添加</el-button
>
<el-button
type="primary"
icon="el-icon-edit"
@@ -16,38 +17,51 @@
:disabled="currentId == null"
@click="openFormDialog(currentData)"
plain
>编辑</el-button>
>编辑</el-button
>
<el-button
:disabled="currentId == null"
@click="deleteMachine(currentId)"
type="danger"
icon="el-icon-delete"
size="mini"
>删除</el-button>
>删除</el-button
>
<el-button
type="success"
:disabled="currentId == null"
@click="fileManage(currentData)"
size="mini"
plain
>文件管理</el-button>
>文件管理</el-button
>
</div>
<div style="float: right;">
<div style="float: right">
<el-input
placeholder="host"
size="mini"
style="width: 140px;"
style="width: 140px"
v-model="params.host"
@clear="search"
plain
clearable
></el-input>
<el-button @click="search" type="success" icon="el-icon-search" size="mini"></el-button>
<el-button
@click="search"
type="success"
icon="el-icon-search"
size="mini"
></el-button>
</div>
</div>
<el-table :data="data.list" stripe style="width: 100%" @current-change="choose">
<el-table
:data="data.list"
stripe
style="width: 100%"
@current-change="choose"
>
<el-table-column label="选择" width="55px">
<template slot-scope="scope">
<el-radio v-model="currentId" :label="scope.row.id">
@@ -70,7 +84,8 @@
icom="el-icon-tickets"
size="mini"
plain
>基本信息</el-button>
>基本信息</el-button
>
<el-button
type="primary"
@click="monitor(scope.row.id)"
@@ -78,14 +93,24 @@
icom="el-icon-tickets"
size="mini"
plain
>监控</el-button>
>监控</el-button
>
<el-button
type="success"
@click="serviceManager(scope.row)"
:ref="scope.row"
size="mini"
plain
>服务管理</el-button>
>服务管理</el-button
>
<el-button
type="success"
@click="showTerminal(scope.row)"
:ref="scope.row"
size="mini"
plain
>终端</el-button
>
</template>
</el-table-column>
</el-table>
@@ -100,17 +125,32 @@
/>
<el-dialog title="基本信息" :visible.sync="infoDialog.visible" width="30%">
<div style="white-space: pre-line;">{{infoDialog.info}}</div>
<div style="white-space: pre-line">{{ infoDialog.info }}</div>
<!-- <span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false"> </el-button>
<el-button type="primary" @click="dialogVisible = false"> </el-button>
</span>-->
</el-dialog>
<el-dialog @close="closeMonitor" title="监控信息" :visible.sync="monitorDialog.visible" width="60%">
<el-dialog
@close="closeMonitor"
title="监控信息"
:visible.sync="monitorDialog.visible"
width="60%"
>
<monitor ref="monitorDialog" :machineId="monitorDialog.machineId" />
</el-dialog>
<el-dialog
title="终端"
:visible.sync="terminalDialog.visible"
width="70%"
:close-on-click-modal="false"
@close="closeTermnial"
>
<ssh-terminal ref="terminal" :socketURI="terminalDialog.socketUri" />
</el-dialog>
<!-- <FileManage
:title="dialog.title"
:visible.sync="dialog.visible"
@@ -132,12 +172,14 @@ import { Component, Vue } from 'vue-property-decorator'
import { DynamicFormDialog } from '@/components/dynamic-form'
import Monitor from './Monitor.vue'
import { machineApi } from './api'
import SshTerminal from './SshTerminal.vue'
@Component({
name: 'MachineList',
components: {
DynamicFormDialog,
Monitor,
SshTerminal,
},
})
export default class MachineList extends Vue {
@@ -166,6 +208,10 @@ export default class MachineList extends Vue {
visible: false,
title: '',
}
terminalDialog = {
visible: false,
socketUri: '',
}
formDialog = {
visible: false,
title: '',
@@ -291,6 +337,18 @@ export default class MachineList extends Vue {
md.cancelInterval()
}
showTerminal(row: any) {
this.terminalDialog.visible = true
this.terminalDialog.socketUri = `ws://localhost:8888/api/machines/${row.id}/terminal`
}
closeTermnial() {
this.terminalDialog.visible = false
this.terminalDialog.socketUri = ''
const t: any = this.$refs['terminal']
t.closeAll()
}
openFormDialog(redis: any) {
let dialogTitle
if (redis) {
@@ -330,4 +388,7 @@ export default class MachineList extends Vue {
</script>
<style>
.el-dialog__body {
padding: 2px 2px;
}
</style>

View File

@@ -0,0 +1,138 @@
<template>
<div style="height: 600px" id="xterm" class="xterm" />
</template>
<script>
import 'xterm/css/xterm.css'
import { Terminal } from 'xterm'
import { FitAddon } from 'xterm-addon-fit'
export default {
name: 'Xterm',
props: {
socketURI: {
type: String,
default: '',
},
},
watch: {
socketURI(val) {
if (val !== '') {
this.initSocket()
}
},
},
mounted() {
this.initSocket()
// this.initTerm()
},
beforeDestroy() {
this.socket.close()
this.term.dispose()
},
methods: {
initXterm() {
const term = new Terminal({
fontSize: 14,
cursorBlink: true,
// cursorStyle: 'underline', //光标样式
disableStdin: false,
theme: {
foreground: "#7e9192", //字体
background: "#002833", //背景色
cursor: "help", //设置光标
lineHeight: 16
}
})
const fitAddon = new FitAddon()
term.loadAddon(fitAddon)
term.open(document.getElementById('xterm'))
fitAddon.fit()
term.focus()
this.term = term
// / **
// *添加事件监听器,用于按下键时的事件。事件值包含
// *将在data事件以及DOM事件中发送的字符串
// *触发了它。
// * @返回一个IDisposable停止监听。
// * /
// / ** 更新xterm 4.x新增
// *为数据事件触发时添加事件侦听器。发生这种情况
// *用户输入或粘贴到终端时的示例。事件值
// *是`string`结果的结果,在典型的设置中,应该通过
// *到支持pty。
// * @返回一个IDisposable停止监听。
// * /
// 支持输入与粘贴方法
term.onData((key) => {
const cmd = {
type: 'cmd',
msg: key,
}
this.send(cmd)
})
// 为解决窗体resize方法才会向后端发送列数和行数所以页面加载时也要触发此方法
this.send({
type: 'resize',
Cols: parseInt(term.cols),
Rows: parseInt(term.rows),
})
},
initSocket() {
this.socket = new WebSocket(this.socketURI)
// 监听socket连接
this.socket.onopen = this.open
// 监听socket错误信息
this.socket.onerror = this.error
// 监听socket消息
this.socket.onmessage = this.getMessage
// 发送socket消息
this.socket.onsend = this.send
},
open: function () {
console.log('socket连接成功')
this.initXterm()
//开启心跳
// this.start();
},
error: function () {
console.log('连接错误')
//重连
this.reconnect()
},
close: function () {
this.socket.close()
console.log('socket已经关闭')
//重连
// this.reconnect()
},
getMessage: function (msg) {
// console.log(msg)
this.term.write(msg['data'])
//msg是返回的数据
// msg = JSON.parse(msg.data);
// this.socket.send("ping");//有事没事ping一下看看ws还活着没
// //switch用于处理返回的数据根据返回数据的格式去判断
// switch (msg["operation"]) {
// case "stdout":
// this.term.write(msg["data"]);//这里write也许不是固定的失败后找后端看一下该怎么往term里面write
// break;
// default:
// console.error("Unexpected message type:", msg);//但是错误是固定的。。。。
// }
//收到服务器信息,心跳重置
// this.reset();
},
send: function (msg) {
// console.log(msg)
this.socket.send(JSON.stringify(msg))
},
closeAll() {
this.close()
this.term.dispose()
this.term = null
},
},
}
</script>

View File

@@ -22,4 +22,5 @@ export const machineApi = {
addConf: Api.create("/devops/machines/{machineId}/files", 'post'),
// 删除配置的文件or目录
delConf: Api.create("/devops/machines/files/{id}", 'delete'),
terminal: Api.create("/api/machines/{id}/terminal", 'get')
}

View File

@@ -1,13 +1,14 @@
package models
import (
"github.com/astaxie/beego/orm"
"mayfly-go/base"
"mayfly-go/base/model"
"mayfly-go/controllers/vo"
"github.com/beego/beego/v2/client/orm"
)
type Account struct {
base.Model
model.Model
Username string `orm:"column(username)" json:"username"`
Password string `orm:"column(password)" json:"-"`
@@ -18,8 +19,8 @@ func init() {
orm.RegisterModelWithPrefix("t_", new(Account))
}
func ListAccount(param *base.PageParam, args ...interface{}) base.PageResult {
func ListAccount(param *model.PageParam, args ...interface{}) model.PageResult {
sql := "SELECT a.id, a.username, a.create_time, a.creator_id, a.creator, r.Id AS 'Role.Id', r.Name AS 'Role.Name'" +
" FROM t_account a LEFT JOIN t_role r ON a.id = r.account_id"
return base.GetPageBySql(sql, new([]vo.AccountVO), param, args)
return model.GetPageBySql(sql, new([]vo.AccountVO), param, args)
}

34
models/db.go Normal file
View File

@@ -0,0 +1,34 @@
package models
import (
"mayfly-go/base/model"
"github.com/beego/beego/v2/client/orm"
)
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"`
Passowrd string `orm:"column(password)" json:"-"`
Database string `orm:"column(database)" json:"database"`
}
func init() {
orm.RegisterModelWithPrefix("t_", new(Db))
}
func GetDbById(id uint64) *Db {
db := new(Db)
db.Id = id
err := model.GetBy(db)
if err != nil {
return nil
}
return db
}

26
models/db_sql.go Normal file
View File

@@ -0,0 +1,26 @@
package models
import (
"mayfly-go/base/model"
"github.com/beego/beego/v2/client/orm"
)
type DbSql struct {
model.Model `orm:"-"`
DbId uint64 `orm:"column(db_id)" json:"db_id"`
Type int `orm:"column(type)" json:"type"` // 类型
Sql string `orm:"column(sql)" json:"sql"`
}
func init() {
orm.RegisterModelWithPrefix("t_", new(DbSql))
}
func GetDbSqlByUser(userId uint64, dbId uint64, sqlType int) *Db {
db := new(Db)
query := model.QuerySetter(db).Filter("CreatorId", userId).Filter("DbId", dbId).Filter("Type", sqlType)
model.GetOne(query, db, db)
return db
}

View File

@@ -1,13 +1,14 @@
package models
import (
"github.com/astaxie/beego/orm"
"mayfly-go/base"
"mayfly-go/base/model"
"mayfly-go/controllers/vo"
"github.com/beego/beego/v2/client/orm"
)
type Machine struct {
base.Model
model.Model
Name string `orm:"column(name)"`
// IP地址
Ip string `orm:"column(ip)" json:"ip"`
@@ -25,7 +26,7 @@ func init() {
func GetMachineById(id uint64) *Machine {
machine := new(Machine)
machine.Id = id
err := base.GetBy(machine)
err := model.GetBy(machine)
if err != nil {
return nil
}
@@ -33,13 +34,13 @@ func GetMachineById(id uint64) *Machine {
}
// 分页获取机器信息列表
func GetMachineList(pageParam *base.PageParam) base.PageResult {
func GetMachineList(pageParam *model.PageParam) model.PageResult {
m := new([]Machine)
querySetter := base.QuerySetter(new(Machine)).OrderBy("-Id")
return base.GetPage(querySetter, pageParam, m, new([]vo.MachineVO))
querySetter := model.QuerySetter(new(Machine)).OrderBy("-Id")
return model.GetPage(querySetter, pageParam, m, new([]vo.MachineVO))
}
// 获取所有需要监控的机器信息列表
func GetNeedMonitorMachine() *[]orm.Params {
return base.GetListBySql("SELECT id FROM t_machine WHERE need_monitor = 1")
return model.GetListBySql("SELECT id FROM t_machine WHERE need_monitor = 1")
}

View File

@@ -1,8 +1,9 @@
package models
import (
"github.com/astaxie/beego/orm"
"time"
"github.com/beego/beego/v2/client/orm"
)
type MachineMonitor struct {

View File

@@ -1,12 +1,13 @@
package models
import (
"github.com/astaxie/beego/orm"
"mayfly-go/base"
"mayfly-go/base/model"
"github.com/beego/beego/v2/client/orm"
)
type Role struct {
base.Model
model.Model
Name string `orm:"column(name)" json:"username"`
//AccountId int64 `orm:"column(account_id)`

View File

@@ -0,0 +1,100 @@
package routers
import (
beego "github.com/beego/beego/v2/server/web"
"github.com/beego/beego/v2/server/web/context/param"
)
func init() {
beego.GlobalControllerRouter["mayfly-go/controllers:AccountController"] = append(beego.GlobalControllerRouter["mayfly-go/controllers:AccountController"],
beego.ControllerComments{
Method: "Accounts",
Router: "/accounts",
AllowHTTPMethods: []string{"get"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["mayfly-go/controllers:AccountController"] = append(beego.GlobalControllerRouter["mayfly-go/controllers:AccountController"],
beego.ControllerComments{
Method: "Login",
Router: "/accounts/login",
AllowHTTPMethods: []string{"post"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["mayfly-go/controllers:DbController"] = append(beego.GlobalControllerRouter["mayfly-go/controllers:DbController"],
beego.ControllerComments{
Method: "ColumnMA",
Router: "/api/db/:dbId/c-metadata",
AllowHTTPMethods: []string{"get"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["mayfly-go/controllers:DbController"] = append(beego.GlobalControllerRouter["mayfly-go/controllers:DbController"],
beego.ControllerComments{
Method: "ExecSql",
Router: "/api/db/:dbId/exec-sql",
AllowHTTPMethods: []string{"post"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["mayfly-go/controllers:DbController"] = append(beego.GlobalControllerRouter["mayfly-go/controllers:DbController"],
beego.ControllerComments{
Method: "HintTables",
Router: "/api/db/:dbId/hint-tables",
AllowHTTPMethods: []string{"get"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["mayfly-go/controllers:DbController"] = append(beego.GlobalControllerRouter["mayfly-go/controllers:DbController"],
beego.ControllerComments{
Method: "SelectData",
Router: "/api/db/:dbId/select",
AllowHTTPMethods: []string{"get"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["mayfly-go/controllers:DbController"] = append(beego.GlobalControllerRouter["mayfly-go/controllers:DbController"],
beego.ControllerComments{
Method: "SaveSql",
Router: "/api/db/:dbId/sql",
AllowHTTPMethods: []string{"post"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["mayfly-go/controllers:DbController"] = append(beego.GlobalControllerRouter["mayfly-go/controllers:DbController"],
beego.ControllerComments{
Method: "GetSql",
Router: "/api/db/:dbId/sql",
AllowHTTPMethods: []string{"get"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["mayfly-go/controllers:DbController"] = append(beego.GlobalControllerRouter["mayfly-go/controllers:DbController"],
beego.ControllerComments{
Method: "TableMA",
Router: "/api/db/:dbId/t-metadata",
AllowHTTPMethods: []string{"get"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["mayfly-go/controllers:DbController"] = append(beego.GlobalControllerRouter["mayfly-go/controllers:DbController"],
beego.ControllerComments{
Method: "Dbs",
Router: "/api/dbs",
AllowHTTPMethods: []string{"get"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
}

View File

@@ -1,23 +1,34 @@
package routers
import (
"github.com/astaxie/beego"
"mayfly-go/controllers"
"github.com/beego/beego/v2/server/web"
)
func init() {
//beego.Router("/account/login", &controllers.LoginController{})
//beego.Router("/account", &controllers.AccountController{})
//beego.Include(&controllers.AccountController{})
//beego.Include()
beego.Router("/api/accounts/login", &controllers.AccountController{}, "post:Login")
beego.Router("/api/accounts", &controllers.AccountController{}, "get:Accounts")
//web.Router("/account/login", &controllers.LoginController{})
//web.Router("/account", &controllers.AccountController{})
//web.Include(&controllers.AccountController{})
//web.Include()
web.Router("/api/accounts/login", &controllers.AccountController{}, "post:Login")
web.Router("/api/accounts", &controllers.AccountController{}, "get:Accounts")
machine := &controllers.MachineController{}
beego.Router("/api/machines", machine, "get:Machines")
beego.Router("/api/machines/?:machineId/run", machine, "get:Run")
beego.Router("/api/machines/?:machineId/top", machine, "get:Top")
beego.Router("/api/machines/?:machineId/sysinfo", machine, "get:SysInfo")
beego.Router("/api/machines/?:machineId/process", machine, "get:GetProcessByName")
//beego.Router("/machines/?:machineId/ws", machine, "get:WsSSH")
web.Router("/api/machines", machine, "get:Machines")
web.Router("/api/machines/?:machineId/run", machine, "get:Run")
web.Router("/api/machines/?:machineId/top", machine, "get:Top")
web.Router("/api/machines/?:machineId/sysinfo", machine, "get:SysInfo")
web.Router("/api/machines/?:machineId/process", machine, "get:GetProcessByName")
web.Router("/api/machines/?:machineId/terminal", machine, "get:WsSSH")
web.Include(&controllers.DbController{})
// db := &controllers.DbController{}
// web.Router("/api/dbs", db, "get:Dbs")
// web.Router("/api/db/?:dbId/select", db, "get:SelectData")
// web.Router("/api/db/?:dbId/t-metadata", db, "get:TableMA")
// web.Router("/api/db/?:dbId/c-metadata", db, "get:ColumnMA")
// web.Router("/api/db/?:dbId/hint-tables", db, "get:HintTables")
// web.Router("/api/db/?:dbId/sql", db, "post:SaveSql")
// web.Router("/api/db/?:dbId/sql", db, "get:GetSql")
}

View File

@@ -1,11 +1,12 @@
package scheduler
import (
"github.com/siddontang/go/log"
"mayfly-go/base"
"mayfly-go/base/model"
"mayfly-go/base/utils"
"mayfly-go/machine"
"mayfly-go/models"
"github.com/siddontang/go/log"
)
func init() {
@@ -17,11 +18,16 @@ func SaveMachineMonitor() {
for _, m := range *models.GetNeedMonitorMachine() {
m := m
go func() {
mm := machine.GetMonitorInfo(machine.GetCli(uint64(utils.GetInt4Map(m, "id"))))
if mm != nil {
err := base.Insert(mm)
cli, err := machine.GetCli(uint64(utils.GetInt4Map(m, "id")))
if err != nil {
log.Error("保存机器监控信息失败: %s", err.Error())
log.Error("获取客户端失败:", err.Error())
return
}
mm := cli.GetMonitorInfo()
if mm != nil {
_, err := model.Insert(mm)
if err != nil {
log.Error("保存机器监控信息失败: ", err.Error())
}
}
}()

View File

@@ -1,8 +1,9 @@
package scheduler
import (
"mayfly-go/base/model"
"github.com/robfig/cron/v3"
"mayfly-go/base"
)
var c = cron.New()
@@ -22,7 +23,7 @@ func GetCron() *cron.Cron {
func AddFun(spec string, cmd func()) cron.EntryID {
id, err := c.AddFunc(spec, cmd)
if err != nil {
panic(base.NewBizErr("添加任务失败:" + err.Error()))
panic(model.NewBizErr("添加任务失败:" + err.Error()))
}
return id
}

View File

@@ -1,37 +0,0 @@
package test
import (
_ "mayfly-go/routers"
"net/http"
"net/http/httptest"
"path/filepath"
"runtime"
"testing"
"github.com/astaxie/beego"
. "github.com/smartystreets/goconvey/convey"
)
func init() {
_, file, _, _ := runtime.Caller(0)
apppath, _ := filepath.Abs(filepath.Dir(filepath.Join(file, ".."+string(filepath.Separator))))
beego.TestBeegoInit(apppath)
}
// TestBeego is a sample to run an endpoint test
func TestBeego(t *testing.T) {
r, _ := http.NewRequest("GET", "/", nil)
w := httptest.NewRecorder()
beego.BeeApp.Handlers.ServeHTTP(w, r)
beego.Trace("testing", "TestBeego", "Code[%d]\n%s", w.Code, w.Body.String())
Convey("Subject: Test Station Endpoint\n", t, func() {
Convey("Status Code Should Be 200", func() {
So(w.Code, ShouldEqual, 200)
})
Convey("The Result Should Not Be Empty", func() {
So(w.Body.Len(), ShouldBeGreaterThan, 0)
})
})
}