mirror of
https://gitee.com/dromara/mayfly-go
synced 2025-11-02 15:30:25 +08:00
feat: 完善数据库信息保存以及项目、redis相关操作
This commit is contained in:
3
.vscode/launch.json
vendored
3
.vscode/launch.json
vendored
@@ -4,6 +4,7 @@
|
||||
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
|
||||
{
|
||||
"name": "mayfly-go",
|
||||
"type": "go",
|
||||
@@ -12,6 +13,6 @@
|
||||
"program": "${fileDirname}/main.go",
|
||||
"env": {},
|
||||
"args": []
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
@@ -6,18 +6,11 @@ type BizError struct {
|
||||
err string
|
||||
}
|
||||
|
||||
const (
|
||||
SuccessCode = 200
|
||||
SuccessMsg = "success"
|
||||
|
||||
BizErrorCode = 400
|
||||
BizErrorMsg = "error"
|
||||
|
||||
ServerErrorCode = 500
|
||||
ServerErrorMsg = "server error"
|
||||
|
||||
TokenErrorCode = 501
|
||||
TokenErrorMsg = "token error"
|
||||
var (
|
||||
Success *BizError = NewBizErrCode(200, "success")
|
||||
BizErr *BizError = NewBizErrCode(400, "biz error")
|
||||
ServerError *BizError = NewBizErrCode(500, "server error")
|
||||
PermissionErr *BizError = NewBizErrCode(501, "token error")
|
||||
)
|
||||
|
||||
// 错误消息
|
||||
@@ -32,7 +25,7 @@ func (e *BizError) Code() int16 {
|
||||
|
||||
// 创建业务逻辑错误结构体,默认为业务逻辑错误
|
||||
func NewBizErr(msg string) *BizError {
|
||||
return &BizError{code: BizErrorCode, err: msg}
|
||||
return &BizError{code: BizErr.code, err: msg}
|
||||
}
|
||||
|
||||
// 创建业务逻辑错误结构体,可设置指定错误code
|
||||
|
||||
28
base/captcha/captcha.go
Normal file
28
base/captcha/captcha.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package captcha
|
||||
|
||||
import (
|
||||
"mayfly-go/base/biz"
|
||||
|
||||
"github.com/mojocn/base64Captcha"
|
||||
)
|
||||
|
||||
var store = base64Captcha.DefaultMemStore
|
||||
var driver base64Captcha.Driver = base64Captcha.DefaultDriverDigit
|
||||
|
||||
// 生成验证码
|
||||
func Generate() (string, string) {
|
||||
c := base64Captcha.NewCaptcha(driver, store)
|
||||
// 获取
|
||||
id, b64s, err := c.Generate()
|
||||
biz.ErrIsNilAppendErr(err, "获取验证码错误: %s")
|
||||
return id, b64s
|
||||
}
|
||||
|
||||
// 验证验证码
|
||||
func Verify(id string, val string) bool {
|
||||
if id == "" || val == "" {
|
||||
return false
|
||||
}
|
||||
// 同时清理掉这个图片
|
||||
return store.Verify(id, val, true)
|
||||
}
|
||||
@@ -4,9 +4,13 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"mayfly-go/base/utils"
|
||||
"mayfly-go/base/utils/assert"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// 配置文件映射对象
|
||||
var Conf *Config
|
||||
|
||||
func init() {
|
||||
configFilePath := flag.String("e", "./config.yml", "配置文件路径,默认为可执行文件目录")
|
||||
flag.Parse()
|
||||
@@ -18,6 +22,8 @@ func init() {
|
||||
if err := utils.LoadYml(startConfigParam.ConfigFilePath, yc); err != nil {
|
||||
panic(fmt.Sprintf("读取配置文件[%s]失败: %s", startConfigParam.ConfigFilePath, err.Error()))
|
||||
}
|
||||
// 校验配置文件内容信息
|
||||
yc.Valid()
|
||||
Conf = yc
|
||||
}
|
||||
|
||||
@@ -33,12 +39,17 @@ var startConfigParam *CmdConfigParam
|
||||
type Config struct {
|
||||
App *App `yaml:"app"`
|
||||
Server *Server `yaml:"server"`
|
||||
Jwt *Jwt `yaml:"jwt"`
|
||||
Redis *Redis `yaml:"redis"`
|
||||
Mysql *Mysql `yaml:"mysql"`
|
||||
Log *Log `yaml:"log"`
|
||||
}
|
||||
|
||||
// 配置文件映射对象
|
||||
var Conf *Config
|
||||
// 配置文件内容校验
|
||||
func (c *Config) Valid() {
|
||||
assert.IsTrue(c.Jwt != nil, "配置文件的[jwt]信息不能为空")
|
||||
c.Jwt.Valid()
|
||||
}
|
||||
|
||||
// 获取执行可执行文件时,指定的启动参数
|
||||
func getStartConfig() *CmdConfigParam {
|
||||
|
||||
13
base/config/jwt.go
Normal file
13
base/config/jwt.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package config
|
||||
|
||||
import "mayfly-go/base/utils/assert"
|
||||
|
||||
type Jwt struct {
|
||||
Key string `yaml:"key"`
|
||||
ExpireTime uint64 `yaml:"expire-time"` // 过期时间,单位分钟
|
||||
}
|
||||
|
||||
func (j *Jwt) Valid() {
|
||||
assert.IsTrue(j.Key != "", "config.yml之 [jwt.key] 不能为空")
|
||||
assert.IsTrue(j.ExpireTime != 0, "config.yml之 [jwt.expire-time] 不能为空")
|
||||
}
|
||||
30
base/config/log.go
Normal file
30
base/config/log.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package config
|
||||
|
||||
import "path"
|
||||
|
||||
type Log struct {
|
||||
Level string `yaml:"level"`
|
||||
File *LogFile `yaml:"file"`
|
||||
}
|
||||
|
||||
type LogFile struct {
|
||||
Name string `yaml:"name"`
|
||||
Path string `yaml:"path"`
|
||||
}
|
||||
|
||||
// 获取完整路径文件名
|
||||
func (l *LogFile) GetFilename() string {
|
||||
var filepath, filename string
|
||||
if fp := l.Path; fp == "" {
|
||||
filepath = "./"
|
||||
} else {
|
||||
filepath = fp
|
||||
}
|
||||
if fn := l.Name; fn == "" {
|
||||
filename = "default.log"
|
||||
} else {
|
||||
filename = fn
|
||||
}
|
||||
|
||||
return path.Join(filepath, filename)
|
||||
}
|
||||
@@ -6,6 +6,7 @@ type Server struct {
|
||||
Port int `yaml:"port"`
|
||||
Model string `yaml:"model"`
|
||||
Cors bool `yaml:"cors"`
|
||||
Tls *Tls `yaml:"tls"`
|
||||
Static *[]*Static `yaml:"static"`
|
||||
StaticFile *[]*StaticFile `yaml:"static-file"`
|
||||
}
|
||||
@@ -23,3 +24,9 @@ type StaticFile struct {
|
||||
RelativePath string `yaml:"relative-path"`
|
||||
Filepath string `yaml:"filepath"`
|
||||
}
|
||||
|
||||
type Tls struct {
|
||||
Enable bool `yaml:"enable"` // 是否启用tls
|
||||
KeyFile string `yaml:"key-file"` // 私钥文件路径
|
||||
CertFile string `yaml:"cert-file"` // 证书文件路径
|
||||
}
|
||||
|
||||
@@ -9,14 +9,9 @@ import (
|
||||
"reflect"
|
||||
"runtime/debug"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func init() {
|
||||
log.SetFormatter(new(logger.LogFormatter))
|
||||
log.SetReportCaller(true)
|
||||
}
|
||||
|
||||
type LogInfo struct {
|
||||
LogResp bool // 是否记录返回结果
|
||||
Description string // 请求描述
|
||||
@@ -37,7 +32,7 @@ func LogHandler(rc *ReqCtx) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
lfs := log.Fields{}
|
||||
lfs := logrus.Fields{}
|
||||
if la := rc.LoginAccount; la != nil {
|
||||
lfs["uid"] = la.Id
|
||||
lfs["uname"] = la.Username
|
||||
@@ -47,10 +42,10 @@ func LogHandler(rc *ReqCtx) error {
|
||||
lfs[req.Method] = req.URL.Path
|
||||
|
||||
if err := rc.Err; err != nil {
|
||||
log.WithFields(lfs).Error(getErrMsg(rc, err))
|
||||
logger.Log.WithFields(lfs).Error(getErrMsg(rc, err))
|
||||
return nil
|
||||
}
|
||||
log.WithFields(lfs).Info(getLogMsg(rc))
|
||||
logger.Log.WithFields(lfs).Info(getLogMsg(rc))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"mayfly-go/base/biz"
|
||||
"mayfly-go/base/cache"
|
||||
"mayfly-go/base/config"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -37,7 +38,7 @@ type DefaultPermissionCodeRegistry struct {
|
||||
|
||||
func (r *DefaultPermissionCodeRegistry) SaveCodes(userId uint64, codes []string) {
|
||||
if r.cache == nil {
|
||||
r.cache = cache.NewTimedCache(30*time.Minute, 5*time.Second).WithUpdateAccessTime(true)
|
||||
r.cache = cache.NewTimedCache(time.Minute*time.Duration(config.Conf.Jwt.ExpireTime), 5*time.Second)
|
||||
}
|
||||
r.cache.Put(fmt.Sprintf("%v", userId), codes)
|
||||
}
|
||||
@@ -79,7 +80,7 @@ func SetPermissionCodeRegistery(pcr PermissionCodeRegistry) {
|
||||
|
||||
var (
|
||||
permissionCodeRegistry PermissionCodeRegistry = &DefaultPermissionCodeRegistry{}
|
||||
permissionError = biz.NewBizErrCode(biz.TokenErrorCode, biz.TokenErrorMsg)
|
||||
// permissionError = biz.NewBizErrCode(biz.TokenErrorCode, biz.TokenErrorMsg)
|
||||
)
|
||||
|
||||
func PermissionHandler(rc *ReqCtx) error {
|
||||
@@ -94,16 +95,16 @@ func PermissionHandler(rc *ReqCtx) error {
|
||||
tokenStr = rc.GinCtx.Query("token")
|
||||
}
|
||||
if tokenStr == "" {
|
||||
return permissionError
|
||||
return biz.PermissionErr
|
||||
}
|
||||
loginAccount, err := ParseToken(tokenStr)
|
||||
if err != nil || loginAccount == nil {
|
||||
return permissionError
|
||||
return biz.PermissionErr
|
||||
}
|
||||
// 权限不为nil,并且permission code不为空,则校验是否有权限code
|
||||
if permission != nil && permission.Code != "" {
|
||||
if !permissionCodeRegistry.HasCode(loginAccount.Id, permission.Code) {
|
||||
return permissionError
|
||||
return biz.PermissionErr
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package ctx
|
||||
import (
|
||||
"mayfly-go/base/ginx"
|
||||
"mayfly-go/base/model"
|
||||
"mayfly-go/base/utils/assert"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
@@ -37,9 +38,7 @@ func (rc *ReqCtx) Handle(handler HandlerFunc) {
|
||||
// 应用所有请求后置处理器
|
||||
ApplyHandlerInterceptor(afterHandlers, rc)
|
||||
}()
|
||||
if ginCtx == nil {
|
||||
panic("ginContext == nil")
|
||||
}
|
||||
assert.IsTrue(ginCtx != nil, "ginContext == nil")
|
||||
|
||||
// 默认为不记录请求参数,可在handler回调函数中覆盖赋值
|
||||
rc.ReqParam = 0
|
||||
|
||||
@@ -4,15 +4,16 @@ import (
|
||||
"errors"
|
||||
|
||||
"mayfly-go/base/biz"
|
||||
"mayfly-go/base/config"
|
||||
"mayfly-go/base/model"
|
||||
"time"
|
||||
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
)
|
||||
|
||||
const (
|
||||
JwtKey = "mykey"
|
||||
ExpTime = time.Hour * 24 * 7
|
||||
var (
|
||||
JwtKey = config.Conf.Jwt.Key
|
||||
ExpTime = config.Conf.Jwt.ExpireTime
|
||||
)
|
||||
|
||||
// 创建用户token
|
||||
@@ -22,7 +23,7 @@ func CreateToken(userId uint64, username string) string {
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
||||
"id": userId,
|
||||
"username": username,
|
||||
"exp": time.Now().Add(ExpTime).Unix(),
|
||||
"exp": time.Now().Add(time.Minute * time.Duration(ExpTime)).Unix(),
|
||||
})
|
||||
|
||||
// 使用自定义字符串加密 and get the complete encoded token as a string
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package ginx
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"mayfly-go/base/biz"
|
||||
"mayfly-go/base/global"
|
||||
"mayfly-go/base/model"
|
||||
@@ -13,14 +12,14 @@ import (
|
||||
|
||||
// 绑定并校验请求结构体参数
|
||||
func BindJsonAndValid(g *gin.Context, data interface{}) {
|
||||
if err := g.BindJSON(data); err != nil {
|
||||
if err := g.ShouldBindJSON(data); err != nil {
|
||||
panic(biz.NewBizErr(err.Error()))
|
||||
}
|
||||
}
|
||||
|
||||
// 绑定查询字符串到
|
||||
func BindQuery(g *gin.Context, data interface{}) {
|
||||
if err := g.BindQuery(data); err != nil {
|
||||
if err := g.ShouldBindQuery(data); err != nil {
|
||||
panic(biz.NewBizErr(err.Error()))
|
||||
}
|
||||
}
|
||||
@@ -44,7 +43,6 @@ func QueryInt(g *gin.Context, qm string, defaultInt int) int {
|
||||
// 获取路径参数
|
||||
func PathParamInt(g *gin.Context, pm string) int {
|
||||
value, _ := strconv.Atoi(g.Param(pm))
|
||||
biz.IsTrue(value != 0, fmt.Sprintf("%s不存在", pm))
|
||||
return value
|
||||
}
|
||||
|
||||
@@ -64,7 +62,7 @@ func SuccessRes(g *gin.Context, data interface{}) {
|
||||
func ErrorRes(g *gin.Context, err interface{}) {
|
||||
switch t := err.(type) {
|
||||
case *biz.BizError:
|
||||
g.JSON(http.StatusOK, model.Error(t.Code(), t.Error()))
|
||||
g.JSON(http.StatusOK, model.Error(t))
|
||||
break
|
||||
case error:
|
||||
g.JSON(http.StatusOK, model.ServerError())
|
||||
|
||||
@@ -1,17 +1,11 @@
|
||||
package global
|
||||
|
||||
import (
|
||||
"mayfly-go/base/config"
|
||||
"mayfly-go/base/logger"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// 日志
|
||||
var Log = logger.Log
|
||||
|
||||
// config.yml配置文件映射对象
|
||||
var Config = config.Conf
|
||||
|
||||
// gorm
|
||||
var Db *gorm.DB
|
||||
var (
|
||||
Log *logrus.Logger // 日志
|
||||
Db *gorm.DB // gorm
|
||||
)
|
||||
|
||||
@@ -7,10 +7,14 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
var client = &http.Client{}
|
||||
|
||||
// 默认超时
|
||||
const DefTimeout = 60
|
||||
|
||||
@@ -22,6 +26,13 @@ type RequestWrapper struct {
|
||||
header map[string]string
|
||||
}
|
||||
|
||||
type MultipartFile struct {
|
||||
FieldName string // 字段名
|
||||
FileName string // 文件名
|
||||
FilePath string // 文件路径,文件路径不为空,则优先读取文件路径的内容
|
||||
Bytes []byte // 文件内容
|
||||
}
|
||||
|
||||
// 创建一个请求
|
||||
func NewRequest(url string) *RequestWrapper {
|
||||
return &RequestWrapper{url: url}
|
||||
@@ -31,12 +42,21 @@ func (r *RequestWrapper) Url(url string) *RequestWrapper {
|
||||
r.url = url
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *RequestWrapper) Header(name, value string) *RequestWrapper {
|
||||
if r.header == nil {
|
||||
r.header = make(map[string]string)
|
||||
}
|
||||
r.header[name] = value
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *RequestWrapper) Timeout(timeout int) *RequestWrapper {
|
||||
r.timeout = timeout
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *RequestWrapper) GetByParam(paramMap map[string]string) ResponseWrapper {
|
||||
func (r *RequestWrapper) GetByParam(paramMap map[string]string) *ResponseWrapper {
|
||||
var params string
|
||||
for k, v := range paramMap {
|
||||
if params != "" {
|
||||
@@ -50,13 +70,13 @@ func (r *RequestWrapper) GetByParam(paramMap map[string]string) ResponseWrapper
|
||||
return r.Get()
|
||||
}
|
||||
|
||||
func (r *RequestWrapper) Get() ResponseWrapper {
|
||||
func (r *RequestWrapper) Get() *ResponseWrapper {
|
||||
r.method = "GET"
|
||||
r.body = nil
|
||||
return request(r)
|
||||
}
|
||||
|
||||
func (r *RequestWrapper) PostJson(body string) ResponseWrapper {
|
||||
func (r *RequestWrapper) PostJson(body string) *ResponseWrapper {
|
||||
buf := bytes.NewBufferString(body)
|
||||
r.method = "POST"
|
||||
r.body = buf
|
||||
@@ -67,7 +87,7 @@ func (r *RequestWrapper) PostJson(body string) ResponseWrapper {
|
||||
return request(r)
|
||||
}
|
||||
|
||||
func (r *RequestWrapper) PostObj(body interface{}) ResponseWrapper {
|
||||
func (r *RequestWrapper) PostObj(body interface{}) *ResponseWrapper {
|
||||
marshal, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return createRequestError(errors.New("解析json obj错误"))
|
||||
@@ -75,7 +95,7 @@ func (r *RequestWrapper) PostObj(body interface{}) ResponseWrapper {
|
||||
return r.PostJson(string(marshal))
|
||||
}
|
||||
|
||||
func (r *RequestWrapper) PostParams(params string) ResponseWrapper {
|
||||
func (r *RequestWrapper) PostParams(params string) *ResponseWrapper {
|
||||
buf := bytes.NewBufferString(params)
|
||||
r.method = "POST"
|
||||
r.body = buf
|
||||
@@ -86,9 +106,54 @@ func (r *RequestWrapper) PostParams(params string) ResponseWrapper {
|
||||
return request(r)
|
||||
}
|
||||
|
||||
func (r *RequestWrapper) PostMulipart(files []MultipartFile, reqParams map[string]string) *ResponseWrapper {
|
||||
buf := &bytes.Buffer{}
|
||||
// 文件写入 buf
|
||||
writer := multipart.NewWriter(buf)
|
||||
for _, uploadFile := range files {
|
||||
var reader io.Reader
|
||||
// 如果文件路径不为空,则读取该路径文件,否则使用bytes
|
||||
if uploadFile.FilePath != "" {
|
||||
file, err := os.Open(uploadFile.FilePath)
|
||||
if err != nil {
|
||||
return createRequestError(err)
|
||||
}
|
||||
defer file.Close()
|
||||
reader = file
|
||||
} else {
|
||||
reader = bytes.NewBuffer(uploadFile.Bytes)
|
||||
}
|
||||
|
||||
part, err := writer.CreateFormFile(uploadFile.FieldName, uploadFile.FileName)
|
||||
if err != nil {
|
||||
return createRequestError(err)
|
||||
}
|
||||
_, err = io.Copy(part, reader)
|
||||
}
|
||||
// 如果有其他参数,则写入body
|
||||
if reqParams != nil {
|
||||
for k, v := range reqParams {
|
||||
if err := writer.WriteField(k, v); err != nil {
|
||||
return createRequestError(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := writer.Close(); err != nil {
|
||||
return createRequestError(err)
|
||||
}
|
||||
|
||||
r.method = "POST"
|
||||
r.body = buf
|
||||
if r.header == nil {
|
||||
r.header = make(map[string]string)
|
||||
}
|
||||
r.header["Content-type"] = writer.FormDataContentType()
|
||||
return request(r)
|
||||
}
|
||||
|
||||
type ResponseWrapper struct {
|
||||
StatusCode int
|
||||
Body string
|
||||
Body []byte
|
||||
Header http.Header
|
||||
}
|
||||
|
||||
@@ -96,28 +161,33 @@ 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) BodyToObj(objPtr interface{}) error {
|
||||
_ = json.Unmarshal(r.Body, &objPtr)
|
||||
return r.getError()
|
||||
}
|
||||
|
||||
func (r *ResponseWrapper) ToMap() map[string]interface{} {
|
||||
if !r.IsSuccess() {
|
||||
return nil
|
||||
}
|
||||
func (r *ResponseWrapper) BodyToString() (string, error) {
|
||||
return string(r.Body), r.getError()
|
||||
}
|
||||
|
||||
func (r *ResponseWrapper) BodyToMap() (map[string]interface{}, error) {
|
||||
var res map[string]interface{}
|
||||
err := json.Unmarshal([]byte(r.Body), &res)
|
||||
err := json.Unmarshal(r.Body, &res)
|
||||
if err != nil {
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
return res
|
||||
return res, r.getError()
|
||||
}
|
||||
|
||||
func request(rw *RequestWrapper) ResponseWrapper {
|
||||
wrapper := ResponseWrapper{StatusCode: 0, Body: "", Header: make(http.Header)}
|
||||
client := &http.Client{}
|
||||
func (r *ResponseWrapper) getError() error {
|
||||
if !r.IsSuccess() {
|
||||
return errors.New(string(r.Body))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func request(rw *RequestWrapper) *ResponseWrapper {
|
||||
wrapper := &ResponseWrapper{StatusCode: 0, Header: make(http.Header)}
|
||||
timeout := rw.timeout
|
||||
if timeout > 0 {
|
||||
client.Timeout = time.Duration(timeout) * time.Second
|
||||
@@ -132,17 +202,17 @@ func request(rw *RequestWrapper) ResponseWrapper {
|
||||
setRequestHeader(req, rw.header)
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
wrapper.Body = fmt.Sprintf("执行HTTP请求错误-%s", err.Error())
|
||||
wrapper.Body = []byte(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())
|
||||
wrapper.Body = []byte(fmt.Sprintf("读取HTTP请求返回值失败-%s", err.Error()))
|
||||
return wrapper
|
||||
}
|
||||
wrapper.StatusCode = resp.StatusCode
|
||||
wrapper.Body = string(body)
|
||||
wrapper.Body = body
|
||||
wrapper.Header = resp.Header
|
||||
|
||||
return wrapper
|
||||
@@ -155,7 +225,6 @@ func setRequestHeader(req *http.Request, header map[string]string) {
|
||||
}
|
||||
}
|
||||
|
||||
func createRequestError(err error) ResponseWrapper {
|
||||
errorMessage := fmt.Sprintf("创建HTTP请求错误-%s", err.Error())
|
||||
return ResponseWrapper{0, errorMessage, make(http.Header)}
|
||||
func createRequestError(err error) *ResponseWrapper {
|
||||
return &ResponseWrapper{0, []byte(fmt.Sprintf("创建HTTP请求错误-%s", err.Error())), make(http.Header)}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,9 @@ package logger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"mayfly-go/base/config"
|
||||
"mayfly-go/base/global"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -11,12 +14,38 @@ import (
|
||||
var Log = logrus.New()
|
||||
|
||||
func init() {
|
||||
// customFormatter := new(logrus.TextFormatter)
|
||||
// customFormatter.TimestampFormat = "2006-01-02 15:04:05.000"
|
||||
// customFormatter.FullTimestamp = true
|
||||
Log.SetFormatter(new(LogFormatter))
|
||||
Log.SetReportCaller(true)
|
||||
Log.SetLevel(logrus.DebugLevel)
|
||||
|
||||
logConf := config.Conf.Log
|
||||
// 如果不存在日志配置信息,则默认debug级别
|
||||
if logConf == nil {
|
||||
Log.SetLevel(logrus.DebugLevel)
|
||||
return
|
||||
}
|
||||
|
||||
// 根据配置文件设置日志级别
|
||||
if level := logConf.Level; level != "" {
|
||||
l, err := logrus.ParseLevel(level)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("日志级别不存在: %s", level))
|
||||
}
|
||||
Log.SetLevel(l)
|
||||
} else {
|
||||
Log.SetLevel(logrus.DebugLevel)
|
||||
}
|
||||
|
||||
if logFile := logConf.File; logFile != nil {
|
||||
//写入文件
|
||||
file, err := os.OpenFile(logFile.GetFilename(), os.O_CREATE|os.O_APPEND|os.O_WRONLY, os.ModeAppend|0666)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("创建日志文件失败: %s", err.Error()))
|
||||
}
|
||||
|
||||
Log.Out = file
|
||||
}
|
||||
|
||||
global.Log = Log
|
||||
}
|
||||
|
||||
type LogFormatter struct{}
|
||||
|
||||
@@ -2,6 +2,7 @@ package model
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"mayfly-go/base/biz"
|
||||
"mayfly-go/base/global"
|
||||
"strconv"
|
||||
|
||||
@@ -71,22 +72,13 @@ func GetById(model interface{}, id uint64, cols ...string) error {
|
||||
|
||||
// 根据id列表查询
|
||||
func GetByIdIn(model interface{}, list interface{}, ids []uint64, orderBy ...string) {
|
||||
var idsStr string
|
||||
for i, v := range ids {
|
||||
idStr := strconv.Itoa(int(v))
|
||||
if i == 0 {
|
||||
idsStr += idStr
|
||||
} else {
|
||||
idsStr += ("," + idStr)
|
||||
}
|
||||
}
|
||||
var orderByStr string
|
||||
if orderBy == nil {
|
||||
orderByStr = "id desc"
|
||||
} else {
|
||||
orderByStr = strings.Join(orderBy, ",")
|
||||
}
|
||||
global.Db.Model(model).Where("id in (?)", idsStr).Order(orderByStr).Find(list)
|
||||
global.Db.Model(model).Where("id in (?)", ids).Order(orderByStr).Find(list)
|
||||
}
|
||||
|
||||
// 根据id列表查询
|
||||
@@ -151,11 +143,11 @@ func GetByConditionTo(conditionModel interface{}, toModel interface{}) error {
|
||||
}
|
||||
|
||||
// 获取分页结果
|
||||
func GetPage(pageParam *PageParam, conditionModel interface{}, toModels interface{}, orderBy ...string) PageResult {
|
||||
func GetPage(pageParam *PageParam, conditionModel interface{}, toModels interface{}, orderBy ...string) *PageResult {
|
||||
var count int64
|
||||
global.Db.Model(conditionModel).Where(conditionModel).Count(&count)
|
||||
if count == 0 {
|
||||
return PageResult{Total: 0, List: []string{}}
|
||||
return &PageResult{Total: 0, List: []string{}}
|
||||
}
|
||||
page := pageParam.PageNum
|
||||
pageSize := pageParam.PageSize
|
||||
@@ -165,12 +157,13 @@ func GetPage(pageParam *PageParam, conditionModel interface{}, toModels interfac
|
||||
} else {
|
||||
orderByStr = strings.Join(orderBy, ",")
|
||||
}
|
||||
global.Db.Model(conditionModel).Where(conditionModel).Order(orderByStr).Limit(pageSize).Offset((page - 1) * pageSize).Find(toModels)
|
||||
return PageResult{Total: count, List: toModels}
|
||||
err := global.Db.Model(conditionModel).Where(conditionModel).Order(orderByStr).Limit(pageSize).Offset((page - 1) * pageSize).Find(toModels).Error
|
||||
biz.ErrIsNil(err, "查询失败")
|
||||
return &PageResult{Total: count, List: toModels}
|
||||
}
|
||||
|
||||
// 根据sql获取分页对象
|
||||
func GetPageBySql(sql string, param *PageParam, toModel interface{}, args ...interface{}) PageResult {
|
||||
func GetPageBySql(sql string, param *PageParam, toModel interface{}, args ...interface{}) *PageResult {
|
||||
db := global.Db
|
||||
selectIndex := strings.Index(sql, "SELECT ") + 7
|
||||
fromIndex := strings.Index(sql, " FROM")
|
||||
@@ -180,12 +173,13 @@ func GetPageBySql(sql string, param *PageParam, toModel interface{}, args ...int
|
||||
var count int
|
||||
db.Raw(countSql, args...).Scan(&count)
|
||||
if count == 0 {
|
||||
return PageResult{Total: 0, List: []string{}}
|
||||
return &PageResult{Total: 0, List: []string{}}
|
||||
}
|
||||
// 分页查询
|
||||
limitSql := sql + " LIMIT " + strconv.Itoa(param.PageNum-1) + ", " + strconv.Itoa(param.PageSize)
|
||||
db.Raw(limitSql).Scan(toModel)
|
||||
return PageResult{Total: int64(count), List: toModel}
|
||||
err := db.Raw(limitSql).Scan(toModel).Error
|
||||
biz.ErrIsNil(err, "查询失败")
|
||||
return &PageResult{Total: int64(count), List: toModel}
|
||||
}
|
||||
|
||||
func GetListBySql(sql string, params ...interface{}) []map[string]interface{} {
|
||||
@@ -194,6 +188,6 @@ func GetListBySql(sql string, params ...interface{}) []map[string]interface{} {
|
||||
return maps
|
||||
}
|
||||
|
||||
func GetListBySql2Model(sql string, toEntity interface{}, params ...interface{}) {
|
||||
global.Db.Raw(sql, params).Find(toEntity)
|
||||
func GetListBySql2Model(sql string, toEntity interface{}, params ...interface{}) error {
|
||||
return global.Db.Raw(sql, params).Find(toEntity).Error
|
||||
}
|
||||
|
||||
@@ -3,20 +3,12 @@ package model
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"mayfly-go/base/biz"
|
||||
)
|
||||
|
||||
const (
|
||||
SuccessCode = 200
|
||||
SuccessMsg = "success"
|
||||
|
||||
BizErrorCode = 400
|
||||
BizErrorMsg = "error"
|
||||
|
||||
ServerErrorCode = 500
|
||||
ServerErrorMsg = "server error"
|
||||
|
||||
TokenErrorCode = 501
|
||||
TokenErrorMsg = "token error"
|
||||
)
|
||||
|
||||
// 统一返回结果结构体
|
||||
@@ -52,15 +44,19 @@ func SuccessNoData() *Result {
|
||||
return &Result{Code: SuccessCode, Msg: SuccessMsg}
|
||||
}
|
||||
|
||||
// 返回服务器错误Result
|
||||
func ServerError() *Result {
|
||||
return &Result{Code: ServerErrorCode, Msg: ServerErrorMsg}
|
||||
func Error(bizerr *biz.BizError) *Result {
|
||||
return &Result{Code: bizerr.Code(), Msg: bizerr.Error()}
|
||||
}
|
||||
|
||||
func Error(code int16, msg string) *Result {
|
||||
return &Result{Code: code, Msg: msg}
|
||||
// 返回服务器错误Result
|
||||
func ServerError() *Result {
|
||||
return Error(biz.ServerError)
|
||||
}
|
||||
|
||||
func TokenError() *Result {
|
||||
return &Result{Code: TokenErrorCode, Msg: TokenErrorMsg}
|
||||
return Error(biz.PermissionErr)
|
||||
}
|
||||
|
||||
func ErrorBy(code int16, msg string) *Result {
|
||||
return &Result{Code: code, Msg: msg}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package starter
|
||||
|
||||
import (
|
||||
"mayfly-go/base/config"
|
||||
"mayfly-go/base/global"
|
||||
|
||||
"gorm.io/driver/mysql"
|
||||
@@ -10,7 +11,7 @@ import (
|
||||
)
|
||||
|
||||
func GormMysql() *gorm.DB {
|
||||
m := global.Config.Mysql
|
||||
m := config.Conf.Mysql
|
||||
if m == nil || m.Dbname == "" {
|
||||
global.Log.Panic("未找到数据库配置信息")
|
||||
return nil
|
||||
|
||||
@@ -2,6 +2,7 @@ package starter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"mayfly-go/base/config"
|
||||
"mayfly-go/base/global"
|
||||
|
||||
"github.com/go-redis/redis"
|
||||
@@ -9,7 +10,7 @@ import (
|
||||
|
||||
func ConnRedis() *redis.Client {
|
||||
// 设置redis客户端
|
||||
redisConf := global.Config.Redis
|
||||
redisConf := config.Conf.Redis
|
||||
if redisConf == nil {
|
||||
global.Log.Panic("未找到redis配置信息")
|
||||
}
|
||||
|
||||
@@ -1,17 +1,24 @@
|
||||
package starter
|
||||
|
||||
import (
|
||||
"mayfly-go/base/config"
|
||||
"mayfly-go/base/global"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func RunWebServer(web *gin.Engine) {
|
||||
port := global.Config.Server.GetPort()
|
||||
if app := global.Config.App; app != nil {
|
||||
server := config.Conf.Server
|
||||
port := server.GetPort()
|
||||
if app := config.Conf.App; app != nil {
|
||||
global.Log.Infof("%s- Listening and serving HTTP on %s", app.GetAppInfo(), port)
|
||||
} else {
|
||||
global.Log.Infof("Listening and serving HTTP on %s", port)
|
||||
}
|
||||
web.Run(port)
|
||||
|
||||
if server.Tls != nil && server.Tls.Enable {
|
||||
web.RunTLS(port, server.Tls.CertFile, server.Tls.KeyFile)
|
||||
} else {
|
||||
web.Run(port)
|
||||
}
|
||||
}
|
||||
|
||||
21
base/utils/assert/assert.go
Normal file
21
base/utils/assert/assert.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package assert
|
||||
|
||||
import "fmt"
|
||||
|
||||
// 断言条件为真,不满足的panic
|
||||
func IsTrue(condition bool, panicMsg string, params ...interface{}) {
|
||||
if !condition {
|
||||
if len(params) != 0 {
|
||||
panic(fmt.Sprintf(panicMsg, params...))
|
||||
}
|
||||
panic(panicMsg)
|
||||
}
|
||||
}
|
||||
|
||||
func State(condition bool, panicMsg string, params ...interface{}) {
|
||||
IsTrue(condition, panicMsg, params...)
|
||||
}
|
||||
|
||||
func NotEmpty(str string, panicMsg string, params ...interface{}) {
|
||||
IsTrue(str != "", panicMsg, params...)
|
||||
}
|
||||
26
base/utils/jsonschemal_util.go
Normal file
26
base/utils/jsonschemal_util.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/xeipuuv/gojsonschema"
|
||||
)
|
||||
|
||||
func ValidJsonString(schemal, json string) error {
|
||||
scheme, jsonLoader := gojsonschema.NewStringLoader(schemal), gojsonschema.NewStringLoader(json)
|
||||
|
||||
result, err := gojsonschema.Validate(scheme, jsonLoader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if result.Valid() {
|
||||
return nil
|
||||
}
|
||||
errs := make([]string, 0)
|
||||
for _, desc := range result.Errors() {
|
||||
errs = append(errs, desc.String())
|
||||
}
|
||||
|
||||
return errors.New(strings.Join(errs, "|"))
|
||||
}
|
||||
58
base/utils/jsonschemal_util_test.go
Normal file
58
base/utils/jsonschemal_util_test.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/xeipuuv/gojsonschema"
|
||||
)
|
||||
|
||||
func TestJsonSchemal(t *testing.T) {
|
||||
schema := `{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"title": "Product",
|
||||
"description": "A product from Acme's catalog",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "The unique identifier for a product",
|
||||
"type": "integer"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name of the product",
|
||||
"type": "string"
|
||||
},
|
||||
"price": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"exclusiveMinimum": true
|
||||
}
|
||||
},
|
||||
"required": ["id", "name", "price"]
|
||||
}
|
||||
`
|
||||
|
||||
json := `{"id": 1, "name": "test", "price": -21}`
|
||||
|
||||
err := ValidJsonString(schema, json)
|
||||
fmt.Print(err)
|
||||
}
|
||||
|
||||
func TestJs(t *testing.T) {
|
||||
schemaLoader := gojsonschema.NewStringLoader(`{"type": "object","properties":{"a":{"type":"object"}},"required":["a"]}`) // json格式
|
||||
documentLoader := gojsonschema.NewStringLoader(`{"a":"b"}`) // 待校验的json数据
|
||||
|
||||
result, err := gojsonschema.Validate(schemaLoader, documentLoader)
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
|
||||
if result.Valid() {
|
||||
fmt.Printf("The document is valid\n")
|
||||
} else {
|
||||
fmt.Printf("The document is not valid. see errors :\n")
|
||||
for _, desc := range result.Errors() {
|
||||
fmt.Printf("- %s\n", desc)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,3 +20,8 @@ func LoadYml(path string, out interface{}) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func LoadYmlByString(yamlStr string, out interface{}) error {
|
||||
// yaml解析
|
||||
return yaml.Unmarshal([]byte(yamlStr), out)
|
||||
}
|
||||
|
||||
23
go.mod
23
go.mod
@@ -5,20 +5,23 @@ go 1.16
|
||||
require (
|
||||
// jwt
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||
github.com/gin-gonic/gin v1.6.2
|
||||
github.com/go-redis/redis v6.14.2+incompatible
|
||||
github.com/go-sql-driver/mysql v1.5.0
|
||||
github.com/gin-gonic/gin v1.7.2
|
||||
github.com/go-redis/redis v6.15.9+incompatible
|
||||
github.com/gorilla/websocket v1.4.2
|
||||
github.com/onsi/ginkgo v1.16.1 // indirect
|
||||
github.com/onsi/gomega v1.11.0 // indirect
|
||||
github.com/pkg/sftp v1.12.0
|
||||
// 验证码
|
||||
github.com/mojocn/base64Captcha v1.3.4
|
||||
github.com/onsi/ginkgo v1.16.4 // indirect
|
||||
github.com/onsi/gomega v1.13.0 // indirect
|
||||
github.com/pkg/sftp v1.13.1
|
||||
// 定时任务
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/siddontang/go v0.0.0-20170517070808-cb568a3e5cc0
|
||||
github.com/sirupsen/logrus v1.6.0
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
// jsonschemal校验
|
||||
github.com/xeipuuv/gojsonschema v1.2.0
|
||||
// ssh
|
||||
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a
|
||||
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c
|
||||
// gorm
|
||||
gorm.io/driver/mysql v1.0.5
|
||||
gorm.io/gorm v1.21.6
|
||||
gorm.io/gorm v1.21.11
|
||||
)
|
||||
|
||||
88
go.sum
88
go.sum
@@ -8,21 +8,23 @@ github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWo
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.6.2 h1:88crIK23zO6TqlQBt+f9FrPJNKm9ZEr7qjp9vl/d5TM=
|
||||
github.com/gin-gonic/gin v1.6.2/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
|
||||
github.com/gin-gonic/gin v1.7.2 h1:Tg03T9yM2xa8j6I3Z3oqLaQRSmKvxPd6g/2HJ6zICFA=
|
||||
github.com/gin-gonic/gin v1.7.2/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
|
||||
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
|
||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
|
||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||
github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY=
|
||||
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
|
||||
github.com/go-redis/redis v6.14.2+incompatible h1:UE9pLhzmWf+xHNmZsoccjXosPicuiNaInPgym8nzfg0=
|
||||
github.com/go-redis/redis v6.14.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
|
||||
github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
|
||||
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||
github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=
|
||||
github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
|
||||
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
@@ -31,12 +33,14 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
@@ -48,8 +52,6 @@ github.com/jinzhu/now v1.1.2 h1:eVKgfIdy9b6zbWBMgFpfDPoAMifwSZagU9HmEU6zgiI=
|
||||
github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
|
||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
|
||||
@@ -60,73 +62,91 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OH
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/mojocn/base64Captcha v1.3.4 h1:9+MZzjNSfBHniYOIpoP4xyDDPCXy14JIjsEFf89PlNw=
|
||||
github.com/mojocn/base64Captcha v1.3.4/go.mod h1:wAQCKEc5bDujxKRmbT6/vTnTt5CjStQ8bRfPWUuz/iY=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.16.1 h1:foqVmeWDD6yYpK+Yz3fHyNIxFYNxswxqNFjSKe+vI54=
|
||||
github.com/onsi/ginkgo v1.16.1/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E=
|
||||
github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E=
|
||||
github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
|
||||
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.11.0 h1:+CqWgvj0OZycCaqclBD1pxKHAU+tOkHmQIWvDHq2aug=
|
||||
github.com/onsi/gomega v1.11.0/go.mod h1:azGKhqFUon9Vuj0YmTfLSmx0FUwqXYSTl5re8lQLTUg=
|
||||
github.com/onsi/gomega v1.13.0 h1:7lLHu94wT9Ij0o6EWWclhu0aOh32VxhkwEJvzuWPeak=
|
||||
github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/sftp v1.12.0 h1:/f3b24xrDhkhddlaobPe2JgBqfdt+gC/NYl0QY9IOuI=
|
||||
github.com/pkg/sftp v1.12.0/go.mod h1:fUqqXB5vEgVCZ131L+9say31RAri6aF6KDViawhxKK8=
|
||||
github.com/pkg/sftp v1.13.1 h1:I2qBYMChEhIjOgazfJmV3/mZM256btk6wkCDRmW7JYs=
|
||||
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/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/siddontang/go v0.0.0-20170517070808-cb568a3e5cc0 h1:QIF48X1cihydXibm+4wfAc0r/qyPyuFiPFRNphdMpEE=
|
||||
github.com/siddontang/go v0.0.0-20170517070808-cb568a3e5cc0/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw=
|
||||
github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
|
||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM=
|
||||
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e h1:gsTQYXdTw2Gq7RBsWvlQ91b+aEQ6bXFUngBGuR8sPpI=
|
||||
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/image v0.0.0-20190501045829-6d32002ffd75 h1:TbGuee8sSq15Iguxu4deQ7+Bqq/d2rsQejGcEtADAMQ=
|
||||
golang.org/x/image v0.0.0-20190501045829-6d32002ffd75/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb h1:eBmm0M9fYhWpKZLjQUUKka/LtIxf46G4fxeEz5KJr9U=
|
||||
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0=
|
||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091 h1:DMyOG0U+gKfu8JZzg2UQe9MeaC1X+xQWlAKcRnjxjCw=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
@@ -140,8 +160,10 @@ google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
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/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
@@ -158,5 +180,5 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
|
||||
gorm.io/driver/mysql v1.0.5 h1:WAAmvLK2rG0tCOqrf5XcLi2QUwugd4rcVJ/W3aoon9o=
|
||||
gorm.io/driver/mysql v1.0.5/go.mod h1:N1OIhHAIhx5SunkMGqWbGFVeh4yTNWKmMo1GOAsohLI=
|
||||
gorm.io/gorm v1.21.3/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
|
||||
gorm.io/gorm v1.21.6 h1:xEFbH7WShsnAM+HeRNv7lOeyqmDAK+dDnf1AMf/cVPQ=
|
||||
gorm.io/gorm v1.21.6/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0=
|
||||
gorm.io/gorm v1.21.11 h1:CxkXW6Cc+VIBlL8yJEHq+Co4RYXdSLiMKNvgoZPjLK4=
|
||||
gorm.io/gorm v1.21.11/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0=
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
ENV = 'production'
|
||||
|
||||
# 线上环境接口地址
|
||||
VITE_API_URL = 'http://localhost:8888/api'
|
||||
VITE_API_URL = 'http://api.mayflygo.1yue.net/api'
|
||||
@@ -18,6 +18,7 @@
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="text/javascript" src="./config.js"></script>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
<!-- <script type="text/javascript" src="https://api.map.baidu.com/api?v=3.0&ak=wsijQt8sLXrCW71YesmispvYHitfG9gv&s=1"></script> -->
|
||||
</body>
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
"lint-fix": "eslint --fix --ext .js --ext .jsx --ext .vue src/"
|
||||
},
|
||||
"dependencies": {
|
||||
"core-js": "^3.6.5",
|
||||
"axios": "^0.21.1",
|
||||
"codemirror": "^5.61.0",
|
||||
"core-js": "^3.6.5",
|
||||
"countup.js": "^2.0.7",
|
||||
"cropperjs": "^1.5.11",
|
||||
"echarts": "^5.1.1",
|
||||
|
||||
3
mayfly_go_web/public/config.js
Normal file
3
mayfly_go_web/public/config.js
Normal file
@@ -0,0 +1,3 @@
|
||||
window.globalConfig = {
|
||||
"BaseApiUrl": "http://localhost:8888/api"
|
||||
}
|
||||
@@ -48,7 +48,7 @@ class Api {
|
||||
* 操作该权限,即请求对应的url
|
||||
* @param {Object} param 请求该权限的参数
|
||||
*/
|
||||
request(param: any): Promise<any> {
|
||||
request(param: any = null): Promise<any> {
|
||||
return request.send(this, param);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*/
|
||||
class AssertError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message); // (1)
|
||||
super(message);
|
||||
// 错误类名
|
||||
this.name = "AssertError";
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const config = {
|
||||
baseApiUrl: import.meta.env.VITE_API_URL
|
||||
baseApiUrl: (window as any).globalConfig.BaseApiUrl
|
||||
}
|
||||
|
||||
export default config
|
||||
@@ -2,7 +2,7 @@ import request from './request'
|
||||
|
||||
export default {
|
||||
login: (param: any) => request.request('POST', '/sys/accounts/login', param, null),
|
||||
captcha: () => request.request('GET', '/open/captcha', null, null),
|
||||
captcha: () => request.request('GET', '/sys/captcha', null, null),
|
||||
logout: (param: any) => request.request('POST', '/sys/accounts/logout/{token}', param, null),
|
||||
getMenuRoute: (param: any) => request.request('Get', '/sys/resources/account', param, null)
|
||||
}
|
||||
@@ -53,6 +53,14 @@ export default defineComponent({
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: "500px",
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: "auto",
|
||||
},
|
||||
canChangeMode: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
@@ -165,15 +173,14 @@ export default defineComponent({
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.options,
|
||||
(newValue, oldValue) => {
|
||||
console.log('options change', newValue);
|
||||
for (const key in newValue) {
|
||||
coder.setOption(key, newValue[key]);
|
||||
}
|
||||
}
|
||||
);
|
||||
// watch(
|
||||
// () => props.options,
|
||||
// (newValue, oldValue) => {
|
||||
// for (const key in newValue) {
|
||||
// coder.setOption(key, newValue[key]);
|
||||
// }
|
||||
// }
|
||||
// );
|
||||
|
||||
const init = () => {
|
||||
if (props.options) {
|
||||
@@ -195,6 +202,9 @@ export default defineComponent({
|
||||
}
|
||||
});
|
||||
|
||||
coder.setSize(props.width, props.height);
|
||||
// editor.setSize('width','height');
|
||||
|
||||
// 修改编辑器的语法配置
|
||||
setMode(language.value);
|
||||
|
||||
@@ -285,6 +295,7 @@ export default defineComponent({
|
||||
coder.setValue(newVal);
|
||||
state.content = newVal;
|
||||
coder.scrollTo(scrollInfo.left, scrollInfo.top);
|
||||
refresh()
|
||||
}
|
||||
};
|
||||
|
||||
@@ -292,6 +303,7 @@ export default defineComponent({
|
||||
...toRefs(state),
|
||||
textarea,
|
||||
changeMode,
|
||||
refresh,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
@@ -85,7 +85,7 @@ export default defineComponent({
|
||||
});
|
||||
|
||||
const submit = () => {
|
||||
dynamicForm.validate((valid: boolean) => {
|
||||
dynamicForm.value.validate((valid: boolean) => {
|
||||
if (valid) {
|
||||
// 提交的表单数据
|
||||
const subform = { ...state.form };
|
||||
|
||||
@@ -8,5 +8,9 @@ export const imports = {
|
||||
"ResourceList": () => import('@/views/system/resource'),
|
||||
"RoleList": () => import('@/views/system/role'),
|
||||
"AccountList": () => import('@/views/system/account'),
|
||||
"SelectData": () => import('@/views/ops/db'),
|
||||
"ProjectList": () => import('@/views/ops/project/ProjectList.vue'),
|
||||
"DbList": () => import('@/views/ops/db/DbList.vue'),
|
||||
"SqlExec": () => import('@/views/ops/db'),
|
||||
"RedisList": () => import('@/views/ops/redis'),
|
||||
"DataOperation": () => import('@/views/ops/redis/DataOperation.vue'),
|
||||
}
|
||||
@@ -14,8 +14,8 @@ body,
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, SimSun, sans-serif;
|
||||
font-weight: 500;
|
||||
font-family: Microsoft YaHei, Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, SimSun, sans-serif;
|
||||
font-weight: 450;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
background-color: #f8f8f8;
|
||||
@@ -274,7 +274,7 @@ body,
|
||||
|
||||
.toolbar {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
padding: 6px;
|
||||
background-color: #ffffff;
|
||||
overflow: hidden;
|
||||
line-height: 32px;
|
||||
@@ -283,4 +283,10 @@ body,
|
||||
|
||||
.fl {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
.el-form-item {
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,10 @@
|
||||
<div class="left">
|
||||
<div class="left-item">
|
||||
<div class="left-item-animation left-item-num">401</div>
|
||||
<div class="left-item-animation left-item-title">您未被授权,没有操作权限</div>
|
||||
<div class="left-item-animation left-item-title">您未被授权或登录超时,没有操作权限</div>
|
||||
<div class="left-item-animation left-item-msg"></div>
|
||||
<div class="left-item-animation left-item-btn">
|
||||
<el-button type="primary" round @click="onSetAuth">重新授权</el-button>
|
||||
<el-button type="primary" round @click="onSetAuth">重新登录</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
<img :src="getUserInfos.photo" />
|
||||
<div class="home-card-first-right ml15">
|
||||
<div class="flex-margin">
|
||||
<div class="home-card-first-right-title">{{ currentTime }},admin!</div>
|
||||
<div class="home-card-first-right-msg mt5">超级管理</div>
|
||||
<div class="home-card-first-right-title">{{ `${currentTime}, ${getUserInfos.username}` }}</div>
|
||||
<!-- <div class="home-card-first-right-msg mt5">超级管理</div> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<el-form class="login-content-form">
|
||||
<el-form-item>
|
||||
<el-form ref="loginFormRef" :model="loginForm" :rules="rules" class="login-content-form">
|
||||
<el-form-item prop="username">
|
||||
<el-input type="text" placeholder="请输入用户名" prefix-icon="el-icon-user" v-model="loginForm.username" clearable autocomplete="off">
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-form-item prop="password">
|
||||
<el-input
|
||||
type="password"
|
||||
placeholder="请输入密码"
|
||||
@@ -15,29 +15,36 @@
|
||||
>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-form-item prop="captcha">
|
||||
<el-row :gutter="15">
|
||||
<el-col :span="16">
|
||||
<el-input
|
||||
type="text"
|
||||
maxlength="4"
|
||||
maxlength="6"
|
||||
placeholder="请输入验证码"
|
||||
prefix-icon="el-icon-position"
|
||||
v-model="loginForm.code"
|
||||
v-model="loginForm.captcha"
|
||||
clearable
|
||||
autocomplete="off"
|
||||
@keyup.enter="onSignIn"
|
||||
@keyup.enter="login"
|
||||
></el-input>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<div class="login-content-code">
|
||||
<span class="login-content-code-img">1234</span>
|
||||
<img
|
||||
class="login-content-code-img"
|
||||
@click="getCaptcha"
|
||||
width="130px"
|
||||
height="40px"
|
||||
:src="captchaImage"
|
||||
style="cursor: pointer"
|
||||
/>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" class="login-content-submit" round @click="onSignIn" :loading="loading.signIn">
|
||||
<el-button type="primary" class="login-content-submit" round @click="login" :loading="loading.signIn">
|
||||
<span>登 录</span>
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
@@ -45,7 +52,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { toRefs, reactive, defineComponent, computed } from 'vue';
|
||||
import { onMounted, ref, toRefs, reactive, defineComponent, computed } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { initAllFun, initBackEndControlRoutesFun } from '@/router/index.ts';
|
||||
@@ -60,22 +67,51 @@ export default defineComponent({
|
||||
const store = useStore();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const loginFormRef: any = ref(null);
|
||||
const state = reactive({
|
||||
captchaImage: '',
|
||||
loginForm: {
|
||||
username: 'test',
|
||||
password: '123456',
|
||||
code: '1234',
|
||||
captcha: '',
|
||||
cid: '',
|
||||
},
|
||||
rules: {
|
||||
username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
|
||||
password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
|
||||
captcha: [{ required: true, message: '请输入验证码', trigger: 'blur' }],
|
||||
},
|
||||
loading: {
|
||||
signIn: false,
|
||||
},
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
getCaptcha();
|
||||
});
|
||||
|
||||
const getCaptcha = async () => {
|
||||
let res: any = await openApi.captcha();
|
||||
state.captchaImage = res.base64Captcha;
|
||||
state.loginForm.cid = res.cid;
|
||||
};
|
||||
|
||||
// 时间获取
|
||||
const currentTime = computed(() => {
|
||||
return formatAxis(new Date());
|
||||
});
|
||||
|
||||
// 校验登录表单并登录
|
||||
const login = () => {
|
||||
loginFormRef.value.validate((valid: boolean) => {
|
||||
if (valid) {
|
||||
onSignIn();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 登录
|
||||
const onSignIn = async () => {
|
||||
state.loading.signIn = true;
|
||||
@@ -87,6 +123,8 @@ export default defineComponent({
|
||||
setSession('menus', loginRes.menus);
|
||||
} catch (e) {
|
||||
state.loading.signIn = false;
|
||||
state.loginForm.captcha = '';
|
||||
getCaptcha();
|
||||
return;
|
||||
}
|
||||
// 用户信息模拟数据
|
||||
@@ -132,9 +170,12 @@ export default defineComponent({
|
||||
ElMessage.success(`${currentTimeInfo},欢迎回来!`);
|
||||
}, 300);
|
||||
};
|
||||
|
||||
return {
|
||||
getCaptcha,
|
||||
currentTime,
|
||||
onSignIn,
|
||||
loginFormRef,
|
||||
login,
|
||||
...toRefs(state),
|
||||
};
|
||||
},
|
||||
|
||||
84
mayfly_go_web/src/views/ops/component/ProjectEnvSelect.vue
Normal file
84
mayfly_go_web/src/views/ops/component/ProjectEnvSelect.vue
Normal file
@@ -0,0 +1,84 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-form class="search-form" label-position="right" :inline="true" label-width="60px" size="small">
|
||||
<el-form-item prop="project" label="项目" label-width="40px">
|
||||
<el-select v-model="projectId" placeholder="请选择项目" @change="changeProject" filterable>
|
||||
<el-option v-for="item in projects" :key="item.id" :label="`${item.name} [${item.remark}]`" :value="item.id"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="env" label="环境" label-width="40px">
|
||||
<el-select style="width: 100px" v-model="envId" placeholder="环境" @change="changeEnv" filterable>
|
||||
<el-option v-for="item in envs" :key="item.id" :label="item.name" :value="item.id">
|
||||
<span style="float: left">{{ item.name }}</span>
|
||||
<span style="float: right; color: #8492a6; font-size: 13px">{{ item.remark }}</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<slot></slot>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ref, toRefs, reactive, watch, defineComponent, onMounted } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { notEmpty } from '@/common/assert';
|
||||
import { projectApi } from '../project/api';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ProjectEnvSelect',
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
},
|
||||
data: {
|
||||
type: Object,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
},
|
||||
machineId: {
|
||||
type: Number,
|
||||
},
|
||||
isCommon: {
|
||||
type: Boolean,
|
||||
},
|
||||
},
|
||||
setup(props: any, { emit }) {
|
||||
const state = reactive({
|
||||
projects: [] as any,
|
||||
envs: [] as any,
|
||||
projectId: null,
|
||||
envId: null,
|
||||
});
|
||||
|
||||
watch(props, (newValue, oldValue) => {});
|
||||
|
||||
onMounted(async () => {
|
||||
state.projects = await projectApi.accountProjects.request(null);
|
||||
});
|
||||
|
||||
const changeProject = async (projectId: any) => {
|
||||
emit('update:projectId', projectId);
|
||||
emit('changeProjectEnv', state.projectId, null);
|
||||
state.envId = null;
|
||||
state.envs = await projectApi.projectEnvs.request({ projectId });
|
||||
};
|
||||
|
||||
const changeEnv = (envId: any) => {
|
||||
emit('update:envId', envId);
|
||||
emit('changeProjectEnv', state.projectId, envId);
|
||||
};
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
changeProject,
|
||||
changeEnv,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="scss">
|
||||
</style>
|
||||
240
mayfly_go_web/src/views/ops/db/DbEdit.vue
Normal file
240
mayfly_go_web/src/views/ops/db/DbEdit.vue
Normal file
@@ -0,0 +1,240 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-dialog :title="title" v-model="visible" :show-close="false" :before-close="cancel" width="35%">
|
||||
<el-form :model="form" ref="dbForm" :rules="rules" label-width="85px" size="small">
|
||||
<el-form-item prop="projectId" label="项目:" required>
|
||||
<el-select style="width: 100%" v-model="form.projectId" placeholder="请选择项目" @change="changeProject" filterable>
|
||||
<el-option v-for="item in projects" :key="item.id" :label="`${item.name} [${item.remark}]`" :value="item.id"> </el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="envId" label="环境:" required>
|
||||
<el-select @change="changeEnv" style="width: 100%" v-model="form.envId" placeholder="请选择环境">
|
||||
<el-option v-for="item in envs" :key="item.id" :label="`${item.name} [${item.remark}]`" :value="item.id"> </el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item prop="name" label="别名:" required>
|
||||
<el-input v-model.trim="form.name" placeholder="请输入数据库别名" auto-complete="off"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="type" label="类型:" required>
|
||||
<el-select style="width: 100%" v-model="form.type" placeholder="请选择数据库类型">
|
||||
<el-option key="item.id" label="mysql" value="mysql"> </el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item prop="host" label="host:" required>
|
||||
<el-input v-model.trim="form.host" placeholder="请输入主机ip" auto-complete="off"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="port" label="port:" required>
|
||||
<el-input type="number" v-model.trim="form.port" placeholder="请输入端口"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="username" label="用户名:" required>
|
||||
<el-input v-model.trim="form.username" placeholder="请输入用户名"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="password" label="密码:" required>
|
||||
<el-input
|
||||
type="password"
|
||||
show-password
|
||||
v-model.trim="form.password"
|
||||
placeholder="请输入密码"
|
||||
autocomplete="new-password"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="database" label="数据库名:" required>
|
||||
<el-input v-model.trim="form.database" placeholder="请输入数据库名"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button type="primary" :loading="btnLoading" @click="btnOk" size="mini">确 定</el-button>
|
||||
<el-button @click="cancel()" size="mini">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { toRefs, reactive, watch, defineComponent, ref } from 'vue';
|
||||
import { dbApi } from './api';
|
||||
import { projectApi } from '../project/api.ts';
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'DbEdit',
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
},
|
||||
projects: {
|
||||
type: Array,
|
||||
},
|
||||
db: {
|
||||
type: [Boolean, Object],
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
},
|
||||
},
|
||||
setup(props: any, { emit }) {
|
||||
const dbForm: any = ref(null);
|
||||
const state = reactive({
|
||||
visible: false,
|
||||
projects: [],
|
||||
envs: [],
|
||||
form: {
|
||||
id: null,
|
||||
name: null,
|
||||
port: 3306,
|
||||
username: null,
|
||||
password: null,
|
||||
project: null,
|
||||
projectId: null,
|
||||
envId: null,
|
||||
env: null,
|
||||
},
|
||||
btnLoading: false,
|
||||
rules: {
|
||||
projectId: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择项目',
|
||||
trigger: ['change', 'blur'],
|
||||
},
|
||||
],
|
||||
envId: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择环境',
|
||||
trigger: ['change', 'blur'],
|
||||
},
|
||||
],
|
||||
name: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入别名',
|
||||
trigger: ['change', 'blur'],
|
||||
},
|
||||
],
|
||||
type: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择数据库类型',
|
||||
trigger: ['change', 'blur'],
|
||||
},
|
||||
],
|
||||
host: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入主机ip',
|
||||
trigger: ['change', 'blur'],
|
||||
},
|
||||
],
|
||||
port: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入端口',
|
||||
trigger: ['change', 'blur'],
|
||||
},
|
||||
],
|
||||
username: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入用户名',
|
||||
trigger: ['change', 'blur'],
|
||||
},
|
||||
],
|
||||
password: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入密码',
|
||||
trigger: ['change', 'blur'],
|
||||
},
|
||||
],
|
||||
database: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入数据库名',
|
||||
trigger: ['change', 'blur'],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
watch(props, async (newValue, oldValue) => {
|
||||
state.visible = newValue.visible;
|
||||
state.projects = newValue.projects;
|
||||
if (newValue.db) {
|
||||
getEnvs(newValue.db.projectId);
|
||||
state.form = { ...newValue.db };
|
||||
} else {
|
||||
state.envs = [];
|
||||
state.form = { port: 3306 } as any;
|
||||
}
|
||||
});
|
||||
|
||||
const getEnvs = async (projectId: any) => {
|
||||
state.envs = await projectApi.projectEnvs.request({ projectId });
|
||||
};
|
||||
|
||||
const changeProject = (projectId: number) => {
|
||||
for (let p of state.projects as any) {
|
||||
if (p.id == projectId) {
|
||||
state.form.project = p.name;
|
||||
}
|
||||
}
|
||||
state.envs = [];
|
||||
getEnvs(projectId);
|
||||
};
|
||||
|
||||
const changeEnv = (envId: number) => {
|
||||
for (let p of state.envs as any) {
|
||||
if (p.id == envId) {
|
||||
state.form.env = p.name;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const btnOk = async () => {
|
||||
dbForm.value.validate((valid: boolean) => {
|
||||
if (valid) {
|
||||
dbApi.saveDb.request(state.form).then((res: any) => {
|
||||
ElMessage.success('保存成功');
|
||||
emit('val-change', state.form);
|
||||
state.btnLoading = true;
|
||||
setTimeout(() => {
|
||||
state.btnLoading = false;
|
||||
}, 1000);
|
||||
|
||||
cancel();
|
||||
});
|
||||
} else {
|
||||
ElMessage.error('请正确填写信息');
|
||||
return false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const cancel = () => {
|
||||
emit('update:visible', false);
|
||||
emit('cancel');
|
||||
setTimeout(() => {
|
||||
dbForm.value.resetFields();
|
||||
// 重置对象属性为null
|
||||
state.form = {} as any;
|
||||
}, 200);
|
||||
};
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
dbForm,
|
||||
changeProject,
|
||||
changeEnv,
|
||||
btnOk,
|
||||
cancel,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="scss">
|
||||
</style>
|
||||
196
mayfly_go_web/src/views/ops/db/DbList.vue
Normal file
196
mayfly_go_web/src/views/ops/db/DbList.vue
Normal file
@@ -0,0 +1,196 @@
|
||||
<template>
|
||||
<div class="db-list">
|
||||
<div class="toolbar">
|
||||
<el-row>
|
||||
<el-col>
|
||||
<el-form class="search-form" label-position="right" :inline="true" label-width="60px" size="small">
|
||||
<el-form-item prop="project" label="项目">
|
||||
<el-select v-model="query.projectId" placeholder="请选择项目" filterable clearable>
|
||||
<el-option v-for="item in projects" :key="item.id" :label="`${item.name} [${item.remark}]`" :value="item.id">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="数据库">
|
||||
<el-input v-model="query.database" auto-complete="off" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" size="mini" @click="search()">查询</el-button>
|
||||
</el-form>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row class="mt5">
|
||||
<el-col>
|
||||
<el-button v-auth="permissions.saveDb" type="primary" icon="el-icon-plus" size="mini" @click="editDb(true)">添加</el-button>
|
||||
<el-button
|
||||
v-auth="permissions.saveDb"
|
||||
:disabled="chooseId == null"
|
||||
@click="editDb(false)"
|
||||
type="primary"
|
||||
icon="el-icon-edit"
|
||||
size="mini"
|
||||
>编辑</el-button
|
||||
>
|
||||
<el-button
|
||||
v-auth="permissions.delDb"
|
||||
:disabled="chooseId == null"
|
||||
@click="deleteDb(chooseId)"
|
||||
type="danger"
|
||||
icon="el-icon-delete"
|
||||
size="mini"
|
||||
>删除</el-button
|
||||
>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
<el-table :data="datas" border ref="table" @current-change="choose" show-overflow-tooltip>
|
||||
<el-table-column label="选择" width="50px">
|
||||
<template #default="scope">
|
||||
<el-radio v-model="chooseId" :label="scope.row.id">
|
||||
<i></i>
|
||||
</el-radio>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="project" label="项目" min-width="100"></el-table-column>
|
||||
<el-table-column prop="env" label="环境" min-width="100"></el-table-column>
|
||||
<el-table-column prop="name" label="名称" min-width="200"></el-table-column>
|
||||
<el-table-column min-width="160" label="host:port">
|
||||
<template #default="scope">
|
||||
{{ `${scope.row.host}:${scope.row.port}` }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="type" label="类型" min-width="80"></el-table-column>
|
||||
<el-table-column prop="database" label="数据库" min-width="120"></el-table-column>
|
||||
<el-table-column prop="username" label="用户名" min-width="100"></el-table-column>
|
||||
|
||||
<el-table-column min-width="115" prop="creator" label="创建账号"></el-table-column>
|
||||
<el-table-column min-width="160" prop="createTime" label="创建时间">
|
||||
<template #default="scope">
|
||||
{{ $filters.dateFormat(scope.row.createTime) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination
|
||||
@current-change="handlePageChange"
|
||||
style="text-align: center"
|
||||
background
|
||||
layout="prev, pager, next, total, jumper"
|
||||
:total="total"
|
||||
v-model:current-page="query.pageNum"
|
||||
:page-size="query.pageSize"
|
||||
/>
|
||||
|
||||
<db-edit @val-change="valChange" :projects="projects" :title="dbEditDialog.title" v-model:visible="dbEditDialog.visible" v-model:db="dbEditDialog.data"></db-edit>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang='ts'>
|
||||
import { toRefs, reactive, onMounted, defineComponent } from 'vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import ProjectEnvSelect from '../component/ProjectEnvSelect.vue';
|
||||
import DbEdit from './DbEdit.vue';
|
||||
import { dbApi } from './api';
|
||||
import { projectApi } from '../project/api.ts';
|
||||
export default defineComponent({
|
||||
name: 'DbList',
|
||||
components: {
|
||||
ProjectEnvSelect,
|
||||
DbEdit,
|
||||
},
|
||||
setup() {
|
||||
const state = reactive({
|
||||
permissions: {
|
||||
saveDb: 'db:save',
|
||||
delDb: 'db:del',
|
||||
},
|
||||
projects: [],
|
||||
chooseId: null,
|
||||
/**
|
||||
* 选中的数据
|
||||
*/
|
||||
chooseData: null,
|
||||
/**
|
||||
* 查询条件
|
||||
*/
|
||||
query: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
},
|
||||
datas: [],
|
||||
total: 0,
|
||||
dbEditDialog: {
|
||||
visible: false,
|
||||
data: null,
|
||||
title: '新增数据库',
|
||||
},
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
search();
|
||||
state.projects = (await projectApi.projects.request({ pageNum: 1, pageSize: 100 })).list;
|
||||
});
|
||||
|
||||
const choose = (item: any) => {
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
state.chooseId = item.id;
|
||||
state.chooseData = item;
|
||||
};
|
||||
|
||||
const search = async () => {
|
||||
let res: any = await dbApi.dbs.request(state.query);
|
||||
state.datas = res.list;
|
||||
state.total = res.total;
|
||||
};
|
||||
|
||||
const handlePageChange = (curPage: number) => {
|
||||
state.query.pageNum = curPage;
|
||||
search();
|
||||
};
|
||||
|
||||
const editDb = (isAdd = false) => {
|
||||
if (isAdd) {
|
||||
state.dbEditDialog.data = null;
|
||||
state.dbEditDialog.title = '新增数据库';
|
||||
} else {
|
||||
state.dbEditDialog.data = state.chooseData;
|
||||
state.dbEditDialog.title = '修改数据库';
|
||||
}
|
||||
state.dbEditDialog.visible = true;
|
||||
};
|
||||
|
||||
const valChange = () => {
|
||||
search();
|
||||
};
|
||||
|
||||
const deleteDb = async (id: number) => {
|
||||
try {
|
||||
await ElMessageBox.confirm(`确定删除该库?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
});
|
||||
await dbApi.deleteDb.request({ id });
|
||||
ElMessage.success('删除成功');
|
||||
state.chooseData = null;
|
||||
state.chooseId = null;
|
||||
search();
|
||||
} catch (err) {}
|
||||
};
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
// enums,
|
||||
search,
|
||||
choose,
|
||||
handlePageChange,
|
||||
editDb,
|
||||
valChange,
|
||||
deleteDb,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="scss">
|
||||
</style>
|
||||
@@ -1,15 +1,28 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="toolbar">
|
||||
<div class="fl">
|
||||
<el-select size="small" v-model="dbId" placeholder="请选择数据库" @change="changeDb" @clear="clearDb" clearable filterable>
|
||||
<el-option v-for="item in dbs" :key="item.id" :label="`${item.name} [${dbTypeName(item.type)}]`" :value="item.id"> </el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
<el-row type="flex" justify="space-between">
|
||||
<el-col :span="24">
|
||||
<project-env-select @changeProjectEnv="changeProjectEnv" @clear="clearDb">
|
||||
<template #default>
|
||||
<el-form-item label="数据库">
|
||||
<el-select v-model="dbId" placeholder="请选择数据库" @change="changeDb" @clear="clearDb" clearable filterable>
|
||||
<el-option v-for="item in dbs" :key="item.id" :label="item.database" :value="item.id">
|
||||
<span style="float: left">{{ item.database }}</span>
|
||||
<span style="float: right; color: #8492a6; margin-left: 6px; font-size: 13px">{{
|
||||
`${item.name} [${item.type}]`
|
||||
}}</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</template>
|
||||
</project-env-select>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<el-container style="height: 50%; border: 1px solid #eee; margin-top: 1px">
|
||||
<el-aside width="70%" style="background-color: rgb(238, 241, 246)">
|
||||
<el-container style="border: 1px solid #eee; margin-top: 1px">
|
||||
<el-aside id="sqlcontent" width="65%" style="background-color: rgb(238, 241, 246)">
|
||||
<div class="toolbar">
|
||||
<div class="fl">
|
||||
<el-button @click="runSql" type="success" icon="el-icon-video-play" size="mini" plain>执行</el-button>
|
||||
@@ -19,12 +32,12 @@
|
||||
<el-button @click="saveSql" type="primary" icon="el-icon-document-add" size="mini" plain>保存</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<codemirror class="codesql" ref="cmEditor" language="sql" v-model="sql" :options="cmOptions" />
|
||||
<codemirror @beforeChange="onBeforeChange" class="codesql" ref="cmEditor" language="sql" v-model="sql" :options="cmOptions" />
|
||||
</el-aside>
|
||||
|
||||
<el-container style="margin-left: 2px">
|
||||
<el-header style="text-align: left; height: 45px; font-size: 12px; padding: 0px">
|
||||
<el-select v-model="tableName" placeholder="请选择表" @change="changeTable" clearable filterable style="width: 99%">
|
||||
<el-select v-model="tableName" placeholder="请选择表" @change="changeTable" filterable style="width: 99%">
|
||||
<el-option
|
||||
v-for="item in tableMetadata"
|
||||
:key="item.tableName"
|
||||
@@ -35,20 +48,22 @@
|
||||
</el-select>
|
||||
</el-header>
|
||||
|
||||
<el-main style="padding: 0px; height: 100%; overflow: hidden">
|
||||
<el-main style="padding: 0px; overflow: hidden">
|
||||
<el-table :data="columnMetadata" height="100%" size="mini">
|
||||
<el-table-column prop="columnName" label="名称" show-overflow-tooltip> </el-table-column>
|
||||
<el-table-column prop="columnType" label="类型" show-overflow-tooltip> </el-table-column>
|
||||
<el-table-column prop="columnComment" label="备注" show-overflow-tooltip> </el-table-column>
|
||||
<el-table-column width="120" prop="columnType" label="类型" show-overflow-tooltip> </el-table-column>
|
||||
</el-table>
|
||||
</el-main>
|
||||
</el-container>
|
||||
</el-container>
|
||||
<el-table style="margin-top: 1px" :data="selectRes.data" size="mini" max-height="300" stripe border>
|
||||
|
||||
<el-table style="margin-top: 1px" :data="execRes.data" size="mini" max-height="300" :empty-text="execRes.emptyResText" stripe border>
|
||||
<el-table-column
|
||||
min-width="92"
|
||||
min-width="100"
|
||||
:width="flexColumnWidth(item, execRes.data)"
|
||||
align="center"
|
||||
v-for="item in selectRes.tableColumn"
|
||||
v-for="item in execRes.tableColumn"
|
||||
:key="item"
|
||||
:prop="item"
|
||||
:label="item"
|
||||
@@ -56,20 +71,11 @@
|
||||
>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- <el-pagination
|
||||
style="text-align: center"
|
||||
background
|
||||
layout="prev, pager, next, total, jumper"
|
||||
:total="data.total"
|
||||
:current-page.sync="params.pageNum"
|
||||
:page-size="params.pageSize"
|
||||
/> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { toRefs, reactive, computed, onMounted, defineComponent, ref } from 'vue';
|
||||
import { toRefs, reactive, computed, defineComponent, ref } from 'vue';
|
||||
import { dbApi } from './api';
|
||||
|
||||
import 'codemirror/theme/ambiance.css';
|
||||
@@ -79,48 +85,56 @@ import 'codemirror/lib/codemirror.css';
|
||||
// 引入主题后还需要在 options 中指定主题才会生效
|
||||
import 'codemirror/theme/base16-light.css';
|
||||
|
||||
// require('codemirror/addon/edit/matchbrackets')
|
||||
import 'codemirror/addon/selection/active-line';
|
||||
import { codemirror } from '@/components/codemirror';
|
||||
// import 'codemirror/mode/sql/sql.js';
|
||||
// import 'codemirror/addon/hint/show-hint.js';
|
||||
// import 'codemirror/addon/hint/sql-hint.js';
|
||||
import 'codemirror/addon/hint/show-hint.js';
|
||||
import 'codemirror/addon/hint/sql-hint.js';
|
||||
|
||||
import sqlFormatter from 'sql-formatter';
|
||||
import { notEmpty } from '@/common/assert';
|
||||
import { notNull, notEmpty } from '@/common/assert';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import ProjectEnvSelect from '../component/ProjectEnvSelect.vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'SelectData',
|
||||
name: 'SqlExec',
|
||||
components: {
|
||||
codemirror,
|
||||
ProjectEnvSelect,
|
||||
},
|
||||
setup() {
|
||||
const cmEditor: any = ref(null);
|
||||
const state = reactive({
|
||||
dbs: [],
|
||||
tables: [],
|
||||
dbId: '',
|
||||
dbId: null,
|
||||
tableName: '',
|
||||
tableMetadata: [],
|
||||
columnMetadata: [],
|
||||
sql: '',
|
||||
selectRes: {
|
||||
sqlTabs: {
|
||||
tabs: [] as any,
|
||||
active: '',
|
||||
index: 1,
|
||||
},
|
||||
execRes: {
|
||||
tableColumn: [],
|
||||
data: [],
|
||||
emptyResText: '没有数据',
|
||||
},
|
||||
params: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
envId: null,
|
||||
},
|
||||
cmOptions: {
|
||||
tabSize: 4,
|
||||
mode: 'text/x-sql',
|
||||
// theme: 'cobalt',
|
||||
lineNumbers: true,
|
||||
line: true,
|
||||
indentWithTabs: true,
|
||||
smartIndent: true,
|
||||
// matchBrackets: true,
|
||||
matchBrackets: true,
|
||||
theme: 'base16-light',
|
||||
autofocus: true,
|
||||
extraKeys: { Tab: 'autocomplete' }, // 自定义快捷键
|
||||
@@ -137,14 +151,19 @@ export default defineComponent({
|
||||
return cmEditor.value.coder;
|
||||
});
|
||||
|
||||
const dbTypeName = (type: any) => {
|
||||
return 'mysql';
|
||||
/**
|
||||
* 项目及环境更改后的回调事件
|
||||
*/
|
||||
const changeProjectEnv = (projectId: any, envId: any) => {
|
||||
state.dbs = [];
|
||||
state.dbId = null;
|
||||
clearDb();
|
||||
if (envId != null) {
|
||||
state.params.envId = envId;
|
||||
search();
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
search();
|
||||
});
|
||||
|
||||
/**
|
||||
* 输入字符给提示
|
||||
*/
|
||||
@@ -154,29 +173,92 @@ export default defineComponent({
|
||||
}
|
||||
};
|
||||
|
||||
const onBeforeChange = (instance: any, changeObj: any) => {
|
||||
var text = changeObj.text[0];
|
||||
// 将sql提示去除
|
||||
changeObj.text[0] = text.split(' ')[0];
|
||||
};
|
||||
|
||||
/**
|
||||
* 执行sql
|
||||
*/
|
||||
const runSql = async () => {
|
||||
notEmpty(state.dbId, '请先选择数据库');
|
||||
notNull(state.dbId, '请先选择数据库');
|
||||
// 没有选中的文本,则为全部文本
|
||||
let selectSql = getSql();
|
||||
notEmpty(selectSql, '内容不能为空');
|
||||
const res = await dbApi.selectData.request({
|
||||
let sql = getSql();
|
||||
notNull(sql, '内容不能为空');
|
||||
|
||||
state.execRes.tableColumn = [];
|
||||
state.execRes.data = [];
|
||||
state.execRes.emptyResText = '查询中...';
|
||||
|
||||
const res = await dbApi.sqlExec.request({
|
||||
id: state.dbId,
|
||||
selectSql: selectSql,
|
||||
sql: sql,
|
||||
});
|
||||
let tableColumn: any;
|
||||
let data;
|
||||
if (res.length > 0) {
|
||||
tableColumn = Object.keys(res[0]);
|
||||
data = res;
|
||||
} else {
|
||||
tableColumn = [];
|
||||
data = [];
|
||||
state.execRes.emptyResText = '没有数据';
|
||||
state.execRes.tableColumn = res.colNames;
|
||||
state.execRes.data = res.res;
|
||||
};
|
||||
|
||||
const flexColumnWidth = (str: any, tableData: any, flag = 'equal') => {
|
||||
// str为该列的字段名(传字符串);tableData为该表格的数据源(传变量);
|
||||
// flag为可选值,可不传该参数,传参时可选'max'或'equal',默认为'max'
|
||||
// flag为'max'则设置列宽适配该列中最长的内容,flag为'equal'则设置列宽适配该列中第一行内容的长度。
|
||||
str = str + '';
|
||||
let columnContent = '';
|
||||
if (!tableData || !tableData.length || tableData.length === 0 || tableData === undefined) {
|
||||
return;
|
||||
}
|
||||
state.selectRes.tableColumn = tableColumn;
|
||||
state.selectRes.data = data;
|
||||
if (!str || !str.length || str.length === 0 || str === undefined) {
|
||||
return;
|
||||
}
|
||||
if (flag === 'equal') {
|
||||
// 获取该列中第一个不为空的数据(内容)
|
||||
for (let i = 0; i < tableData.length; i++) {
|
||||
if (tableData[i][str].length > 0) {
|
||||
columnContent = tableData[i][str];
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 获取该列中最长的数据(内容)
|
||||
let index = 0;
|
||||
for (let i = 0; i < tableData.length; i++) {
|
||||
if (tableData[i][str] === null) {
|
||||
return;
|
||||
}
|
||||
const now_temp = tableData[i][str] + '';
|
||||
const max_temp = tableData[index][str] + '';
|
||||
if (now_temp.length > max_temp.length) {
|
||||
index = i;
|
||||
}
|
||||
}
|
||||
columnContent = tableData[index][str];
|
||||
}
|
||||
// 以下分配的单位长度可根据实际需求进行调整
|
||||
let flexWidth = 0;
|
||||
for (const char of columnContent) {
|
||||
if ((char >= 'A' && char <= 'Z') || (char >= 'a' && char <= 'z')) {
|
||||
// 如果是英文字符,为字符分配8个单位宽度
|
||||
flexWidth += 8;
|
||||
} else if (char >= '\u4e00' && char <= '\u9fa5') {
|
||||
// 如果是中文字符,为字符分配15个单位宽度
|
||||
flexWidth += 15;
|
||||
} else {
|
||||
// 其他种类字符,为字符分配8个单位宽度
|
||||
flexWidth += 8;
|
||||
}
|
||||
}
|
||||
if (flexWidth < 80) {
|
||||
// 设置最小宽度
|
||||
flexWidth = 80;
|
||||
}
|
||||
if (flexWidth > 350) {
|
||||
// 设置最大宽度
|
||||
flexWidth = 350;
|
||||
}
|
||||
return flexWidth + 'px';
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -193,7 +275,7 @@ export default defineComponent({
|
||||
|
||||
const saveSql = async () => {
|
||||
notEmpty(state.sql, 'sql内容不能为空');
|
||||
notEmpty(state.dbId, '请先选择数据库');
|
||||
notNull(state.dbId, '请先选择数据库');
|
||||
await dbApi.saveSql.request({ id: state.dbId, sql: state.sql, type: 1 });
|
||||
ElMessage.success('保存成功');
|
||||
};
|
||||
@@ -235,9 +317,10 @@ export default defineComponent({
|
||||
state.tableName = '';
|
||||
state.tableMetadata = [];
|
||||
state.columnMetadata = [];
|
||||
state.selectRes.data = [];
|
||||
state.selectRes.tableColumn = [];
|
||||
state.execRes.data = [];
|
||||
state.execRes.tableColumn = [];
|
||||
state.sql = '';
|
||||
state.cmOptions.hintOptions.tables = [];
|
||||
};
|
||||
|
||||
// 选择表事件
|
||||
@@ -268,7 +351,7 @@ export default defineComponent({
|
||||
codemirror.value.replaceSelection(sqlFormatter.format(selectSql));
|
||||
} else {
|
||||
/* 将sql内容进行格式后放入编辑器中*/
|
||||
codemirror.value.setValue(sqlFormatter.format(state.sql));
|
||||
state.sql = sqlFormatter.format(sqlFormatter.format(state.sql));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -280,14 +363,16 @@ export default defineComponent({
|
||||
return {
|
||||
...toRefs(state),
|
||||
cmEditor,
|
||||
dbTypeName,
|
||||
changeProjectEnv,
|
||||
inputRead,
|
||||
changeTable,
|
||||
runSql,
|
||||
flexColumnWidth,
|
||||
saveSql,
|
||||
changeDb,
|
||||
clearDb,
|
||||
formatSql,
|
||||
onBeforeChange,
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -298,4 +383,9 @@ export default defineComponent({
|
||||
font-size: 10pt;
|
||||
font-family: Consolas, Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace, serif;
|
||||
}
|
||||
#sqlcontent {
|
||||
.CodeMirror {
|
||||
height: 300px !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -3,11 +3,13 @@ import Api from '@/common/Api';
|
||||
export const dbApi = {
|
||||
// 获取权限列表
|
||||
dbs: Api.create("/dbs", 'get'),
|
||||
saveDb: Api.create("/dbs", 'post'),
|
||||
deleteDb: Api.create("/dbs/{id}", 'delete'),
|
||||
tableMetadata: Api.create("/dbs/{id}/t-metadata", 'get'),
|
||||
columnMetadata: Api.create("/dbs/{id}/c-metadata", 'get'),
|
||||
// 获取表即列提示
|
||||
hintTables: Api.create("/dbs/{id}/hint-tables", 'get'),
|
||||
selectData: Api.create("/dbs/{id}/select", 'get'),
|
||||
sqlExec: Api.create("/dbs/{id}/exec-sql", 'get'),
|
||||
// 保存sql
|
||||
saveSql: Api.create("/dbs/{id}/sql", 'post'),
|
||||
// 获取保存的sql
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { default } from './SelectData.vue';
|
||||
export { default } from './SqlExec.vue';
|
||||
182
mayfly_go_web/src/views/ops/machine/MachineEdit.vue
Normal file
182
mayfly_go_web/src/views/ops/machine/MachineEdit.vue
Normal file
@@ -0,0 +1,182 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-dialog :title="title" v-model="visible" :show-close="false" :before-close="cancel" width="35%">
|
||||
<el-form :model="form" ref="machineForm" :rules="rules" label-width="85px" size="small">
|
||||
<!-- <el-form-item prop="projectId" label="项目:" required>
|
||||
<el-select style="width: 100%" v-model="form.projectId" placeholder="请选择项目" @change="changeProject" filterable>
|
||||
<el-option v-for="item in projects" :key="item.id" :label="`${item.name} [${item.remark}]`" :value="item.id"> </el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="envId" label="环境:" required>
|
||||
<el-select @change="changeEnv" style="width: 100%" v-model="form.envId" placeholder="请选择环境">
|
||||
<el-option v-for="item in envs" :key="item.id" :label="`${item.name} [${item.remark}]`" :value="item.id"> </el-option>
|
||||
</el-select>
|
||||
</el-form-item> -->
|
||||
<el-form-item prop="name" label="名称:" required>
|
||||
<el-input v-model.trim="form.name" placeholder="请输入机器别名" auto-complete="off"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="ip" label="ip:" required>
|
||||
<el-input v-model.trim="form.ip" placeholder="请输入主机ip" auto-complete="off"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="port" label="port:" required>
|
||||
<el-input type="number" v-model.trim="form.port" placeholder="请输入端口"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="username" label="用户名:" required>
|
||||
<el-input v-model.trim="form.username" placeholder="请输入用户名"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="password" label="密码:" required>
|
||||
<el-input
|
||||
type="password"
|
||||
show-password
|
||||
v-model.trim="form.password"
|
||||
placeholder="请输入密码"
|
||||
autocomplete="new-password"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button type="primary" :loading="btnLoading" @click="btnOk" size="mini">确 定</el-button>
|
||||
<el-button @click="cancel()" size="mini">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { toRefs, reactive, watch, onMounted, defineComponent, ref } from 'vue';
|
||||
import { machineApi } from './api';
|
||||
import { projectApi } from '../project/api.ts';
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'MachineEdit',
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
},
|
||||
machine: {
|
||||
type: [Boolean, Object],
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
},
|
||||
},
|
||||
setup(props: any, { emit }) {
|
||||
const machineForm: any = ref(null);
|
||||
const state = reactive({
|
||||
visible: false,
|
||||
form: {
|
||||
id: null,
|
||||
name: null,
|
||||
port: 22,
|
||||
username: null,
|
||||
password: null,
|
||||
},
|
||||
btnLoading: false,
|
||||
rules: {
|
||||
projectId: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择项目',
|
||||
trigger: ['change', 'blur'],
|
||||
},
|
||||
],
|
||||
envId: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择环境',
|
||||
trigger: ['change', 'blur'],
|
||||
},
|
||||
],
|
||||
name: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入别名',
|
||||
trigger: ['change', 'blur'],
|
||||
},
|
||||
],
|
||||
ip: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入主机ip',
|
||||
trigger: ['change', 'blur'],
|
||||
},
|
||||
],
|
||||
port: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入端口',
|
||||
trigger: ['change', 'blur'],
|
||||
},
|
||||
],
|
||||
username: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入用户名',
|
||||
trigger: ['change', 'blur'],
|
||||
},
|
||||
],
|
||||
password: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入密码',
|
||||
trigger: ['change', 'blur'],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
watch(props, async (newValue, oldValue) => {
|
||||
state.visible = newValue.visible;
|
||||
if (newValue.machine) {
|
||||
state.form = { ...newValue.machine };
|
||||
} else {
|
||||
state.form = { port: 22 } as any;
|
||||
}
|
||||
});
|
||||
|
||||
const btnOk = async () => {
|
||||
machineForm.value.validate((valid: boolean) => {
|
||||
if (valid) {
|
||||
machineApi.saveMachine.request(state.form).then((res: any) => {
|
||||
ElMessage.success('保存成功');
|
||||
emit('val-change', state.form);
|
||||
state.btnLoading = true;
|
||||
setTimeout(() => {
|
||||
state.btnLoading = false;
|
||||
}, 1000);
|
||||
|
||||
cancel();
|
||||
});
|
||||
} else {
|
||||
ElMessage.error('请正确填写信息');
|
||||
return false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const cancel = () => {
|
||||
emit('update:visible', false);
|
||||
emit('cancel');
|
||||
setTimeout(() => {
|
||||
machineForm.value.resetFields();
|
||||
// 重置对象属性为null
|
||||
state.form = {} as any;
|
||||
}, 200);
|
||||
};
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
machineForm,
|
||||
btnOk,
|
||||
cancel,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="scss">
|
||||
</style>
|
||||
@@ -42,16 +42,19 @@
|
||||
</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="端口" :min-width="40"></el-table-column>
|
||||
<el-table-column prop="username" label="用户名" :min-width="40"></el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间" :min-width="100">
|
||||
<el-table-column prop="ip" label="ip:port" min-width="160">
|
||||
<template #default="scope">
|
||||
{{ `${scope.row.ip}:${scope.row.port}` }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="username" label="用户名" :min-width="45"></el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间" min-width="160">
|
||||
<template #default="scope">
|
||||
{{ $filters.dateFormat(scope.row.createTime) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="creator" label="创建者" :min-width="50"></el-table-column>
|
||||
<el-table-column prop="updateTime" label="更新时间" :min-width="100">
|
||||
<el-table-column prop="creator" label="创建者" min-width="50"></el-table-column>
|
||||
<el-table-column prop="updateTime" label="更新时间" min-width="160">
|
||||
<template #default="scope">
|
||||
{{ $filters.dateFormat(scope.row.updateTime) }}
|
||||
</template>
|
||||
@@ -59,7 +62,7 @@
|
||||
<el-table-column prop="modifier" label="修改者" :min-width="50"></el-table-column>
|
||||
<el-table-column label="操作" min-width="200px">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" @click="monitor(scope.row.id)" icom="el-icon-tickets" size="mini" plain>监控</el-button>
|
||||
<!-- <el-button type="primary" @click="monitor(scope.row.id)" icom="el-icon-tickets" size="mini" plain>监控</el-button> -->
|
||||
<el-button type="success" @click="serviceManager(scope.row)" size="mini" plain>脚本管理</el-button>
|
||||
<el-button v-auth="'machine:terminal'" type="success" @click="showTerminal(scope.row)" size="mini" plain>终端</el-button>
|
||||
</template>
|
||||
@@ -75,6 +78,12 @@
|
||||
:page-size="params.pageSize"
|
||||
/>
|
||||
|
||||
<machine-edit
|
||||
:title="machineEditDialog.title"
|
||||
v-model:visible="machineEditDialog.visible"
|
||||
v-model:machine="machineEditDialog.data"
|
||||
></machine-edit>
|
||||
|
||||
<!-- <el-dialog @close="closeMonitor" title="监控信息" v-model="monitorDialog.visible" width="60%">
|
||||
<monitor ref="monitorDialogRef" :machineId="monitorDialog.machineId" />
|
||||
</el-dialog> -->
|
||||
@@ -82,40 +91,32 @@
|
||||
<service-manage :title="serviceDialog.title" v-model:visible="serviceDialog.visible" v-model:machineId="serviceDialog.machineId" />
|
||||
|
||||
<file-manage :title="fileDialog.title" v-model:visible="fileDialog.visible" v-model:machineId="fileDialog.machineId" />
|
||||
|
||||
<dynamic-form-dialog
|
||||
v-model:visible="formDialog.visible"
|
||||
:title="formDialog.title"
|
||||
:formInfo="formDialog.formInfo"
|
||||
v-model:formData="formDialog.formData"
|
||||
@submitSuccess="submitSuccess"
|
||||
></dynamic-form-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { toRefs, reactive, onMounted, defineComponent } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { DynamicFormDialog } from '@/components/dynamic-form';
|
||||
// import Monitor from './Monitor.vue';
|
||||
import { machineApi } from './api';
|
||||
import SshTerminal from './SshTerminal.vue';
|
||||
import ServiceManage from './ServiceManage.vue';
|
||||
import FileManage from './FileManage.vue';
|
||||
import MachineEdit from './MachineEdit.vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'MachineList',
|
||||
components: {
|
||||
// Monitor,
|
||||
SshTerminal,
|
||||
ServiceManage,
|
||||
FileManage,
|
||||
DynamicFormDialog,
|
||||
MachineEdit,
|
||||
},
|
||||
setup() {
|
||||
const router = useRouter();
|
||||
// const monitorDialogRef = ref();
|
||||
const state = reactive({
|
||||
params: {
|
||||
pageNum: 1,
|
||||
@@ -149,86 +150,10 @@ export default defineComponent({
|
||||
visible: false,
|
||||
machineId: 0,
|
||||
},
|
||||
formDialog: {
|
||||
machineEditDialog: {
|
||||
visible: false,
|
||||
title: '',
|
||||
formInfo: {
|
||||
createApi: machineApi.save,
|
||||
updateApi: machineApi.save,
|
||||
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 },
|
||||
data: null,
|
||||
title: '新增机器',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -261,7 +186,6 @@ export default defineComponent({
|
||||
// };
|
||||
|
||||
const showTerminal = (row: any) => {
|
||||
// router.push(`/machine/${row.id}/terminal?id=${row.id}&name=${row.name}&time=${new Date().getTime()}`);
|
||||
const { href } = router.resolve({
|
||||
path: `/machine/terminal`,
|
||||
query: {
|
||||
@@ -275,21 +199,30 @@ export default defineComponent({
|
||||
const openFormDialog = (redis: any) => {
|
||||
let dialogTitle;
|
||||
if (redis) {
|
||||
state.formDialog.formData = state.currentData as any;
|
||||
state.machineEditDialog.data = state.currentData as any;
|
||||
dialogTitle = '编辑机器';
|
||||
} else {
|
||||
state.formDialog.formData = { port: 22 };
|
||||
state.machineEditDialog.data = { port: 22 } as any;
|
||||
dialogTitle = '添加机器';
|
||||
}
|
||||
|
||||
state.formDialog.title = dialogTitle;
|
||||
state.formDialog.visible = true;
|
||||
state.machineEditDialog.title = dialogTitle;
|
||||
state.machineEditDialog.visible = true;
|
||||
};
|
||||
|
||||
const deleteMachine = async (id: number) => {
|
||||
await machineApi.del.request({ id });
|
||||
ElMessage.success('操作成功');
|
||||
search();
|
||||
try {
|
||||
await ElMessageBox.confirm(`确定删除该机器信息? 该操作将同时删除脚本及文件配置信息`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
});
|
||||
await machineApi.del.request({ id });
|
||||
ElMessage.success('操作成功');
|
||||
state.currentId = null;
|
||||
state.currentData = null;
|
||||
search();
|
||||
} catch (err) {}
|
||||
};
|
||||
|
||||
const serviceManager = (row: any) => {
|
||||
|
||||
@@ -24,6 +24,10 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="params" label="参数">
|
||||
<el-input v-model.trim="form.params" placeholder="参数数组json,若无可不填"></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="script" label="内容" id="content">
|
||||
<codemirror ref="cmEditor" v-model="form.script" language="shell" />
|
||||
</el-form-item>
|
||||
@@ -31,7 +35,15 @@
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button v-auth="'machine:script:save'" type="primary" :loading="btnLoading" @click="btnOk" size="mini" :disabled="submitDisabled">保 存</el-button>
|
||||
<el-button
|
||||
v-auth="'machine:script:save'"
|
||||
type="primary"
|
||||
:loading="btnLoading"
|
||||
@click="btnOk"
|
||||
size="mini"
|
||||
:disabled="submitDisabled"
|
||||
>保 存</el-button
|
||||
>
|
||||
<el-button @click="cancel()" :disabled="submitDisabled" size="mini">关 闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
@@ -83,6 +95,7 @@ export default defineComponent({
|
||||
machineId: 0,
|
||||
description: '',
|
||||
script: '',
|
||||
params: null,
|
||||
type: null,
|
||||
},
|
||||
btnLoading: false,
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
</el-radio>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="name" label="名称" :min-width="50"> </el-table-column>
|
||||
<el-table-column prop="name" label="名称" :min-width="70"> </el-table-column>
|
||||
<el-table-column prop="description" label="描述" :min-width="100" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="name" label="类型" :min-width="50">
|
||||
<template #default="scope">
|
||||
@@ -64,6 +64,19 @@
|
||||
</el-table>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog title="脚本参数" v-model="scriptParamsDialog.visible" width="400px">
|
||||
<el-form ref="paramsForm" :model="scriptParamsDialog.params" label-width="70px" size="mini">
|
||||
<el-form-item v-for="item in scriptParamsDialog.paramsFormItem" :key="item.name" :prop="item.model" :label="item.name" required>
|
||||
<el-input v-model="scriptParamsDialog.params[item.model]" :placeholder="item.placeholder" autocomplete="off"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button type="primary" @click="hasParamsRun(currentData)" size="mini">确 定</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog title="执行结果" v-model="resultDialog.visible" width="40%">
|
||||
<div style="white-space: pre-line; padding: 10px; color: #000000">
|
||||
{{ resultDialog.result }}
|
||||
@@ -94,7 +107,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { toRefs, reactive, watch, defineComponent } from 'vue';
|
||||
import { ref, toRefs, reactive, watch, defineComponent } from 'vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import SshTerminal from './SshTerminal.vue';
|
||||
import { machineApi } from './api';
|
||||
@@ -113,6 +126,7 @@ export default defineComponent({
|
||||
title: { type: String },
|
||||
},
|
||||
setup(props: any, context) {
|
||||
const paramsForm: any = ref(null);
|
||||
const state = reactive({
|
||||
visible: false,
|
||||
type: 0,
|
||||
@@ -125,6 +139,11 @@ export default defineComponent({
|
||||
machineId: 9999999,
|
||||
},
|
||||
scriptTable: [],
|
||||
scriptParamsDialog: {
|
||||
visible: false,
|
||||
params: {},
|
||||
paramsFormItem: [],
|
||||
},
|
||||
resultDialog: {
|
||||
visible: false,
|
||||
result: '',
|
||||
@@ -152,13 +171,43 @@ export default defineComponent({
|
||||
};
|
||||
|
||||
const runScript = async (script: any) => {
|
||||
// 如果存在参数,则弹窗输入参数后执行
|
||||
if (script.params) {
|
||||
state.scriptParamsDialog.paramsFormItem = JSON.parse(script.params);
|
||||
state.scriptParamsDialog.visible = true;
|
||||
return;
|
||||
}
|
||||
|
||||
run(script);
|
||||
};
|
||||
|
||||
// 有参数的脚本执行函数
|
||||
const hasParamsRun = async (script: any) => {
|
||||
// 如果脚本参数弹窗显示,则校验参数表单数据通过后执行
|
||||
if (state.scriptParamsDialog.visible) {
|
||||
paramsForm.value.validate((valid: any) => {
|
||||
if (valid) {
|
||||
run(script);
|
||||
state.scriptParamsDialog.params = {};
|
||||
state.scriptParamsDialog.visible = false;
|
||||
paramsForm.value.resetFields();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const run = async (script: any) => {
|
||||
const noResult = script.type == enums.scriptTypeEnum['NO_RESULT'].value;
|
||||
// 如果脚本类型为有结果类型,则显示结果信息
|
||||
if (script.type == enums.scriptTypeEnum['RESULT'].value || noResult) {
|
||||
const res = await machineApi.runScript.request({
|
||||
machineId: props.machineId,
|
||||
scriptId: script.id,
|
||||
params: state.scriptParamsDialog.params,
|
||||
});
|
||||
|
||||
if (noResult) {
|
||||
ElMessage.success('执行完成');
|
||||
return;
|
||||
@@ -241,9 +290,11 @@ export default defineComponent({
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
paramsForm,
|
||||
enums,
|
||||
getScripts,
|
||||
runScript,
|
||||
hasParamsRun,
|
||||
closeTermnial,
|
||||
choose,
|
||||
editScript,
|
||||
|
||||
@@ -6,9 +6,9 @@ export const machineApi = {
|
||||
info: Api.create("/machines/{id}/sysinfo", 'get'),
|
||||
top: Api.create("/machines/{id}/top", 'get'),
|
||||
// 保存按钮
|
||||
save: Api.create("/machines", 'post'),
|
||||
saveMachine: Api.create("/machines", 'post'),
|
||||
// 删除机器
|
||||
del: Api.create("/devops/machines/{id}", 'delete'),
|
||||
del: Api.create("/machines/delete/{id}", 'delete'),
|
||||
scripts: Api.create("/machines/{machineId}/scripts", 'get'),
|
||||
runScript: Api.create("/machines/{machineId}/scripts/{scriptId}/run", 'get'),
|
||||
saveScript: Api.create("/machines/{machineId}/scripts", 'post'),
|
||||
|
||||
413
mayfly_go_web/src/views/ops/project/ProjectList.vue
Normal file
413
mayfly_go_web/src/views/ops/project/ProjectList.vue
Normal file
@@ -0,0 +1,413 @@
|
||||
<template>
|
||||
<div class="project-list">
|
||||
<div class="toolbar">
|
||||
<el-button @click="showAddProjectDialog" v-auth="permissions.saveProject" type="primary" icon="el-icon-plus" size="mini">添加</el-button>
|
||||
<el-button
|
||||
@click="showAddProjectDialog(chooseData)"
|
||||
v-auth="permissions.saveProject"
|
||||
:disabled="chooseId == null"
|
||||
type="primary"
|
||||
icon="el-icon-edit"
|
||||
size="mini"
|
||||
>编辑</el-button
|
||||
>
|
||||
<el-button @click="showMembers(chooseData)" :disabled="chooseId == null" type="success" icon="el-icon-setting" size="mini"
|
||||
>成员管理</el-button
|
||||
>
|
||||
|
||||
<el-button @click="showEnv(chooseData)" :disabled="chooseId == null" type="info" icon="el-icon-setting" size="mini">环境管理</el-button>
|
||||
|
||||
<el-button v-auth="'role:del'" :disabled="chooseId == null" type="danger" icon="el-icon-delete" size="mini">删除</el-button>
|
||||
|
||||
<div style="float: right">
|
||||
<el-input
|
||||
class="mr2"
|
||||
placeholder="请输入项目名!"
|
||||
size="small"
|
||||
style="width: 140px"
|
||||
v-model="query.name"
|
||||
@clear="search"
|
||||
clearable
|
||||
></el-input>
|
||||
<el-button @click="search" type="success" icon="el-icon-search" size="mini"></el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-table :data="projects" @current-change="choose" border ref="table" style="width: 100%">
|
||||
<el-table-column label="选择" width="50px">
|
||||
<template #default="scope">
|
||||
<el-radio v-model="chooseId" :label="scope.row.id">
|
||||
<i></i>
|
||||
</el-radio>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="name" label="项目名"></el-table-column>
|
||||
<el-table-column prop="remark" label="描述" min-width="180px" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间">
|
||||
<template #default="scope">
|
||||
{{ $filters.dateFormat(scope.row.createTime) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="creator" label="创建者"> </el-table-column>
|
||||
<!-- <el-table-column label="查看更多" min-width="80px">
|
||||
<template #default="scope">
|
||||
<el-link @click.prevent="showMembers(scope.row)" type="success">成员</el-link>
|
||||
|
||||
<el-link class="ml5" @click.prevent="showEnv(scope.row)" type="info">环境</el-link>
|
||||
</template>
|
||||
</el-table-column> -->
|
||||
</el-table>
|
||||
<el-pagination
|
||||
@current-change="handlePageChange"
|
||||
style="text-align: center"
|
||||
background
|
||||
layout="prev, pager, next, total, jumper"
|
||||
:total="total"
|
||||
v-model:current-page="query.pageNum"
|
||||
:page-size="query.pageSize"
|
||||
/>
|
||||
|
||||
<el-dialog width="400px" title="项目编辑" :before-close="cancelAddProject" v-model="addProjectDialog.visible">
|
||||
<el-form :model="addProjectDialog.form" size="small" label-width="70px">
|
||||
<el-form-item label="项目名:" required>
|
||||
<el-input :disabled="addProjectDialog.form.id" v-model="addProjectDialog.form.name" auto-complete="off"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="描述:">
|
||||
<el-input v-model="addProjectDialog.form.remark" auto-complete="off"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="addProject" type="primary" size="small">确 定</el-button>
|
||||
<el-button @click="cancelAddProject()" size="small">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog width="500px" :title="showEnvDialog.title" v-model="showEnvDialog.visible">
|
||||
<div class="toolbar">
|
||||
<el-button @click="showAddEnvDialog" v-auth="permissions.saveMember" type="primary" icon="el-icon-plus" size="mini">添加</el-button>
|
||||
<!-- <el-button v-auth="'role:update'" :disabled="chooseId == null" type="danger" icon="el-icon-delete" size="mini">删除</el-button> -->
|
||||
</div>
|
||||
<el-table border :data="showEnvDialog.envs" size="small">
|
||||
<el-table-column property="name" label="环境名" width="125"></el-table-column>
|
||||
<el-table-column property="remark" label="描述" width="125"></el-table-column>
|
||||
<el-table-column property="createTime" label="创建时间">
|
||||
<template #default="scope">
|
||||
{{ $filters.dateFormat(scope.row.createTime) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-dialog width="400px" title="添加环境" :before-close="cancelAddEnv" v-model="showEnvDialog.addVisible">
|
||||
<el-form :model="showEnvDialog.envForm" size="small" label-width="70px">
|
||||
<el-form-item label="环境名:" required>
|
||||
<el-input v-model="showEnvDialog.envForm.name" auto-complete="off"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="描述:">
|
||||
<el-input v-model="showEnvDialog.envForm.remark" auto-complete="off"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button v-auth="permissions.saveEnv" @click="addEnv" type="primary" :loading="btnLoading" size="small">确 定</el-button>
|
||||
<el-button @click="cancelAddEnv()" size="small">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog width="500px" :title="showMemDialog.title" v-model="showMemDialog.visible">
|
||||
<div class="toolbar">
|
||||
<el-button v-auth="permissions.saveMember" @click="showAddMemberDialog()" type="primary" icon="el-icon-plus" size="mini"
|
||||
>添加</el-button
|
||||
>
|
||||
<el-button
|
||||
v-auth="permissions.delMember"
|
||||
@click="deleteMember"
|
||||
:disabled="showMemDialog.chooseId == null"
|
||||
type="danger"
|
||||
icon="el-icon-delete"
|
||||
size="mini"
|
||||
>移除</el-button
|
||||
>
|
||||
</div>
|
||||
<el-table @current-change="chooseMember" border :data="showMemDialog.members.list" size="small">
|
||||
<el-table-column label="选择" width="50px">
|
||||
<template #default="scope">
|
||||
<el-radio v-model="showMemDialog.chooseId" :label="scope.row.id">
|
||||
<i></i>
|
||||
</el-radio>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column property="username" label="账号" width="125"></el-table-column>
|
||||
<el-table-column property="createTime" label="加入时间">
|
||||
<template #default="scope">
|
||||
{{ $filters.dateFormat(scope.row.createTime) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column property="creator" label="分配者" width="125"></el-table-column>
|
||||
</el-table>
|
||||
<el-pagination
|
||||
@current-change="setMemebers"
|
||||
style="text-align: center"
|
||||
background
|
||||
layout="prev, pager, next, total, jumper"
|
||||
:total="showMemDialog.members.total"
|
||||
v-model:current-page="showMemDialog.query.pageNum"
|
||||
:page-size="showMemDialog.query.pageSize"
|
||||
/>
|
||||
|
||||
<el-dialog width="400px" title="添加成员" :before-close="cancelAddMember" v-model="showMemDialog.addVisible">
|
||||
<el-form :model="showMemDialog.memForm" size="small" label-width="70px">
|
||||
<el-form-item label="账号:">
|
||||
<el-select style="width: 100%" remote :remote-method="getAccount" v-model="showMemDialog.memForm.accountId" filterable placeholder="请选择">
|
||||
<el-option v-for="item in showMemDialog.accounts" :key="item.id" :label="item.username" :value="item.id"> </el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<!-- <el-form-item label="描述:">
|
||||
<el-input v-model="showEnvDialog.envForm.remark" auto-complete="off"></el-input>
|
||||
</el-form-item> -->
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button v-auth="permissions.saveMember" @click="addMember" type="primary" :loading="btnLoading" size="small"
|
||||
>确 定</el-button
|
||||
>
|
||||
<el-button @click="cancelAddMember()" size="small">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { toRefs, reactive, onMounted, defineComponent } from 'vue';
|
||||
import { projectApi } from './api';
|
||||
import { accountApi } from '../../system/api';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { notEmpty, notNull } from '@/common/assert';
|
||||
import { auth } from '../../../common/utils/authFunction';
|
||||
export default defineComponent({
|
||||
name: 'ProjectList',
|
||||
components: {},
|
||||
setup() {
|
||||
const state = reactive({
|
||||
permissions: {
|
||||
saveProject: 'project:save',
|
||||
saveMember: 'project:member:add',
|
||||
delMember: 'project:member:del',
|
||||
saveEnv: 'project:env:add',
|
||||
},
|
||||
query: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
name: null,
|
||||
},
|
||||
total: 0,
|
||||
projects: [],
|
||||
btnLoading: false,
|
||||
chooseId: null as any,
|
||||
chooseData: null as any,
|
||||
addProjectDialog: {
|
||||
title: '新增项目',
|
||||
visible: false,
|
||||
form: { name: '', remark: '' },
|
||||
},
|
||||
showEnvDialog: {
|
||||
visible: false,
|
||||
envs: [],
|
||||
title: '',
|
||||
addVisible: false,
|
||||
envForm: {
|
||||
name: '',
|
||||
remark: '',
|
||||
projectId: 0,
|
||||
},
|
||||
},
|
||||
showMemDialog: {
|
||||
visible: false,
|
||||
chooseId: null,
|
||||
chooseData: null,
|
||||
query: {
|
||||
pageSize: 8,
|
||||
pageNum: 1,
|
||||
projectId: null,
|
||||
},
|
||||
members: {
|
||||
list: [],
|
||||
total: null,
|
||||
},
|
||||
title: '',
|
||||
addVisible: false,
|
||||
memForm: {},
|
||||
accounts: [],
|
||||
},
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
search();
|
||||
});
|
||||
|
||||
const search = async () => {
|
||||
let res = await projectApi.projects.request(state.query);
|
||||
state.projects = res.list;
|
||||
state.total = res.total;
|
||||
};
|
||||
|
||||
const handlePageChange = (curPage: number) => {
|
||||
state.query.pageNum = curPage;
|
||||
search();
|
||||
};
|
||||
|
||||
const showAddProjectDialog = (data: any) => {
|
||||
if (data) {
|
||||
state.addProjectDialog.form = data;
|
||||
} else {
|
||||
state.addProjectDialog.form = {} as any;
|
||||
}
|
||||
state.addProjectDialog.visible = true;
|
||||
};
|
||||
|
||||
const cancelAddProject = () => {
|
||||
state.addProjectDialog.visible = false;
|
||||
state.addProjectDialog.form = {} as any;
|
||||
};
|
||||
|
||||
const addProject = async () => {
|
||||
const form = state.addProjectDialog.form as any;
|
||||
notEmpty(form.name, '项目名不能为空');
|
||||
notEmpty(form.remark, '项目描述不能为空');
|
||||
|
||||
await projectApi.saveProject.request(form);
|
||||
ElMessage.success('保存成功');
|
||||
search();
|
||||
cancelAddProject();
|
||||
};
|
||||
|
||||
const choose = (item: any) => {
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
state.chooseId = item.id;
|
||||
state.chooseData = item;
|
||||
};
|
||||
|
||||
const showMembers = async (project: any) => {
|
||||
state.showMemDialog.query.projectId = project.id;
|
||||
await setMemebers();
|
||||
state.showMemDialog.title = `${project.name}的成员信息`;
|
||||
state.showMemDialog.visible = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* 选中成员
|
||||
*/
|
||||
const chooseMember = (item: any) => {
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
state.showMemDialog.chooseData = item;
|
||||
state.showMemDialog.chooseId = item.id;
|
||||
};
|
||||
|
||||
const deleteMember = async () => {
|
||||
notNull(state.showMemDialog.chooseData, '请选选择成员');
|
||||
await projectApi.deleteProjectMem.request(state.showMemDialog.chooseData);
|
||||
ElMessage.success('移除成功');
|
||||
// 重新赋值成员列表
|
||||
setMemebers();
|
||||
};
|
||||
|
||||
/**
|
||||
* 设置成员列表信息
|
||||
*/
|
||||
const setMemebers = async () => {
|
||||
const res = await projectApi.projectMems.request(state.showMemDialog.query);
|
||||
state.showMemDialog.members.list = res.list;
|
||||
state.showMemDialog.members.total = res.total;
|
||||
};
|
||||
|
||||
const showEnv = async (project: any) => {
|
||||
state.showEnvDialog.envs = await projectApi.projectEnvs.request({ projectId: project.id });
|
||||
state.showEnvDialog.title = `${project.name}的环境信息`;
|
||||
state.showEnvDialog.visible = true;
|
||||
};
|
||||
|
||||
const showAddMemberDialog = () => {
|
||||
state.showMemDialog.addVisible = true;
|
||||
};
|
||||
|
||||
const addMember = async () => {
|
||||
const memForm = state.showMemDialog.memForm as any;
|
||||
memForm.projectId = state.chooseData.id;
|
||||
notEmpty(memForm.accountId, '请先选择账号');
|
||||
|
||||
await projectApi.saveProjectMem.request(memForm);
|
||||
ElMessage.success('保存成功');
|
||||
setMemebers();
|
||||
cancelAddMember();
|
||||
};
|
||||
|
||||
const cancelAddMember = () => {
|
||||
state.showMemDialog.memForm = {};
|
||||
state.showMemDialog.addVisible = false;
|
||||
state.showMemDialog.chooseData = null;
|
||||
state.showMemDialog.chooseId = null;
|
||||
};
|
||||
|
||||
const getAccount = (username: any) => {
|
||||
accountApi.list.request({ username }).then((res) => {
|
||||
state.showMemDialog.accounts = res.list;
|
||||
});
|
||||
};
|
||||
|
||||
const showAddEnvDialog = () => {
|
||||
state.showEnvDialog.addVisible = true;
|
||||
};
|
||||
|
||||
const addEnv = async () => {
|
||||
const envForm = state.showEnvDialog.envForm;
|
||||
envForm.projectId = state.chooseData.id;
|
||||
await projectApi.saveProjectEnv.request(envForm);
|
||||
ElMessage.success('保存成功');
|
||||
state.showEnvDialog.envs = await projectApi.projectEnvs.request({ projectId: envForm.projectId });
|
||||
cancelAddEnv();
|
||||
};
|
||||
|
||||
const cancelAddEnv = () => {
|
||||
state.showEnvDialog.envForm = {} as any;
|
||||
state.showEnvDialog.addVisible = false;
|
||||
};
|
||||
|
||||
const roleEditChange = (data: any) => {
|
||||
ElMessage.success('修改成功!');
|
||||
search();
|
||||
};
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
search,
|
||||
handlePageChange,
|
||||
choose,
|
||||
showAddProjectDialog,
|
||||
addProject,
|
||||
cancelAddProject,
|
||||
showMembers,
|
||||
setMemebers,
|
||||
showEnv,
|
||||
showAddMemberDialog,
|
||||
addMember,
|
||||
chooseMember,
|
||||
deleteMember,
|
||||
cancelAddMember,
|
||||
showAddEnvDialog,
|
||||
addEnv,
|
||||
cancelAddEnv,
|
||||
getAccount,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="scss">
|
||||
</style>
|
||||
15
mayfly_go_web/src/views/ops/project/api.ts
Normal file
15
mayfly_go_web/src/views/ops/project/api.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import Api from '@/common/Api';
|
||||
|
||||
export const projectApi = {
|
||||
// 获取账号可访问的项目列表
|
||||
accountProjects: Api.create("/accounts/projects", 'get'),
|
||||
projects: Api.create("/projects", 'get'),
|
||||
saveProject: Api.create("/projects", 'post'),
|
||||
// 获取项目下的环境信息
|
||||
projectEnvs: Api.create("/projects/{projectId}/envs", 'get'),
|
||||
saveProjectEnv: Api.create("/projects/{projectId}/envs", 'post'),
|
||||
// 获取项目下的成员信息
|
||||
projectMems: Api.create("/projects/{projectId}/members", 'get'),
|
||||
saveProjectMem: Api.create("/projects/{projectId}/members", 'post'),
|
||||
deleteProjectMem: Api.create("/projects/{projectId}/members/{accountId}", 'delete'),
|
||||
}
|
||||
281
mayfly_go_web/src/views/ops/redis/DataOperation.vue
Normal file
281
mayfly_go_web/src/views/ops/redis/DataOperation.vue
Normal file
@@ -0,0 +1,281 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="toolbar">
|
||||
<div style="float: left">
|
||||
<el-row type="flex" justify="space-between">
|
||||
<el-col :span="24">
|
||||
<project-env-select @changeProjectEnv="changeProjectEnv" @clear="clearRedis">
|
||||
<template #default>
|
||||
<el-form-item label="redis" label-width="40px">
|
||||
<el-select v-model="scanParam.id" placeholder="请选择redis" @change="changeRedis" @clear="clearRedis" clearable>
|
||||
<el-option v-for="item in redisList" :key="item.id" :label="item.host" :value="item.id">
|
||||
<span style="float: left">{{ item.host }}</span>
|
||||
<span style="float: right; color: #8492a6; margin-left: 6px; font-size: 13px">{{
|
||||
`库: [${item.db}]`
|
||||
}}</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="key" label-width="40px">
|
||||
<el-input
|
||||
placeholder="支持*模糊key"
|
||||
style="width: 180px"
|
||||
v-model="scanParam.match"
|
||||
size="mini"
|
||||
@clear="clear()"
|
||||
clearable
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label-width="40px">
|
||||
<el-input placeholder="count" style="width: 62px" v-model="scanParam.count" size="mini"></el-input>
|
||||
</el-form-item>
|
||||
<el-button @click="searchKey()" type="success" icon="el-icon-search" size="mini" plain></el-button>
|
||||
<el-button @click="scan()" icon="el-icon-bottom" size="mini" plain>scan</el-button>
|
||||
<el-button type="primary" icon="el-icon-plus" size="mini" @click="save(false)" plain></el-button>
|
||||
</template>
|
||||
</project-env-select>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
<div style="float: right">
|
||||
<!-- <el-button @click="scan()" icon="el-icon-refresh" size="small" plain>刷新</el-button> -->
|
||||
<span>keys: {{ dbsize }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<el-table v-loading="loading" :data="keys" border stripe :highlight-current-row="true" style="cursor: pointer">
|
||||
<el-table-column show-overflow-tooltip prop="key" label="key"></el-table-column>
|
||||
<el-table-column prop="type" label="type" width="80"> </el-table-column>
|
||||
<el-table-column prop="ttl" label="ttl(过期时间)" width="120">
|
||||
<template #default="scope">
|
||||
{{ ttlConveter(scope.row.ttl) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作">
|
||||
<template #default="scope">
|
||||
<el-button @click="getValue(scope.row)" type="success" icon="el-icon-search" size="mini" plain>查看</el-button>
|
||||
<el-button @click="del(scope.row.key)" type="danger" size="mini" icon="el-icon-delete" plain>删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div style="text-align: center; margin-top: 10px"></div>
|
||||
|
||||
<value-dialog v-model:visible="valueDialog.visible" :keyValue="valueDialog.value" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import ValueDialog from './ValueDialog.vue';
|
||||
import { redisApi } from './api';
|
||||
import { toRefs, reactive, defineComponent } from 'vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import ProjectEnvSelect from '../component/ProjectEnvSelect.vue';
|
||||
import { isTrue, notNull } from '@/common/assert';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'DataOperation',
|
||||
components: {
|
||||
ValueDialog,
|
||||
ProjectEnvSelect,
|
||||
},
|
||||
setup() {
|
||||
const state = reactive({
|
||||
loading: false,
|
||||
cluster: 0,
|
||||
redisList: [],
|
||||
query: {
|
||||
envId: 0,
|
||||
},
|
||||
// redis: {
|
||||
// id: 0,
|
||||
// info: '',
|
||||
// conf: '',
|
||||
// },
|
||||
scanParam: {
|
||||
id: null,
|
||||
cluster: 0,
|
||||
match: null,
|
||||
count: 10,
|
||||
cursor: 0,
|
||||
prevCursor: null,
|
||||
},
|
||||
valueDialog: {
|
||||
visible: false,
|
||||
value: {},
|
||||
},
|
||||
keys: [],
|
||||
dbsize: 0,
|
||||
});
|
||||
|
||||
const searchRedis = async () => {
|
||||
notNull(state.query.envId, '请先选择项目环境');
|
||||
const res = await redisApi.redisList.request(state.query);
|
||||
state.redisList = res.list;
|
||||
};
|
||||
|
||||
const changeProjectEnv = (projectId: any, envId: any) => {
|
||||
clearRedis();
|
||||
if (envId != null) {
|
||||
state.query.envId = envId;
|
||||
searchRedis();
|
||||
}
|
||||
};
|
||||
|
||||
const changeRedis = (redisId: any) => {
|
||||
resetScanParam();
|
||||
state.keys = [];
|
||||
state.dbsize = 0;
|
||||
searchKey();
|
||||
};
|
||||
|
||||
const scan = () => {
|
||||
isTrue(state.scanParam.id != null, '请先选择redis');
|
||||
isTrue(state.scanParam.count < 2001, 'count不能超过2000');
|
||||
|
||||
state.loading = true;
|
||||
state.scanParam.cluster = state.cluster == 0 ? 0 : 1;
|
||||
|
||||
redisApi.scan.request(state.scanParam).then((res) => {
|
||||
state.keys = res.keys;
|
||||
state.dbsize = res.dbSize;
|
||||
state.scanParam.cursor = res.cursor;
|
||||
state.loading = false;
|
||||
});
|
||||
};
|
||||
|
||||
const searchKey = () => {
|
||||
state.scanParam.cursor = 0;
|
||||
scan();
|
||||
};
|
||||
|
||||
const clearRedis = () => {
|
||||
state.redisList = [];
|
||||
state.scanParam.id = null;
|
||||
resetScanParam();
|
||||
state.keys = [];
|
||||
state.dbsize = 0;
|
||||
};
|
||||
|
||||
const clear = () => {
|
||||
resetScanParam();
|
||||
if (state.scanParam.id) {
|
||||
scan();
|
||||
}
|
||||
};
|
||||
|
||||
const resetScanParam = () => {
|
||||
state.scanParam.match = null;
|
||||
state.scanParam.cursor = 0;
|
||||
state.scanParam.count = 10;
|
||||
};
|
||||
|
||||
const getValue = async (row: any) => {
|
||||
let api: any;
|
||||
switch (row.type) {
|
||||
case 'string':
|
||||
api = redisApi.getStringValue;
|
||||
break;
|
||||
case 'hash':
|
||||
api = redisApi.getHashValue;
|
||||
break;
|
||||
case 'set':
|
||||
api = redisApi.getSetValue;
|
||||
break;
|
||||
default:
|
||||
api = redisApi.getStringValue;
|
||||
break;
|
||||
}
|
||||
const id = state.cluster == 0 ? state.scanParam.id : state.cluster;
|
||||
const res = await api.request({
|
||||
cluster: state.cluster,
|
||||
key: row.key,
|
||||
id,
|
||||
});
|
||||
|
||||
let timed = row.ttl == 18446744073709552000 ? 0 : row.ttl;
|
||||
state.valueDialog.value = { id: state.scanParam.id, key: row.key, value: res, timed: timed, type: row.type };
|
||||
state.valueDialog.visible = true;
|
||||
};
|
||||
|
||||
// closeValueDialog() {
|
||||
// this.valueDialog.visible = false
|
||||
// this.valueDialog.value = {}
|
||||
// }
|
||||
|
||||
const update = (key: string) => {};
|
||||
|
||||
const del = (key: string) => {
|
||||
ElMessageBox.confirm(`此操作将删除对应的key , 是否继续?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => {
|
||||
let id = state.cluster == 0 ? state.scanParam.id : state.cluster;
|
||||
redisApi.delKey
|
||||
.request({
|
||||
cluster: state.cluster,
|
||||
key,
|
||||
id,
|
||||
})
|
||||
.then((res) => {
|
||||
ElMessage.success('删除成功!');
|
||||
scan();
|
||||
});
|
||||
})
|
||||
.catch((err) => {});
|
||||
};
|
||||
|
||||
const ttlConveter = (ttl: any) => {
|
||||
if (ttl == 18446744073709552000) {
|
||||
return '永久';
|
||||
}
|
||||
if (!ttl) {
|
||||
ttl = 0;
|
||||
}
|
||||
let second = parseInt(ttl); // 秒
|
||||
let min = 0; // 分
|
||||
let hour = 0; // 小时
|
||||
let day = 0;
|
||||
if (second > 60) {
|
||||
min = parseInt(second / 60 + '');
|
||||
second = second % 60;
|
||||
if (min > 60) {
|
||||
hour = parseInt(min / 60 + '');
|
||||
min = min % 60;
|
||||
if (hour > 24) {
|
||||
day = parseInt(hour / 24 + '');
|
||||
hour = hour % 24;
|
||||
}
|
||||
}
|
||||
}
|
||||
let result = '' + second + 's';
|
||||
if (min > 0) {
|
||||
result = '' + min + 'm:' + result;
|
||||
}
|
||||
if (hour > 0) {
|
||||
result = '' + hour + 'h:' + result;
|
||||
}
|
||||
if (day > 0) {
|
||||
result = '' + day + 'd:' + result;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
changeProjectEnv,
|
||||
changeRedis,
|
||||
clearRedis,
|
||||
searchKey,
|
||||
scan,
|
||||
clear,
|
||||
getValue,
|
||||
del,
|
||||
ttlConveter,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
200
mayfly_go_web/src/views/ops/redis/Info.vue
Normal file
200
mayfly_go_web/src/views/ops/redis/Info.vue
Normal file
@@ -0,0 +1,200 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-dialog :title="title" v-model="visible" :show-close="true" width="35%" @close="close()">
|
||||
<el-collapse>
|
||||
<el-collapse-item title="Server(Redis服务器的一般信息)" name="server">
|
||||
<div class="row">
|
||||
<span class="title">redis_version(版本):</span>
|
||||
<span class="value">{{ info.Server.redis_version }}</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="title">tcp_port(端口):</span>
|
||||
<span class="value">{{ info.Server.tcp_port }}</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="title">redis_mode(模式):</span>
|
||||
<span class="value">{{ info.Server.redis_mode }}</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="title">os(宿主操作系统):</span>
|
||||
<span class="value">{{ info.Server.os }}</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="title">uptime_in_days(运行天数):</span>
|
||||
<span class="value">{{ info.Server.uptime_in_days }}</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="title">executable(可执行文件路径):</span>
|
||||
<span class="value">{{ info.Server.executable }}</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="title">config_file(配置文件路径):</span>
|
||||
<span class="value">{{ info.Server.config_file }}</span>
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
|
||||
<el-collapse-item title="Clients(客户端连接)" name="client">
|
||||
<div class="row">
|
||||
<span class="title">connected_clients(已连接客户端数):</span>
|
||||
<span class="value">{{ info.Clients.connected_clients }}</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="title">blocked_clients(正在等待阻塞命令客户端数):</span>
|
||||
<span class="value">{{ info.Clients.blocked_clients }}</span>
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
<el-collapse-item title="Keyspace(key信息)" name="keyspace">
|
||||
<div class="row" v-for="(value, key) in info.Keyspace" :key="key">
|
||||
<span class="title">{{ key }}: </span>
|
||||
<span class="value">{{ value }}</span>
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
|
||||
<el-collapse-item title="Stats(统计)" name="state">
|
||||
<div class="row">
|
||||
<span class="title">total_commands_processed(总处理命令数):</span>
|
||||
<span class="value">{{ info.Stats.total_commands_processed }}</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="title">instantaneous_ops_per_sec(当前qps):</span>
|
||||
<span class="value">{{ info.Stats.instantaneous_ops_per_sec }}</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="title">total_net_input_bytes(网络入口流量字节数):</span>
|
||||
<span class="value">{{ info.Stats.total_net_input_bytes }}</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="title">total_net_output_bytes(网络出口流量字节数):</span>
|
||||
<span class="value">{{ info.Stats.total_net_output_bytes }}</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="title">expired_keys(过期key的总数量):</span>
|
||||
<span class="value">{{ info.Stats.expired_keys }}</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="title">instantaneous_ops_per_sec(当前qps):</span>
|
||||
<span class="value">{{ info.Stats.instantaneous_ops_per_sec }}</span>
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
|
||||
<el-collapse-item title="Persistence(持久化)" name="persistence">
|
||||
<div class="row">
|
||||
<span class="title">aof_enabled(是否启用aof):</span>
|
||||
<span class="value">{{ info.Persistence.aof_enabled }}</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="title">loading(是否正在载入持久化文件):</span>
|
||||
<span class="value">{{ info.Persistence.loading }}</span>
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
|
||||
<el-collapse-item title="Cluster(集群)" name="cluster">
|
||||
<div class="row">
|
||||
<span class="title">cluster_enabled(是否启用集群模式):</span>
|
||||
<span class="value">{{ info.Cluster.cluster_enabled }}</span>
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
|
||||
<el-collapse-item title="Memory(内存消耗相关信息)" name="memory">
|
||||
<div class="row">
|
||||
<span class="title">used_memory(分配内存总量):</span>
|
||||
<span class="value">{{ info.Memory.used_memory_human }}</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="title">maxmemory(最大内存配置):</span>
|
||||
<span class="value">{{ info.Memory.maxmemory }}</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="title">used_memory_rss(已分配的内存总量,操作系统角度):</span>
|
||||
<span class="value">{{ info.Memory.used_memory_rss_human }}</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="title">mem_fragmentation_ratio(used_memory_rss和used_memory 之间的比率):</span>
|
||||
<span class="value">{{ info.Memory.mem_fragmentation_ratio }}</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="title">used_memory_peak(内存消耗峰值):</span>
|
||||
<span class="value">{{ info.Memory.used_memory_peak_human }}</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="title">total_system_memory(主机总内存):</span>
|
||||
<span class="value">{{ info.Memory.total_system_memory_human }}</span>
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
|
||||
<el-collapse-item title="CPU" name="cpu">
|
||||
<div class="row">
|
||||
<span class="title">used_cpu_sys(由Redis服务器消耗的系统CPU):</span>
|
||||
<span class="value">{{ info.CPU.used_cpu_sys }}</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="title">used_cpu_user(由Redis服务器消耗的用户CPU):</span>
|
||||
<span class="value">{{ info.CPU.used_cpu_user }}</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="title">used_cpu_sys_children(由后台进程消耗的系统CPU):</span>
|
||||
<span class="value">{{ info.CPU.used_cpu_sys_children }}</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="title">used_cpu_user_children(由后台进程消耗的用户CPU):</span>
|
||||
<span class="value">{{ info.CPU.used_cpu_user_children }}</span>
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive, watch, toRefs } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Info',
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
},
|
||||
info: {
|
||||
type: [Boolean, Object],
|
||||
},
|
||||
},
|
||||
setup(props: any, { emit }) {
|
||||
const state = reactive({
|
||||
visible: false,
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(val) => {
|
||||
state.visible = val;
|
||||
}
|
||||
);
|
||||
|
||||
const close = () => {
|
||||
emit('update:visible', false);
|
||||
emit('close');
|
||||
};
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
close,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.row .title {
|
||||
font-size: 12px;
|
||||
color: #8492a6;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.row .value {
|
||||
font-size: 12px;
|
||||
color: black;
|
||||
}
|
||||
</style>
|
||||
190
mayfly_go_web/src/views/ops/redis/RedisEdit.vue
Normal file
190
mayfly_go_web/src/views/ops/redis/RedisEdit.vue
Normal file
@@ -0,0 +1,190 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-dialog :title="title" v-model="visible" :show-close="false" :before-close="cancel" width="35%">
|
||||
<el-form :model="form" ref="redisForm" :rules="rules" label-width="85px" size="small">
|
||||
<el-form-item prop="projectId" label="项目:" required>
|
||||
<el-select style="width: 100%" v-model="form.projectId" placeholder="请选择项目" @change="changeProject" filterable>
|
||||
<el-option v-for="item in projects" :key="item.id" :label="`${item.name} [${item.remark}]`" :value="item.id"> </el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="envId" label="环境:" required>
|
||||
<el-select @change="changeEnv" style="width: 100%" v-model="form.envId" placeholder="请选择环境">
|
||||
<el-option v-for="item in envs" :key="item.id" :label="`${item.name} [${item.remark}]`" :value="item.id"> </el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item prop="host" label="host:" required>
|
||||
<el-input v-model.trim="form.host" placeholder="请输入host:port" auto-complete="off"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="password" label="密码:">
|
||||
<el-input
|
||||
type="password"
|
||||
show-password
|
||||
v-model.trim="form.password"
|
||||
placeholder="请输入密码"
|
||||
autocomplete="new-password"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="db" label="库号:" required>
|
||||
<el-input v-model.trim="form.db" placeholder="请输入库号"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button type="primary" :loading="btnLoading" @click="btnOk" size="mini">确 定</el-button>
|
||||
<el-button @click="cancel()" size="mini">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { toRefs, reactive, watch, defineComponent, ref } from 'vue';
|
||||
import { redisApi } from './api';
|
||||
import { projectApi } from '../project/api.ts';
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'RedisEdit',
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
},
|
||||
projects: {
|
||||
type: Array,
|
||||
},
|
||||
redis: {
|
||||
type: [Boolean, Object],
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
},
|
||||
},
|
||||
setup(props: any, { emit }) {
|
||||
const redisForm: any = ref(null);
|
||||
const state = reactive({
|
||||
visible: false,
|
||||
projects: [],
|
||||
envs: [],
|
||||
form: {
|
||||
id: null,
|
||||
name: null,
|
||||
host: null,
|
||||
password: null,
|
||||
project: null,
|
||||
projectId: null,
|
||||
envId: null,
|
||||
env: null,
|
||||
},
|
||||
btnLoading: false,
|
||||
rules: {
|
||||
projectId: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择项目',
|
||||
trigger: ['change', 'blur'],
|
||||
},
|
||||
],
|
||||
envId: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择环境',
|
||||
trigger: ['change', 'blur'],
|
||||
},
|
||||
],
|
||||
host: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入主机ip:port',
|
||||
trigger: ['change', 'blur'],
|
||||
},
|
||||
],
|
||||
db: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入库号',
|
||||
trigger: ['change', 'blur'],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
watch(props, async (newValue, oldValue) => {
|
||||
state.visible = newValue.visible;
|
||||
state.projects = newValue.projects;
|
||||
if (newValue.redis) {
|
||||
getEnvs(newValue.redis.projectId);
|
||||
state.form = { ...newValue.redis };
|
||||
} else {
|
||||
state.envs = [];
|
||||
state.form = { db: 0 } as any;
|
||||
}
|
||||
});
|
||||
|
||||
const getEnvs = async (projectId: any) => {
|
||||
state.envs = await projectApi.projectEnvs.request({ projectId });
|
||||
};
|
||||
|
||||
const changeProject = (projectId: number) => {
|
||||
for (let p of state.projects as any) {
|
||||
if (p.id == projectId) {
|
||||
state.form.project = p.name;
|
||||
}
|
||||
}
|
||||
state.envs = [];
|
||||
getEnvs(projectId);
|
||||
};
|
||||
|
||||
const changeEnv = (envId: number) => {
|
||||
for (let p of state.envs as any) {
|
||||
if (p.id == envId) {
|
||||
state.form.env = p.name;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const btnOk = async () => {
|
||||
redisForm.value.validate((valid: boolean) => {
|
||||
if (valid) {
|
||||
redisApi.saveRedis.request(state.form).then((res: any) => {
|
||||
ElMessage.success('保存成功');
|
||||
emit('val-change', state.form);
|
||||
state.btnLoading = true;
|
||||
setTimeout(() => {
|
||||
state.btnLoading = false;
|
||||
}, 1000);
|
||||
|
||||
cancel();
|
||||
});
|
||||
} else {
|
||||
ElMessage.error('请正确填写信息');
|
||||
return false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const cancel = () => {
|
||||
emit('update:visible', false);
|
||||
emit('cancel');
|
||||
setTimeout(() => {
|
||||
redisForm.value.resetFields();
|
||||
// 重置对象属性为null
|
||||
state.form = {} as any;
|
||||
}, 200);
|
||||
};
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
redisForm,
|
||||
changeProject,
|
||||
changeEnv,
|
||||
btnOk,
|
||||
cancel,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="scss">
|
||||
</style>
|
||||
369
mayfly_go_web/src/views/ops/redis/RedisList.vue
Normal file
369
mayfly_go_web/src/views/ops/redis/RedisList.vue
Normal file
@@ -0,0 +1,369 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="toolbar">
|
||||
<el-button type="primary" icon="el-icon-plus" size="mini" @click="editRedis(true)" plain>添加</el-button>
|
||||
<el-button type="primary" icon="el-icon-edit" :disabled="currentId == null" size="mini" @click="editRedis(false)" plain>编辑</el-button>
|
||||
<el-button type="danger" icon="el-icon-delete" :disabled="currentId == null" size="mini" @click="deleteRedis" plain>删除</el-button>
|
||||
<div style="float: right">
|
||||
<!-- <el-input placeholder="host" size="mini" style="width: 140px" v-model="query.host" @clear="search" plain clearable></el-input>
|
||||
<el-select v-model="params.clusterId" size="mini" clearable placeholder="集群选择">
|
||||
<el-option v-for="item in clusters" :key="item.id" :value="item.id" :label="item.name"></el-option>
|
||||
</el-select> -->
|
||||
<el-select v-model="query.projectId" placeholder="请选择项目" filterable clearable size="small">
|
||||
<el-option v-for="item in projects" :key="item.id" :label="`${item.name} [${item.remark}]`" :value="item.id"> </el-option>
|
||||
</el-select>
|
||||
<el-button class="ml5" @click="search" type="success" icon="el-icon-search" size="mini"></el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-table :data="redisTable" stripe style="width: 100%" @current-change="choose">
|
||||
<el-table-column label="选择" width="50px">
|
||||
<template #default="scope">
|
||||
<el-radio v-model="currentId" :label="scope.row.id">
|
||||
<i></i>
|
||||
</el-radio>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="project" label="项目" width></el-table-column>
|
||||
<el-table-column prop="env" label="环境" width></el-table-column>
|
||||
<el-table-column prop="host" label="host:port" width></el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间">
|
||||
<template #default="scope">
|
||||
{{ $filters.dateFormat(scope.row.createTime) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="creator" label="创建人"></el-table-column>
|
||||
<el-table-column label="操作" width>
|
||||
<template #default="scope">
|
||||
<el-button type="primary" @click="info(scope.row)" icon="el-icon-tickets" size="mini" plain>info</el-button>
|
||||
<!-- <el-button type="success" @click="manage(scope.row)" :ref="scope.row" size="mini" plain>数据管理</el-button> -->
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination
|
||||
@current-change="handlePageChange"
|
||||
style="text-align: center"
|
||||
background
|
||||
layout="prev, pager, next, total, jumper"
|
||||
:total="total"
|
||||
v-model:current-page="query.pageNum"
|
||||
:page-size="query.pageSize"
|
||||
/>
|
||||
|
||||
<info v-model:visible="infoDialog.visible" :title="infoDialog.title" :info="infoDialog.info"></info>
|
||||
|
||||
<redis-edit
|
||||
@val-change="valChange"
|
||||
:projects="projects"
|
||||
:title="redisEditDialog.title"
|
||||
v-model:visible="redisEditDialog.visible"
|
||||
v-model:redis="redisEditDialog.data"
|
||||
></redis-edit>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Info from './Info.vue';
|
||||
import { redisApi } from './api';
|
||||
import { toRefs, reactive, defineComponent, onMounted } from 'vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { projectApi } from '../project/api.ts';
|
||||
import RedisEdit from './RedisEdit.vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'RedisList',
|
||||
components: {
|
||||
Info,
|
||||
RedisEdit,
|
||||
},
|
||||
setup() {
|
||||
const state = reactive({
|
||||
projects: [],
|
||||
redisTable: [],
|
||||
total: 0,
|
||||
currentId: null,
|
||||
currentData: null,
|
||||
query: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
prjectId: null,
|
||||
clusterId: null,
|
||||
},
|
||||
redisInfo: {
|
||||
url: '',
|
||||
},
|
||||
clusters: [
|
||||
{
|
||||
id: 0,
|
||||
name: '单机',
|
||||
},
|
||||
],
|
||||
infoDialog: {
|
||||
title: '',
|
||||
visible: false,
|
||||
info: {
|
||||
Server: {},
|
||||
Keyspace: {},
|
||||
Clients: {},
|
||||
CPU: {},
|
||||
Memory: {},
|
||||
},
|
||||
},
|
||||
redisEditDialog: {
|
||||
visible: false,
|
||||
data: null,
|
||||
title: '新增redis',
|
||||
},
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
search();
|
||||
state.projects = (await projectApi.projects.request({ pageNum: 1, pageSize: 100 })).list;
|
||||
});
|
||||
|
||||
const handlePageChange = (curPage: number) => {
|
||||
state.query.pageNum = curPage;
|
||||
search();
|
||||
};
|
||||
|
||||
const choose = (item: any) => {
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
state.currentId = item.id;
|
||||
state.currentData = item;
|
||||
};
|
||||
|
||||
// connect() {
|
||||
// Req.post('/open/redis/connect', this.form, res => {
|
||||
// this.redisInfo = res
|
||||
// })
|
||||
// }
|
||||
|
||||
const deleteRedis = async () => {
|
||||
try {
|
||||
await ElMessageBox.confirm(`确定删除该redis?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
});
|
||||
await redisApi.delRedis.request({ id: state.currentId });
|
||||
ElMessage.success('删除成功');
|
||||
state.currentData = null;
|
||||
state.currentId = null;
|
||||
search();
|
||||
} catch (err) {}
|
||||
};
|
||||
|
||||
const info = (redis: any) => {
|
||||
redisApi.redisInfo.request({ id: redis.id }).then((res: any) => {
|
||||
state.infoDialog.info = res;
|
||||
state.infoDialog.title = `'${redis.host}' info`;
|
||||
state.infoDialog.visible = true;
|
||||
});
|
||||
};
|
||||
|
||||
const search = async () => {
|
||||
const res = await redisApi.redisList.request(state.query);
|
||||
state.redisTable = res.list;
|
||||
state.total = res.total;
|
||||
};
|
||||
|
||||
const editRedis = (isAdd = false) => {
|
||||
if (isAdd) {
|
||||
state.redisEditDialog.data = null;
|
||||
state.redisEditDialog.title = '新增redis';
|
||||
} else {
|
||||
state.redisEditDialog.data = state.currentData;
|
||||
state.redisEditDialog.title = '修改redis';
|
||||
}
|
||||
state.redisEditDialog.visible = true;
|
||||
};
|
||||
|
||||
const valChange = () => {
|
||||
search();
|
||||
};
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
search,
|
||||
handlePageChange,
|
||||
choose,
|
||||
info,
|
||||
deleteRedis,
|
||||
editRedis,
|
||||
valChange,
|
||||
};
|
||||
},
|
||||
});
|
||||
// @Component({
|
||||
// name: 'RedisList',
|
||||
// components: {
|
||||
// Info,
|
||||
// DynamicFormDialog
|
||||
// }
|
||||
// })
|
||||
// export default class RedisList extends Vue {
|
||||
// validatePort = (rule: any, value: any, callback: any) => {
|
||||
// if (value > 65535 || value < 1) {
|
||||
// callback(new Error('端口号错误'))
|
||||
// }
|
||||
// callback()
|
||||
// }
|
||||
|
||||
// redisTable = []
|
||||
// permission = redisPermission
|
||||
// keyPermission = redisKeyPermission
|
||||
// currentId = null
|
||||
// currentData: any = null
|
||||
// params = {
|
||||
// host: null,
|
||||
// clusterId: null
|
||||
// }
|
||||
// redisInfo = {
|
||||
// url: ''
|
||||
// }
|
||||
// clusters = [
|
||||
// {
|
||||
// id: 0,
|
||||
// name: '单机'
|
||||
// }
|
||||
// ]
|
||||
// infoDialog = {
|
||||
// title: '',
|
||||
// visible: false,
|
||||
// info: {
|
||||
// Server: {},
|
||||
// Keyspace: {},
|
||||
// Clients: {},
|
||||
// CPU: {},
|
||||
// Memory: {}
|
||||
// }
|
||||
// }
|
||||
// formDialog = {
|
||||
// visible: false,
|
||||
// title: '',
|
||||
// formInfo: {
|
||||
// createApi: redisApi.save,
|
||||
// updateApi: redisApi.update,
|
||||
// formRows: [
|
||||
// [
|
||||
// {
|
||||
// type: 'input',
|
||||
// label: '主机:',
|
||||
// name: 'host',
|
||||
// placeholder: '请输入节点ip',
|
||||
// rules: [
|
||||
// {
|
||||
// required: true,
|
||||
// message: '请输入节点ip',
|
||||
// trigger: ['blur', 'change']
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
// ],
|
||||
// [
|
||||
// {
|
||||
// type: 'input',
|
||||
// label: '端口号:',
|
||||
// name: 'port',
|
||||
// placeholder: '请输入节点端口号',
|
||||
// inputType: 'number',
|
||||
// rules: [
|
||||
// {
|
||||
// required: true,
|
||||
// message: '请输入节点端口号',
|
||||
// trigger: ['blur', 'change']
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
// ],
|
||||
// [
|
||||
// {
|
||||
// type: 'input',
|
||||
// label: '密码:',
|
||||
// name: 'pwd',
|
||||
// placeholder: '请输入节点密码',
|
||||
// inputType: 'password'
|
||||
// }
|
||||
// ],
|
||||
// [
|
||||
// {
|
||||
// type: 'input',
|
||||
// label: '描述:',
|
||||
// name: 'description',
|
||||
// placeholder: '请输入节点描述',
|
||||
// inputType: 'textarea'
|
||||
// }
|
||||
// ]
|
||||
// ]
|
||||
// },
|
||||
// formData: { port: 6379 }
|
||||
// }
|
||||
|
||||
// mounted() {
|
||||
// this.search()
|
||||
// }
|
||||
|
||||
// choose(item: any) {
|
||||
// if (!item) {
|
||||
// return
|
||||
// }
|
||||
// this.currentId = item.id
|
||||
// this.currentData = item
|
||||
// }
|
||||
|
||||
// // connect() {
|
||||
// // Req.post('/open/redis/connect', this.form, res => {
|
||||
// // this.redisInfo = res
|
||||
// // })
|
||||
// // }
|
||||
|
||||
// async deleteNode() {
|
||||
// await redisApi.del.request({ id: this.currentId })
|
||||
// this.$message.success('删除成功')
|
||||
// this.search()
|
||||
// }
|
||||
|
||||
// manage(row: any) {
|
||||
// this.$router.push(`/redis_operation/${row.clusterId}/${row.id}`)
|
||||
// }
|
||||
|
||||
// info(redis: any) {
|
||||
// redisApi.info.request({ id: redis.id }).then(res => {
|
||||
// this.infoDialog.info = res
|
||||
// this.infoDialog.title = `'${redis.host}' info`
|
||||
// this.infoDialog.visible = true
|
||||
// })
|
||||
// }
|
||||
|
||||
// search() {
|
||||
// redisApi.list.request(this.params).then(res => {
|
||||
// this.redisTable = res
|
||||
// })
|
||||
// }
|
||||
|
||||
// openFormDialog(redis: any) {
|
||||
// let dialogTitle
|
||||
// if (redis) {
|
||||
// this.formDialog.formData = this.currentData
|
||||
// dialogTitle = '编辑redis节点'
|
||||
// } else {
|
||||
// this.formDialog.formData = { port: 6379 }
|
||||
// dialogTitle = '添加redis节点'
|
||||
// }
|
||||
|
||||
// this.formDialog.title = dialogTitle
|
||||
// this.formDialog.visible = true
|
||||
// }
|
||||
|
||||
// submitSuccess() {
|
||||
// this.currentId = null
|
||||
// this.currentData = null
|
||||
// this.search()
|
||||
// }
|
||||
|
||||
// }
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
76
mayfly_go_web/src/views/ops/redis/ValueDialog.vue
Normal file
76
mayfly_go_web/src/views/ops/redis/ValueDialog.vue
Normal file
@@ -0,0 +1,76 @@
|
||||
<template>
|
||||
<el-dialog :title="keyValue.key" v-model="visible" :before-close="cancel" :show-close="false" width="750px">
|
||||
<el-form>
|
||||
<el-form-item>
|
||||
<el-input v-model="keyValue.value" type="textarea" :autosize="{ minRows: 10, maxRows: 20 }" autocomplete="off"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="saveValue" type="primary" size="mini">确 定</el-button>
|
||||
<el-button @click="cancel()" size="mini">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive, watch, toRefs } from 'vue';
|
||||
import { redisApi } from './api';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { isTrue } from '@/common/assert';
|
||||
export default defineComponent({
|
||||
name: 'ValueDialog',
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
},
|
||||
keyValue: {
|
||||
type: [String, Object],
|
||||
},
|
||||
},
|
||||
setup(props: any, { emit }) {
|
||||
const state = reactive({
|
||||
visible: false,
|
||||
keyValue: {} as any,
|
||||
});
|
||||
const cancel = () => {
|
||||
emit('update:visible', false);
|
||||
emit('cancel');
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(val) => {
|
||||
state.visible = val;
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.keyValue,
|
||||
(val) => {
|
||||
state.keyValue = val;
|
||||
if (state.keyValue.type != 'string') {
|
||||
state.keyValue.value = JSON.stringify(val.value, undefined, 2)
|
||||
}
|
||||
// state.keyValue.value = JSON.stringify(val.value, undefined, 2)
|
||||
}
|
||||
);
|
||||
|
||||
const saveValue = async () => {
|
||||
isTrue(state.keyValue.type == 'string', "暂不支持除string外其他类型修改")
|
||||
await redisApi.saveStringValue.request(state.keyValue);
|
||||
ElMessage.success('保存成功');
|
||||
cancel();
|
||||
};
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
saveValue,
|
||||
cancel,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
17
mayfly_go_web/src/views/ops/redis/api.ts
Normal file
17
mayfly_go_web/src/views/ops/redis/api.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import Api from '@/common/Api';
|
||||
|
||||
export const redisApi = {
|
||||
redisList : Api.create("/redis", 'get'),
|
||||
redisInfo: Api.create("/redis/{id}/info", 'get'),
|
||||
saveRedis: Api.create("/redis", 'post'),
|
||||
delRedis: Api.create("/redis/{id}", 'delete'),
|
||||
// 获取权限列表
|
||||
scan: Api.create("/redis/{id}/scan/{cursor}/{count}", 'get'),
|
||||
getStringValue: Api.create("/redis/{id}/string-value", 'get'),
|
||||
saveStringValue: Api.create("/redis/{id}/string-value", 'post'),
|
||||
getHashValue: Api.create("/redis/{id}/hash-value", 'get'),
|
||||
getSetValue: Api.create("/redis/{id}/set-value", 'get'),
|
||||
saveHashValue: Api.create("/redis/{id}/hash-value", 'post'),
|
||||
del: Api.create("/redis/{id}/scan/{cursor}/{count}", 'delete'),
|
||||
delKey: Api.create("/redis/{id}/key", 'delete'),
|
||||
}
|
||||
1
mayfly_go_web/src/views/ops/redis/index.ts
Normal file
1
mayfly_go_web/src/views/ops/redis/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './RedisList.vue';
|
||||
@@ -3,14 +3,14 @@
|
||||
<el-dialog :title="title" v-model="visible" :show-close="false" width="35%">
|
||||
<el-form :model="form" ref="accountForm" :rules="rules" label-width="85px" size="small">
|
||||
<el-form-item prop="username" label="用户名:" required>
|
||||
<el-input :disabled="edit" v-model.trim="form.username" placeholder="请输入用户名" auto-complete="off"></el-input>
|
||||
<el-input :disabled="edit" v-model.trim="form.username" placeholder="请输入账号用户名" auto-complete="off"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="password" label="密码:" required>
|
||||
<!-- <el-form-item prop="password" label="密码:" required>
|
||||
<el-input type="password" v-model.trim="form.password" placeholder="请输入密码" autocomplete="new-password"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="!edit" label="确认密码:" required>
|
||||
<el-input type="password" v-model.trim="form.repassword" placeholder="请输入确认密码" autocomplete="new-password"></el-input>
|
||||
</el-form-item>
|
||||
</el-form-item> -->
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
@@ -61,13 +61,13 @@ export default defineComponent({
|
||||
trigger: ['change', 'blur'],
|
||||
},
|
||||
],
|
||||
password: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入密码',
|
||||
trigger: ['change', 'blur'],
|
||||
},
|
||||
],
|
||||
// password: [
|
||||
// {
|
||||
// required: true,
|
||||
// message: '请输入密码',
|
||||
// trigger: ['change', 'blur'],
|
||||
// },
|
||||
// ],
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
>
|
||||
<div style="float: right">
|
||||
<el-input
|
||||
class="mr2"
|
||||
placeholder="请输入账号名"
|
||||
size="small"
|
||||
style="width: 140px"
|
||||
@@ -45,8 +46,13 @@
|
||||
<el-tag v-if="scope.row.status == -1" type="danger" size="mini">禁用</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column min-width="160" prop="lastLoginTime" label="最后登录时间">
|
||||
<template #default="scope">
|
||||
{{ $filters.dateFormat(scope.row.lastLoginTime) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<!-- <el-table-column min-width="115" prop="creator" label="创建账号"></el-table-column> -->
|
||||
<el-table-column min-width="115" prop="creator" label="创建账号"></el-table-column>
|
||||
<el-table-column min-width="160" prop="createTime" label="创建时间">
|
||||
<template #default="scope">
|
||||
{{ $filters.dateFormat(scope.row.createTime) }}
|
||||
@@ -58,8 +64,8 @@
|
||||
{{ $filters.dateFormat(scope.row.updateTime) }}
|
||||
</template>
|
||||
</el-table-column> -->
|
||||
<el-table-column min-width="160" prop="lastLoginTime" label="最后登录时间"></el-table-column>
|
||||
<el-table-column min-width="120" prop="remark" label="备注" show-overflow-tooltip></el-table-column>
|
||||
|
||||
<!-- <el-table-column min-width="120" prop="remark" label="备注" show-overflow-tooltip></el-table-column> -->
|
||||
<el-table-column label="查看更多" min-width="150">
|
||||
<template #default="scope">
|
||||
<el-link @click.prevent="showRoles(scope.row)" type="success">角色</el-link>
|
||||
@@ -70,14 +76,21 @@
|
||||
|
||||
<el-table-column label="操作" min-width="200px">
|
||||
<template #default="scope">
|
||||
<el-button v-auth="'account:changeStatus'" v-if="scope.row.status == 1" type="danger" icom="el-icon-tickets" size="mini" plain
|
||||
<el-button
|
||||
v-auth="'account:changeStatus'"
|
||||
@click="changeStatus(scope.row)"
|
||||
v-if="scope.row.status == 1"
|
||||
type="danger"
|
||||
icom="el-icon-tickets"
|
||||
size="mini"
|
||||
plain
|
||||
>禁用</el-button
|
||||
>
|
||||
<el-button
|
||||
v-auth="'account:changeStatus'"
|
||||
v-if="scope.row.status == -1"
|
||||
type="success"
|
||||
@click="serviceManager(scope.row)"
|
||||
@click="changeStatus(scope.row)"
|
||||
size="mini"
|
||||
plain
|
||||
>启用</el-button
|
||||
@@ -136,7 +149,7 @@ import RoleEdit from './RoleEdit.vue';
|
||||
import AccountEdit from './AccountEdit.vue';
|
||||
import enums from '../enums';
|
||||
import { accountApi } from '../api';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
export default defineComponent({
|
||||
name: 'AccountList',
|
||||
components: {
|
||||
@@ -223,13 +236,13 @@ export default defineComponent({
|
||||
|
||||
const changeStatus = async (row: any) => {
|
||||
let id = row.id;
|
||||
let status = row.status ? 1 : -1;
|
||||
// await accountApi.changeStatus.request({
|
||||
// id,
|
||||
// status,
|
||||
// });
|
||||
// ElMessage.success('操作成功');
|
||||
// search();
|
||||
let status = row.status == -1 ? 1 : -1;
|
||||
await accountApi.changeStatus.request({
|
||||
id,
|
||||
status,
|
||||
});
|
||||
ElMessage.success('操作成功');
|
||||
search();
|
||||
};
|
||||
|
||||
const handlePageChange = (curPage: number) => {
|
||||
@@ -267,12 +280,17 @@ export default defineComponent({
|
||||
|
||||
const deleteAccount = async () => {
|
||||
try {
|
||||
await ElMessageBox.confirm(`确定删除该账号?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
});
|
||||
await accountApi.del.request({ id: state.chooseId });
|
||||
ElMessage.success('删除成功');
|
||||
state.chooseData = null;
|
||||
state.chooseId = null;
|
||||
search();
|
||||
} catch (error) {
|
||||
ElMessage.error('刪除失败');
|
||||
}
|
||||
} catch (err) {}
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -13,8 +13,9 @@
|
||||
</div>
|
||||
</div>
|
||||
<el-table :data="allRole" border ref="roleTable" @select="select" style="width: 100%">
|
||||
<el-table-column type="selection" width="40"></el-table-column>
|
||||
<el-table-column :selectable="selectable" type="selection" width="40"></el-table-column>
|
||||
<el-table-column prop="name" label="角色名称"></el-table-column>
|
||||
<el-table-column prop="code" label="角色code"></el-table-column>
|
||||
<el-table-column prop="remark" label="角色描述">
|
||||
<template #default="scope">
|
||||
{{ scope.row.remark ? scope.row.remark : '暂无描述' }}
|
||||
@@ -92,6 +93,11 @@ export default defineComponent({
|
||||
search();
|
||||
};
|
||||
|
||||
const selectable = (row: any) => {
|
||||
// 角色code不以COMMON开头才可勾选
|
||||
return row.code.indexOf('COMMON') != 0;
|
||||
};
|
||||
|
||||
const select = (val: any, row: any) => {
|
||||
let roles = state.roles;
|
||||
// 如果账号的角色id存在则为取消该角色(删除角色id列表中的该记录id),否则为新增角色
|
||||
@@ -164,6 +170,7 @@ export default defineComponent({
|
||||
roleTable,
|
||||
search,
|
||||
handlePageChange,
|
||||
selectable,
|
||||
select,
|
||||
btnOk,
|
||||
cancel,
|
||||
|
||||
@@ -25,7 +25,7 @@ export const accountApi = {
|
||||
save: Api.create("/sys/accounts", 'post'),
|
||||
update: Api.create("/sys/accounts/{id}", 'put'),
|
||||
del: Api.create("/sys/accounts/{id}", 'delete'),
|
||||
changeStatus: Api.create("/sys/accounts/{id}/{status}", 'put'),
|
||||
changeStatus: Api.create("/sys/accounts/change-status/{id}/{status}", 'put'),
|
||||
roleIds: Api.create("/sys/accounts/{id}/roleIds", 'get'),
|
||||
roles: Api.create("/sys/accounts/{id}/roles", 'get'),
|
||||
resources: Api.create("/sys/accounts/{id}/resources", 'get'),
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="code" label="path|code">
|
||||
<el-input v-model.trim="form.code" placeholder="菜单为路由path"></el-input>
|
||||
<el-input v-model.trim="form.code" placeholder="菜单不带/自动拼接父路径"></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="序号" prop="weight" required>
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
<el-form-item label="角色名称:" required>
|
||||
<el-input v-model="form.name" auto-complete="off"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="角色code:" required>
|
||||
<el-input :disabled="form.id" v-model="form.code" placeholder="COMMON开头则为所有账号共有角色" auto-complete="off"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="角色描述:">
|
||||
<el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请输入角色描述"></el-input>
|
||||
</el-form-item>
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="name" label="角色名称"></el-table-column>
|
||||
<el-table-column prop="code" label="角色code"></el-table-column>
|
||||
<el-table-column prop="remark" label="描述" min-width="180px" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间">
|
||||
<template #default="scope">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package initialize
|
||||
|
||||
import (
|
||||
"mayfly-go/base/global"
|
||||
"mayfly-go/base/config"
|
||||
"mayfly-go/base/middleware"
|
||||
"mayfly-go/mock-server/routers"
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
|
||||
func InitRouter() *gin.Engine {
|
||||
// server配置
|
||||
serverConfig := global.Config.Server
|
||||
serverConfig := config.Conf.Server
|
||||
gin.SetMode(serverConfig.Model)
|
||||
|
||||
var router = gin.New()
|
||||
|
||||
Binary file not shown.
@@ -1,5 +1,5 @@
|
||||
app:
|
||||
name: devops
|
||||
name: mayfly-go
|
||||
version: 1.0.0
|
||||
|
||||
server:
|
||||
@@ -7,6 +7,10 @@ server:
|
||||
model: release
|
||||
port: 8888
|
||||
cors: true
|
||||
tls:
|
||||
enable: false
|
||||
key-file: ./default.key
|
||||
cert-file: ./default.pem
|
||||
# 静态资源
|
||||
static:
|
||||
- relative-path: /assets
|
||||
@@ -18,6 +22,11 @@ server:
|
||||
- relative-path: /favicon.ico
|
||||
filepath: ./static/favicon.ico
|
||||
|
||||
jwt:
|
||||
key: mykey
|
||||
# 过期时间单位分钟
|
||||
expire-time: 1440
|
||||
|
||||
redis:
|
||||
host: 127.0.0.1
|
||||
port: 6379
|
||||
@@ -27,4 +36,11 @@ mysql:
|
||||
username: root
|
||||
password: 111049
|
||||
db-name: mayfly-job
|
||||
config: charset=utf8&loc=Local&parseTime=true
|
||||
config: charset=utf8&loc=Local&parseTime=true
|
||||
|
||||
log:
|
||||
# 日志等级, trace, debug, info, warn, error, fatal
|
||||
level: info
|
||||
# file:
|
||||
# path: ./
|
||||
# name: mayfly.log
|
||||
27
server/default.key
Normal file
27
server/default.key
Normal file
@@ -0,0 +1,27 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpgIBAAKCAQEA0CsawvEZl42Vf+0BlTuZ3Dp10yW8Oty1tjimxUj3s0WPeKil
|
||||
6+TehnQELS8vGJfek+yT99nyrt+bkRmg1kxZ57FtQFEuthG4OQZoaMDUz6Ab+8P1
|
||||
PQ9VH0XimnnYabxztJiQjl8HdJt6N4WP35kGlcul7qQ+Qc7iwjhSadfAhXVycqVI
|
||||
cGQyHiPPfbmYRjueAIC4czmMUxwFKCwjepGYkwzWuGkpMD0hg/SIXpFJE2dcqYPR
|
||||
2nCah1gxZZG00lHU1X2pehNmmgeHRkB5S7mrsCdyyV/33SAYk6T6PT7dOqY54bfn
|
||||
h3C0k+T7IzvKTXKG76eG63STmxVa6luVoKMvxwIDAQABAoIBAQCI2Y2CUpYMd9us
|
||||
edbskH4ZtaT35nrUB3y+Cog4cjvE8xnarKRHa/KOWX7VZYuEk3KTtJeh/Pn51K6k
|
||||
uUBvIUqJcq7r9XLL5uJBOuEw3HQK+qrq3GxAc+/12y+Zdji7alR2iUWfEwIHup6i
|
||||
GX/38tXNbE/tjrQO9z9Dh1tGkbvS/66tPn/T/oMxsRvZB6mCjB7yuOlEIwYTomYB
|
||||
pUFemELt8T5RtfxRa8T1VoITbfuj7zvecqlThW0H8UizsFxvrOCUaga7jtsJOCHo
|
||||
bcW5WvWwazoOfQ2BGpksKkBDf1N6pj85e4kOoYcVG9UN03ZwDvAGfQPWUlHB4YzW
|
||||
PybMwIQBAoGBAPfuOQ+ukVmkiEKj6wCBe3Z0pYeNqBGec9aj62bKFh79BCE3ZopS
|
||||
7JtGs8VfBKkBAaOy+MDuvJ2fvRNRtHT4BYe1U6ZRsmVFqHScACOaO/7TR0tz0ihL
|
||||
0QLCkbSwsXExG6bYbwP4jMHkhHArT7Hy8WXvup8PffjSiEs1A1uGvYSBAoGBANbx
|
||||
lHo+39nsc1OO8TUAWZChIQUib2hFIwzQYngSzINdfXQaGFT/omOsudAtfdjvp+qO
|
||||
Tr7WpwgFEFveDFsdJfZ2Kc2x9a3ty7IYIWaAjK2ghkAKz3Tt4gClreB6qG2SBycP
|
||||
4C2ImbY6hMaFHz3ENtTEzzTMdD1ByxQVMvoem3BHAoGBAJdaTmtMXl8jGivUdXnx
|
||||
kbVWsFZ4G8nluUGm/+XYKHjybLr6XxbCWL7SApzSzL1/Z8jPURw2od53za0li8x8
|
||||
PKQEBfTamtVIGPZW5Z7WYRnHURa2tezzm7zbmqd71lcLa54HMn5yFTuojVEMn7I6
|
||||
ZTOdjYfcpUJpA9slmc8eCkQBAoGBAJEIbxRRaoBEQMkH8Y++zbB+WKZ7RssHo4/Y
|
||||
6Ch3HtIg+i6mEPcBitRQzww+NeV0SExHe7Dfa9NIf3JNkO7F60CzGJ/3zXtvsftY
|
||||
tujQIpxhbVS3NqaCgPXI1VtbyFwupW7hEnYG7xj7wW2mk578z7afmeTZdDGFPH8v
|
||||
krccgeuvAoGBAPAwiqbZlXNx+ueI1B3T8VpXnG0ozKxG+l5B71kssZWa7xcv9yRd
|
||||
c15l2PSXNtnoT/mBID7+dqQOmfYxsDHAkUdd/BrxhXtdi9FR3AfHSEQz+VKsogAD
|
||||
uLyRd7jWTYqqGa2UToF/CBV+c6QyMB+6pzFNk5DmUEm4Gd6jcHDITYeI
|
||||
-----END RSA PRIVATE KEY-----
|
||||
21
server/default.pem
Normal file
21
server/default.pem
Normal file
@@ -0,0 +1,21 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDejCCAmICCQDQU4ZRt2G46TANBgkqhkiG9w0BAQsFADB/MQswCQYDVQQGEwJ6
|
||||
aDEPMA0GA1UECAwGbWF5Zmx5MQswCQYDVQQHDAJ4bTEPMA0GA1UECgwGbWF5Zmx5
|
||||
MQ8wDQYDVQQLDAZtYXlmbHkxDzANBgNVBAMMBm1heWZseTEfMB0GCSqGSIb3DQEJ
|
||||
ARYQOTU0NTM3NDczQHFxLmNvbTAeFw0yMTA2MjQwMzI2MzBaFw0zMTA2MjIwMzI2
|
||||
MzBaMH8xCzAJBgNVBAYTAnpoMQ8wDQYDVQQIDAZtYXlmbHkxCzAJBgNVBAcMAnht
|
||||
MQ8wDQYDVQQKDAZtYXlmbHkxDzANBgNVBAsMBm1heWZseTEPMA0GA1UEAwwGbWF5
|
||||
Zmx5MR8wHQYJKoZIhvcNAQkBFhA5NTQ1Mzc0NzNAcXEuY29tMIIBIjANBgkqhkiG
|
||||
9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0CsawvEZl42Vf+0BlTuZ3Dp10yW8Oty1tjim
|
||||
xUj3s0WPeKil6+TehnQELS8vGJfek+yT99nyrt+bkRmg1kxZ57FtQFEuthG4OQZo
|
||||
aMDUz6Ab+8P1PQ9VH0XimnnYabxztJiQjl8HdJt6N4WP35kGlcul7qQ+Qc7iwjhS
|
||||
adfAhXVycqVIcGQyHiPPfbmYRjueAIC4czmMUxwFKCwjepGYkwzWuGkpMD0hg/SI
|
||||
XpFJE2dcqYPR2nCah1gxZZG00lHU1X2pehNmmgeHRkB5S7mrsCdyyV/33SAYk6T6
|
||||
PT7dOqY54bfnh3C0k+T7IzvKTXKG76eG63STmxVa6luVoKMvxwIDAQABMA0GCSqG
|
||||
SIb3DQEBCwUAA4IBAQB/e8EO2XEtkYBxebR1w6i50vaegLsxQJR3l5qm7rsHu3Cr
|
||||
smJXGsc56axKCAqJ4XvSI65BT51FghAoGn62QNyiQgc0YoS99nwCCGFtnhZ2lmSe
|
||||
pfhUHegN/Qo4I8FemEMD+o9kGeAzwrnaIVIT/cNOEQgm+RzrgHHJh5QBn2XgJalU
|
||||
NeFTWaimyefwSezSa/vPbyMoAl9HkT6kdvnms/yOth4AOle6+5pM2StWjmMi4yx4
|
||||
16y3NvLTku6nAUazaHTOOu/MCqLWL2/qYTk3r7OCop2jr9Rp+HLbg5AfKLUIVXjG
|
||||
/1fnXJIuD+2u9qgDLN5PZNgz4MlU86ugtmYPFkVt
|
||||
-----END CERTIFICATE-----
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"mayfly-go/base/ctx"
|
||||
"mayfly-go/base/ginx"
|
||||
"mayfly-go/base/model"
|
||||
"mayfly-go/base/utils"
|
||||
"mayfly-go/server/devops/apis/form"
|
||||
"mayfly-go/server/devops/apis/vo"
|
||||
"mayfly-go/server/devops/application"
|
||||
@@ -17,45 +18,73 @@ import (
|
||||
)
|
||||
|
||||
type Db struct {
|
||||
DbApp application.IDb
|
||||
DbApp application.Db
|
||||
}
|
||||
|
||||
// @router /api/dbs [get]
|
||||
func (d *Db) Dbs(rc *ctx.ReqCtx) {
|
||||
m := new(entity.Db)
|
||||
g := rc.GinCtx
|
||||
m := &entity.Db{EnvId: uint64(ginx.QueryInt(g, "envId", 0)),
|
||||
ProjectId: uint64(ginx.QueryInt(g, "projectId", 0)),
|
||||
Database: g.Query("database"),
|
||||
}
|
||||
ginx.BindQuery(g, m)
|
||||
rc.ResData = d.DbApp.GetPageList(m, ginx.GetPageParam(rc.GinCtx), new([]vo.SelectDataDbVO))
|
||||
}
|
||||
|
||||
// @router /api/db/:dbId/select [get]
|
||||
func (d *Db) SelectData(rc *ctx.ReqCtx) {
|
||||
func (d *Db) Save(rc *ctx.ReqCtx) {
|
||||
form := &form.DbForm{}
|
||||
ginx.BindJsonAndValid(rc.GinCtx, form)
|
||||
|
||||
rc.ReqParam = form
|
||||
|
||||
db := new(entity.Db)
|
||||
utils.Copy(db, form)
|
||||
db.SetBaseInfo(rc.LoginAccount)
|
||||
d.DbApp.Save(db)
|
||||
}
|
||||
|
||||
func (d *Db) DeleteDb(rc *ctx.ReqCtx) {
|
||||
d.DbApp.Delete(uint64(ginx.PathParamInt(rc.GinCtx, "id")))
|
||||
}
|
||||
|
||||
// @router /api/db/:dbId/exec-sql [get]
|
||||
func (d *Db) ExecSql(rc *ctx.ReqCtx) {
|
||||
g := rc.GinCtx
|
||||
// 去除前后空格及换行符
|
||||
selectSql := strings.TrimFunc(g.Query("selectSql"), func(r rune) bool {
|
||||
sql := strings.TrimFunc(g.Query("sql"), func(r rune) bool {
|
||||
s := string(r)
|
||||
return s == " " || s == "\n"
|
||||
})
|
||||
rc.ReqParam = selectSql
|
||||
rc.ReqParam = sql
|
||||
|
||||
biz.NotEmpty(selectSql, "selectSql不能为空")
|
||||
res, err := d.DbApp.GetDbInstance(GetDbId(g)).SelectData(selectSql)
|
||||
if err != nil {
|
||||
panic(biz.NewBizErr(fmt.Sprintf("查询失败: %s", err.Error())))
|
||||
}
|
||||
rc.ResData = res
|
||||
}
|
||||
|
||||
// @router /api/db/:dbId/exec-sql [post]
|
||||
func (d *Db) ExecSql(g *gin.Context) {
|
||||
rc := ctx.NewReqCtxWithGin(g).WithLog(ctx.NewLogInfo("sql执行"))
|
||||
rc.Handle(func(rc *ctx.ReqCtx) {
|
||||
selectSql := g.Query("sql")
|
||||
biz.NotEmpty(selectSql, "sql不能为空")
|
||||
num, err := d.DbApp.GetDbInstance(GetDbId(rc.GinCtx)).Exec(selectSql)
|
||||
biz.NotEmpty(sql, "sql不能为空")
|
||||
if strings.HasPrefix(sql, "SELECT") || strings.HasPrefix(sql, "select") {
|
||||
colNames, res, err := d.DbApp.GetDbInstance(GetDbId(g)).SelectData(sql)
|
||||
if err != nil {
|
||||
panic(biz.NewBizErr(fmt.Sprintf("查询失败: %s", err.Error())))
|
||||
}
|
||||
colAndRes := make(map[string]interface{})
|
||||
colAndRes["colNames"] = colNames
|
||||
colAndRes["res"] = res
|
||||
rc.ResData = colAndRes
|
||||
} else {
|
||||
rowsAffected, err := d.DbApp.GetDbInstance(GetDbId(g)).Exec(sql)
|
||||
if err != nil {
|
||||
panic(biz.NewBizErr(fmt.Sprintf("执行失败: %s", err.Error())))
|
||||
}
|
||||
rc.ResData = num
|
||||
})
|
||||
res := make([]map[string]string, 0)
|
||||
resData := make(map[string]string)
|
||||
resData["影响条数"] = fmt.Sprintf("%d", rowsAffected)
|
||||
res = append(res, resData)
|
||||
|
||||
colAndRes := make(map[string]interface{})
|
||||
colAndRes["colNames"] = []string{"影响条数"}
|
||||
colAndRes["res"] = res
|
||||
|
||||
rc.ResData = colAndRes
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// @router /api/db/:dbId/t-metadata [get]
|
||||
@@ -74,21 +103,32 @@ func (d *Db) ColumnMA(rc *ctx.ReqCtx) {
|
||||
// @router /api/db/:dbId/hint-tables [get]
|
||||
func (d *Db) HintTables(rc *ctx.ReqCtx) {
|
||||
dbi := d.DbApp.GetDbInstance(GetDbId(rc.GinCtx))
|
||||
// 获取所有表
|
||||
tables := dbi.GetTableMetedatas()
|
||||
res := make(map[string][]string)
|
||||
|
||||
tableNames := make([]string, 0)
|
||||
for _, v := range tables {
|
||||
tableName := v["tableName"]
|
||||
columnMds := dbi.GetColumnMetadatas(tableName)
|
||||
columnNames := make([]string, len(columnMds))
|
||||
for i, v := range columnMds {
|
||||
comment := v["columnComment"]
|
||||
if comment != "" {
|
||||
columnNames[i] = v["columnName"] + " [" + comment + "]"
|
||||
} else {
|
||||
columnNames[i] = v["columnName"]
|
||||
}
|
||||
tableNames = append(tableNames, v["tableName"])
|
||||
}
|
||||
// 获取所有表下的所有列信息
|
||||
columnMds := dbi.GetColumnMetadatas(tableNames...)
|
||||
// key = 表名,value = 列名数组
|
||||
res := make(map[string][]string)
|
||||
|
||||
for _, v := range columnMds {
|
||||
tName := v["tableName"]
|
||||
if res[tName] == nil {
|
||||
res[tName] = make([]string, 0)
|
||||
}
|
||||
res[tableName] = columnNames
|
||||
|
||||
columnName := fmt.Sprintf("%s [%s]", v["columnName"], v["columnType"])
|
||||
comment := v["columnComment"]
|
||||
// 如果字段备注不为空,则加上备注信息
|
||||
if comment != "" {
|
||||
columnName = fmt.Sprintf("%s[%s]", columnName, comment)
|
||||
}
|
||||
|
||||
res[tName] = append(res[tName], columnName)
|
||||
}
|
||||
rc.ResData = res
|
||||
}
|
||||
|
||||
16
server/devops/apis/form/db.go
Normal file
16
server/devops/apis/form/db.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package form
|
||||
|
||||
type DbForm struct {
|
||||
Id uint64
|
||||
Name string `binding:"required" json:"name"`
|
||||
Type string `binding:"required" json:"type"` // 类型,mysql oracle等
|
||||
Host string `binding:"required" json:"host"`
|
||||
Port int `binding:"required" json:"port"`
|
||||
Username string `binding:"required" json:"username"`
|
||||
Password string `binding:"required" json:"password"`
|
||||
Database string `binding:"required" json:"database"`
|
||||
ProjectId uint64 `binding:"required" json:"projectId"`
|
||||
Project string `json:"project"`
|
||||
Env string `json:"env"`
|
||||
EnvId uint64 `binding:"required" json:"envId"`
|
||||
}
|
||||
@@ -2,45 +2,46 @@ package form
|
||||
|
||||
type MachineForm struct {
|
||||
Id uint64 `json:"id"`
|
||||
Name string `json:"name" valid:"Required"`
|
||||
Name string `json:"name" binding:"required"`
|
||||
// IP地址
|
||||
Ip string `json:"ip" valid:"Required"`
|
||||
Ip string `json:"ip" binding:"required"`
|
||||
// 用户名
|
||||
Username string `json:"username" valid:"Required"`
|
||||
Password string `json:"password" valid:"Required"`
|
||||
Username string `json:"username" binding:"required"`
|
||||
Password string `json:"password" binding:"required"`
|
||||
// 端口号
|
||||
Port int `json:"port" valid:"Required"`
|
||||
Port int `json:"port" binding:"required"`
|
||||
}
|
||||
|
||||
type MachineRunForm struct {
|
||||
MachineId int64 `valid:"Required"`
|
||||
Cmd string `valid:"Required"`
|
||||
MachineId int64 `binding:"required"`
|
||||
Cmd string `binding:"required"`
|
||||
}
|
||||
|
||||
type MachineFileForm struct {
|
||||
Id uint64
|
||||
Name string `valid:"Required"`
|
||||
MachineId uint64 `valid:"Required"`
|
||||
Type int `valid:"Required"`
|
||||
Path string `valid:"Required"`
|
||||
Name string `binding:"required"`
|
||||
MachineId uint64 `binding:"required"`
|
||||
Type int `binding:"required"`
|
||||
Path string `binding:"required"`
|
||||
}
|
||||
|
||||
type MachineScriptForm struct {
|
||||
Id uint64
|
||||
Name string `valid:"Required"`
|
||||
MachineId uint64 `valid:"Required"`
|
||||
Type int `valid:"Required"`
|
||||
Description string `valid:"Required"`
|
||||
Script string `valid:"Required"`
|
||||
Name string `binding:"required"`
|
||||
MachineId uint64 `binding:"required"`
|
||||
Type int `binding:"required"`
|
||||
Description string `binding:"required"`
|
||||
Params string
|
||||
Script string `binding:"required"`
|
||||
}
|
||||
|
||||
type DbSqlSaveForm struct {
|
||||
Sql string `valid:"Required"`
|
||||
Type int `valid:"Required"`
|
||||
Sql string `binding:"required"`
|
||||
Type int `binding:"required"`
|
||||
}
|
||||
|
||||
type MachineFileUpdateForm struct {
|
||||
Content string `valid:"Required"`
|
||||
Id uint64 `valid:"Required"`
|
||||
Path string `valid:"Required"`
|
||||
Content string `binding:"required"`
|
||||
Id uint64 `binding:"required"`
|
||||
Path string `binding:"required"`
|
||||
}
|
||||
|
||||
18
server/devops/apis/form/redis.go
Normal file
18
server/devops/apis/form/redis.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package form
|
||||
|
||||
type Redis struct {
|
||||
Id uint64
|
||||
Host string `binding:"required" json:"host"`
|
||||
Password string `json:"password"`
|
||||
Db int `json:"db"`
|
||||
ProjectId uint64 `binding:"required" json:"projectId"`
|
||||
Project string `json:"project"`
|
||||
Env string `json:"env"`
|
||||
EnvId uint64 `binding:"required" json:"envId"`
|
||||
}
|
||||
|
||||
type KeyValue struct {
|
||||
Key string `binding:"required" json:"key"`
|
||||
Value interface{} `binding:"required" json:"value"`
|
||||
Timed uint64
|
||||
}
|
||||
@@ -26,7 +26,7 @@ var WsUpgrader = websocket.Upgrader{
|
||||
}
|
||||
|
||||
type Machine struct {
|
||||
MachineApp application.IMachine
|
||||
MachineApp application.Machine
|
||||
}
|
||||
|
||||
func (m *Machine) Machines(rc *ctx.ReqCtx) {
|
||||
@@ -45,6 +45,12 @@ func (m *Machine) SaveMachine(rc *ctx.ReqCtx) {
|
||||
m.MachineApp.Save(entity)
|
||||
}
|
||||
|
||||
func (m *Machine) DeleteMachine(rc *ctx.ReqCtx) {
|
||||
id := uint64(ginx.PathParamInt(rc.GinCtx, "id"))
|
||||
rc.ReqParam = id
|
||||
m.MachineApp.Delete(id)
|
||||
}
|
||||
|
||||
// top命令信息
|
||||
func (m *Machine) Top(rc *ctx.ReqCtx) {
|
||||
rc.ResData = m.MachineApp.GetCli(GetMachineId(rc.GinCtx)).GetTop()
|
||||
|
||||
@@ -19,8 +19,8 @@ import (
|
||||
)
|
||||
|
||||
type MachineFile struct {
|
||||
MachineFileApp application.IMachineFile
|
||||
MachineApp application.IMachine
|
||||
MachineFileApp application.MachineFile
|
||||
MachineApp application.Machine
|
||||
}
|
||||
|
||||
const (
|
||||
|
||||
@@ -16,8 +16,8 @@ import (
|
||||
)
|
||||
|
||||
type MachineScript struct {
|
||||
MachineScriptApp application.IMachineScript
|
||||
MachineApp application.IMachine
|
||||
MachineScriptApp application.MachineScript
|
||||
MachineApp application.Machine
|
||||
}
|
||||
|
||||
func (m *MachineScript) MachineScripts(rc *ctx.ReqCtx) {
|
||||
@@ -57,8 +57,12 @@ func (m *MachineScript) RunMachineScript(rc *ctx.ReqCtx) {
|
||||
biz.NotNil(ms, "该脚本不存在")
|
||||
biz.IsTrue(ms.MachineId == application.Common_Script_Machine_Id || ms.MachineId == machineId, "该脚本不属于该机器")
|
||||
|
||||
vars := g.QueryMap("params")
|
||||
res, err := m.MachineApp.GetCli(machineId).Run(utils.TemplateParse(ms.Script, vars))
|
||||
script := ms.Script
|
||||
// 如果有脚本参数,则用脚本参数替换脚本中的模板占位符参数
|
||||
if params := g.Query("params"); params != "" {
|
||||
script = utils.TemplateParse(ms.Script, utils.Json2Map(params))
|
||||
}
|
||||
res, err := m.MachineApp.GetCli(machineId).Run(script)
|
||||
// 记录请求参数
|
||||
rc.ReqParam = fmt.Sprintf("[machineId: %d, scriptId: %d, name: %s]", machineId, scriptId, ms.Name)
|
||||
if err != nil {
|
||||
|
||||
100
server/devops/apis/project.go
Normal file
100
server/devops/apis/project.go
Normal file
@@ -0,0 +1,100 @@
|
||||
package apis
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"mayfly-go/base/biz"
|
||||
"mayfly-go/base/ctx"
|
||||
"mayfly-go/base/ginx"
|
||||
"mayfly-go/server/devops/apis/vo"
|
||||
"mayfly-go/server/devops/application"
|
||||
"mayfly-go/server/devops/domain/entity"
|
||||
sys_applicaiton "mayfly-go/server/sys/application"
|
||||
sys_entity "mayfly-go/server/sys/domain/entity"
|
||||
)
|
||||
|
||||
type Project struct {
|
||||
ProjectApp application.Project
|
||||
AccountApp sys_applicaiton.Account
|
||||
}
|
||||
|
||||
// 获取当前登录用户可以访问的项目列表
|
||||
func (p *Project) GetProjectsByLoginAccount(rc *ctx.ReqCtx) {
|
||||
// 获取登录用户拥有的项目ids
|
||||
projectMembers := &[]entity.ProjectMember{}
|
||||
p.ProjectApp.ListMember(&entity.ProjectMember{AccountId: rc.LoginAccount.Id}, projectMembers)
|
||||
var pids []uint64
|
||||
for _, pm := range *projectMembers {
|
||||
pids = append(pids, pm.ProjectId)
|
||||
}
|
||||
|
||||
// 获取项目信息
|
||||
projects := &vo.AccountProjects{}
|
||||
p.ProjectApp.ListProjectByIds(pids, projects)
|
||||
rc.ResData = projects
|
||||
}
|
||||
|
||||
func (p *Project) GetProjects(rc *ctx.ReqCtx) {
|
||||
condition := &entity.Project{}
|
||||
ginx.BindQuery(rc.GinCtx, condition)
|
||||
// condition.Name = rc.GinCtx.Query("name")
|
||||
rc.ResData = p.ProjectApp.GetPageList(condition, ginx.GetPageParam(rc.GinCtx), new([]entity.Project))
|
||||
}
|
||||
|
||||
func (p *Project) SaveProject(rc *ctx.ReqCtx) {
|
||||
project := &entity.Project{}
|
||||
ginx.BindJsonAndValid(rc.GinCtx, project)
|
||||
rc.ReqParam = project
|
||||
|
||||
project.SetBaseInfo(rc.LoginAccount)
|
||||
p.ProjectApp.SaveProject(project)
|
||||
}
|
||||
|
||||
// 获取项目下的环境信息
|
||||
func (p *Project) GetProjectEnvs(rc *ctx.ReqCtx) {
|
||||
projectEnvs := &[]entity.ProjectEnv{}
|
||||
p.ProjectApp.ListEnvByProjectId(uint64(ginx.PathParamInt(rc.GinCtx, "projectId")), projectEnvs)
|
||||
rc.ResData = projectEnvs
|
||||
}
|
||||
|
||||
//保存项目下的环境信息
|
||||
func (p *Project) SaveProjectEnvs(rc *ctx.ReqCtx) {
|
||||
projectEnv := &entity.ProjectEnv{}
|
||||
ginx.BindJsonAndValid(rc.GinCtx, projectEnv)
|
||||
rc.ReqParam = projectEnv
|
||||
|
||||
projectEnv.SetBaseInfo(rc.LoginAccount)
|
||||
p.ProjectApp.SaveProjectEnv(projectEnv)
|
||||
}
|
||||
|
||||
// 获取项目下的成员信息
|
||||
func (p *Project) GetProjectMembers(rc *ctx.ReqCtx) {
|
||||
projectMems := &[]entity.ProjectMember{}
|
||||
rc.ResData = p.ProjectApp.GetMemberPage(&entity.ProjectMember{ProjectId: uint64(ginx.PathParamInt(rc.GinCtx, "projectId"))},
|
||||
ginx.GetPageParam(rc.GinCtx), projectMems)
|
||||
}
|
||||
|
||||
//保存项目的成员信息
|
||||
func (p *Project) SaveProjectMember(rc *ctx.ReqCtx) {
|
||||
projectMem := &entity.ProjectMember{}
|
||||
ginx.BindJsonAndValid(rc.GinCtx, projectMem)
|
||||
rc.ReqParam = projectMem
|
||||
|
||||
// 校验账号,并赋值username
|
||||
account := &sys_entity.Account{}
|
||||
account.Id = projectMem.AccountId
|
||||
biz.ErrIsNil(p.AccountApp.GetAccount(account, "Id", "Username"), "账号不存在")
|
||||
projectMem.Username = account.Username
|
||||
|
||||
projectMem.SetBaseInfo(rc.LoginAccount)
|
||||
p.ProjectApp.SaveProjectMember(projectMem)
|
||||
}
|
||||
|
||||
//删除项目成员
|
||||
func (p *Project) DelProjectMember(rc *ctx.ReqCtx) {
|
||||
g := rc.GinCtx
|
||||
pid := ginx.PathParamInt(g, "projectId")
|
||||
aid := ginx.PathParamInt(g, "accountId")
|
||||
rc.ReqParam = fmt.Sprintf("projectId: %d, accountId: %d", pid, aid)
|
||||
|
||||
p.ProjectApp.DeleteMember(uint64(pid), uint64(aid))
|
||||
}
|
||||
203
server/devops/apis/redis.go
Normal file
203
server/devops/apis/redis.go
Normal file
@@ -0,0 +1,203 @@
|
||||
package apis
|
||||
|
||||
import (
|
||||
"mayfly-go/base/biz"
|
||||
"mayfly-go/base/ctx"
|
||||
"mayfly-go/base/ginx"
|
||||
"mayfly-go/base/utils"
|
||||
"mayfly-go/server/devops/apis/form"
|
||||
"mayfly-go/server/devops/apis/vo"
|
||||
"mayfly-go/server/devops/application"
|
||||
"mayfly-go/server/devops/domain/entity"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Redis struct {
|
||||
RedisApp application.Redis
|
||||
}
|
||||
|
||||
func (r *Redis) RedisList(rc *ctx.ReqCtx) {
|
||||
g := rc.GinCtx
|
||||
m := &entity.Redis{EnvId: uint64(ginx.QueryInt(g, "envId", 0)),
|
||||
ProjectId: uint64(ginx.QueryInt(g, "projectId", 0)),
|
||||
}
|
||||
ginx.BindQuery(g, m)
|
||||
rc.ResData = r.RedisApp.GetPageList(m, ginx.GetPageParam(rc.GinCtx), new([]vo.Redis))
|
||||
}
|
||||
|
||||
func (r *Redis) Save(rc *ctx.ReqCtx) {
|
||||
form := &form.Redis{}
|
||||
ginx.BindJsonAndValid(rc.GinCtx, form)
|
||||
|
||||
rc.ReqParam = form
|
||||
|
||||
redis := new(entity.Redis)
|
||||
utils.Copy(redis, form)
|
||||
redis.SetBaseInfo(rc.LoginAccount)
|
||||
r.RedisApp.Save(redis)
|
||||
}
|
||||
|
||||
func (r *Redis) DeleteRedis(rc *ctx.ReqCtx) {
|
||||
r.RedisApp.Delete(uint64(ginx.PathParamInt(rc.GinCtx, "id")))
|
||||
}
|
||||
|
||||
func (r *Redis) RedisInfo(rc *ctx.ReqCtx) {
|
||||
res, _ := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(rc.GinCtx, "id"))).Cli.Info().Result()
|
||||
|
||||
datas := strings.Split(res, "\r\n")
|
||||
i := 0
|
||||
length := len(datas)
|
||||
|
||||
parseMap := make(map[string]map[string]string, 0)
|
||||
for {
|
||||
if i >= length {
|
||||
break
|
||||
}
|
||||
if strings.Contains(datas[i], "#") {
|
||||
key := utils.SubString(datas[i], strings.Index(datas[i], "#")+1, utils.StrLen(datas[i]))
|
||||
i++
|
||||
key = strings.Trim(key, " ")
|
||||
|
||||
sectionMap := make(map[string]string, 0)
|
||||
for {
|
||||
if i >= length || !strings.Contains(datas[i], ":") {
|
||||
break
|
||||
}
|
||||
pair := strings.Split(datas[i], ":")
|
||||
i++
|
||||
if len(pair) != 2 {
|
||||
continue
|
||||
}
|
||||
sectionMap[pair[0]] = pair[1]
|
||||
}
|
||||
parseMap[key] = sectionMap
|
||||
} else {
|
||||
i++
|
||||
}
|
||||
}
|
||||
rc.ResData = parseMap
|
||||
}
|
||||
|
||||
// scan获取redis的key列表信息
|
||||
func (r *Redis) Scan(rc *ctx.ReqCtx) {
|
||||
g := rc.GinCtx
|
||||
|
||||
ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id")))
|
||||
keys, cursor := ri.Scan(uint64(ginx.PathParamInt(g, "cursor")), g.Query("match"), int64(ginx.PathParamInt(g, "count")))
|
||||
|
||||
var keyInfoSplit []string
|
||||
if len(keys) > 0 {
|
||||
keyInfoLua := `
|
||||
local result = {}
|
||||
-- KEYS[1]为第1个参数,lua数组下标从1开始
|
||||
local ttl = redis.call('ttl', KEYS[1]);
|
||||
local keyType = redis.call('type', KEYS[1]);
|
||||
for i = 1, #KEYS do
|
||||
local ttl = redis.call('ttl', KEYS[i]);
|
||||
local keyType = redis.call('type', KEYS[i]);
|
||||
table.insert(result, string.format("%d,%s", ttl, keyType['ok']));
|
||||
end;
|
||||
return table.concat(result, ".");`
|
||||
// 通过lua获取 ttl,type.ttl2,type2格式,以便下面切割获取ttl和type。避免多次调用ttl和type函数
|
||||
keyInfos, _ := ri.Cli.Eval(keyInfoLua, keys).Result()
|
||||
keyInfoSplit = strings.Split(keyInfos.(string), ".")
|
||||
}
|
||||
|
||||
kis := make([]*vo.KeyInfo, 0)
|
||||
for i, k := range keys {
|
||||
ttlType := strings.Split(keyInfoSplit[i], ",")
|
||||
ttl, _ := strconv.Atoi(ttlType[0])
|
||||
ki := &vo.KeyInfo{Key: k, Type: ttlType[1], Ttl: uint64(ttl)}
|
||||
kis = append(kis, ki)
|
||||
}
|
||||
|
||||
size, _ := ri.Cli.DBSize().Result()
|
||||
rc.ResData = &vo.Keys{Cursor: cursor, Keys: kis, DbSize: size}
|
||||
}
|
||||
|
||||
func (r *Redis) DeleteKey(rc *ctx.ReqCtx) {
|
||||
g := rc.GinCtx
|
||||
key := g.Query("key")
|
||||
biz.NotEmpty(key, "key不能为空")
|
||||
|
||||
ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id")))
|
||||
rc.ReqParam = key
|
||||
ri.Cli.Del(key)
|
||||
}
|
||||
|
||||
func (r *Redis) checkKey(rc *ctx.ReqCtx) (*application.RedisInstance, string) {
|
||||
g := rc.GinCtx
|
||||
key := g.Query("key")
|
||||
biz.NotEmpty(key, "key不能为空")
|
||||
|
||||
return r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id"))), key
|
||||
}
|
||||
|
||||
func (r *Redis) GetStringValue(rc *ctx.ReqCtx) {
|
||||
ri, key := r.checkKey(rc)
|
||||
str, err := ri.Cli.Get(key).Result()
|
||||
biz.ErrIsNilAppendErr(err, "获取字符串值失败: %s")
|
||||
rc.ResData = str
|
||||
}
|
||||
|
||||
func (r *Redis) GetHashValue(rc *ctx.ReqCtx) {
|
||||
ri, key := r.checkKey(rc)
|
||||
res, err := ri.Cli.HGetAll(key).Result()
|
||||
biz.ErrIsNilAppendErr(err, "获取hash值失败: %s")
|
||||
rc.ResData = res
|
||||
}
|
||||
|
||||
func (r *Redis) GetSetValue(rc *ctx.ReqCtx) {
|
||||
ri, key := r.checkKey(rc)
|
||||
res, err := ri.Cli.SMembers(key).Result()
|
||||
biz.ErrIsNilAppendErr(err, "获取set值失败: %s")
|
||||
rc.ResData = res
|
||||
}
|
||||
|
||||
func (r *Redis) Test(rc *ctx.ReqCtx) {
|
||||
schema := `{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"title": "Product",
|
||||
"description": "A product from Acme's catalog",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "The unique identifier for a product",
|
||||
"type": "integer"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name of the product",
|
||||
"type": "string"
|
||||
},
|
||||
"price": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"exclusiveMinimum": true
|
||||
}
|
||||
},
|
||||
"required": ["id", "name", "price"]
|
||||
}
|
||||
`
|
||||
// 获取请求报文的内容长度
|
||||
len := rc.GinCtx.Request.ContentLength
|
||||
// 新建一个字节切片,长度与请求报文的内容长度相同
|
||||
body := make([]byte, len)
|
||||
|
||||
// 读取 r 的请求主体,并将具体内容读入 body 中
|
||||
rc.GinCtx.Request.Body.Read(body)
|
||||
err := utils.ValidJsonString(schema, string(body))
|
||||
biz.ErrIsNilAppendErr(err, "%s")
|
||||
}
|
||||
|
||||
func (r *Redis) SetStringValue(rc *ctx.ReqCtx) {
|
||||
g := rc.GinCtx
|
||||
keyValue := new(form.KeyValue)
|
||||
ginx.BindJsonAndValid(g, keyValue)
|
||||
|
||||
ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id")))
|
||||
str, err := ri.Cli.Set(keyValue.Key, keyValue.Value, time.Second*time.Duration(keyValue.Timed)).Result()
|
||||
biz.ErrIsNilAppendErr(err, "保存字符串值失败: %s")
|
||||
rc.ResData = str
|
||||
}
|
||||
@@ -10,6 +10,11 @@ type SelectDataDbVO struct {
|
||||
Port *int `json:"port"`
|
||||
Type *string `json:"type"`
|
||||
Database *string `json:"database"`
|
||||
Username *string `json:"username"`
|
||||
ProjectId *int64 `json:"projectId"`
|
||||
Project *string `json:"project"`
|
||||
Env *string `json:"env"`
|
||||
EnvId *int64 `json:"envId"`
|
||||
CreateTime *time.Time `json:"createTime"`
|
||||
Creator *string `json:"creator"`
|
||||
CreatorId *int64 `json:"creatorId"`
|
||||
|
||||
10
server/devops/apis/vo/project.go
Normal file
10
server/devops/apis/vo/project.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package vo
|
||||
|
||||
// 用户选择项目
|
||||
type AccountProject struct {
|
||||
Id uint64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Remark string `json:"remark"`
|
||||
}
|
||||
|
||||
type AccountProjects []AccountProject
|
||||
29
server/devops/apis/vo/redis.go
Normal file
29
server/devops/apis/vo/redis.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package vo
|
||||
|
||||
import "time"
|
||||
|
||||
type Redis struct {
|
||||
Id *int64 `json:"id"`
|
||||
// Name *string `json:"name"`
|
||||
Host *string `json:"host"`
|
||||
Db int `json:"db"`
|
||||
ProjectId *int64 `json:"projectId"`
|
||||
Project *string `json:"project"`
|
||||
Env *string `json:"env"`
|
||||
EnvId *int64 `json:"envId"`
|
||||
CreateTime *time.Time `json:"createTime"`
|
||||
Creator *string `json:"creator"`
|
||||
CreatorId *int64 `json:"creatorId"`
|
||||
}
|
||||
|
||||
type Keys struct {
|
||||
Cursor uint64 `json:"cursor"`
|
||||
Keys []*KeyInfo `json:"keys"`
|
||||
DbSize int64 `json:"dbSize"`
|
||||
}
|
||||
|
||||
type KeyInfo struct {
|
||||
Key string `json:"key"`
|
||||
Ttl uint64 `json:"ttl"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
@@ -34,6 +34,7 @@ type MachineScriptVO struct {
|
||||
Script *string `json:"script"`
|
||||
Type *int `json:"type"`
|
||||
Description *string `json:"description"`
|
||||
Params *string `json:"params"`
|
||||
MachineId *uint64 `json:"machineId"`
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"mayfly-go/base/biz"
|
||||
"mayfly-go/base/model"
|
||||
"mayfly-go/server/devops/domain/entity"
|
||||
"mayfly-go/server/devops/domain/repository"
|
||||
"mayfly-go/server/devops/infrastructure/db"
|
||||
"mayfly-go/server/devops/infrastructure/persistence"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type IDb interface {
|
||||
type Db interface {
|
||||
// 分页获取机器脚本信息列表
|
||||
GetPageList(condition *entity.Db, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) model.PageResult
|
||||
GetPageList(condition *entity.Db, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult
|
||||
|
||||
// 根据条件获取
|
||||
GetDbBy(condition *entity.Db, cols ...string) error
|
||||
@@ -20,36 +26,260 @@ type IDb interface {
|
||||
|
||||
Save(entity *entity.Db)
|
||||
|
||||
GetDbInstance(id uint64) *db.DbInstance
|
||||
// 删除数据库信息
|
||||
Delete(id uint64)
|
||||
|
||||
// 获取数据库连接实例
|
||||
GetDbInstance(id uint64) *DbInstance
|
||||
}
|
||||
|
||||
type dbApp struct {
|
||||
dbRepo repository.Db
|
||||
type dbAppImpl struct {
|
||||
dbRepo repository.Db
|
||||
dbSqlRepo repository.DbSql
|
||||
}
|
||||
|
||||
var Db IDb = &dbApp{dbRepo: persistence.DbDao}
|
||||
var DbApp Db = &dbAppImpl{
|
||||
dbRepo: persistence.DbDao,
|
||||
dbSqlRepo: persistence.DbSqlDao,
|
||||
}
|
||||
|
||||
// 分页获取数据库信息列表
|
||||
func (d *dbApp) GetPageList(condition *entity.Db, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) model.PageResult {
|
||||
func (d *dbAppImpl) GetPageList(condition *entity.Db, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
|
||||
return d.dbRepo.GetDbList(condition, pageParam, toEntity, orderBy...)
|
||||
}
|
||||
|
||||
// 根据条件获取
|
||||
func (d *dbApp) GetDbBy(condition *entity.Db, cols ...string) error {
|
||||
func (d *dbAppImpl) GetDbBy(condition *entity.Db, cols ...string) error {
|
||||
return d.dbRepo.GetDb(condition, cols...)
|
||||
}
|
||||
|
||||
// 根据id获取
|
||||
func (d *dbApp) GetById(id uint64, cols ...string) *entity.Db {
|
||||
func (d *dbAppImpl) GetById(id uint64, cols ...string) *entity.Db {
|
||||
return d.dbRepo.GetById(id, cols...)
|
||||
}
|
||||
|
||||
func (d *dbApp) Save(entity *entity.Db) {
|
||||
func (d *dbAppImpl) Save(dbEntity *entity.Db) {
|
||||
// 默认tcp连接
|
||||
dbEntity.Network = "tcp"
|
||||
// 测试连接
|
||||
TestConnection(dbEntity)
|
||||
|
||||
// 查找是否存在该库
|
||||
oldDb := &entity.Db{Host: dbEntity.Host, Port: dbEntity.Port, Database: dbEntity.Database}
|
||||
err := d.GetDbBy(oldDb)
|
||||
|
||||
if dbEntity.Id == 0 {
|
||||
biz.IsTrue(err != nil, "该库已存在")
|
||||
d.dbRepo.Insert(dbEntity)
|
||||
} else {
|
||||
// 如果存在该库,则校验修改的库是否为该库
|
||||
if err == nil {
|
||||
biz.IsTrue(oldDb.Id == dbEntity.Id, "该库已存在")
|
||||
}
|
||||
// 先关闭数据库连接
|
||||
CloseDb(dbEntity.Id)
|
||||
d.dbRepo.Update(dbEntity)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *dbApp) GetDbInstance(id uint64) *db.DbInstance {
|
||||
return db.GetDbInstance(id, func(id uint64) *entity.Db {
|
||||
return d.dbRepo.GetById(id)
|
||||
})
|
||||
func (d *dbAppImpl) Delete(id uint64) {
|
||||
// 关闭连接
|
||||
CloseDb(id)
|
||||
d.dbRepo.Delete(id)
|
||||
// 删除该库下用户保存的所有sql信息
|
||||
d.dbSqlRepo.DeleteBy(&entity.DbSql{DbId: id})
|
||||
}
|
||||
|
||||
func (da *dbAppImpl) GetDbInstance(id uint64) *DbInstance {
|
||||
// Id不为0,则为需要缓存
|
||||
needCache := id != 0
|
||||
if needCache {
|
||||
load, ok := dbCache.Load(id)
|
||||
if ok {
|
||||
return load.(*DbInstance)
|
||||
}
|
||||
}
|
||||
d := da.GetById(id)
|
||||
biz.NotNil(d, "数据库信息不存在")
|
||||
DB, err := sql.Open(d.Type, getDsn(d))
|
||||
biz.ErrIsNil(err, fmt.Sprintf("Open %s failed, err:%v\n", d.Type, err))
|
||||
perr := DB.Ping()
|
||||
if perr != nil {
|
||||
panic(biz.NewBizErr(fmt.Sprintf("数据库连接失败: %s", perr.Error())))
|
||||
}
|
||||
|
||||
// 最大连接周期,超过时间的连接就close
|
||||
DB.SetConnMaxLifetime(100 * time.Second)
|
||||
// 设置最大连接数
|
||||
DB.SetMaxOpenConns(2)
|
||||
// 设置闲置连接数
|
||||
DB.SetMaxIdleConns(1)
|
||||
|
||||
dbi := &DbInstance{Id: id, Type: d.Type, db: DB}
|
||||
if needCache {
|
||||
dbCache.LoadOrStore(d.Id, dbi)
|
||||
}
|
||||
return dbi
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
var dbCache sync.Map
|
||||
|
||||
func GetDbInstanceByCache(id uint64) *DbInstance {
|
||||
if load, ok := dbCache.Load(id); ok {
|
||||
return load.(*DbInstance)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestConnection(d *entity.Db) {
|
||||
biz.NotNil(d, "数据库信息不存在")
|
||||
DB, err := sql.Open(d.Type, getDsn(d))
|
||||
biz.ErrIsNil(err, "Open %s failed, err:%v\n", d.Type, err)
|
||||
defer DB.Close()
|
||||
perr := DB.Ping()
|
||||
biz.ErrIsNilAppendErr(perr, "数据库连接失败: %s")
|
||||
}
|
||||
|
||||
// db实例
|
||||
type DbInstance struct {
|
||||
Id uint64
|
||||
Type string
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
// 执行查询语句
|
||||
// 依次返回 列名数组,结果map,错误
|
||||
func (d *DbInstance) SelectData(sql string) ([]string, []map[string]string, error) {
|
||||
sql = strings.Trim(sql, " ")
|
||||
if !strings.HasPrefix(sql, "SELECT") && !strings.HasPrefix(sql, "select") {
|
||||
return nil, nil, errors.New("该sql非查询语句")
|
||||
}
|
||||
// 没加limit,则默认限制50条
|
||||
if !strings.Contains(sql, "limit") && !strings.Contains(sql, "LIMIT") {
|
||||
sql = sql + " LIMIT 50"
|
||||
}
|
||||
rows, err := d.db.Query(sql)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
// rows对象一定要close掉,如果出错,不关掉则会很迅速的达到设置最大连接数,
|
||||
// 后面的链接过来直接报错或拒绝,实际上也没有起效果
|
||||
defer func() {
|
||||
if rows != nil {
|
||||
rows.Close()
|
||||
}
|
||||
}()
|
||||
cols, _ := rows.Columns()
|
||||
// 这里表示一行填充数据
|
||||
scans := make([]interface{}, len(cols))
|
||||
// 这里表示一行所有列的值,用[]byte表示
|
||||
vals := make([][]byte, len(cols))
|
||||
// 这里scans引用vals,把数据填充到[]byte里
|
||||
for k := range vals {
|
||||
scans[k] = &vals[k]
|
||||
}
|
||||
|
||||
result := make([]map[string]string, 0)
|
||||
// 列名
|
||||
colNames := make([]string, 0)
|
||||
// 是否第一次遍历,列名数组只需第一次遍历时加入
|
||||
isFirst := true
|
||||
for rows.Next() {
|
||||
// 不Scan也会导致等待,该链接实际处于未工作的状态,然后也会导致连接数迅速达到最大
|
||||
err := rows.Scan(scans...)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
// 每行数据
|
||||
rowData := make(map[string]string)
|
||||
// 把vals中的数据复制到row中
|
||||
for k, v := range vals {
|
||||
key := cols[k]
|
||||
// 如果是密码字段,则脱敏显示
|
||||
if key == "password" {
|
||||
v = []byte("******")
|
||||
}
|
||||
if isFirst {
|
||||
colNames = append(colNames, key)
|
||||
}
|
||||
// 这里把[]byte数据转成string
|
||||
rowData[key] = string(v)
|
||||
}
|
||||
//放入结果集
|
||||
result = append(result, rowData)
|
||||
isFirst = false
|
||||
}
|
||||
return colNames, result, nil
|
||||
}
|
||||
|
||||
// 执行 update, insert, delete,建表等sql
|
||||
// 返回影响条数和错误
|
||||
func (d *DbInstance) Exec(sql string) (int64, error) {
|
||||
res, err := d.db.Exec(sql)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return res.RowsAffected()
|
||||
}
|
||||
|
||||
// 关闭连接,并从缓存中移除
|
||||
func (d *DbInstance) Close() {
|
||||
d.db.Close()
|
||||
dbCache.Delete(d.Id)
|
||||
}
|
||||
|
||||
// 获取dataSourceName
|
||||
func getDsn(d *entity.Db) string {
|
||||
if d.Type == "mysql" {
|
||||
return fmt.Sprintf("%s:%s@%s(%s:%d)/%s", d.Username, d.Password, d.Network, d.Host, d.Port, d.Database)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func CloseDb(id uint64) {
|
||||
if di := GetDbInstanceByCache(id); di != nil {
|
||||
di.Close()
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------元数据-------------------------------------------
|
||||
|
||||
const (
|
||||
// mysql 表信息元数据
|
||||
MYSQL_TABLE_MA = `SELECT table_name tableName, engine, table_comment tableComment,
|
||||
create_time createTime from information_schema.tables
|
||||
WHERE table_schema = (SELECT database())`
|
||||
|
||||
// mysql 列信息元数据
|
||||
MYSQL_COLOUMN_MA = `SELECT table_name tableName, column_name columnName, column_type columnType,
|
||||
column_comment columnComment, column_key columnKey, extra from information_schema.columns
|
||||
WHERE table_name in (%s) AND table_schema = (SELECT database()) ORDER BY ordinal_position limit 15000`
|
||||
)
|
||||
|
||||
func (d *DbInstance) GetTableMetedatas() []map[string]string {
|
||||
var sql string
|
||||
if d.Type == "mysql" {
|
||||
sql = MYSQL_TABLE_MA
|
||||
}
|
||||
_, res, _ := d.SelectData(sql)
|
||||
return res
|
||||
}
|
||||
|
||||
func (d *DbInstance) GetColumnMetadatas(tableNames ...string) []map[string]string {
|
||||
var sql, tableName string
|
||||
for i := 0; i < len(tableNames); i++ {
|
||||
if i != 0 {
|
||||
tableName = tableName + ", "
|
||||
}
|
||||
tableName = tableName + "'" + tableNames[i] + "'"
|
||||
}
|
||||
if d.Type == "mysql" {
|
||||
sql = fmt.Sprintf(MYSQL_COLOUMN_MA, tableName)
|
||||
}
|
||||
|
||||
_, res, err := d.SelectData(sql)
|
||||
biz.ErrIsNilAppendErr(err, "获取数据库列信息失败: %s")
|
||||
return res
|
||||
}
|
||||
|
||||
@@ -7,58 +7,95 @@ import (
|
||||
"mayfly-go/server/devops/domain/repository"
|
||||
"mayfly-go/server/devops/infrastructure/machine"
|
||||
"mayfly-go/server/devops/infrastructure/persistence"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type IMachine interface {
|
||||
type Machine interface {
|
||||
// 根据条件获取账号信息
|
||||
GetMachine(condition *entity.Machine, cols ...string) error
|
||||
|
||||
Save(entity *entity.Machine)
|
||||
|
||||
Delete(id uint64)
|
||||
|
||||
// 根据id获取
|
||||
GetById(id uint64, cols ...string) *entity.Machine
|
||||
|
||||
// 分页获取机器信息列表
|
||||
GetMachineList(condition *entity.Machine, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) model.PageResult
|
||||
GetMachineList(condition *entity.Machine, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult
|
||||
|
||||
// 获取机器连接
|
||||
GetCli(id uint64) *machine.Cli
|
||||
}
|
||||
|
||||
type machineApp struct {
|
||||
type machineAppImpl struct {
|
||||
machineRepo repository.Machine
|
||||
}
|
||||
|
||||
var Machine IMachine = &machineApp{machineRepo: persistence.MachineDao}
|
||||
var MachineApp Machine = &machineAppImpl{machineRepo: persistence.MachineDao}
|
||||
|
||||
// 分页获取机器信息列表
|
||||
func (m *machineApp) GetMachineList(condition *entity.Machine, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) model.PageResult {
|
||||
func (m *machineAppImpl) GetMachineList(condition *entity.Machine, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
|
||||
return m.machineRepo.GetMachineList(condition, pageParam, toEntity, orderBy...)
|
||||
}
|
||||
|
||||
// 根据条件获取机器信息
|
||||
func (m *machineApp) Save(entity *entity.Machine) {
|
||||
biz.ErrIsNil(machine.TestConn(entity), "该机器无法连接")
|
||||
if entity.Id != 0 {
|
||||
m.machineRepo.UpdateById(entity)
|
||||
func (m *machineAppImpl) Save(me *entity.Machine) {
|
||||
biz.ErrIsNilAppendErr(machine.TestConn(me), "该机器无法连接: %s")
|
||||
|
||||
oldMachine := &entity.Machine{Ip: me.Ip, Port: me.Port, Username: me.Username}
|
||||
err := m.GetMachine(oldMachine)
|
||||
|
||||
if me.Id != 0 {
|
||||
// 如果存在该库,则校验修改的库是否为该库
|
||||
if err == nil {
|
||||
biz.IsTrue(oldMachine.Id == me.Id, "该机器信息已存在")
|
||||
}
|
||||
// 关闭连接
|
||||
machine.Close(me.Id)
|
||||
m.machineRepo.UpdateById(me)
|
||||
} else {
|
||||
m.machineRepo.Create(entity)
|
||||
biz.IsTrue(err != nil, "该机器信息已存在")
|
||||
m.machineRepo.Create(me)
|
||||
}
|
||||
}
|
||||
|
||||
// 根据条件获取机器信息
|
||||
func (m *machineApp) GetMachine(condition *entity.Machine, cols ...string) error {
|
||||
func (m *machineAppImpl) Delete(id uint64) {
|
||||
// 关闭连接
|
||||
machine.Close(id)
|
||||
model.Tx(
|
||||
func(db *gorm.DB) error {
|
||||
// 删除machine表信息
|
||||
return db.Delete(new(entity.Machine), "id = ?", id).Error
|
||||
},
|
||||
func(db *gorm.DB) error {
|
||||
// 删除machine_file
|
||||
machineFile := &entity.MachineFile{MachineId: id}
|
||||
return db.Where(machineFile).Delete(machineFile).Error
|
||||
},
|
||||
func(db *gorm.DB) error {
|
||||
// 删除machine_script
|
||||
machineScript := &entity.MachineScript{MachineId: id}
|
||||
return db.Where(machineScript).Delete(machineScript).Error
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// 根据条件获取机器信息
|
||||
func (m *machineAppImpl) GetMachine(condition *entity.Machine, cols ...string) error {
|
||||
return m.machineRepo.GetMachine(condition, cols...)
|
||||
}
|
||||
|
||||
func (m *machineApp) GetById(id uint64, cols ...string) *entity.Machine {
|
||||
func (m *machineAppImpl) GetById(id uint64, cols ...string) *entity.Machine {
|
||||
return m.machineRepo.GetById(id, cols...)
|
||||
}
|
||||
|
||||
func (m *machineApp) GetCli(id uint64) *machine.Cli {
|
||||
func (m *machineAppImpl) GetCli(id uint64) *machine.Cli {
|
||||
cli, err := machine.GetCli(id, func(machineId uint64) *entity.Machine {
|
||||
return m.GetById(machineId)
|
||||
})
|
||||
biz.ErrIsNil(err, "获取客户端错误")
|
||||
biz.ErrIsNilAppendErr(err, "获取客户端错误: %s")
|
||||
return cli
|
||||
}
|
||||
|
||||
@@ -15,9 +15,9 @@ import (
|
||||
"github.com/pkg/sftp"
|
||||
)
|
||||
|
||||
type IMachineFile interface {
|
||||
type MachineFile interface {
|
||||
// 分页获取机器文件信息列表
|
||||
GetPageList(condition *entity.MachineFile, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) model.PageResult
|
||||
GetPageList(condition *entity.MachineFile, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult
|
||||
|
||||
// 根据条件获取
|
||||
GetMachineFile(condition *entity.MachineFile, cols ...string) error
|
||||
@@ -47,34 +47,34 @@ type IMachineFile interface {
|
||||
RemoveFile(fileId uint64, path string)
|
||||
}
|
||||
|
||||
type machineFileApp struct {
|
||||
type machineFileAppImpl struct {
|
||||
machineFileRepo repository.MachineFile
|
||||
machineRepo repository.Machine
|
||||
}
|
||||
|
||||
// 实现类单例
|
||||
var MachineFile IMachineFile = &machineFileApp{
|
||||
var MachineFileApp MachineFile = &machineFileAppImpl{
|
||||
machineRepo: persistence.MachineDao,
|
||||
machineFileRepo: persistence.MachineFileDao,
|
||||
}
|
||||
|
||||
// 分页获取机器脚本信息列表
|
||||
func (m *machineFileApp) GetPageList(condition *entity.MachineFile, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) model.PageResult {
|
||||
func (m *machineFileAppImpl) GetPageList(condition *entity.MachineFile, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
|
||||
return m.machineFileRepo.GetPageList(condition, pageParam, toEntity, orderBy...)
|
||||
}
|
||||
|
||||
// 根据条件获取
|
||||
func (m *machineFileApp) GetMachineFile(condition *entity.MachineFile, cols ...string) error {
|
||||
func (m *machineFileAppImpl) GetMachineFile(condition *entity.MachineFile, cols ...string) error {
|
||||
return m.machineFileRepo.GetMachineFile(condition, cols...)
|
||||
}
|
||||
|
||||
// 根据id获取
|
||||
func (m *machineFileApp) GetById(id uint64, cols ...string) *entity.MachineFile {
|
||||
func (m *machineFileAppImpl) GetById(id uint64, cols ...string) *entity.MachineFile {
|
||||
return m.machineFileRepo.GetById(id, cols...)
|
||||
}
|
||||
|
||||
// 保存机器文件配置
|
||||
func (m *machineFileApp) Save(entity *entity.MachineFile) {
|
||||
func (m *machineFileAppImpl) Save(entity *entity.MachineFile) {
|
||||
biz.NotNil(m.machineRepo.GetById(entity.MachineId, "Name"), "该机器不存在")
|
||||
|
||||
if entity.Id != 0 {
|
||||
@@ -85,11 +85,11 @@ func (m *machineFileApp) Save(entity *entity.MachineFile) {
|
||||
}
|
||||
|
||||
// 根据id删除
|
||||
func (m *machineFileApp) Delete(id uint64) {
|
||||
func (m *machineFileAppImpl) Delete(id uint64) {
|
||||
m.machineFileRepo.Delete(id)
|
||||
}
|
||||
|
||||
func (m *machineFileApp) ReadDir(fid uint64, path string) []fs.FileInfo {
|
||||
func (m *machineFileAppImpl) ReadDir(fid uint64, path string) []fs.FileInfo {
|
||||
path, machineId := m.checkAndReturnPathMid(fid, path)
|
||||
if !strings.HasSuffix(path, "/") {
|
||||
path = path + "/"
|
||||
@@ -101,7 +101,7 @@ func (m *machineFileApp) ReadDir(fid uint64, path string) []fs.FileInfo {
|
||||
return fis
|
||||
}
|
||||
|
||||
func (m *machineFileApp) ReadFile(fileId uint64, path string) ([]byte, fs.FileInfo) {
|
||||
func (m *machineFileAppImpl) ReadFile(fileId uint64, path string) ([]byte, fs.FileInfo) {
|
||||
path, machineId := m.checkAndReturnPathMid(fileId, path)
|
||||
sftpCli := m.getSftpCli(machineId)
|
||||
// 读取文件内容
|
||||
@@ -119,7 +119,7 @@ func (m *machineFileApp) ReadFile(fileId uint64, path string) ([]byte, fs.FileIn
|
||||
}
|
||||
|
||||
// 写文件内容
|
||||
func (m *machineFileApp) WriteFileContent(fileId uint64, path string, content []byte) {
|
||||
func (m *machineFileAppImpl) WriteFileContent(fileId uint64, path string, content []byte) {
|
||||
_, machineId := m.checkAndReturnPathMid(fileId, path)
|
||||
|
||||
sftpCli := m.getSftpCli(machineId)
|
||||
@@ -133,7 +133,7 @@ func (m *machineFileApp) WriteFileContent(fileId uint64, path string, content []
|
||||
}
|
||||
|
||||
// 上传文件
|
||||
func (m *machineFileApp) UploadFile(fileId uint64, path, filename string, content []byte) {
|
||||
func (m *machineFileAppImpl) UploadFile(fileId uint64, path, filename string, content []byte) {
|
||||
path, machineId := m.checkAndReturnPathMid(fileId, path)
|
||||
if !strings.HasSuffix(path, "/") {
|
||||
path = path + "/"
|
||||
@@ -148,7 +148,7 @@ func (m *machineFileApp) UploadFile(fileId uint64, path, filename string, conten
|
||||
}
|
||||
|
||||
// 删除文件
|
||||
func (m *machineFileApp) RemoveFile(fileId uint64, path string) {
|
||||
func (m *machineFileAppImpl) RemoveFile(fileId uint64, path string) {
|
||||
path, machineId := m.checkAndReturnPathMid(fileId, path)
|
||||
|
||||
sftpCli := m.getSftpCli(machineId)
|
||||
@@ -164,12 +164,12 @@ func (m *machineFileApp) RemoveFile(fileId uint64, path string) {
|
||||
}
|
||||
|
||||
// 获取sftp client
|
||||
func (m *machineFileApp) getSftpCli(machineId uint64) *sftp.Client {
|
||||
return Machine.GetCli(machineId).GetSftpCli()
|
||||
func (m *machineFileAppImpl) getSftpCli(machineId uint64) *sftp.Client {
|
||||
return MachineApp.GetCli(machineId).GetSftpCli()
|
||||
}
|
||||
|
||||
// 校验并返回实际可访问的文件path
|
||||
func (m *machineFileApp) checkAndReturnPathMid(fid uint64, inputPath string) (string, uint64) {
|
||||
func (m *machineFileAppImpl) checkAndReturnPathMid(fid uint64, inputPath string) (string, uint64) {
|
||||
biz.IsTrue(fid != 0, "文件id不能为空")
|
||||
mf := m.GetById(uint64(fid))
|
||||
biz.NotNil(mf, "文件不存在")
|
||||
|
||||
@@ -8,9 +8,9 @@ import (
|
||||
"mayfly-go/server/devops/infrastructure/persistence"
|
||||
)
|
||||
|
||||
type IMachineScript interface {
|
||||
type MachineScript interface {
|
||||
// 分页获取机器脚本信息列表
|
||||
GetPageList(condition *entity.MachineScript, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) model.PageResult
|
||||
GetPageList(condition *entity.MachineScript, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult
|
||||
|
||||
// 根据条件获取
|
||||
GetMachineScript(condition *entity.MachineScript, cols ...string) error
|
||||
@@ -23,7 +23,7 @@ type IMachineScript interface {
|
||||
Delete(id uint64)
|
||||
}
|
||||
|
||||
type machineScriptApp struct {
|
||||
type machineScriptAppImpl struct {
|
||||
machineScriptRepo repository.MachineScript
|
||||
machineRepo repository.Machine
|
||||
}
|
||||
@@ -31,27 +31,27 @@ type machineScriptApp struct {
|
||||
const Common_Script_Machine_Id = 9999999
|
||||
|
||||
// 实现类单例
|
||||
var MachineScript IMachineScript = &machineScriptApp{
|
||||
var MachineScriptApp MachineScript = &machineScriptAppImpl{
|
||||
machineRepo: persistence.MachineDao,
|
||||
machineScriptRepo: persistence.MachineScriptDao}
|
||||
|
||||
// 分页获取机器脚本信息列表
|
||||
func (m *machineScriptApp) GetPageList(condition *entity.MachineScript, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) model.PageResult {
|
||||
func (m *machineScriptAppImpl) GetPageList(condition *entity.MachineScript, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
|
||||
return m.machineScriptRepo.GetPageList(condition, pageParam, toEntity, orderBy...)
|
||||
}
|
||||
|
||||
// 根据条件获取
|
||||
func (m *machineScriptApp) GetMachineScript(condition *entity.MachineScript, cols ...string) error {
|
||||
func (m *machineScriptAppImpl) GetMachineScript(condition *entity.MachineScript, cols ...string) error {
|
||||
return m.machineScriptRepo.GetMachineScript(condition, cols...)
|
||||
}
|
||||
|
||||
// 根据id获取
|
||||
func (m *machineScriptApp) GetById(id uint64, cols ...string) *entity.MachineScript {
|
||||
func (m *machineScriptAppImpl) GetById(id uint64, cols ...string) *entity.MachineScript {
|
||||
return m.machineScriptRepo.GetById(id, cols...)
|
||||
}
|
||||
|
||||
// 保存机器脚本
|
||||
func (m *machineScriptApp) Save(entity *entity.MachineScript) {
|
||||
func (m *machineScriptAppImpl) Save(entity *entity.MachineScript) {
|
||||
// 如果机器id不为公共脚本id,则校验机器是否存在
|
||||
if machineId := entity.MachineId; machineId != Common_Script_Machine_Id {
|
||||
biz.NotNil(m.machineRepo.GetById(machineId, "Name"), "该机器不存在")
|
||||
@@ -65,6 +65,6 @@ func (m *machineScriptApp) Save(entity *entity.MachineScript) {
|
||||
}
|
||||
|
||||
// 根据id删除
|
||||
func (m *machineScriptApp) Delete(id uint64) {
|
||||
func (m *machineScriptAppImpl) Delete(id uint64) {
|
||||
m.machineScriptRepo.Delete(id)
|
||||
}
|
||||
|
||||
95
server/devops/application/project_app.go
Normal file
95
server/devops/application/project_app.go
Normal file
@@ -0,0 +1,95 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"mayfly-go/base/biz"
|
||||
"mayfly-go/base/model"
|
||||
"mayfly-go/server/devops/domain/entity"
|
||||
"mayfly-go/server/devops/domain/repository"
|
||||
"mayfly-go/server/devops/infrastructure/persistence"
|
||||
)
|
||||
|
||||
type Project interface {
|
||||
// 分页获取项目信息列表
|
||||
GetPageList(condition *entity.Project, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult
|
||||
|
||||
ListProjectByIds(ids []uint64, toEntity interface{}, orderBy ...string)
|
||||
|
||||
SaveProject(project *entity.Project)
|
||||
|
||||
// 根据项目id获取所有该项目下的环境信息列表
|
||||
ListEnvByProjectId(projectId uint64, listPtr interface{})
|
||||
|
||||
// 保存项目环境信息
|
||||
SaveProjectEnv(projectEnv *entity.ProjectEnv)
|
||||
|
||||
// 根据条件获取项目成员信息
|
||||
ListMember(condition *entity.ProjectMember, toEntity interface{}, orderBy ...string)
|
||||
|
||||
SaveProjectMember(pm *entity.ProjectMember)
|
||||
|
||||
// 根据条件获取项目成员信息
|
||||
GetMemberPage(condition *entity.ProjectMember, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult
|
||||
|
||||
DeleteMember(projectId, accountId uint64)
|
||||
}
|
||||
|
||||
type projectAppImpl struct {
|
||||
projectRepo repository.Project
|
||||
projectEnvRepo repository.ProjectEnv
|
||||
projectMemberRepo repository.ProjectMemeber
|
||||
}
|
||||
|
||||
var ProjectApp Project = &projectAppImpl{
|
||||
projectRepo: persistence.ProjectRepo,
|
||||
projectEnvRepo: persistence.ProjectEnvRepo,
|
||||
projectMemberRepo: persistence.ProjectMemberRepo,
|
||||
}
|
||||
|
||||
// 分页获取项目信息列表
|
||||
func (p *projectAppImpl) GetPageList(condition *entity.Project, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
|
||||
return p.projectRepo.GetPageList(condition, pageParam, toEntity, orderBy...)
|
||||
}
|
||||
|
||||
func (p *projectAppImpl) ListProjectByIds(ids []uint64, toEntity interface{}, orderBy ...string) {
|
||||
p.projectRepo.GetByIdIn(ids, toEntity, orderBy...)
|
||||
}
|
||||
|
||||
func (p *projectAppImpl) SaveProject(project *entity.Project) {
|
||||
if project.Id == 0 {
|
||||
p.projectRepo.Save(project)
|
||||
} else {
|
||||
// 防止误传导致项目名更新
|
||||
project.Name = ""
|
||||
p.projectRepo.Update(project)
|
||||
}
|
||||
}
|
||||
|
||||
// 根据项目id获取所有该项目下的环境信息列表
|
||||
func (p *projectAppImpl) ListEnvByProjectId(projectId uint64, listPtr interface{}) {
|
||||
p.projectEnvRepo.ListEnv(&entity.ProjectEnv{ProjectId: projectId}, listPtr)
|
||||
}
|
||||
|
||||
// 保存项目环境信息
|
||||
func (p *projectAppImpl) SaveProjectEnv(projectEnv *entity.ProjectEnv) {
|
||||
p.projectEnvRepo.Save(projectEnv)
|
||||
}
|
||||
|
||||
// 根据条件获取项目成员信息
|
||||
func (p *projectAppImpl) ListMember(condition *entity.ProjectMember, toEntity interface{}, orderBy ...string) {
|
||||
p.projectMemberRepo.ListMemeber(condition, toEntity, orderBy...)
|
||||
}
|
||||
|
||||
func (p *projectAppImpl) SaveProjectMember(pm *entity.ProjectMember) {
|
||||
pms := new([]entity.ProjectMember)
|
||||
p.ListMember(&entity.ProjectMember{ProjectId: pm.ProjectId, AccountId: pm.AccountId}, pms)
|
||||
biz.IsTrue(len(*pms) == 0, "该成员已存在")
|
||||
p.projectMemberRepo.Save(pm)
|
||||
}
|
||||
|
||||
func (p *projectAppImpl) GetMemberPage(condition *entity.ProjectMember, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
|
||||
return p.projectMemberRepo.GetPageList(condition, pageParam, toEntity, orderBy...)
|
||||
}
|
||||
|
||||
func (p *projectAppImpl) DeleteMember(projectId, accountId uint64) {
|
||||
p.projectMemberRepo.DeleteByPidMid(projectId, accountId)
|
||||
}
|
||||
146
server/devops/application/redis_app.go
Normal file
146
server/devops/application/redis_app.go
Normal file
@@ -0,0 +1,146 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"mayfly-go/base/biz"
|
||||
"mayfly-go/base/model"
|
||||
"mayfly-go/server/devops/domain/entity"
|
||||
"mayfly-go/server/devops/domain/repository"
|
||||
"mayfly-go/server/devops/infrastructure/persistence"
|
||||
"sync"
|
||||
|
||||
"github.com/go-redis/redis"
|
||||
)
|
||||
|
||||
type Redis interface {
|
||||
// 分页获取机器脚本信息列表
|
||||
GetPageList(condition *entity.Redis, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult
|
||||
|
||||
// 根据id获取
|
||||
GetById(id uint64, cols ...string) *entity.Redis
|
||||
|
||||
// 根据条件获取
|
||||
GetRedisBy(condition *entity.Redis, cols ...string) error
|
||||
|
||||
Save(entity *entity.Redis)
|
||||
|
||||
// 删除数据库信息
|
||||
Delete(id uint64)
|
||||
|
||||
// 获取数据库连接实例
|
||||
GetRedisInstance(id uint64) *RedisInstance
|
||||
}
|
||||
|
||||
type redisAppImpl struct {
|
||||
redisRepo repository.Redis
|
||||
}
|
||||
|
||||
var RedisApp Redis = &redisAppImpl{
|
||||
redisRepo: persistence.RedisDao,
|
||||
}
|
||||
|
||||
// 分页获取机器脚本信息列表
|
||||
func (r *redisAppImpl) GetPageList(condition *entity.Redis, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
|
||||
return r.redisRepo.GetRedisList(condition, pageParam, toEntity, orderBy...)
|
||||
}
|
||||
|
||||
// 根据id获取
|
||||
func (r *redisAppImpl) GetById(id uint64, cols ...string) *entity.Redis {
|
||||
return r.redisRepo.GetById(id, cols...)
|
||||
}
|
||||
|
||||
// 根据条件获取
|
||||
func (r *redisAppImpl) GetRedisBy(condition *entity.Redis, cols ...string) error {
|
||||
return r.redisRepo.GetRedis(condition, cols...)
|
||||
}
|
||||
|
||||
func (r *redisAppImpl) Save(re *entity.Redis) {
|
||||
TestRedisConnection(re)
|
||||
|
||||
// 查找是否存在该库
|
||||
oldRedis := &entity.Redis{Host: re.Host, Db: re.Db}
|
||||
err := r.GetRedisBy(oldRedis)
|
||||
|
||||
if re.Id == 0 {
|
||||
biz.IsTrue(err != nil, "该库已存在")
|
||||
r.redisRepo.Insert(re)
|
||||
} else {
|
||||
// 如果存在该库,则校验修改的库是否为该库
|
||||
if err == nil {
|
||||
biz.IsTrue(re.Id == re.Id, "该库已存在")
|
||||
}
|
||||
// 先关闭数据库连接
|
||||
CloseRedis(re.Id)
|
||||
r.redisRepo.Update(re)
|
||||
}
|
||||
}
|
||||
|
||||
// 删除Redis信息
|
||||
func (r *redisAppImpl) Delete(id uint64) {
|
||||
CloseRedis(id)
|
||||
r.redisRepo.Delete(id)
|
||||
}
|
||||
|
||||
// 获取数据库连接实例
|
||||
func (r *redisAppImpl) GetRedisInstance(id uint64) *RedisInstance {
|
||||
// Id不为0,则为需要缓存
|
||||
needCache := id != 0
|
||||
if needCache {
|
||||
load, ok := redisCache.Load(id)
|
||||
if ok {
|
||||
return load.(*RedisInstance)
|
||||
}
|
||||
}
|
||||
// 缓存不存在,则回调获取redis信息
|
||||
re := r.GetById(id)
|
||||
biz.NotNil(re, "redis信息不存在")
|
||||
rcli := redis.NewClient(&redis.Options{
|
||||
Addr: re.Host,
|
||||
Password: re.Password, // no password set
|
||||
DB: re.Db, // use default DB
|
||||
})
|
||||
// 测试连接
|
||||
_, e := rcli.Ping().Result()
|
||||
biz.ErrIsNilAppendErr(e, "redis连接失败: %s")
|
||||
|
||||
ri := &RedisInstance{Id: id, Cli: rcli}
|
||||
if needCache {
|
||||
redisCache.LoadOrStore(re.Id, ri)
|
||||
}
|
||||
return ri
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
var redisCache sync.Map
|
||||
|
||||
// redis实例
|
||||
type RedisInstance struct {
|
||||
Id uint64
|
||||
Cli *redis.Client
|
||||
}
|
||||
|
||||
// 关闭redis连接
|
||||
func CloseRedis(id uint64) {
|
||||
if load, ok := redisCache.Load(id); ok {
|
||||
load.(*RedisInstance).Cli.Close()
|
||||
redisCache.Delete(id)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRedisConnection(re *entity.Redis) {
|
||||
rcli := redis.NewClient(&redis.Options{
|
||||
Addr: re.Host,
|
||||
Password: re.Password, // no password set
|
||||
DB: re.Db, // use default DB
|
||||
})
|
||||
defer rcli.Close()
|
||||
// 测试连接
|
||||
_, e := rcli.Ping().Result()
|
||||
biz.ErrIsNilAppendErr(e, "Redis连接失败: %s")
|
||||
}
|
||||
|
||||
func (r *RedisInstance) Scan(cursor uint64, match string, count int64) ([]string, uint64) {
|
||||
keys, newcursor, err := r.Cli.Scan(cursor, match, count).Result()
|
||||
biz.ErrIsNilAppendErr(err, "scan失败: %s")
|
||||
return keys, newcursor
|
||||
}
|
||||
@@ -7,12 +7,16 @@ import (
|
||||
type Db struct {
|
||||
model.Model
|
||||
|
||||
Name string `orm:"column(name)" json:"name"`
|
||||
Type string `orm:"column(type)" json:"type"` // 类型,mysql oracle等
|
||||
Host string `orm:"column(host)" json:"host"`
|
||||
Port int `orm:"column(port)" json:"port"`
|
||||
Network string `orm:"column(network)" json:"network"`
|
||||
Username string `orm:"column(username)" json:"username"`
|
||||
Password string `orm:"column(password)" json:"-"`
|
||||
Database string `orm:"column(database)" json:"database"`
|
||||
Name string `orm:"column(name)" json:"name"`
|
||||
Type string `orm:"column(type)" json:"type"` // 类型,mysql oracle等
|
||||
Host string `orm:"column(host)" json:"host"`
|
||||
Port int `orm:"column(port)" json:"port"`
|
||||
Network string `orm:"column(network)" json:"network"`
|
||||
Username string `orm:"column(username)" json:"username"`
|
||||
Password string `orm:"column(password)" json:"-"`
|
||||
Database string `orm:"column(database)" json:"database"`
|
||||
ProjectId uint64
|
||||
Project string
|
||||
EnvId uint64
|
||||
Env string
|
||||
}
|
||||
|
||||
@@ -4,12 +4,10 @@ import "mayfly-go/base/model"
|
||||
|
||||
type MachineScript struct {
|
||||
model.Model
|
||||
Name string `json:"name"`
|
||||
// 机器id
|
||||
MachineId uint64 `json:"machineId"`
|
||||
Type int `json:"type"`
|
||||
// 脚本内容
|
||||
Description string `json:"description"`
|
||||
// 脚本内容
|
||||
Script string `json:"script"`
|
||||
Name string `json:"name"`
|
||||
MachineId uint64 `json:"machineId"` // 机器id
|
||||
Type int `json:"type"`
|
||||
Description string `json:"description"` // 脚本描述
|
||||
Params string `json:"params"` // 参数列表json
|
||||
Script string `json:"script"` // 脚本内容
|
||||
}
|
||||
|
||||
10
server/devops/domain/entity/project.go
Normal file
10
server/devops/domain/entity/project.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package entity
|
||||
|
||||
import "mayfly-go/base/model"
|
||||
|
||||
// 项目
|
||||
type Project struct {
|
||||
model.Model
|
||||
Name string `json:"name"` // 项目名
|
||||
Remark string `json:"remark"` // 备注说明
|
||||
}
|
||||
11
server/devops/domain/entity/project_env.go
Normal file
11
server/devops/domain/entity/project_env.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package entity
|
||||
|
||||
import "mayfly-go/base/model"
|
||||
|
||||
// 项目环境
|
||||
type ProjectEnv struct {
|
||||
model.Model
|
||||
Name string `json:"name"` // 环境名
|
||||
ProjectId uint64 `json:"projectId"` // 项目id
|
||||
Remark string `json:"remark"` // 备注说明
|
||||
}
|
||||
11
server/devops/domain/entity/project_member.go
Normal file
11
server/devops/domain/entity/project_member.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package entity
|
||||
|
||||
import "mayfly-go/base/model"
|
||||
|
||||
// 项目成员,用于对项目下组件的访问控制
|
||||
type ProjectMember struct {
|
||||
model.Model
|
||||
AccountId uint64 `json:"accountId"` // 账号
|
||||
Username string `json:"username"` // 账号用户名
|
||||
ProjectId uint64 `json:"projectId"` // 项目id
|
||||
}
|
||||
17
server/devops/domain/entity/redis.go
Normal file
17
server/devops/domain/entity/redis.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"mayfly-go/base/model"
|
||||
)
|
||||
|
||||
type Redis struct {
|
||||
model.Model
|
||||
|
||||
Host string `orm:"column(host)" json:"host"`
|
||||
Password string `orm:"column(password)" json:"-"`
|
||||
Db int `orm:"column(database)" json:"db"`
|
||||
ProjectId uint64
|
||||
Project string
|
||||
EnvId uint64
|
||||
Env string
|
||||
}
|
||||
@@ -7,11 +7,17 @@ import (
|
||||
|
||||
type Db interface {
|
||||
// 分页获取机器信息列表
|
||||
GetDbList(condition *entity.Db, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) model.PageResult
|
||||
GetDbList(condition *entity.Db, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult
|
||||
|
||||
// 根据条件获取账号信息
|
||||
GetDb(condition *entity.Db, cols ...string) error
|
||||
|
||||
// 根据id获取
|
||||
GetById(id uint64, cols ...string) *entity.Db
|
||||
|
||||
Insert(db *entity.Db)
|
||||
|
||||
Update(db *entity.Db)
|
||||
|
||||
Delete(id uint64)
|
||||
}
|
||||
|
||||
7
server/devops/domain/repository/db_sql.go
Normal file
7
server/devops/domain/repository/db_sql.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package repository
|
||||
|
||||
import "mayfly-go/server/devops/domain/entity"
|
||||
|
||||
type DbSql interface {
|
||||
DeleteBy(condition *entity.DbSql)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user