mirror of
				https://gitee.com/dromara/mayfly-go
				synced 2025-11-04 08:20:25 +08:00 
			
		
		
		
	first
This commit is contained in:
		
							
								
								
									
										41
									
								
								base/assert.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								base/assert.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,41 @@
 | 
			
		||||
package base
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"reflect"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func BizErrIsNil(err error, msg string) {
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic(NewBizErr(msg))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ErrIsNil(err error, msg string) {
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic(err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func IsTrue(exp bool, msg string) {
 | 
			
		||||
	if !exp {
 | 
			
		||||
		panic(NewBizErr(msg))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NotEmpty(str string, msg string) {
 | 
			
		||||
	if str == "" {
 | 
			
		||||
		panic(NewBizErr(msg))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NotNil(data interface{}, msg string) {
 | 
			
		||||
	if reflect.ValueOf(data).IsNil() {
 | 
			
		||||
		panic(NewBizErr(msg))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Nil(data interface{}, msg string) {
 | 
			
		||||
	if !reflect.ValueOf(data).IsNil() {
 | 
			
		||||
		panic(NewBizErr(msg))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										27
									
								
								base/bizerror.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								base/bizerror.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
			
		||||
package base
 | 
			
		||||
 | 
			
		||||
// 业务错误
 | 
			
		||||
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}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										141
									
								
								base/controller.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										141
									
								
								base/controller.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,141 @@
 | 
			
		||||
package base
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"github.com/astaxie/beego"
 | 
			
		||||
	"github.com/astaxie/beego/logs"
 | 
			
		||||
	"github.com/astaxie/beego/validation"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Controller struct {
 | 
			
		||||
	beego.Controller
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 获取数据函数
 | 
			
		||||
type getDataFunc func(loginAccount *LoginAccount) interface{}
 | 
			
		||||
 | 
			
		||||
// 操作函数,无返回数据
 | 
			
		||||
type operationFunc func(loginAccount *LoginAccount)
 | 
			
		||||
 | 
			
		||||
// 将请求体的json赋值给指定的结构体
 | 
			
		||||
func (c *Controller) UnmarshalBody(data interface{}) {
 | 
			
		||||
	err := json.Unmarshal(c.Ctx.Input.RequestBody, data)
 | 
			
		||||
	BizErrIsNil(err, "request body解析错误")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 校验表单数据
 | 
			
		||||
func (c *Controller) validForm(form interface{}) {
 | 
			
		||||
	valid := validation.Validation{}
 | 
			
		||||
	b, err := valid.Valid(form)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic(err)
 | 
			
		||||
	}
 | 
			
		||||
	if !b {
 | 
			
		||||
		e := valid.Errors[0]
 | 
			
		||||
		panic(NewBizErr(e.Field + " " + e.Message))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 将请求体的json赋值给指定的结构体,并校验表单数据
 | 
			
		||||
func (c *Controller) UnmarshalBodyAndValid(data interface{}) {
 | 
			
		||||
	c.UnmarshalBody(data)
 | 
			
		||||
	c.validForm(data)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 返回数据
 | 
			
		||||
// @param checkToken  是否校验token
 | 
			
		||||
// @param getData  获取数据的回调函数
 | 
			
		||||
func (c *Controller) ReturnData(checkToken bool, getData getDataFunc) {
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if err := recover(); err != nil {
 | 
			
		||||
			c.parseErr(err)
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
	var loginAccount *LoginAccount
 | 
			
		||||
	if checkToken {
 | 
			
		||||
		loginAccount = c.CheckToken()
 | 
			
		||||
	}
 | 
			
		||||
	c.Success(getData(loginAccount))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 无返回数据的操作,如新增修改等无需返回数据的操作
 | 
			
		||||
// @param checkToken  是否校验token
 | 
			
		||||
func (c *Controller) Operation(checkToken bool, operation operationFunc) {
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if err := recover(); err != nil {
 | 
			
		||||
			c.parseErr(err)
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
	var loginAccount *LoginAccount
 | 
			
		||||
	if checkToken {
 | 
			
		||||
		loginAccount = c.CheckToken()
 | 
			
		||||
	}
 | 
			
		||||
	operation(loginAccount)
 | 
			
		||||
	c.SuccessNoData()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 校验token,并返回登录者账号信息
 | 
			
		||||
func (c *Controller) CheckToken() *LoginAccount {
 | 
			
		||||
	tokenStr := c.Ctx.Input.Header("Authorization")
 | 
			
		||||
	loginAccount, err := ParseToken(tokenStr)
 | 
			
		||||
	if err != nil || loginAccount == nil {
 | 
			
		||||
		panic(NewBizErrCode(TokenErrorCode, TokenErrorMsg))
 | 
			
		||||
	}
 | 
			
		||||
	return loginAccount
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 获取分页参数
 | 
			
		||||
func (c *Controller) GetPageParam() *PageParam {
 | 
			
		||||
	pn, err := c.GetInt("pageNum", 1)
 | 
			
		||||
	BizErrIsNil(err, "pageNum参数错误")
 | 
			
		||||
	ps, serr := c.GetInt("pageSize", 10)
 | 
			
		||||
	BizErrIsNil(serr, "pageSize参数错误")
 | 
			
		||||
	return &PageParam{PageNum: pn, PageSize: ps}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 统一返回Result json对象
 | 
			
		||||
func (c *Controller) Result(result *Result) {
 | 
			
		||||
	c.Data["json"] = result
 | 
			
		||||
	c.ServeJSON()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 返回成功结果
 | 
			
		||||
func (c *Controller) Success(data interface{}) {
 | 
			
		||||
	c.Result(Success(data))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 返回成功结果
 | 
			
		||||
func (c *Controller) SuccessNoData() {
 | 
			
		||||
	c.Result(SuccessNoData())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 返回业务错误
 | 
			
		||||
func (c *Controller) BizError(bizError BizError) {
 | 
			
		||||
	c.Result(Error(bizError.Code(), bizError.Error()))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 返回服务器错误结果
 | 
			
		||||
func (c *Controller) ServerError() {
 | 
			
		||||
	c.Result(ServerError())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 解析error,并对不同error返回不同result
 | 
			
		||||
func (c *Controller) parseErr(err interface{}) {
 | 
			
		||||
	switch t := err.(type) {
 | 
			
		||||
	case BizError:
 | 
			
		||||
		c.BizError(t)
 | 
			
		||||
		break
 | 
			
		||||
	case error:
 | 
			
		||||
		c.ServerError()
 | 
			
		||||
		logs.Error(t)
 | 
			
		||||
		panic(err)
 | 
			
		||||
		//break
 | 
			
		||||
	case string:
 | 
			
		||||
		c.ServerError()
 | 
			
		||||
		logs.Error(t)
 | 
			
		||||
		panic(err)
 | 
			
		||||
		//break
 | 
			
		||||
	default:
 | 
			
		||||
		logs.Error(t)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										161
									
								
								base/httpclient/httpclient.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										161
									
								
								base/httpclient/httpclient.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,161 @@
 | 
			
		||||
package httpclient
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// 默认超时
 | 
			
		||||
const DefTimeout = 60
 | 
			
		||||
 | 
			
		||||
type RequestWrapper struct {
 | 
			
		||||
	url     string
 | 
			
		||||
	method  string
 | 
			
		||||
	timeout int
 | 
			
		||||
	body    io.Reader
 | 
			
		||||
	header  map[string]string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 创建一个请求
 | 
			
		||||
func NewRequest(url string) *RequestWrapper {
 | 
			
		||||
	return &RequestWrapper{url: url}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *RequestWrapper) Url(url string) *RequestWrapper {
 | 
			
		||||
	r.url = url
 | 
			
		||||
	return r
 | 
			
		||||
}
 | 
			
		||||
func (r *RequestWrapper) Timeout(timeout int) *RequestWrapper {
 | 
			
		||||
	r.timeout = timeout
 | 
			
		||||
	return r
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *RequestWrapper) GetByParam(paramMap map[string]string) ResponseWrapper {
 | 
			
		||||
	var params string
 | 
			
		||||
	for k, v := range paramMap {
 | 
			
		||||
		if params != "" {
 | 
			
		||||
			params += "&"
 | 
			
		||||
		} else {
 | 
			
		||||
			params += "?"
 | 
			
		||||
		}
 | 
			
		||||
		params += k + "=" + v
 | 
			
		||||
	}
 | 
			
		||||
	r.url += "?" + params
 | 
			
		||||
	return r.Get()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *RequestWrapper) Get() ResponseWrapper {
 | 
			
		||||
	r.method = "GET"
 | 
			
		||||
	r.body = nil
 | 
			
		||||
	return request(r)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *RequestWrapper) PostJson(body string) ResponseWrapper {
 | 
			
		||||
	buf := bytes.NewBufferString(body)
 | 
			
		||||
	r.method = "POST"
 | 
			
		||||
	r.body = buf
 | 
			
		||||
	if r.header == nil {
 | 
			
		||||
		r.header = make(map[string]string)
 | 
			
		||||
	}
 | 
			
		||||
	r.header["Content-type"] = "application/json"
 | 
			
		||||
	return request(r)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *RequestWrapper) PostObj(body interface{}) ResponseWrapper {
 | 
			
		||||
	marshal, err := json.Marshal(body)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return createRequestError(errors.New("解析json obj错误"))
 | 
			
		||||
	}
 | 
			
		||||
	return r.PostJson(string(marshal))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *RequestWrapper) PostParams(params string) ResponseWrapper {
 | 
			
		||||
	buf := bytes.NewBufferString(params)
 | 
			
		||||
	r.method = "POST"
 | 
			
		||||
	r.body = buf
 | 
			
		||||
	if r.header == nil {
 | 
			
		||||
		r.header = make(map[string]string)
 | 
			
		||||
	}
 | 
			
		||||
	r.header["Content-type"] = "application/x-www-form-urlencoded"
 | 
			
		||||
	return request(r)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ResponseWrapper struct {
 | 
			
		||||
	StatusCode int
 | 
			
		||||
	Body       string
 | 
			
		||||
	Header     http.Header
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *ResponseWrapper) IsSuccess() bool {
 | 
			
		||||
	return r.StatusCode == 200
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *ResponseWrapper) ToObj(obj interface{}) {
 | 
			
		||||
	if !r.IsSuccess() {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	_ = json.Unmarshal([]byte(r.Body), &obj)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *ResponseWrapper) ToMap() map[string]interface{} {
 | 
			
		||||
	if !r.IsSuccess() {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	var res map[string]interface{}
 | 
			
		||||
	err := json.Unmarshal([]byte(r.Body), &res)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	return res
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func request(rw *RequestWrapper) ResponseWrapper {
 | 
			
		||||
	wrapper := ResponseWrapper{StatusCode: 0, Body: "", Header: make(http.Header)}
 | 
			
		||||
	client := &http.Client{}
 | 
			
		||||
	timeout := rw.timeout
 | 
			
		||||
	if timeout > 0 {
 | 
			
		||||
		client.Timeout = time.Duration(timeout) * time.Second
 | 
			
		||||
	} else {
 | 
			
		||||
		timeout = DefTimeout
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	req, err := http.NewRequest(rw.method, rw.url, rw.body)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return createRequestError(err)
 | 
			
		||||
	}
 | 
			
		||||
	setRequestHeader(req, rw.header)
 | 
			
		||||
	resp, err := client.Do(req)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		wrapper.Body = fmt.Sprintf("执行HTTP请求错误-%s", err.Error())
 | 
			
		||||
		return wrapper
 | 
			
		||||
	}
 | 
			
		||||
	defer resp.Body.Close()
 | 
			
		||||
	body, err := ioutil.ReadAll(resp.Body)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		wrapper.Body = fmt.Sprintf("读取HTTP请求返回值失败-%s", err.Error())
 | 
			
		||||
		return wrapper
 | 
			
		||||
	}
 | 
			
		||||
	wrapper.StatusCode = resp.StatusCode
 | 
			
		||||
	wrapper.Body = string(body)
 | 
			
		||||
	wrapper.Header = resp.Header
 | 
			
		||||
 | 
			
		||||
	return wrapper
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func setRequestHeader(req *http.Request, header map[string]string) {
 | 
			
		||||
	req.Header.Set("User-Agent", "golang/mayflyjob")
 | 
			
		||||
	for k, v := range header {
 | 
			
		||||
		req.Header.Set(k, v)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func createRequestError(err error) ResponseWrapper {
 | 
			
		||||
	errorMessage := fmt.Sprintf("创建HTTP请求错误-%s", err.Error())
 | 
			
		||||
	return ResponseWrapper{0, errorMessage, make(http.Header)}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										216
									
								
								base/model.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										216
									
								
								base/model.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,216 @@
 | 
			
		||||
package base
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"github.com/astaxie/beego/orm"
 | 
			
		||||
	"github.com/siddontang/go/log"
 | 
			
		||||
	"mayfly-go/base/utils"
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Model struct {
 | 
			
		||||
	Id         uint64    `orm:"column(id);auto" json:"id"`
 | 
			
		||||
	CreateTime time.Time `orm:"column(create_time);type(datetime);null" json:"createTime"`
 | 
			
		||||
	CreatorId  uint64    `orm:"column(creator_id)" json:"creatorId"`
 | 
			
		||||
	Creator    string    `orm:"column(creator)" json:"creator"`
 | 
			
		||||
	UpdateTime time.Time `orm:"column(update_time);type(datetime);null" json:"updateTime"`
 | 
			
		||||
	ModifierId uint64    `orm:"column(modifier_id)" json:"modifierId"`
 | 
			
		||||
	Modifier   string    `orm:"column(modifier)" json:"modifier"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 获取orm querySeter
 | 
			
		||||
func QuerySetter(table interface{}) orm.QuerySeter {
 | 
			
		||||
	return getOrm().QueryTable(table)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 获取分页结果
 | 
			
		||||
func GetPage(seter orm.QuerySeter, pageParam *PageParam, models interface{}, toModels interface{}) PageResult {
 | 
			
		||||
	count, _ := seter.Count()
 | 
			
		||||
	if count == 0 {
 | 
			
		||||
		return PageResult{Total: 0, List: nil}
 | 
			
		||||
	}
 | 
			
		||||
	_, qerr := seter.Limit(pageParam.PageSize, pageParam.PageNum-1).All(models, getFieldNames(toModels)...)
 | 
			
		||||
	BizErrIsNil(qerr, "查询错误")
 | 
			
		||||
	err := utils.Copy(toModels, models)
 | 
			
		||||
	BizErrIsNil(err, "实体转换错误")
 | 
			
		||||
	return PageResult{Total: count, List: toModels}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 根据sql获取分页对象
 | 
			
		||||
func GetPageBySql(sql string, toModel interface{}, param *PageParam, args ...interface{}) PageResult {
 | 
			
		||||
	selectIndex := strings.Index(sql, "SELECT ") + 7
 | 
			
		||||
	fromIndex := strings.Index(sql, " FROM")
 | 
			
		||||
	selectCol := sql[selectIndex:fromIndex]
 | 
			
		||||
	countSql := strings.Replace(sql, selectCol, "COUNT(*) AS total ", 1)
 | 
			
		||||
	// 查询count
 | 
			
		||||
	o := getOrm()
 | 
			
		||||
	type TotalRes struct {
 | 
			
		||||
		Total int64
 | 
			
		||||
	}
 | 
			
		||||
	var totalRes TotalRes
 | 
			
		||||
	_ = o.Raw(countSql, args).QueryRow(&totalRes)
 | 
			
		||||
	total := totalRes.Total
 | 
			
		||||
	if total == 0 {
 | 
			
		||||
		return PageResult{Total: 0, List: nil}
 | 
			
		||||
	}
 | 
			
		||||
	// 分页查询
 | 
			
		||||
	limitSql := sql + " LIMIT " + strconv.Itoa(param.PageNum-1) + ", " + strconv.Itoa(param.PageSize)
 | 
			
		||||
	var maps []orm.Params
 | 
			
		||||
	_, err := o.Raw(limitSql, args).Values(&maps)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic(errors.New("查询错误 : " + err.Error()))
 | 
			
		||||
	}
 | 
			
		||||
	e := ormParams2Struct(maps, toModel)
 | 
			
		||||
	if e != nil {
 | 
			
		||||
		panic(e)
 | 
			
		||||
	}
 | 
			
		||||
	return PageResult{Total: total, List: toModel}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetListBySql(sql string, params ...interface{}) *[]orm.Params {
 | 
			
		||||
	var maps []orm.Params
 | 
			
		||||
	_, err := getOrm().Raw(sql, params).Values(&maps)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Error("根据sql查询数据列表失败:%s", err.Error())
 | 
			
		||||
	}
 | 
			
		||||
	return &maps
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 获取所有列表数据
 | 
			
		||||
func GetList(seter orm.QuerySeter, model interface{}, toModel interface{}) {
 | 
			
		||||
	_, _ = seter.All(model, getFieldNames(toModel)...)
 | 
			
		||||
	err := utils.Copy(toModel, model)
 | 
			
		||||
	BizErrIsNil(err, "实体转换错误")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 根据toModel结构体字段查询单条记录,并将值赋值给toModel
 | 
			
		||||
func GetOne(seter orm.QuerySeter, model interface{}, toModel interface{}) error {
 | 
			
		||||
	err := seter.One(model, getFieldNames(toModel)...)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	cerr := utils.Copy(toModel, model)
 | 
			
		||||
	BizErrIsNil(cerr, "实体转换错误")
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 根据实体以及指定字段值查询实体,若字段数组为空,则默认用id查
 | 
			
		||||
func GetBy(model interface{}, fs ...string) error {
 | 
			
		||||
	err := getOrm().Read(model, fs...)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if err == orm.ErrNoRows {
 | 
			
		||||
			return errors.New("该数据不存在")
 | 
			
		||||
		} else {
 | 
			
		||||
			return errors.New("查询失败")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Insert(model interface{}) error {
 | 
			
		||||
	_, err := getOrm().Insert(model)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.New("数据插入失败")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Update(model interface{}, fs ...string) error {
 | 
			
		||||
	_, err := getOrm().Update(model, fs...)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.New("数据更新失败")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Delete(model interface{}, fs ...string) error {
 | 
			
		||||
	_, err := getOrm().Delete(model, fs...)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.New("数据删除失败")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getOrm() orm.Ormer {
 | 
			
		||||
	return orm.NewOrm()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 结果模型缓存
 | 
			
		||||
var resultModelCache = make(map[string][]string)
 | 
			
		||||
 | 
			
		||||
// 获取实体对象的字段名
 | 
			
		||||
func getFieldNames(obj interface{}) []string {
 | 
			
		||||
	objType := indirectType(reflect.TypeOf(obj))
 | 
			
		||||
	cacheKey := objType.PkgPath() + "." + objType.Name()
 | 
			
		||||
	cache := resultModelCache[cacheKey]
 | 
			
		||||
	if cache != nil {
 | 
			
		||||
		return cache
 | 
			
		||||
	}
 | 
			
		||||
	cache = getFieldNamesByType("", reflect.TypeOf(obj))
 | 
			
		||||
	resultModelCache[cacheKey] = cache
 | 
			
		||||
	return cache
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func indirectType(reflectType reflect.Type) reflect.Type {
 | 
			
		||||
	for reflectType.Kind() == reflect.Ptr || reflectType.Kind() == reflect.Slice {
 | 
			
		||||
		reflectType = reflectType.Elem()
 | 
			
		||||
	}
 | 
			
		||||
	return reflectType
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getFieldNamesByType(namePrefix string, reflectType reflect.Type) []string {
 | 
			
		||||
	var fieldNames []string
 | 
			
		||||
 | 
			
		||||
	if reflectType = indirectType(reflectType); reflectType.Kind() == reflect.Struct {
 | 
			
		||||
		for i := 0; i < reflectType.NumField(); i++ {
 | 
			
		||||
			t := reflectType.Field(i)
 | 
			
		||||
			tName := t.Name
 | 
			
		||||
			// 判断结构体字段是否为结构体,是的话则跳过
 | 
			
		||||
			it := indirectType(t.Type)
 | 
			
		||||
			if it.Kind() == reflect.Struct {
 | 
			
		||||
				itName := it.Name()
 | 
			
		||||
				// 如果包含Time或time则表示为time类型,无需递归该结构体字段
 | 
			
		||||
				if !strings.Contains(itName, "BaseModel") && !strings.Contains(itName, "Time") &&
 | 
			
		||||
					!strings.Contains(itName, "time") {
 | 
			
		||||
					fieldNames = append(fieldNames, getFieldNamesByType(tName+"__", it)...)
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if t.Anonymous {
 | 
			
		||||
				fieldNames = append(fieldNames, getFieldNamesByType("", t.Type)...)
 | 
			
		||||
			} else {
 | 
			
		||||
				fieldNames = append(fieldNames, namePrefix+tName)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return fieldNames
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ormParams2Struct(maps []orm.Params, structs interface{}) error {
 | 
			
		||||
	structsV := reflect.Indirect(reflect.ValueOf(structs))
 | 
			
		||||
	valType := structsV.Type()
 | 
			
		||||
	valElemType := valType.Elem()
 | 
			
		||||
	sliceType := reflect.SliceOf(valElemType)
 | 
			
		||||
 | 
			
		||||
	length := len(maps)
 | 
			
		||||
 | 
			
		||||
	valSlice := structsV
 | 
			
		||||
	if valSlice.IsNil() {
 | 
			
		||||
		// Make a new slice to hold our result, same size as the original data.
 | 
			
		||||
		valSlice = reflect.MakeSlice(sliceType, length, length)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i := 0; i < length; i++ {
 | 
			
		||||
		err := utils.Map2Struct(maps[i], valSlice.Index(i).Addr().Interface())
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	structsV.Set(valSlice)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										71
									
								
								base/model_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								base/model_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,71 @@
 | 
			
		||||
package base
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"github.com/astaxie/beego/orm"
 | 
			
		||||
	_ "github.com/go-sql-driver/mysql"
 | 
			
		||||
	"mayfly-go/base/utils"
 | 
			
		||||
	"mayfly-go/controllers/vo"
 | 
			
		||||
	"mayfly-go/models"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
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"))
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										13
									
								
								base/page.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								base/page.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
package base
 | 
			
		||||
 | 
			
		||||
// 分页参数
 | 
			
		||||
type PageParam struct {
 | 
			
		||||
	PageNum  int `json:"pageNum"`
 | 
			
		||||
	PageSize int `json:"pageSize"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 分页结果
 | 
			
		||||
type PageResult struct {
 | 
			
		||||
	Total int64       `json:"total"`
 | 
			
		||||
	List  interface{} `json:"list"`
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										66
									
								
								base/result.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								base/result.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,66 @@
 | 
			
		||||
package base
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	SuccessCode = 200
 | 
			
		||||
	SuccessMsg  = "success"
 | 
			
		||||
 | 
			
		||||
	BizErrorCode = 400
 | 
			
		||||
	BizErrorMsg  = "error"
 | 
			
		||||
 | 
			
		||||
	ServerErrorCode = 500
 | 
			
		||||
	ServerErrorMsg  = "server error"
 | 
			
		||||
 | 
			
		||||
	TokenErrorCode = 501
 | 
			
		||||
	TokenErrorMsg  = "token error"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// 统一返回结果结构体
 | 
			
		||||
type Result struct {
 | 
			
		||||
	Code int16       `json:"code"`
 | 
			
		||||
	Msg  string      `json:"msg"`
 | 
			
		||||
	Data interface{} `json:"data"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 将Result转为json字符串
 | 
			
		||||
func (r *Result) ToJson() string {
 | 
			
		||||
	jsonData, err := json.Marshal(r)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Println("data转json错误")
 | 
			
		||||
	}
 | 
			
		||||
	return string(jsonData)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 判断该Result是否为成功状态
 | 
			
		||||
func (r *Result) IsSuccess() bool {
 | 
			
		||||
	return r.Code == SuccessCode
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 返回成功状态的Result
 | 
			
		||||
// @param data 成功附带的数据消息
 | 
			
		||||
func Success(data interface{}) *Result {
 | 
			
		||||
	return &Result{Code: SuccessCode, Msg: SuccessMsg, Data: data}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 返回成功状态的Result
 | 
			
		||||
// @param data 成功不附带数据
 | 
			
		||||
func SuccessNoData() *Result {
 | 
			
		||||
	return &Result{Code: SuccessCode, Msg: SuccessMsg}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 返回服务器错误Result
 | 
			
		||||
func ServerError() *Result {
 | 
			
		||||
	return &Result{Code: ServerErrorCode, Msg: ServerErrorMsg}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Error(code int16, msg string) *Result {
 | 
			
		||||
	return &Result{Code: code, Msg: msg}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TokenError() *Result {
 | 
			
		||||
	return &Result{Code: TokenErrorCode, Msg: TokenErrorMsg}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										49
									
								
								base/token.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								base/token.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,49 @@
 | 
			
		||||
package base
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"github.com/dgrijalva/jwt-go"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	JwtKey  = "mykey"
 | 
			
		||||
	ExpTime = time.Hour * 24 * 7
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type LoginAccount struct {
 | 
			
		||||
	Id       uint64
 | 
			
		||||
	Username string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 创建用户token
 | 
			
		||||
func CreateToken(userId uint64, username string) string {
 | 
			
		||||
	// 带权限创建令牌
 | 
			
		||||
	// 设置有效期,过期需要重新登录获取token
 | 
			
		||||
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
 | 
			
		||||
		"id":       userId,
 | 
			
		||||
		"username": username,
 | 
			
		||||
		"exp":      time.Now().Add(ExpTime).Unix(),
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	// 使用自定义字符串加密 and get the complete encoded token as a string
 | 
			
		||||
	tokenString, err := token.SignedString([]byte(JwtKey))
 | 
			
		||||
	BizErrIsNil(err, "token创建失败")
 | 
			
		||||
	return tokenString
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 解析token,并返回登录者账号信息
 | 
			
		||||
func ParseToken(tokenStr string) (*LoginAccount, error) {
 | 
			
		||||
	if tokenStr == "" {
 | 
			
		||||
		return nil, errors.New("token error")
 | 
			
		||||
	}
 | 
			
		||||
	// Parse token
 | 
			
		||||
	token, err := jwt.Parse(tokenStr, func(token *jwt.Token) (interface{}, error) {
 | 
			
		||||
		return []byte(JwtKey), nil
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil || token == nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	i := token.Claims.(jwt.MapClaims)
 | 
			
		||||
	return &LoginAccount{Id: uint64(i["id"].(float64)), Username: i["username"].(string)}, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										23
									
								
								base/utils/map_utils.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								base/utils/map_utils.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
			
		||||
package utils
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"strconv"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func GetString4Map(m map[string]interface{}, key string) string {
 | 
			
		||||
	return m[key].(string)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetInt4Map(m map[string]interface{}, key string) int {
 | 
			
		||||
	i := m[key]
 | 
			
		||||
	iKind := reflect.TypeOf(i).Kind()
 | 
			
		||||
	if iKind == reflect.Int {
 | 
			
		||||
		return i.(int)
 | 
			
		||||
	}
 | 
			
		||||
	if iKind == reflect.String {
 | 
			
		||||
		i, _ := strconv.Atoi(i.(string))
 | 
			
		||||
		return i
 | 
			
		||||
	}
 | 
			
		||||
	return 0
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										89
									
								
								base/utils/str_utils.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								base/utils/str_utils.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,89 @@
 | 
			
		||||
package utils
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"text/template"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// 可判断中文
 | 
			
		||||
func StrLen(str string) int {
 | 
			
		||||
	return len([]rune(str))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 去除字符串左右空字符
 | 
			
		||||
func StrTrim(str string) string {
 | 
			
		||||
	return strings.Trim(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 TemplateResolve(temp string, data interface{}) string {
 | 
			
		||||
	t, _ := template.New("string-temp").Parse(temp)
 | 
			
		||||
	var tmplBytes bytes.Buffer
 | 
			
		||||
 | 
			
		||||
	err := t.Execute(&tmplBytes, data)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic(err)
 | 
			
		||||
	}
 | 
			
		||||
	return tmplBytes.String()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ReverStrTemplate(temp, str string, res map[string]interface{}) {
 | 
			
		||||
	index := UnicodeIndex(temp, "{")
 | 
			
		||||
	ei := UnicodeIndex(temp, "}") + 1
 | 
			
		||||
	next := StrTrim(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 := StrTrim(SubString(str, index, valueLastIndex))
 | 
			
		||||
	res[key] = value
 | 
			
		||||
	// 如果后面的还有需要解析的,则递归调用解析
 | 
			
		||||
	if nextContain != -1 {
 | 
			
		||||
		ReverStrTemplate(next, StrTrim(SubString(str, UnicodeIndex(str, value)+StrLen(value), StrLen(str))), res)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										629
									
								
								base/utils/struct_utils.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										629
									
								
								base/utils/struct_utils.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,629 @@
 | 
			
		||||
package utils
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"database/sql"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Copy copy things,引用至copier
 | 
			
		||||
func Copy(toValue interface{}, fromValue interface{}) (err error) {
 | 
			
		||||
	var (
 | 
			
		||||
		isSlice bool
 | 
			
		||||
		amount  = 1
 | 
			
		||||
		from    = Indirect(reflect.ValueOf(fromValue))
 | 
			
		||||
		to      = Indirect(reflect.ValueOf(toValue))
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	if !to.CanAddr() {
 | 
			
		||||
		return errors.New("copy to value is unaddressable")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Return is from value is invalid
 | 
			
		||||
	if !from.IsValid() {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fromType := IndirectType(from.Type())
 | 
			
		||||
	toType := IndirectType(to.Type())
 | 
			
		||||
 | 
			
		||||
	// Just set it if possible to assign
 | 
			
		||||
	// And need to do copy anyway if the type is struct
 | 
			
		||||
	if fromType.Kind() != reflect.Struct && from.Type().AssignableTo(to.Type()) {
 | 
			
		||||
		to.Set(from)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if fromType.Kind() != reflect.Struct || toType.Kind() != reflect.Struct {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if to.Kind() == reflect.Slice {
 | 
			
		||||
		isSlice = true
 | 
			
		||||
		if from.Kind() == reflect.Slice {
 | 
			
		||||
			amount = from.Len()
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i := 0; i < amount; i++ {
 | 
			
		||||
		var dest, source reflect.Value
 | 
			
		||||
 | 
			
		||||
		if isSlice {
 | 
			
		||||
			// source
 | 
			
		||||
			if from.Kind() == reflect.Slice {
 | 
			
		||||
				source = Indirect(from.Index(i))
 | 
			
		||||
			} else {
 | 
			
		||||
				source = Indirect(from)
 | 
			
		||||
			}
 | 
			
		||||
			// dest
 | 
			
		||||
			dest = Indirect(reflect.New(toType).Elem())
 | 
			
		||||
		} else {
 | 
			
		||||
			source = Indirect(from)
 | 
			
		||||
			dest = Indirect(to)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// check source
 | 
			
		||||
		if source.IsValid() {
 | 
			
		||||
			fromTypeFields := deepFields(fromType)
 | 
			
		||||
			//fmt.Printf("%#v", fromTypeFields)
 | 
			
		||||
			// Copy from field to field or method
 | 
			
		||||
			for _, field := range fromTypeFields {
 | 
			
		||||
				name := field.Name
 | 
			
		||||
 | 
			
		||||
				if fromField := source.FieldByName(name); fromField.IsValid() {
 | 
			
		||||
					// has field
 | 
			
		||||
					if toField := dest.FieldByName(name); toField.IsValid() {
 | 
			
		||||
						if toField.CanSet() {
 | 
			
		||||
							if !set(toField, fromField) {
 | 
			
		||||
								if err := Copy(toField.Addr().Interface(), fromField.Interface()); err != nil {
 | 
			
		||||
									return err
 | 
			
		||||
								}
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
					} else {
 | 
			
		||||
						// try to set to method
 | 
			
		||||
						var toMethod reflect.Value
 | 
			
		||||
						if dest.CanAddr() {
 | 
			
		||||
							toMethod = dest.Addr().MethodByName(name)
 | 
			
		||||
						} else {
 | 
			
		||||
							toMethod = dest.MethodByName(name)
 | 
			
		||||
						}
 | 
			
		||||
 | 
			
		||||
						if toMethod.IsValid() && toMethod.Type().NumIn() == 1 && fromField.Type().AssignableTo(toMethod.Type().In(0)) {
 | 
			
		||||
							toMethod.Call([]reflect.Value{fromField})
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Copy from method to field
 | 
			
		||||
			for _, field := range deepFields(toType) {
 | 
			
		||||
				name := field.Name
 | 
			
		||||
 | 
			
		||||
				var fromMethod reflect.Value
 | 
			
		||||
				if source.CanAddr() {
 | 
			
		||||
					fromMethod = source.Addr().MethodByName(name)
 | 
			
		||||
				} else {
 | 
			
		||||
					fromMethod = source.MethodByName(name)
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if fromMethod.IsValid() && fromMethod.Type().NumIn() == 0 && fromMethod.Type().NumOut() == 1 {
 | 
			
		||||
					if toField := dest.FieldByName(name); toField.IsValid() && toField.CanSet() {
 | 
			
		||||
						values := fromMethod.Call([]reflect.Value{})
 | 
			
		||||
						if len(values) >= 1 {
 | 
			
		||||
							set(toField, values[0])
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if isSlice {
 | 
			
		||||
			if dest.Addr().Type().AssignableTo(to.Type().Elem()) {
 | 
			
		||||
				to.Set(reflect.Append(to, dest.Addr()))
 | 
			
		||||
			} else if dest.Type().AssignableTo(to.Type().Elem()) {
 | 
			
		||||
				to.Set(reflect.Append(to, dest))
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func deepFields(reflectType reflect.Type) []reflect.StructField {
 | 
			
		||||
	var fields []reflect.StructField
 | 
			
		||||
 | 
			
		||||
	if reflectType = IndirectType(reflectType); reflectType.Kind() == reflect.Struct {
 | 
			
		||||
		for i := 0; i < reflectType.NumField(); i++ {
 | 
			
		||||
			v := reflectType.Field(i)
 | 
			
		||||
			if v.Anonymous {
 | 
			
		||||
				fields = append(fields, deepFields(v.Type)...)
 | 
			
		||||
			} else {
 | 
			
		||||
				fields = append(fields, v)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return fields
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Indirect(reflectValue reflect.Value) reflect.Value {
 | 
			
		||||
	for reflectValue.Kind() == reflect.Ptr {
 | 
			
		||||
		reflectValue = reflectValue.Elem()
 | 
			
		||||
	}
 | 
			
		||||
	return reflectValue
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func IndirectType(reflectType reflect.Type) reflect.Type {
 | 
			
		||||
	for reflectType.Kind() == reflect.Ptr || reflectType.Kind() == reflect.Slice {
 | 
			
		||||
		reflectType = reflectType.Elem()
 | 
			
		||||
	}
 | 
			
		||||
	return reflectType
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func set(to, from reflect.Value) bool {
 | 
			
		||||
	if from.IsValid() {
 | 
			
		||||
		if to.Kind() == reflect.Ptr {
 | 
			
		||||
			//set `to` to nil if from is nil
 | 
			
		||||
			if from.Kind() == reflect.Ptr && from.IsNil() {
 | 
			
		||||
				to.Set(reflect.Zero(to.Type()))
 | 
			
		||||
				return true
 | 
			
		||||
			} else if to.IsNil() {
 | 
			
		||||
				to.Set(reflect.New(to.Type().Elem()))
 | 
			
		||||
			}
 | 
			
		||||
			to = to.Elem()
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if from.Type().ConvertibleTo(to.Type()) {
 | 
			
		||||
			to.Set(from.Convert(to.Type()))
 | 
			
		||||
		} else if scanner, ok := to.Addr().Interface().(sql.Scanner); ok {
 | 
			
		||||
			err := scanner.Scan(from.Interface())
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return false
 | 
			
		||||
			}
 | 
			
		||||
		} else if from.Kind() == reflect.Ptr {
 | 
			
		||||
			return set(to, from.Elem())
 | 
			
		||||
		} else {
 | 
			
		||||
			return false
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Map2Struct(m map[string]interface{}, s interface{}) error {
 | 
			
		||||
	toValue := Indirect(reflect.ValueOf(s))
 | 
			
		||||
	if !toValue.CanAddr() {
 | 
			
		||||
		return errors.New("to value is unaddressable")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	innerStructMaps := getInnerStructMaps(m)
 | 
			
		||||
	if len(innerStructMaps) != 0 {
 | 
			
		||||
		for k, v := range innerStructMaps {
 | 
			
		||||
			var fieldV reflect.Value
 | 
			
		||||
			if strings.Contains(k, ".") {
 | 
			
		||||
				fieldV = getFiledValueByPath(k, toValue)
 | 
			
		||||
			} else {
 | 
			
		||||
				fieldV = toValue.FieldByName(k)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if !fieldV.CanSet() || !fieldV.CanAddr() {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			fieldT := fieldV.Type().Elem()
 | 
			
		||||
			if fieldT.Kind() != reflect.Struct {
 | 
			
		||||
				return errors.New(k + "不是结构体")
 | 
			
		||||
			}
 | 
			
		||||
			// 如果值为nil,则默认创建一个并赋值
 | 
			
		||||
			if fieldV.IsNil() {
 | 
			
		||||
				fieldV.Set(reflect.New(fieldT))
 | 
			
		||||
			}
 | 
			
		||||
			err := Map2Struct(v, fieldV.Addr().Interface())
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	var err error
 | 
			
		||||
	for k, v := range m {
 | 
			
		||||
		if v == nil {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		k = strings.Title(k)
 | 
			
		||||
		// 如果key含有下划线,则将其转为驼峰
 | 
			
		||||
		if strings.Contains(k, "_") {
 | 
			
		||||
			k = Case2Camel(k)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		fieldV := toValue.FieldByName(k)
 | 
			
		||||
		if !fieldV.CanSet() {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		err = decode(k, v, fieldV)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Maps2Structs(maps []map[string]interface{}, structs interface{}) error {
 | 
			
		||||
	structsV := reflect.Indirect(reflect.ValueOf(structs))
 | 
			
		||||
	valType := structsV.Type()
 | 
			
		||||
	valElemType := valType.Elem()
 | 
			
		||||
	sliceType := reflect.SliceOf(valElemType)
 | 
			
		||||
 | 
			
		||||
	length := len(maps)
 | 
			
		||||
 | 
			
		||||
	valSlice := structsV
 | 
			
		||||
	if valSlice.IsNil() {
 | 
			
		||||
		// Make a new slice to hold our result, same size as the original data.
 | 
			
		||||
		valSlice = reflect.MakeSlice(sliceType, length, length)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i := 0; i < length; i++ {
 | 
			
		||||
		err := Map2Struct(maps[i], valSlice.Index(i).Addr().Interface())
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	structsV.Set(valSlice)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getFiledValueByPath(path string, value reflect.Value) reflect.Value {
 | 
			
		||||
	split := strings.Split(path, ".")
 | 
			
		||||
	for _, v := range split {
 | 
			
		||||
		if value.Type().Kind() == reflect.Ptr {
 | 
			
		||||
			// 如果值为nil,则创建并赋值
 | 
			
		||||
			if value.IsNil() {
 | 
			
		||||
				value.Set(reflect.New(IndirectType(value.Type())))
 | 
			
		||||
			}
 | 
			
		||||
			value = value.Elem()
 | 
			
		||||
		}
 | 
			
		||||
		value = value.FieldByName(v)
 | 
			
		||||
	}
 | 
			
		||||
	return value
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getInnerStructMaps(m map[string]interface{}) map[string]map[string]interface{} {
 | 
			
		||||
	key2map := make(map[string]map[string]interface{})
 | 
			
		||||
	for k, v := range m {
 | 
			
		||||
		if !strings.Contains(k, ".") {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		lastIndex := strings.LastIndex(k, ".")
 | 
			
		||||
		prefix := k[0:lastIndex]
 | 
			
		||||
		m2 := key2map[prefix]
 | 
			
		||||
		if m2 == nil {
 | 
			
		||||
			key2map[prefix] = map[string]interface{}{k[lastIndex+1:]: v}
 | 
			
		||||
		} else {
 | 
			
		||||
			m2[k[lastIndex+1:]] = v
 | 
			
		||||
		}
 | 
			
		||||
		delete(m, k)
 | 
			
		||||
	}
 | 
			
		||||
	return key2map
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//  decode等方法摘抄自mapstructure库
 | 
			
		||||
 | 
			
		||||
func decode(name string, input interface{}, outVal reflect.Value) error {
 | 
			
		||||
	var inputVal reflect.Value
 | 
			
		||||
	if input != nil {
 | 
			
		||||
		inputVal = reflect.ValueOf(input)
 | 
			
		||||
 | 
			
		||||
		// We need to check here if input is a typed nil. Typed nils won't
 | 
			
		||||
		// match the "input == nil" below so we check that here.
 | 
			
		||||
		if inputVal.Kind() == reflect.Ptr && inputVal.IsNil() {
 | 
			
		||||
			input = nil
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !inputVal.IsValid() {
 | 
			
		||||
		// If the input value is invalid, then we just set the value
 | 
			
		||||
		// to be the zero value.
 | 
			
		||||
		outVal.Set(reflect.Zero(outVal.Type()))
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var err error
 | 
			
		||||
	outputKind := getKind(outVal)
 | 
			
		||||
	switch outputKind {
 | 
			
		||||
	case reflect.Int:
 | 
			
		||||
		err = decodeInt(name, input, outVal)
 | 
			
		||||
	case reflect.Uint:
 | 
			
		||||
		err = decodeUint(name, input, outVal)
 | 
			
		||||
	case reflect.Float32:
 | 
			
		||||
		err = decodeFloat(name, input, outVal)
 | 
			
		||||
	case reflect.String:
 | 
			
		||||
		err = decodeString(name, input, outVal)
 | 
			
		||||
	case reflect.Ptr:
 | 
			
		||||
		_, err = decodePtr(name, input, outVal)
 | 
			
		||||
	default:
 | 
			
		||||
		// If we reached this point then we weren't able to decode it
 | 
			
		||||
		return fmt.Errorf("%s: unsupported type: %s", name, outputKind)
 | 
			
		||||
	}
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func decodeInt(name string, data interface{}, val reflect.Value) error {
 | 
			
		||||
	dataVal := reflect.Indirect(reflect.ValueOf(data))
 | 
			
		||||
	dataKind := getKind(dataVal)
 | 
			
		||||
	dataType := dataVal.Type()
 | 
			
		||||
 | 
			
		||||
	switch {
 | 
			
		||||
	case dataKind == reflect.Int:
 | 
			
		||||
		val.SetInt(dataVal.Int())
 | 
			
		||||
	case dataKind == reflect.Uint:
 | 
			
		||||
		val.SetInt(int64(dataVal.Uint()))
 | 
			
		||||
	case dataKind == reflect.Float32:
 | 
			
		||||
		val.SetInt(int64(dataVal.Float()))
 | 
			
		||||
	case dataKind == reflect.Bool:
 | 
			
		||||
		if dataVal.Bool() {
 | 
			
		||||
			val.SetInt(1)
 | 
			
		||||
		} else {
 | 
			
		||||
			val.SetInt(0)
 | 
			
		||||
		}
 | 
			
		||||
	case dataKind == reflect.String:
 | 
			
		||||
		i, err := strconv.ParseInt(dataVal.String(), 0, val.Type().Bits())
 | 
			
		||||
		if err == nil {
 | 
			
		||||
			val.SetInt(i)
 | 
			
		||||
		} else {
 | 
			
		||||
			return fmt.Errorf("cannot parse '%s' as int: %s", name, err)
 | 
			
		||||
		}
 | 
			
		||||
	case dataType.PkgPath() == "encoding/json" && dataType.Name() == "Number":
 | 
			
		||||
		jn := data.(json.Number)
 | 
			
		||||
		i, err := jn.Int64()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return fmt.Errorf(
 | 
			
		||||
				"error decoding json.Number into %s: %s", name, err)
 | 
			
		||||
		}
 | 
			
		||||
		val.SetInt(i)
 | 
			
		||||
	default:
 | 
			
		||||
		return fmt.Errorf(
 | 
			
		||||
			"'%s' expected type '%s', got unconvertible type '%s'",
 | 
			
		||||
			name, val.Type(), dataVal.Type())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func decodeUint(name string, data interface{}, val reflect.Value) error {
 | 
			
		||||
	dataVal := reflect.Indirect(reflect.ValueOf(data))
 | 
			
		||||
	dataKind := getKind(dataVal)
 | 
			
		||||
	dataType := dataVal.Type()
 | 
			
		||||
 | 
			
		||||
	switch {
 | 
			
		||||
	case dataKind == reflect.Int:
 | 
			
		||||
		i := dataVal.Int()
 | 
			
		||||
		if i < 0 {
 | 
			
		||||
			return fmt.Errorf("cannot parse '%s', %d overflows uint",
 | 
			
		||||
				name, i)
 | 
			
		||||
		}
 | 
			
		||||
		val.SetUint(uint64(i))
 | 
			
		||||
	case dataKind == reflect.Uint:
 | 
			
		||||
		val.SetUint(dataVal.Uint())
 | 
			
		||||
	case dataKind == reflect.Float32:
 | 
			
		||||
		f := dataVal.Float()
 | 
			
		||||
		if f < 0 {
 | 
			
		||||
			return fmt.Errorf("cannot parse '%s', %f overflows uint",
 | 
			
		||||
				name, f)
 | 
			
		||||
		}
 | 
			
		||||
		val.SetUint(uint64(f))
 | 
			
		||||
	case dataKind == reflect.Bool:
 | 
			
		||||
		if dataVal.Bool() {
 | 
			
		||||
			val.SetUint(1)
 | 
			
		||||
		} else {
 | 
			
		||||
			val.SetUint(0)
 | 
			
		||||
		}
 | 
			
		||||
	case dataKind == reflect.String:
 | 
			
		||||
		i, err := strconv.ParseUint(dataVal.String(), 0, val.Type().Bits())
 | 
			
		||||
		if err == nil {
 | 
			
		||||
			val.SetUint(i)
 | 
			
		||||
		} else {
 | 
			
		||||
			return fmt.Errorf("cannot parse '%s' as uint: %s", name, err)
 | 
			
		||||
		}
 | 
			
		||||
	case dataType.PkgPath() == "encoding/json" && dataType.Name() == "Number":
 | 
			
		||||
		jn := data.(json.Number)
 | 
			
		||||
		i, err := jn.Int64()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return fmt.Errorf(
 | 
			
		||||
				"error decoding json.Number into %s: %s", name, err)
 | 
			
		||||
		}
 | 
			
		||||
		if i < 0 {
 | 
			
		||||
			return fmt.Errorf("cannot parse '%s', %d overflows uint",
 | 
			
		||||
				name, i)
 | 
			
		||||
		}
 | 
			
		||||
		val.SetUint(uint64(i))
 | 
			
		||||
	default:
 | 
			
		||||
		return fmt.Errorf(
 | 
			
		||||
			"'%s' expected type '%s', got unconvertible type '%s'",
 | 
			
		||||
			name, val.Type(), dataVal.Type())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func decodeFloat(name string, data interface{}, val reflect.Value) error {
 | 
			
		||||
	dataVal := reflect.Indirect(reflect.ValueOf(data))
 | 
			
		||||
	dataKind := getKind(dataVal)
 | 
			
		||||
	dataType := dataVal.Type()
 | 
			
		||||
 | 
			
		||||
	switch {
 | 
			
		||||
	case dataKind == reflect.Int:
 | 
			
		||||
		val.SetFloat(float64(dataVal.Int()))
 | 
			
		||||
	case dataKind == reflect.Uint:
 | 
			
		||||
		val.SetFloat(float64(dataVal.Uint()))
 | 
			
		||||
	case dataKind == reflect.Float32:
 | 
			
		||||
		val.SetFloat(dataVal.Float())
 | 
			
		||||
	case dataKind == reflect.Bool:
 | 
			
		||||
		if dataVal.Bool() {
 | 
			
		||||
			val.SetFloat(1)
 | 
			
		||||
		} else {
 | 
			
		||||
			val.SetFloat(0)
 | 
			
		||||
		}
 | 
			
		||||
	case dataKind == reflect.String:
 | 
			
		||||
		f, err := strconv.ParseFloat(dataVal.String(), val.Type().Bits())
 | 
			
		||||
		if err == nil {
 | 
			
		||||
			val.SetFloat(f)
 | 
			
		||||
		} else {
 | 
			
		||||
			return fmt.Errorf("cannot parse '%s' as float: %s", name, err)
 | 
			
		||||
		}
 | 
			
		||||
	case dataType.PkgPath() == "encoding/json" && dataType.Name() == "Number":
 | 
			
		||||
		jn := data.(json.Number)
 | 
			
		||||
		i, err := jn.Float64()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return fmt.Errorf(
 | 
			
		||||
				"error decoding json.Number into %s: %s", name, err)
 | 
			
		||||
		}
 | 
			
		||||
		val.SetFloat(i)
 | 
			
		||||
	default:
 | 
			
		||||
		return fmt.Errorf(
 | 
			
		||||
			"'%s' expected type '%s', got unconvertible type '%s'",
 | 
			
		||||
			name, val.Type(), dataVal.Type())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func decodeString(name string, data interface{}, val reflect.Value) error {
 | 
			
		||||
	dataVal := reflect.Indirect(reflect.ValueOf(data))
 | 
			
		||||
	dataKind := getKind(dataVal)
 | 
			
		||||
 | 
			
		||||
	converted := true
 | 
			
		||||
	switch {
 | 
			
		||||
	case dataKind == reflect.String:
 | 
			
		||||
		val.SetString(dataVal.String())
 | 
			
		||||
	case dataKind == reflect.Bool:
 | 
			
		||||
		if dataVal.Bool() {
 | 
			
		||||
			val.SetString("1")
 | 
			
		||||
		} else {
 | 
			
		||||
			val.SetString("0")
 | 
			
		||||
		}
 | 
			
		||||
	case dataKind == reflect.Int:
 | 
			
		||||
		val.SetString(strconv.FormatInt(dataVal.Int(), 10))
 | 
			
		||||
	case dataKind == reflect.Uint:
 | 
			
		||||
		val.SetString(strconv.FormatUint(dataVal.Uint(), 10))
 | 
			
		||||
	case dataKind == reflect.Float32:
 | 
			
		||||
		val.SetString(strconv.FormatFloat(dataVal.Float(), 'f', -1, 64))
 | 
			
		||||
	case dataKind == reflect.Slice,
 | 
			
		||||
		dataKind == reflect.Array:
 | 
			
		||||
		dataType := dataVal.Type()
 | 
			
		||||
		elemKind := dataType.Elem().Kind()
 | 
			
		||||
		switch elemKind {
 | 
			
		||||
		case reflect.Uint8:
 | 
			
		||||
			var uints []uint8
 | 
			
		||||
			if dataKind == reflect.Array {
 | 
			
		||||
				uints = make([]uint8, dataVal.Len(), dataVal.Len())
 | 
			
		||||
				for i := range uints {
 | 
			
		||||
					uints[i] = dataVal.Index(i).Interface().(uint8)
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				uints = dataVal.Interface().([]uint8)
 | 
			
		||||
			}
 | 
			
		||||
			val.SetString(string(uints))
 | 
			
		||||
		default:
 | 
			
		||||
			converted = false
 | 
			
		||||
		}
 | 
			
		||||
	default:
 | 
			
		||||
		converted = false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !converted {
 | 
			
		||||
		return fmt.Errorf(
 | 
			
		||||
			"'%s' expected type '%s', got unconvertible type '%s'",
 | 
			
		||||
			name, val.Type(), dataVal.Type())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func decodePtr(name string, data interface{}, val reflect.Value) (bool, error) {
 | 
			
		||||
	// If the input data is nil, then we want to just set the output
 | 
			
		||||
	// pointer to be nil as well.
 | 
			
		||||
	isNil := data == nil
 | 
			
		||||
	if !isNil {
 | 
			
		||||
		switch v := reflect.Indirect(reflect.ValueOf(data)); v.Kind() {
 | 
			
		||||
		case reflect.Chan,
 | 
			
		||||
			reflect.Func,
 | 
			
		||||
			reflect.Interface,
 | 
			
		||||
			reflect.Map,
 | 
			
		||||
			reflect.Ptr,
 | 
			
		||||
			reflect.Slice:
 | 
			
		||||
			isNil = v.IsNil()
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if isNil {
 | 
			
		||||
		if !val.IsNil() && val.CanSet() {
 | 
			
		||||
			nilValue := reflect.New(val.Type()).Elem()
 | 
			
		||||
			val.Set(nilValue)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return true, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Create an element of the concrete (non pointer) type and decode
 | 
			
		||||
	// into that. Then set the value of the pointer to this type.
 | 
			
		||||
	valType := val.Type()
 | 
			
		||||
	valElemType := valType.Elem()
 | 
			
		||||
	if val.CanSet() {
 | 
			
		||||
		realVal := val
 | 
			
		||||
		if realVal.IsNil() {
 | 
			
		||||
			realVal = reflect.New(valElemType)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := decode(name, data, reflect.Indirect(realVal)); err != nil {
 | 
			
		||||
			return false, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		val.Set(realVal)
 | 
			
		||||
	} else {
 | 
			
		||||
		if err := decode(name, data, reflect.Indirect(val)); err != nil {
 | 
			
		||||
			return false, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getKind(val reflect.Value) reflect.Kind {
 | 
			
		||||
	kind := val.Kind()
 | 
			
		||||
 | 
			
		||||
	switch {
 | 
			
		||||
	case kind >= reflect.Int && kind <= reflect.Int64:
 | 
			
		||||
		return reflect.Int
 | 
			
		||||
	case kind >= reflect.Uint && kind <= reflect.Uint64:
 | 
			
		||||
		return reflect.Uint
 | 
			
		||||
	case kind >= reflect.Float32 && kind <= reflect.Float64:
 | 
			
		||||
		return reflect.Float32
 | 
			
		||||
	default:
 | 
			
		||||
		return kind
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 下划线写法转为驼峰写法
 | 
			
		||||
func Case2Camel(name string) string {
 | 
			
		||||
	name = strings.Replace(name, "_", " ", -1)
 | 
			
		||||
	name = strings.Title(name)
 | 
			
		||||
	return strings.Replace(name, " ", "", -1)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func isBlank(value reflect.Value) bool {
 | 
			
		||||
	switch value.Kind() {
 | 
			
		||||
	case reflect.String:
 | 
			
		||||
		return value.Len() == 0
 | 
			
		||||
	case reflect.Bool:
 | 
			
		||||
		return !value.Bool()
 | 
			
		||||
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
 | 
			
		||||
		return value.Int() == 0
 | 
			
		||||
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
 | 
			
		||||
		return value.Uint() == 0
 | 
			
		||||
	case reflect.Float32, reflect.Float64:
 | 
			
		||||
		return value.Float() == 0
 | 
			
		||||
	case reflect.Interface, reflect.Ptr:
 | 
			
		||||
		return value.IsNil()
 | 
			
		||||
	}
 | 
			
		||||
	return reflect.DeepEqual(value.Interface(), reflect.Zero(value.Type()).Interface())
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										195
									
								
								base/utils/struct_utils_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										195
									
								
								base/utils/struct_utils_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,195 @@
 | 
			
		||||
package utils
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"github.com/mitchellh/mapstructure"
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Src struct {
 | 
			
		||||
	Id         *int64    `json:"id"`
 | 
			
		||||
	Username   string    `json:"username"`
 | 
			
		||||
	CreateTime time.Time `json:"time"`
 | 
			
		||||
	UpdateTime time.Time
 | 
			
		||||
	Inner      *SrcInner
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type SrcInner struct {
 | 
			
		||||
	Name string
 | 
			
		||||
	Desc string
 | 
			
		||||
	Id   int64
 | 
			
		||||
	Dest *Dest
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Dest struct {
 | 
			
		||||
	Username   string
 | 
			
		||||
	Id         int64
 | 
			
		||||
	CreateTime time.Time
 | 
			
		||||
	Inner      *DestInner
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type DestInner struct {
 | 
			
		||||
	Desc string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDeepFields(t *testing.T) {
 | 
			
		||||
	////src := Src{Username: "test", Id: 1000, CreateTime: time.Now()}
 | 
			
		||||
	//si := SrcInner{Desc: "desc"}
 | 
			
		||||
	//src.Inner = &si
 | 
			
		||||
	////src.Id = 1222
 | 
			
		||||
	//dest := new(Dest)
 | 
			
		||||
	//err := structutils.Copy(dest, src)
 | 
			
		||||
	//if err != nil {
 | 
			
		||||
	//	fmt.Println(err.Error())
 | 
			
		||||
	//} else {
 | 
			
		||||
	//	fmt.Println(dest)
 | 
			
		||||
	//}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGetFieldNames(t *testing.T) {
 | 
			
		||||
	//names := structutils.GetFieldNames(new(Src))
 | 
			
		||||
	//fmt.Println(names)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestMaps2Structs(t *testing.T) {
 | 
			
		||||
	mapInstance := make(map[string]interface{})
 | 
			
		||||
	mapInstance["Username"] = "liang637210"
 | 
			
		||||
	mapInstance["Id"] = 28
 | 
			
		||||
	mapInstance["CreateTime"] = time.Now()
 | 
			
		||||
	mapInstance["Creator"] = "createor"
 | 
			
		||||
	mapInstance["Inner.Id"] = 10
 | 
			
		||||
	mapInstance["Inner.Name"] = "hahah"
 | 
			
		||||
	mapInstance["Inner.Desc"] = "inner desc"
 | 
			
		||||
	mapInstance["Inner.Dest.Username"] = "inner dest uername"
 | 
			
		||||
	mapInstance["Inner.Dest.Inner.Desc"] = "inner dest inner desc"
 | 
			
		||||
 | 
			
		||||
	mapInstance2 := make(map[string]interface{})
 | 
			
		||||
	mapInstance2["Username"] = "liang6372102"
 | 
			
		||||
	mapInstance2["Id"] = 282
 | 
			
		||||
	mapInstance2["CreateTime"] = time.Now()
 | 
			
		||||
	mapInstance2["Creator"] = "createor2"
 | 
			
		||||
	mapInstance2["Inner.Id"] = 102
 | 
			
		||||
	mapInstance2["Inner.Name"] = "hahah2"
 | 
			
		||||
	mapInstance2["Inner.Desc"] = "inner desc2"
 | 
			
		||||
	mapInstance2["Inner.Dest.Username"] = "inner dest uername2"
 | 
			
		||||
	mapInstance2["Inner.Dest.Inner.Desc"] = "inner dest inner desc2"
 | 
			
		||||
 | 
			
		||||
	maps := make([]map[string]interface{}, 2)
 | 
			
		||||
	maps[0] = mapInstance
 | 
			
		||||
	maps[1] = mapInstance2
 | 
			
		||||
	res := new([]Src)
 | 
			
		||||
	err := Maps2Structs(maps, res)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Println(err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestMap2Struct(t *testing.T) {
 | 
			
		||||
	mapInstance := make(map[string]interface{})
 | 
			
		||||
	mapInstance["Username"] = "liang637210"
 | 
			
		||||
	mapInstance["Id"] = 12
 | 
			
		||||
	mapInstance["CreateTime"] = time.Now()
 | 
			
		||||
	mapInstance["Creator"] = "createor"
 | 
			
		||||
	mapInstance["Inner.Id"] = nil
 | 
			
		||||
	mapInstance["Inner.Name"] = "hahah"
 | 
			
		||||
	mapInstance["Inner.Desc"] = "inner desc"
 | 
			
		||||
	mapInstance["Inner.Dest.Username"] = "inner dest uername"
 | 
			
		||||
	mapInstance["Inner.Dest.Inner.Desc"] = "inner dest inner desc"
 | 
			
		||||
 | 
			
		||||
	//innerMap := make(map[string]interface{})
 | 
			
		||||
	//innerMap["Name"] = "Innername"
 | 
			
		||||
 | 
			
		||||
	//a := new(Src)
 | 
			
		||||
	////a.Inner = new(SrcInner)
 | 
			
		||||
	//
 | 
			
		||||
	//stime := time.Now().UnixNano()
 | 
			
		||||
	//for i := 0; i < 1000000; i++ {
 | 
			
		||||
	//	err := structutils.Map2Struct(mapInstance, a)
 | 
			
		||||
	//	if err != nil {
 | 
			
		||||
	//		fmt.Println(err)
 | 
			
		||||
	//	}
 | 
			
		||||
	//}
 | 
			
		||||
	//etime := time.Now().UnixNano()
 | 
			
		||||
	//fmt.Println(etime - stime)
 | 
			
		||||
	//if err != nil {
 | 
			
		||||
	//	fmt.Println(err)
 | 
			
		||||
	//} else {
 | 
			
		||||
	//	fmt.Println(a)
 | 
			
		||||
	//}
 | 
			
		||||
 | 
			
		||||
	s := new(Src)
 | 
			
		||||
	//name, b := structutils.IndirectType(reflect.TypeOf(s)).FieldByName("Inner")
 | 
			
		||||
	//if structutils.IndirectType(name.Type).Kind() != reflect.Struct {
 | 
			
		||||
	//	fmt.Println(name.Name + "不是结构体")
 | 
			
		||||
	//} else {
 | 
			
		||||
	//	//innerType := name.Type
 | 
			
		||||
	//	innerValue := structutils.Indirect(reflect.ValueOf(s)).FieldByName("Inner")
 | 
			
		||||
	//	//if innerValue.IsValid() && innerValue.IsNil() {
 | 
			
		||||
	//	//	innerValue.Set(reflect.New(innerValue.Type().Elem()))
 | 
			
		||||
	//	//}
 | 
			
		||||
	//	if !innerValue.IsValid() {
 | 
			
		||||
	//		fmt.Println("is valid")
 | 
			
		||||
	//	} else {
 | 
			
		||||
	//		//innerValue.Set(reflect.New(innerValue.Type()))
 | 
			
		||||
	//		fmt.Println(innerValue.CanSet())
 | 
			
		||||
	//		fmt.Println(innerValue.CanAddr())
 | 
			
		||||
	//		//mapstructure.Decode(innerMap, innerValue.Addr().Interface())
 | 
			
		||||
	//	}
 | 
			
		||||
	//
 | 
			
		||||
	//}
 | 
			
		||||
	//
 | 
			
		||||
	//fmt.Println(name, b)
 | 
			
		||||
	//将 map 转换为指定的结构体
 | 
			
		||||
	if err := mapstructure.Decode(mapInstance, &s); err != nil {
 | 
			
		||||
		fmt.Println(err)
 | 
			
		||||
	}
 | 
			
		||||
	fmt.Printf("map2struct后得到的 struct 内容为:%v", s)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getPrefixKeyMap(m map[string]interface{}) map[string]map[string]interface{} {
 | 
			
		||||
	key2map := make(map[string]map[string]interface{})
 | 
			
		||||
	for k, v := range m {
 | 
			
		||||
		if !strings.Contains(k, ".") {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		lastIndex := strings.LastIndex(k, ".")
 | 
			
		||||
		prefix := k[0:lastIndex]
 | 
			
		||||
		m2 := key2map[prefix]
 | 
			
		||||
		if m2 == nil {
 | 
			
		||||
			key2map[prefix] = map[string]interface{}{k[lastIndex+1:]: v}
 | 
			
		||||
		} else {
 | 
			
		||||
			m2[k[lastIndex+1:]] = v
 | 
			
		||||
		}
 | 
			
		||||
		delete(m, k)
 | 
			
		||||
	}
 | 
			
		||||
	return key2map
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestReflect(t *testing.T) {
 | 
			
		||||
	type dog struct {
 | 
			
		||||
		LegCount int
 | 
			
		||||
	}
 | 
			
		||||
	// 获取dog实例的反射值对象
 | 
			
		||||
	valueOfDog := reflect.ValueOf(&dog{}).Elem()
 | 
			
		||||
 | 
			
		||||
	// 获取legCount字段的值
 | 
			
		||||
	vLegCount := valueOfDog.FieldByName("LegCount")
 | 
			
		||||
 | 
			
		||||
	fmt.Println(vLegCount.CanSet())
 | 
			
		||||
	fmt.Println(vLegCount.CanAddr())
 | 
			
		||||
	// 尝试设置legCount的值(这里会发生崩溃)
 | 
			
		||||
	vLegCount.SetInt(4)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestTemplateResolve(t *testing.T) {
 | 
			
		||||
	d := make(map[string]string)
 | 
			
		||||
	d["Name"] = "黄先生"
 | 
			
		||||
	d["Age"] = "23jlfdsjf"
 | 
			
		||||
	resolve := TemplateResolve("{{.Name}} is name, and {{.Age}} is age", d)
 | 
			
		||||
	fmt.Println(resolve)
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										21
									
								
								conf/app.conf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								conf/app.conf
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
			
		||||
appname = mayfly-job
 | 
			
		||||
httpport = 8888
 | 
			
		||||
copyrequestbody = true
 | 
			
		||||
autorender = false
 | 
			
		||||
EnableErrorsRender = false
 | 
			
		||||
runmode = "dev"
 | 
			
		||||
; mysqluser = "root"
 | 
			
		||||
; mysqlpass = "111049"
 | 
			
		||||
; mysqlurls = "127.0.0.1"
 | 
			
		||||
; mysqldb   = "mayfly-job"
 | 
			
		||||
EnableAdmin = true
 | 
			
		||||
AdminHttpAddr = 0.0.0.0 #默认监听地址是localhost
 | 
			
		||||
AdminHttpPort = 8088
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
[dev]
 | 
			
		||||
httpport = 8888
 | 
			
		||||
[prod]
 | 
			
		||||
httpport = 8080
 | 
			
		||||
[test]
 | 
			
		||||
httpport = 8888
 | 
			
		||||
							
								
								
									
										42
									
								
								controllers/account.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								controllers/account.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,42 @@
 | 
			
		||||
package controllers
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"mayfly-go/base"
 | 
			
		||||
	"mayfly-go/controllers/form"
 | 
			
		||||
	"mayfly-go/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type AccountController struct {
 | 
			
		||||
	base.Controller
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//func (c *AccountController) URLMapping() {
 | 
			
		||||
//	c.Mapping("Login", c.Login)
 | 
			
		||||
//	c.Mapping("Accounts", c.Accounts)
 | 
			
		||||
//}
 | 
			
		||||
 | 
			
		||||
// @router /accounts/login [post]
 | 
			
		||||
func (c *AccountController) Login() {
 | 
			
		||||
	c.ReturnData(false, func(la *base.LoginAccount) interface{} {
 | 
			
		||||
		loginForm := &form.LoginForm{}
 | 
			
		||||
		c.UnmarshalBodyAndValid(loginForm)
 | 
			
		||||
 | 
			
		||||
		a := &models.Account{Username: loginForm.Username, Password: loginForm.Password}
 | 
			
		||||
		base.BizErrIsNil(base.GetBy(a, "Username", "Password"), "用户名或密码错误")
 | 
			
		||||
		return map[string]interface{}{
 | 
			
		||||
			"token":    base.CreateToken(a.Id, a.Username),
 | 
			
		||||
			"username": a.Username,
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// @router /accounts [get]
 | 
			
		||||
func (c *AccountController) Accounts() {
 | 
			
		||||
	c.ReturnData(true, func(account *base.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))
 | 
			
		||||
 | 
			
		||||
		return models.ListAccount(c.GetPageParam())
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										12
									
								
								controllers/form/form.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								controllers/form/form.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
			
		||||
package form
 | 
			
		||||
 | 
			
		||||
// 登录表单
 | 
			
		||||
type LoginForm struct {
 | 
			
		||||
	Username string `valid:"Required"`
 | 
			
		||||
	Password string `valid:"Required"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type MachineRunForm struct {
 | 
			
		||||
	MachineId int64  `valid:"Required"`
 | 
			
		||||
	Cmd       string `valid:"Required"`
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										90
									
								
								controllers/machine.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								controllers/machine.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,90 @@
 | 
			
		||||
package controllers
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/gorilla/websocket"
 | 
			
		||||
	"mayfly-go/base"
 | 
			
		||||
	"mayfly-go/machine"
 | 
			
		||||
	"mayfly-go/models"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"strconv"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type MachineController struct {
 | 
			
		||||
	base.Controller
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var upGrader = websocket.Upgrader{
 | 
			
		||||
	ReadBufferSize:  1024,
 | 
			
		||||
	WriteBufferSize: 1024 * 1024 * 10,
 | 
			
		||||
	CheckOrigin: func(r *http.Request) bool {
 | 
			
		||||
		return true
 | 
			
		||||
	},
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *MachineController) Machines() {
 | 
			
		||||
	c.ReturnData(true, func(account *base.LoginAccount) interface{} {
 | 
			
		||||
		return models.GetMachineList(c.GetPageParam())
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *MachineController) Run() {
 | 
			
		||||
	c.ReturnData(true, func(account *base.LoginAccount) interface{} {
 | 
			
		||||
		cmd := c.GetString("cmd")
 | 
			
		||||
		base.NotEmpty(cmd, "cmd不能为空")
 | 
			
		||||
 | 
			
		||||
		return machine.GetCli(c.GetMachineId()).Run(cmd)
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 系统基本信息
 | 
			
		||||
func (c *MachineController) SysInfo() {
 | 
			
		||||
	c.ReturnData(true, func(account *base.LoginAccount) interface{} {
 | 
			
		||||
		return machine.GetSystemInfo(machine.GetCli(c.GetMachineId()))
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// top命令信息
 | 
			
		||||
func (c *MachineController) Top() {
 | 
			
		||||
	c.ReturnData(true, func(account *base.LoginAccount) interface{} {
 | 
			
		||||
		return machine.GetTop(machine.GetCli(c.GetMachineId()))
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *MachineController) GetProcessByName() {
 | 
			
		||||
	c.ReturnData(true, func(account *base.LoginAccount) interface{} {
 | 
			
		||||
		name := c.GetString("name")
 | 
			
		||||
		base.NotEmpty(name, "name不能为空")
 | 
			
		||||
		return machine.GetProcessByName(machine.GetCli(c.GetMachineId()), name)
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//func (c *MachineController) WsSSH() {
 | 
			
		||||
//	wsConn, err := upGrader.Upgrade(c.Ctx.ResponseWriter, c.Ctx.Request, nil)
 | 
			
		||||
//	if err != nil {
 | 
			
		||||
//		panic(base.NewBizErr("获取requst responsewirte错误"))
 | 
			
		||||
//	}
 | 
			
		||||
//
 | 
			
		||||
//	cols, _ := c.GetInt("col", 80)
 | 
			
		||||
//	rows, _ := c.GetInt("rows", 40)
 | 
			
		||||
//
 | 
			
		||||
//	sws, err := machine.NewLogicSshWsSession(cols, rows, true, machine.GetCli(c.GetMachineId()), wsConn)
 | 
			
		||||
//	if sws == nil {
 | 
			
		||||
//		panic(base.NewBizErr("连接失败"))
 | 
			
		||||
//	}
 | 
			
		||||
//	//if wshandleError(wsConn, err) {
 | 
			
		||||
//	//	return
 | 
			
		||||
//	//}
 | 
			
		||||
//	defer sws.Close()
 | 
			
		||||
//
 | 
			
		||||
//	quitChan := make(chan bool, 3)
 | 
			
		||||
//	sws.Start(quitChan)
 | 
			
		||||
//	go sws.Wait(quitChan)
 | 
			
		||||
//
 | 
			
		||||
//	<-quitChan
 | 
			
		||||
//}
 | 
			
		||||
 | 
			
		||||
func (c *MachineController) GetMachineId() uint64 {
 | 
			
		||||
	machineId, _ := strconv.Atoi(c.Ctx.Input.Param(":machineId"))
 | 
			
		||||
	base.IsTrue(machineId > 0, "machineId错误")
 | 
			
		||||
	return uint64(machineId)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										31
									
								
								controllers/vo/vo.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								controllers/vo/vo.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,31 @@
 | 
			
		||||
package vo
 | 
			
		||||
 | 
			
		||||
import "time"
 | 
			
		||||
 | 
			
		||||
type AccountVO struct {
 | 
			
		||||
	//models.BaseModel
 | 
			
		||||
	Id         *int64  `json:"id"`
 | 
			
		||||
	Username   *string `json:"username"`
 | 
			
		||||
	CreateTime *string `json:"createTime"`
 | 
			
		||||
	Creator    *string `json:"creator"`
 | 
			
		||||
	CreatorId  *int64  `json:"creatorId"`
 | 
			
		||||
	Role       *RoleVO `json:"roles"`
 | 
			
		||||
	//Status   int8   `json:"status"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type MachineVO struct {
 | 
			
		||||
	//models.BaseModel
 | 
			
		||||
	Id         *int64     `json:"id"`
 | 
			
		||||
	Name       *string    `json:"name"`
 | 
			
		||||
	Username   *string    `json:"username"`
 | 
			
		||||
	Ip         *string    `json:"ip"`
 | 
			
		||||
	Port       *int       `json:"port"`
 | 
			
		||||
	CreateTime *time.Time `json:"createTime"`
 | 
			
		||||
	Creator    *string    `json:"creator"`
 | 
			
		||||
	CreatorId  *int64     `json:"creatorId"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type RoleVO struct {
 | 
			
		||||
	Id   *int64
 | 
			
		||||
	Name *string
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										21
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
			
		||||
module mayfly-go
 | 
			
		||||
 | 
			
		||||
go 1.13
 | 
			
		||||
 | 
			
		||||
require github.com/astaxie/beego v1.12.1
 | 
			
		||||
 | 
			
		||||
require github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 // indirect
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	github.com/dgrijalva/jwt-go v3.2.0+incompatible
 | 
			
		||||
	github.com/gliderlabs/ssh v0.2.2
 | 
			
		||||
	github.com/go-sql-driver/mysql v1.4.1
 | 
			
		||||
	github.com/gorilla/websocket v1.4.2
 | 
			
		||||
	github.com/mitchellh/mapstructure v1.3.3
 | 
			
		||||
	github.com/pkg/sftp v1.11.0
 | 
			
		||||
	github.com/robfig/cron/v3 v3.0.1
 | 
			
		||||
	github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726
 | 
			
		||||
	github.com/smartystreets/goconvey v1.6.4
 | 
			
		||||
	golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550
 | 
			
		||||
	google.golang.org/appengine v1.6.6 // indirect
 | 
			
		||||
)
 | 
			
		||||
							
								
								
									
										95
									
								
								go.sum
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								go.sum
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,95 @@
 | 
			
		||||
github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
 | 
			
		||||
github.com/OwnLocal/goes v1.0.0/go.mod h1:8rIFjBGTue3lCU0wplczcUgt9Gxgrkkrw7etMIcn8TM=
 | 
			
		||||
github.com/astaxie/beego v1.12.1 h1:dfpuoxpzLVgclveAXe4PyNKqkzgm5zF4tgF2B3kkM2I=
 | 
			
		||||
github.com/astaxie/beego v1.12.1/go.mod h1:kPBWpSANNbSdIqOc8SUL9h+1oyBMZhROeYsXQDbidWQ=
 | 
			
		||||
github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd/go.mod h1:1b+Y/CofkYwXMUU0OhQqGvsY2Bvgr4j6jfT699wyZKQ=
 | 
			
		||||
github.com/beego/x2j v0.0.0-20131220205130-a0352aadc542/go.mod h1:kSeGC/p1AbBiEp5kat81+DSQrZenVBZXklMLaELspWU=
 | 
			
		||||
github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60=
 | 
			
		||||
github.com/casbin/casbin v1.7.0/go.mod h1:c67qKN6Oum3UF5Q1+BByfFxkwKvhwW57ITjqwtzR1KE=
 | 
			
		||||
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80=
 | 
			
		||||
github.com/couchbase/go-couchbase v0.0.0-20181122212707-3e9b6e1258bb/go.mod h1:TWI8EKQMs5u5jLKW/tsb9VwauIrMIxQG1r5fMsswK5U=
 | 
			
		||||
github.com/couchbase/gomemcached v0.0.0-20181122193126-5125a94a666c/go.mod h1:srVSlQLB8iXBVXHgnqemxUXqN6FCvClgCMPCsjBDR7c=
 | 
			
		||||
github.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs=
 | 
			
		||||
github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY=
 | 
			
		||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
			
		||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
 | 
			
		||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
 | 
			
		||||
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
 | 
			
		||||
github.com/elazarl/go-bindata-assetfs v1.0.0 h1:G/bYguwHIzWq9ZoyUQqrjTmJbbYn3j3CKKpKinvZLFk=
 | 
			
		||||
github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
 | 
			
		||||
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
 | 
			
		||||
github.com/go-redis/redis v6.14.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
 | 
			
		||||
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
 | 
			
		||||
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
 | 
			
		||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 | 
			
		||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 | 
			
		||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 | 
			
		||||
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
 | 
			
		||||
github.com/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/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/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
 | 
			
		||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
 | 
			
		||||
github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
 | 
			
		||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 | 
			
		||||
github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o=
 | 
			
		||||
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
 | 
			
		||||
github.com/mitchellh/mapstructure v1.3.3 h1:SzB1nHZ2Xi+17FP0zVQBHIZqvwRN9408fJO8h+eeNA8=
 | 
			
		||||
github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
 | 
			
		||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
 | 
			
		||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 | 
			
		||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
 | 
			
		||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 | 
			
		||||
github.com/pkg/sftp v1.11.0 h1:4Zv0OGbpkg4yNuUtH0s8rvoYxRCNyT29NVUo6pgPmxI=
 | 
			
		||||
github.com/pkg/sftp v1.11.0/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
 | 
			
		||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 | 
			
		||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
 | 
			
		||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
 | 
			
		||||
github.com/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/siddontang/go v0.0.0-20180604090527-bdc77568d726 h1:xT+JlYxNGqyT+XcU8iUrN18JYed2TvG9yN5ULG2jATM=
 | 
			
		||||
github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw=
 | 
			
		||||
github.com/siddontang/ledisdb v0.0.0-20181029004158-becf5f38d373/go.mod h1:mF1DpOSOUiJRMR+FDqaqu3EBqrybQtrDDszLUZ6oxPg=
 | 
			
		||||
github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z92TR1JKMkLLoaOQk++LVnOKL3ScbJ8GNGA=
 | 
			
		||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
 | 
			
		||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
 | 
			
		||||
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
 | 
			
		||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
 | 
			
		||||
github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec/go.mod h1:QBvMkMya+gXctz3kmljlUCu/yB3GZ6oee+dUozsezQE=
 | 
			
		||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 | 
			
		||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 | 
			
		||||
github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
 | 
			
		||||
github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b/go.mod h1:Q12BUT7DqIlHRmgv3RskH+UCM/4eqVMgI0EMmlSpAXc=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 | 
			
		||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
 | 
			
		||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 | 
			
		||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 | 
			
		||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
 | 
			
		||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
 | 
			
		||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 | 
			
		||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
			
		||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
 | 
			
		||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
 | 
			
		||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 | 
			
		||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
 | 
			
		||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 | 
			
		||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 | 
			
		||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 | 
			
		||||
golang.org/x/tools v0.0.0-20200117065230-39095c1d176c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 | 
			
		||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 | 
			
		||||
google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc=
 | 
			
		||||
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
 | 
			
		||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 | 
			
		||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
			
		||||
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
 | 
			
		||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 | 
			
		||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
 | 
			
		||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 | 
			
		||||
							
								
								
									
										188
									
								
								machine/machine.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										188
									
								
								machine/machine.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,188 @@
 | 
			
		||||
package machine
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"github.com/pkg/sftp"
 | 
			
		||||
	"golang.org/x/crypto/ssh"
 | 
			
		||||
	"golang.org/x/crypto/ssh/terminal"
 | 
			
		||||
	"io"
 | 
			
		||||
	"mayfly-go/base"
 | 
			
		||||
	"mayfly-go/models"
 | 
			
		||||
	"net"
 | 
			
		||||
	"os"
 | 
			
		||||
	"sync"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// 客户端信息
 | 
			
		||||
type Cli struct {
 | 
			
		||||
	machine *models.Machine
 | 
			
		||||
	// ssh客户端
 | 
			
		||||
	client *ssh.Client
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 客户端缓存
 | 
			
		||||
var clientCache sync.Map
 | 
			
		||||
var mutex sync.Mutex
 | 
			
		||||
 | 
			
		||||
// 从缓存中获取客户端信息,不存在则查库,并新建
 | 
			
		||||
func GetCli(machineId uint64) *Cli {
 | 
			
		||||
	mutex.Lock()
 | 
			
		||||
	defer mutex.Unlock()
 | 
			
		||||
	load, ok := clientCache.Load(machineId)
 | 
			
		||||
	if ok {
 | 
			
		||||
		return load.(*Cli)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cli, err := newClient(models.GetMachineById(machineId))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic(base.NewBizErr(err.Error()))
 | 
			
		||||
	}
 | 
			
		||||
	clientCache.LoadOrStore(machineId, cli)
 | 
			
		||||
	return cli
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//根据机器信息创建客户端对象
 | 
			
		||||
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, errors.New("获取机器client失败:" + err.Error())
 | 
			
		||||
	}
 | 
			
		||||
	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 c.machine.Id > 0 {
 | 
			
		||||
		clientCache.Delete(c.machine.Id)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 获取sftp client
 | 
			
		||||
func (c *Cli) GetSftpCli() *sftp.Client {
 | 
			
		||||
	if c.client == nil {
 | 
			
		||||
		if err := c.connect(); err != nil {
 | 
			
		||||
			panic(base.NewBizErr("连接ssh失败:" + err.Error()))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	client, serr := sftp.NewClient(c.client, sftp.MaxPacket(1<<15))
 | 
			
		||||
	if serr != nil {
 | 
			
		||||
		panic(base.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 {
 | 
			
		||||
	session, err := c.GetSession()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic(base.NewBizErr("获取ssh session失败:" + err.Error()))
 | 
			
		||||
	}
 | 
			
		||||
	defer session.Close()
 | 
			
		||||
	buf, rerr := session.CombinedOutput(shell)
 | 
			
		||||
	if rerr != nil {
 | 
			
		||||
		panic(base.NewBizErr("执行命令失败:" + rerr.Error()))
 | 
			
		||||
	}
 | 
			
		||||
	return string(buf)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//执行带交互的命令
 | 
			
		||||
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)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										142
									
								
								machine/machine_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										142
									
								
								machine/machine_test.go
									
									
									
									
									
										Normal 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) {
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										54
									
								
								machine/shell.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								machine/shell.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,54 @@
 | 
			
		||||
package machine
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/siddontang/go/log"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"mayfly-go/base"
 | 
			
		||||
	"mayfly-go/base/utils"
 | 
			
		||||
	"mayfly-go/models"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const BasePath = "./machine/shell/"
 | 
			
		||||
 | 
			
		||||
const MonitorTemp = "cpuRate:{cpuRate}%,memRate:{memRate}%,sysLoad:{sysLoad}\n"
 | 
			
		||||
 | 
			
		||||
// shell文件内容缓存,避免每次读取文件
 | 
			
		||||
var shellCache = make(map[string]string)
 | 
			
		||||
 | 
			
		||||
func GetProcessByName(cli *Cli, name string) string {
 | 
			
		||||
	return cli.Run(getShellContent("sys_info"))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetSystemInfo(cli *Cli) string {
 | 
			
		||||
	return cli.Run(getShellContent("system_info"))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetMonitorInfo(cli *Cli) *models.MachineMonitor {
 | 
			
		||||
	mm := new(models.MachineMonitor)
 | 
			
		||||
	res := cli.Run(getShellContent("monitor"))
 | 
			
		||||
	resMap := make(map[string]interface{})
 | 
			
		||||
	utils.ReverStrTemplate(MonitorTemp, res, resMap)
 | 
			
		||||
 | 
			
		||||
	err := utils.Map2Struct(resMap, mm)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Error("解析machine monitor: %s", err.Error())
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	mm.MachineId = cli.machine.Id
 | 
			
		||||
	mm.CreateTime = time.Now()
 | 
			
		||||
	return mm
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 获取shell内容
 | 
			
		||||
func getShellContent(name string) string {
 | 
			
		||||
	cacheShell := shellCache[name]
 | 
			
		||||
	if cacheShell != "" {
 | 
			
		||||
		return cacheShell
 | 
			
		||||
	}
 | 
			
		||||
	bytes, err := ioutil.ReadFile(BasePath + name + ".sh")
 | 
			
		||||
	base.ErrIsNil(err, "获取shell文件失败")
 | 
			
		||||
	shellStr := string(bytes)
 | 
			
		||||
	shellCache[name] = shellStr
 | 
			
		||||
	return shellStr
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										23
									
								
								machine/shell/get_process_by_name.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								machine/shell/get_process_by_name.sh
									
									
									
									
									
										Normal 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
 | 
			
		||||
							
								
								
									
										13
									
								
								machine/shell/monitor.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								machine/shell/monitor.sh
									
									
									
									
									
										Normal 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
 | 
			
		||||
							
								
								
									
										192
									
								
								machine/shell/sys_info.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										192
									
								
								machine/shell/sys_info.sh
									
									
									
									
									
										Normal 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
 | 
			
		||||
							
								
								
									
										41
									
								
								machine/shell/system_info.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								machine/shell/system_info.sh
									
									
									
									
									
										Normal 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
 | 
			
		||||
							
								
								
									
										105
									
								
								machine/status.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								machine/status.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,105 @@
 | 
			
		||||
package machine
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"mayfly-go/base"
 | 
			
		||||
	"mayfly-go/base/utils"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type SystemVersion struct {
 | 
			
		||||
	Version string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetSystemVersion(cli *Cli) *SystemVersion {
 | 
			
		||||
	res := cli.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"`
 | 
			
		||||
	// 空闲的(idle)CPU时间占比
 | 
			
		||||
	CpuId float32 `json:"cpuId"`
 | 
			
		||||
	// 等待(wait)I/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 GetTop(cli *Cli) *Top {
 | 
			
		||||
	res := cli.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)
 | 
			
		||||
	base.BizErrIsNil(err, "解析top出错")
 | 
			
		||||
	return top
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Status struct {
 | 
			
		||||
	// 系统版本
 | 
			
		||||
	SysVersion SystemVersion
 | 
			
		||||
	// top信息
 | 
			
		||||
	Top Top
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										261
									
								
								machine/ws_shell_session.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										261
									
								
								machine/ws_shell_session.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,261 @@
 | 
			
		||||
package machine
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"github.com/astaxie/beego/logs"
 | 
			
		||||
	"github.com/gorilla/websocket"
 | 
			
		||||
	"golang.org/x/crypto/ssh"
 | 
			
		||||
	"io"
 | 
			
		||||
	"sync"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
//func WsSsh(c *controllers.MachineController) {
 | 
			
		||||
//	wsConn, err := upGrader.Upgrade(c.Ctx.ResponseWriter, c.Ctx.Request, nil)
 | 
			
		||||
//	if err != nil {
 | 
			
		||||
//		panic(base.NewBizErr("获取requst responsewirte错误"))
 | 
			
		||||
//	}
 | 
			
		||||
//
 | 
			
		||||
//	cols, _ := c.GetInt("col", 80)
 | 
			
		||||
//	rows, _ := c.GetInt("rows", 40)
 | 
			
		||||
//
 | 
			
		||||
//	sws, err := NewLogicSshWsSession(cols, rows, true, GetCli(c.GetMachineId()).client, wsConn)
 | 
			
		||||
//	if sws == nil {
 | 
			
		||||
//		panic(base.NewBizErr("连接失败"))
 | 
			
		||||
//	}
 | 
			
		||||
//	//if wshandleError(wsConn, err) {
 | 
			
		||||
//	//	return
 | 
			
		||||
//	//}
 | 
			
		||||
//	defer sws.Close()
 | 
			
		||||
//
 | 
			
		||||
//	quitChan := make(chan bool, 3)
 | 
			
		||||
//	sws.Start(quitChan)
 | 
			
		||||
//	go sws.Wait(quitChan)
 | 
			
		||||
//
 | 
			
		||||
//	<-quitChan
 | 
			
		||||
//	//保存日志
 | 
			
		||||
//
 | 
			
		||||
//	////write logs
 | 
			
		||||
//	//xtermLog := model.SshLog{
 | 
			
		||||
//	//	StartedAt: startTime,
 | 
			
		||||
//	//	UserId:    userM.Id,
 | 
			
		||||
//	//	Log:       sws.LogString(),
 | 
			
		||||
//	//	MachineId: idx,
 | 
			
		||||
//	//	ClientIp:  cIp,
 | 
			
		||||
//	//}
 | 
			
		||||
//	//err = xtermLog.Create()
 | 
			
		||||
//	//if wshandleError(wsConn, err) {
 | 
			
		||||
//	//	return
 | 
			
		||||
//}
 | 
			
		||||
 | 
			
		||||
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"`
 | 
			
		||||
	Cmd  string `json:"cmd"`
 | 
			
		||||
	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
 | 
			
		||||
	isAdmin         bool
 | 
			
		||||
	IsFlagged       bool `comment:"当前session是否包含禁止命令"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewLogicSshWsSession(cols, rows int, isAdmin bool, 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,
 | 
			
		||||
		isAdmin:         isAdmin,
 | 
			
		||||
		IsFlagged:       false,
 | 
			
		||||
	}, 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 {
 | 
			
		||||
				logs.Error("reading webSocket message failed")
 | 
			
		||||
				//panic(base.NewBizErr("reading webSocket message failed"))
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			//unmashal bytes into struct
 | 
			
		||||
			msgObj := wsMsg{}
 | 
			
		||||
			if err := json.Unmarshal(wsData, &msgObj); err != nil {
 | 
			
		||||
				logs.Error("unmarshal websocket message failed")
 | 
			
		||||
				//panic(base.NewBizErr("unmarshal websocket message failed"))
 | 
			
		||||
			}
 | 
			
		||||
			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 {
 | 
			
		||||
						logs.Error("ssh pty change windows size failed")
 | 
			
		||||
						//panic(base.NewBizErr("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.Cmd))
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//sendWebsocketInputCommandToSshSessionStdinPipe
 | 
			
		||||
func (sws *LogicSshWsSession) sendWebsocketInputCommandToSshSessionStdinPipe(cmdBytes []byte) {
 | 
			
		||||
	if _, err := sws.stdinPipe.Write(cmdBytes); err != nil {
 | 
			
		||||
		logs.Error("ws cmd bytes write to ssh.stdin pipe failed")
 | 
			
		||||
		//panic(base.NewBizErr("ws cmd bytes write to ssh.stdin pipe failed"))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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 {
 | 
			
		||||
					logs.Error("ssh sending combo output to webSocket failed")
 | 
			
		||||
					//panic(base.NewBizErr("ssh sending combo output to webSocket failed"))
 | 
			
		||||
				}
 | 
			
		||||
				_, err = sws.logBuff.Write(bs)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					logs.Error("combo output to log buffer failed")
 | 
			
		||||
					//panic(base.NewBizErr("combo output to log buffer failed"))
 | 
			
		||||
				}
 | 
			
		||||
				sws.comboOutput.buffer.Reset()
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		case <-exitCh:
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (sws *LogicSshWsSession) Wait(quitChan chan bool) {
 | 
			
		||||
	if err := sws.session.Wait(); err != nil {
 | 
			
		||||
		logs.Error("ssh session wait failed")
 | 
			
		||||
		//panic(base.NewBizErr("ssh session wait failed"))
 | 
			
		||||
		setQuit(quitChan)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (sws *LogicSshWsSession) LogString() string {
 | 
			
		||||
	return sws.logBuff.buffer.String()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func setQuit(ch chan bool) {
 | 
			
		||||
	ch <- true
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										42
									
								
								main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								main.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,42 @@
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/astaxie/beego"
 | 
			
		||||
	"github.com/astaxie/beego/context"
 | 
			
		||||
	"github.com/astaxie/beego/orm"
 | 
			
		||||
	"github.com/astaxie/beego/plugins/cors"
 | 
			
		||||
	_ "github.com/go-sql-driver/mysql"
 | 
			
		||||
	_ "mayfly-go/routers"
 | 
			
		||||
	scheduler "mayfly-go/scheudler"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	orm.RegisterDriver("mysql", orm.DRMySQL)
 | 
			
		||||
 | 
			
		||||
	orm.RegisterDataBase("default", "mysql", "root:111049@tcp(localhost:3306)/mayfly-job?charset=utf8&loc=Local")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func main() {
 | 
			
		||||
	orm.Debug = true
 | 
			
		||||
	// 跨域配置
 | 
			
		||||
	beego.InsertFilter("/**", beego.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,
 | 
			
		||||
	}))
 | 
			
		||||
	scheduler.Start()
 | 
			
		||||
	defer scheduler.Stop()
 | 
			
		||||
	beego.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)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										3
									
								
								mayfly-go-front/.browserslistrc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								mayfly-go-front/.browserslistrc
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
> 1%
 | 
			
		||||
last 2 versions
 | 
			
		||||
not dead
 | 
			
		||||
							
								
								
									
										5
									
								
								mayfly-go-front/.env.development
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								mayfly-go-front/.env.development
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
			
		||||
# just a flag
 | 
			
		||||
ENV = 'development'
 | 
			
		||||
 | 
			
		||||
# base api
 | 
			
		||||
VUE_APP_BASE_API = 'http://localhost:8888/api'
 | 
			
		||||
							
								
								
									
										5
									
								
								mayfly-go-front/.env.production
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								mayfly-go-front/.env.production
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
			
		||||
# just a flag
 | 
			
		||||
ENV = 'production'
 | 
			
		||||
 | 
			
		||||
# base api
 | 
			
		||||
VUE_APP_BASE_API = 'http://localhost:8888/api'
 | 
			
		||||
							
								
								
									
										18
									
								
								mayfly-go-front/.eslintrc.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								mayfly-go-front/.eslintrc.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
			
		||||
module.exports = {
 | 
			
		||||
  root: true,
 | 
			
		||||
  env: {
 | 
			
		||||
    node: true
 | 
			
		||||
  },
 | 
			
		||||
  'extends': [
 | 
			
		||||
    'plugin:vue/essential',
 | 
			
		||||
    'eslint:recommended',
 | 
			
		||||
    '@vue/typescript/recommended'
 | 
			
		||||
  ],
 | 
			
		||||
  parserOptions: {
 | 
			
		||||
    ecmaVersion: 2020
 | 
			
		||||
  },
 | 
			
		||||
  rules: {
 | 
			
		||||
    'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
 | 
			
		||||
    'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										23
									
								
								mayfly-go-front/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								mayfly-go-front/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
			
		||||
.DS_Store
 | 
			
		||||
node_modules
 | 
			
		||||
/dist
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# local env files
 | 
			
		||||
.env.local
 | 
			
		||||
.env.*.local
 | 
			
		||||
 | 
			
		||||
# Log files
 | 
			
		||||
npm-debug.log*
 | 
			
		||||
yarn-debug.log*
 | 
			
		||||
yarn-error.log*
 | 
			
		||||
pnpm-debug.log*
 | 
			
		||||
 | 
			
		||||
# Editor directories and files
 | 
			
		||||
.idea
 | 
			
		||||
.vscode
 | 
			
		||||
*.suo
 | 
			
		||||
*.ntvs*
 | 
			
		||||
*.njsproj
 | 
			
		||||
*.sln
 | 
			
		||||
*.sw?
 | 
			
		||||
							
								
								
									
										24
									
								
								mayfly-go-front/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								mayfly-go-front/README.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
			
		||||
# mayfly-go-front
 | 
			
		||||
 | 
			
		||||
## Project setup
 | 
			
		||||
```
 | 
			
		||||
npm install
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Compiles and hot-reloads for development
 | 
			
		||||
```
 | 
			
		||||
npm run serve
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Compiles and minifies for production
 | 
			
		||||
```
 | 
			
		||||
npm run build
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Lints and fixes files
 | 
			
		||||
```
 | 
			
		||||
npm run lint
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Customize configuration
 | 
			
		||||
See [Configuration Reference](https://cli.vuejs.org/config/).
 | 
			
		||||
							
								
								
									
										5
									
								
								mayfly-go-front/babel.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								mayfly-go-front/babel.config.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
			
		||||
module.exports = {
 | 
			
		||||
  presets: [
 | 
			
		||||
    '@vue/cli-plugin-babel/preset'
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										12584
									
								
								mayfly-go-front/package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										12584
									
								
								mayfly-go-front/package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										41
									
								
								mayfly-go-front/package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								mayfly-go-front/package.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,41 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "mayfly-go-front",
 | 
			
		||||
  "version": "0.1.0",
 | 
			
		||||
  "private": true,
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "serve": "vue-cli-service serve",
 | 
			
		||||
    "build": "vue-cli-service build",
 | 
			
		||||
    "lint": "vue-cli-service lint"
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@types/echarts": "^4.6.4",
 | 
			
		||||
    "axios": "^0.19.2",
 | 
			
		||||
    "core-js": "^3.6.5",
 | 
			
		||||
    "echarts": "^4.8.0",
 | 
			
		||||
    "element-ui": "^2.13.2",
 | 
			
		||||
    "vue": "^2.6.11",
 | 
			
		||||
    "vue-class-component": "^7.2.3",
 | 
			
		||||
    "vue-property-decorator": "^8.4.2",
 | 
			
		||||
    "vue-router": "^3.2.0",
 | 
			
		||||
    "vuex": "^3.4.0"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@typescript-eslint/eslint-plugin": "^2.33.0",
 | 
			
		||||
    "@typescript-eslint/parser": "^2.33.0",
 | 
			
		||||
    "@vue/cli-plugin-babel": "~4.5.0",
 | 
			
		||||
    "@vue/cli-plugin-eslint": "~4.5.0",
 | 
			
		||||
    "@vue/cli-plugin-router": "~4.5.0",
 | 
			
		||||
    "@vue/cli-plugin-typescript": "~4.5.0",
 | 
			
		||||
    "@vue/cli-plugin-vuex": "~4.5.0",
 | 
			
		||||
    "@vue/cli-service": "~4.5.0",
 | 
			
		||||
    "@vue/eslint-config-typescript": "^5.0.2",
 | 
			
		||||
    "eslint": "^6.7.2",
 | 
			
		||||
    "eslint-plugin-vue": "^6.2.2",
 | 
			
		||||
    "typescript": "~3.9.3",
 | 
			
		||||
    "vue-template-compiler": "^2.6.11",
 | 
			
		||||
    "sass-resources-loader": "^2.0.3",
 | 
			
		||||
    "ts-import-plugin": "^1.6.6",
 | 
			
		||||
    "less": "^3.10.3",
 | 
			
		||||
    "less-loader": "^5.0.0"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								mayfly-go-front/public/favicon.ico
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								mayfly-go-front/public/favicon.ico
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 4.2 KiB  | 
							
								
								
									
										17
									
								
								mayfly-go-front/public/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								mayfly-go-front/public/index.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
			
		||||
<!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.0">
 | 
			
		||||
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
 | 
			
		||||
    <title><%= htmlWebpackPlugin.options.title %></title>
 | 
			
		||||
  </head>
 | 
			
		||||
  <body>
 | 
			
		||||
    <noscript>
 | 
			
		||||
      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
 | 
			
		||||
    </noscript>
 | 
			
		||||
    <div id="app"></div>
 | 
			
		||||
    <!-- built files will be auto injected -->
 | 
			
		||||
  </body>
 | 
			
		||||
</html>
 | 
			
		||||
							
								
								
									
										11
									
								
								mayfly-go-front/src/App.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								mayfly-go-front/src/App.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div id="app">
 | 
			
		||||
    <router-view/>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
#app {
 | 
			
		||||
   background-color: #222d32;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										75
									
								
								mayfly-go-front/src/assets/css/style.css
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										75
									
								
								mayfly-go-front/src/assets/css/style.css
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,75 @@
 | 
			
		||||
* {
 | 
			
		||||
    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-track {
 | 
			
		||||
    -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
 | 
			
		||||
    background-color: #F5F5F5;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
::-webkit-scrollbar-thumb {
 | 
			
		||||
    -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 /* .fade-leave-active below version 2.1.8 */ {
 | 
			
		||||
    opacity: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 元素无法被选择 */
 | 
			
		||||
.none-select {
 | 
			
		||||
    moz-user-select: -moz-none;
 | 
			
		||||
    -moz-user-select: none;
 | 
			
		||||
    -o-user-select: none;
 | 
			
		||||
    -khtml-user-select: none;
 | 
			
		||||
    -webkit-user-select: none;
 | 
			
		||||
    -ms-user-select: none;
 | 
			
		||||
    user-select: none;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
.toolbar {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    padding: 8px;
 | 
			
		||||
    background-color: #ffffff;
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
    line-height: 32px;
 | 
			
		||||
    border: 1px solid #e6ebf5;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.fl {
 | 
			
		||||
    float: left;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								mayfly-go-front/src/assets/images/favicon.ico
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								mayfly-go-front/src/assets/images/favicon.ico
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 17 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								mayfly-go-front/src/assets/images/logo.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								mayfly-go-front/src/assets/images/logo.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 38 KiB  | 
							
								
								
									
										77
									
								
								mayfly-go-front/src/common/Api.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								mayfly-go-front/src/common/Api.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,77 @@
 | 
			
		||||
import request from './request'
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 可用于各模块定义各自api请求
 | 
			
		||||
 */
 | 
			
		||||
class Api {
 | 
			
		||||
  /**
 | 
			
		||||
   * 请求url
 | 
			
		||||
   */
 | 
			
		||||
  url: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * 请求方法
 | 
			
		||||
   */
 | 
			
		||||
  method: string;
 | 
			
		||||
 | 
			
		||||
  constructor(url: string, method: string) {
 | 
			
		||||
    this.url = url;
 | 
			
		||||
    this.method = method;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * 设置rl
 | 
			
		||||
   * @param {String} uri 请求url
 | 
			
		||||
   */
 | 
			
		||||
  setUrl(url: string) {
 | 
			
		||||
    this.url = url;
 | 
			
		||||
    return this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * url的请求方法
 | 
			
		||||
   * @param {String} method 请求方法
 | 
			
		||||
   */
 | 
			
		||||
  setMethod(method: string) {
 | 
			
		||||
    this.method = method;
 | 
			
		||||
    return this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * 获取权限的完整url
 | 
			
		||||
   */
 | 
			
		||||
  getUrl() {
 | 
			
		||||
    return request.getApiUrl(this.url);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * 操作该权限,即请求对应的url
 | 
			
		||||
   * @param {Object} param 请求该权限的参数
 | 
			
		||||
   */
 | 
			
		||||
  request(param: any): Promise<any> {
 | 
			
		||||
    return request.send(this, param);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
   /**
 | 
			
		||||
   * 操作该权限,即请求对应的url
 | 
			
		||||
   * @param {Object} param 请求该权限的参数
 | 
			
		||||
   */
 | 
			
		||||
  requestWithHeaders(param: any, headers: any): Promise<any> {
 | 
			
		||||
    return request.sendWithHeaders(this, param, headers);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  /**    静态方法     **/
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * 静态工厂,返回Api对象,并设置url与method属性
 | 
			
		||||
   * @param url url
 | 
			
		||||
   * @param method 请求方法(get,post,put,delete...)
 | 
			
		||||
   */
 | 
			
		||||
  static create(url: string, method: string) {
 | 
			
		||||
    return new Api(url, method);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export default Api
 | 
			
		||||
							
								
								
									
										26
									
								
								mayfly-go-front/src/common/AuthUtils.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								mayfly-go-front/src/common/AuthUtils.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
			
		||||
export class AuthUtils {
 | 
			
		||||
 | 
			
		||||
    private static tokenName = 'token'
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 保存token
 | 
			
		||||
     * @param token token
 | 
			
		||||
     */
 | 
			
		||||
    static saveToken(token: string) {
 | 
			
		||||
        sessionStorage.setItem(this.tokenName, token)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取token
 | 
			
		||||
     */
 | 
			
		||||
    static getToken() {
 | 
			
		||||
        return sessionStorage.getItem(this.tokenName)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 移除token
 | 
			
		||||
     */
 | 
			
		||||
    static removeToken() {
 | 
			
		||||
        sessionStorage.removeItem(this.tokenName)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										5
									
								
								mayfly-go-front/src/common/config.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								mayfly-go-front/src/common/config.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
			
		||||
const config = {
 | 
			
		||||
    baseApiUrl: process.env.VUE_APP_BASE_API
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default config
 | 
			
		||||
							
								
								
									
										27
									
								
								mayfly-go-front/src/common/enums.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								mayfly-go-front/src/common/enums.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
			
		||||
interface BaseEnum {
 | 
			
		||||
  name: string
 | 
			
		||||
  value: any
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const success: BaseEnum = {
 | 
			
		||||
  name: 'success',
 | 
			
		||||
  value: 200
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export enum ResultEnum {
 | 
			
		||||
  SUCCESS = 200,
 | 
			
		||||
  ERROR = 400,
 | 
			
		||||
  PARAM_ERROR = 405,
 | 
			
		||||
  SERVER_ERROR = 500,
 | 
			
		||||
  NO_PERMISSION = 501
 | 
			
		||||
}
 | 
			
		||||
// /**
 | 
			
		||||
//  * 全局公共枚举类
 | 
			
		||||
//  */
 | 
			
		||||
// export default {
 | 
			
		||||
//   // uri请求方法
 | 
			
		||||
//   requestMethod: new Enum().add('GET', 'GET', 1).add('POST', 'POST', 2).add('PUT', 'PUT', 3).add('DELETE', 'DELETE', 4),
 | 
			
		||||
//   // 结果枚举
 | 
			
		||||
//   ResultEnum: new Enum().add('SUCCESS', '操作成功', 200).add('ERROR', '操作失败', 400).add('PARAM_ERROR', '参数错误', 405).add('SERVER_ERROR', '服务器异常', 500)
 | 
			
		||||
//     .add('NO_PERMISSION', '没有权限', 501)
 | 
			
		||||
// }
 | 
			
		||||
							
								
								
									
										105
									
								
								mayfly-go-front/src/common/filter/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								mayfly-go-front/src/common/filter/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,105 @@
 | 
			
		||||
/*
 | 
			
		||||
 * @Date: 2020-05-23 09:55:10
 | 
			
		||||
 * @LastEditors: JOU(wx: huzhen555)
 | 
			
		||||
 * @LastEditTime: 2020-05-27 15:34:15
 | 
			
		||||
 */ 
 | 
			
		||||
import { time2Date } from '@/common/util';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @description: 格式化时间过滤器
 | 
			
		||||
 * @author: JOU(wx: huzhen555)
 | 
			
		||||
 * @param {any} value 过滤器参数
 | 
			
		||||
 * @return: 转换后的参数
 | 
			
		||||
 */
 | 
			
		||||
function timeStr2Date(value: string) {
 | 
			
		||||
  return time2Date(value);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @description: 以一个分隔符替换为另一个分隔符,常用于数组字符串转换为某格式
 | 
			
		||||
 * @author: JOU(wx: huzhen555)
 | 
			
		||||
 * @param {any} value 过滤器参数
 | 
			
		||||
 * @return: 转换后的参数
 | 
			
		||||
 */
 | 
			
		||||
function replaceTag(value: string, newSep = '', oldSep = ',') {
 | 
			
		||||
  return value.replace(new RegExp(oldSep, 'g'), () => newSep);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @description: 字符串转数组
 | 
			
		||||
 * @author: JOU(wx: huzhen555)
 | 
			
		||||
 * @param {string} value 待转换字符串
 | 
			
		||||
 * @return: 转换后的数组
 | 
			
		||||
 */
 | 
			
		||||
function str2Ary(value: string, sep = ',') {
 | 
			
		||||
  return (value || '').split(sep);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @description: 按shopName(subName)格式化店名
 | 
			
		||||
 * @author: JOU(wx: huzhen555)
 | 
			
		||||
 * @param {string} value 待转化字符串
 | 
			
		||||
 * @return: 格式化后的店名
 | 
			
		||||
 */
 | 
			
		||||
function formatShopName(value: string, subName = '') {
 | 
			
		||||
  if (subName) {
 | 
			
		||||
    return `${value}(${subName})`;
 | 
			
		||||
  }
 | 
			
		||||
  return value;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export const vueFilters = {
 | 
			
		||||
  timeStr2Date, replaceTag, formatShopName,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// /**
 | 
			
		||||
//  * @description: 返回数据的格式化,如有些数据需要以逗号隔开转换成数组等
 | 
			
		||||
//  * @author: JOU(wx: huzhen555)
 | 
			
		||||
//  * @param {any}  data 格式化的数据
 | 
			
		||||
//  * @param {any} rules 转换规则,可对传入object,string,function
 | 
			
		||||
//  *                    object时data必需为array,格式为 { key1: ['filterName', 'arg1', 'arg2'], key2: function <= [自定义过滤器] }
 | 
			
		||||
//  *                    string时,表示某个过滤器的方法名
 | 
			
		||||
//  *                    function时,表示某个自定义过滤器
 | 
			
		||||
//  * @return: 转换后的数据
 | 
			
		||||
//  */
 | 
			
		||||
// const filterHandlers = { ...vueFilters, str2Ary };
 | 
			
		||||
// type TCustomerFilter = (...args: any[]) => any;
 | 
			
		||||
// type TRuleMap = IGeneralObject<[string, ...any[]]|TCustomerFilter>
 | 
			
		||||
// export function formatResp(data: any, rules: TRuleMap|TCustomerFilter|string) {
 | 
			
		||||
//   const ruleHandler = (rule: TCustomerFilter|string, dataItem: any, origin: any[]) => {
 | 
			
		||||
//     if (typeof rule === 'string' && typeof filterHandlers[rule] === 'function') {
 | 
			
		||||
//       dataItem = filterHandlers[rule](dataItem);
 | 
			
		||||
//     }
 | 
			
		||||
//     else if (Array.isArray(rule) && rule.length > 0 && typeof filterHandlers[rule[0]] === 'function') {
 | 
			
		||||
//       dataItem = filterHandlers[rule[0]].apply([dataItem, ...rule.slice(1)]);
 | 
			
		||||
//     }
 | 
			
		||||
//     else if (typeof rule  === 'function') {
 | 
			
		||||
//       dataItem = rule(dataItem, origin);
 | 
			
		||||
//     }
 | 
			
		||||
//     return dataItem;
 | 
			
		||||
//   }
 | 
			
		||||
  
 | 
			
		||||
  
 | 
			
		||||
//   if (Array.isArray(data)) {
 | 
			
		||||
//     if (data.length <= 0 || Object.keys(rules).length <= 0) {
 | 
			
		||||
//       return data;
 | 
			
		||||
//     }
 | 
			
		||||
 | 
			
		||||
//     return data.map(dataItem => {
 | 
			
		||||
//       rules = rules as TRuleMap;
 | 
			
		||||
//       for (let ruleKey in rules) {
 | 
			
		||||
//         let rule = rules[ruleKey];
 | 
			
		||||
//         dataItem[ruleKey] = ruleHandler(rule, dataItem[ruleKey], dataItem);
 | 
			
		||||
//       }
 | 
			
		||||
//       return dataItem;
 | 
			
		||||
//     });
 | 
			
		||||
//   }
 | 
			
		||||
//   else if (typeof rules === 'string' || typeof rules === 'function') {
 | 
			
		||||
//     return ruleHandler(rules, data, data);
 | 
			
		||||
//   }
 | 
			
		||||
//   else {
 | 
			
		||||
//     return data;
 | 
			
		||||
//   }
 | 
			
		||||
// }
 | 
			
		||||
							
								
								
									
										7
									
								
								mayfly-go-front/src/common/openApi.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								mayfly-go-front/src/common/openApi.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
			
		||||
import request from './request'
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  login: (param: any) => request.request('POST', '/accounts/login', param, null),
 | 
			
		||||
  captcha: () => request.request('GET', '/open/captcha', null, null),
 | 
			
		||||
  logout: (param: any) => request.request('POST', '/sys/accounts/logout/{token}', param, null)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										183
									
								
								mayfly-go-front/src/common/request.ts
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										183
									
								
								mayfly-go-front/src/common/request.ts
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,183 @@
 | 
			
		||||
import router from "../router";
 | 
			
		||||
import Axios from 'axios';
 | 
			
		||||
import { ResultEnum } from './enums'
 | 
			
		||||
import Api from './Api';
 | 
			
		||||
import { AuthUtils } from './AuthUtils'
 | 
			
		||||
import config from './config';
 | 
			
		||||
import ElementUI from 'element-ui';
 | 
			
		||||
 | 
			
		||||
export interface Result {
 | 
			
		||||
  /**
 | 
			
		||||
   * 响应码
 | 
			
		||||
   */
 | 
			
		||||
  code: number;
 | 
			
		||||
  /**
 | 
			
		||||
   * 响应消息
 | 
			
		||||
   */
 | 
			
		||||
  msg: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * 数据
 | 
			
		||||
   */
 | 
			
		||||
  data?: any;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const baseUrl = config.baseApiUrl
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 通知错误消息
 | 
			
		||||
 * @param msg 错误消息
 | 
			
		||||
 */
 | 
			
		||||
function notifyErrorMsg(msg: string) {
 | 
			
		||||
  // 危险通知
 | 
			
		||||
  ElementUI.Message.error(msg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// create an axios instance
 | 
			
		||||
const service = Axios.create({
 | 
			
		||||
  baseURL: baseUrl, // url = base url + request url
 | 
			
		||||
  timeout: 20000 // request timeout
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
// request interceptor
 | 
			
		||||
service.interceptors.request.use(
 | 
			
		||||
  config => {
 | 
			
		||||
    // do something before request is sent
 | 
			
		||||
    const token = AuthUtils.getToken()
 | 
			
		||||
    if (token) {
 | 
			
		||||
      // 设置token
 | 
			
		||||
      config.headers['Authorization'] = token
 | 
			
		||||
    }
 | 
			
		||||
    return config
 | 
			
		||||
  },
 | 
			
		||||
  error => {
 | 
			
		||||
    console.log(error) // for debug
 | 
			
		||||
    return Promise.reject(error)
 | 
			
		||||
  }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// response interceptor
 | 
			
		||||
service.interceptors.response.use(
 | 
			
		||||
  response => {
 | 
			
		||||
    // 获取请求返回结果
 | 
			
		||||
    const data: Result = response.data;
 | 
			
		||||
    // 如果提示没有权限,则移除token,使其重新登录
 | 
			
		||||
    if (data.code === ResultEnum.NO_PERMISSION) {
 | 
			
		||||
      AuthUtils.removeToken()
 | 
			
		||||
      notifyErrorMsg('登录超时')
 | 
			
		||||
      setTimeout(() => {
 | 
			
		||||
        router.push({
 | 
			
		||||
          path: '/login',
 | 
			
		||||
        });
 | 
			
		||||
      }, 1000)
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    if (data.code === ResultEnum.SUCCESS) {
 | 
			
		||||
      return data.data;
 | 
			
		||||
    } else {
 | 
			
		||||
      return Promise.reject(data);
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  (  error: any) => {
 | 
			
		||||
    return Promise.reject(error)
 | 
			
		||||
  }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @author: hml
 | 
			
		||||
 *
 | 
			
		||||
 * 将带有{id}的url替换为真实值;
 | 
			
		||||
 * 若restUrl:/category/{categoryId}/product/{productId}  param:{categoryId:1, productId:2}
 | 
			
		||||
 * 则返回 /category/1/product/2 的url
 | 
			
		||||
 */
 | 
			
		||||
function parseRestUrl(restUrl: string, param: any) {
 | 
			
		||||
  return restUrl.replace(/\{\w+\}/g, (word) => {
 | 
			
		||||
    const key = word.substring(1, word.length - 1);
 | 
			
		||||
    const value = param[key];
 | 
			
		||||
    if (value != null || value != undefined) {
 | 
			
		||||
      // delete param[key]
 | 
			
		||||
      return value;
 | 
			
		||||
    }
 | 
			
		||||
    return "";
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 请求uri
 | 
			
		||||
 * 该方法已处理请求结果中code != 200的message提示,如需其他错误处理(取消加载状态,重置对象状态等等),可catch继续处理
 | 
			
		||||
 * 
 | 
			
		||||
 * @param {Object} method 请求方法(GET,POST,PUT,DELTE等)
 | 
			
		||||
 * @param {Object} uri    uri
 | 
			
		||||
 * @param {Object} params 参数
 | 
			
		||||
 */
 | 
			
		||||
function request(method: string, url: string, params: any, headers: any): Promise<any> {
 | 
			
		||||
  if (!url)
 | 
			
		||||
    throw new Error('请求url不能为空');
 | 
			
		||||
  // 简单判断该url是否是restful风格
 | 
			
		||||
  if (url.indexOf("{") != -1) {
 | 
			
		||||
    url = parseRestUrl(url, params);
 | 
			
		||||
  }
 | 
			
		||||
  const query: any = {
 | 
			
		||||
    method,
 | 
			
		||||
    url: url,
 | 
			
		||||
  };
 | 
			
		||||
  if (headers) {
 | 
			
		||||
    query.headers = headers
 | 
			
		||||
  } 
 | 
			
		||||
  // else {
 | 
			
		||||
  //   query.headers = {}
 | 
			
		||||
  // }
 | 
			
		||||
  const lowMethod = method.toLowerCase();
 | 
			
		||||
  // const signKey = 'sd8mow3RPMDS0PMPmMP98AS2RG43T'
 | 
			
		||||
  // if (params) { 
 | 
			
		||||
  //   delete params.sign
 | 
			
		||||
  //   query.headers = headers || {}
 | 
			
		||||
  //   // query.headers.sign = md5(Object.keys(params).sort().map(key => `${key}=${params[key]}`).join('&') + signKey)
 | 
			
		||||
  // } else {
 | 
			
		||||
  //   query.headers = headers || {}
 | 
			
		||||
  //   query.headers.sign = {'sign': md5(signKey)}
 | 
			
		||||
  // }
 | 
			
		||||
  // post和put使用json格式传参
 | 
			
		||||
  if (lowMethod === 'post' || lowMethod === 'put') {
 | 
			
		||||
    query.data = params;
 | 
			
		||||
    // query.headers.sign = md5(JSON.stringify(params) + signKey)
 | 
			
		||||
  } else {
 | 
			
		||||
    query.params = params;
 | 
			
		||||
    // query.headers.sign = md5(Object.keys(params).sort().map(key => `${key}=${params[key]}`).join('&') + signKey)
 | 
			
		||||
  }
 | 
			
		||||
  return service.request(query).then(res => res)
 | 
			
		||||
    .catch(e => {
 | 
			
		||||
      notifyErrorMsg(e.msg || e.message)
 | 
			
		||||
      return Promise.reject(e);
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 根据api执行对应接口
 | 
			
		||||
 * @param api Api实例
 | 
			
		||||
 * @param params 请求参数
 | 
			
		||||
 */
 | 
			
		||||
function send(api: Api, params: any): Promise<any> {
 | 
			
		||||
  return request(api.method, api.url, params, null);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 根据api执行对应接口
 | 
			
		||||
 * @param api Api实例
 | 
			
		||||
 * @param params 请求参数
 | 
			
		||||
 */
 | 
			
		||||
function sendWithHeaders(api: Api, params: any, headers: any): Promise<any> {
 | 
			
		||||
  return request(api.method, api.url, params, headers);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getApiUrl(url: string) {
 | 
			
		||||
  // 只是返回api地址而不做请求,用在上传组件之类的
 | 
			
		||||
  return baseUrl + url + '?token=' + AuthUtils.getToken();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  request,
 | 
			
		||||
  send,
 | 
			
		||||
  sendWithHeaders,
 | 
			
		||||
  parseRestUrl,
 | 
			
		||||
  getApiUrl
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										96
									
								
								mayfly-go-front/src/common/util.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								mayfly-go-front/src/common/util.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,96 @@
 | 
			
		||||
/**
 | 
			
		||||
 * 时间字符串转成具体日期,数据库里的时间戳可直接传入转换
 | 
			
		||||
 * @author JOU
 | 
			
		||||
 * @time   2019-03-31T21:58:06+0800
 | 
			
		||||
 * @param  {number}                 timeStr 时间字符串
 | 
			
		||||
 * @return {string}                    转换后的具体时间日期
 | 
			
		||||
 */
 | 
			
		||||
export function time2Date(timeStr: string) {
 | 
			
		||||
  if (timeStr === '2100-01-01 00:00:00') {
 | 
			
		||||
    return '长期';
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  const
 | 
			
		||||
    ts = new Date(timeStr).getTime() / 1000,
 | 
			
		||||
    dateObj = new Date(),
 | 
			
		||||
    tsn = Date.parse(dateObj.toString()) / 1000,
 | 
			
		||||
    timeGap = tsn - ts,
 | 
			
		||||
    oneDayTs = 24 * 60 * 60,
 | 
			
		||||
    oneHourTs = 60 * 60,
 | 
			
		||||
    oneMinuteTs = 60,
 | 
			
		||||
    fillZero = (num: number) => num >= 0 && num < 10 ? ('0' + num) : num.toString(),
 | 
			
		||||
    getTimestamp = (dateObj: Date) => Date.parse(dateObj.toString()) / 1000;
 | 
			
		||||
 | 
			
		||||
  // 未来的时间1天后的,显示“xx天后”
 | 
			
		||||
  if (timeGap < -oneDayTs) {
 | 
			
		||||
    return Math.floor(-timeGap / oneDayTs) + '天后';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 未来不到一天的时间,显示“xx小时后”
 | 
			
		||||
  if (timeGap > -oneDayTs && timeGap < -oneHourTs) {
 | 
			
		||||
    return Math.floor(-timeGap / oneHourTs) + '小时后';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 未来不到一小时的时间,显示“xx分钟后”
 | 
			
		||||
  if (timeGap > -oneHourTs && timeGap < 0) {
 | 
			
		||||
    return Math.floor(-timeGap / oneMinuteTs) + '小时后';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 十分钟前返回“刚刚”
 | 
			
		||||
  if (timeGap < (oneMinuteTs * 10)) {
 | 
			
		||||
    return '刚刚';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 一小时前显示“xx分钟前”
 | 
			
		||||
  if (timeGap < oneHourTs) {
 | 
			
		||||
    return `${Math.floor(timeGap / oneMinuteTs)}分钟前`;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 当天的显示”xx小时前“
 | 
			
		||||
  dateObj.setHours(0, 0, 0, 0);
 | 
			
		||||
  if (timeGap < tsn - getTimestamp(dateObj)) {
 | 
			
		||||
    return `${Math.floor(timeGap / oneHourTs)}小时前`;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 昨天显示”昨天 xx:xx“
 | 
			
		||||
  const
 | 
			
		||||
    date = dateObj.getDate(),
 | 
			
		||||
    d = new Date(ts * 1000);
 | 
			
		||||
  dateObj.setDate(date - 1);
 | 
			
		||||
  if (timeGap < tsn - getTimestamp(dateObj)) {
 | 
			
		||||
    return `昨天 ${fillZero(d.getHours())}:${fillZero(d.getMinutes())}`;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 前天显示”前天 xx:xx“
 | 
			
		||||
  dateObj.setDate(date - 2);
 | 
			
		||||
  if (timeGap < tsn - getTimestamp(dateObj)) {
 | 
			
		||||
    return `前天 ${fillZero(d.getHours())}:${fillZero(d.getMinutes())}`;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 这周显示”这周x xx:xx“
 | 
			
		||||
  // 因为上面减了两天,需设置回去
 | 
			
		||||
  dateObj.setDate(date);
 | 
			
		||||
  let currentDay = dateObj.getDay(), day = d.getDay();
 | 
			
		||||
  const weeks = [ '一', '二', '三', '四', '五', '六', '天' ];
 | 
			
		||||
 | 
			
		||||
  currentDay = currentDay === 0 ? 7 : currentDay;
 | 
			
		||||
  day = day === 0 ? 7 : day;
 | 
			
		||||
  dateObj.setDate(date - currentDay + 1);
 | 
			
		||||
  if (timeGap < tsn - getTimestamp(dateObj)) {
 | 
			
		||||
    return `这周${weeks[day - 1]} ${fillZero(d.getHours())}:${fillZero(d.getMinutes())}`;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 上周显示”上周x xx:xx“
 | 
			
		||||
  dateObj.setDate(date - 6 - currentDay);
 | 
			
		||||
  if (timeGap < tsn - getTimestamp(dateObj)) {
 | 
			
		||||
    return `上周${weeks[day - 1]} ${fillZero(d.getHours())}:${fillZero(d.getMinutes())}`;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 今年再往前的日期则显示”xx-xx xx:xx“(表示xx月xx日 xx点xx分)
 | 
			
		||||
  dateObj.setMonth(0, 1);
 | 
			
		||||
  if (timeGap < tsn - getTimestamp(dateObj)) {
 | 
			
		||||
    return `${fillZero(d.getMonth() + 1)}-${fillZero(d.getDate())} ${fillZero(d.getHours())}:${fillZero(d.getMinutes())}`;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return `${d.getFullYear()}-${fillZero(d.getMonth() + 1)}-${fillZero(d.getDate())} ${fillZero(d.getHours())}:${fillZero(d.getMinutes())}`;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										64
									
								
								mayfly-go-front/src/components/chart/ActivePlate.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								mayfly-go-front/src/components/chart/ActivePlate.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,64 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="active-plate-main">
 | 
			
		||||
    <ul class="active-list">
 | 
			
		||||
      <li class="item" v-for="item in infoList" :key="item.title">
 | 
			
		||||
        <p class="num" :style="{color:item.color}">{{item.count}}</p>
 | 
			
		||||
        <p class="desc">{{item.title}}</p>
 | 
			
		||||
      </li>
 | 
			
		||||
    </ul>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
export default {
 | 
			
		||||
  name: 'activePlate',
 | 
			
		||||
  components: {},
 | 
			
		||||
  props: {
 | 
			
		||||
    // 需要展示的数据集合
 | 
			
		||||
    infoList: {
 | 
			
		||||
      type: Array,
 | 
			
		||||
      require: true,
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="less">
 | 
			
		||||
.active-plate-main {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 130px;
 | 
			
		||||
  .active-list {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    list-style: none;
 | 
			
		||||
    padding-top: 15px;
 | 
			
		||||
    .item {
 | 
			
		||||
      position: relative;
 | 
			
		||||
      flex: 1;
 | 
			
		||||
      text-align: center;
 | 
			
		||||
      .num {
 | 
			
		||||
        font-size: 42px;
 | 
			
		||||
        font-weight: bold;
 | 
			
		||||
        font-family: sans-serif;
 | 
			
		||||
      }
 | 
			
		||||
      .desc {
 | 
			
		||||
        font-size: 16px;
 | 
			
		||||
      }
 | 
			
		||||
      &::after {
 | 
			
		||||
        position: absolute;
 | 
			
		||||
        top: 18px;
 | 
			
		||||
        right: 0;
 | 
			
		||||
        content: '';
 | 
			
		||||
        display: block;
 | 
			
		||||
        width: 1px;
 | 
			
		||||
        height: 56px;
 | 
			
		||||
        background: #e7eef0;
 | 
			
		||||
      }
 | 
			
		||||
      &:nth-last-of-type(1) {
 | 
			
		||||
        &::after {
 | 
			
		||||
          background: none;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										39
									
								
								mayfly-go-front/src/components/chart/BaseChart.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								mayfly-go-front/src/components/chart/BaseChart.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,39 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="base-chart" id="box" ref="dom"></div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import echarts from 'echarts'
 | 
			
		||||
import tdTheme from './theme.json'
 | 
			
		||||
import { on, off } from './onoff'
 | 
			
		||||
echarts.registerTheme('tdTheme', tdTheme)
 | 
			
		||||
export default {
 | 
			
		||||
  props: {
 | 
			
		||||
    option: Object,
 | 
			
		||||
  },
 | 
			
		||||
  mounted() {
 | 
			
		||||
    this.initChart()
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    resize() {
 | 
			
		||||
      this.dom.resize()
 | 
			
		||||
    },
 | 
			
		||||
    initChart() {
 | 
			
		||||
      this.$nextTick(() => {
 | 
			
		||||
        this.dom = echarts.init(this.$refs.dom, 'tdTheme')
 | 
			
		||||
        this.dom.setOption(this.option)
 | 
			
		||||
        on(window, 'resize', this.resize)
 | 
			
		||||
      })
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style>
 | 
			
		||||
.base-chart {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 360px;
 | 
			
		||||
  padding: 28px;
 | 
			
		||||
  background: #fff;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										43
									
								
								mayfly-go-front/src/components/chart/Card.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								mayfly-go-front/src/components/chart/Card.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,43 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="card-main">
 | 
			
		||||
    <div class="title">
 | 
			
		||||
      {{title}}
 | 
			
		||||
      <span>{{desc}}</span>
 | 
			
		||||
    </div>
 | 
			
		||||
    <slot></slot>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
export default {
 | 
			
		||||
  props: {
 | 
			
		||||
    title: {
 | 
			
		||||
      type: String,
 | 
			
		||||
      default: '标题'
 | 
			
		||||
    },
 | 
			
		||||
    desc: {
 | 
			
		||||
      type: String,
 | 
			
		||||
      default: '描述'
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang='less'>
 | 
			
		||||
.card-main {
 | 
			
		||||
  border-radius: 8px;
 | 
			
		||||
  background: #fff;
 | 
			
		||||
  margin-bottom: 20px;
 | 
			
		||||
  padding-bottom: 10px;
 | 
			
		||||
}
 | 
			
		||||
.title {
 | 
			
		||||
  color: #060606;
 | 
			
		||||
  font-size: 16px;
 | 
			
		||||
  padding: 20px 32px;
 | 
			
		||||
  span {
 | 
			
		||||
    padding-left: 17px;
 | 
			
		||||
    font-size: 12px;
 | 
			
		||||
    color: #dededf;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										138
									
								
								mayfly-go-front/src/components/chart/ChartBar.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										138
									
								
								mayfly-go-front/src/components/chart/ChartBar.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,138 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="bar-main" id="box" ref="dom"></div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import echarts from 'echarts'
 | 
			
		||||
import tdTheme from './theme.json'
 | 
			
		||||
import { on, off } from './onoff'
 | 
			
		||||
echarts.registerTheme('tdTheme', tdTheme)
 | 
			
		||||
export default {
 | 
			
		||||
  props: {
 | 
			
		||||
    value: Object,
 | 
			
		||||
    text: String,
 | 
			
		||||
    subtext: String
 | 
			
		||||
  },
 | 
			
		||||
  mounted() {
 | 
			
		||||
    this.initChart()
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    resize() {
 | 
			
		||||
      this.dom.resize()
 | 
			
		||||
    },
 | 
			
		||||
    initChart() {
 | 
			
		||||
      this.$nextTick(() => {
 | 
			
		||||
        const xAxisData = Object.keys(this.value)
 | 
			
		||||
        const seriesData = Object.values(this.value)
 | 
			
		||||
        const option = {
 | 
			
		||||
          grid: {
 | 
			
		||||
            left: '1%',
 | 
			
		||||
            right: '1%',
 | 
			
		||||
            top: '2%',
 | 
			
		||||
            bottom: '1%',
 | 
			
		||||
            containLabel: true
 | 
			
		||||
          },
 | 
			
		||||
          title: {
 | 
			
		||||
            text: this.text,
 | 
			
		||||
            subtext: this.subtext,
 | 
			
		||||
            x: 'center'
 | 
			
		||||
          },
 | 
			
		||||
          tooltip: {
 | 
			
		||||
            trigger: 'item',
 | 
			
		||||
            formatter: '{c}人',
 | 
			
		||||
            // position: ['30%', '90%'],
 | 
			
		||||
            position: 'top',
 | 
			
		||||
            backgroundColor: '#FAFBFE',
 | 
			
		||||
            textStyle: {
 | 
			
		||||
              fontSize: 14,
 | 
			
		||||
              color: '#6d6d6d'
 | 
			
		||||
            }
 | 
			
		||||
          },
 | 
			
		||||
          xAxis: {
 | 
			
		||||
            // show: false,
 | 
			
		||||
            type: 'category',
 | 
			
		||||
            data: xAxisData,
 | 
			
		||||
            splitLine: {
 | 
			
		||||
              show: false
 | 
			
		||||
            }
 | 
			
		||||
          },
 | 
			
		||||
          yAxis: [
 | 
			
		||||
            {
 | 
			
		||||
              // show: false,
 | 
			
		||||
              type: 'value',
 | 
			
		||||
              splitLine: {
 | 
			
		||||
                show: true,
 | 
			
		||||
                lineStyle: {
 | 
			
		||||
                  // 设置刻度线粗度(粗的宽度)
 | 
			
		||||
                  width: 1,
 | 
			
		||||
                  // 颜色数组,数组数量要比刻度线数量大才能不循环使用
 | 
			
		||||
                  color: [
 | 
			
		||||
                    'rgba(0, 0, 0, 0)',
 | 
			
		||||
                    '#eee',
 | 
			
		||||
                    '#eee',
 | 
			
		||||
                    '#eee',
 | 
			
		||||
                    '#eee',
 | 
			
		||||
                    '#eee',
 | 
			
		||||
                    '#eee',
 | 
			
		||||
                    '#eee',
 | 
			
		||||
                    '#eee'
 | 
			
		||||
                  ]
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
          ],
 | 
			
		||||
          series: [
 | 
			
		||||
            {
 | 
			
		||||
              data: seriesData,
 | 
			
		||||
              type: 'bar',
 | 
			
		||||
              barWidth: 36,
 | 
			
		||||
              areaStyle: {
 | 
			
		||||
                normal: {
 | 
			
		||||
                  color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
 | 
			
		||||
                    { offset: 0, color: '#f2f5ff' },
 | 
			
		||||
                    { offset: 1, color: '#fff' }
 | 
			
		||||
                  ])
 | 
			
		||||
                }
 | 
			
		||||
              },
 | 
			
		||||
              itemStyle: {
 | 
			
		||||
                normal: {
 | 
			
		||||
                  barBorderRadius: [50],
 | 
			
		||||
                  color: new echarts.graphic.LinearGradient(
 | 
			
		||||
                    0,
 | 
			
		||||
                    1,
 | 
			
		||||
                    0,
 | 
			
		||||
                    0,
 | 
			
		||||
                    [
 | 
			
		||||
                      {
 | 
			
		||||
                        offset: 0,
 | 
			
		||||
                        color: '#3AA1FF' // 0% 处的颜色
 | 
			
		||||
                      },
 | 
			
		||||
                      {
 | 
			
		||||
                        offset: 1,
 | 
			
		||||
                        color: '#36CBCB' // 100% 处的颜色
 | 
			
		||||
                      }
 | 
			
		||||
                    ],
 | 
			
		||||
                    false
 | 
			
		||||
                  )
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
          ]
 | 
			
		||||
        }
 | 
			
		||||
        this.dom = echarts.init(this.$refs.dom, 'tdTheme')
 | 
			
		||||
        this.dom.setOption(option)
 | 
			
		||||
        on(window, 'resize', this.resize)
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style>
 | 
			
		||||
.bar-main {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 360px;
 | 
			
		||||
  padding: 28px;
 | 
			
		||||
  background: #fff;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										92
									
								
								mayfly-go-front/src/components/chart/ChartContinuou.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								mayfly-go-front/src/components/chart/ChartContinuou.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,92 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="line-main" id="box" ref="dom"></div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import echarts from 'echarts'
 | 
			
		||||
import tdTheme from './theme.json'
 | 
			
		||||
import { on, off } from './onoff'
 | 
			
		||||
echarts.registerTheme('tdTheme', tdTheme)
 | 
			
		||||
export default {
 | 
			
		||||
  props: {
 | 
			
		||||
    value: Array,
 | 
			
		||||
    title: String,
 | 
			
		||||
    subtext: String,
 | 
			
		||||
  },
 | 
			
		||||
  mounted() {
 | 
			
		||||
    this.initChart()
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    resize() {
 | 
			
		||||
      this.dom.resize()
 | 
			
		||||
    },
 | 
			
		||||
    initChart() {
 | 
			
		||||
      this.$nextTick(() => {
 | 
			
		||||
        const dateList = this.value.map(function (item) {
 | 
			
		||||
          return item[0]
 | 
			
		||||
        })
 | 
			
		||||
        const valueList = this.value.map(function (item) {
 | 
			
		||||
          return item[1]
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        const option = {
 | 
			
		||||
          // Make gradient line here
 | 
			
		||||
          visualMap: [
 | 
			
		||||
            {
 | 
			
		||||
              show: false,
 | 
			
		||||
              type: 'continuous',
 | 
			
		||||
              seriesIndex: 0,
 | 
			
		||||
              min: 0,
 | 
			
		||||
              max: 400,
 | 
			
		||||
            }
 | 
			
		||||
          ],
 | 
			
		||||
 | 
			
		||||
          title: [
 | 
			
		||||
            {
 | 
			
		||||
              left: 'center',
 | 
			
		||||
              text: this.title,
 | 
			
		||||
            }
 | 
			
		||||
          ],
 | 
			
		||||
          tooltip: {
 | 
			
		||||
            trigger: 'axis',
 | 
			
		||||
          },
 | 
			
		||||
          xAxis: [
 | 
			
		||||
            {
 | 
			
		||||
              data: dateList,
 | 
			
		||||
            }
 | 
			
		||||
          ],
 | 
			
		||||
          yAxis: [
 | 
			
		||||
            {
 | 
			
		||||
              splitLine: { show: false },
 | 
			
		||||
            },
 | 
			
		||||
          ],
 | 
			
		||||
          grid: [
 | 
			
		||||
            {
 | 
			
		||||
              
 | 
			
		||||
            },
 | 
			
		||||
          ],
 | 
			
		||||
          series: [
 | 
			
		||||
            {
 | 
			
		||||
              type: 'line',
 | 
			
		||||
              showSymbol: false,
 | 
			
		||||
              data: valueList,
 | 
			
		||||
            },
 | 
			
		||||
          ],
 | 
			
		||||
        }
 | 
			
		||||
        this.dom = echarts.init(this.$refs.dom, 'tdTheme')
 | 
			
		||||
        this.dom.setOption(option)
 | 
			
		||||
        on(window, 'resize', this.resize)
 | 
			
		||||
      })
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style>
 | 
			
		||||
.line-main {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 360px;
 | 
			
		||||
  padding: 28px;
 | 
			
		||||
  background: #fff;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										104
									
								
								mayfly-go-front/src/components/chart/ChartFunnel.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								mayfly-go-front/src/components/chart/ChartFunnel.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,104 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="funnel-main" id="box" ref="dom"></div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import echarts from 'echarts'
 | 
			
		||||
import tdTheme from './theme.json'
 | 
			
		||||
import { on, off } from './onoff'
 | 
			
		||||
echarts.registerTheme('tdTheme', tdTheme)
 | 
			
		||||
export default {
 | 
			
		||||
  props: {
 | 
			
		||||
    value: Array,
 | 
			
		||||
    text: String,
 | 
			
		||||
    subtext: String
 | 
			
		||||
  },
 | 
			
		||||
  mounted() {
 | 
			
		||||
    this.initChart()
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    resize() {
 | 
			
		||||
      this.dom.resize()
 | 
			
		||||
    },
 | 
			
		||||
    initChart() {
 | 
			
		||||
      this.$nextTick(() => {
 | 
			
		||||
        const legend = this.value.map(_ => _.name)
 | 
			
		||||
        const option = {
 | 
			
		||||
          grid: {
 | 
			
		||||
            left: '1%',
 | 
			
		||||
            right: '1%',
 | 
			
		||||
            top: '2%',
 | 
			
		||||
            bottom: '1%',
 | 
			
		||||
            containLabel: true
 | 
			
		||||
          },
 | 
			
		||||
          title: {
 | 
			
		||||
            text: this.text,
 | 
			
		||||
            subtext: this.subtext,
 | 
			
		||||
            x: 'center'
 | 
			
		||||
          },
 | 
			
		||||
          tooltip: {
 | 
			
		||||
            show: false,
 | 
			
		||||
            trigger: 'item',
 | 
			
		||||
            formatter: '{c} ({d}%)',
 | 
			
		||||
            // position: ['30%', '90%'],
 | 
			
		||||
            position: 'right',
 | 
			
		||||
            backgroundColor: 'transparent',
 | 
			
		||||
            textStyle: {
 | 
			
		||||
              fontSize: 14,
 | 
			
		||||
              color: '#666'
 | 
			
		||||
            }
 | 
			
		||||
          },
 | 
			
		||||
          legend: {
 | 
			
		||||
            orient: 'vertical',
 | 
			
		||||
            left: 'right',
 | 
			
		||||
            bottom: 0,
 | 
			
		||||
 | 
			
		||||
            // data: legend,
 | 
			
		||||
            backgroundColor: 'transparent',
 | 
			
		||||
            icon: 'circle'
 | 
			
		||||
          },
 | 
			
		||||
          series: [
 | 
			
		||||
            {
 | 
			
		||||
              name: '访问来源',
 | 
			
		||||
              type: 'funnel',
 | 
			
		||||
              radius: ['50%', '65%'],
 | 
			
		||||
              avoidLabelOverlap: false,
 | 
			
		||||
              label: {
 | 
			
		||||
                normal: {
 | 
			
		||||
                  show: false,
 | 
			
		||||
                  position: 'right',
 | 
			
		||||
                  formatter: '{c} ({d}%)'
 | 
			
		||||
                }
 | 
			
		||||
              },
 | 
			
		||||
              //   labelLine: {
 | 
			
		||||
              //     normal: {
 | 
			
		||||
              //       show: false
 | 
			
		||||
              //     }
 | 
			
		||||
              //   },
 | 
			
		||||
              data: [
 | 
			
		||||
                { value: 400, name: '交易完成' },
 | 
			
		||||
                { value: 300, name: '支付订单' },
 | 
			
		||||
                { value: 200, name: '生成订单' },
 | 
			
		||||
                { value: 100, name: '放入购物车' },
 | 
			
		||||
                { value: 100, name: '浏览网站' }
 | 
			
		||||
              ]
 | 
			
		||||
            }
 | 
			
		||||
          ]
 | 
			
		||||
        }
 | 
			
		||||
        this.dom = echarts.init(this.$refs.dom, 'tdTheme')
 | 
			
		||||
        this.dom.setOption(option)
 | 
			
		||||
        on(window, 'resize', this.resize)
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style>
 | 
			
		||||
.funnel-main {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 295px;
 | 
			
		||||
  padding: 28px;
 | 
			
		||||
  background: #fff;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										89
									
								
								mayfly-go-front/src/components/chart/ChartGauge.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								mayfly-go-front/src/components/chart/ChartGauge.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,89 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="gauge-main" id="box" ref="dom"></div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import echarts from 'echarts'
 | 
			
		||||
import tdTheme from './theme.json'
 | 
			
		||||
import { on, off } from './onoff'
 | 
			
		||||
echarts.registerTheme('tdTheme', tdTheme)
 | 
			
		||||
export default {
 | 
			
		||||
  props: {
 | 
			
		||||
    value: Object,
 | 
			
		||||
    text: String,
 | 
			
		||||
    subtext: String
 | 
			
		||||
  },
 | 
			
		||||
  mounted() {
 | 
			
		||||
    this.initChart()
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    resize() {
 | 
			
		||||
      this.dom.resize()
 | 
			
		||||
    },
 | 
			
		||||
    initChart() {
 | 
			
		||||
      this.$nextTick(() => {
 | 
			
		||||
        const option = {
 | 
			
		||||
          grid: {
 | 
			
		||||
            left: 0,
 | 
			
		||||
            right: 0,
 | 
			
		||||
            top: 0,
 | 
			
		||||
            bottom: 0
 | 
			
		||||
            // containLabel: true
 | 
			
		||||
          },
 | 
			
		||||
          tooltip: {
 | 
			
		||||
            formatter: '{a} <br/>{b} : {c}%'
 | 
			
		||||
          },
 | 
			
		||||
          toolbox: {},
 | 
			
		||||
          series: [
 | 
			
		||||
            {
 | 
			
		||||
              name: '业务指标',
 | 
			
		||||
              startAngle: 195,
 | 
			
		||||
              endAngle: -15,
 | 
			
		||||
              axisLine: {
 | 
			
		||||
                show: true,
 | 
			
		||||
                lineStyle: {
 | 
			
		||||
                  color: [
 | 
			
		||||
                    [0.6, '#4ECB73'],
 | 
			
		||||
                    [0.8, '#FBD437'],
 | 
			
		||||
                    [1, '#F47F92']
 | 
			
		||||
                  ],
 | 
			
		||||
                  width: 16
 | 
			
		||||
                }
 | 
			
		||||
              },
 | 
			
		||||
              pointer: {
 | 
			
		||||
                length: '80%',
 | 
			
		||||
                width: 3,
 | 
			
		||||
                color: 'auto'
 | 
			
		||||
              },
 | 
			
		||||
              axisTick: {
 | 
			
		||||
                show: false
 | 
			
		||||
              },
 | 
			
		||||
              splitLine: { show: false },
 | 
			
		||||
              type: 'gauge',
 | 
			
		||||
              detail: {
 | 
			
		||||
                formatter: '{value}%',
 | 
			
		||||
                textStyle: {
 | 
			
		||||
                  color: '#595959',
 | 
			
		||||
                  fontSize: 32
 | 
			
		||||
                }
 | 
			
		||||
              },
 | 
			
		||||
              data: [{ value: 10 }]
 | 
			
		||||
            }
 | 
			
		||||
          ]
 | 
			
		||||
        }
 | 
			
		||||
        this.dom = echarts.init(this.$refs.dom, 'tdTheme')
 | 
			
		||||
        this.dom.setOption(option)
 | 
			
		||||
        on(window, 'resize', this.resize)
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style>
 | 
			
		||||
.gauge-main {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 360px;
 | 
			
		||||
  background: #fff;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										121
									
								
								mayfly-go-front/src/components/chart/ChartLine.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								mayfly-go-front/src/components/chart/ChartLine.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,121 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="line-main" id="box" ref="dom"></div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import echarts from 'echarts'
 | 
			
		||||
import tdTheme from './theme.json'
 | 
			
		||||
import { on, off } from './onoff'
 | 
			
		||||
echarts.registerTheme('tdTheme', tdTheme)
 | 
			
		||||
export default {
 | 
			
		||||
  props: {
 | 
			
		||||
    value: Object,
 | 
			
		||||
    text: String,
 | 
			
		||||
    subtext: String
 | 
			
		||||
  },
 | 
			
		||||
  mounted() {
 | 
			
		||||
    this.initChart()
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    resize() {
 | 
			
		||||
      this.dom.resize()
 | 
			
		||||
    },
 | 
			
		||||
    initChart() {
 | 
			
		||||
      this.$nextTick(() => {
 | 
			
		||||
        const xAxisData = Object.keys(this.value)
 | 
			
		||||
        const seriesData = Object.values(this.value)
 | 
			
		||||
        const option = {
 | 
			
		||||
          grid: {
 | 
			
		||||
            left: '1%',
 | 
			
		||||
            right: '1%',
 | 
			
		||||
            top: '2%',
 | 
			
		||||
            bottom: '1%',
 | 
			
		||||
            containLabel: true
 | 
			
		||||
          },
 | 
			
		||||
          title: {
 | 
			
		||||
            text: this.text,
 | 
			
		||||
            subtext: this.subtext,
 | 
			
		||||
            x: 'center'
 | 
			
		||||
          },
 | 
			
		||||
          tooltip: {
 | 
			
		||||
            trigger: 'item',
 | 
			
		||||
            formatter: '{c}人',
 | 
			
		||||
            // position: ['30%', '90%'],
 | 
			
		||||
            position: 'top',
 | 
			
		||||
            backgroundColor: '#387DE1',
 | 
			
		||||
            textStyle: {
 | 
			
		||||
              fontSize: 18,
 | 
			
		||||
              color: '#fff'
 | 
			
		||||
            }
 | 
			
		||||
          },
 | 
			
		||||
          xAxis: {
 | 
			
		||||
            // show: false,
 | 
			
		||||
            type: 'category',
 | 
			
		||||
            data: xAxisData,
 | 
			
		||||
            splitLine: {
 | 
			
		||||
              show: false
 | 
			
		||||
            }
 | 
			
		||||
          },
 | 
			
		||||
          yAxis: [
 | 
			
		||||
            {
 | 
			
		||||
              // show: false,
 | 
			
		||||
              type: 'value',
 | 
			
		||||
              splitLine: {
 | 
			
		||||
                show: true,
 | 
			
		||||
                lineStyle: {
 | 
			
		||||
                  // 设置刻度线粗度(粗的宽度)
 | 
			
		||||
                  width: 1,
 | 
			
		||||
                  // 颜色数组,数组数量要比刻度线数量大才能不循环使用
 | 
			
		||||
                  color: [
 | 
			
		||||
                    'rgba(0, 0, 0, 0)',
 | 
			
		||||
                    '#eee',
 | 
			
		||||
                    '#eee',
 | 
			
		||||
                    '#eee',
 | 
			
		||||
                    '#eee',
 | 
			
		||||
                    '#eee',
 | 
			
		||||
                    '#eee',
 | 
			
		||||
                    '#eee',
 | 
			
		||||
                    '#eee'
 | 
			
		||||
                  ]
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
          ],
 | 
			
		||||
          series: [
 | 
			
		||||
            {
 | 
			
		||||
              data: seriesData,
 | 
			
		||||
              type: 'line',
 | 
			
		||||
              areaStyle: {
 | 
			
		||||
                normal: {
 | 
			
		||||
                  color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
 | 
			
		||||
                    { offset: 0, color: '#f2f5ff' },
 | 
			
		||||
                    { offset: 1, color: '#fff' }
 | 
			
		||||
                  ])
 | 
			
		||||
                }
 | 
			
		||||
              },
 | 
			
		||||
              lineStyle: {
 | 
			
		||||
                normal: {
 | 
			
		||||
                  width: 5,
 | 
			
		||||
                  color: '#36CBCB'
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
          ]
 | 
			
		||||
        }
 | 
			
		||||
        this.dom = echarts.init(this.$refs.dom, 'tdTheme')
 | 
			
		||||
        this.dom.setOption(option)
 | 
			
		||||
        on(window, 'resize', this.resize)
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style>
 | 
			
		||||
.line-main {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 360px;
 | 
			
		||||
  padding: 28px;
 | 
			
		||||
  background: #fff;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										111
									
								
								mayfly-go-front/src/components/chart/ChartPie.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								mayfly-go-front/src/components/chart/ChartPie.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,111 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="pie-main" id="box" ref="dom"></div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import echarts from 'echarts'
 | 
			
		||||
import tdTheme from './theme.json'
 | 
			
		||||
import { on, off } from './onoff'
 | 
			
		||||
echarts.registerTheme('tdTheme', tdTheme)
 | 
			
		||||
export default {
 | 
			
		||||
  props: {
 | 
			
		||||
    value: Array,
 | 
			
		||||
    text: String,
 | 
			
		||||
    subtext: String,
 | 
			
		||||
  },
 | 
			
		||||
  watch: {
 | 
			
		||||
    value: {
 | 
			
		||||
      handler: function (val, oldval) {
 | 
			
		||||
        this.value = val
 | 
			
		||||
        this.initChart()
 | 
			
		||||
      },
 | 
			
		||||
      deep: true, //对象内部的属性监听,也叫深度监听
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  mounted() {
 | 
			
		||||
    this.initChart()
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    resize() {
 | 
			
		||||
      this.dom.resize()
 | 
			
		||||
    },
 | 
			
		||||
    initChart() {
 | 
			
		||||
      this.$nextTick(() => {
 | 
			
		||||
        const legend = this.value.map((_) => _.name)
 | 
			
		||||
        const option = {
 | 
			
		||||
          title: {
 | 
			
		||||
            text: this.text,
 | 
			
		||||
            subtext: this.subtext,
 | 
			
		||||
            x: 'center',
 | 
			
		||||
          },
 | 
			
		||||
          position: {
 | 
			
		||||
            top: 40,
 | 
			
		||||
          },
 | 
			
		||||
          tooltip: {
 | 
			
		||||
            trigger: 'item',
 | 
			
		||||
            formatter: '{c} ({d}%)',
 | 
			
		||||
            // position: ['30%', '90%'],
 | 
			
		||||
            position: function (point, params, dom, rect, size) {
 | 
			
		||||
              console.log(size)
 | 
			
		||||
              const leftWidth = size.viewSize[0] / 2 - size.contentSize[0] / 2
 | 
			
		||||
              console.log(leftWidth)
 | 
			
		||||
              return { left: leftWidth, bottom: 0 }
 | 
			
		||||
            },
 | 
			
		||||
            backgroundColor: 'transparent',
 | 
			
		||||
            textStyle: {
 | 
			
		||||
              fontSize: 24,
 | 
			
		||||
              color: '#666',
 | 
			
		||||
            },
 | 
			
		||||
          },
 | 
			
		||||
          legend: {
 | 
			
		||||
            // orient: 'vertical',
 | 
			
		||||
            top: 0,
 | 
			
		||||
            data: legend,
 | 
			
		||||
            backgroundColor: 'transparent',
 | 
			
		||||
            icon: 'circle',
 | 
			
		||||
          },
 | 
			
		||||
          series: [
 | 
			
		||||
            {
 | 
			
		||||
              name: '访问来源',
 | 
			
		||||
              type: 'pie',
 | 
			
		||||
              radius: ['45%', '60%'],
 | 
			
		||||
              center: ['50%', '52%'],
 | 
			
		||||
              avoidLabelOverlap: false,
 | 
			
		||||
              label: {
 | 
			
		||||
                normal: {
 | 
			
		||||
                  show: false,
 | 
			
		||||
                  position: 'center',
 | 
			
		||||
                },
 | 
			
		||||
                emphasis: {
 | 
			
		||||
                  show: true,
 | 
			
		||||
                  textStyle: {
 | 
			
		||||
                    fontSize: '24',
 | 
			
		||||
                  },
 | 
			
		||||
                },
 | 
			
		||||
              },
 | 
			
		||||
              labelLine: {
 | 
			
		||||
                normal: {
 | 
			
		||||
                  show: false,
 | 
			
		||||
                },
 | 
			
		||||
              },
 | 
			
		||||
              data: this.value,
 | 
			
		||||
            },
 | 
			
		||||
          ],
 | 
			
		||||
        }
 | 
			
		||||
        this.dom = echarts.init(this.$refs.dom, 'tdTheme')
 | 
			
		||||
        this.dom.setOption(option)
 | 
			
		||||
        on(window, 'resize', this.resize)
 | 
			
		||||
      })
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style>
 | 
			
		||||
.pie-main {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 360px;
 | 
			
		||||
  padding: 28px;
 | 
			
		||||
  background: #fff;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										37
									
								
								mayfly-go-front/src/components/chart/onoff.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								mayfly-go-front/src/components/chart/onoff.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,37 @@
 | 
			
		||||
/**
 | 
			
		||||
 * @description 绑定事件 on(element, event, handler)
 | 
			
		||||
 */
 | 
			
		||||
export const on = (function () {
 | 
			
		||||
    if (document.addEventListener != null) {
 | 
			
		||||
      return function (element, event, handler) {
 | 
			
		||||
        if (element && event && handler) {
 | 
			
		||||
          element.addEventListener(event, handler, false);
 | 
			
		||||
        }
 | 
			
		||||
      };
 | 
			
		||||
    } else {
 | 
			
		||||
      return function (element, event, handler) {
 | 
			
		||||
        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, event, handler) {
 | 
			
		||||
        if (element && event) {
 | 
			
		||||
          element.removeEventListener(event, handler, false);
 | 
			
		||||
        }
 | 
			
		||||
      };
 | 
			
		||||
    } else {
 | 
			
		||||
      return function (element, event, handler) {
 | 
			
		||||
        if (element && event) {
 | 
			
		||||
          element.detachEvent('on' + event, handler);
 | 
			
		||||
        }
 | 
			
		||||
      };
 | 
			
		||||
    }
 | 
			
		||||
  })();
 | 
			
		||||
							
								
								
									
										490
									
								
								mayfly-go-front/src/components/chart/theme.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										490
									
								
								mayfly-go-front/src/components/chart/theme.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,490 @@
 | 
			
		||||
{
 | 
			
		||||
    "color": [
 | 
			
		||||
      "#2d8cf0",
 | 
			
		||||
      "#19be6b",
 | 
			
		||||
      "#ff9900",
 | 
			
		||||
      "#E46CBB",
 | 
			
		||||
      "#9A66E4",
 | 
			
		||||
      "#ed3f14"
 | 
			
		||||
    ],
 | 
			
		||||
    "backgroundColor": "rgba(0,0,0,0)",
 | 
			
		||||
    "textStyle": {},
 | 
			
		||||
    "title": {
 | 
			
		||||
      "textStyle": {
 | 
			
		||||
        "color": "#516b91"
 | 
			
		||||
      },
 | 
			
		||||
      "subtextStyle": {
 | 
			
		||||
        "color": "#93b7e3"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "line": {
 | 
			
		||||
      "itemStyle": {
 | 
			
		||||
        "normal": {
 | 
			
		||||
          "borderWidth": "2"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "lineStyle": {
 | 
			
		||||
        "normal": {
 | 
			
		||||
          "width": "2"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "symbolSize": "6",
 | 
			
		||||
      "symbol": "emptyCircle",
 | 
			
		||||
      "smooth": true
 | 
			
		||||
    },
 | 
			
		||||
    "radar": {
 | 
			
		||||
      "itemStyle": {
 | 
			
		||||
        "normal": {
 | 
			
		||||
          "borderWidth": "2"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "lineStyle": {
 | 
			
		||||
        "normal": {
 | 
			
		||||
          "width": "2"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "symbolSize": "6",
 | 
			
		||||
      "symbol": "emptyCircle",
 | 
			
		||||
      "smooth": true
 | 
			
		||||
    },
 | 
			
		||||
    "bar": {
 | 
			
		||||
      "itemStyle": {
 | 
			
		||||
        "normal": {
 | 
			
		||||
          "barBorderWidth": 0,
 | 
			
		||||
          "barBorderColor": "#ccc"
 | 
			
		||||
        },
 | 
			
		||||
        "emphasis": {
 | 
			
		||||
          "barBorderWidth": 0,
 | 
			
		||||
          "barBorderColor": "#ccc"
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "pie": {
 | 
			
		||||
      "itemStyle": {
 | 
			
		||||
        "normal": {
 | 
			
		||||
          "borderWidth": 0,
 | 
			
		||||
          "borderColor": "#ccc"
 | 
			
		||||
        },
 | 
			
		||||
        "emphasis": {
 | 
			
		||||
          "borderWidth": 0,
 | 
			
		||||
          "borderColor": "#ccc"
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "scatter": {
 | 
			
		||||
      "itemStyle": {
 | 
			
		||||
        "normal": {
 | 
			
		||||
          "borderWidth": 0,
 | 
			
		||||
          "borderColor": "#ccc"
 | 
			
		||||
        },
 | 
			
		||||
        "emphasis": {
 | 
			
		||||
          "borderWidth": 0,
 | 
			
		||||
          "borderColor": "#ccc"
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "boxplot": {
 | 
			
		||||
      "itemStyle": {
 | 
			
		||||
        "normal": {
 | 
			
		||||
          "borderWidth": 0,
 | 
			
		||||
          "borderColor": "#ccc"
 | 
			
		||||
        },
 | 
			
		||||
        "emphasis": {
 | 
			
		||||
          "borderWidth": 0,
 | 
			
		||||
          "borderColor": "#ccc"
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "parallel": {
 | 
			
		||||
      "itemStyle": {
 | 
			
		||||
        "normal": {
 | 
			
		||||
          "borderWidth": 0,
 | 
			
		||||
          "borderColor": "#ccc"
 | 
			
		||||
        },
 | 
			
		||||
        "emphasis": {
 | 
			
		||||
          "borderWidth": 0,
 | 
			
		||||
          "borderColor": "#ccc"
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "sankey": {
 | 
			
		||||
      "itemStyle": {
 | 
			
		||||
        "normal": {
 | 
			
		||||
          "borderWidth": 0,
 | 
			
		||||
          "borderColor": "#ccc"
 | 
			
		||||
        },
 | 
			
		||||
        "emphasis": {
 | 
			
		||||
          "borderWidth": 0,
 | 
			
		||||
          "borderColor": "#ccc"
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "funnel": {
 | 
			
		||||
      "itemStyle": {
 | 
			
		||||
        "normal": {
 | 
			
		||||
          "borderWidth": 0,
 | 
			
		||||
          "borderColor": "#ccc"
 | 
			
		||||
        },
 | 
			
		||||
        "emphasis": {
 | 
			
		||||
          "borderWidth": 0,
 | 
			
		||||
          "borderColor": "#ccc"
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "gauge": {
 | 
			
		||||
      "itemStyle": {
 | 
			
		||||
        "normal": {
 | 
			
		||||
          "borderWidth": 0,
 | 
			
		||||
          "borderColor": "#ccc"
 | 
			
		||||
        },
 | 
			
		||||
        "emphasis": {
 | 
			
		||||
          "borderWidth": 0,
 | 
			
		||||
          "borderColor": "#ccc"
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "candlestick": {
 | 
			
		||||
      "itemStyle": {
 | 
			
		||||
        "normal": {
 | 
			
		||||
          "color": "#edafda",
 | 
			
		||||
          "color0": "transparent",
 | 
			
		||||
          "borderColor": "#d680bc",
 | 
			
		||||
          "borderColor0": "#8fd3e8",
 | 
			
		||||
          "borderWidth": "2"
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "graph": {
 | 
			
		||||
      "itemStyle": {
 | 
			
		||||
        "normal": {
 | 
			
		||||
          "borderWidth": 0,
 | 
			
		||||
          "borderColor": "#ccc"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "lineStyle": {
 | 
			
		||||
        "normal": {
 | 
			
		||||
          "width": 1,
 | 
			
		||||
          "color": "#aaa"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "symbolSize": "6",
 | 
			
		||||
      "symbol": "emptyCircle",
 | 
			
		||||
      "smooth": true,
 | 
			
		||||
      "color": [
 | 
			
		||||
        "#2d8cf0",
 | 
			
		||||
        "#19be6b",
 | 
			
		||||
        "#f5ae4a",
 | 
			
		||||
        "#9189d5",
 | 
			
		||||
        "#56cae2",
 | 
			
		||||
        "#cbb0e3"
 | 
			
		||||
      ],
 | 
			
		||||
      "label": {
 | 
			
		||||
        "normal": {
 | 
			
		||||
          "textStyle": {
 | 
			
		||||
            "color": "#eee"
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "map": {
 | 
			
		||||
      "itemStyle": {
 | 
			
		||||
        "normal": {
 | 
			
		||||
          "areaColor": "#f3f3f3",
 | 
			
		||||
          "borderColor": "#516b91",
 | 
			
		||||
          "borderWidth": 0.5
 | 
			
		||||
        },
 | 
			
		||||
        "emphasis": {
 | 
			
		||||
          "areaColor": "rgba(165,231,240,1)",
 | 
			
		||||
          "borderColor": "#516b91",
 | 
			
		||||
          "borderWidth": 1
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "label": {
 | 
			
		||||
        "normal": {
 | 
			
		||||
          "textStyle": {
 | 
			
		||||
            "color": "#000"
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "emphasis": {
 | 
			
		||||
          "textStyle": {
 | 
			
		||||
            "color": "rgb(81,107,145)"
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "geo": {
 | 
			
		||||
      "itemStyle": {
 | 
			
		||||
        "normal": {
 | 
			
		||||
          "areaColor": "#f3f3f3",
 | 
			
		||||
          "borderColor": "#516b91",
 | 
			
		||||
          "borderWidth": 0.5
 | 
			
		||||
        },
 | 
			
		||||
        "emphasis": {
 | 
			
		||||
          "areaColor": "rgba(165,231,240,1)",
 | 
			
		||||
          "borderColor": "#516b91",
 | 
			
		||||
          "borderWidth": 1
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "label": {
 | 
			
		||||
        "normal": {
 | 
			
		||||
          "textStyle": {
 | 
			
		||||
            "color": "#000"
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "emphasis": {
 | 
			
		||||
          "textStyle": {
 | 
			
		||||
            "color": "rgb(81,107,145)"
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "categoryAxis": {
 | 
			
		||||
      "axisLine": {
 | 
			
		||||
        "show": true,
 | 
			
		||||
        "lineStyle": {
 | 
			
		||||
          "color": "#cccccc"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "axisTick": {
 | 
			
		||||
        "show": false,
 | 
			
		||||
        "lineStyle": {
 | 
			
		||||
          "color": "#333"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "axisLabel": {
 | 
			
		||||
        "show": true,
 | 
			
		||||
        "textStyle": {
 | 
			
		||||
          "color": "#999999"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "splitLine": {
 | 
			
		||||
        "show": true,
 | 
			
		||||
        "lineStyle": {
 | 
			
		||||
          "color": [
 | 
			
		||||
            "#eeeeee"
 | 
			
		||||
          ]
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "splitArea": {
 | 
			
		||||
        "show": false,
 | 
			
		||||
        "areaStyle": {
 | 
			
		||||
          "color": [
 | 
			
		||||
            "rgba(250,250,250,0.05)",
 | 
			
		||||
            "rgba(200,200,200,0.02)"
 | 
			
		||||
          ]
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "valueAxis": {
 | 
			
		||||
      "axisLine": {
 | 
			
		||||
        "show": true,
 | 
			
		||||
        "lineStyle": {
 | 
			
		||||
          "color": "#cccccc"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "axisTick": {
 | 
			
		||||
        "show": false,
 | 
			
		||||
        "lineStyle": {
 | 
			
		||||
          "color": "#333"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "axisLabel": {
 | 
			
		||||
        "show": true,
 | 
			
		||||
        "textStyle": {
 | 
			
		||||
          "color": "#999999"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "splitLine": {
 | 
			
		||||
        "show": true,
 | 
			
		||||
        "lineStyle": {
 | 
			
		||||
          "color": [
 | 
			
		||||
            "#eeeeee"
 | 
			
		||||
          ]
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "splitArea": {
 | 
			
		||||
        "show": false,
 | 
			
		||||
        "areaStyle": {
 | 
			
		||||
          "color": [
 | 
			
		||||
            "rgba(250,250,250,0.05)",
 | 
			
		||||
            "rgba(200,200,200,0.02)"
 | 
			
		||||
          ]
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "logAxis": {
 | 
			
		||||
      "axisLine": {
 | 
			
		||||
        "show": true,
 | 
			
		||||
        "lineStyle": {
 | 
			
		||||
          "color": "#cccccc"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "axisTick": {
 | 
			
		||||
        "show": false,
 | 
			
		||||
        "lineStyle": {
 | 
			
		||||
          "color": "#333"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "axisLabel": {
 | 
			
		||||
        "show": true,
 | 
			
		||||
        "textStyle": {
 | 
			
		||||
          "color": "#999999"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "splitLine": {
 | 
			
		||||
        "show": true,
 | 
			
		||||
        "lineStyle": {
 | 
			
		||||
          "color": [
 | 
			
		||||
            "#eeeeee"
 | 
			
		||||
          ]
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "splitArea": {
 | 
			
		||||
        "show": false,
 | 
			
		||||
        "areaStyle": {
 | 
			
		||||
          "color": [
 | 
			
		||||
            "rgba(250,250,250,0.05)",
 | 
			
		||||
            "rgba(200,200,200,0.02)"
 | 
			
		||||
          ]
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "timeAxis": {
 | 
			
		||||
      "axisLine": {
 | 
			
		||||
        "show": true,
 | 
			
		||||
        "lineStyle": {
 | 
			
		||||
          "color": "#cccccc"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "axisTick": {
 | 
			
		||||
        "show": false,
 | 
			
		||||
        "lineStyle": {
 | 
			
		||||
          "color": "#333"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "axisLabel": {
 | 
			
		||||
        "show": true,
 | 
			
		||||
        "textStyle": {
 | 
			
		||||
          "color": "#999999"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "splitLine": {
 | 
			
		||||
        "show": true,
 | 
			
		||||
        "lineStyle": {
 | 
			
		||||
          "color": [
 | 
			
		||||
            "#eeeeee"
 | 
			
		||||
          ]
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "splitArea": {
 | 
			
		||||
        "show": false,
 | 
			
		||||
        "areaStyle": {
 | 
			
		||||
          "color": [
 | 
			
		||||
            "rgba(250,250,250,0.05)",
 | 
			
		||||
            "rgba(200,200,200,0.02)"
 | 
			
		||||
          ]
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "toolbox": {
 | 
			
		||||
      "iconStyle": {
 | 
			
		||||
        "normal": {
 | 
			
		||||
          "borderColor": "#999"
 | 
			
		||||
        },
 | 
			
		||||
        "emphasis": {
 | 
			
		||||
          "borderColor": "#666"
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "legend": {
 | 
			
		||||
      "textStyle": {
 | 
			
		||||
        "color": "#999999"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "tooltip": {
 | 
			
		||||
      "axisPointer": {
 | 
			
		||||
        "lineStyle": {
 | 
			
		||||
          "color": "#ccc",
 | 
			
		||||
          "width": 1
 | 
			
		||||
        },
 | 
			
		||||
        "crossStyle": {
 | 
			
		||||
          "color": "#ccc",
 | 
			
		||||
          "width": 1
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "timeline": {
 | 
			
		||||
      "lineStyle": {
 | 
			
		||||
        "color": "#8fd3e8",
 | 
			
		||||
        "width": 1
 | 
			
		||||
      },
 | 
			
		||||
      "itemStyle": {
 | 
			
		||||
        "normal": {
 | 
			
		||||
          "color": "#8fd3e8",
 | 
			
		||||
          "borderWidth": 1
 | 
			
		||||
        },
 | 
			
		||||
        "emphasis": {
 | 
			
		||||
          "color": "#8fd3e8"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "controlStyle": {
 | 
			
		||||
        "normal": {
 | 
			
		||||
          "color": "#8fd3e8",
 | 
			
		||||
          "borderColor": "#8fd3e8",
 | 
			
		||||
          "borderWidth": 0.5
 | 
			
		||||
        },
 | 
			
		||||
        "emphasis": {
 | 
			
		||||
          "color": "#8fd3e8",
 | 
			
		||||
          "borderColor": "#8fd3e8",
 | 
			
		||||
          "borderWidth": 0.5
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "checkpointStyle": {
 | 
			
		||||
        "color": "#8fd3e8",
 | 
			
		||||
        "borderColor": "rgba(138,124,168,0.37)"
 | 
			
		||||
      },
 | 
			
		||||
      "label": {
 | 
			
		||||
        "normal": {
 | 
			
		||||
          "textStyle": {
 | 
			
		||||
            "color": "#8fd3e8"
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "emphasis": {
 | 
			
		||||
          "textStyle": {
 | 
			
		||||
            "color": "#8fd3e8"
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "visualMap": {
 | 
			
		||||
      "color": [
 | 
			
		||||
        "#516b91",
 | 
			
		||||
        "#59c4e6",
 | 
			
		||||
        "#a5e7f0"
 | 
			
		||||
      ]
 | 
			
		||||
    },
 | 
			
		||||
    "dataZoom": {
 | 
			
		||||
      "backgroundColor": "rgba(0,0,0,0)",
 | 
			
		||||
      "dataBackgroundColor": "rgba(255,255,255,0.3)",
 | 
			
		||||
      "fillerColor": "rgba(167,183,204,0.4)",
 | 
			
		||||
      "handleColor": "#a7b7cc",
 | 
			
		||||
      "handleSize": "100%",
 | 
			
		||||
      "textStyle": {
 | 
			
		||||
        "color": "#333"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "markPoint": {
 | 
			
		||||
      "label": {
 | 
			
		||||
        "normal": {
 | 
			
		||||
          "textStyle": {
 | 
			
		||||
            "color": "#eee"
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "emphasis": {
 | 
			
		||||
          "textStyle": {
 | 
			
		||||
            "color": "#eee"
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
							
								
								
									
										142
									
								
								mayfly-go-front/src/components/dynamic-form/DynamicForm.vue
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										142
									
								
								mayfly-go-front/src/components/dynamic-form/DynamicForm.vue
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,142 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="dynamic-form">
 | 
			
		||||
    <el-form
 | 
			
		||||
      :model="form"
 | 
			
		||||
      ref="dynamicForm"
 | 
			
		||||
      :label-width="formInfo.labelWidth ? formInfo.labelWidth : '100px'"
 | 
			
		||||
      :size="formInfo.size ? formInfo.size : 'small'"
 | 
			
		||||
    >
 | 
			
		||||
      <el-row v-for="fr in formInfo.formRows" :key="fr.key">
 | 
			
		||||
        <el-col v-for="item in fr" :key="item.key" :span="item.span ? item.span : 24/fr.length">
 | 
			
		||||
          <el-form-item
 | 
			
		||||
            :prop="item.name"
 | 
			
		||||
            :label="item.label"
 | 
			
		||||
            :label-width="item.labelWidth"
 | 
			
		||||
            :required="item.required"
 | 
			
		||||
            :rules="item.rules"
 | 
			
		||||
          >
 | 
			
		||||
            <!-- input输入框 -->
 | 
			
		||||
            <el-input
 | 
			
		||||
              v-if="item.type === 'input'"
 | 
			
		||||
              v-model.trim="form[item.name]"
 | 
			
		||||
              :placeholder="item.placeholder"
 | 
			
		||||
              :type="item.inputType"
 | 
			
		||||
              clearable
 | 
			
		||||
              autocomplete="new-password"
 | 
			
		||||
              @change="item.change ? item.change(form) : ''"
 | 
			
		||||
            ></el-input>
 | 
			
		||||
 | 
			
		||||
            <!-- 普通文本信息(可用于不可修改字段等) -->
 | 
			
		||||
            <span v-else-if="item.type === 'text'">{{ form[item.name] }}</span>
 | 
			
		||||
 | 
			
		||||
            <!-- select选择框 -->
 | 
			
		||||
            <!-- optionProps.label: 指定option中的label为options对象的某个属性值,默认就是label字段 -->
 | 
			
		||||
            <!-- optionProps.value: 指定option中的value为options对象的某个属性值,默认就是value字段 -->
 | 
			
		||||
            <el-select
 | 
			
		||||
              v-else-if="item.type === 'select'"
 | 
			
		||||
              v-model.trim="form[item.name]"
 | 
			
		||||
              :placeholder="item.placeholder"
 | 
			
		||||
              :filterable="item.filterable"
 | 
			
		||||
              :remote="item.remote"
 | 
			
		||||
              :remote-method="item.remoteMethod"
 | 
			
		||||
              @focus="item.focus ? item.focus(form) : ''"
 | 
			
		||||
              clearable
 | 
			
		||||
              :disabled="item.updateDisabled && form.id != null"
 | 
			
		||||
              style="width: 100%;"
 | 
			
		||||
            >
 | 
			
		||||
              <el-option
 | 
			
		||||
                v-for="i in item.options"
 | 
			
		||||
                :key="i.key"
 | 
			
		||||
                :label="i[item.optionProps ? item.optionProps.label || 'label' : 'label']"
 | 
			
		||||
                :value="i[item.optionProps ? item.optionProps.value || 'value' : 'value']"
 | 
			
		||||
              ></el-option>
 | 
			
		||||
            </el-select>
 | 
			
		||||
          </el-form-item>
 | 
			
		||||
        </el-col>
 | 
			
		||||
      </el-row>
 | 
			
		||||
 | 
			
		||||
      <el-row type="flex" justify="center">
 | 
			
		||||
        <slot name="btns" :submitDisabled="submitDisabled" :data="form" :submit="submit">
 | 
			
		||||
          <el-button @click="reset" size="mini">重 置</el-button>
 | 
			
		||||
          <el-button type="primary" @click="submit" size="mini">保 存</el-button>
 | 
			
		||||
        </slot>
 | 
			
		||||
      </el-row>
 | 
			
		||||
    </el-form>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import { Component, Vue, Prop, Watch } from 'vue-property-decorator'
 | 
			
		||||
@Component({
 | 
			
		||||
  name: 'DynamicForm'
 | 
			
		||||
})
 | 
			
		||||
export default class DynamicForm extends Vue {
 | 
			
		||||
  @Prop()
 | 
			
		||||
  formInfo: object
 | 
			
		||||
  @Prop()
 | 
			
		||||
  formData: [object,boolean]|undefined
 | 
			
		||||
 | 
			
		||||
  form = {}
 | 
			
		||||
  submitDisabled = false
 | 
			
		||||
 | 
			
		||||
  @Watch('formData', { deep: true })
 | 
			
		||||
  onRoleChange() {
 | 
			
		||||
    if (this.formData) {
 | 
			
		||||
      this.form = { ...this.formData }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  submit() {
 | 
			
		||||
    const dynamicForm: any = this.$refs['dynamicForm']
 | 
			
		||||
    dynamicForm.validate((valid: boolean) => {
 | 
			
		||||
      if (valid) {
 | 
			
		||||
        // 提交的表单数据
 | 
			
		||||
        const subform = { ...this.form }
 | 
			
		||||
        const operation = this.form['id']
 | 
			
		||||
          ? this.formInfo['updateApi']
 | 
			
		||||
          : this.formInfo['createApi']
 | 
			
		||||
        if (operation) {
 | 
			
		||||
          this.submitDisabled = true
 | 
			
		||||
          operation.request(this.form).then(
 | 
			
		||||
            (res: any) => {
 | 
			
		||||
              this.$message.success('保存成功')
 | 
			
		||||
              this.$emit('submitSuccess', subform)
 | 
			
		||||
              this.submitDisabled = false
 | 
			
		||||
              // this.cancel()
 | 
			
		||||
            },
 | 
			
		||||
            (e: any) => {
 | 
			
		||||
              this.submitDisabled = false
 | 
			
		||||
            }
 | 
			
		||||
          )
 | 
			
		||||
        } else {
 | 
			
		||||
          this.$message.error('表单未设置对应的提交权限')
 | 
			
		||||
        }
 | 
			
		||||
      } else {
 | 
			
		||||
        return false
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  reset() {
 | 
			
		||||
    this.$emit('reset')
 | 
			
		||||
    this.resetFieldsAndData()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * 重置表单以及表单数据
 | 
			
		||||
   */
 | 
			
		||||
  resetFieldsAndData() {
 | 
			
		||||
    // 对整个表单进行重置,将所有字段值重置为初始值并移除校验结果
 | 
			
		||||
    const df: any = this.$refs['dynamicForm']
 | 
			
		||||
    df.resetFields()
 | 
			
		||||
    // 重置表单数据
 | 
			
		||||
    this.form = {}
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  mounted() {
 | 
			
		||||
    // 组件可能还没有初始化,第一次初始化的时候无法watch对象
 | 
			
		||||
    this.form = { ...this.formData }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
@@ -0,0 +1,67 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="form-dialog">
 | 
			
		||||
    <el-dialog :title="title" :visible="visible" :width="dialogWidth ? dialogWidth : '500px'">
 | 
			
		||||
      <dynamic-form
 | 
			
		||||
        ref="df"
 | 
			
		||||
        :form-info="formInfo"
 | 
			
		||||
        :form-data="formData"
 | 
			
		||||
        @submitSuccess="submitSuccess"
 | 
			
		||||
      >
 | 
			
		||||
        <template slot="btns" slot-scope="props">
 | 
			
		||||
          <slot name="btns">
 | 
			
		||||
            <el-button
 | 
			
		||||
              :disabled="props.submitDisabled"
 | 
			
		||||
              type="primary"
 | 
			
		||||
              @click="props.submit"
 | 
			
		||||
              size="mini"
 | 
			
		||||
            >保 存</el-button>
 | 
			
		||||
            <el-button :disabled="props.submitDisabled" @click="close()" size="mini">取 消</el-button>
 | 
			
		||||
          </slot>
 | 
			
		||||
        </template>
 | 
			
		||||
      </dynamic-form>
 | 
			
		||||
    </el-dialog>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import { Component, Vue, Prop } from 'vue-property-decorator'
 | 
			
		||||
import DynamicForm from './DynamicForm.vue'
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  name: 'DynamicFormDialog',
 | 
			
		||||
  components: {
 | 
			
		||||
    DynamicForm
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
export default class DynamicFormDialog extends Vue {
 | 
			
		||||
  @Prop()
 | 
			
		||||
  visible: boolean|undefined
 | 
			
		||||
  @Prop()
 | 
			
		||||
  dialogWidth: string|undefined
 | 
			
		||||
  @Prop()
 | 
			
		||||
  title: string|undefined
 | 
			
		||||
  @Prop()
 | 
			
		||||
  formInfo: object|undefined
 | 
			
		||||
  @Prop()
 | 
			
		||||
  formData: [object,boolean]|undefined
 | 
			
		||||
 | 
			
		||||
  close() {
 | 
			
		||||
    // 更新父组件visible prop对应的值为false
 | 
			
		||||
    this.$emit('update:visible', false)
 | 
			
		||||
    // 关闭窗口,则将表单数据置为null
 | 
			
		||||
    this.$emit('update:formData', null)
 | 
			
		||||
    this.$emit('close')
 | 
			
		||||
    // 取消动态表单的校验以及form数据
 | 
			
		||||
    setTimeout(() => {
 | 
			
		||||
      const df: any = this.$refs.df
 | 
			
		||||
      df.resetFieldsAndData()
 | 
			
		||||
    }, 200)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  submitSuccess(form: any) {
 | 
			
		||||
    this.$emit('submitSuccess', form)
 | 
			
		||||
    this.close()
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
							
								
								
									
										2
									
								
								mayfly-go-front/src/components/dynamic-form/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								mayfly-go-front/src/components/dynamic-form/index.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
			
		||||
export { default as DynamicForm } from './DynamicForm.vue';
 | 
			
		||||
export { default as DynamicFormDialog } from './DynamicFormDialog.vue';
 | 
			
		||||
							
								
								
									
										337
									
								
								mayfly-go-front/src/layout/Layout.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										337
									
								
								mayfly-go-front/src/layout/Layout.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,337 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="main">
 | 
			
		||||
    <div class="header">
 | 
			
		||||
      <div class="logo">
 | 
			
		||||
        <span class="big">Mayfly-Go</span>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="right">
 | 
			
		||||
        <span class="header-btn">
 | 
			
		||||
          <el-badge :value="3" class="badge">
 | 
			
		||||
            <i class="el-icon-bell"></i>
 | 
			
		||||
          </el-badge>
 | 
			
		||||
        </span>
 | 
			
		||||
        <el-dropdown>
 | 
			
		||||
          <span class="header-btn">
 | 
			
		||||
            {{username}}
 | 
			
		||||
            <i class="el-icon-arrow-down el-icon--right"></i>
 | 
			
		||||
          </span>
 | 
			
		||||
          <el-dropdown-menu slot="dropdown">
 | 
			
		||||
            <el-dropdown-item @click.native="this.$router.push('/personal')">
 | 
			
		||||
              <i style="padding-right: 8px" class="fa fa-cog"></i>个人中心
 | 
			
		||||
            </el-dropdown-item>
 | 
			
		||||
            <el-dropdown-item @click.native="logout">
 | 
			
		||||
              <i style="padding-right: 8px" class="fa fa-key"></i>退出系统
 | 
			
		||||
            </el-dropdown-item>
 | 
			
		||||
          </el-dropdown-menu>
 | 
			
		||||
        </el-dropdown>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="app">
 | 
			
		||||
      <div class="aside">
 | 
			
		||||
        <div class="menu">
 | 
			
		||||
          <el-menu
 | 
			
		||||
            background-color="#222d32"
 | 
			
		||||
            text-color="#bbbbbb"
 | 
			
		||||
            active-text-color="#fff"
 | 
			
		||||
            class="menu"
 | 
			
		||||
          >
 | 
			
		||||
            <MenuTree @toPath="toPath" :menus="this.menus"></MenuTree>
 | 
			
		||||
          </el-menu>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="app-body">
 | 
			
		||||
        <el-tabs
 | 
			
		||||
          id="nav-bar"
 | 
			
		||||
          class="none-select"
 | 
			
		||||
          v-model="activeName"
 | 
			
		||||
          @tab-click="tabClick"
 | 
			
		||||
          @tab-remove="removeTab"
 | 
			
		||||
          type="card"
 | 
			
		||||
          closable
 | 
			
		||||
        >
 | 
			
		||||
          <el-tab-pane
 | 
			
		||||
            :key="item.name"
 | 
			
		||||
            v-for="(item) in tabs"
 | 
			
		||||
            :label="item.title"
 | 
			
		||||
            :name="item.name"
 | 
			
		||||
          ></el-tab-pane>
 | 
			
		||||
        </el-tabs>
 | 
			
		||||
        <div id="mainContainer" class="main-container">
 | 
			
		||||
          <router-view v-if="!iframe"></router-view>
 | 
			
		||||
          <iframe
 | 
			
		||||
            style="width: calc(100% - 235px); height: calc(100% - 90px)"
 | 
			
		||||
            rameborder="0"
 | 
			
		||||
            v-else
 | 
			
		||||
            :src="iframeSrc"
 | 
			
		||||
          ></iframe>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import { Component, Vue } from 'vue-property-decorator'
 | 
			
		||||
import MenuTree from './MenuTree.vue'
 | 
			
		||||
import api from '@/common/openApi'
 | 
			
		||||
import { AuthUtils } from '../common/AuthUtils'
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  name: 'Layout',
 | 
			
		||||
  components: {
 | 
			
		||||
    MenuTree,
 | 
			
		||||
  },
 | 
			
		||||
})
 | 
			
		||||
export default class App extends Vue {
 | 
			
		||||
  private iframe = false
 | 
			
		||||
  private iframeSrc: string | null = null
 | 
			
		||||
  private username = ''
 | 
			
		||||
  private menus: Array<object> = []
 | 
			
		||||
  private tabs: Array<any> = []
 | 
			
		||||
  private activeName = ''
 | 
			
		||||
  private tabIndex = 2
 | 
			
		||||
 | 
			
		||||
  private toPath(menu: any) {
 | 
			
		||||
    const path = menu.url
 | 
			
		||||
    this.goToPath(path)
 | 
			
		||||
    this.addTab(path, menu.name)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private goToPath(path: string) {
 | 
			
		||||
    // 如果是请求其他地址,则使用iframe展示
 | 
			
		||||
    if (path && (path.startsWith('http://') || path.startsWith('https://'))) {
 | 
			
		||||
      this.iframe = true
 | 
			
		||||
      this.iframeSrc = path
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
    this.iframe = false
 | 
			
		||||
    this.iframeSrc = null
 | 
			
		||||
    this.$router
 | 
			
		||||
      .push({
 | 
			
		||||
        path,
 | 
			
		||||
      })
 | 
			
		||||
      // eslint-disable-next-line @typescript-eslint/no-empty-function
 | 
			
		||||
      .catch((err: any) => {})
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private tabClick(tab: any) {
 | 
			
		||||
    this.goToPath(tab.name)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private addTab(path: string, title: string) {
 | 
			
		||||
    for (const n of this.tabs) {
 | 
			
		||||
      if (n.name === path) {
 | 
			
		||||
        this.activeName = path
 | 
			
		||||
        return
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    this.tabs.push({
 | 
			
		||||
      name: path,
 | 
			
		||||
      title: title,
 | 
			
		||||
    })
 | 
			
		||||
    this.activeName = path
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private removeTab(targetName: string) {
 | 
			
		||||
    const tabs = this.tabs
 | 
			
		||||
    let activeName = this.activeName
 | 
			
		||||
    if (activeName === targetName) {
 | 
			
		||||
      tabs.forEach((tab, index) => {
 | 
			
		||||
        if (tab.name == targetName) {
 | 
			
		||||
          const nextTab = tabs[index + 1] || tabs[index - 1]
 | 
			
		||||
          if (nextTab) {
 | 
			
		||||
            activeName = nextTab.name
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
    this.activeName = activeName
 | 
			
		||||
    this.tabs = tabs.filter((tab) => tab.name !== targetName)
 | 
			
		||||
    this.goToPath(activeName)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private async logout() {
 | 
			
		||||
    sessionStorage.clear()
 | 
			
		||||
    this.$router.push({
 | 
			
		||||
      path: '/login',
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  mounted() {
 | 
			
		||||
    const menu = [
 | 
			
		||||
      {
 | 
			
		||||
        id: 1,
 | 
			
		||||
        type: 1,
 | 
			
		||||
        name: '机器管理',
 | 
			
		||||
        icon: 'el-icon-menu',
 | 
			
		||||
        children: [
 | 
			
		||||
          {
 | 
			
		||||
            type: 1,
 | 
			
		||||
            name: '机器列表',
 | 
			
		||||
            url: '/machines',
 | 
			
		||||
            icon: 'el-icon-menu',
 | 
			
		||||
            code: 'index',
 | 
			
		||||
          },
 | 
			
		||||
        ],
 | 
			
		||||
      },
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    if (menu != null) {
 | 
			
		||||
      this.menus = menu
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const user = sessionStorage.getItem('admin')
 | 
			
		||||
    if (user != null) {
 | 
			
		||||
      this.username = JSON.parse(user).username
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this.addTab(this.$route.path, this.$route.meta.title)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</script>>
 | 
			
		||||
<style lang="less">
 | 
			
		||||
.main {
 | 
			
		||||
  display: flex;
 | 
			
		||||
 | 
			
		||||
  .el-menu:not(.el-menu--collapse) {
 | 
			
		||||
    width: 230px;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .app {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    background-color: #ecf0f5;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .aside {
 | 
			
		||||
    position: fixed;
 | 
			
		||||
    margin-top: 50px;
 | 
			
		||||
    z-index: 10;
 | 
			
		||||
    background-color: #222d32;
 | 
			
		||||
    transition: all 0.3s ease-in-out;
 | 
			
		||||
 | 
			
		||||
    .menu {
 | 
			
		||||
      overflow-y: auto;
 | 
			
		||||
      height: calc(~'100vh');
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .app-body {
 | 
			
		||||
    margin-left: 230px;
 | 
			
		||||
    -webkit-transition: margin-left 0.3s ease-in-out;
 | 
			
		||||
    transition: margin-left 0.3s ease-in-out;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .main-container {
 | 
			
		||||
    margin-top: 88px;
 | 
			
		||||
    padding: 2px;
 | 
			
		||||
    min-height: calc(~'100vh - 88px');
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.header {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  position: fixed;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  height: 50px;
 | 
			
		||||
  background-color: #303643;
 | 
			
		||||
  z-index: 10;
 | 
			
		||||
 | 
			
		||||
  .logo {
 | 
			
		||||
    .min {
 | 
			
		||||
      display: none;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    width: 230px;
 | 
			
		||||
    height: 50px;
 | 
			
		||||
    text-align: center;
 | 
			
		||||
    line-height: 50px;
 | 
			
		||||
    color: #fff;
 | 
			
		||||
    background-color: #303643;
 | 
			
		||||
    -webkit-transition: width 0.35s;
 | 
			
		||||
    transition: all 0.3s ease-in-out;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .right {
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    right: 0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .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: 0.25em;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
    height: 50px;
 | 
			
		||||
    display: inline-block;
 | 
			
		||||
    text-align: center;
 | 
			
		||||
    line-height: 50px;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
    padding: 0 14px;
 | 
			
		||||
    color: #fff;
 | 
			
		||||
 | 
			
		||||
    &:hover {
 | 
			
		||||
      background-color: #222d32;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.menu {
 | 
			
		||||
  border-right: none;
 | 
			
		||||
  // 禁止选择
 | 
			
		||||
  moz-user-select: -moz-none;
 | 
			
		||||
  -moz-user-select: none;
 | 
			
		||||
  -o-user-select: none;
 | 
			
		||||
  -khtml-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-submenu__title {
 | 
			
		||||
  font-weight: 500;
 | 
			
		||||
}
 | 
			
		||||
.el-menu-item {
 | 
			
		||||
  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, 0.12), 0 0 3px 0 rgba(0, 0, 0, 0.04);
 | 
			
		||||
  position: fixed;
 | 
			
		||||
  top: 0;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										42
									
								
								mayfly-go-front/src/layout/MenuTree.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								mayfly-go-front/src/layout/MenuTree.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,42 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div>
 | 
			
		||||
    <template v-for="menu in this.menus">
 | 
			
		||||
      <!-- 只有菜单的子节点为菜单类型才继续展开 -->
 | 
			
		||||
      <el-submenu
 | 
			
		||||
        :key="menu.id"
 | 
			
		||||
        :index="!menu.code ? menu.id + '' : menu.code"
 | 
			
		||||
        v-if="menu.children && menu.children[0].type === 1"
 | 
			
		||||
      >
 | 
			
		||||
        <template slot="title">
 | 
			
		||||
          <i :class="menu.icon"></i>
 | 
			
		||||
          <span slot="title">{{menu.name}}</span>
 | 
			
		||||
        </template>
 | 
			
		||||
        <MenuTree @toPath="toPath" :menus="menu.children"></MenuTree>
 | 
			
		||||
      </el-submenu>
 | 
			
		||||
      <el-menu-item
 | 
			
		||||
        @click="toPath(menu)"
 | 
			
		||||
        :key="menu.id"
 | 
			
		||||
        :index="!menu.path ? menu.id + '' : menu.path"
 | 
			
		||||
        v-else
 | 
			
		||||
      >
 | 
			
		||||
        <i class="iconfont" :class="menu.icon"></i>
 | 
			
		||||
        <span slot="title">{{menu.name}}</span>
 | 
			
		||||
      </el-menu-item>
 | 
			
		||||
    </template>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import { Component, Vue, Prop } from 'vue-property-decorator'
 | 
			
		||||
@Component({
 | 
			
		||||
  name: 'MenuTree'
 | 
			
		||||
})
 | 
			
		||||
export default class MenuTree extends Vue {
 | 
			
		||||
  @Prop()
 | 
			
		||||
  menus: object
 | 
			
		||||
 | 
			
		||||
  toPath(menu: any) {
 | 
			
		||||
    this.$emit('toPath', menu)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</script>>
 | 
			
		||||
							
								
								
									
										25
									
								
								mayfly-go-front/src/main.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								mayfly-go-front/src/main.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
			
		||||
import Vue from 'vue'
 | 
			
		||||
import App from './App.vue'
 | 
			
		||||
import router from './router'
 | 
			
		||||
import store from './store'
 | 
			
		||||
import ElementUI from 'element-ui'
 | 
			
		||||
import 'element-ui/lib/theme-chalk/index.css'
 | 
			
		||||
import './assets/css/style.css'
 | 
			
		||||
 | 
			
		||||
// import ECharts from 'vue-echarts' // 在 webpack 环境下指向 components/ECharts.vue
 | 
			
		||||
 | 
			
		||||
// 手动引入 ECharts 各模块来减小打包体积
 | 
			
		||||
// import 'echarts/lib/chart/bar'
 | 
			
		||||
// import 'echarts/lib/component/tooltip'
 | 
			
		||||
 | 
			
		||||
Vue.config.productionTip = false
 | 
			
		||||
// 注册组件后即可使用
 | 
			
		||||
// Vue.component('v-chart', ECharts)
 | 
			
		||||
 | 
			
		||||
Vue.use(ElementUI)
 | 
			
		||||
 | 
			
		||||
new Vue({
 | 
			
		||||
  router,
 | 
			
		||||
  store,
 | 
			
		||||
  render: h => h(App)
 | 
			
		||||
}).$mount('#app')
 | 
			
		||||
							
								
								
									
										57
									
								
								mayfly-go-front/src/router/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								mayfly-go-front/src/router/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,57 @@
 | 
			
		||||
import Vue from 'vue'
 | 
			
		||||
import VueRouter, { RouteConfig } from 'vue-router'
 | 
			
		||||
import Layout from "@/layout/Layout.vue"
 | 
			
		||||
import { AuthUtils } from '../common/AuthUtils';
 | 
			
		||||
 | 
			
		||||
Vue.use(VueRouter)
 | 
			
		||||
 | 
			
		||||
const routes: Array<RouteConfig> = [
 | 
			
		||||
  {
 | 
			
		||||
    path: '/login',
 | 
			
		||||
    name: 'Login',
 | 
			
		||||
    meta: {
 | 
			
		||||
      title: '登录',
 | 
			
		||||
      keepAlive: false
 | 
			
		||||
    },
 | 
			
		||||
    component: () => import('@/views/login/Login.vue')
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    path: '/',
 | 
			
		||||
    component: Layout,
 | 
			
		||||
    meta: {
 | 
			
		||||
      title: '首页',
 | 
			
		||||
      keepAlive: false,
 | 
			
		||||
    },
 | 
			
		||||
    children: [{
 | 
			
		||||
      path: 'machines',
 | 
			
		||||
      name: 'machines',
 | 
			
		||||
      meta: {
 | 
			
		||||
        title: '机器列表',
 | 
			
		||||
        keepAlive: false
 | 
			
		||||
      },
 | 
			
		||||
      component: () => import('@/views/machine')
 | 
			
		||||
    }]
 | 
			
		||||
  },
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
const router = new VueRouter({
 | 
			
		||||
  mode: 'history',
 | 
			
		||||
  base: process.env.BASE_URL,
 | 
			
		||||
  routes
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
router.beforeEach((to: any, from: any, next: any) => {
 | 
			
		||||
  window.document.title = to.meta.title
 | 
			
		||||
  const toPath = to.path
 | 
			
		||||
  if (toPath.startsWith('/open')) {
 | 
			
		||||
    next()
 | 
			
		||||
    return
 | 
			
		||||
  }
 | 
			
		||||
  if (!AuthUtils.getToken() && toPath != '/login') {
 | 
			
		||||
    next({ path: '/login' });
 | 
			
		||||
  } else {
 | 
			
		||||
    next();
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export default router
 | 
			
		||||
							
								
								
									
										13
									
								
								mayfly-go-front/src/shims-tsx.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								mayfly-go-front/src/shims-tsx.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
import Vue, { VNode } from 'vue'
 | 
			
		||||
 | 
			
		||||
declare global {
 | 
			
		||||
  namespace JSX {
 | 
			
		||||
    // tslint:disable no-empty-interface
 | 
			
		||||
    interface Element extends VNode {}
 | 
			
		||||
    // tslint:disable no-empty-interface
 | 
			
		||||
    interface ElementClass extends Vue {}
 | 
			
		||||
    interface IntrinsicElements {
 | 
			
		||||
      [elem: string]: any;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										4
									
								
								mayfly-go-front/src/shims-vue.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								mayfly-go-front/src/shims-vue.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,4 @@
 | 
			
		||||
declare module '*.vue' {
 | 
			
		||||
  import Vue from 'vue'
 | 
			
		||||
  export default Vue
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										15
									
								
								mayfly-go-front/src/store/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								mayfly-go-front/src/store/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
			
		||||
import Vue from 'vue'
 | 
			
		||||
import Vuex from 'vuex'
 | 
			
		||||
 | 
			
		||||
Vue.use(Vuex)
 | 
			
		||||
 | 
			
		||||
export default new Vuex.Store({
 | 
			
		||||
  state: {
 | 
			
		||||
  },
 | 
			
		||||
  mutations: {
 | 
			
		||||
  },
 | 
			
		||||
  actions: {
 | 
			
		||||
  },
 | 
			
		||||
  modules: {
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
							
								
								
									
										28
									
								
								mayfly-go-front/src/views/login/Login.less
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										28
									
								
								mayfly-go-front/src/views/login/Login.less
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,28 @@
 | 
			
		||||
.login{
 | 
			
		||||
  display: flex;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  height: 100%;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  background-color: #e4e5e6;
 | 
			
		||||
  .login-form{
 | 
			
		||||
    width: 375px;
 | 
			
		||||
    height: 435px;
 | 
			
		||||
    padding: 30px;
 | 
			
		||||
    background-color: white;
 | 
			
		||||
    text-align: left;
 | 
			
		||||
    border-radius: 4px;
 | 
			
		||||
    position: relative;
 | 
			
		||||
    margin-left: 0;
 | 
			
		||||
    margin-right: 0;
 | 
			
		||||
    zoom: 1;
 | 
			
		||||
    display: block;
 | 
			
		||||
    .login-header{
 | 
			
		||||
      text-align: center;
 | 
			
		||||
      font-size: 16px;
 | 
			
		||||
      font-weight: bold;
 | 
			
		||||
      margin-bottom: 20px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										145
									
								
								mayfly-go-front/src/views/login/Login.vue
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										145
									
								
								mayfly-go-front/src/views/login/Login.vue
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,145 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="login">
 | 
			
		||||
    <div class="login-form">
 | 
			
		||||
      <div class="login-header">
 | 
			
		||||
        <img src="../../assets/images/logo.png" width="150" height="120" alt />
 | 
			
		||||
        <!-- <p>{{ $Config.name.siteName }}</p> -->
 | 
			
		||||
      </div>
 | 
			
		||||
      <el-input
 | 
			
		||||
        placeholder="请输入用户名"
 | 
			
		||||
        suffix-icon="fa fa-user"
 | 
			
		||||
        v-model="loginForm.username"
 | 
			
		||||
        style="margin-bottom: 18px"
 | 
			
		||||
      ></el-input>
 | 
			
		||||
 | 
			
		||||
      <el-input
 | 
			
		||||
        placeholder="请输入密码"
 | 
			
		||||
        suffix-icon="fa fa-keyboard-o"
 | 
			
		||||
        v-model="loginForm.password"
 | 
			
		||||
        type="password"
 | 
			
		||||
        style="margin-bottom: 18px"
 | 
			
		||||
        autocomplete="new-password"
 | 
			
		||||
      ></el-input>
 | 
			
		||||
 | 
			
		||||
      <!-- <el-row>
 | 
			
		||||
        <el-col :span="12">
 | 
			
		||||
          <img
 | 
			
		||||
            @click="getCaptcha"
 | 
			
		||||
            width="130px"
 | 
			
		||||
            height="40px"
 | 
			
		||||
            :src="captchaImage"
 | 
			
		||||
            style="cursor: pointer"
 | 
			
		||||
          />
 | 
			
		||||
        </el-col>
 | 
			
		||||
        <el-col :span="12">
 | 
			
		||||
          <el-input
 | 
			
		||||
            placeholder="请输入算术结果"
 | 
			
		||||
            suffix-icon="fa fa-user"
 | 
			
		||||
            v-model="loginForm.captcha"
 | 
			
		||||
            style="margin-bottom: 18px"
 | 
			
		||||
            @keyup.native.enter="login"
 | 
			
		||||
          ></el-input>
 | 
			
		||||
        </el-col>
 | 
			
		||||
      </el-row> -->
 | 
			
		||||
 | 
			
		||||
      <el-button
 | 
			
		||||
        type="primary"
 | 
			
		||||
        :loading="loginLoading"
 | 
			
		||||
        style="width: 100%;margin-bottom: 18px"
 | 
			
		||||
        @click.native="login"
 | 
			
		||||
      >登录</el-button>
 | 
			
		||||
      <div>
 | 
			
		||||
        <el-checkbox v-model="remember">记住密码</el-checkbox>
 | 
			
		||||
        <!-- <a href="javascript:;" style="float: right;color: #3C8DBC;font-size: 14px">Register</a> -->
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import openApi from '../../common/openApi'
 | 
			
		||||
import { Component, Vue } from 'vue-property-decorator'
 | 
			
		||||
import { AuthUtils } from '@/common/AuthUtils'
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  name: 'Login',
 | 
			
		||||
})
 | 
			
		||||
export default class Login extends Vue {
 | 
			
		||||
  // private captchaImage = ''
 | 
			
		||||
  private loginForm = {
 | 
			
		||||
    username: '',
 | 
			
		||||
    password: '',
 | 
			
		||||
    // captcha: '',
 | 
			
		||||
    uuid: '',
 | 
			
		||||
  }
 | 
			
		||||
  private remember = false
 | 
			
		||||
  private loginLoading = false
 | 
			
		||||
 | 
			
		||||
  mounted() {
 | 
			
		||||
    // this.getCaptcha()
 | 
			
		||||
    const r = this.getRemember()
 | 
			
		||||
    let rememberAccount: any
 | 
			
		||||
    if (r != null) {
 | 
			
		||||
      rememberAccount = JSON.parse(r)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (rememberAccount) {
 | 
			
		||||
      this.remember = true
 | 
			
		||||
      this.loginForm.username = rememberAccount.username
 | 
			
		||||
      this.loginForm.password = rememberAccount.password
 | 
			
		||||
    } else {
 | 
			
		||||
      this.remember = false
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private async getCaptcha() {
 | 
			
		||||
    const res: any = await openApi.captcha()
 | 
			
		||||
    // this.captchaImage = res.base64Img
 | 
			
		||||
    this.loginForm.uuid = res.uuid
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private async login() {
 | 
			
		||||
    this.loginLoading = true
 | 
			
		||||
    try {
 | 
			
		||||
      const res = await openApi.login(this.loginForm)
 | 
			
		||||
      if (this.remember) {
 | 
			
		||||
        localStorage.setItem('remember', JSON.stringify(this.loginForm))
 | 
			
		||||
      } else {
 | 
			
		||||
        localStorage.removeItem('remember')
 | 
			
		||||
      }
 | 
			
		||||
      setTimeout(() => {
 | 
			
		||||
        //保存用户token以及菜单按钮权限
 | 
			
		||||
        // this['$Permission'].savePermission(res)
 | 
			
		||||
        AuthUtils.saveToken(res.token)
 | 
			
		||||
        this.$notify({
 | 
			
		||||
          title: '登录成功',
 | 
			
		||||
          message: '很高兴你使用Mayfly Admin!别忘了给个Star哦。',
 | 
			
		||||
          type: 'success',
 | 
			
		||||
        })
 | 
			
		||||
        this.loginLoading = false
 | 
			
		||||
        // 有重定向则重定向,否则到首页
 | 
			
		||||
        const redirect: any = this.$route.query.redirect
 | 
			
		||||
        if (redirect) {
 | 
			
		||||
          this.$router.push(redirect)
 | 
			
		||||
        } else {
 | 
			
		||||
          this.$router.push({
 | 
			
		||||
            path: '/',
 | 
			
		||||
          })
 | 
			
		||||
        }
 | 
			
		||||
      }, 500)
 | 
			
		||||
    } catch (err) {
 | 
			
		||||
      this.loginLoading = false
 | 
			
		||||
      // this.loginForm.captcha = ''
 | 
			
		||||
      // this.getCaptcha()
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private getRemember() {
 | 
			
		||||
    return localStorage.getItem('remember')
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="less">
 | 
			
		||||
@import 'Login.less';
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										333
									
								
								mayfly-go-front/src/views/machine/MachineList.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										333
									
								
								mayfly-go-front/src/views/machine/MachineList.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,333 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div>
 | 
			
		||||
    <div class="toolbar">
 | 
			
		||||
      <div class="fl">
 | 
			
		||||
        <el-button
 | 
			
		||||
          type="primary"
 | 
			
		||||
          icon="el-icon-plus"
 | 
			
		||||
          size="mini"
 | 
			
		||||
          @click="openFormDialog(false)"
 | 
			
		||||
          plain
 | 
			
		||||
        >添加</el-button>
 | 
			
		||||
        <el-button
 | 
			
		||||
          type="primary"
 | 
			
		||||
          icon="el-icon-edit"
 | 
			
		||||
          size="mini"
 | 
			
		||||
          :disabled="currentId == null"
 | 
			
		||||
          @click="openFormDialog(currentData)"
 | 
			
		||||
          plain
 | 
			
		||||
        >编辑</el-button>
 | 
			
		||||
        <el-button
 | 
			
		||||
          :disabled="currentId == null"
 | 
			
		||||
          @click="deleteMachine(currentId)"
 | 
			
		||||
          type="danger"
 | 
			
		||||
          icon="el-icon-delete"
 | 
			
		||||
          size="mini"
 | 
			
		||||
        >删除</el-button>
 | 
			
		||||
        <el-button
 | 
			
		||||
          type="success"
 | 
			
		||||
          :disabled="currentId == null"
 | 
			
		||||
          @click="fileManage(currentData)"
 | 
			
		||||
          size="mini"
 | 
			
		||||
          plain
 | 
			
		||||
        >文件管理</el-button>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div style="float: right;">
 | 
			
		||||
        <el-input
 | 
			
		||||
          placeholder="host"
 | 
			
		||||
          size="mini"
 | 
			
		||||
          style="width: 140px;"
 | 
			
		||||
          v-model="params.host"
 | 
			
		||||
          @clear="search"
 | 
			
		||||
          plain
 | 
			
		||||
          clearable
 | 
			
		||||
        ></el-input>
 | 
			
		||||
        <el-button @click="search" type="success" icon="el-icon-search" size="mini"></el-button>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <el-table :data="data.list" stripe style="width: 100%" @current-change="choose">
 | 
			
		||||
      <el-table-column label="选择" width="55px">
 | 
			
		||||
        <template slot-scope="scope">
 | 
			
		||||
          <el-radio v-model="currentId" :label="scope.row.id">
 | 
			
		||||
            <i></i>
 | 
			
		||||
          </el-radio>
 | 
			
		||||
        </template>
 | 
			
		||||
      </el-table-column>
 | 
			
		||||
      <el-table-column prop="name" label="名称" width></el-table-column>
 | 
			
		||||
      <el-table-column prop="ip" label="IP" width></el-table-column>
 | 
			
		||||
      <el-table-column prop="port" label="端口" width></el-table-column>
 | 
			
		||||
      <el-table-column prop="username" label="用户名"></el-table-column>
 | 
			
		||||
      <el-table-column prop="createTime" label="创建时间"></el-table-column>
 | 
			
		||||
      <el-table-column prop="updateTime" label="更新时间"></el-table-column>
 | 
			
		||||
      <el-table-column label="操作" min-width="200px">
 | 
			
		||||
        <template slot-scope="scope">
 | 
			
		||||
          <el-button
 | 
			
		||||
            type="primary"
 | 
			
		||||
            @click="info(scope.row.id)"
 | 
			
		||||
            :ref="scope.row"
 | 
			
		||||
            icom="el-icon-tickets"
 | 
			
		||||
            size="mini"
 | 
			
		||||
            plain
 | 
			
		||||
          >基本信息</el-button>
 | 
			
		||||
          <el-button
 | 
			
		||||
            type="primary"
 | 
			
		||||
            @click="monitor(scope.row.id)"
 | 
			
		||||
            :ref="scope.row"
 | 
			
		||||
            icom="el-icon-tickets"
 | 
			
		||||
            size="mini"
 | 
			
		||||
            plain
 | 
			
		||||
          >监控</el-button>
 | 
			
		||||
          <el-button
 | 
			
		||||
            type="success"
 | 
			
		||||
            @click="serviceManager(scope.row)"
 | 
			
		||||
            :ref="scope.row"
 | 
			
		||||
            size="mini"
 | 
			
		||||
            plain
 | 
			
		||||
          >服务管理</el-button>
 | 
			
		||||
        </template>
 | 
			
		||||
      </el-table-column>
 | 
			
		||||
    </el-table>
 | 
			
		||||
 | 
			
		||||
    <el-pagination
 | 
			
		||||
      style="text-align: center"
 | 
			
		||||
      background
 | 
			
		||||
      layout="prev, pager, next, total, jumper"
 | 
			
		||||
      :total="data.total"
 | 
			
		||||
      :current-page.sync="params.pageNum"
 | 
			
		||||
      :page-size="params.pageSize"
 | 
			
		||||
    />
 | 
			
		||||
 | 
			
		||||
    <el-dialog title="基本信息" :visible.sync="infoDialog.visible" width="30%">
 | 
			
		||||
      <div style="white-space: pre-line;">{{infoDialog.info}}</div>
 | 
			
		||||
      <!-- <span slot="footer" class="dialog-footer">
 | 
			
		||||
        <el-button @click="dialogVisible = false">取 消</el-button>
 | 
			
		||||
        <el-button type="primary" @click="dialogVisible = false">确 定</el-button>
 | 
			
		||||
      </span>-->
 | 
			
		||||
    </el-dialog>
 | 
			
		||||
 | 
			
		||||
    <el-dialog @close="closeMonitor" title="监控信息" :visible.sync="monitorDialog.visible" width="60%">
 | 
			
		||||
      <monitor ref="monitorDialog" :machineId="monitorDialog.machineId" />
 | 
			
		||||
    </el-dialog>
 | 
			
		||||
 | 
			
		||||
    <!-- <FileManage
 | 
			
		||||
      :title="dialog.title"
 | 
			
		||||
      :visible.sync="dialog.visible"
 | 
			
		||||
      :machineId.sync="dialog.machineId"
 | 
			
		||||
    />-->
 | 
			
		||||
 | 
			
		||||
    <dynamic-form-dialog
 | 
			
		||||
      :visible.sync="formDialog.visible"
 | 
			
		||||
      :title="formDialog.title"
 | 
			
		||||
      :formInfo="formDialog.formInfo"
 | 
			
		||||
      :formData.sync="formDialog.formData"
 | 
			
		||||
      @submitSuccess="submitSuccess"
 | 
			
		||||
    ></dynamic-form-dialog>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import { Component, Vue } from 'vue-property-decorator'
 | 
			
		||||
import { DynamicFormDialog } from '@/components/dynamic-form'
 | 
			
		||||
import Monitor from './Monitor.vue'
 | 
			
		||||
import { machineApi } from './api'
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  name: 'MachineList',
 | 
			
		||||
  components: {
 | 
			
		||||
    DynamicFormDialog,
 | 
			
		||||
    Monitor,
 | 
			
		||||
  },
 | 
			
		||||
})
 | 
			
		||||
export default class MachineList extends Vue {
 | 
			
		||||
  data = {
 | 
			
		||||
    list: [],
 | 
			
		||||
    total: 10,
 | 
			
		||||
  }
 | 
			
		||||
  infoDialog = {
 | 
			
		||||
    visible: false,
 | 
			
		||||
    info: '',
 | 
			
		||||
  }
 | 
			
		||||
  monitorDialog = {
 | 
			
		||||
    visible: false,
 | 
			
		||||
    machineId: 0,
 | 
			
		||||
  }
 | 
			
		||||
  currentId = null
 | 
			
		||||
  currentData: any = null
 | 
			
		||||
  params = {
 | 
			
		||||
    pageNum: 1,
 | 
			
		||||
    pageSize: 10,
 | 
			
		||||
    host: null,
 | 
			
		||||
    clusterId: null,
 | 
			
		||||
  }
 | 
			
		||||
  dialog = {
 | 
			
		||||
    machineId: null,
 | 
			
		||||
    visible: false,
 | 
			
		||||
    title: '',
 | 
			
		||||
  }
 | 
			
		||||
  formDialog = {
 | 
			
		||||
    visible: false,
 | 
			
		||||
    title: '',
 | 
			
		||||
    formInfo: {
 | 
			
		||||
      createApi: machineApi.save,
 | 
			
		||||
      updateApi: machineApi.update,
 | 
			
		||||
      formRows: [
 | 
			
		||||
        [
 | 
			
		||||
          {
 | 
			
		||||
            type: 'input',
 | 
			
		||||
            label: '名称:',
 | 
			
		||||
            name: 'name',
 | 
			
		||||
            placeholder: '请输入名称',
 | 
			
		||||
            rules: [
 | 
			
		||||
              {
 | 
			
		||||
                required: true,
 | 
			
		||||
                message: '请输入名称',
 | 
			
		||||
                trigger: ['blur', 'change'],
 | 
			
		||||
              },
 | 
			
		||||
            ],
 | 
			
		||||
          },
 | 
			
		||||
        ],
 | 
			
		||||
        [
 | 
			
		||||
          {
 | 
			
		||||
            type: 'input',
 | 
			
		||||
            label: 'ip:',
 | 
			
		||||
            name: 'ip',
 | 
			
		||||
            placeholder: '请输入ip',
 | 
			
		||||
            rules: [
 | 
			
		||||
              {
 | 
			
		||||
                required: true,
 | 
			
		||||
                message: '请输入ip',
 | 
			
		||||
                trigger: ['blur', 'change'],
 | 
			
		||||
              },
 | 
			
		||||
            ],
 | 
			
		||||
          },
 | 
			
		||||
        ],
 | 
			
		||||
        [
 | 
			
		||||
          {
 | 
			
		||||
            type: 'input',
 | 
			
		||||
            label: '端口号:',
 | 
			
		||||
            name: 'port',
 | 
			
		||||
            placeholder: '请输入端口号',
 | 
			
		||||
            inputType: 'number',
 | 
			
		||||
            rules: [
 | 
			
		||||
              {
 | 
			
		||||
                required: true,
 | 
			
		||||
                message: '请输入ip',
 | 
			
		||||
                trigger: ['blur', 'change'],
 | 
			
		||||
              },
 | 
			
		||||
            ],
 | 
			
		||||
          },
 | 
			
		||||
        ],
 | 
			
		||||
        [
 | 
			
		||||
          {
 | 
			
		||||
            type: 'input',
 | 
			
		||||
            label: '用户名:',
 | 
			
		||||
            name: 'username',
 | 
			
		||||
            placeholder: '请输入用户名',
 | 
			
		||||
            rules: [
 | 
			
		||||
              {
 | 
			
		||||
                required: true,
 | 
			
		||||
                message: '请输入用户名',
 | 
			
		||||
                trigger: ['blur', 'change'],
 | 
			
		||||
              },
 | 
			
		||||
            ],
 | 
			
		||||
          },
 | 
			
		||||
        ],
 | 
			
		||||
        [
 | 
			
		||||
          {
 | 
			
		||||
            type: 'input',
 | 
			
		||||
            label: '密码:',
 | 
			
		||||
            name: 'password',
 | 
			
		||||
            placeholder: '请输入密码',
 | 
			
		||||
            inputType: 'password',
 | 
			
		||||
          },
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
    },
 | 
			
		||||
    formData: { port: 22 },
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  mounted() {
 | 
			
		||||
    this.search()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  choose(item: any) {
 | 
			
		||||
    if (!item) {
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
    this.currentId = item.id
 | 
			
		||||
    this.currentData = item
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async info(id: number) {
 | 
			
		||||
    const res = await machineApi.info.request({ id })
 | 
			
		||||
    this.infoDialog.info = res
 | 
			
		||||
    this.infoDialog.visible = true
 | 
			
		||||
    // res.data
 | 
			
		||||
    // this.$alert(res, '机器基本信息', {
 | 
			
		||||
    //   type: 'info',
 | 
			
		||||
    //   dangerouslyUseHTMLString: false,
 | 
			
		||||
    //   closeOnClickModal: true,
 | 
			
		||||
    //   showConfirmButton: false,
 | 
			
		||||
    // }).catch((r) => {
 | 
			
		||||
    //   console.log(r)
 | 
			
		||||
    // })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  monitor(id: number) {
 | 
			
		||||
    this.monitorDialog.machineId = id
 | 
			
		||||
    this.monitorDialog.visible = true
 | 
			
		||||
    // 如果重复打开同一个则开启定时任务
 | 
			
		||||
    const md: any = this.$refs['monitorDialog']
 | 
			
		||||
    if (md) {
 | 
			
		||||
      md.startInterval()
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  closeMonitor() {
 | 
			
		||||
    // 关闭窗口,取消定时任务
 | 
			
		||||
    const md: any = this.$refs['monitorDialog']
 | 
			
		||||
    md.cancelInterval()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  openFormDialog(redis: any) {
 | 
			
		||||
    let dialogTitle
 | 
			
		||||
    if (redis) {
 | 
			
		||||
      this.formDialog.formData = this.currentData
 | 
			
		||||
      dialogTitle = '编辑机器'
 | 
			
		||||
    } else {
 | 
			
		||||
      this.formDialog.formData = { port: 22 }
 | 
			
		||||
      dialogTitle = '添加机器'
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this.formDialog.title = dialogTitle
 | 
			
		||||
    this.formDialog.visible = true
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async deleteMachine(id: number) {
 | 
			
		||||
    await machineApi.del.request({ id })
 | 
			
		||||
    this.$message.success('操作成功')
 | 
			
		||||
    this.search()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fileManage(row: any) {
 | 
			
		||||
    this.dialog.machineId = row.id
 | 
			
		||||
    this.dialog.visible = true
 | 
			
		||||
    this.dialog.title = `${row.name} => ${row.ip}`
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  submitSuccess() {
 | 
			
		||||
    this.currentId = null
 | 
			
		||||
    ;(this.currentData = null), this.search()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async search() {
 | 
			
		||||
    const res = await machineApi.list.request(this.params)
 | 
			
		||||
    this.data = res
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style>
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										325
									
								
								mayfly-go-front/src/views/machine/Monitor.vue
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										325
									
								
								mayfly-go-front/src/views/machine/Monitor.vue
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,325 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div>
 | 
			
		||||
    <el-row>
 | 
			
		||||
      <el-col>
 | 
			
		||||
        <HomeCard desc="Base info" title="基础信息">
 | 
			
		||||
          <ActivePlate :infoList="infoCardData" />
 | 
			
		||||
        </HomeCard>
 | 
			
		||||
      </el-col>
 | 
			
		||||
    </el-row>
 | 
			
		||||
    <el-row :gutter="20">
 | 
			
		||||
      <el-col :lg="6" :md="24">
 | 
			
		||||
        <HomeCard desc="Task info" title="任务">
 | 
			
		||||
          <ChartPie :value.sync="taskData" />
 | 
			
		||||
        </HomeCard>
 | 
			
		||||
      </el-col>
 | 
			
		||||
      <el-col :lg="6" :md="24">
 | 
			
		||||
        <HomeCard desc="Mem info" title="内存">
 | 
			
		||||
          <ChartPie :value.sync="memData" />
 | 
			
		||||
        </HomeCard>
 | 
			
		||||
      </el-col>
 | 
			
		||||
      <el-col :lg="6" :md="24">
 | 
			
		||||
        <HomeCard desc="Swap info" title="CPU">
 | 
			
		||||
          <ChartPie :value.sync="cpuData" />
 | 
			
		||||
        </HomeCard>
 | 
			
		||||
      </el-col>
 | 
			
		||||
    </el-row>
 | 
			
		||||
 | 
			
		||||
    <!-- <el-row :gutter="20">
 | 
			
		||||
      <el-col :lg="18" :md="24">
 | 
			
		||||
        <HomeCard desc="User active" title="每周用户活跃量">
 | 
			
		||||
          <ChartLine :value="lineData" />
 | 
			
		||||
        </HomeCard>
 | 
			
		||||
      </el-col>
 | 
			
		||||
    </el-row>-->
 | 
			
		||||
 | 
			
		||||
    <el-row :gutter="20">
 | 
			
		||||
      <el-col :lg="12" :md="24">
 | 
			
		||||
        <ChartContinuou :value="this.data" title="内存" />
 | 
			
		||||
      </el-col>
 | 
			
		||||
      <el-col :lg="12" :md="24">
 | 
			
		||||
        <ChartContinuou  :value="this.data" title="CPU" />
 | 
			
		||||
      </el-col>
 | 
			
		||||
    </el-row>
 | 
			
		||||
 | 
			
		||||
    <el-row :gutter="20">
 | 
			
		||||
      <el-col :lg="12" :md="24">
 | 
			
		||||
        <HomeCard desc="load info" title="负载情况">
 | 
			
		||||
          <BaseChart :option="this.loadChartOption" />
 | 
			
		||||
        </HomeCard>
 | 
			
		||||
      </el-col>
 | 
			
		||||
      <el-col :lg="12" :md="24">
 | 
			
		||||
        <ChartContinuou :value="this.data" title="磁盘IO" />
 | 
			
		||||
      </el-col>
 | 
			
		||||
    </el-row>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import { Component, Vue, Prop, Watch } from 'vue-property-decorator'
 | 
			
		||||
import ActivePlate from '@/components/chart/ActivePlate.vue'
 | 
			
		||||
import HomeCard from '@/components/chart/Card.vue'
 | 
			
		||||
import ChartPie from '@/components/chart/ChartPie.vue'
 | 
			
		||||
import ChartLine from '@/components/chart/ChartLine.vue'
 | 
			
		||||
import ChartGauge from '@/components/chart/ChartGauge.vue'
 | 
			
		||||
import ChartBar from '@/components/chart/ChartBar.vue'
 | 
			
		||||
import ChartFunnel from '@/components/chart/ChartFunnel.vue'
 | 
			
		||||
import ChartContinuou from '@/components/chart/ChartContinuou.vue'
 | 
			
		||||
import BaseChart from '@/components/chart/BaseChart.vue'
 | 
			
		||||
import { machineApi } from './api'
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  name: 'Monitor',
 | 
			
		||||
  components: {
 | 
			
		||||
    HomeCard,
 | 
			
		||||
    ActivePlate,
 | 
			
		||||
    ChartPie,
 | 
			
		||||
    ChartFunnel,
 | 
			
		||||
    ChartLine,
 | 
			
		||||
    ChartGauge,
 | 
			
		||||
    ChartBar,
 | 
			
		||||
    ChartContinuou,
 | 
			
		||||
    BaseChart,
 | 
			
		||||
  },
 | 
			
		||||
})
 | 
			
		||||
export default class Monitor extends Vue {
 | 
			
		||||
  @Prop()
 | 
			
		||||
  machineId: number
 | 
			
		||||
 | 
			
		||||
  timer: number
 | 
			
		||||
 | 
			
		||||
  infoCardData = [
 | 
			
		||||
    {
 | 
			
		||||
      title: 'total task',
 | 
			
		||||
      icon: 'md-person-add',
 | 
			
		||||
      count: 0,
 | 
			
		||||
      color: '#11A0F8',
 | 
			
		||||
    },
 | 
			
		||||
    { title: '总内存', icon: 'md-locate', count: '', color: '#FFBB44 ' },
 | 
			
		||||
    {
 | 
			
		||||
      title: '可用内存',
 | 
			
		||||
      icon: 'md-help-circle',
 | 
			
		||||
      count: '',
 | 
			
		||||
      color: '#7ACE4C',
 | 
			
		||||
    },
 | 
			
		||||
    { title: '空闲交换空间', icon: 'md-share', count: 657, color: '#11A0F8' },
 | 
			
		||||
    {
 | 
			
		||||
      title: '使用中交换空间',
 | 
			
		||||
      icon: 'md-chatbubbles',
 | 
			
		||||
      count: 12,
 | 
			
		||||
      color: '#91AFC8',
 | 
			
		||||
    },
 | 
			
		||||
    { title: '新增页面', icon: 'md-map', count: 14, color: '#91AFC8' },
 | 
			
		||||
  ]
 | 
			
		||||
  taskData = [
 | 
			
		||||
    { value: 0, name: '运行中', color: '#3AA1FFB' },
 | 
			
		||||
    { value: 0, name: '睡眠中', color: '#36CBCB' },
 | 
			
		||||
    { value: 0, name: '结束', color: '#4ECB73' },
 | 
			
		||||
    { value: 0, name: '僵尸', color: '#F47F92' },
 | 
			
		||||
  ]
 | 
			
		||||
 | 
			
		||||
  memData = [
 | 
			
		||||
    { value: 0, name: '空闲', color: '#3AA1FFB' },
 | 
			
		||||
    { value: 0, name: '使用中', color: '#36CBCB' },
 | 
			
		||||
    { value: 0, name: '缓存', color: '#4ECB73' },
 | 
			
		||||
  ]
 | 
			
		||||
 | 
			
		||||
  swapData = [
 | 
			
		||||
    { value: 0, name: '空闲', color: '#3AA1FFB' },
 | 
			
		||||
    { value: 0, name: '使用中', color: '#36CBCB' },
 | 
			
		||||
  ]
 | 
			
		||||
 | 
			
		||||
  cpuData = [
 | 
			
		||||
    { value: 0, name: '用户空间', color: '#3AA1FFB' },
 | 
			
		||||
    { value: 0, name: '内核空间', color: '#36CBCB' },
 | 
			
		||||
    { value: 0, name: '改变优先级', color: '#4ECB73' },
 | 
			
		||||
    { value: 0, name: '空闲率', color: '#4ECB73' },
 | 
			
		||||
    { value: 0, name: '等待IO', color: '#4ECB73' },
 | 
			
		||||
    { value: 0, name: '硬中断', color: '#4ECB73' },
 | 
			
		||||
    { value: 0, name: '软中断', color: '#4ECB73' },
 | 
			
		||||
    { value: 0, name: '虚拟机', color: '#4ECB73' },
 | 
			
		||||
  ]
 | 
			
		||||
  data = [
 | 
			
		||||
    ['06/05 15:01', 116.12],
 | 
			
		||||
    ['06/05 15:06', 129.21],
 | 
			
		||||
    ['06/05 15:11', 135.43],
 | 
			
		||||
    ['2000-06-08', 86.33],
 | 
			
		||||
    ['2000-06-09', 73.98],
 | 
			
		||||
    ['2000-06-10', 85],
 | 
			
		||||
    ['2000-06-11', 73],
 | 
			
		||||
    ['2000-06-12', 68],
 | 
			
		||||
    ['2000-06-13', 92],
 | 
			
		||||
    ['2000-06-14', 130],
 | 
			
		||||
    ['2000-06-15', 245],
 | 
			
		||||
    ['2000-06-16', 139],
 | 
			
		||||
    ['2000-06-17', 115],
 | 
			
		||||
    ['2000-06-18', 111],
 | 
			
		||||
    ['2000-06-19', 309],
 | 
			
		||||
    ['2000-06-20', 206],
 | 
			
		||||
    ['2000-06-21', 137],
 | 
			
		||||
    ['2000-06-22', 128],
 | 
			
		||||
    ['2000-06-23', 85],
 | 
			
		||||
    ['2000-06-24', 94],
 | 
			
		||||
    ['2000-06-25', 71],
 | 
			
		||||
    ['2000-06-26', 106],
 | 
			
		||||
    ['2000-06-27', 84],
 | 
			
		||||
    ['2000-06-28', 93],
 | 
			
		||||
    ['2000-06-29', 85],
 | 
			
		||||
    ['2000-06-30', 73],
 | 
			
		||||
    ['2000-07-01', 83],
 | 
			
		||||
    ['2000-07-02', 125],
 | 
			
		||||
    ['2000-07-03', 107],
 | 
			
		||||
    ['2000-07-04', 82],
 | 
			
		||||
    ['2000-07-05', 44],
 | 
			
		||||
    ['2000-07-06', 72],
 | 
			
		||||
    ['2000-07-07', 106],
 | 
			
		||||
    ['2000-07-08', 107],
 | 
			
		||||
    ['2000-07-09', 66],
 | 
			
		||||
    ['2000-07-10', 91],
 | 
			
		||||
    ['2000-07-11', 92],
 | 
			
		||||
    ['2000-07-12', 113],
 | 
			
		||||
    ['2000-07-13', 107],
 | 
			
		||||
    ['2000-07-14', 131],
 | 
			
		||||
    ['2000-07-15', 111],
 | 
			
		||||
    ['2000-07-16', 64],
 | 
			
		||||
    ['2000-07-17', 69],
 | 
			
		||||
    ['2000-07-18', 88],
 | 
			
		||||
    ['2000-07-19', 77],
 | 
			
		||||
    ['2000-07-20', 83],
 | 
			
		||||
    ['2000-07-21', 111],
 | 
			
		||||
    ['2000-07-22', 57],
 | 
			
		||||
    ['2000-07-23', 55],
 | 
			
		||||
    ['2000-07-24', 60],
 | 
			
		||||
  ]
 | 
			
		||||
 | 
			
		||||
  dateList = this.data.map(function (item) {
 | 
			
		||||
    return item[0]
 | 
			
		||||
  })
 | 
			
		||||
  valueList = this.data.map(function (item) {
 | 
			
		||||
    return item[1]
 | 
			
		||||
  })
 | 
			
		||||
  loadChartOption = {
 | 
			
		||||
    // Make gradient line here
 | 
			
		||||
    visualMap: [
 | 
			
		||||
      {
 | 
			
		||||
        show: false,
 | 
			
		||||
        type: 'continuous',
 | 
			
		||||
        seriesIndex: 0,
 | 
			
		||||
        min: 0,
 | 
			
		||||
        max: 400,
 | 
			
		||||
      },
 | 
			
		||||
    ],
 | 
			
		||||
    legend: {
 | 
			
		||||
      data: ['1分钟', '5分钟', '15分钟'],
 | 
			
		||||
    },
 | 
			
		||||
    tooltip: {
 | 
			
		||||
      trigger: 'axis',
 | 
			
		||||
    },
 | 
			
		||||
    xAxis: [
 | 
			
		||||
      {
 | 
			
		||||
        data: this.dateList,
 | 
			
		||||
      },
 | 
			
		||||
    ],
 | 
			
		||||
    yAxis: [
 | 
			
		||||
      {
 | 
			
		||||
        splitLine: { show: false },
 | 
			
		||||
      },
 | 
			
		||||
    ],
 | 
			
		||||
    grid: [{}],
 | 
			
		||||
    series: [
 | 
			
		||||
      {
 | 
			
		||||
        name: '1分钟',
 | 
			
		||||
        type: 'line',
 | 
			
		||||
        showSymbol: false,
 | 
			
		||||
        data: this.valueList,
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        name: '5分钟',
 | 
			
		||||
        type: 'line',
 | 
			
		||||
        showSymbol: false,
 | 
			
		||||
        data: [100, 22, 33, 121, 32, 332, 322, 222, 232],
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        name: '15分钟',
 | 
			
		||||
        type: 'line',
 | 
			
		||||
        showSymbol: true,
 | 
			
		||||
        data: [130, 222, 373, 135, 456, 332, 333, 343, 342],
 | 
			
		||||
      },
 | 
			
		||||
    ],
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  lineData = {
 | 
			
		||||
    Mon: 13253,
 | 
			
		||||
    Tue: 34235,
 | 
			
		||||
    Wed: 26321,
 | 
			
		||||
    Thu: 12340,
 | 
			
		||||
    Fri: 24643,
 | 
			
		||||
    Sat: 1322,
 | 
			
		||||
    Sun: 1324,
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Watch('machineId', { deep: true })
 | 
			
		||||
  onDataChange() {
 | 
			
		||||
    if (this.machineId) {
 | 
			
		||||
      this.intervalGetTop()
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  mounted() {
 | 
			
		||||
    this.intervalGetTop()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  beforeDestroy() {
 | 
			
		||||
    this.cancelInterval()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  cancelInterval() {
 | 
			
		||||
    clearInterval(this.timer)
 | 
			
		||||
    this.timer = 0
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  startInterval() {
 | 
			
		||||
    if (!this.timer) {
 | 
			
		||||
      this.timer = setInterval(this.getTop, 3000)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  intervalGetTop() {
 | 
			
		||||
    this.getTop()
 | 
			
		||||
    this.startInterval()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async getTop() {
 | 
			
		||||
    const topInfo = await machineApi.top.request({ id: this.machineId })
 | 
			
		||||
    this.infoCardData[0].count = topInfo.totalTask
 | 
			
		||||
    this.infoCardData[1].count = Math.round(topInfo.totalMem / 1024) + 'M'
 | 
			
		||||
    this.infoCardData[2].count = Math.round(topInfo.availMem / 1024) + 'M'
 | 
			
		||||
    this.infoCardData[3].count = Math.round(topInfo.freeSwap / 1024) + 'M'
 | 
			
		||||
    this.infoCardData[4].count = Math.round(topInfo.usedSwap / 1024) + 'M'
 | 
			
		||||
 | 
			
		||||
    this.taskData[0].value = topInfo.runningTask
 | 
			
		||||
    this.taskData[1].value = topInfo.sleepingTask
 | 
			
		||||
    this.taskData[2].value = topInfo.stoppedTask
 | 
			
		||||
    this.taskData[3].value = topInfo.zombieTask
 | 
			
		||||
 | 
			
		||||
    this.memData[0].value = Math.round(topInfo.freeMem / 1024)
 | 
			
		||||
    this.memData[1].value = Math.round(topInfo.usedMem / 1024)
 | 
			
		||||
    this.memData[2].value = Math.round(topInfo.cacheMem / 1024)
 | 
			
		||||
 | 
			
		||||
    this.cpuData[0].value = topInfo.cpuUs
 | 
			
		||||
    this.cpuData[1].value = topInfo.cpuSy
 | 
			
		||||
    this.cpuData[2].value = topInfo.cpuNi
 | 
			
		||||
    this.cpuData[3].value = topInfo.cpuId
 | 
			
		||||
    this.cpuData[4].value = topInfo.cpuWa
 | 
			
		||||
    this.cpuData[5].value = topInfo.cpuHi
 | 
			
		||||
    this.cpuData[6].value = topInfo.cpuSi
 | 
			
		||||
    this.cpuData[7].value = topInfo.cpuSt
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="less">
 | 
			
		||||
.count-style {
 | 
			
		||||
  font-size: 50px;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										25
									
								
								mayfly-go-front/src/views/machine/api.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								mayfly-go-front/src/views/machine/api.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
			
		||||
import Api from '@/common/Api';
 | 
			
		||||
 | 
			
		||||
export const machineApi = {
 | 
			
		||||
    // 获取权限列表
 | 
			
		||||
    list: Api.create("/machines", 'get'),
 | 
			
		||||
    info: Api.create("/machines/{id}/sysinfo", 'get'),
 | 
			
		||||
    top: Api.create("/machines/{id}/top", 'get'),
 | 
			
		||||
    // 保存按钮
 | 
			
		||||
    save: Api.create("/devops/machines", 'post'),
 | 
			
		||||
    update: Api.create("/devops/machines/{id}", 'put'),
 | 
			
		||||
    // 删除机器
 | 
			
		||||
    del: Api.create("/devops/machines/{id}", 'delete'),
 | 
			
		||||
    // 获取配置文件列表
 | 
			
		||||
    files: Api.create("/devops/machines/{id}/files", 'get'),
 | 
			
		||||
    lsFile: Api.create("/devops/machines/files/{fileId}/ls", 'get'),
 | 
			
		||||
    rmFile: Api.create("/devops/machines/files/{fileId}/rm", 'delete'),
 | 
			
		||||
    uploadFile: Api.create("/devops/machines/files/upload", 'post'),
 | 
			
		||||
    fileContent: Api.create("/devops/machines/files/{fileId}/cat", 'get'),
 | 
			
		||||
    // 修改文件内容
 | 
			
		||||
    updateFileContent: Api.create("/devops/machines/files/{id}", 'put'),
 | 
			
		||||
    // 添加文件or目录
 | 
			
		||||
    addConf: Api.create("/devops/machines/{machineId}/files", 'post'),
 | 
			
		||||
    // 删除配置的文件or目录
 | 
			
		||||
    delConf: Api.create("/devops/machines/files/{id}", 'delete'),
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1
									
								
								mayfly-go-front/src/views/machine/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								mayfly-go-front/src/views/machine/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
export { default } from './MachineList.vue';
 | 
			
		||||
							
								
								
									
										45
									
								
								mayfly-go-front/tsconfig.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								mayfly-go-front/tsconfig.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,45 @@
 | 
			
		||||
{
 | 
			
		||||
  "compilerOptions": {
 | 
			
		||||
    "target": "esnext",
 | 
			
		||||
    "module": "esnext",
 | 
			
		||||
    "strict": true,
 | 
			
		||||
    "jsx": "preserve",
 | 
			
		||||
    "importHelpers": true,
 | 
			
		||||
    "moduleResolution": "node",
 | 
			
		||||
    "experimentalDecorators": true,
 | 
			
		||||
    // 定义一个变量就必须给它一个初始值
 | 
			
		||||
    "strictPropertyInitialization": false,
 | 
			
		||||
    "suppressImplicitAnyIndexErrors": true,
 | 
			
		||||
     // 允许编译javascript文件
 | 
			
		||||
     "allowJs": true,
 | 
			
		||||
    "skipLibCheck": true,
 | 
			
		||||
    "esModuleInterop": true,
 | 
			
		||||
    "allowSyntheticDefaultImports": true,
 | 
			
		||||
    "sourceMap": true,
 | 
			
		||||
    "baseUrl": ".",
 | 
			
		||||
    "types": [
 | 
			
		||||
      "webpack-env"
 | 
			
		||||
    ],
 | 
			
		||||
    "paths": {
 | 
			
		||||
      "@/*": [
 | 
			
		||||
        "src/*"
 | 
			
		||||
      ]
 | 
			
		||||
    },
 | 
			
		||||
    "lib": [
 | 
			
		||||
      "esnext",
 | 
			
		||||
      "dom",
 | 
			
		||||
      "dom.iterable",
 | 
			
		||||
      "scripthost"
 | 
			
		||||
    ]
 | 
			
		||||
  },
 | 
			
		||||
  "include": [
 | 
			
		||||
    "src/**/*.ts",
 | 
			
		||||
    "src/**/*.tsx",
 | 
			
		||||
    "src/**/*.vue",
 | 
			
		||||
    "tests/**/*.ts",
 | 
			
		||||
    "tests/**/*.tsx"
 | 
			
		||||
  ],
 | 
			
		||||
  "exclude": [
 | 
			
		||||
    "node_modules"
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										80
									
								
								mayfly-go-front/vue.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								mayfly-go-front/vue.config.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,80 @@
 | 
			
		||||
const merge = require("webpack-merge");
 | 
			
		||||
const tsImportPluginFactory = require("ts-import-plugin");
 | 
			
		||||
const path = require('path')
 | 
			
		||||
 | 
			
		||||
function resolve(dir) {
 | 
			
		||||
  return path.join(__dirname, dir)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// If your port is set to 80,
 | 
			
		||||
// use administrator privileges to execute the command line.
 | 
			
		||||
// For example, Mac: sudo npm run
 | 
			
		||||
// You can change the port by the following method:
 | 
			
		||||
// port = 8000 npm run dev OR npm run dev --port = 8000
 | 
			
		||||
const port = process.env.port || process.env.npm_config_port || 8000 // dev port
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
  publicPath: '/',
 | 
			
		||||
  outputDir: 'dist',
 | 
			
		||||
  assetsDir: 'static',
 | 
			
		||||
  lintOnSave: false,
 | 
			
		||||
  productionSourceMap: false,
 | 
			
		||||
  devServer: {
 | 
			
		||||
    port: port,
 | 
			
		||||
    open: true,
 | 
			
		||||
    overlay: {
 | 
			
		||||
      warnings: false,
 | 
			
		||||
      errors: true
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  configureWebpack: {
 | 
			
		||||
    // provide the app's title in webpack's name field, so that
 | 
			
		||||
    // it can be accessed in index.html to inject the correct title.
 | 
			
		||||
    name: 'eatlife',
 | 
			
		||||
    resolve: {
 | 
			
		||||
      alias: {
 | 
			
		||||
        '@': resolve('src')
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  transpileDependencies: [
 | 
			
		||||
    'vue-echarts',
 | 
			
		||||
    'resize-detector'
 | 
			
		||||
  ],
 | 
			
		||||
  chainWebpack: config => {
 | 
			
		||||
    config.module
 | 
			
		||||
      .rule("ts")
 | 
			
		||||
      .use("ts-loader")
 | 
			
		||||
      .tap(options => {
 | 
			
		||||
        options = merge(options, {
 | 
			
		||||
          transpileOnly: true,
 | 
			
		||||
          getCustomTransformers: () => ({
 | 
			
		||||
            before: [
 | 
			
		||||
              tsImportPluginFactory({
 | 
			
		||||
                libraryName: "vant",
 | 
			
		||||
                libraryDirectory: "es",
 | 
			
		||||
                style: true
 | 
			
		||||
              })
 | 
			
		||||
            ]
 | 
			
		||||
          }),
 | 
			
		||||
          compilerOptions: {
 | 
			
		||||
            module: "es2015"
 | 
			
		||||
          }
 | 
			
		||||
        });
 | 
			
		||||
        return options;
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
    // 自动注入通用的scss,不需要自己在每个文件里手动注入
 | 
			
		||||
    // const types = ['vue-modules', 'vue', 'normal-modules', 'normal']
 | 
			
		||||
    // types.forEach(type => {
 | 
			
		||||
    //   config.module.rule('scss').oneOf(type)
 | 
			
		||||
    //   .use('sass-resource')
 | 
			
		||||
    //   .loader('sass-resources-loader')
 | 
			
		||||
    //   .options({
 | 
			
		||||
    //     resources: [
 | 
			
		||||
    //       path.resolve(__dirname, './src/assets/styles/global.scss'),
 | 
			
		||||
    //     ],
 | 
			
		||||
    //   });
 | 
			
		||||
    // });
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										25
									
								
								models/account.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								models/account.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
			
		||||
package models
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/astaxie/beego/orm"
 | 
			
		||||
	"mayfly-go/base"
 | 
			
		||||
	"mayfly-go/controllers/vo"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Account struct {
 | 
			
		||||
	base.Model
 | 
			
		||||
 | 
			
		||||
	Username string `orm:"column(username)" json:"username"`
 | 
			
		||||
	Password string `orm:"column(password)" json:"-"`
 | 
			
		||||
	Status   int8   `json:"status"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	orm.RegisterModelWithPrefix("t_", new(Account))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ListAccount(param *base.PageParam, args ...interface{}) base.PageResult {
 | 
			
		||||
	sql := "SELECT a.id, a.username, a.create_time, a.creator_id, a.creator, r.Id AS 'Role.Id', r.Name AS 'Role.Name'" +
 | 
			
		||||
		" FROM t_account a LEFT JOIN t_role r ON a.id = r.account_id"
 | 
			
		||||
	return base.GetPageBySql(sql, new([]vo.AccountVO), param, args)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										45
									
								
								models/machine.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								models/machine.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,45 @@
 | 
			
		||||
package models
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/astaxie/beego/orm"
 | 
			
		||||
	"mayfly-go/base"
 | 
			
		||||
	"mayfly-go/controllers/vo"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Machine struct {
 | 
			
		||||
	base.Model
 | 
			
		||||
	Name string `orm:"column(name)"`
 | 
			
		||||
	// IP地址
 | 
			
		||||
	Ip string `orm:"column(ip)" json:"ip"`
 | 
			
		||||
	// 用户名
 | 
			
		||||
	Username string `orm:"column(username)" json:"username"`
 | 
			
		||||
	Password string `orm:"column(password)" json:"-"`
 | 
			
		||||
	// 端口号
 | 
			
		||||
	Port int `orm:"column(port)" json:"port"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	orm.RegisterModelWithPrefix("t_", new(Machine))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetMachineById(id uint64) *Machine {
 | 
			
		||||
	machine := new(Machine)
 | 
			
		||||
	machine.Id = id
 | 
			
		||||
	err := base.GetBy(machine)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	return machine
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 分页获取机器信息列表
 | 
			
		||||
func GetMachineList(pageParam *base.PageParam) base.PageResult {
 | 
			
		||||
	m := new([]Machine)
 | 
			
		||||
	querySetter := base.QuerySetter(new(Machine)).OrderBy("-Id")
 | 
			
		||||
	return base.GetPage(querySetter, pageParam, m, new([]vo.MachineVO))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 获取所有需要监控的机器信息列表
 | 
			
		||||
func GetNeedMonitorMachine() *[]orm.Params {
 | 
			
		||||
	return base.GetListBySql("SELECT id FROM t_machine WHERE need_monitor = 1")
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										19
									
								
								models/machine_monitor.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								models/machine_monitor.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
			
		||||
package models
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/astaxie/beego/orm"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type MachineMonitor struct {
 | 
			
		||||
	Id         uint64    `orm:"column(id)" json:"id"`
 | 
			
		||||
	MachineId  uint64    `orm:"column(machine_id)" json:"machineId"`
 | 
			
		||||
	CpuRate    float32   `orm:"column(cpu_rate)" json:"cpuRate"`
 | 
			
		||||
	MemRate    float32   `orm:"column(mem_rate)" json:"memRate"`
 | 
			
		||||
	SysLoad    string    `orm:"column(sys_load)" json:"sysLoad"`
 | 
			
		||||
	CreateTime time.Time `orm:"column(create_time)" json:"createTime"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	orm.RegisterModelWithPrefix("t_", new(MachineMonitor))
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										19
									
								
								models/role.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								models/role.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
			
		||||
package models
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/astaxie/beego/orm"
 | 
			
		||||
	"mayfly-go/base"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Role struct {
 | 
			
		||||
	base.Model
 | 
			
		||||
 | 
			
		||||
	Name string `orm:"column(name)" json:"username"`
 | 
			
		||||
	//AccountId int64  `orm:"column(account_id)`
 | 
			
		||||
 | 
			
		||||
	Account *Account `orm:"rel(fk);index"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	orm.RegisterModelWithPrefix("t_", new(Role))
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										23
									
								
								routers/router.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								routers/router.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
			
		||||
package routers
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/astaxie/beego"
 | 
			
		||||
	"mayfly-go/controllers"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	//beego.Router("/account/login", &controllers.LoginController{})
 | 
			
		||||
	//beego.Router("/account", &controllers.AccountController{})
 | 
			
		||||
	//beego.Include(&controllers.AccountController{})
 | 
			
		||||
	//beego.Include()
 | 
			
		||||
	beego.Router("/api/accounts/login", &controllers.AccountController{}, "post:Login")
 | 
			
		||||
	beego.Router("/api/accounts", &controllers.AccountController{}, "get:Accounts")
 | 
			
		||||
 | 
			
		||||
	machine := &controllers.MachineController{}
 | 
			
		||||
	beego.Router("/api/machines", machine, "get:Machines")
 | 
			
		||||
	beego.Router("/api/machines/?:machineId/run", machine, "get:Run")
 | 
			
		||||
	beego.Router("/api/machines/?:machineId/top", machine, "get:Top")
 | 
			
		||||
	beego.Router("/api/machines/?:machineId/sysinfo", machine, "get:SysInfo")
 | 
			
		||||
	beego.Router("/api/machines/?:machineId/process", machine, "get:GetProcessByName")
 | 
			
		||||
	//beego.Router("/machines/?:machineId/ws", machine, "get:WsSSH")
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										30
									
								
								scheudler/mytask.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								scheudler/mytask.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
			
		||||
package scheduler
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/siddontang/go/log"
 | 
			
		||||
	"mayfly-go/base"
 | 
			
		||||
	"mayfly-go/base/utils"
 | 
			
		||||
	"mayfly-go/machine"
 | 
			
		||||
	"mayfly-go/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	SaveMachineMonitor()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func SaveMachineMonitor() {
 | 
			
		||||
	AddFun("@every 60s", func() {
 | 
			
		||||
		for _, m := range *models.GetNeedMonitorMachine() {
 | 
			
		||||
			m := m
 | 
			
		||||
			go func() {
 | 
			
		||||
				mm := machine.GetMonitorInfo(machine.GetCli(uint64(utils.GetInt4Map(m, "id"))))
 | 
			
		||||
				if mm != nil {
 | 
			
		||||
					err := base.Insert(mm)
 | 
			
		||||
					if err != nil {
 | 
			
		||||
						log.Error("保存机器监控信息失败: %s", err.Error())
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}()
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										28
									
								
								scheudler/scheduler.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								scheudler/scheduler.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
			
		||||
package scheduler
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/robfig/cron/v3"
 | 
			
		||||
	"mayfly-go/base"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var c = cron.New()
 | 
			
		||||
 | 
			
		||||
func Start() {
 | 
			
		||||
	c.Start()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Stop() {
 | 
			
		||||
	c.Stop()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetCron() *cron.Cron {
 | 
			
		||||
	return c
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func AddFun(spec string, cmd func()) cron.EntryID {
 | 
			
		||||
	id, err := c.AddFunc(spec, cmd)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic(base.NewBizErr("添加任务失败:" + err.Error()))
 | 
			
		||||
	}
 | 
			
		||||
	return id
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								static/favicon.ico
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								static/favicon.ico
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 4.2 KiB  | 
							
								
								
									
										1
									
								
								static/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								static/index.html
									
									
									
									
									
										Normal 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-6c422708.2d81c5bb.css rel=prefetch><link href=/static/js/chunk-4a3c1aef.94cc2a02.js rel=prefetch><link href=/static/js/chunk-6c422708.a09466dd.js rel=prefetch><link href=/static/js/chunk-945da412.570aca5d.js rel=prefetch><link href=/static/css/app.e8323368.css rel=preload as=style><link href=/static/css/chunk-vendors.08810481.css rel=preload as=style><link href=/static/js/app.aa8651f8.js rel=preload as=script><link href=/static/js/chunk-vendors.a6a99ea9.js rel=preload as=script><link href=/static/css/chunk-vendors.08810481.css rel=stylesheet><link href=/static/css/app.e8323368.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.a6a99ea9.js></script><script src=/static/js/app.aa8651f8.js></script></body></html>
 | 
			
		||||
							
								
								
									
										1
									
								
								static/static/css/app.e8323368.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								static/static/css/app.e8323368.css
									
									
									
									
									
										Normal 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{-webkit-filter:grayscale(100%);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}
 | 
			
		||||
							
								
								
									
										1
									
								
								static/static/css/chunk-6c422708.2d81c5bb.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								static/static/css/chunk-6c422708.2d81c5bb.css
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
.login{display:flex;justify-content:center;align-items:center;position:absolute;height:100%;width:100%;background-color:#e4e5e6}.login .login-form{width:375px;height:435px;padding:30px;background-color:#fff;text-align:left;border-radius:4px;position:relative;margin-left:0;margin-right:0;zoom:1;display:block}.login .login-form .login-header{text-align:center;font-size:16px;font-weight:700;margin-bottom:20px}
 | 
			
		||||
							
								
								
									
										1
									
								
								static/static/css/chunk-vendors.08810481.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								static/static/css/chunk-vendors.08810481.css
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								static/static/fonts/element-icons.535877f5.woff
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								static/static/fonts/element-icons.535877f5.woff
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								static/static/fonts/element-icons.732389de.ttf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								static/static/fonts/element-icons.732389de.ttf
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								static/static/img/logo.e92f231a.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								static/static/img/logo.e92f231a.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 38 KiB  | 
							
								
								
									
										1
									
								
								static/static/js/app.aa8651f8.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								static/static/js/app.aa8651f8.js
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										1
									
								
								static/static/js/chunk-4a3c1aef.94cc2a02.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								static/static/js/chunk-4a3c1aef.94cc2a02.js
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										1
									
								
								static/static/js/chunk-6c422708.a09466dd.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								static/static/js/chunk-6c422708.a09466dd.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-6c422708"],{"9d64":function(e,t,n){e.exports=n.p+"static/img/logo.e92f231a.png"},a248:function(e,t,n){"use strict";var r=n("df3e"),a=n.n(r);a.a},df3e:function(e,t,n){},ede4:function(e,t,n){"use strict";n.r(t);var r=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",{staticClass:"login"},[n("div",{staticClass:"login-form"},[e._m(0),n("el-input",{staticStyle:{"margin-bottom":"18px"},attrs:{placeholder:"请输入用户名","suffix-icon":"fa fa-user"},model:{value:e.loginForm.username,callback:function(t){e.$set(e.loginForm,"username",t)},expression:"loginForm.username"}}),n("el-input",{staticStyle:{"margin-bottom":"18px"},attrs:{placeholder:"请输入密码","suffix-icon":"fa fa-keyboard-o",type:"password",autocomplete:"new-password"},model:{value:e.loginForm.password,callback:function(t){e.$set(e.loginForm,"password",t)},expression:"loginForm.password"}}),n("el-button",{staticStyle:{width:"100%","margin-bottom":"18px"},attrs:{type:"primary",loading:e.loginLoading},nativeOn:{click:function(t){return e.login(t)}}},[e._v("登录")]),n("div",[n("el-checkbox",{model:{value:e.remember,callback:function(t){e.remember=t},expression:"remember"}},[e._v("记住密码")])],1)],1)])},a=[function(){var e=this,t=e.$createElement,r=e._self._c||t;return r("div",{staticClass:"login-header"},[r("img",{attrs:{src:n("9d64"),width:"150",height:"120",alt:""}})])}],o=(n("6a61"),n("cf7f")),i=n("1462"),s=n("a340"),u=n("bb06"),c=n("9691"),l=n("0372"),m=n("d789"),g={login:function(e){return m["a"].request("POST","/accounts/login",e,null)},captcha:function(){return m["a"].request("GET","/open/captcha",null,null)},logout:function(e){return m["a"].request("POST","/sys/accounts/logout/{token}",e,null)}},p=n("e4a1"),f=n("79cb"),d=function(e){Object(u["a"])(n,e);var t=Object(c["a"])(n);function n(){var e;return Object(i["a"])(this,n),e=t.apply(this,arguments),e.loginForm={username:"",password:"",uuid:""},e.remember=!1,e.loginLoading=!1,e}return Object(s["a"])(n,[{key:"mounted",value:function(){var e,t=this.getRemember();null!=t&&(e=JSON.parse(t)),e?(this.remember=!0,this.loginForm.username=e.username,this.loginForm.password=e.password):this.remember=!1}},{key:"getCaptcha",value:function(){var e=Object(o["a"])(regeneratorRuntime.mark((function e(){var t;return regeneratorRuntime.wrap((function(e){while(1)switch(e.prev=e.next){case 0:return e.next=2,g.captcha();case 2:t=e.sent,this.loginForm.uuid=t.uuid;case 4:case"end":return e.stop()}}),e,this)})));function t(){return e.apply(this,arguments)}return t}()},{key:"login",value:function(){var e=Object(o["a"])(regeneratorRuntime.mark((function e(){var t,n=this;return regeneratorRuntime.wrap((function(e){while(1)switch(e.prev=e.next){case 0:return this.loginLoading=!0,e.prev=1,e.next=4,g.login(this.loginForm);case 4:t=e.sent,this.remember?localStorage.setItem("remember",JSON.stringify(this.loginForm)):localStorage.removeItem("remember"),setTimeout((function(){f["a"].saveToken(t.token),n.$notify({title:"登录成功",message:"很高兴你使用Mayfly Admin!别忘了给个Star哦。",type:"success"}),n.loginLoading=!1;var e=n.$route.query.redirect;e?n.$router.push(e):n.$router.push({path:"/"})}),500),e.next=12;break;case 9:e.prev=9,e.t0=e["catch"](1),this.loginLoading=!1;case 12:case"end":return e.stop()}}),e,this,[[1,9]])})));function t(){return e.apply(this,arguments)}return t}()},{key:"getRemember",value:function(){return localStorage.getItem("remember")}}]),n}(p["c"]);d=Object(l["a"])([Object(p["a"])({name:"Login"})],d);var h=d,b=h,v=(n("a248"),n("9ca4")),w=Object(v["a"])(b,r,a,!1,null,null,null);t["default"]=w.exports}}]);
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user