feat: 目录及代码优化调整

This commit is contained in:
meilin.huang
2021-03-24 17:18:39 +08:00
parent 4c2e6b6155
commit 39e9f15def
116 changed files with 2964 additions and 310 deletions

2
.vscode/launch.json vendored
View File

@@ -9,7 +9,7 @@
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${fileDirname}/../main.go",
"program": "${fileDirname}/main.go",
"env": {},
"args": []
}

View File

@@ -1,7 +1,8 @@
package model
package biz
import (
"fmt"
"reflect"
)
@@ -23,6 +24,12 @@ func IsTrue(exp bool, msg string, params ...interface{}) {
}
}
func IsTrueBy(exp bool, err BizError) {
if !exp {
panic(err)
}
}
func NotEmpty(str string, msg string, params ...interface{}) {
if str == "" {
panic(NewBizErr(fmt.Sprintf(msg, params...)))

41
base/biz/bizerror.go Normal file
View File

@@ -0,0 +1,41 @@
package biz
// 业务错误
type BizError struct {
code int16
err string
}
const (
SuccessCode = 200
SuccessMsg = "success"
BizErrorCode = 400
BizErrorMsg = "error"
ServerErrorCode = 500
ServerErrorMsg = "server error"
TokenErrorCode = 501
TokenErrorMsg = "token error"
)
// 错误消息
func (e *BizError) Error() string {
return e.err
}
// 错误码
func (e *BizError) Code() int16 {
return e.code
}
// 创建业务逻辑错误结构体,默认为业务逻辑错误
func NewBizErr(msg string) *BizError {
return &BizError{code: BizErrorCode, err: msg}
}
// 创建业务逻辑错误结构体可设置指定错误code
func NewBizErrCode(code int16, msg string) *BizError {
return &BizError{code: code, err: msg}
}

View File

@@ -2,12 +2,11 @@ package base
import (
"encoding/json"
"fmt"
"mayfly-go/base/biz"
"mayfly-go/base/ctx"
"mayfly-go/base/mlog"
"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"
)
@@ -25,7 +24,7 @@ type operationFunc func(loginAccount *ctx.LoginAccount)
// 将请求体的json赋值给指定的结构体
func (c *Controller) UnmarshalBody(data interface{}) {
err := json.Unmarshal(c.Ctx.Input.RequestBody, data)
model.BizErrIsNil(err, "request body解析错误")
biz.BizErrIsNil(err, "request body解析错误")
}
// 校验表单数据
@@ -37,7 +36,7 @@ func (c *Controller) validForm(form interface{}) {
}
if !b {
e := valid.Errors[0]
panic(model.NewBizErr(e.Field + " " + e.Message))
panic(biz.NewBizErr(e.Field + " " + e.Message))
}
}
@@ -48,72 +47,61 @@ func (c *Controller) UnmarshalBodyAndValid(data interface{}) {
}
// 返回数据
// @param checkToken 是否校验token
// @param reqCtx 请求上下文
// @param getData 获取数据的回调函数
func (c *Controller) ReturnData(checkToken bool, getData getDataFunc) {
func (c *Controller) ReturnData(reqCtx *ctx.ReqCtx, getData getDataFunc) {
defer func() {
if err := recover(); err != nil {
ctx.ApplyAfterHandler(reqCtx, err.(error))
c.parseErr(err)
} else {
// 应用所有请求后置处理器
ctx.ApplyAfterHandler(reqCtx, nil)
}
}()
var loginAccount *ctx.LoginAccount
if checkToken {
loginAccount = c.CheckToken()
reqCtx.Req = c.Ctx.Request
// 调用请求前所有处理器
err := ctx.ApplyBeforeHandler(reqCtx)
if err != nil {
panic(err)
}
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)
resp := getData(reqCtx.LoginAccount)
c.Success(resp)
reqCtx.RespObj = resp
}
// 无返回数据的操作,如新增修改等无需返回数据的操作
// @param checkToken 是否校验token
func (c *Controller) Operation(checkToken bool, operation operationFunc) {
// @param reqCtx 请求上下文
func (c *Controller) Operation(reqCtx *ctx.ReqCtx, operation operationFunc) {
defer func() {
if err := recover(); err != nil {
ctx.ApplyAfterHandler(reqCtx, err.(error))
c.parseErr(err)
} else {
ctx.ApplyAfterHandler(reqCtx, nil)
}
}()
var loginAccount *ctx.LoginAccount
if checkToken {
loginAccount = c.CheckToken()
reqCtx.Req = c.Ctx.Request
// 调用请求前所有处理器
err := ctx.ApplyBeforeHandler(reqCtx)
if err != nil {
panic(err)
}
operation(loginAccount)
c.SuccessNoData()
}
// 校验token并返回登录者账号信息
func (c *Controller) CheckToken() *ctx.LoginAccount {
tokenStr := c.Ctx.Input.Header("Authorization")
loginAccount, err := token.ParseToken(tokenStr)
if err != nil || loginAccount == nil {
panic(model.NewBizErrCode(model.TokenErrorCode, model.TokenErrorMsg))
}
return loginAccount
operation(reqCtx.LoginAccount)
c.SuccessNoData()
// 不记录返回结果
reqCtx.RespObj = 0
}
// 获取分页参数
func (c *Controller) GetPageParam() *model.PageParam {
pn, err := c.GetInt("pageNum", 1)
model.BizErrIsNil(err, "pageNum参数错误")
biz.BizErrIsNil(err, "pageNum参数错误")
ps, serr := c.GetInt("pageSize", 10)
model.BizErrIsNil(serr, "pageSize参数错误")
biz.BizErrIsNil(serr, "pageSize参数错误")
return &model.PageParam{PageNum: pn, PageSize: ps}
}
@@ -134,7 +122,7 @@ func (c *Controller) SuccessNoData() {
}
// 返回业务错误
func (c *Controller) BizError(bizError model.BizError) {
func (c *Controller) BizError(bizError *biz.BizError) {
c.Result(model.Error(bizError.Code(), bizError.Error()))
}
@@ -146,20 +134,20 @@ func (c *Controller) ServerError() {
// 解析error并对不同error返回不同result
func (c *Controller) parseErr(err interface{}) {
switch t := err.(type) {
case model.BizError:
case *biz.BizError:
c.BizError(t)
break
case error:
c.ServerError()
logs.Error(t)
mlog.Log.Error(t)
panic(err)
//break
case string:
c.ServerError()
logs.Error(t)
mlog.Log.Error(t)
panic(err)
//break
default:
logs.Error(t)
mlog.Log.Error(t)
}
}

View File

@@ -1,5 +1,8 @@
package ctx
type AppContext struct {
}
type LoginAccount struct {
Id uint64
Username string

62
base/ctx/req_ctx.go Normal file
View File

@@ -0,0 +1,62 @@
package ctx
import (
"net/http"
)
type ReqCtx struct {
Req *http.Request
NeedToken bool // 是否需要token
LoginAccount *LoginAccount // 登录账号信息
// 日志相关信息
NeedLog bool // 是否需要记录日志
LogResp bool // 是否记录返回结果
Description string // 请求描述
ReqParam interface{} // 请求参数
RespObj interface{} // 响应结果
}
// 请求前置处理器
type BeforeHandler interface {
Handler(rc *ReqCtx) error
}
// 请求后置处理器
type AfterHandler interface {
Handler(rc *ReqCtx, err error)
}
var (
BeforeHandlers []BeforeHandler
AfterHandlers []AfterHandler
)
// 应用所有请求前置处理器
func ApplyBeforeHandler(rc *ReqCtx) error {
for _, e := range BeforeHandlers {
err := e.Handler(rc)
if err != nil {
return err
}
}
return nil
}
// 应用所有后置处理器
func ApplyAfterHandler(rc *ReqCtx, err error) {
for _, e := range AfterHandlers {
e.Handler(rc, err)
}
}
// 新建请求上下文,默认为需要记录日志
// @param needToken 是否需要token才可访问
// @param description 请求描述
func NewReqCtx(needToken bool, description string) *ReqCtx {
return &ReqCtx{NeedToken: needToken, Description: description, NeedLog: true}
}
func NewNoLogReqCtx(needToken bool) *ReqCtx {
return &ReqCtx{NeedToken: needToken, NeedLog: false}
}

View File

@@ -0,0 +1,86 @@
package ctx
import (
"encoding/json"
"fmt"
"mayfly-go/base/biz"
"mayfly-go/base/mlog"
"mayfly-go/base/utils"
"reflect"
log "github.com/sirupsen/logrus"
)
func init() {
// customFormatter := new(log.TextFormatter)
// customFormatter.TimestampFormat = "2006-01-02 15:04:05.000"
// customFormatter.FullTimestamp = true
log.SetFormatter(new(mlog.LogFormatter))
log.SetReportCaller(true)
AfterHandlers = append(AfterHandlers, new(LogHandler))
}
type LogHandler struct{}
func (l *LogHandler) Handler(rc *ReqCtx, err error) {
if !rc.NeedLog {
return
}
lfs := log.Fields{}
if la := rc.LoginAccount; la != nil {
lfs["uid"] = la.Id
lfs["uname"] = la.Username
}
if err != nil {
// lfs["errMsg"] = err.Error()
// switch t := err.(type) {
// case *biz.BizError:
// lfs["errCode"] = t.Code()
// break
// default:
// }
log.WithFields(lfs).Error(getErrMsg(rc, err))
return
}
// rb, _ := json.Marshal(rc.ReqParam)
// lfs["req"] = string(rb)
// // 返回结果不为空,则记录返回结果
// if rc.LogResp && !utils.IsBlank(reflect.ValueOf(rc.RespObj)) {
// respB, _ := json.Marshal(rc.RespObj)
// lfs["resp"] = string(respB)
// }
log.WithFields(lfs).Info(getLogMsg(rc))
}
func getLogMsg(rc *ReqCtx) string {
msg := rc.Description
rb, _ := json.Marshal(rc.ReqParam)
msg = msg + fmt.Sprintf("\n--> %s", string(rb))
// 返回结果不为空,则记录返回结果
if rc.LogResp && !utils.IsBlank(reflect.ValueOf(rc.RespObj)) {
respB, _ := json.Marshal(rc.RespObj)
msg = msg + fmt.Sprintf("\n<-- %s", string(respB))
}
return msg
}
func getErrMsg(rc *ReqCtx, err error) string {
msg := rc.Description
rb, _ := json.Marshal(rc.ReqParam)
msg = msg + fmt.Sprintf("\n--> %s", string(rb))
var errMsg string
switch t := err.(type) {
case *biz.BizError:
errMsg = fmt.Sprintf("\n<-e errCode: %d, errMsg: %s", t.Code(), t.Error())
break
default:
errMsg = fmt.Sprintf("\n<-e errMsg: %s", t.Error())
}
return (msg + errMsg)
}

View File

@@ -0,0 +1,29 @@
package ctx
import (
"mayfly-go/base/biz"
)
func init() {
BeforeHandlers = append(BeforeHandlers, new(PermissionHandler))
}
var permissionError = biz.NewBizErrCode(501, "token error")
type PermissionHandler struct{}
func (p *PermissionHandler) Handler(rc *ReqCtx) error {
if !rc.NeedToken {
return nil
}
tokenStr := rc.Req.Header.Get("Authorization")
if tokenStr == "" {
return permissionError
}
loginAccount, err := ParseToken(tokenStr)
if err != nil || loginAccount == nil {
return permissionError
}
rc.LoginAccount = loginAccount
return nil
}

View File

@@ -1,9 +1,9 @@
package token
package ctx
import (
"errors"
"mayfly-go/base/ctx"
"mayfly-go/base/model"
"mayfly-go/base/biz"
"time"
"github.com/dgrijalva/jwt-go"
@@ -26,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))
model.BizErrIsNil(err, "token创建失败")
biz.BizErrIsNil(err, "token创建失败")
return tokenString
}
// 解析token并返回登录者账号信息
func ParseToken(tokenStr string) (*ctx.LoginAccount, error) {
func ParseToken(tokenStr string) (*LoginAccount, error) {
if tokenStr == "" {
return nil, errors.New("token error")
}
@@ -43,5 +43,5 @@ func ParseToken(tokenStr string) (*ctx.LoginAccount, error) {
return nil, err
}
i := token.Claims.(jwt.MapClaims)
return &ctx.LoginAccount{Id: uint64(i["id"].(float64)), Username: i["username"].(string)}, nil
return &LoginAccount{Id: uint64(i["id"].(float64)), Username: i["username"].(string)}, nil
}

37
base/mlog/mlog.go Normal file
View File

@@ -0,0 +1,37 @@
package mlog
import (
"fmt"
"strings"
"time"
"github.com/sirupsen/logrus"
)
var Log = logrus.New()
func init() {
// customFormatter := new(logrus.TextFormatter)
// customFormatter.TimestampFormat = "2006-01-02 15:04:05.000"
// customFormatter.FullTimestamp = true
Log.SetFormatter(new(LogFormatter))
Log.SetReportCaller(true)
}
type LogFormatter struct{}
func (l *LogFormatter) Format(entry *logrus.Entry) ([]byte, error) {
timestamp := time.Now().Local().Format("2006-01-02 15:04:05.000")
level := entry.Level
logMsg := fmt.Sprintf("%s [%s]", timestamp, strings.ToUpper(level.String()))
// 如果存在调用信息且为error级别以上记录文件及行号
if caller := entry.Caller; caller != nil && level <= logrus.ErrorLevel {
logMsg = logMsg + fmt.Sprintf(" [%s:%d]", caller.File, caller.Line)
}
for k, v := range entry.Data {
logMsg = logMsg + fmt.Sprintf(" [%s=%v]", k, v)
}
logMsg = logMsg + fmt.Sprintf(" : %s\n", entry.Message)
return []byte(logMsg), nil
}

View File

@@ -1,27 +0,0 @@
package model
// 业务错误
type BizError struct {
code int16
err string
}
// 错误消息
func (e *BizError) Error() string {
return e.err
}
// 错误码
func (e *BizError) Code() int16 {
return e.code
}
// 创建业务逻辑错误结构体,默认为业务逻辑错误
func NewBizErr(msg string) BizError {
return BizError{code: BizErrorCode, err: msg}
}
// 创建业务逻辑错误结构体可设置指定错误code
func NewBizErrCode(code int16, msg string) BizError {
return BizError{code: code, err: msg}
}

View File

@@ -2,6 +2,7 @@ package model
import (
"errors"
"mayfly-go/base/biz"
"mayfly-go/base/ctx"
"mayfly-go/base/utils"
"reflect"
@@ -125,9 +126,9 @@ func GetPage(seter orm.QuerySeter, pageParam *PageParam, models interface{}, toM
return PageResult{Total: 0, List: nil}
}
_, qerr := seter.Limit(pageParam.PageSize, pageParam.PageNum-1).All(models, getFieldNames(toModels)...)
BizErrIsNil(qerr, "查询错误")
biz.BizErrIsNil(qerr, "查询错误")
err := utils.Copy(toModels, models)
BizErrIsNil(err, "实体转换错误")
biz.BizErrIsNil(err, "实体转换错误")
return PageResult{Total: count, List: toModels}
}
@@ -176,7 +177,7 @@ func GetListBySql(sql string, params ...interface{}) *[]orm.Params {
func GetList(seter orm.QuerySeter, model interface{}, toModel interface{}) {
_, _ = seter.All(model, getFieldNames(toModel)...)
err := utils.Copy(toModel, model)
BizErrIsNil(err, "实体转换错误")
biz.BizErrIsNil(err, "实体转换错误")
}
// 根据toModel结构体字段查询单条记录并将值赋值给toModel
@@ -186,7 +187,7 @@ func GetOne(seter orm.QuerySeter, model interface{}, toModel interface{}) error
return err
}
cerr := utils.Copy(toModel, model)
BizErrIsNil(cerr, "实体转换错误")
biz.BizErrIsNil(cerr, "实体转换错误")
return nil
}

View File

@@ -1,72 +0,0 @@
package model
import (
"fmt"
"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 {
Id int64
Username string
}
func init() {
orm.RegisterDriver("mysql", orm.DRMySQL)
orm.RegisterDataBase("default", "mysql", "root:111049@tcp(localhost:3306)/mayfly-go?charset=utf8")
orm.Debug = true
}
func TestGetList(t *testing.T) {
query := QuerySetter(new(models.Account)).OrderBy("-Id")
list := new([]AccountDetailVO)
GetList(query, new([]models.Account), list)
fmt.Println(list)
}
func TestGetOne(t *testing.T) {
model := new(models.Account)
query := QuerySetter(model).Filter("Id", 2)
adv := new(AccountDetailVO)
GetOne(query, model, adv)
fmt.Println(adv)
}
func TestMap(t *testing.T) {
//o := getOrm()
//
////v := new([]Account)
//var maps []orm.Params
//_, err := o.Raw("SELECT a.Id, a.Username, r.Id AS 'Role.Id', r.Name AS 'Role.Name' FROM " +
// "t_account a JOIN t_role r ON a.id = r.account_id").Values(&maps)
//fmt.Println(err)
//////res := new([]Account)
////model := &Account{}
////o.QueryTable("t_account").Filter("id", 1).RelatedSel().One(model)
////o.LoadRelated(model, "Role")
res := new([]vo.AccountVO)
sql := "SELECT a.Id, a.Username, r.Id AS 'Role.Id', r.Name AS 'Role.Name' FROM t_account a JOIN t_role r ON a.id = r.account_id"
//limitSql := sql + " LIMIT 1, 3"
//selectIndex := strings.Index(sql, "SELECT ") + 7
//fromIndex := strings.Index(sql, " FROM")
//selectCol := sql[selectIndex:fromIndex]
//countSql := strings.Replace(sql, selectCol, "COUNT(*)", 1)
//fmt.Println(limitSql)
//fmt.Println(selectCol)
//fmt.Println(countSql)
page := GetPageBySql(sql, res, &PageParam{PageNum: 1, PageSize: 1})
fmt.Println(page)
//return res
}
func TestCase2Camel(t *testing.T) {
fmt.Println(utils.Case2Camel("create_time"))
fmt.Println(strings.Title("username"))
}

64
base/rediscli/rediscli.go Normal file
View File

@@ -0,0 +1,64 @@
package rediscli
import (
"fmt"
"time"
"github.com/go-redis/redis"
)
var cli *redis.Client
func SetCli(client *redis.Client) {
cli = client
}
func GetCli() *redis.Client {
return cli
}
// get key value
func Get(key string) string {
val, err := cli.Get(key).Result()
switch {
case err == redis.Nil:
fmt.Println("key does not exist")
case err != nil:
fmt.Println("Get failed", err)
case val == "":
fmt.Println("value is empty")
}
return val
}
// set key value
func Set(key string, val string, expiration time.Duration) {
cli.Set(key, val, expiration)
}
func HSet(key string, field string, val interface{}) {
cli.HSet(key, field, val)
}
// hget
func HGet(key string, field string) string {
val, _ := cli.HGet(key, field).Result()
return val
}
// hget
func HExist(key string, field string) bool {
val, _ := cli.HExists(key, field).Result()
return val
}
// hgetall
func HGetAll(key string) map[string]string {
vals, _ := cli.HGetAll(key).Result()
return vals
}
// hdel
func HDel(key string, fields ...string) int {
return int(cli.HDel(key, fields...).Val())
}

22
base/utils/yml/yml.go Normal file
View File

@@ -0,0 +1,22 @@
package yml
import (
"errors"
"io/ioutil"
"gopkg.in/yaml.v3"
)
// 从指定路径加载yaml文件
func LoadYml(path string, out interface{}) error {
yamlFileBytes, readErr := ioutil.ReadFile(path)
if readErr != nil {
return readErr
}
// yaml解析
err := yaml.Unmarshal(yamlFileBytes, out)
if err != nil {
return errors.New("无法解析 [" + path + "]")
}
return nil
}

View File

@@ -3,7 +3,7 @@ httpport = 8888
copyrequestbody = true
autorender = false
EnableErrorsRender = false
runmode = "dev"
runmode = "prod"
; mysqluser = "root"
; mysqlpass = "111049"
; mysqlurls = "127.0.0.1"
@@ -16,6 +16,6 @@ AdminHttpPort = 8088
[dev]
httpport = 8888
[prod]
httpport = 8080
httpport = 8888
[test]
httpport = 8888

View File

@@ -2,11 +2,11 @@ package controllers
import (
"mayfly-go/base"
"mayfly-go/base/biz"
"mayfly-go/base/ctx"
"mayfly-go/base/model"
"mayfly-go/base/token"
"mayfly-go/controllers/form"
"mayfly-go/models"
"mayfly-go/devops/controllers/form"
"mayfly-go/devops/models"
)
type AccountController struct {
@@ -20,14 +20,14 @@ type AccountController struct {
// @router /accounts/login [post]
func (c *AccountController) Login() {
c.ReturnData(false, func(la *ctx.LoginAccount) interface{} {
c.ReturnData(ctx.NewReqCtx(false, "用户登录"), func(la *ctx.LoginAccount) interface{} {
loginForm := &form.LoginForm{}
c.UnmarshalBodyAndValid(loginForm)
a := &models.Account{Username: loginForm.Username, Password: loginForm.Password}
model.BizErrIsNil(model.GetBy(a, "Username", "Password"), "用户名或密码错误")
biz.BizErrIsNil(model.GetBy(a, "Username", "Password"), "用户名或密码错误")
return map[string]interface{}{
"token": token.CreateToken(a.Id, a.Username),
"token": ctx.CreateToken(a.Id, a.Username),
"username": a.Username,
}
})
@@ -35,7 +35,7 @@ func (c *AccountController) Login() {
// @router /accounts [get]
func (c *AccountController) Accounts() {
c.ReturnData(true, func(account *ctx.LoginAccount) interface{} {
c.ReturnData(ctx.NewReqCtx(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))

View File

@@ -3,12 +3,13 @@ package controllers
import (
"fmt"
"mayfly-go/base"
"mayfly-go/base/biz"
"mayfly-go/base/ctx"
"mayfly-go/base/model"
"mayfly-go/controllers/form"
"mayfly-go/controllers/vo"
"mayfly-go/db"
"mayfly-go/models"
"mayfly-go/devops/controllers/form"
"mayfly-go/devops/controllers/vo"
"mayfly-go/devops/db"
"mayfly-go/devops/models"
"strconv"
)
@@ -18,7 +19,7 @@ type DbController struct {
// @router /api/dbs [get]
func (c *DbController) Dbs() {
c.ReturnData(false, func(account *ctx.LoginAccount) interface{} {
c.ReturnData(ctx.NewNoLogReqCtx(true), 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))
@@ -27,12 +28,14 @@ func (c *DbController) Dbs() {
// @router /api/db/:dbId/select [get]
func (c *DbController) SelectData() {
c.ReturnData(false, func(account *ctx.LoginAccount) interface{} {
rc := ctx.NewReqCtx(true, "执行数据库查询语句")
c.ReturnData(rc, func(account *ctx.LoginAccount) interface{} {
selectSql := c.GetString("selectSql")
model.NotEmpty(selectSql, "selectSql不能为空")
rc.ReqParam = selectSql
biz.NotEmpty(selectSql, "selectSql不能为空")
res, err := db.GetDbInstance(c.GetDbId()).SelectData(selectSql)
if err != nil {
panic(model.NewBizErr(fmt.Sprintf("查询失败: %s", err.Error())))
panic(biz.NewBizErr(fmt.Sprintf("查询失败: %s", err.Error())))
}
return res
})
@@ -40,12 +43,12 @@ func (c *DbController) SelectData() {
// @router /api/db/:dbId/exec-sql [post]
func (c *DbController) ExecSql() {
c.ReturnData(false, func(account *ctx.LoginAccount) interface{} {
c.ReturnData(ctx.NewReqCtx(true, "sql执行"), func(account *ctx.LoginAccount) interface{} {
selectSql := c.GetString("sql")
model.NotEmpty(selectSql, "sql不能为空")
biz.NotEmpty(selectSql, "sql不能为空")
num, err := db.GetDbInstance(c.GetDbId()).Exec(selectSql)
if err != nil {
panic(model.NewBizErr(fmt.Sprintf("执行失败: %s", err.Error())))
panic(biz.NewBizErr(fmt.Sprintf("执行失败: %s", err.Error())))
}
return num
})
@@ -53,16 +56,16 @@ func (c *DbController) ExecSql() {
// @router /api/db/:dbId/t-metadata [get]
func (c *DbController) TableMA() {
c.ReturnData(false, func(account *ctx.LoginAccount) interface{} {
c.ReturnData(ctx.NewNoLogReqCtx(true), 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{} {
c.ReturnData(ctx.NewNoLogReqCtx(true), func(account *ctx.LoginAccount) interface{} {
tn := c.GetString("tableName")
model.NotEmpty(tn, "tableName不能为空")
biz.NotEmpty(tn, "tableName不能为空")
return db.GetDbInstance(c.GetDbId()).GetColumnMetadatas(tn)
})
}
@@ -70,7 +73,7 @@ func (c *DbController) ColumnMA() {
// @router /api/db/:dbId/hint-tables [get]
// 数据表及字段前端提示接口
func (c *DbController) HintTables() {
c.ReturnData(false, func(account *ctx.LoginAccount) interface{} {
c.ReturnData(ctx.NewNoLogReqCtx(true), func(account *ctx.LoginAccount) interface{} {
dbi := db.GetDbInstance(c.GetDbId())
tables := dbi.GetTableMetedatas()
res := make(map[string][]string)
@@ -94,14 +97,16 @@ func (c *DbController) HintTables() {
// @router /api/db/:dbId/sql [post]
func (c *DbController) SaveSql() {
c.Operation(true, func(account *ctx.LoginAccount) {
rc := ctx.NewReqCtx(true, "保存sql内容")
c.Operation(rc, func(account *ctx.LoginAccount) {
dbSqlForm := &form.DbSqlSaveForm{}
c.UnmarshalBodyAndValid(dbSqlForm)
rc.ReqParam = dbSqlForm
dbId := c.GetDbId()
// 判断dbId是否存在
err := model.GetById(new(models.Db), dbId)
model.BizErrIsNil(err, "该数据库信息不存在")
biz.BizErrIsNil(err, "该数据库信息不存在")
// 获取用于是否有该dbsql的保存记录有则更改否则新增
dbSql := &models.DbSql{Type: dbSqlForm.Type, DbId: dbId}
@@ -121,7 +126,7 @@ func (c *DbController) SaveSql() {
// @router /api/db/:dbId/sql [get]
func (c *DbController) GetSql() {
c.ReturnData(true, func(account *ctx.LoginAccount) interface{} {
c.ReturnData(ctx.NewNoLogReqCtx(true), func(account *ctx.LoginAccount) interface{} {
// 获取用于是否有该dbsql的保存记录有则更改否则新增
dbSql := &models.DbSql{Type: 1, DbId: c.GetDbId()}
dbSql.CreatorId = account.Id
@@ -135,6 +140,6 @@ func (c *DbController) GetSql() {
func (c *DbController) GetDbId() uint64 {
dbId, _ := strconv.Atoi(c.Ctx.Input.Param(":dbId"))
model.IsTrue(dbId > 0, "dbId错误")
biz.IsTrue(dbId > 0, "dbId错误")
return uint64(dbId)
}

View File

@@ -2,10 +2,10 @@ package controllers
import (
"mayfly-go/base"
"mayfly-go/base/biz"
"mayfly-go/base/ctx"
"mayfly-go/base/model"
"mayfly-go/machine"
"mayfly-go/models"
"mayfly-go/devops/machine"
"mayfly-go/devops/models"
"net/http"
"strconv"
@@ -25,44 +25,47 @@ var upgrader = websocket.Upgrader{
}
func (c *MachineController) Machines() {
c.ReturnData(true, func(account *ctx.LoginAccount) interface{} {
c.ReturnData(ctx.NewNoLogReqCtx(true), func(account *ctx.LoginAccount) interface{} {
return models.GetMachineList(c.GetPageParam())
})
}
func (c *MachineController) Run() {
c.ReturnData(true, func(account *ctx.LoginAccount) interface{} {
rc := ctx.NewReqCtx(true, "执行机器命令")
c.ReturnData(rc, func(account *ctx.LoginAccount) interface{} {
cmd := c.GetString("cmd")
model.NotEmpty(cmd, "cmd不能为空")
biz.NotEmpty(cmd, "cmd不能为空")
rc.ReqParam = cmd
res, err := c.getCli().Run(cmd)
model.BizErrIsNil(err, "执行命令失败")
biz.BizErrIsNil(err, "执行命令失败")
return res
})
}
// 系统基本信息
func (c *MachineController) SysInfo() {
c.ReturnData(true, func(account *ctx.LoginAccount) interface{} {
c.ReturnData(ctx.NewNoLogReqCtx(true), func(account *ctx.LoginAccount) interface{} {
res, err := c.getCli().GetSystemInfo()
model.BizErrIsNil(err, "获取系统基本信息失败")
biz.BizErrIsNil(err, "获取系统基本信息失败")
return res
})
}
// top命令信息
func (c *MachineController) Top() {
c.ReturnData(true, func(account *ctx.LoginAccount) interface{} {
c.ReturnData(ctx.NewNoLogReqCtx(true), func(account *ctx.LoginAccount) interface{} {
return c.getCli().GetTop()
})
}
func (c *MachineController) GetProcessByName() {
c.ReturnData(true, func(account *ctx.LoginAccount) interface{} {
c.ReturnData(ctx.NewNoLogReqCtx(true), func(account *ctx.LoginAccount) interface{} {
name := c.GetString("name")
model.NotEmpty(name, "name不能为空")
biz.NotEmpty(name, "name不能为空")
res, err := c.getCli().GetProcessByName(name)
model.BizErrIsNil(err, "获取失败")
biz.BizErrIsNil(err, "获取失败")
return res
})
}
@@ -70,7 +73,7 @@ func (c *MachineController) GetProcessByName() {
func (c *MachineController) WsSSH() {
wsConn, err := upgrader.Upgrade(c.Ctx.ResponseWriter, c.Ctx.Request, nil)
if err != nil {
panic(model.NewBizErr("获取requst responsewirte错误"))
panic(biz.NewBizErr("获取requst responsewirte错误"))
}
cols, _ := c.GetInt("cols", 80)
@@ -78,7 +81,7 @@ func (c *MachineController) WsSSH() {
sws, err := machine.NewLogicSshWsSession(cols, rows, c.getCli(), wsConn)
if sws == nil {
panic(model.NewBizErr("连接失败"))
panic(biz.NewBizErr("连接失败"))
}
//if wshandleError(wsConn, err) {
// return
@@ -94,12 +97,12 @@ func (c *MachineController) WsSSH() {
func (c *MachineController) GetMachineId() uint64 {
machineId, _ := strconv.Atoi(c.Ctx.Input.Param(":machineId"))
model.IsTrue(machineId > 0, "machineId错误")
biz.IsTrue(machineId > 0, "machineId错误")
return uint64(machineId)
}
func (c *MachineController) getCli() *machine.Cli {
cli, err := machine.GetCli(c.GetMachineId())
model.BizErrIsNil(err, "获取客户端错误")
biz.BizErrIsNil(err, "获取客户端错误")
return cli
}

View File

@@ -4,8 +4,8 @@ import (
"database/sql"
"errors"
"fmt"
"mayfly-go/base/model"
"mayfly-go/models"
"mayfly-go/base/biz"
"mayfly-go/devops/models"
"strings"
"sync"
"time"
@@ -104,12 +104,12 @@ func GetDbInstance(id uint64) *DbInstance {
}
}
d := models.GetDbById(uint64(id))
model.NotNil(d, "数据库信息不存在")
biz.NotNil(d, "数据库信息不存在")
DB, err := sql.Open(d.Type, getDsn(d))
model.ErrIsNil(err, fmt.Sprintf("Open %s failed, err:%v\n", d.Type, err))
biz.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())))
panic(biz.NewBizErr(fmt.Sprintf("数据库连接失败: %s", perr.Error())))
}
// 最大连接周期超过时间的连接就close

1
devops/lastupdate.tmp Executable file
View File

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

View File

@@ -4,8 +4,8 @@ import (
"errors"
"fmt"
"io"
"mayfly-go/base/model"
"mayfly-go/models"
"mayfly-go/base/biz"
"mayfly-go/devops/models"
"net"
"os"
"sync"
@@ -115,12 +115,12 @@ func (c *Cli) Close() {
func (c *Cli) GetSftpCli() *sftp.Client {
if c.client == nil {
if err := c.connect(); err != nil {
panic(model.NewBizErr("连接ssh失败" + err.Error()))
panic(biz.NewBizErr("连接ssh失败" + err.Error()))
}
}
client, serr := sftp.NewClient(c.client, sftp.MaxPacket(1<<15))
if serr != nil {
panic(model.NewBizErr("获取sftp client失败" + serr.Error()))
panic(biz.NewBizErr("获取sftp client失败" + serr.Error()))
}
return client
}

View File

@@ -2,9 +2,9 @@ package machine
import (
"io/ioutil"
"mayfly-go/base/model"
"mayfly-go/base/biz"
"mayfly-go/base/utils"
"mayfly-go/models"
"mayfly-go/devops/models"
"time"
"github.com/siddontang/go/log"
@@ -51,7 +51,7 @@ func getShellContent(name string) string {
return cacheShell
}
bytes, err := ioutil.ReadFile(BasePath + name + ".sh")
model.ErrIsNil(err, "获取shell文件失败")
biz.ErrIsNil(err, "获取shell文件失败")
shellStr := string(bytes)
shellCache[name] = shellStr
return shellStr

View File

@@ -1,7 +1,7 @@
package machine
import (
"mayfly-go/base/model"
"mayfly-go/base/biz"
"mayfly-go/base/utils"
"strconv"
"strings"
@@ -93,7 +93,7 @@ func (c *Cli) GetTop() *Top {
top := &Top{Time: time, Up: up, NowUsers: users, OneMinLoadavg: float32(oneMinLa), FiveMinLoadavg: float32(fiveMinLa), FifteenMinLoadavg: float32(fifMinLa)}
err := utils.Map2Struct(resMap, top)
model.BizErrIsNil(err, "解析top出错")
biz.BizErrIsNil(err, "解析top出错")
return top
}

View File

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

View File

@@ -1,8 +1,8 @@
package main
import (
_ "mayfly-go/routers"
scheduler "mayfly-go/scheudler"
_ "mayfly-go/devops/routers"
scheduler "mayfly-go/devops/scheudler"
"net/http"
"strings"
@@ -20,7 +20,7 @@ func init() {
}
func main() {
orm.Debug = true
// orm.Debug = true
// 跨域配置
web.InsertFilter("/**", web.BeforeRouter, cors.Allow(&cors.Options{
AllowAllOrigins: true,
@@ -39,5 +39,5 @@ func TransparentStatic(ctx *context.Context) {
if strings.Index(ctx.Request.URL.Path, "api/") >= 0 {
return
}
http.ServeFile(ctx.ResponseWriter, ctx.Request, "static/"+ctx.Request.URL.Path)
http.ServeFile(ctx.ResponseWriter, ctx.Request, "mock-server/static/"+ctx.Request.URL.Path)
}

View File

@@ -2,7 +2,7 @@ package models
import (
"mayfly-go/base/model"
"mayfly-go/controllers/vo"
"mayfly-go/devops/controllers/vo"
"github.com/beego/beego/v2/client/orm"
)

View File

@@ -2,7 +2,7 @@ package models
import (
"mayfly-go/base/model"
"mayfly-go/controllers/vo"
"mayfly-go/devops/controllers/vo"
"github.com/beego/beego/v2/client/orm"
)

View File

@@ -7,7 +7,7 @@ import (
func init() {
beego.GlobalControllerRouter["mayfly-go/controllers:AccountController"] = append(beego.GlobalControllerRouter["mayfly-go/controllers:AccountController"],
beego.GlobalControllerRouter["mayfly-go/devops/controllers:AccountController"] = append(beego.GlobalControllerRouter["mayfly-go/devops/controllers:AccountController"],
beego.ControllerComments{
Method: "Accounts",
Router: "/accounts",
@@ -16,7 +16,7 @@ func init() {
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["mayfly-go/controllers:AccountController"] = append(beego.GlobalControllerRouter["mayfly-go/controllers:AccountController"],
beego.GlobalControllerRouter["mayfly-go/devops/controllers:AccountController"] = append(beego.GlobalControllerRouter["mayfly-go/devops/controllers:AccountController"],
beego.ControllerComments{
Method: "Login",
Router: "/accounts/login",
@@ -25,7 +25,7 @@ func init() {
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["mayfly-go/controllers:DbController"] = append(beego.GlobalControllerRouter["mayfly-go/controllers:DbController"],
beego.GlobalControllerRouter["mayfly-go/devops/controllers:DbController"] = append(beego.GlobalControllerRouter["mayfly-go/devops/controllers:DbController"],
beego.ControllerComments{
Method: "ColumnMA",
Router: "/api/db/:dbId/c-metadata",
@@ -34,7 +34,7 @@ func init() {
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["mayfly-go/controllers:DbController"] = append(beego.GlobalControllerRouter["mayfly-go/controllers:DbController"],
beego.GlobalControllerRouter["mayfly-go/devops/controllers:DbController"] = append(beego.GlobalControllerRouter["mayfly-go/devops/controllers:DbController"],
beego.ControllerComments{
Method: "ExecSql",
Router: "/api/db/:dbId/exec-sql",
@@ -43,7 +43,7 @@ func init() {
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["mayfly-go/controllers:DbController"] = append(beego.GlobalControllerRouter["mayfly-go/controllers:DbController"],
beego.GlobalControllerRouter["mayfly-go/devops/controllers:DbController"] = append(beego.GlobalControllerRouter["mayfly-go/devops/controllers:DbController"],
beego.ControllerComments{
Method: "HintTables",
Router: "/api/db/:dbId/hint-tables",
@@ -52,7 +52,7 @@ func init() {
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["mayfly-go/controllers:DbController"] = append(beego.GlobalControllerRouter["mayfly-go/controllers:DbController"],
beego.GlobalControllerRouter["mayfly-go/devops/controllers:DbController"] = append(beego.GlobalControllerRouter["mayfly-go/devops/controllers:DbController"],
beego.ControllerComments{
Method: "SelectData",
Router: "/api/db/:dbId/select",
@@ -61,7 +61,7 @@ func init() {
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["mayfly-go/controllers:DbController"] = append(beego.GlobalControllerRouter["mayfly-go/controllers:DbController"],
beego.GlobalControllerRouter["mayfly-go/devops/controllers:DbController"] = append(beego.GlobalControllerRouter["mayfly-go/devops/controllers:DbController"],
beego.ControllerComments{
Method: "SaveSql",
Router: "/api/db/:dbId/sql",
@@ -70,7 +70,7 @@ func init() {
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["mayfly-go/controllers:DbController"] = append(beego.GlobalControllerRouter["mayfly-go/controllers:DbController"],
beego.GlobalControllerRouter["mayfly-go/devops/controllers:DbController"] = append(beego.GlobalControllerRouter["mayfly-go/devops/controllers:DbController"],
beego.ControllerComments{
Method: "GetSql",
Router: "/api/db/:dbId/sql",
@@ -79,7 +79,7 @@ func init() {
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["mayfly-go/controllers:DbController"] = append(beego.GlobalControllerRouter["mayfly-go/controllers:DbController"],
beego.GlobalControllerRouter["mayfly-go/devops/controllers:DbController"] = append(beego.GlobalControllerRouter["mayfly-go/devops/controllers:DbController"],
beego.ControllerComments{
Method: "TableMA",
Router: "/api/db/:dbId/t-metadata",
@@ -88,7 +88,7 @@ func init() {
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["mayfly-go/controllers:DbController"] = append(beego.GlobalControllerRouter["mayfly-go/controllers:DbController"],
beego.GlobalControllerRouter["mayfly-go/devops/controllers:DbController"] = append(beego.GlobalControllerRouter["mayfly-go/devops/controllers:DbController"],
beego.ControllerComments{
Method: "Dbs",
Router: "/api/dbs",

View File

@@ -1,7 +1,7 @@
package routers
import (
"mayfly-go/controllers"
"mayfly-go/devops/controllers"
"github.com/beego/beego/v2/server/web"
)

View File

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

View File

@@ -1,7 +1,7 @@
package scheduler
import (
"mayfly-go/base/model"
"mayfly-go/base/biz"
"github.com/robfig/cron/v3"
)
@@ -23,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(model.NewBizErr("添加任务失败:" + err.Error()))
panic(biz.NewBizErr("添加任务失败:" + err.Error()))
}
return id
}

View File

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

5
go.mod
View File

@@ -6,13 +6,16 @@ require (
github.com/beego/beego/v2 v2.0.1
// jwt
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/go-redis/redis v6.14.2+incompatible
github.com/go-sql-driver/mysql v1.5.0
github.com/gorilla/websocket v1.4.2
github.com/pkg/sftp v1.12.0
//
github.com/robfig/cron/v3 v3.0.1
github.com/siddontang/go v0.0.0-20170517070808-cb568a3e5cc0
github.com/smartystreets/goconvey v1.6.4
// ssh
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c
github.com/sirupsen/logrus v1.6.0
// github.com/go-redis/redis/v8 v8.6.0
)

35
go.sum
View File

@@ -7,8 +7,6 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF
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=
@@ -43,19 +41,19 @@ github.com/elazarl/go-bindata-assetfs v1.0.0 h1:G/bYguwHIzWq9ZoyUQqrjTmJbbYn3j3C
github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
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 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
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 h1:UE9pLhzmWf+xHNmZsoccjXosPicuiNaInPgym8nzfg0=
github.com/go-redis/redis v6.14.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
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=
@@ -82,22 +80,20 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
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 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
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/konsorten/go-windows-terminal-sequences v1.0.3/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=
@@ -114,8 +110,6 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0j
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=
@@ -125,12 +119,13 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWb
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 h1:Iw5WCbBcaAAd0fpRb1c9r5YCylv4XDoCSigm1zLevwU=
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 h1:K0jcRCwNQM3vFGh1ppMtDh/+7ApJrjldlX8fA0jDTLQ=
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=
@@ -163,20 +158,15 @@ github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzG
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/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/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
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=
@@ -204,8 +194,6 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
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=
@@ -252,8 +240,6 @@ golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1 h1:ogLJMz+qpzav7lGMh10LMvAkM
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=
@@ -265,7 +251,6 @@ golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGm
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-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=
@@ -302,8 +287,10 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8
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 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
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 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
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=

View File

@@ -2,4 +2,4 @@
ENV = 'production'
# base api
VUE_APP_BASE_API = 'http://localhost:8888/api'
VUE_APP_BASE_API = 'http://128.64.98.54:8888/api'

View File

@@ -13,6 +13,7 @@
"core-js": "^3.6.5",
"echarts": "^4.8.0",
"element-ui": "^2.13.2",
"jsonlint": "^1.6.3",
"sql-formatter": "^2.3.3",
"vue": "^2.6.11",
"vue-class-component": "^7.2.3",

View File

@@ -0,0 +1,72 @@
/**
* 工具类
*/
class Utils {
/**
* 属性拷贝,将一个对象的属性拷贝给另一个对象
* @param {Object} source 源对象
* @param {Object} target 目标对象
*/
static copyProperties(source: any, target: any) {
for (const k in target) {
const value = source[k];
if (value) {
target[k] = value;
}
}
}
/**
* 重置对象属性为null
* @param {Object} target 对象
*/
static resetProperties(target: any) {
for (const k in target) {
const value = target[k];
if (value != null) {
target[k] = null;
}
}
}
}
export default Utils
/**
* @description 绑定事件 on(element, event, handler)
*/
export const on = (function () {
if (document.addEventListener != null) {
return function (element: any, event: any, handler: any) {
if (element && event && handler) {
element.addEventListener(event, handler, false);
}
};
} else {
return function (element: any, event: any, handler: any) {
if (element && event && handler) {
element.attachEvent('on' + event, handler);
}
};
}
})();
/**
* @description 解绑事件 off(element, event, handler)
*/
export const off = (function () {
if (document.removeEventListener != null) {
return function (element: any, event: any, handler: any) {
if (element && event) {
element.removeEventListener(event, handler, false);
}
};
} else {
return function (element: any, event: any, handler: any) {
if (element && event) {
element.detachEvent('on' + event, handler);
}
};
}
})();

View File

@@ -4,7 +4,7 @@ import { ResultEnum } from './enums'
import Api from './Api';
import { AuthUtils } from './AuthUtils'
import config from './config';
import ElementUI from 'element-ui';
import { Message } from 'element-ui';
export interface Result {
/**
@@ -29,7 +29,7 @@ const baseUrl = config.baseApiUrl
*/
function notifyErrorMsg(msg: string) {
// 危险通知
ElementUI.Message.error(msg);
Message.error(msg);
}
// create an axios instance

View File

@@ -67,6 +67,7 @@
<script lang="ts">
import { Component, Vue, Prop, Watch } from 'vue-property-decorator'
import { Message } from 'element-ui'
@Component({
name: 'DynamicForm'
})
@@ -99,7 +100,7 @@ export default class DynamicForm extends Vue {
this.submitDisabled = true
operation.request(this.form).then(
(res: any) => {
this.$message.success('保存成功')
Message.success('保存成功')
this.$emit('submitSuccess', subform)
this.submitDisabled = false
// this.cancel()
@@ -109,7 +110,7 @@ export default class DynamicForm extends Vue {
}
)
} else {
this.$message.error('表单未设置对应的提交权限')
Message.error('表单未设置对应的提交权限')
}
} else {
return false

View File

@@ -2,7 +2,7 @@
<div class="main">
<div class="header">
<div class="logo">
<span class="big">Mayfly-Go</span>
<span class="big">Mock-Server.go</span>
</div>
<div class="right">
<span class="header-btn">
@@ -176,6 +176,22 @@ export default class App extends Vue {
},
],
},
{
id: 3,
type: 1,
name: 'Mock数据',
icon: 'el-icon-menu',
children: [
{
id: 31,
type: 1,
name: '数据列表',
url: '/mock-data',
icon: 'el-icon-menu',
code: 'mock-data',
},
],
},
{
id: 2,
type: 1,

View File

@@ -48,7 +48,16 @@ const routes: Array<RouteConfig> = [
// },
// component: () => import('@/views/db/SelectData.vue')
// }]
}]
},
{
path: 'mock-data',
name: 'mock-data',
meta: {
title: '数据列表',
keepAlive: false
},
component: () => import('@/views/mock-server')
},]
},
]
@@ -65,11 +74,13 @@ router.beforeEach((to: any, from: any, next: any) => {
next()
return
}
if (!AuthUtils.getToken() && toPath != '/login') {
next({ path: '/login' });
} else {
// if (!AuthUtils.getToken() && toPath != '/login') {
// next({ path: '/login' });
// } else {
// next();
// }
next();
}
});
export default router

View File

@@ -149,6 +149,7 @@ import 'codemirror/addon/hint/sql-hint.js'
import sqlFormatter from 'sql-formatter'
import { notEmpty } from '@/common/assert'
import { Message } from 'element-ui'
@Component({
@@ -239,7 +240,7 @@ export default class SelectData extends Vue {
notEmpty(this.sql, 'sql内容不能为空')
notEmpty(this.dbId, '请先选择数据库')
await dbApi.saveSql.request({ id: this.dbId, sql: this.sql, type: 1 })
this.$message.success('保存成功')
Message.success('保存成功')
}
// 更改数据库事件

View File

@@ -146,16 +146,17 @@
:visible.sync="terminalDialog.visible"
width="70%"
:close-on-click-modal="false"
:modal="false"
@close="closeTermnial"
>
<ssh-terminal ref="terminal" :socketURI="terminalDialog.socketUri" />
</el-dialog>
<!-- <FileManage
:title="dialog.title"
:visible.sync="dialog.visible"
:machineId.sync="dialog.machineId"
/>-->
<service-manage
:title="serviceDialog.title"
:visible.sync="serviceDialog.visible"
:machineId.sync="serviceDialog.machineId"
/>
<dynamic-form-dialog
:visible.sync="formDialog.visible"
@@ -173,6 +174,7 @@ import { DynamicFormDialog } from '@/components/dynamic-form'
import Monitor from './Monitor.vue'
import { machineApi } from './api'
import SshTerminal from './SshTerminal.vue'
import ServiceManage from './ServiceManage.vue';
@Component({
name: 'MachineList',
@@ -180,6 +182,7 @@ import SshTerminal from './SshTerminal.vue'
DynamicFormDialog,
Monitor,
SshTerminal,
ServiceManage
},
})
export default class MachineList extends Vue {
@@ -191,6 +194,11 @@ export default class MachineList extends Vue {
visible: false,
info: '',
}
serviceDialog = {
visible: false,
machineId: 0,
title: ''
}
monitorDialog = {
visible: false,
machineId: 0,
@@ -369,10 +377,10 @@ export default class MachineList extends Vue {
this.search()
}
fileManage(row: any) {
this.dialog.machineId = row.id
this.dialog.visible = true
this.dialog.title = `${row.name} => ${row.ip}`
serviceManager(row: any) {
this.serviceDialog.machineId = row.id
this.serviceDialog.visible = true
this.serviceDialog.title = `${row.name} => ${row.ip}`
}
submitSuccess() {

View File

@@ -108,8 +108,7 @@ export default class Monitor extends Vue {
icon: 'md-chatbubbles',
count: 12,
color: '#91AFC8',
},
{ title: '新增页面', icon: 'md-map', count: 14, color: '#91AFC8' },
}
]
taskData = [
{ value: 0, name: '运行中', color: '#3AA1FFB' },
@@ -280,7 +279,7 @@ export default class Monitor extends Vue {
startInterval() {
if (!this.timer) {
this.timer = setInterval(this.getTop, 3000)
// this.timer = setInterval(this.getTop, 3000)
}
}

View File

@@ -0,0 +1,345 @@
<template>
<div class="file-manage">
<el-dialog
:title="title"
:visible.sync="visible"
:show-close="true"
:before-close="handleClose"
width="800px"
>
<div style="float: right;">
<el-button
type="primary"
@click="add"
icon="el-icon-plus"
size="mini"
plain
>添加</el-button>
</div>
<el-table :data="fileTable" stripe style="width: 100%">
<el-table-column prop="name" label="名称" width>
<template slot-scope="scope">
<el-input
v-model="scope.row.name"
size="mini"
:disabled="scope.row.id != null"
clearable
></el-input>
</template>
</el-table-column>
<el-table-column prop="name" label="类型" width>
<template slot-scope="scope">
<el-select
:disabled="scope.row.id != null"
size="mini"
v-model="scope.row.type"
style="width: 100px"
placeholder="请选择"
>
<el-option
v-for="item in enums.FileTypeEnum"
:key="item.value"
:label="item.label"
:value="item.value"
></el-option>
</el-select>
</template>
</el-table-column>
<el-table-column prop="path" label="路径" width>
<template slot-scope="scope">
<el-input
v-model="scope.row.path"
:disabled="scope.row.id != null"
size="mini"
clearable
></el-input>
</template>
</el-table-column>
<el-table-column label="操作" width>
<template slot-scope="scope">
<el-button
v-if="scope.row.id == null"
@click="addFiles(scope.row)"
type="success"
:ref="scope.row"
icon="el-icon-success"
size="mini"
plain
>确定</el-button>
<el-button
v-if="scope.row.id != null"
@click="getConf(scope.row)"
type="primary"
:ref="scope.row"
icon="el-icon-tickets"
size="mini"
plain
>查看</el-button>
<el-button
type="danger"
:ref="scope.row"
@click="deleteRow(scope.$index, scope.row)"
icon="el-icon-delete"
size="mini"
plain
>删除</el-button>
</template>
</el-table-column>
</el-table>
</el-dialog>
<el-dialog
:title="fileContent.dialogTitle"
:visible.sync="fileContent.contentVisible"
width="850px"
>
<el-form :model="form">
<el-form-item>
<el-input
v-model="fileContent.content"
type="textarea"
:autosize="{ minRows: 18, maxRows:25}"
autocomplete="off"
></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="fileContent.contentVisible = false" size="mini"> </el-button>
<el-button
v-permission="permission.updateFileContent.code"
type="primary"
@click="updateContent"
size="mini"
> </el-button>
</div>
</el-dialog>
</div>
</template>
<script lang="ts">
import { Component, Vue, Prop, Watch } from 'vue-property-decorator'
import { machineApi } from './api'
@Component({
name: 'ServiceManage',
})
export default class ServiceManage extends Vue {
@Prop()
visible: boolean
@Prop()
machineId: [number]
@Prop()
title: string
addFile = machineApi.addConf
delFile = machineApi.delConf
updateFileContent = machineApi.updateFileContent
uploadFile = machineApi.uploadFile
files = machineApi.files
activeName = 'conf-file'
token = sessionStorage.getItem('token')
form = {
id: null,
type: null,
name: '',
remark: '',
}
fileTable: any[] = []
btnLoading = false
fileContent = {
fileId: 0,
content: '',
contentVisible: false,
dialogTitle: '',
path: '',
}
tree = {
title: '',
visible: false,
folder: { id: 0 },
node: {
childNodes: [],
},
resolve: {},
}
props = {
label: 'name',
children: 'zones',
isLeaf: 'leaf',
}
@Watch('machineId', { deep: true })
onDataChange() {
if (this.machineId) {
this.getFiles()
}
}
async getFiles() {
const res = await this.files.request({ id: this.machineId })
this.fileTable = res
}
/**
* tab切换触发事件
* @param {Object} tab
* @param {Object} event
*/
// handleClick(tab, event) {
// // if (tab.name == 'file-manage') {
// // this.fileManage.node.childNodes = [];
// // this.loadNode(this.fileManage.node, this.fileManage.resolve);
// // }
// }
add() {
// 往数组头部添加元素
this.fileTable = [{}].concat(this.fileTable)
}
async addFiles(row: any) {
row.machineId = this.machineId
await this.addFile.request(row)
this.$message.success('添加成功')
this.getFiles()
}
deleteRow(idx: any, row: any) {
if (row.id) {
this.$confirm(`此操作将删除 [${row.name}], 是否继续?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(() => {
// 删除配置文件
this.delFile
.request({
machineId: this.machineId,
id: row.id,
})
.then((res) => {
this.fileTable.splice(idx, 1)
})
})
} else {
this.fileTable.splice(idx, 1)
}
}
getConf(row: any) {
if (row.type == 1) {
this.tree.folder = row
this.tree.title = row.name
const treeNode = (this.tree.node.childNodes = [])
this.loadNode(this.tree.node, this.tree.resolve)
this.tree.visible = true
return
}
this.getFileContent(row.id, row.path)
}
async getFileContent(fileId: number, path: string) {
const res = await machineApi.fileContent.request({
fileId,
path,
})
this.fileContent.content = res
this.fileContent.fileId = fileId
this.fileContent.dialogTitle = path
this.fileContent.path = path
this.fileContent.contentVisible = true
}
async updateContent() {
await this.updateFileContent.request({
content: this.fileContent.content,
id: this.fileContent.fileId,
path: this.fileContent.path,
})
this.$message.success('修改成功')
this.fileContent.contentVisible = false
this.fileContent.content = ''
}
/**
* 关闭取消按钮触发的事件
*/
handleClose() {
this.$emit('update:visible', false)
this.$emit('update:machineId', null)
this.$emit('cancel')
this.activeName = 'conf-file'
this.fileTable = []
this.tree.folder = { id: 0 }
}
/**
* 加载文件树节点
* @param {Object} node
* @param {Object} resolve
*/
async loadNode(node: any, resolve: any) {
if (typeof resolve !== 'function') {
return
}
const folder: any = this.tree.folder
if (node.level === 0) {
this.tree.node = node
this.tree.resolve = resolve
// let folder: any = this.tree.folder
const path = folder ? folder.path : '/'
return resolve([
{
name: path,
type: 'd',
path: path,
},
])
}
let path
const data = node.data
// 只有在第一级节点时name==path即上述level==0时设置的
if (!data || data.name == data.path) {
path = folder.path
} else {
path = data.path
}
const res = await machineApi.lsFile.request({
fileId: folder.id,
path,
})
for (const file of res) {
const type = file.type
if (type != 'd') {
file.leaf = true
}
}
return resolve(res)
}
deleteFile(node: any, data: any) {
const file = data.path
this.$confirm(`此操作将删除 [${file}], 是否继续?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => {
machineApi.rmFile
.request({ fileId: this.tree.folder.id, path: file })
.then((res) => {
this.$message.success('删除成功')
const fileTree: any = this.$refs.fileTree
fileTree.remove(node)
})
})
.catch(() => {})
}
}
</script>
<style lang="less">
</style>

View File

@@ -14,6 +14,7 @@ export default {
type: String,
default: '',
},
cmd: String
},
watch: {
socketURI(val) {
@@ -38,11 +39,11 @@ export default {
// cursorStyle: 'underline', //光标样式
disableStdin: false,
theme: {
foreground: "#7e9192", //字体
background: "#002833", //背景色
cursor: "help", //设置光标
lineHeight: 16
}
foreground: '#7e9192', //字体
background: '#002833', //背景色
cursor: 'help', //设置光标
lineHeight: 16,
},
})
const fitAddon = new FitAddon()
term.loadAddon(fitAddon)
@@ -66,11 +67,7 @@ export default {
// * /
// 支持输入与粘贴方法
term.onData((key) => {
const cmd = {
type: 'cmd',
msg: key,
}
this.send(cmd)
this.sendCmd(key)
})
// 为解决窗体resize方法才会向后端发送列数和行数所以页面加载时也要触发此方法
this.send({
@@ -78,6 +75,10 @@ export default {
Cols: parseInt(term.cols),
Rows: parseInt(term.rows),
})
// 如果有初始要执行的命令,则发送执行命令
if (this.cmd) {
this.sendCmd(this.cmd + " \r")
}
},
initSocket() {
this.socket = new WebSocket(this.socketURI)
@@ -128,6 +129,13 @@ export default {
// console.log(msg)
this.socket.send(JSON.stringify(msg))
},
sendCmd(key) {
this.send({
type: 'cmd',
msg: key,
})
},
closeAll() {
this.close()
this.term.dispose()

View File

@@ -0,0 +1,236 @@
<template>
<div class="mock-data-dialog">
<el-dialog
:title="title"
:visible="visible"
:show-close="false"
width="800px"
>
<el-form :model="form" ref="mockDataForm" label-width="70px" size="small">
<el-form-item prop="method" label="方法名">
<el-input
:disabled="type == 1"
v-model.trim="form.method"
placeholder="请输入方法名"
></el-input>
</el-form-item>
<el-form-item prop="description" label="描述">
<el-input
v-model.trim="form.description"
placeholder="请输入方法描述"
></el-input>
</el-form-item>
<el-form-item prop="description" label="生效用户">
<el-select
v-model="form.effectiveUser"
multiple
filterable
allow-create
default-first-option
style="width:100%"
placeholder="请选择或创建生效用户,空为所有用户都生效"
>
</el-select>
</el-form-item>
<el-form-item prop="data" label="数据" id="jsonedit">
<codemirror
style="height: 400px"
ref="cmEditor"
v-model="form.data"
:options="cmOptions"
/>
</el-form-item>
</el-form>
<div style="text-align: center" class="dialog-footer">
<el-button
type="primary"
:loading="btnLoading"
@click="btnOk"
size="mini"
:disabled="submitDisabled"
> </el-button
>
<el-button @click="cancel()" :disabled="submitDisabled" size="mini"
> </el-button
>
</div>
</el-dialog>
</div>
</template>
<script lang="ts">
import { Component, Vue, Prop, Watch } from 'vue-property-decorator'
import { mockApi } from './api'
import { notEmpty } from '@/common/assert'
import Utils from '../../common/Utils'
import { codemirror } from 'vue-codemirror'
import 'codemirror/lib/codemirror.css'
import 'codemirror/theme/panda-syntax.css'
// import base style
require('codemirror/addon/selection/active-line')
import 'codemirror/mode/javascript/javascript.js'
import 'codemirror/addon/selection/active-line.js'
// 匹配括号
import 'codemirror/addon/edit/matchbrackets.js'
// json校验
require('script-loader!jsonlint')
import 'codemirror/addon/lint/lint'
import 'codemirror/addon/lint/lint.css'
import 'codemirror/addon/lint/json-lint'
@Component({
name: 'MockDataEdit',
components: {
codemirror,
},
})
export default class MockDataEdit extends Vue {
@Prop()
visible: boolean
@Prop()
data: [object, boolean]
@Prop()
title: string
@Prop()
type: number
cmOptions = {
tabSize: 2,
mode: 'application/json',
theme: 'panda-syntax',
// mode: {
// // 模式, 可查看 codemirror/mode 中的所有模式
// name: 'javascript',
// json: true,
// },
line: true,
// 开启校验
lint: true,
gutters: ['CodeMirror-lint-markers'],
indentWithTabs: true,
smartIndent: true,
matchBrackets: true,
autofocus: true,
styleSelectedText: true,
styleActiveLine: true, // 高亮选中行
foldGutter: true, // 块槽
hintOptions: {
// 当匹配只有一项的时候是否自动补全
completeSingle: false,
},
}
submitDisabled = false
form = {
method: '',
description: '',
data: '',
effectiveUser: null
}
btnLoading = false
// rules = {
// method: [
// {
// required: true,
// message: '请输入方法名',
// trigger: ['change', 'blur'],
// },
// ],
// description: [
// {
// required: true,
// message: '请输入方法描述',
// trigger: ['change', 'blur'],
// },
// ],
// }
@Watch('data', { deep: true })
onDataChange() {
if (this.data) {
Utils.copyProperties(this.data, this.form)
} else {
Utils.resetProperties(this.form)
this.form.data = ''
}
}
// mounted() {
// Utils.copyProperties(this.data, this.form)
// this.codemirror.setValue(
// JSON.stringify(JSON.parse(this.data['data']), null, 2)
// )
// }
get codemirror() {
return this.$refs.cmEditor['codemirror']
}
btnOk() {
const mockDataForm: any = this.$refs['mockDataForm']
mockDataForm.validate((valid: any) => {
if (valid) {
notEmpty(this.form.method, '方法名不能为空')
notEmpty(this.form.description, '描述不能为空')
notEmpty(this.form.data, '数据不能为空')
let res
if (this.type == 1) {
res = mockApi.update.request(this.form)
} else {
res = mockApi.create.request(this.form)
}
res.then(
(res: any) => {
this.$message.success('保存成功')
this.$emit('submitSuccess')
this.submitDisabled = false
this.cancel()
},
(e: any) => {
this.submitDisabled = false
}
)
} else {
return false
}
})
}
cancel() {
this.$emit('update:visible', false)
this.$emit('cancel')
// this.codemirror.setValue('')
// setTimeout(() => {
// this.resetForm()
// }, 200)
}
resetForm() {
const mockDataForm: any = this.$refs['mockDataForm']
if (mockDataForm) {
mockDataForm.clearValidate()
}
}
}
</script>
<style lang="less">
// .m-dialog {
// .el-cascader {
// width: 100%;
// }
// }
#jsonedit {
.CodeMirror {
overflow-y: scroll !important;
height: 400px !important;
}
}
</style>

View File

@@ -0,0 +1,244 @@
<template>
<div>
<div class="toolbar">
<div class="fl">
<el-button
@click="search"
type="primary"
icon="el-icon-refresh"
size="mini"
plain
>刷新</el-button
>
<el-button
@click="editData(null)"
type="primary"
icon="el-icon-plus"
size="mini"
plain
>添加</el-button
>
<el-button
type="primary"
icon="el-icon-edit"
size="mini"
:disabled="currentData == null"
@click="editData(currentData)"
plain
>查看&编辑</el-button
>
<el-button
:disabled="currentData == null"
type="danger"
icon="el-icon-delete"
size="mini"
@click="deleteData()"
>删除</el-button
>
</div>
<div style="float: right">
<el-input
placeholder="方法名过滤"
size="mini"
style="width: 140px"
@clear="search"
plain
v-model="queryParam"
clearable
></el-input>
<el-button
@click="filterData"
type="success"
icon="el-icon-search"
size="mini"
></el-button>
</div>
</div>
<el-table
:data="data"
@current-change="choose"
border
stripe
style="width: 100%"
>
<el-table-column label="选择" width="55px">
<template slot-scope="scope">
<el-radio v-model="currentMethod" :label="scope.row.method">
<i></i>
</el-radio>
</template>
</el-table-column>
<el-table-column
prop="method"
label="方法名"
:min-width="50"
></el-table-column>
<el-table-column
prop="description"
label="描述"
:min-width="50"
></el-table-column>
<el-table-column prop="description" label="状态" :width="75">
<template slot-scope="scope">
<el-tooltip
:content="scope.row.enable == 1 ? '启用' : '禁用'"
placement="top"
>
<el-switch
v-model="scope.row.enable"
:active-value="1"
active-color="#13ce66"
inactive-color="#ff4949"
@change="changeStatus(scope.row)"
></el-switch>
</el-tooltip>
</template>
</el-table-column>
<el-table-column
prop="effectiveUser"
label="生效用户"
:min-width="70"
show-overflow-tooltip
>
<template slot-scope="scope">
{{ showEffectiveUser(scope.row.effectiveUser) }}
</template>
</el-table-column>
<el-table-column
prop="data"
label="数据"
min-width="100"
show-overflow-tooltip
></el-table-column>
</el-table>
<mock-data-edit
:visible.sync="editDialog.visible"
:data.sync="editDialog.data"
:title="editDialog.title"
:type="editDialog.type"
@cancel="closeEditDialog"
@submitSuccess="submitSuccess"
/>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
import { mockApi } from './api'
import MockDataEdit from './MockDataEdit.vue'
@Component({
name: 'MockDataList',
components: {
MockDataEdit,
},
})
export default class MockDataList extends Vue {
data: Array<any> = []
currentData: any = null
currentMethod = null
queryParam = null
editDialog = {
data: {},
visible: false,
title: '',
type: 0,
}
choose(item: any) {
if (!item) {
return
}
this.currentData = item
this.currentMethod = item.method
}
editData(data: any) {
this.editDialog.data = data
if (data) {
this.editDialog.title = '修改mock数据'
this.editDialog.type = 1
} else {
this.editDialog.title = '新增mock数据'
this.editDialog.type = 0
// this.delChoose()
}
this.editDialog.visible = true
}
closeEditDialog() {
// this.currentData.opentime = Date.now()
// this.editDialog.data = this.currentData
}
delChoose() {
this.currentMethod = null
this.currentData = null
}
changeStatus(row: any) {
const enable = row.enable
row.enable = enable ? 1 : 0
mockApi.update
.request(row)
.then((res) => {
this.$message.success('操作成功')
})
.catch((e) => {
row.enable = enable
this.$message.success('操作失败')
})
}
showEffectiveUser(users: string[]) {
if (!users || users.length == 0) {
return '全部用户'
}
return users.join(', ')
}
deleteData() {
mockApi.delete.request({ method: this.currentMethod }).then((res) => {
this.$message.success('删除成功')
this.search()
})
}
submitSuccess() {
this.delChoose()
this.search()
}
mounted() {
this.search()
}
filterData() {
this.data = this.data.filter(
(item) => item.method.indexOf(this.queryParam) != -1
)
}
async search() {
if (this.data.length != 0) {
this.data = []
}
const res = await mockApi.list.request(null)
const values: string[] = Object.values(res)
for (const value of values) {
this.data.push(JSON.parse(value))
}
}
}
</script>
<style>
.el-dialog__body {
padding: 2px 2px;
}
</style>

View File

@@ -0,0 +1,9 @@
import Api from '@/common/Api';
export const mockApi = {
// 获取权限列表
list: Api.create("/mock-datas", 'get'),
create: Api.create("/mock-datas", 'post'),
update: Api.create("/mock-datas", 'put'),
delete: Api.create("/mock-datas/{method}", 'delete'),
}

View File

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

21
mock-server/conf/app.conf Normal file
View File

@@ -0,0 +1,21 @@
appname = mayfly-mock
httpport = 8888
copyrequestbody = true
autorender = false
EnableErrorsRender = false
runmode = "prod"
; mysqluser = "root"
; mysqlpass = "111049"
; mysqlurls = "127.0.0.1"
; mysqldb = "mayfly-job"
# EnableAdmin = false
# AdminHttpAddr = 0.0.0.0 #默认监听地址是localhost
# AdminHttpPort = 8088
[dev]
httpport = 8888
[prod]
httpport = 8888
[test]
httpport = 8888

3
mock-server/config.yml Normal file
View File

@@ -0,0 +1,3 @@
redis:
host: 127.0.0.1
port: 6379

View File

@@ -0,0 +1,23 @@
package form
type MockData struct {
Method string `valid:"Required" json:"method"`
Enable uint `json:"enable"`
Description string `valid:"Required" json:"description"`
Data string `valid:"Required" json:"data"`
EffectiveUser []string `json:"effectiveUser"`
}
type Machine struct {
Name string `json:"name"`
Ip string `json:"ip"` // IP地址
Username string `json:"username"` // 用户名
Password string `json:"-"`
Port int `json:"port"` // 端口号
}
type MachineService struct {
Name string `json:"name"`
Ip string `json:"ip"` // IP地址
Service string `json:"service"` // 服务命令
}

View File

@@ -0,0 +1,127 @@
package controllers
import (
"mayfly-go/base"
"mayfly-go/base/biz"
"mayfly-go/base/ctx"
"mayfly-go/base/rediscli"
"mayfly-go/base/utils"
"mayfly-go/mock-server/machine"
"mayfly-go/mock-server/models"
"net/http"
"github.com/gorilla/websocket"
)
type MachineController struct {
base.Controller
}
const machineKey = "ccbscf:machines"
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024 * 1024 * 10,
CheckOrigin: func(r *http.Request) bool {
return true
},
}
func (c *MachineController) Machines() {
c.ReturnData(ctx.NewNoLogReqCtx(true), func(account *ctx.LoginAccount) interface{} {
return rediscli.HGetAll(machineKey)
})
}
// 创建机器信息
func (c *MachineController) CreateMachine() {
c.Operation(ctx.NewNoLogReqCtx(true), func(account *ctx.LoginAccount) {
machine := &models.Machine{}
c.UnmarshalBodyAndValid(machine)
machine.CreateMachine()
})
}
// @router /api/mock-datas/:method [delete]
func (c *MockController) DeleteMachine() {
c.Operation(ctx.NewReqCtx(false, "删除mock数据"), func(account *ctx.LoginAccount) {
models.DeleteMachine(c.Ctx.Input.Param(":ip"))
})
}
func (c *MachineController) Run() {
rc := ctx.NewReqCtx(true, "执行机器命令")
c.ReturnData(rc, func(account *ctx.LoginAccount) interface{} {
cmd := c.GetString("cmd")
biz.NotEmpty(cmd, "cmd不能为空")
rc.ReqParam = cmd
res, err := c.getCli().Run(cmd)
biz.BizErrIsNil(err, "执行命令失败")
return res
})
}
// 系统基本信息
func (c *MachineController) SysInfo() {
c.ReturnData(ctx.NewNoLogReqCtx(true), func(account *ctx.LoginAccount) interface{} {
res, err := c.getCli().GetSystemInfo()
biz.BizErrIsNil(err, "获取系统基本信息失败")
return res
})
}
// top命令信息
func (c *MachineController) Top() {
c.ReturnData(ctx.NewNoLogReqCtx(true), func(account *ctx.LoginAccount) interface{} {
return c.getCli().GetTop()
})
}
func (c *MachineController) GetProcessByName() {
c.ReturnData(ctx.NewNoLogReqCtx(true), func(account *ctx.LoginAccount) interface{} {
name := c.GetString("name")
biz.NotEmpty(name, "name不能为空")
res, err := c.getCli().GetProcessByName(name)
biz.BizErrIsNil(err, "获取失败")
return res
})
}
func (c *MachineController) WsSSH() {
wsConn, err := upgrader.Upgrade(c.Ctx.ResponseWriter, c.Ctx.Request, nil)
if err != nil {
panic(biz.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(biz.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) GetMachineIp() string {
machineIp := c.Ctx.Input.Param(":ip")
biz.IsTrue(utils.StrLen(machineIp) > 0, "ip错误")
return machineIp
}
func (c *MachineController) getCli() *machine.Cli {
cli, err := machine.GetCli(c.GetMachineIp())
biz.BizErrIsNil(err, "获取客户端错误")
return cli
}

View File

@@ -0,0 +1,78 @@
package controllers
import (
"encoding/json"
"mayfly-go/base"
"mayfly-go/base/biz"
"mayfly-go/base/ctx"
"mayfly-go/base/rediscli"
"mayfly-go/base/utils"
"mayfly-go/mock-server/controllers/form"
)
const key = "ccbscf:mock:data"
type MockController struct {
base.Controller
}
// @router /api/mock-datas/:method [get]
func (c *MockController) GetMockData() {
c.ReturnData(ctx.NewNoLogReqCtx(false), func(account *ctx.LoginAccount) interface{} {
val := rediscli.HGet(key, c.Ctx.Input.Param(":method"))
mockData := &form.MockData{}
json.Unmarshal([]byte(val), mockData)
biz.IsTrue(mockData.Enable == 1, "无该mock数据")
eu := mockData.EffectiveUser
// 如果设置的生效用户为空,则表示所有用户都生效
if len(eu) == 0 {
return mockData.Data
}
// 该mock数据需要指定的生效用户才可访问
username := c.GetString("username")
biz.IsTrue(utils.StrLen(username) != 0, "该用户无法访问该mock数据")
for _, e := range eu {
if username == e {
return mockData.Data
}
}
panic(biz.NewBizErr("该用户无法访问该mock数据"))
})
}
// @router /api/mock-datas [put]
func (c *MockController) UpdateMockData() {
c.Operation(ctx.NewReqCtx(true, "修改mock数据"), func(account *ctx.LoginAccount) {
mockData := &form.MockData{}
c.UnmarshalBodyAndValid(mockData)
val, _ := json.Marshal(mockData)
rediscli.HSet(key, mockData.Method, val)
})
}
// @router /api/mock-datas [post]
func (c *MockController) CreateMockData() {
c.Operation(ctx.NewReqCtx(true, "保存mock数据"), func(account *ctx.LoginAccount) {
mockData := &form.MockData{}
c.UnmarshalBodyAndValid(mockData)
biz.IsTrue(!rediscli.HExist(key, mockData.Method), "该方法已存在")
val, _ := json.Marshal(mockData)
rediscli.HSet(key, mockData.Method, val)
})
}
// @router /api/mock-datas [get]
func (c *MockController) GetAllData() {
c.ReturnData(ctx.NewNoLogReqCtx(false), func(account *ctx.LoginAccount) interface{} {
return rediscli.HGetAll(key)
})
}
// @router /api/mock-datas/:method [delete]
func (c *MockController) DeleteMockData() {
c.Operation(ctx.NewReqCtx(false, "删除mock数据"), func(account *ctx.LoginAccount) {
rediscli.HDel(key, c.Ctx.Input.Param(":method"))
})
}

1
mock-server/lastupdate.tmp Executable file
View File

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

View File

@@ -0,0 +1,192 @@
package machine
import (
"errors"
"fmt"
"io"
"mayfly-go/base/biz"
"mayfly-go/base/utils"
"mayfly-go/mock-server/models"
"net"
"os"
"sync"
"time"
"github.com/pkg/sftp"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/terminal"
)
// 客户端信息
type Cli struct {
machine *models.Machine
// ssh客户端
client *ssh.Client
}
// 客户端缓存
var clientCache sync.Map
var mutex sync.Mutex
// 从缓存中获取客户端信息,不存在则查库,并新建
func GetCli(machineIp string) (*Cli, error) {
mutex.Lock()
defer mutex.Unlock()
load, ok := clientCache.Load(machineIp)
if ok {
return load.(*Cli), nil
}
cli, err := newClient(models.GetMachineByIp(machineIp))
if err != nil {
return nil, err
}
clientCache.LoadOrStore(machineIp, cli)
return cli, nil
}
//根据机器信息创建客户端对象
func newClient(machine *models.Machine) (*Cli, error) {
if machine == nil {
return nil, errors.New("机器不存在")
}
cli := new(Cli)
cli.machine = machine
err := cli.connect()
if err != nil {
return nil, err
}
return cli, nil
}
//连接
func (c *Cli) connect() error {
// 如果已经有client则直接返回
if c.client != nil {
return nil
}
m := c.machine
config := ssh.ClientConfig{
User: m.Username,
Auth: []ssh.AuthMethod{ssh.Password(m.Password)},
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
return nil
},
Timeout: 5 * time.Second,
}
addr := fmt.Sprintf("%s:%d", m.Ip, m.Port)
sshClient, err := ssh.Dial("tcp", addr, &config)
if err != nil {
return err
}
c.client = sshClient
return nil
}
// 测试连接
func TestConn(m *models.Machine) (*ssh.Client, error) {
config := ssh.ClientConfig{
User: m.Username,
Auth: []ssh.AuthMethod{ssh.Password(m.Password)},
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
return nil
},
Timeout: 5 * time.Second,
}
addr := fmt.Sprintf("%s:%d", m.Ip, m.Port)
sshClient, err := ssh.Dial("tcp", addr, &config)
if err != nil {
return nil, err
}
return sshClient, nil
}
// 关闭client和并从缓存中移除
func (c *Cli) Close() {
if c.client != nil {
c.client.Close()
}
if utils.StrLen(c.machine.Ip) > 0 {
clientCache.Delete(c.machine.Ip)
}
}
// 获取sftp client
func (c *Cli) GetSftpCli() *sftp.Client {
if c.client == nil {
if err := c.connect(); err != nil {
panic(biz.NewBizErr("连接ssh失败" + err.Error()))
}
}
client, serr := sftp.NewClient(c.client, sftp.MaxPacket(1<<15))
if serr != nil {
panic(biz.NewBizErr("获取sftp client失败" + serr.Error()))
}
return client
}
// 获取session
func (c *Cli) GetSession() (*ssh.Session, error) {
if c.client == nil {
if err := c.connect(); err != nil {
return nil, err
}
}
return c.client.NewSession()
}
//执行shell
//@param shell shell脚本命令
func (c *Cli) Run(shell string) (*string, error) {
session, err := c.GetSession()
if err != nil {
c.Close()
return nil, err
}
defer session.Close()
buf, rerr := session.CombinedOutput(shell)
if rerr != nil {
return nil, rerr
}
res := string(buf)
return &res, nil
}
//执行带交互的命令
func (c *Cli) RunTerminal(shell string, stdout, stderr io.Writer) error {
session, err := c.GetSession()
if err != nil {
return err
}
//defer session.Close()
fd := int(os.Stdin.Fd())
oldState, err := terminal.MakeRaw(fd)
if err != nil {
panic(err)
}
defer terminal.Restore(fd, oldState)
session.Stdout = stdout
session.Stderr = stderr
session.Stdin = os.Stdin
termWidth, termHeight, err := terminal.GetSize(fd)
if err != nil {
panic(err)
}
// Set up terminal modes
modes := ssh.TerminalModes{
ssh.ECHO: 1, // enable echoing
ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
}
// Request pseudo terminal
if err := session.RequestPty("xterm-256color", termHeight, termWidth, modes); err != nil {
return err
}
return session.Run(shell)
}

View File

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

View File

@@ -0,0 +1,34 @@
package machine
import (
"io/ioutil"
"mayfly-go/base/biz"
)
const BasePath = "./machine/shell/"
const MonitorTemp = "cpuRate:{cpuRate}%,memRate:{memRate}%,sysLoad:{sysLoad}\n"
// shell文件内容缓存避免每次读取文件
var shellCache = make(map[string]string)
func (c *Cli) GetProcessByName(name string) (*string, error) {
return c.Run(getShellContent("sys_info"))
}
func (c *Cli) GetSystemInfo() (*string, error) {
return c.Run(getShellContent("system_info"))
}
// 获取shell内容
func getShellContent(name string) string {
cacheShell := shellCache[name]
if cacheShell != "" {
return cacheShell
}
bytes, err := ioutil.ReadFile(BasePath + name + ".sh")
biz.ErrIsNil(err, "获取shell文件失败")
shellStr := string(bytes)
shellCache[name] = shellStr
return shellStr
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -199,7 +199,6 @@ 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: ", err)
setQuit(quitChan)
}
}

95
mock-server/main.go Normal file
View File

@@ -0,0 +1,95 @@
package main
import (
"flag"
"fmt"
"mayfly-go/base/rediscli"
"mayfly-go/base/utils/yml"
_ "mayfly-go/mock-server/routers"
"net/http"
"path/filepath"
"strings"
"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-redis/redis"
// _ "github.com/go-sql-driver/mysql"
)
// 启动配置参数
type StartConfigParam struct {
ConfigFilePath string // 配置文件路径
}
// yaml配置文件映射对象
type Config struct {
Server struct {
Port int `yaml:"port"`
}
Redis struct {
Host string `yaml:"host"`
Port int `yaml:"port"`
Password string `yaml:"password"`
Db int `yaml:"db"`
}
}
// 启动可执行文件时的参数
var startConfigParam *StartConfigParam
// 配置文件映射对象
var ymlConfig Config
// 获取执行可执行文件时,指定的启动参数
func getStartConfig() *StartConfigParam {
configFilePath := flag.String("e", "./config.yml", "配置文件路径,默认为可执行文件目录")
flag.Parse()
// 获取配置文件绝对路径
path, _ := filepath.Abs(*configFilePath)
sc := &StartConfigParam{ConfigFilePath: path}
return sc
}
func init() {
configFilePath := flag.String("e", "./config.yml", "配置文件路径,默认为可执行文件目录")
flag.Parse()
// 获取启动参数中,配置文件的绝对路径
path, _ := filepath.Abs(*configFilePath)
startConfigParam = &StartConfigParam{ConfigFilePath: path}
// 读取配置文件信息
yc := &Config{}
if err := yml.LoadYml(startConfigParam.ConfigFilePath, yc); err != nil {
panic(fmt.Sprintf("读取配置文件[%s]失败: %s", startConfigParam.ConfigFilePath, err.Error()))
}
ymlConfig = *yc
}
func main() {
// 设置redis客户端
rdb := redis.NewClient(&redis.Options{
Addr: fmt.Sprintf("%s:%d", ymlConfig.Redis.Host, ymlConfig.Redis.Port),
Password: ymlConfig.Redis.Password, // no password set
DB: ymlConfig.Redis.Db, // use default DB
})
rediscli.SetCli(rdb)
web.InsertFilter("/*", web.BeforeRouter, TransparentStatic)
// 跨域配置
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"},
ExposeHeaders: []string{"Content-Length", "Access-Control-Allow-Origin", "Access-Control-Allow-Headers", "Content-Type"},
AllowCredentials: true,
}))
web.Run()
}
// 解决beego无法访问根目录静态文件
func TransparentStatic(ctx *context.Context) {
if strings.Index(ctx.Request.URL.Path, "api/") >= 0 {
return
}
http.ServeFile(ctx.ResponseWriter, ctx.Request, "static/"+ctx.Request.URL.Path)
}

BIN
mock-server/mock-server Executable file

Binary file not shown.

View File

@@ -0,0 +1,37 @@
package models
import (
"encoding/json"
"mayfly-go/base/biz"
"mayfly-go/base/rediscli"
)
const machineKey = "ccbscf:machines"
type Machine struct {
Name string `json:"name"`
Ip string `json:"ip"` // IP地址
Username string `json:"username"` // 用户名
Password string `json:"-"`
Port int `json:"port"` // 端口号
}
func (c *Machine) CreateMachine() {
biz.IsTrue(!rediscli.HExist(machineKey, c.Ip), "该机器已存在")
val, _ := json.Marshal(c)
rediscli.HSet(machineKey, c.Ip, val)
}
func DeleteMachine(ip string) {
rediscli.HDel(machineKey, ip)
}
func GetMachineByIp(ip string) *Machine {
machine := &Machine{}
json.Unmarshal([]byte(rediscli.HGet(machineKey, ip)), machine)
return machine
}
func GetMachineList() map[string]string {
return rediscli.HGetAll(machineKey)
}

View File

@@ -0,0 +1,55 @@
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/mock-server/controllers:MockController"] = append(beego.GlobalControllerRouter["mayfly-go/mock-server/controllers:MockController"],
beego.ControllerComments{
Method: "UpdateMockData",
Router: "/api/mock-datas",
AllowHTTPMethods: []string{"put"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["mayfly-go/mock-server/controllers:MockController"] = append(beego.GlobalControllerRouter["mayfly-go/mock-server/controllers:MockController"],
beego.ControllerComments{
Method: "CreateMockData",
Router: "/api/mock-datas",
AllowHTTPMethods: []string{"post"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["mayfly-go/mock-server/controllers:MockController"] = append(beego.GlobalControllerRouter["mayfly-go/mock-server/controllers:MockController"],
beego.ControllerComments{
Method: "GetAllData",
Router: "/api/mock-datas",
AllowHTTPMethods: []string{"get"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["mayfly-go/mock-server/controllers:MockController"] = append(beego.GlobalControllerRouter["mayfly-go/mock-server/controllers:MockController"],
beego.ControllerComments{
Method: "GetMockData",
Router: "/api/mock-datas/:method",
AllowHTTPMethods: []string{"get"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["mayfly-go/mock-server/controllers:MockController"] = append(beego.GlobalControllerRouter["mayfly-go/mock-server/controllers:MockController"],
beego.ControllerComments{
Method: "DeleteMockData",
Router: "/api/mock-datas/:method",
AllowHTTPMethods: []string{"delete"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
}

View File

@@ -0,0 +1,17 @@
package routers
import (
"mayfly-go/mock-server/controllers"
"github.com/beego/beego/v2/server/web"
)
func init() {
web.Include(&controllers.MockController{})
mock := &controllers.MockController{}
web.Router("/api/mock-datas", mock, "post:CreateMockData")
web.Router("/api/mock-datas/?:method", mock, "get:GetMockData")
web.Router("/api/mock-datas", mock, "put:UpdateMockData")
web.Router("/api/mock-datas/?:method", mock, "delete:DeleteMockData")
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -0,0 +1 @@
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/favicon.ico"><title>mayfly-go-front</title><link href="/static/css/chunk-09c3f704.f03c28a3.css" rel="prefetch"><link href="/static/css/chunk-589cf4a2.676f6792.css" rel="prefetch"><link href="/static/css/chunk-76193938.2d81c5bb.css" rel="prefetch"><link href="/static/css/chunk-7f8e443f.53f73f21.css" rel="prefetch"><link href="/static/css/chunk-e3082940.1463bb24.css" rel="prefetch"><link href="/static/js/chunk-09c3f704.a1d504c3.js" rel="prefetch"><link href="/static/js/chunk-589cf4a2.b6d10755.js" rel="prefetch"><link href="/static/js/chunk-6e9f0a70.cf14b007.js" rel="prefetch"><link href="/static/js/chunk-76193938.f9c11d74.js" rel="prefetch"><link href="/static/js/chunk-7f8e443f.2c011f90.js" rel="prefetch"><link href="/static/js/chunk-e3082940.ee363d97.js" rel="prefetch"><link href="/static/css/app.b4088619.css" rel="preload" as="style"><link href="/static/css/chunk-vendors.16da611a.css" rel="preload" as="style"><link href="/static/js/app.3ffb27d0.js" rel="preload" as="script"><link href="/static/js/chunk-vendors.bf6005f5.js" rel="preload" as="script"><link href="/static/css/chunk-vendors.16da611a.css" rel="stylesheet"><link href="/static/css/app.b4088619.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but mayfly-go-front doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="/static/js/chunk-vendors.bf6005f5.js"></script><script src="/static/js/app.3ffb27d0.js"></script></body></html>

View File

@@ -0,0 +1 @@
#app{background-color:#222d32}.main{display:flex}.main .el-menu:not(.el-menu--collapse){width:230px}.main .app{width:100%;background-color:#ecf0f5}.main .aside{position:fixed;margin-top:50px;z-index:10;background-color:#222d32;transition:all .3s ease-in-out}.main .aside .menu{overflow-y:auto;height:100vh}.main .app-body{margin-left:230px;transition:margin-left .3s ease-in-out}.main .main-container{margin-top:88px;padding:2px;min-height:calc(100vh - 88px)}.header{width:100%;position:fixed;display:flex;z-index:10}.header,.header .logo{height:50px;background-color:#303643}.header .logo{width:230px;text-align:center;line-height:50px;color:#fff;transition:all .3s ease-in-out}.header .logo .min{display:none}.header .right{position:absolute;right:0}.header .header-btn{overflow:hidden;height:50px;display:inline-block;text-align:center;line-height:50px;cursor:pointer;padding:0 14px;color:#fff}.header .header-btn .el-badge__content{top:14px;right:7px;text-align:center;font-size:9px;padding:0 3px;background-color:#00a65a;color:#fff;border:none;white-space:nowrap;vertical-align:baseline;border-radius:.25em}.header .header-btn:hover{background-color:#222d32}.menu{border-right:none;-moz-user-select:-moz-none;-moz-user-select:none;-o-user-select:none;-webkit-user-select:none;-ms-user-select:none;user-select:none}.el-menu--vertical{min-width:190px}.setting-category{padding:10px 0;border-bottom:1px solid #eee}#mainContainer iframe{border:none;outline:none;width:100%;height:100%;position:absolute;background-color:#ecf0f5}.el-menu-item,.el-submenu__title{font-weight:500}#nav-bar{margin-top:50px;height:38px;width:100%;z-index:8;background:#fff;box-shadow:0 1px 3px 0 rgba(0,0,0,.12),0 0 3px 0 rgba(0,0,0,.04);position:fixed;top:0}*{padding:0;margin:0;outline:none;box-sizing:border-box}body{font-family:Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei,,Arial,sans-serif}a{color:#3c8dbc;text-decoration:none}::-webkit-scrollbar{width:4px;height:8px;background-color:#f5f5f5}::-webkit-scrollbar-thumb,::-webkit-scrollbar-track{-webkit-box-shadow:inset 0 0 6px rgba(0,0,0,.3);background-color:#f5f5f5}.el-menu .fa{vertical-align:middle;margin-right:5px;width:24px;text-align:center}.el-menu .fa:not(.is-children){font-size:14px}.gray-mode{filter:grayscale(100%)}.fade-enter-active,.fade-leave-active{transition:opacity .2s ease-in-out}.fade-enter,.fade-leave-to{opacity:0}.none-select{moz-user-select:-moz-none;-moz-user-select:none;-o-user-select:none;-webkit-user-select:none;-ms-user-select:none;user-select:none}.toolbar{width:100%;padding:8px;background-color:#fff;overflow:hidden;line-height:32px;border:1px solid #e6ebf5}.fl{float:left}

View File

@@ -0,0 +1 @@
.cm-s-panda-syntax{background:#292a2b;color:#e6e6e6;line-height:1.5;font-family:Operator Mono,Source Code Pro,Menlo,Monaco,Consolas,Courier New,monospace}.cm-s-panda-syntax .CodeMirror-cursor{border-color:#ff2c6d}.cm-s-panda-syntax .CodeMirror-activeline-background{background:rgba(99,123,156,.1)}.cm-s-panda-syntax .CodeMirror-selected{background:#fff}.cm-s-panda-syntax .cm-comment{font-style:italic;color:#676b79}.cm-s-panda-syntax .cm-operator{color:#f3f3f3}.cm-s-panda-syntax .cm-string{color:#19f9d8}.cm-s-panda-syntax .cm-string-2{color:#ffb86c}.cm-s-panda-syntax .cm-tag{color:#ff2c6d}.cm-s-panda-syntax .cm-meta{color:#b084eb}.cm-s-panda-syntax .cm-number{color:#ffb86c}.cm-s-panda-syntax .cm-atom{color:#ff2c6d}.cm-s-panda-syntax .cm-keyword{color:#ff75b5}.cm-s-panda-syntax .cm-variable{color:#ffb86c}.cm-s-panda-syntax .cm-type,.cm-s-panda-syntax .cm-variable-2,.cm-s-panda-syntax .cm-variable-3{color:#ff9ac1}.cm-s-panda-syntax .cm-def{color:#e6e6e6}.cm-s-panda-syntax .cm-property{color:#f3f3f3}.cm-s-panda-syntax .cm-attribute,.cm-s-panda-syntax .cm-unit{color:#ffb86c}.cm-s-panda-syntax .CodeMirror-matchingbracket{border-bottom:1px dotted #19f9d8;padding-bottom:2px;color:#e6e6e6}.cm-s-panda-syntax .CodeMirror-gutters{background:#292a2b;border-right-color:hsla(0,0%,100%,.1)}.cm-s-panda-syntax .CodeMirror-linenumber{color:#e6e6e6;opacity:.6}.CodeMirror-lint-markers{width:16px}.CodeMirror-lint-tooltip{background-color:#ffd;border:1px solid #000;border-radius:4px 4px 4px 4px;color:#000;font-family:monospace;font-size:10pt;overflow:hidden;padding:2px 5px;position:fixed;white-space:pre;white-space:pre-wrap;z-index:100;max-width:600px;opacity:0;transition:opacity .4s;-moz-transition:opacity .4s;-webkit-transition:opacity .4s;-o-transition:opacity .4s;-ms-transition:opacity .4s}.CodeMirror-lint-mark{background-position:0 100%;background-repeat:repeat-x}.CodeMirror-lint-mark-warning{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJFhQXEbhTg7YAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAMklEQVQI12NkgIIvJ3QXMjAwdDN+OaEbysDA4MPAwNDNwMCwiOHLCd1zX07o6kBVGQEAKBANtobskNMAAAAASUVORK5CYII=")}.CodeMirror-lint-mark-error{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJDw4cOCW1/KIAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAHElEQVQI12NggIL/DAz/GdA5/xkY/qPKMDAwAADLZwf5rvm+LQAAAABJRU5ErkJggg==")}.CodeMirror-lint-marker{background-position:50%;background-repeat:no-repeat;cursor:pointer;display:inline-block;height:16px;width:16px;vertical-align:middle;position:relative}.CodeMirror-lint-message{padding-left:18px;background-position:0 0;background-repeat:no-repeat}.CodeMirror-lint-marker-warning,.CodeMirror-lint-message-warning{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAANlBMVEX/uwDvrwD/uwD/uwD/uwD/uwD/uwD/uwD/uwD6twD/uwAAAADurwD2tQD7uAD+ugAAAAD/uwDhmeTRAAAADHRSTlMJ8mN1EYcbmiixgACm7WbuAAAAVklEQVR42n3PUQqAIBBFUU1LLc3u/jdbOJoW1P08DA9Gba8+YWJ6gNJoNYIBzAA2chBth5kLmG9YUoG0NHAUwFXwO9LuBQL1giCQb8gC9Oro2vp5rncCIY8L8uEx5ZkAAAAASUVORK5CYII=")}.CodeMirror-lint-marker-error,.CodeMirror-lint-message-error{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAHlBMVEW7AAC7AACxAAC7AAC7AAAAAAC4AAC5AAD///+7AAAUdclpAAAABnRSTlMXnORSiwCK0ZKSAAAATUlEQVR42mWPOQ7AQAgDuQLx/z8csYRmPRIFIwRGnosRrpamvkKi0FTIiMASR3hhKW+hAN6/tIWhu9PDWiTGNEkTtIOucA5Oyr9ckPgAWm0GPBog6v4AAAAASUVORK5CYII=")}.CodeMirror-lint-marker-multiple{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAMAAADzjKfhAAAACVBMVEUAAAAAAAC/v7914kyHAAAAAXRSTlMAQObYZgAAACNJREFUeNo1ioEJAAAIwmz/H90iFFSGJgFMe3gaLZ0od+9/AQZ0ADosbYraAAAAAElFTkSuQmCC");background-repeat:no-repeat;background-position:100% 100%;width:100%;height:100%}#jsonedit .CodeMirror{overflow-y:scroll!important;height:400px!important}.el-dialog__body{padding:2px 2px}

Some files were not shown because too many files have changed in this diff Show More