mirror of
https://gitee.com/dromara/mayfly-go
synced 2025-12-25 00:56:35 +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