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