refactor: 初步提交全局授权凭证-资源多账号改造

This commit is contained in:
meilin.huang
2024-04-09 12:55:51 +08:00
parent 408bac09a1
commit 21498584b1
59 changed files with 1779 additions and 656 deletions

View File

@@ -16,10 +16,10 @@ const (
// RedisConnExpireTime = 2 * time.Minute
// MongoConnExpireTime = 2 * time.Minute
TagResourceTypeMachine = 1
TagResourceTypeDb = 2
TagResourceTypeRedis = 3
TagResourceTypeMongo = 4
TagResourceTypeMachine int8 = 1
TagResourceTypeDb int8 = 2
TagResourceTypeRedis int8 = 3
TagResourceTypeMongo int8 = 4
// 删除机器的事件主题名
DeleteMachineEventTopic = "machine:delete"

View File

@@ -9,6 +9,7 @@ import (
"mayfly-go/internal/db/domain/entity"
"mayfly-go/internal/db/domain/repository"
tagapp "mayfly-go/internal/tag/application"
tagentity "mayfly-go/internal/tag/domain/entity"
"mayfly-go/pkg/base"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/errorx"
@@ -87,7 +88,7 @@ func (d *dbAppImpl) SaveDb(ctx context.Context, dbEntity *entity.Db, tagIds ...u
}, func(ctx context.Context) error {
return d.tagApp.SaveResource(ctx, &tagapp.SaveResourceTagParam{
ResourceCode: dbEntity.Code,
ResourceType: consts.TagResourceTypeDb,
ResourceType: tagentity.TagTypeDb,
TagIds: tagIds,
})
})
@@ -127,7 +128,7 @@ func (d *dbAppImpl) SaveDb(ctx context.Context, dbEntity *entity.Db, tagIds ...u
}, func(ctx context.Context) error {
return d.tagApp.SaveResource(ctx, &tagapp.SaveResourceTagParam{
ResourceCode: old.Code,
ResourceType: consts.TagResourceTypeDb,
ResourceType: tagentity.TagTypeDb,
TagIds: tagIds,
})
})
@@ -154,7 +155,7 @@ func (d *dbAppImpl) Delete(ctx context.Context, id uint64) error {
}, func(ctx context.Context) error {
return d.tagApp.SaveResource(ctx, &tagapp.SaveResourceTagParam{
ResourceCode: db.Code,
ResourceType: consts.TagResourceTypeDb,
ResourceType: tagentity.TagTypeDb,
})
})
}

View File

@@ -1,21 +1,27 @@
package api
import (
"mayfly-go/internal/common/consts"
"mayfly-go/internal/machine/application"
tagapp "mayfly-go/internal/tag/application"
tagentity "mayfly-go/internal/tag/domain/entity"
"mayfly-go/pkg/req"
"mayfly-go/pkg/utils/collx"
)
type Dashbord struct {
TagTreeApp tagapp.TagTree `inject:""`
MachineApp application.Machine `inject:""`
ResourceAuthCertApp tagapp.ResourceAuthCert `inject:""`
MachineApp application.Machine `inject:""`
}
func (m *Dashbord) Dashbord(rc *req.Ctx) {
accountId := rc.GetLoginAccount().Id
machienNum := len(m.TagTreeApp.GetAccountResourceCodes(accountId, consts.TagResourceTypeMachine, ""))
machienAuthCerts := m.ResourceAuthCertApp.GetAccountAuthCert(accountId, tagentity.TagTypeMachineAuthCert)
machineCodes := collx.ArrayMap(machienAuthCerts, func(ac *tagentity.ResourceAuthCert) string {
return ac.ResourceCode
})
machienNum := len(collx.ArrayDeduplicate(machineCodes))
rc.ResData = collx.M{
"machineNum": machienNum,

View File

@@ -1,5 +1,7 @@
package form
import tagentity "mayfly-go/internal/tag/domain/entity"
type MachineForm struct {
Id uint64 `json:"id"`
Protocol int `json:"protocol" binding:"required"`
@@ -8,11 +10,8 @@ type MachineForm struct {
Ip string `json:"ip" binding:"required"` // IP地址
Port int `json:"port" binding:"required"` // 端口号
// 资产授权凭证信息列表
AuthCertId int `json:"authCertId"`
TagId []uint64 `json:"tagId" binding:"required"`
Username string `json:"username"`
Password string `json:"password"`
TagId []uint64 `json:"tagId" binding:"required"`
AuthCerts []*tagentity.ResourceAuthCert // 资产授权凭证信息列表
Remark string `json:"remark"`
SshTunnelMachineId int `json:"sshTunnelMachineId"` // ssh隧道机器id
@@ -24,14 +23,6 @@ type MachineRunForm struct {
Cmd string `json:"cmd" binding:"required"`
}
type MachineFileForm struct {
Id uint64 `json:"id"`
Name string `json:"name" binding:"required"`
MachineId uint64 `json:"machineId" binding:"required"`
Type int `json:"type" binding:"required"`
Path string `json:"path" binding:"required"`
}
type MachineScriptForm struct {
Id uint64 `json:"id"`
Name string `json:"name" binding:"required"`
@@ -42,39 +33,6 @@ type MachineScriptForm struct {
Script string `json:"script" binding:"required"`
}
type ServerFileOptionForm struct {
MachineId uint64 `form:"machineId"`
Protocol int `form:"protocol"`
Path string `form:"path"`
Type string `form:"type"`
Content string `form:"content"`
Id uint64 `form:"id"`
FileId uint64 `form:"fileId"`
}
type MachineFileUpdateForm struct {
Content string `json:"content" binding:"required"`
Id uint64 `json:"id" binding:"required"`
Path string `json:"path" binding:"required"`
}
type MachineFileOpForm struct {
Path []string `json:"path" binding:"required"`
ToPath string `json:"toPath"`
MachineId uint64 `json:"machineId" binding:"required"`
Protocol int `json:"protocol" binding:"required"`
FileId uint64 `json:"fileId" binding:"required"`
}
type MachineFileRename struct {
MachineId uint64 `json:"machineId" binding:"required"`
Protocol int `json:"protocol" binding:"required"`
FileId uint64 `json:"fileId" binding:"required"`
Oldname string `json:"oldname" binding:"required"`
Newname string `json:"newname" binding:"required"`
}
// 授权凭证
type AuthCertForm struct {
Id uint64 `json:"id"`

View File

@@ -0,0 +1,48 @@
package form
import "mayfly-go/internal/machine/application"
type MachineFileForm struct {
Id uint64 `json:"id"`
Name string `json:"name" binding:"required"`
MachineId uint64 `json:"machineId" binding:"required"`
Type int `json:"type" binding:"required"`
Path string `json:"path" binding:"required"`
}
type MachineFileUpdateForm struct {
Content string `json:"content" binding:"required"`
Id uint64 `json:"id" binding:"required"`
Path string `json:"path" binding:"required"`
}
type CreateFileForm struct {
*application.MachineFileOpParam
Type string `json:"type"`
}
type WriteFileContentForm struct {
*application.MachineFileOpParam
Content string `json:"content" binding:"required"`
}
type RemoveFileForm struct {
*application.MachineFileOpParam
Paths []string `json:"paths" binding:"required"`
}
type CopyFileForm struct {
*application.MachineFileOpParam
Paths []string `json:"paths" binding:"required"`
ToPath string `json:"toPath" binding:"required"`
}
type RenameForm struct {
*application.MachineFileOpParam
Newname string `json:"newname" binding:"required"`
}

View File

@@ -3,10 +3,6 @@ package api
import (
"encoding/base64"
"fmt"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
"github.com/may-fly/cast"
"mayfly-go/internal/common/consts"
"mayfly-go/internal/machine/api/form"
"mayfly-go/internal/machine/api/vo"
"mayfly-go/internal/machine/application"
@@ -29,24 +25,33 @@ import (
"strconv"
"strings"
"time"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
"github.com/may-fly/cast"
)
type Machine struct {
MachineApp application.Machine `inject:""`
MachineTermOpApp application.MachineTermOp `inject:""`
TagApp tagapp.TagTree `inject:"TagTreeApp"`
MachineApp application.Machine `inject:""`
MachineTermOpApp application.MachineTermOp `inject:""`
TagApp tagapp.TagTree `inject:"TagTreeApp"`
ResourceAuthCertApp tagapp.ResourceAuthCert `inject:""`
}
func (m *Machine) Machines(rc *req.Ctx) {
condition, pageParam := req.BindQueryAndPage(rc, new(entity.MachineQuery))
// 不存在可访问标签id即没有可操作数据
codes := m.TagApp.GetAccountResourceCodes(rc.GetLoginAccount().Id, consts.TagResourceTypeMachine, condition.TagPath)
if len(codes) == 0 {
authCerts := m.ResourceAuthCertApp.GetAccountAuthCert(rc.GetLoginAccount().Id, tagentity.TagTypeMachineAuthCert, condition.TagPath)
// 不存在可操作的授权凭证,即没有可操作数据
if len(authCerts) == 0 {
rc.ResData = model.EmptyPageResult[any]()
return
}
condition.Codes = codes
machineCodes := collx.ArrayMap(authCerts, func(ac *tagentity.ResourceAuthCert) string {
return ac.ResourceCode
})
condition.Codes = collx.ArrayDeduplicate(machineCodes)
var machinevos []*vo.MachineVO
res, err := m.MachineApp.GetMachineList(condition, pageParam, &machinevos)
@@ -61,6 +66,11 @@ func (m *Machine) Machines(rc *req.Ctx) {
return mvo
})...)
// 填充授权凭证信息
m.ResourceAuthCertApp.FillAuthCert(authCerts, collx.ArrayMap(machinevos, func(mvo *vo.MachineVO) tagentity.IAuthCert {
return mvo
})...)
for _, mv := range machinevos {
if machineStats, err := m.MachineApp.GetMachineStats(mv.Id); err == nil {
mv.Stat = collx.M{
@@ -85,16 +95,20 @@ func (m *Machine) SaveMachine(rc *req.Ctx) {
machineForm := new(form.MachineForm)
me := req.BindJsonAndCopyTo(rc, machineForm, new(entity.Machine))
machineForm.Password = "******"
rc.ReqParam = machineForm
biz.ErrIsNil(m.MachineApp.SaveMachine(rc.MetaCtx, me, machineForm.TagId...))
biz.ErrIsNil(m.MachineApp.SaveMachine(rc.MetaCtx, &application.SaveMachineParam{
Machine: me,
TagIds: machineForm.TagId,
AuthCerts: machineForm.AuthCerts,
}))
}
func (m *Machine) TestConn(rc *req.Ctx) {
me := req.BindJsonAndCopyTo(rc, new(form.MachineForm), new(entity.Machine))
machineForm := new(form.MachineForm)
me := req.BindJsonAndCopyTo(rc, machineForm, new(entity.Machine))
// 测试连接
biz.ErrIsNilAppendErr(m.MachineApp.TestConn(me), "该机器无法连接: %s")
biz.ErrIsNilAppendErr(m.MachineApp.TestConn(me, machineForm.AuthCerts[0]), "该机器无法连接: %s")
}
func (m *Machine) ChangeStatus(rc *req.Ctx) {
@@ -175,7 +189,7 @@ func (m *Machine) WsSSH(g *gin.Context) {
panic(errorx.NewBiz("\033[1;31m您没有权限操作该机器终端,请重新登录后再试~\033[0m"))
}
cli, err := m.MachineApp.NewCli(GetMachineId(rc))
cli, err := m.MachineApp.NewCli(GetMachineAc(rc))
biz.ErrIsNilAppendErr(err, "获取客户端连接失败: %s")
defer cli.Close()
biz.ErrIsNilAppendErr(m.TagApp.CanAccess(rc.GetLoginAccount().Id, cli.Info.TagPath...), "%s")
@@ -231,9 +245,9 @@ func (m *Machine) WsGuacamole(g *gin.Context) {
biz.ErrIsNil(err)
rc := req.NewCtxWithGin(g).WithRequiredPermission(req.NewPermission("machine:terminal"))
machineId := GetMachineId(rc)
ac := GetMachineAc(rc)
mi, err := m.MachineApp.ToMachineInfoById(machineId)
mi, err := m.MachineApp.ToMachineInfoByAc(ac)
if err != nil {
return
}
@@ -258,7 +272,7 @@ func (m *Machine) WsGuacamole(g *gin.Context) {
if mi.EnableRecorder == 1 {
// 操作记录 查看文档https://guacamole.apache.org/doc/gug/configuring-guacamole.html#graphical-recording
params["recording-path"] = fmt.Sprintf("/rdp-rec/%d", machineId)
params["recording-path"] = fmt.Sprintf("/rdp-rec/%s", ac)
params["create-recording-path"] = "true"
params["recording-include-keys"] = "true"
}
@@ -273,14 +287,14 @@ func (m *Machine) WsGuacamole(g *gin.Context) {
if query.Get("force") != "" {
// 判断是否强制连接,是的话,查询是否有正在连接的会话,有的话强制关闭
if cast.ToBool(query.Get("force")) {
tn := sessions.Get(machineId)
tn := sessions.Get(ac)
if tn != nil {
_ = tn.Close()
}
}
}
tunnel, err := guac.DoConnect(query, params, machineId)
tunnel, err := guac.DoConnect(query, params, ac)
if err != nil {
return
}
@@ -290,9 +304,9 @@ func (m *Machine) WsGuacamole(g *gin.Context) {
}
}()
sessions.Add(machineId, wsConn, g.Request, tunnel)
sessions.Add(ac, wsConn, g.Request, tunnel)
defer sessions.Delete(machineId, wsConn, g.Request, tunnel)
defer sessions.Delete(ac, wsConn, g.Request, tunnel)
writer := tunnel.AcquireWriter()
reader := tunnel.AcquireReader()
@@ -312,3 +326,9 @@ func GetMachineId(rc *req.Ctx) uint64 {
biz.IsTrue(machineId != 0, "machineId错误")
return uint64(machineId)
}
func GetMachineAc(rc *req.Ctx) string {
ac := rc.PathParam("ac")
biz.IsTrue(ac != "", "authCertName错误")
return ac
}

View File

@@ -23,9 +23,10 @@ import (
"os"
"path/filepath"
"sort"
"strconv"
"strings"
"sync"
"github.com/may-fly/cast"
)
type MachineFile struct {
@@ -62,8 +63,7 @@ func (m *MachineFile) DeleteFile(rc *req.Ctx) {
/*** sftp相关操作 */
func (m *MachineFile) CreateFile(rc *req.Ctx) {
opForm := req.BindJsonAndValid(rc, new(form.ServerFileOptionForm))
opForm := req.BindJsonAndValid(rc, new(form.CreateFileForm))
path := opForm.Path
attrs := collx.Kvs("path", path)
@@ -71,10 +71,10 @@ func (m *MachineFile) CreateFile(rc *req.Ctx) {
var err error
if opForm.Type == dir {
attrs["type"] = "目录"
mi, err = m.MachineFileApp.MkDir(opForm.FileId, opForm.Path, opForm)
mi, err = m.MachineFileApp.MkDir(opForm.MachineFileOpParam)
} else {
attrs["type"] = "文件"
mi, err = m.MachineFileApp.CreateFile(opForm.FileId, opForm.Path, opForm)
mi, err = m.MachineFileApp.CreateFile(opForm.MachineFileOpParam)
}
attrs["machine"] = mi
rc.ReqParam = attrs
@@ -82,7 +82,7 @@ func (m *MachineFile) CreateFile(rc *req.Ctx) {
}
func (m *MachineFile) ReadFileContent(rc *req.Ctx) {
opForm := req.BindQuery(rc, new(form.ServerFileOptionForm))
opForm := req.BindQuery(rc, new(application.MachineFileOpParam))
readPath := opForm.Path
// 特殊处理rdp文件
if opForm.Protocol == entity.MachineProtocolRdp {
@@ -96,7 +96,7 @@ func (m *MachineFile) ReadFileContent(rc *req.Ctx) {
return
}
sftpFile, mi, err := m.MachineFileApp.ReadFile(opForm.FileId, readPath)
sftpFile, mi, err := m.MachineFileApp.ReadFile(opForm)
rc.ReqParam = collx.Kvs("machine", mi, "path", readPath)
biz.ErrIsNilAppendErr(err, "打开文件失败: %s")
defer sftpFile.Close()
@@ -112,7 +112,7 @@ func (m *MachineFile) ReadFileContent(rc *req.Ctx) {
}
func (m *MachineFile) DownloadFile(rc *req.Ctx) {
opForm := req.BindQuery(rc, new(form.ServerFileOptionForm))
opForm := req.BindQuery(rc, new(application.MachineFileOpParam))
readPath := opForm.Path
@@ -131,7 +131,7 @@ func (m *MachineFile) DownloadFile(rc *req.Ctx) {
return
}
sftpFile, mi, err := m.MachineFileApp.ReadFile(opForm.FileId, readPath)
sftpFile, mi, err := m.MachineFileApp.ReadFile(opForm)
rc.ReqParam = collx.Kvs("machine", mi, "path", readPath)
biz.ErrIsNilAppendErr(err, "打开文件失败: %s")
defer sftpFile.Close()
@@ -140,11 +140,11 @@ func (m *MachineFile) DownloadFile(rc *req.Ctx) {
}
func (m *MachineFile) GetDirEntry(rc *req.Ctx) {
opForm := req.BindQuery(rc, new(form.ServerFileOptionForm))
opForm := req.BindQuery(rc, new(application.MachineFileOpParam))
readPath := opForm.Path
rc.ReqParam = fmt.Sprintf("path: %s", readPath)
fis, err := m.MachineFileApp.ReadDir(opForm.FileId, opForm)
fis, err := m.MachineFileApp.ReadDir(opForm)
biz.ErrIsNilAppendErr(err, "读取目录失败: %s")
fisVO := make([]vo.MachineFileInfo, 0)
@@ -173,34 +173,34 @@ func (m *MachineFile) GetDirEntry(rc *req.Ctx) {
}
func (m *MachineFile) GetDirSize(rc *req.Ctx) {
opForm := req.BindQuery(rc, new(form.ServerFileOptionForm))
opForm := req.BindQuery(rc, new(application.MachineFileOpParam))
size, err := m.MachineFileApp.GetDirSize(opForm.FileId, opForm)
size, err := m.MachineFileApp.GetDirSize(opForm)
biz.ErrIsNil(err)
rc.ResData = size
}
func (m *MachineFile) GetFileStat(rc *req.Ctx) {
opForm := req.BindQuery(rc, new(form.ServerFileOptionForm))
opForm := req.BindQuery(rc, new(application.MachineFileOpParam))
res, err := m.MachineFileApp.FileStat(opForm)
biz.ErrIsNil(err, res)
rc.ResData = res
}
func (m *MachineFile) WriteFileContent(rc *req.Ctx) {
opForm := req.BindQuery(rc, new(form.ServerFileOptionForm))
opForm := req.BindJsonAndValid(rc, new(form.WriteFileContentForm))
path := opForm.Path
mi, err := m.MachineFileApp.WriteFileContent(opForm.FileId, path, []byte(opForm.Content), opForm)
mi, err := m.MachineFileApp.WriteFileContent(opForm.MachineFileOpParam, []byte(opForm.Content))
rc.ReqParam = collx.Kvs("machine", mi, "path", path)
biz.ErrIsNilAppendErr(err, "打开文件失败: %s")
}
func (m *MachineFile) UploadFile(rc *req.Ctx) {
fid := GetMachineFileId(rc)
path := rc.PostForm("path")
protocol, err := strconv.Atoi(rc.PostForm("protocol"))
machineId, err := strconv.Atoi(rc.PostForm("machineId"))
protocol := cast.ToInt(rc.PostForm("protocol"))
machineId := cast.ToUint64(rc.PostForm("machineId"))
authCertName := rc.PostForm("authCertName")
fileheader, err := rc.FormFile("file")
biz.ErrIsNilAppendErr(err, "读取文件失败: %s")
@@ -219,14 +219,14 @@ func (m *MachineFile) UploadFile(rc *req.Ctx) {
}
}()
opForm := &form.ServerFileOptionForm{
FileId: fid,
MachineId: uint64(machineId),
Protocol: protocol,
Path: path,
opForm := &application.MachineFileOpParam{
MachineId: machineId,
AuthCertName: authCertName,
Protocol: protocol,
Path: path,
}
mi, err := m.MachineFileApp.UploadFile(fid, path, fileheader.Filename, file, opForm)
mi, err := m.MachineFileApp.UploadFile(opForm, fileheader.Filename, file)
rc.ReqParam = collx.Kvs("machine", mi, "path", fmt.Sprintf("%s/%s", path, fileheader.Filename))
biz.ErrIsNilAppendErr(err, "创建文件失败: %s")
// 保存消息并发送文件上传成功通知
@@ -239,8 +239,6 @@ type FolderFile struct {
}
func (m *MachineFile) UploadFolder(rc *req.Ctx) {
fid := GetMachineFileId(rc)
mf, err := rc.MultipartForm()
biz.ErrIsNilAppendErr(err, "获取表单信息失败: %s")
basePath := mf.Value["basePath"][0]
@@ -256,21 +254,24 @@ func (m *MachineFile) UploadFolder(rc *req.Ctx) {
biz.IsTrue(allFileSize <= maxUploadFileSize, "文件夹总大小不能超过%d字节", maxUploadFileSize)
paths := mf.Value["paths"]
authCertName := mf.Value["authCertName"][0]
machineId := cast.ToUint64(mf.Value["machineId"][0])
// protocol
protocol, err := strconv.Atoi(mf.Value["protocol"][0])
protocol := cast.ToInt(mf.Value["protocol"][0])
opForm := &application.MachineFileOpParam{
MachineId: machineId,
Protocol: protocol,
AuthCertName: authCertName,
}
if protocol == entity.MachineProtocolRdp {
machineId, _ := strconv.Atoi(mf.Value["machineId"][0])
opForm := &form.ServerFileOptionForm{
MachineId: uint64(machineId),
Protocol: protocol,
}
m.MachineFileApp.UploadFiles(basePath, fileheaders, paths, opForm)
m.MachineFileApp.UploadFiles(opForm, basePath, fileheaders, paths)
return
}
folderName := filepath.Dir(paths[0])
mcli, err := m.MachineFileApp.GetMachineCli(fid, basePath+"/"+folderName)
mcli, err := m.MachineFileApp.GetMachineCli(authCertName)
biz.ErrIsNil(err)
mi := mcli.Info
@@ -344,30 +345,30 @@ func (m *MachineFile) UploadFolder(rc *req.Ctx) {
}
func (m *MachineFile) RemoveFile(rc *req.Ctx) {
opForm := req.BindJsonAndValid(rc, new(form.MachineFileOpForm))
opForm := req.BindJsonAndValid(rc, new(form.RemoveFileForm))
mi, err := m.MachineFileApp.RemoveFile(opForm)
mi, err := m.MachineFileApp.RemoveFile(opForm.MachineFileOpParam, opForm.Paths...)
rc.ReqParam = collx.Kvs("machine", mi, "path", opForm)
biz.ErrIsNilAppendErr(err, "删除文件失败: %s")
}
func (m *MachineFile) CopyFile(rc *req.Ctx) {
opForm := req.BindJsonAndValid(rc, new(form.MachineFileOpForm))
mi, err := m.MachineFileApp.Copy(opForm)
opForm := req.BindJsonAndValid(rc, new(form.CopyFileForm))
mi, err := m.MachineFileApp.Copy(opForm.MachineFileOpParam, opForm.ToPath, opForm.Paths...)
biz.ErrIsNilAppendErr(err, "文件拷贝失败: %s")
rc.ReqParam = collx.Kvs("machine", mi, "cp", opForm)
}
func (m *MachineFile) MvFile(rc *req.Ctx) {
opForm := req.BindJsonAndValid(rc, new(form.MachineFileOpForm))
mi, err := m.MachineFileApp.Mv(opForm)
opForm := req.BindJsonAndValid(rc, new(form.CopyFileForm))
mi, err := m.MachineFileApp.Mv(opForm.MachineFileOpParam, opForm.ToPath, opForm.Paths...)
rc.ReqParam = collx.Kvs("machine", mi, "mv", opForm)
biz.ErrIsNilAppendErr(err, "文件移动失败: %s")
}
func (m *MachineFile) Rename(rc *req.Ctx) {
renameForm := req.BindJsonAndValid(rc, new(form.MachineFileRename))
mi, err := m.MachineFileApp.Rename(renameForm)
renameForm := req.BindJsonAndValid(rc, new(form.RenameForm))
mi, err := m.MachineFileApp.Rename(renameForm.MachineFileOpParam, renameForm.Newname)
rc.ReqParam = collx.Kvs("machine", mi, "rename", renameForm)
biz.ErrIsNilAppendErr(err, "文件重命名失败: %s")
}

View File

@@ -13,7 +13,8 @@ type AuthCertBaseVO struct {
}
type MachineVO struct {
tagentity.ResourceTags
tagentity.ResourceTags // 标签信息
tagentity.AuthCerts // 授权凭证信息
Id uint64 `json:"id"`
Code string `json:"code"`
@@ -21,8 +22,6 @@ type MachineVO struct {
Protocol int `json:"protocol"`
Ip string `json:"ip"`
Port int `json:"port"`
Username string `json:"username"`
AuthCertId int `json:"authCertId"`
Status *int8 `json:"status"`
SshTunnelMachineId int `json:"sshTunnelMachineId"` // ssh隧道机器id
CreateTime *time.Time `json:"createTime"`
@@ -33,8 +32,6 @@ type MachineVO struct {
ModifierId *int64 `json:"modifierId"`
Remark *string `json:"remark"`
EnableRecorder int8 `json:"enableRecorder"`
// TagId uint64 `json:"tagId"`
// TagPath string `json:"tagPath"`
Stat map[string]any `json:"stat" gorm:"-"`
}

View File

@@ -10,6 +10,7 @@ import (
"mayfly-go/internal/machine/infrastructure/cache"
"mayfly-go/internal/machine/mcm"
tagapp "mayfly-go/internal/tag/application"
tagentity "mayfly-go/internal/tag/domain/entity"
"mayfly-go/pkg/base"
"mayfly-go/pkg/errorx"
"mayfly-go/pkg/global"
@@ -17,15 +18,23 @@ import (
"mayfly-go/pkg/model"
"mayfly-go/pkg/scheduler"
"time"
"github.com/may-fly/cast"
)
type SaveMachineParam struct {
Machine *entity.Machine
TagIds []uint64
AuthCerts []*tagentity.ResourceAuthCert
}
type Machine interface {
base.App[*entity.Machine]
SaveMachine(ctx context.Context, m *entity.Machine, tagIds ...uint64) error
SaveMachine(ctx context.Context, param *SaveMachineParam) error
// 测试机器连接
TestConn(me *entity.Machine) error
TestConn(me *entity.Machine, authCert *tagentity.ResourceAuthCert) error
// 调整机器状态
ChangeStatus(ctx context.Context, id uint64, status int8) error
@@ -36,11 +45,14 @@ type Machine interface {
GetMachineList(condition *entity.MachineQuery, pageParam *model.PageParam, toEntity *[]*vo.MachineVO, orderBy ...string) (*model.PageResult[*[]*vo.MachineVO], error)
// 新建机器客户端连接需手动调用Close
NewCli(id uint64) (*mcm.Cli, error)
NewCli(authCertName string) (*mcm.Cli, error)
// 获取已缓存的机器连接,若不存在则新建客户端连接并缓存,主要用于定时获取状态等(避免频繁创建连接)
GetCli(id uint64) (*mcm.Cli, error)
// 根据授权凭证获取客户端连接
GetCliByAc(authCertName string) (*mcm.Cli, error)
// 获取ssh隧道机器连接
GetSshTunnelMachine(id int) (*mcm.SshTunnelMachine, error)
@@ -50,14 +62,15 @@ type Machine interface {
// 获取机器运行时状态信息
GetMachineStats(machineId uint64) (*mcm.Stats, error)
ToMachineInfoById(machineId uint64) (*mcm.MachineInfo, error)
ToMachineInfoByAc(ac string) (*mcm.MachineInfo, error)
}
type machineAppImpl struct {
base.AppImpl[*entity.Machine, repository.Machine]
authCertApp AuthCert `inject:"AuthCertApp"`
tagApp tagapp.TagTree `inject:"TagTreeApp"`
// authCertApp AuthCert `inject:"AuthCertApp"`
tagApp tagapp.TagTree `inject:"TagTreeApp"`
resourceAuthCertApp tagapp.ResourceAuthCert `inject:"ResourceAuthCertApp"`
}
// 注入MachineRepo
@@ -70,19 +83,21 @@ func (m *machineAppImpl) GetMachineList(condition *entity.MachineQuery, pagePara
return m.GetRepo().GetMachineList(condition, pageParam, toEntity, orderBy...)
}
func (m *machineAppImpl) SaveMachine(ctx context.Context, me *entity.Machine, tagIds ...uint64) error {
func (m *machineAppImpl) SaveMachine(ctx context.Context, param *SaveMachineParam) error {
me := param.Machine
tagIds := param.TagIds
authCerts := param.AuthCerts
resourceType := tagentity.TagTypeMachine
authCertTagType := tagentity.TagTypeMachineAuthCert
oldMachine := &entity.Machine{
Ip: me.Ip,
Port: me.Port,
Username: me.Username,
SshTunnelMachineId: me.SshTunnelMachineId,
}
err := m.GetBy(oldMachine)
if errEnc := me.PwdEncrypt(); errEnc != nil {
return errorx.NewBiz(errEnc.Error())
}
if me.Id == 0 {
if err == nil {
return errorx.NewBiz("该机器信息已存在")
@@ -94,14 +109,23 @@ func (m *machineAppImpl) SaveMachine(ctx context.Context, me *entity.Machine, ta
// 新增机器,默认启用状态
me.Status = entity.MachineStatusEnable
return m.Tx(ctx, func(ctx context.Context) error {
if err := m.Tx(ctx, func(ctx context.Context) error {
return m.Insert(ctx, me)
}, func(ctx context.Context) error {
return m.tagApp.SaveResource(ctx, &tagapp.SaveResourceTagParam{
ResourceCode: me.Code,
ResourceType: consts.TagResourceTypeMachine,
ResourceType: resourceType,
TagIds: tagIds,
})
}); err != nil {
return err
}
return m.resourceAuthCertApp.SaveAuthCert(ctx, &tagapp.SaveAuthCertParam{
ResourceCode: me.Code,
ResourceType: resourceType,
AuthCertTagType: authCertTagType,
AuthCerts: authCerts,
})
}
@@ -118,20 +142,29 @@ func (m *machineAppImpl) SaveMachine(ctx context.Context, me *entity.Machine, ta
mcm.DeleteCli(me.Id)
// 防止误传修改
me.Code = ""
return m.Tx(ctx, func(ctx context.Context) error {
if err := m.Tx(ctx, func(ctx context.Context) error {
return m.UpdateById(ctx, me)
}, func(ctx context.Context) error {
return m.tagApp.SaveResource(ctx, &tagapp.SaveResourceTagParam{
ResourceCode: oldMachine.Code,
ResourceType: consts.TagResourceTypeMachine,
ResourceType: resourceType,
TagIds: tagIds,
})
}); err != nil {
return err
}
return m.resourceAuthCertApp.SaveAuthCert(ctx, &tagapp.SaveAuthCertParam{
ResourceCode: oldMachine.Code,
ResourceType: resourceType,
AuthCertTagType: authCertTagType,
AuthCerts: authCerts,
})
}
func (m *machineAppImpl) TestConn(me *entity.Machine) error {
func (m *machineAppImpl) TestConn(me *entity.Machine, authCert *tagentity.ResourceAuthCert) error {
me.Id = 0
mi, err := m.toMachineInfo(me)
mi, err := m.toMi(me, authCert)
if err != nil {
return err
}
@@ -165,31 +198,51 @@ func (m *machineAppImpl) Delete(ctx context.Context, id uint64) error {
// 发布机器删除事件
global.EventBus.Publish(ctx, consts.DeleteMachineEventTopic, machine)
resourceType := tagentity.TagTypeMachine
return m.Tx(ctx,
func(ctx context.Context) error {
return m.DeleteById(ctx, id)
}, func(ctx context.Context) error {
return m.tagApp.SaveResource(ctx, &tagapp.SaveResourceTagParam{
ResourceCode: machine.Code,
ResourceType: consts.TagResourceTypeMachine,
ResourceType: resourceType,
})
}, func(ctx context.Context) error {
return m.resourceAuthCertApp.SaveAuthCert(ctx, &tagapp.SaveAuthCertParam{
ResourceCode: machine.Code,
ResourceType: resourceType,
})
})
}
func (m *machineAppImpl) NewCli(machineId uint64) (*mcm.Cli, error) {
if mi, err := m.ToMachineInfoById(machineId); err != nil {
func (m *machineAppImpl) NewCli(authCertName string) (*mcm.Cli, error) {
if mi, err := m.ToMachineInfoByAc(authCertName); err != nil {
return nil, err
} else {
return mi.Conn()
}
}
func (m *machineAppImpl) GetCli(machineId uint64) (*mcm.Cli, error) {
return mcm.GetMachineCli(machineId, func(mid uint64) (*mcm.MachineInfo, error) {
return m.ToMachineInfoById(mid)
func (m *machineAppImpl) GetCliByAc(authCertName string) (*mcm.Cli, error) {
return mcm.GetMachineCli(authCertName, func(ac string) (*mcm.MachineInfo, error) {
return m.ToMachineInfoByAc(ac)
})
}
func (m *machineAppImpl) GetCli(machineId uint64) (*mcm.Cli, error) {
cli, err := mcm.GetMachineCliById(machineId)
if err == nil {
return cli, nil
}
_, authCert, err := m.getMachineAndAuthCert(machineId)
if err != nil {
return nil, err
}
return m.GetCliByAc(authCert.Name)
}
func (m *machineAppImpl) GetSshTunnelMachine(machineId int) (*mcm.SshTunnelMachine, error) {
return mcm.GetSshTunnelMachine(machineId, func(mid uint64) (*mcm.MachineInfo, error) {
return m.ToMachineInfoById(mid)
@@ -229,62 +282,68 @@ func (m *machineAppImpl) GetMachineStats(machineId uint64) (*mcm.Stats, error) {
return cache.GetMachineStats(machineId)
}
// 生成机器信息根据授权凭证id填充用户密码等
func (m *machineAppImpl) ToMachineInfoById(machineId uint64) (*mcm.MachineInfo, error) {
me, err := m.GetById(new(entity.Machine), machineId)
// 根据授权凭证,生成机器信息
func (m *machineAppImpl) ToMachineInfoByAc(authCertName string) (*mcm.MachineInfo, error) {
authCert, err := m.resourceAuthCertApp.GetAuthCert(authCertName)
if err != nil {
return nil, errorx.NewBiz("机器信息不存在")
}
if me.Status != entity.MachineStatusEnable && me.Protocol == 1 {
return nil, errorx.NewBiz("该机器已被停用")
return nil, err
}
if mi, err := m.toMachineInfo(me); err != nil {
return nil, err
} else {
return mi, nil
machine := &entity.Machine{
Code: authCert.ResourceCode,
}
if err := m.GetBy(machine); err != nil {
return nil, errorx.NewBiz("该授权凭证关联的机器信息不存在")
}
return m.toMi(machine, authCert)
}
func (m *machineAppImpl) toMachineInfo(me *entity.Machine) (*mcm.MachineInfo, error) {
// 生成机器信息根据授权凭证id填充用户密码等
func (m *machineAppImpl) ToMachineInfoById(machineId uint64) (*mcm.MachineInfo, error) {
me, authCert, err := m.getMachineAndAuthCert(machineId)
if err != nil {
return nil, err
}
return m.toMi(me, authCert)
}
func (m *machineAppImpl) getMachineAndAuthCert(machineId uint64) (*entity.Machine, *tagentity.ResourceAuthCert, error) {
me, err := m.GetById(new(entity.Machine), machineId)
if err != nil {
return nil, nil, errorx.NewBiz("[%d]机器信息不存在", machineId)
}
if me.Status != entity.MachineStatusEnable && me.Protocol == 1 {
return nil, nil, errorx.NewBiz("[%s]该机器已被停用", me.Code)
}
authCert, err := m.resourceAuthCertApp.GetResourceAuthCert(tagentity.TagTypeMachine, me.Code)
if err != nil {
return nil, nil, err
}
return me, authCert, nil
}
func (m *machineAppImpl) toMi(me *entity.Machine, authCert *tagentity.ResourceAuthCert) (*mcm.MachineInfo, error) {
mi := new(mcm.MachineInfo)
mi.Id = me.Id
mi.Name = me.Name
mi.Ip = me.Ip
mi.Port = me.Port
mi.Username = me.Username
mi.TagPath = m.tagApp.ListTagPathByResource(consts.TagResourceTypeMachine, me.Code)
mi.TagPath = m.tagApp.ListTagPathByResource(int8(tagentity.TagTypeMachineAuthCert), authCert.Name)
mi.EnableRecorder = me.EnableRecorder
mi.Protocol = me.Protocol
if me.UseAuthCert() {
ac, err := m.authCertApp.GetById(new(entity.AuthCert), uint64(me.AuthCertId))
if err != nil {
return nil, errorx.NewBiz("授权凭证信息已不存在,请重新关联")
}
mi.AuthMethod = ac.AuthMethod
if err := ac.PwdDecrypt(); err != nil {
return nil, errorx.NewBiz(err.Error())
}
mi.Password = ac.Password
mi.Passphrase = ac.Passphrase
} else {
mi.AuthMethod = entity.AuthCertAuthMethodPassword
if me.Id != 0 {
if err := me.PwdDecrypt(); err != nil {
return nil, errorx.NewBiz(err.Error())
}
}
mi.Password = me.Password
}
mi.Username = authCert.Username
mi.Password = authCert.Ciphertext
mi.Passphrase = cast.ToString(authCert.Extra["passphrase"])
mi.AuthMethod = int8(authCert.CiphertextType)
// 使用了ssh隧道则将隧道机器信息也附上
if me.SshTunnelMachineId > 0 {
sshTunnelMe, err := m.GetById(new(entity.Machine), uint64(me.SshTunnelMachineId))
if err != nil {
return nil, errorx.NewBiz("隧道机器信息不存在")
}
sshTunnelMi, err := m.toMachineInfo(sshTunnelMe)
sshTunnelMi, err := m.ToMachineInfoById(uint64(me.SshTunnelMachineId))
if err != nil {
return nil, err
}

View File

@@ -6,8 +6,6 @@ import (
"fmt"
"io"
"io/fs"
"io/ioutil"
"mayfly-go/internal/machine/api/form"
"mayfly-go/internal/machine/config"
"mayfly-go/internal/machine/domain/entity"
"mayfly-go/internal/machine/domain/repository"
@@ -17,6 +15,7 @@ import (
"mayfly-go/pkg/logx"
"mayfly-go/pkg/model"
"mayfly-go/pkg/utils/bytex"
"mayfly-go/pkg/utils/collx"
"mime/multipart"
"os"
"path/filepath"
@@ -25,6 +24,13 @@ import (
"github.com/pkg/sftp"
)
type MachineFileOpParam struct {
MachineId uint64 `json:"machineId" binding:"required" form:"machineId"`
Protocol int `json:"protocol" binding:"required" form:"protocol"`
AuthCertName string `json:"authCertName" binding:"required" form:"authCertName"` // 授权凭证
Path string `json:"path" form:"path"` // 文件路径
}
type MachineFile interface {
base.App[*entity.MachineFile]
@@ -36,50 +42,47 @@ type MachineFile interface {
Save(ctx context.Context, entity *entity.MachineFile) error
// 获取文件关联的机器信息,主要用于记录日志使用
// GetMachine(fileId uint64) *mcm.Info
// 检查文件路径并返回机器id
GetMachineCli(fileId uint64, path ...string) (*mcm.Cli, error)
// 获取机器cli
GetMachineCli(authCertName string) (*mcm.Cli, error)
GetRdpFilePath(MachineId uint64, path string) string
/** sftp 相关操作 **/
// 创建目录
MkDir(fid uint64, path string, opForm *form.ServerFileOptionForm) (*mcm.MachineInfo, error)
MkDir(opParam *MachineFileOpParam) (*mcm.MachineInfo, error)
// 创建文件
CreateFile(fid uint64, path string, opForm *form.ServerFileOptionForm) (*mcm.MachineInfo, error)
CreateFile(opParam *MachineFileOpParam) (*mcm.MachineInfo, error)
// 读取目录
ReadDir(fid uint64, opForm *form.ServerFileOptionForm) ([]fs.FileInfo, error)
ReadDir(opParam *MachineFileOpParam) ([]fs.FileInfo, error)
// 获取指定目录内容大小
GetDirSize(fid uint64, opForm *form.ServerFileOptionForm) (string, error)
GetDirSize(opParam *MachineFileOpParam) (string, error)
// 获取文件stat
FileStat(opForm *form.ServerFileOptionForm) (string, error)
FileStat(opParam *MachineFileOpParam) (string, error)
// 读取文件内容
ReadFile(fileId uint64, path string) (*sftp.File, *mcm.MachineInfo, error)
ReadFile(opParam *MachineFileOpParam) (*sftp.File, *mcm.MachineInfo, error)
// 写文件
WriteFileContent(fileId uint64, path string, content []byte, opForm *form.ServerFileOptionForm) (*mcm.MachineInfo, error)
WriteFileContent(opParam *MachineFileOpParam, content []byte) (*mcm.MachineInfo, error)
// 文件上传
UploadFile(fileId uint64, path, filename string, reader io.Reader, opForm *form.ServerFileOptionForm) (*mcm.MachineInfo, error)
UploadFile(opParam *MachineFileOpParam, filename string, reader io.Reader) (*mcm.MachineInfo, error)
UploadFiles(basePath string, fileHeaders []*multipart.FileHeader, paths []string, opForm *form.ServerFileOptionForm) (*mcm.MachineInfo, error)
UploadFiles(opParam *MachineFileOpParam, basePath string, fileHeaders []*multipart.FileHeader, paths []string) (*mcm.MachineInfo, error)
// 移除文件
RemoveFile(opForm *form.MachineFileOpForm) (*mcm.MachineInfo, error)
RemoveFile(opParam *MachineFileOpParam, path ...string) (*mcm.MachineInfo, error)
Copy(opForm *form.MachineFileOpForm) (*mcm.MachineInfo, error)
Copy(opParam *MachineFileOpParam, toPath string, path ...string) (*mcm.MachineInfo, error)
Mv(opForm *form.MachineFileOpForm) (*mcm.MachineInfo, error)
Mv(opParam *MachineFileOpParam, toPath string, path ...string) (*mcm.MachineInfo, error)
Rename(renameForm *form.MachineFileRename) (*mcm.MachineInfo, error)
Rename(opParam *MachineFileOpParam, newname string) (*mcm.MachineInfo, error)
}
type machineFileAppImpl struct {
@@ -117,29 +120,37 @@ func (m *machineFileAppImpl) Save(ctx context.Context, mf *entity.MachineFile) e
return m.Insert(ctx, mf)
}
func (m *machineFileAppImpl) ReadDir(fid uint64, opForm *form.ServerFileOptionForm) ([]fs.FileInfo, error) {
if !strings.HasSuffix(opForm.Path, "/") {
opForm.Path = opForm.Path + "/"
func (m *machineFileAppImpl) ReadDir(opParam *MachineFileOpParam) ([]fs.FileInfo, error) {
path := opParam.Path
if !strings.HasSuffix(path, "/") {
path = path + "/"
}
// 如果是rdp则直接读取本地文件
if opForm.Protocol == entity.MachineProtocolRdp {
opForm.Path = m.GetRdpFilePath(opForm.MachineId, opForm.Path)
return ioutil.ReadDir(opForm.Path)
if opParam.Protocol == entity.MachineProtocolRdp {
path = m.GetRdpFilePath(opParam.MachineId, path)
dirs, err := os.ReadDir(path)
if err != nil {
return nil, err
}
return collx.ArrayMap[fs.DirEntry, fs.FileInfo](dirs, func(val fs.DirEntry) fs.FileInfo {
fi, _ := val.Info()
return fi
}), nil
}
_, sftpCli, err := m.GetMachineSftpCli(fid, opForm.Path)
_, sftpCli, err := m.GetMachineSftpCli(opParam)
if err != nil {
return nil, err
}
return sftpCli.ReadDir(opForm.Path)
return sftpCli.ReadDir(path)
}
func (m *machineFileAppImpl) GetDirSize(fid uint64, opForm *form.ServerFileOptionForm) (string, error) {
path := opForm.Path
func (m *machineFileAppImpl) GetDirSize(opParam *MachineFileOpParam) (string, error) {
path := opParam.Path
if opForm.Protocol == entity.MachineProtocolRdp {
dirPath := m.GetRdpFilePath(opForm.MachineId, path)
if opParam.Protocol == entity.MachineProtocolRdp {
dirPath := m.GetRdpFilePath(opParam.MachineId, path)
// 递归计算目录下文件大小
var totalSize int64
@@ -160,7 +171,7 @@ func (m *machineFileAppImpl) GetDirSize(fid uint64, opForm *form.ServerFileOptio
return bytex.FormatSize(totalSize), nil
}
mcli, err := m.GetMachineCli(fid, path)
mcli, err := m.GetMachineCli(opParam.AuthCertName)
if err != nil {
return "", err
}
@@ -184,32 +195,34 @@ func (m *machineFileAppImpl) GetDirSize(fid uint64, opForm *form.ServerFileOptio
return strings.Split(res, "\t")[0], nil
}
func (m *machineFileAppImpl) FileStat(opForm *form.ServerFileOptionForm) (string, error) {
if opForm.Protocol == entity.MachineProtocolRdp {
path := m.GetRdpFilePath(opForm.MachineId, opForm.Path)
func (m *machineFileAppImpl) FileStat(opParam *MachineFileOpParam) (string, error) {
path := opParam.Path
if opParam.Protocol == entity.MachineProtocolRdp {
path = m.GetRdpFilePath(opParam.MachineId, path)
stat, err := os.Stat(path)
return fmt.Sprintf("%v", stat), err
}
mcli, err := m.GetMachineCli(opForm.FileId, opForm.Path)
mcli, err := m.GetMachineCli(opParam.AuthCertName)
if err != nil {
return "", err
}
return mcli.Run(fmt.Sprintf("stat -L %s", opForm.Path))
return mcli.Run(fmt.Sprintf("stat -L %s", path))
}
func (m *machineFileAppImpl) MkDir(fid uint64, path string, opForm *form.ServerFileOptionForm) (*mcm.MachineInfo, error) {
func (m *machineFileAppImpl) MkDir(opParam *MachineFileOpParam) (*mcm.MachineInfo, error) {
path := opParam.Path
if !strings.HasSuffix(path, "/") {
path = path + "/"
}
if opForm.Protocol == entity.MachineProtocolRdp {
path = m.GetRdpFilePath(opForm.MachineId, path)
if opParam.Protocol == entity.MachineProtocolRdp {
path = m.GetRdpFilePath(opParam.MachineId, path)
os.MkdirAll(path, os.ModePerm)
return nil, nil
}
mi, sftpCli, err := m.GetMachineSftpCli(fid, path)
mi, sftpCli, err := m.GetMachineSftpCli(opParam)
if err != nil {
return nil, err
}
@@ -218,14 +231,15 @@ func (m *machineFileAppImpl) MkDir(fid uint64, path string, opForm *form.ServerF
return mi, err
}
func (m *machineFileAppImpl) CreateFile(fid uint64, path string, opForm *form.ServerFileOptionForm) (*mcm.MachineInfo, error) {
mi, sftpCli, err := m.GetMachineSftpCli(fid, path)
func (m *machineFileAppImpl) CreateFile(opParam *MachineFileOpParam) (*mcm.MachineInfo, error) {
mi, sftpCli, err := m.GetMachineSftpCli(opParam)
if err != nil {
return nil, err
}
if opForm.Protocol == entity.MachineProtocolRdp {
path = m.GetRdpFilePath(opForm.MachineId, path)
path := opParam.Path
if opParam.Protocol == entity.MachineProtocolRdp {
path = m.GetRdpFilePath(opParam.MachineId, path)
file, err := os.Create(path)
defer file.Close()
return nil, err
@@ -239,22 +253,22 @@ func (m *machineFileAppImpl) CreateFile(fid uint64, path string, opForm *form.Se
return mi, err
}
func (m *machineFileAppImpl) ReadFile(fileId uint64, path string) (*sftp.File, *mcm.MachineInfo, error) {
mi, sftpCli, err := m.GetMachineSftpCli(fileId, path)
func (m *machineFileAppImpl) ReadFile(opParam *MachineFileOpParam) (*sftp.File, *mcm.MachineInfo, error) {
mi, sftpCli, err := m.GetMachineSftpCli(opParam)
if err != nil {
return nil, nil, err
}
// 读取文件内容
fc, err := sftpCli.Open(path)
fc, err := sftpCli.Open(opParam.Path)
return fc, mi, err
}
// 写文件内容
func (m *machineFileAppImpl) WriteFileContent(fileId uint64, path string, content []byte, opForm *form.ServerFileOptionForm) (*mcm.MachineInfo, error) {
if opForm.Protocol == entity.MachineProtocolRdp {
path = m.GetRdpFilePath(opForm.MachineId, path)
func (m *machineFileAppImpl) WriteFileContent(opParam *MachineFileOpParam, content []byte) (*mcm.MachineInfo, error) {
path := opParam.Path
if opParam.Protocol == entity.MachineProtocolRdp {
path = m.GetRdpFilePath(opParam.MachineId, path)
file, err := os.Create(path)
defer file.Close()
if err != nil {
@@ -264,7 +278,7 @@ func (m *machineFileAppImpl) WriteFileContent(fileId uint64, path string, conten
return nil, err
}
mi, sftpCli, err := m.GetMachineSftpCli(fileId, path)
mi, sftpCli, err := m.GetMachineSftpCli(opParam)
if err != nil {
return nil, err
}
@@ -279,13 +293,14 @@ func (m *machineFileAppImpl) WriteFileContent(fileId uint64, path string, conten
}
// 上传文件
func (m *machineFileAppImpl) UploadFile(fileId uint64, path, filename string, reader io.Reader, opForm *form.ServerFileOptionForm) (*mcm.MachineInfo, error) {
func (m *machineFileAppImpl) UploadFile(opParam *MachineFileOpParam, filename string, reader io.Reader) (*mcm.MachineInfo, error) {
path := opParam.Path
if !strings.HasSuffix(path, "/") {
path = path + "/"
}
if opForm.Protocol == entity.MachineProtocolRdp {
path = m.GetRdpFilePath(opForm.MachineId, path)
if opParam.Protocol == entity.MachineProtocolRdp {
path = m.GetRdpFilePath(opParam.MachineId, path)
file, err := os.Create(path + filename)
defer file.Close()
if err != nil {
@@ -295,7 +310,7 @@ func (m *machineFileAppImpl) UploadFile(fileId uint64, path, filename string, re
return nil, nil
}
mi, sftpCli, err := m.GetMachineSftpCli(fileId, path)
mi, sftpCli, err := m.GetMachineSftpCli(opParam)
if err != nil {
return nil, err
}
@@ -309,9 +324,9 @@ func (m *machineFileAppImpl) UploadFile(fileId uint64, path, filename string, re
return mi, err
}
func (m *machineFileAppImpl) UploadFiles(basePath string, fileHeaders []*multipart.FileHeader, paths []string, opForm *form.ServerFileOptionForm) (*mcm.MachineInfo, error) {
if opForm.Protocol == entity.MachineProtocolRdp {
baseFolder := m.GetRdpFilePath(opForm.MachineId, basePath)
func (m *machineFileAppImpl) UploadFiles(opParam *MachineFileOpParam, basePath string, fileHeaders []*multipart.FileHeader, paths []string) (*mcm.MachineInfo, error) {
if opParam.Protocol == entity.MachineProtocolRdp {
baseFolder := m.GetRdpFilePath(opParam.MachineId, basePath)
for i, fileHeader := range fileHeaders {
file, err := fileHeader.Open()
@@ -326,7 +341,11 @@ func (m *machineFileAppImpl) UploadFiles(basePath string, fileHeaders []*multipa
rdpBaseDir = rdpBaseDir + "/"
}
rdpDir := filepath.Dir(rdpBaseDir + paths[i])
m.MkDir(0, rdpDir, opForm)
m.MkDir(&MachineFileOpParam{
MachineId: opParam.MachineId,
Protocol: opParam.Protocol,
Path: rdpDir,
})
// 创建文件
if !strings.HasSuffix(baseFolder, "/") {
@@ -348,24 +367,23 @@ func (m *machineFileAppImpl) UploadFiles(basePath string, fileHeaders []*multipa
}
// 删除文件
func (m *machineFileAppImpl) RemoveFile(opForm *form.MachineFileOpForm) (*mcm.MachineInfo, error) {
if opForm.Protocol == entity.MachineProtocolRdp {
for _, pt := range opForm.Path {
pt = m.GetRdpFilePath(opForm.MachineId, pt)
func (m *machineFileAppImpl) RemoveFile(opParam *MachineFileOpParam, path ...string) (*mcm.MachineInfo, error) {
if opParam.Protocol == entity.MachineProtocolRdp {
for _, pt := range path {
pt = m.GetRdpFilePath(opParam.MachineId, pt)
os.RemoveAll(pt)
}
return nil, nil
}
mcli, err := m.GetMachineCli(opForm.FileId, opForm.Path...)
mcli, err := m.GetMachineCli(opParam.AuthCertName)
if err != nil {
return nil, err
}
minfo := mcli.Info
// 优先使用命令删除速度快sftp需要递归遍历删除子文件等
res, err := mcli.Run(fmt.Sprintf("rm -rf %s", strings.Join(opForm.Path, " ")))
res, err := mcli.Run(fmt.Sprintf("rm -rf %s", strings.Join(path, " ")))
if err == nil {
return minfo, nil
}
@@ -376,7 +394,7 @@ func (m *machineFileAppImpl) RemoveFile(opForm *form.MachineFileOpForm) (*mcm.Ma
return minfo, err
}
for _, p := range opForm.Path {
for _, p := range path {
err = sftpCli.RemoveAll(p)
if err != nil {
break
@@ -385,11 +403,11 @@ func (m *machineFileAppImpl) RemoveFile(opForm *form.MachineFileOpForm) (*mcm.Ma
return minfo, err
}
func (m *machineFileAppImpl) Copy(opForm *form.MachineFileOpForm) (*mcm.MachineInfo, error) {
if opForm.Protocol == entity.MachineProtocolRdp {
for _, pt := range opForm.Path {
srcPath := m.GetRdpFilePath(opForm.MachineId, pt)
targetPath := m.GetRdpFilePath(opForm.MachineId, opForm.ToPath+pt)
func (m *machineFileAppImpl) Copy(opParam *MachineFileOpParam, toPath string, path ...string) (*mcm.MachineInfo, error) {
if opParam.Protocol == entity.MachineProtocolRdp {
for _, pt := range path {
srcPath := m.GetRdpFilePath(opParam.MachineId, pt)
targetPath := m.GetRdpFilePath(opParam.MachineId, toPath+pt)
// 打开源文件
srcFile, err := os.Open(srcPath)
@@ -408,59 +426,57 @@ func (m *machineFileAppImpl) Copy(opForm *form.MachineFileOpForm) (*mcm.MachineI
return nil, nil
}
mcli, err := m.GetMachineCli(opForm.FileId, opForm.Path...)
mcli, err := m.GetMachineCli(opParam.AuthCertName)
if err != nil {
return nil, err
}
mi := mcli.Info
res, err := mcli.Run(fmt.Sprintf("cp -r %s %s", strings.Join(opForm.Path, " "), opForm.ToPath))
res, err := mcli.Run(fmt.Sprintf("cp -r %s %s", strings.Join(path, " "), toPath))
if err != nil {
return mi, errors.New(res)
}
return mi, err
}
func (m *machineFileAppImpl) Mv(opForm *form.MachineFileOpForm) (*mcm.MachineInfo, error) {
if opForm.Protocol == entity.MachineProtocolRdp {
for _, pt := range opForm.Path {
func (m *machineFileAppImpl) Mv(opParam *MachineFileOpParam, toPath string, path ...string) (*mcm.MachineInfo, error) {
if opParam.Protocol == entity.MachineProtocolRdp {
for _, pt := range path {
// 获取文件名
filename := filepath.Base(pt)
topath := opForm.ToPath
if !strings.HasSuffix(topath, "/") {
topath += "/"
if !strings.HasSuffix(toPath, "/") {
toPath += "/"
}
srcPath := m.GetRdpFilePath(opForm.MachineId, pt)
targetPath := m.GetRdpFilePath(opForm.MachineId, topath+filename)
srcPath := m.GetRdpFilePath(opParam.MachineId, pt)
targetPath := m.GetRdpFilePath(opParam.MachineId, toPath+filename)
os.Rename(srcPath, targetPath)
}
return nil, nil
}
mcli, err := m.GetMachineCli(opForm.FileId, opForm.Path...)
mcli, err := m.GetMachineCli(opParam.AuthCertName)
if err != nil {
return nil, err
}
mi := mcli.Info
res, err := mcli.Run(fmt.Sprintf("mv %s %s", strings.Join(opForm.Path, " "), opForm.ToPath))
res, err := mcli.Run(fmt.Sprintf("mv %s %s", strings.Join(path, " "), toPath))
if err != nil {
return mi, errorx.NewBiz(res)
}
return mi, err
}
func (m *machineFileAppImpl) Rename(renameForm *form.MachineFileRename) (*mcm.MachineInfo, error) {
oldname := renameForm.Oldname
newname := renameForm.Newname
if renameForm.Protocol == entity.MachineProtocolRdp {
oldname = m.GetRdpFilePath(renameForm.MachineId, renameForm.Oldname)
newname = m.GetRdpFilePath(renameForm.MachineId, renameForm.Newname)
func (m *machineFileAppImpl) Rename(opParam *MachineFileOpParam, newname string) (*mcm.MachineInfo, error) {
oldname := opParam.Path
if opParam.Protocol == entity.MachineProtocolRdp {
oldname = m.GetRdpFilePath(opParam.MachineId, oldname)
newname = m.GetRdpFilePath(opParam.MachineId, newname)
return nil, os.Rename(oldname, newname)
}
mi, sftpCli, err := m.GetMachineSftpCli(renameForm.FileId, newname)
mi, sftpCli, err := m.GetMachineSftpCli(opParam)
if err != nil {
return nil, err
}
@@ -468,24 +484,13 @@ func (m *machineFileAppImpl) Rename(renameForm *form.MachineFileRename) (*mcm.Ma
}
// 获取文件机器cli
func (m *machineFileAppImpl) GetMachineCli(fid uint64, inputPath ...string) (*mcm.Cli, error) {
mf, err := m.GetById(new(entity.MachineFile), fid)
if err != nil {
return nil, errorx.NewBiz("文件不存在")
}
for _, path := range inputPath {
// 接口传入的地址需为配置路径的子路径
if !strings.HasPrefix(path, mf.Path) {
return nil, errorx.NewBiz("无权访问该目录或文件: %s", path)
}
}
return m.machineApp.GetCli(mf.MachineId)
func (m *machineFileAppImpl) GetMachineCli(authCertName string) (*mcm.Cli, error) {
return m.machineApp.GetCliByAc(authCertName)
}
// 获取文件机器 sftp cli
func (m *machineFileAppImpl) GetMachineSftpCli(fid uint64, inputPath ...string) (*mcm.MachineInfo, *sftp.Client, error) {
mcli, err := m.GetMachineCli(fid, inputPath...)
func (m *machineFileAppImpl) GetMachineSftpCli(opParam *MachineFileOpParam) (*mcm.MachineInfo, *sftp.Client, error) {
mcli, err := m.GetMachineCli(opParam.AuthCertName)
if err != nil {
return nil, nil, err
}
@@ -498,6 +503,6 @@ func (m *machineFileAppImpl) GetMachineSftpCli(fid uint64, inputPath ...string)
return mcli.Info, sftpCli, nil
}
func (m *machineFileAppImpl) GetRdpFilePath(MachineId uint64, path string) string {
return fmt.Sprintf("%s/%d%s", config.GetMachine().GuacdFilePath, MachineId, path)
func (m *machineFileAppImpl) GetRdpFilePath(machineId uint64, path string) string {
return fmt.Sprintf("%s/%d%s", config.GetMachine().GuacdFilePath, machineId, path)
}

View File

@@ -1,8 +1,6 @@
package entity
import (
"errors"
"mayfly-go/internal/common/utils"
"mayfly-go/pkg/model"
)
@@ -14,9 +12,6 @@ type Machine struct {
Protocol int `json:"protocol"` // 连接协议 1.ssh 2.rdp
Ip string `json:"ip"` // IP地址
Port int `json:"port"` // 端口号
Username string `json:"username"` // 用户名
Password string `json:"password"` // 密码
AuthCertId int `json:"authCertId"` // 授权凭证id
Status int8 `json:"status"` // 状态 1:启用2:停用
Remark string `json:"remark"` // 备注
SshTunnelMachineId int `json:"sshTunnelMachineId"` // ssh隧道机器id
@@ -30,27 +25,3 @@ const (
MachineProtocolSsh = 1
MachineProtocolRdp = 2
)
func (m *Machine) PwdEncrypt() error {
// 密码替换为加密后的密码
password, err := utils.PwdAesEncrypt(m.Password)
if err != nil {
return errors.New("加密主机密码失败")
}
m.Password = password
return nil
}
func (m *Machine) PwdDecrypt() error {
// 密码替换为解密后的密码
password, err := utils.PwdAesDecrypt(m.Password)
if err != nil {
return errors.New("解密主机密码失败")
}
m.Password = password
return nil
}
func (m *Machine) UseAuthCert() bool {
return m.AuthCertId > 0
}

View File

@@ -16,7 +16,7 @@ import (
)
// creates the tunnel to the remote machine (via guacd)
func DoConnect(query url.Values, parameters map[string]string, machineId uint64) (Tunnel, error) {
func DoConnect(query url.Values, parameters map[string]string, ac string) (Tunnel, error) {
conf := NewGuacamoleConfiguration()
parameters["enable-wallpaper"] = "true" // 允许显示墙纸
@@ -33,7 +33,7 @@ func DoConnect(query url.Values, parameters map[string]string, machineId uint64)
parameters["enable-drive"] = "true"
parameters["drive-name"] = "Filesystem"
parameters["create-drive-path"] = "true"
parameters["drive-path"] = fmt.Sprintf("/rdp-file/%d", machineId)
parameters["drive-path"] = fmt.Sprintf("/rdp-file/%s", ac)
conf.Protocol = parameters["scheme"]
conf.Parameters = parameters

View File

@@ -1,34 +1,35 @@
package guac
import (
"github.com/gorilla/websocket"
"net/http"
"sync"
"github.com/gorilla/websocket"
)
// MemorySessionStore is a simple in-memory store of connected sessions that is used by
// the WebsocketServer to store active sessions.
type MemorySessionStore struct {
sync.RWMutex
ConnIds map[uint64]Tunnel
ConnIds map[string]Tunnel
}
// NewMemorySessionStore creates a new store
func NewMemorySessionStore() *MemorySessionStore {
return &MemorySessionStore{
ConnIds: map[uint64]Tunnel{},
ConnIds: map[string]Tunnel{},
}
}
// Get returns a connection by uuid
func (s *MemorySessionStore) Get(id uint64) Tunnel {
func (s *MemorySessionStore) Get(id string) Tunnel {
s.RLock()
defer s.RUnlock()
return s.ConnIds[id]
}
// Add inserts a new connection by uuid
func (s *MemorySessionStore) Add(id uint64, conn *websocket.Conn, req *http.Request, tunnel Tunnel) {
func (s *MemorySessionStore) Add(id string, conn *websocket.Conn, req *http.Request, tunnel Tunnel) {
s.Lock()
defer s.Unlock()
n, ok := s.ConnIds[id]
@@ -41,7 +42,7 @@ func (s *MemorySessionStore) Add(id uint64, conn *websocket.Conn, req *http.Requ
}
// Delete removes a connection by uuid
func (s *MemorySessionStore) Delete(id uint64, conn *websocket.Conn, req *http.Request, tunnel Tunnel) {
func (s *MemorySessionStore) Delete(id string, conn *websocket.Conn, req *http.Request, tunnel Tunnel) {
s.Lock()
defer s.Unlock()
n, ok := s.ConnIds[id]

View File

@@ -1,7 +1,9 @@
package mcm
import (
"errors"
"mayfly-go/internal/common/consts"
tagentity "mayfly-go/internal/tag/domain/entity"
"mayfly-go/pkg/cache"
"mayfly-go/pkg/logx"
"time"
@@ -29,29 +31,63 @@ func init() {
go checkClientAvailability(3 * time.Minute)
}
// 从缓存中获取客户端信息,不存在则回调获取机器信息函数,并新建
func GetMachineCli(machineId uint64, getMachine func(uint64) (*MachineInfo, error)) (*Cli, error) {
if load, ok := cliCache.Get(machineId); ok {
// 从缓存中获取客户端信息,不存在则回调获取机器信息函数,并新建
// @param 机器的授权凭证名
func GetMachineCli(authCertName string, getMachine func(string) (*MachineInfo, error)) (*Cli, error) {
if load, ok := cliCache.Get(authCertName); ok {
return load.(*Cli), nil
}
me, err := getMachine(machineId)
mi, err := getMachine(authCertName)
if err != nil {
return nil, err
}
mi.Key = authCertName
c, err := mi.Conn()
if err != nil {
return nil, err
}
c, err := me.Conn()
if err != nil {
return nil, err
}
cliCache.Put(machineId, c)
cliCache.Put(authCertName, c)
return c, nil
}
// 根据机器id从已连接的机器客户端中获取特权账号连接, 若不存在特权账号,则随机返回一个
func GetMachineCliById(machineId uint64) (*Cli, error) {
// 遍历所有机器连接实例删除指定机器id关联的连接...
items := cliCache.Items()
var machineCli *Cli
for _, v := range items {
cli := v.Value.(*Cli)
mi := cli.Info
if mi.Id != machineId {
continue
}
machineCli = cli
// 如果是特权账号,则跳出
if mi.AuthCertType == tagentity.AuthCertTypePrivileged {
break
}
}
if machineCli != nil {
return machineCli, nil
}
return nil, errors.New("不存在该机器id的连接")
}
// 删除指定机器缓存客户端,并关闭客户端连接
func DeleteCli(id uint64) {
cliCache.Delete(id)
// 遍历所有机器连接实例删除指定机器id关联的连接...
items := cliCache.Items()
for _, v := range items {
mi := v.Value.(*Cli).Info
if mi.Id == id {
cliCache.Delete(mi.Key)
}
}
}
// 检查缓存中的客户端是否可用,不可用则关闭客户端连接

View File

@@ -2,7 +2,7 @@ package mcm
import (
"fmt"
"mayfly-go/internal/machine/domain/entity"
tagentity "mayfly-go/internal/tag/domain/entity"
"mayfly-go/pkg/errorx"
"mayfly-go/pkg/logx"
"net"
@@ -13,16 +13,20 @@ import (
// 机器信息
type MachineInfo struct {
Key string `json:"key"` // 缓存key
Id uint64 `json:"id"`
Name string `json:"name"`
Protocol int `json:"protocol"`
Ip string `json:"ip"` // IP地址
Port int `json:"-"` // 端口号
AuthMethod int8 `json:"-"` // 授权认证方式
Username string `json:"-"` // 用户名
Password string `json:"-"`
Passphrase string `json:"-"` // 私钥口令
Ip string `json:"ip"` // IP地址
Port int `json:"-"` // 端口号
AuthCertName string `json:"authCertName"`
AuthCertType tagentity.AuthCertType `json:"-"`
AuthMethod int8 `json:"-"` // 授权认证方式
Username string `json:"-"` // 用户名
Password string `json:"-"`
Passphrase string `json:"-"` // 私钥口令
SshTunnelMachine *MachineInfo `json:"-"` // ssh隧道机器
TempSshMachineId uint64 `json:"-"` // ssh隧道机器id用于记录隧道机器id连接出错后关闭隧道
@@ -118,9 +122,9 @@ func GetSshClient(m *MachineInfo, jumpClient *ssh.Client) (*ssh.Client, error) {
},
Timeout: 5 * time.Second,
}
if m.AuthMethod == entity.AuthCertAuthMethodPassword {
if m.AuthMethod == int8(tagentity.AuthCertCiphertextTypePassword) {
config.Auth = []ssh.AuthMethod{ssh.Password(m.Password)}
} else if m.AuthMethod == entity.MachineAuthMethodPublicKey {
} else if m.AuthMethod == int8(tagentity.AuthCertCiphertextTypePrivateKey) {
var key ssh.Signer
var err error

View File

@@ -49,9 +49,9 @@ func InitMachineRouter(router *gin.RouterGroup) {
req.BatchSetGroup(machines, reqs[:])
// 终端连接
machines.GET(":machineId/terminal", m.WsSSH)
machines.GET("terminal/:ac", m.WsSSH)
// 终端连接
machines.GET(":machineId/rdp", m.WsGuacamole)
machines.GET("rdp/:ac", m.WsGuacamole)
}
}

View File

@@ -7,6 +7,7 @@ import (
"mayfly-go/internal/mongo/domain/repository"
"mayfly-go/internal/mongo/mgm"
tagapp "mayfly-go/internal/tag/application"
tagentity "mayfly-go/internal/tag/domain/entity"
"mayfly-go/pkg/base"
"mayfly-go/pkg/errorx"
"mayfly-go/pkg/model"
@@ -59,7 +60,7 @@ func (d *mongoAppImpl) Delete(ctx context.Context, id uint64) error {
},
func(ctx context.Context) error {
return d.tagApp.SaveResource(ctx, &tagapp.SaveResourceTagParam{
ResourceType: consts.TagResourceTypeMongo,
ResourceType: tagentity.TagTypeMongo,
ResourceCode: mongoEntity.Code,
})
})
@@ -90,7 +91,7 @@ func (d *mongoAppImpl) SaveMongo(ctx context.Context, m *entity.Mongo, tagIds ..
return d.Insert(ctx, m)
}, func(ctx context.Context) error {
return d.tagApp.SaveResource(ctx, &tagapp.SaveResourceTagParam{
ResourceType: consts.TagResourceTypeMongo,
ResourceType: tagentity.TagTypeMongo,
ResourceCode: m.Code,
TagIds: tagIds,
})
@@ -113,7 +114,7 @@ func (d *mongoAppImpl) SaveMongo(ctx context.Context, m *entity.Mongo, tagIds ..
return d.UpdateById(ctx, m)
}, func(ctx context.Context) error {
return d.tagApp.SaveResource(ctx, &tagapp.SaveResourceTagParam{
ResourceType: consts.TagResourceTypeMongo,
ResourceType: tagentity.TagTypeMongo,
ResourceCode: oldMongo.Code,
TagIds: tagIds,
})

View File

@@ -9,6 +9,7 @@ import (
"mayfly-go/internal/redis/domain/repository"
"mayfly-go/internal/redis/rdm"
tagapp "mayfly-go/internal/tag/application"
tagenttiy "mayfly-go/internal/tag/domain/entity"
"mayfly-go/pkg/base"
"mayfly-go/pkg/errorx"
"mayfly-go/pkg/logx"
@@ -107,7 +108,7 @@ func (r *redisAppImpl) SaveRedis(ctx context.Context, re *entity.Redis, tagIds .
return r.Insert(ctx, re)
}, func(ctx context.Context) error {
return r.tagApp.SaveResource(ctx, &tagapp.SaveResourceTagParam{
ResourceType: consts.TagResourceTypeRedis,
ResourceType: tagenttiy.TagTypeRedis,
ResourceCode: re.Code,
TagIds: tagIds,
})
@@ -138,7 +139,7 @@ func (r *redisAppImpl) SaveRedis(ctx context.Context, re *entity.Redis, tagIds .
return r.UpdateById(ctx, re)
}, func(ctx context.Context) error {
return r.tagApp.SaveResource(ctx, &tagapp.SaveResourceTagParam{
ResourceType: consts.TagResourceTypeRedis,
ResourceType: tagenttiy.TagTypeRedis,
ResourceCode: oldRedis.Code,
TagIds: tagIds,
})
@@ -161,7 +162,7 @@ func (r *redisAppImpl) Delete(ctx context.Context, id uint64) error {
return r.DeleteById(ctx, id)
}, func(ctx context.Context) error {
return r.tagApp.SaveResource(ctx, &tagapp.SaveResourceTagParam{
ResourceType: consts.TagResourceTypeRedis,
ResourceType: tagenttiy.TagTypeRedis,
ResourceCode: re.Code,
})
})

View File

@@ -0,0 +1,29 @@
package api
import (
"mayfly-go/internal/tag/application"
"mayfly-go/internal/tag/domain/entity"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/req"
)
type ResourceAuthCert struct {
ResourceAuthCertApp application.ResourceAuthCert `inject:""`
}
func (r *ResourceAuthCert) ListByQuery(rc *req.Ctx) {
cond := new(entity.ResourceAuthCert)
cond.ResourceCode = rc.Query("resourceCode")
cond.ResourceType = int8(rc.QueryInt("resourceType"))
cond.Type = entity.AuthCertType(rc.QueryInt("type"))
cond.CiphertextType = entity.AuthCertCiphertextType(rc.QueryInt("ciphertextType"))
cond.Name = rc.Query("name")
var racs []*entity.ResourceAuthCert
res, err := r.ResourceAuthCertApp.PageQuery(cond, rc.GetPageParam(), &racs)
biz.ErrIsNil(err)
for _, rac := range racs {
rac.CiphertextDecrypt()
}
rc.ResData = res
}

View File

@@ -15,14 +15,16 @@ import (
type TagTree struct {
TagTreeApp application.TagTree `inject:""`
ResourceAuthCertApp application.ResourceAuthCert `inject:""`
}
func (p *TagTree) GetTagTree(rc *req.Ctx) {
tagType := rc.QueryInt("type")
tagType := entity.TagType(rc.QueryInt("type"))
// 超管返回所有标签树
if rc.GetLoginAccount().Id == consts.AdminId {
var tagTrees vo.TagTreeVOS
p.TagTreeApp.ListByQuery(&entity.TagTreeQuery{Type: int8(tagType)}, &tagTrees)
p.TagTreeApp.ListByQuery(&entity.TagTreeQuery{Type: tagType}, &tagTrees)
rc.ResData = tagTrees.ToTrees(0)
return
}
@@ -40,7 +42,7 @@ func (p *TagTree) GetTagTree(rc *req.Ctx) {
// 获取所有以root标签开头的子标签
var tags []*entity.TagTree
p.TagTreeApp.ListByQuery(&entity.TagTreeQuery{CodePathLikes: collx.MapKeys(rootTag), Type: int8(tagType)}, &tags)
p.TagTreeApp.ListByQuery(&entity.TagTreeQuery{CodePathLikes: collx.MapKeys(rootTag), Type: tagType}, &tags)
tagTrees := make(vo.TagTreeVOS, 0)
for _, tag := range tags {
@@ -81,12 +83,14 @@ func (p *TagTree) DelTagTree(rc *req.Ctx) {
biz.ErrIsNil(p.TagTreeApp.Delete(rc.MetaCtx, uint64(rc.PathParamInt("id"))))
}
// 获取用户可操作的资源标签路径
// 获取用户可操作的标签路径
func (p *TagTree) TagResources(rc *req.Ctx) {
resourceType := int8(rc.PathParamInt("rtype"))
tagResources := p.TagTreeApp.GetAccountTagResources(rc.GetLoginAccount().Id, resourceType, "")
tagPath2Resource := collx.ArrayToMap[entity.TagTree, string](tagResources, func(tagResource entity.TagTree) string {
return tagResource.GetParentPath()
accountId := rc.GetLoginAccount().Id
tagResources := p.TagTreeApp.GetAccountTagResources(accountId, &entity.TagTreeQuery{Type: entity.TagType(resourceType)})
tagPath2Resource := collx.ArrayToMap[*entity.TagTree, string](tagResources, func(tagResource *entity.TagTree) string {
return tagResource.GetParentPath(1)
})
tagPaths := collx.MapKeys(tagPath2Resource)

View File

@@ -0,0 +1,22 @@
package vo
import (
"mayfly-go/internal/tag/domain/entity"
"mayfly-go/pkg/model"
"time"
)
type ResourceAuthCert struct {
Id uint64 `json:"id"`
Name string `json:"name"` // 名称
ResourceCode string `json:"resourceCode"` // 资源编号
ResourceType int8 `json:"resourceType"` // 资源类型
Username string `json:"username"` // 用户名
Ciphertext string `json:"ciphertext"` // 密文
CiphertextType entity.AuthCertCiphertextType `json:"ciphertextType"` // 密文类型
Extra model.Map[string, any] `json:"extra"` // 账号需要的其他额外信息(如秘钥口令等)
Type entity.AuthCertType `json:"type"` // 凭证类型
Remark string `json:"remark"` // 备注
CreateTime *time.Time `json:"createTime"`
}

View File

@@ -7,4 +7,5 @@ import (
func InitIoc() {
ioc.Register(new(tagTreeAppImpl), ioc.WithComponentName("TagTreeApp"))
ioc.Register(new(teamAppImpl), ioc.WithComponentName("TeamApp"))
ioc.Register(new(resourceAuthCertAppImpl), ioc.WithComponentName("ResourceAuthCertApp"))
}

View File

@@ -0,0 +1,268 @@
package application
import (
"context"
"mayfly-go/internal/tag/domain/entity"
"mayfly-go/internal/tag/domain/repository"
"mayfly-go/pkg/base"
"mayfly-go/pkg/errorx"
"mayfly-go/pkg/utils/collx"
)
type SaveAuthCertParam struct {
ResourceCode string
// 资源标签类型
ResourceType entity.TagType
// 授权凭证类型
AuthCertTagType entity.TagType
// 空数组则为删除该资源绑定的授权凭证
AuthCerts []*entity.ResourceAuthCert
}
type ResourceAuthCert interface {
base.App[*entity.ResourceAuthCert]
// SaveAuthCert 保存资源授权凭证信息,不可放于事务中
SaveAuthCert(ctx context.Context, param *SaveAuthCertParam) error
// GetAuthCert 根据授权凭证名称获取授权凭证
GetAuthCert(authCertName string) (*entity.ResourceAuthCert, error)
// GetResourceAuthCert 获取资源授权凭证,默认获取特权账号,若没有则返回第一个
GetResourceAuthCert(resourceType entity.TagType, resourceCode string) (*entity.ResourceAuthCert, error)
// GetAccountAuthCert 获取账号有权限操作的授权凭证信息
GetAccountAuthCert(accountId uint64, authCertTagType entity.TagType, tagPath ...string) []*entity.ResourceAuthCert
// FillAuthCert 填充资源的授权凭证信息
// @param resources 实现了entity.IAuthCert接口的资源信息
FillAuthCert(authCerts []*entity.ResourceAuthCert, resources ...entity.IAuthCert)
}
type resourceAuthCertAppImpl struct {
base.AppImpl[*entity.ResourceAuthCert, repository.ResourceAuthCert]
tagTreeApp TagTree `inject:"TagTreeApp"`
}
// 注入Repo
func (r *resourceAuthCertAppImpl) InjectResourceAuthCertRepo(resourceAuthCertRepo repository.ResourceAuthCert) {
r.Repo = resourceAuthCertRepo
}
func (r *resourceAuthCertAppImpl) SaveAuthCert(ctx context.Context, params *SaveAuthCertParam) error {
resourceCode := params.ResourceCode
resourceType := int8(params.ResourceType)
resourceAuthCerts := params.AuthCerts
authCertTagType := params.AuthCertTagType
if authCertTagType == 0 {
return errorx.NewBiz("资源授权凭证所属标签类型不能为空")
}
if resourceCode == "" {
return errorx.NewBiz("资源授权凭证的资源编号不能为空")
}
// 删除授权信息
if len(resourceAuthCerts) == 0 {
if err := r.DeleteByCond(ctx, &entity.ResourceAuthCert{ResourceCode: resourceCode, ResourceType: resourceType}); err != nil {
return err
}
// 删除该资源下的所有授权凭证资源标签
if err := r.tagTreeApp.DeleteResource(ctx, &DelResourceTagParam{
ResourceCode: resourceCode,
ResourceType: params.ResourceType,
ChildType: authCertTagType,
}); err != nil {
return err
}
return nil
}
name2AuthCert := make(map[string]*entity.ResourceAuthCert, 0)
for _, resourceAuthCert := range resourceAuthCerts {
resourceAuthCert.ResourceCode = resourceCode
resourceAuthCert.ResourceType = int8(resourceType)
name2AuthCert[resourceAuthCert.Name] = resourceAuthCert
existNameAc := &entity.ResourceAuthCert{Name: resourceAuthCert.Name}
if r.GetBy(existNameAc) == nil && existNameAc.ResourceCode != resourceCode {
return errorx.NewBiz("授权凭证的名称不能重复[%s]", resourceAuthCert.Name)
}
// 公共授权凭证,则无需进行密文加密,密文即为公共授权凭证名
if resourceAuthCert.CiphertextType == entity.AuthCertCiphertextTypePublic {
continue
}
// 密文加密
if err := resourceAuthCert.CiphertextEncrypt(); err != nil {
return errorx.NewBiz(err.Error())
}
}
var oldAuthCert []*entity.ResourceAuthCert
r.ListByCond(&entity.ResourceAuthCert{ResourceCode: resourceCode, ResourceType: resourceType}, &oldAuthCert)
var adds, dels, unmodifys []string
if len(oldAuthCert) == 0 {
adds = collx.MapKeys(name2AuthCert)
} else {
oldNames := collx.ArrayMap(oldAuthCert, func(ac *entity.ResourceAuthCert) string {
return ac.Name
})
adds, dels, unmodifys = collx.ArrayCompare[string](collx.MapKeys(name2AuthCert), oldNames)
}
addAuthCerts := make([]*entity.ResourceAuthCert, 0)
for _, add := range adds {
addAc := name2AuthCert[add]
addAc.Id = 0
addAuthCerts = append(addAuthCerts, addAc)
}
// 处理新增的授权凭证
if len(addAuthCerts) > 0 {
if err := r.BatchInsert(ctx, addAuthCerts); err != nil {
return err
}
// 获取资源编号对应的资源标签信息
var resourceTags []*entity.TagTree
r.tagTreeApp.ListByCond(&entity.TagTree{Type: params.ResourceType, Code: resourceCode}, &resourceTags)
// 资源标签id相当于父tag id
resourceTagIds := collx.ArrayMap(resourceTags, func(tag *entity.TagTree) uint64 {
return tag.Id
})
// 保存授权凭证类型的资源标签
for _, authCert := range addAuthCerts {
if err := r.tagTreeApp.SaveResource(ctx, &SaveResourceTagParam{
ResourceCode: authCert.Name,
ResourceType: authCertTagType,
ResourceName: authCert.Username,
TagIds: resourceTagIds,
}); err != nil {
return err
}
}
}
for _, del := range dels {
if err := r.DeleteByCond(ctx, &entity.ResourceAuthCert{ResourceCode: resourceCode, ResourceType: resourceType, Name: del}); err != nil {
return err
}
// 删除对应授权凭证资源标签
if err := r.tagTreeApp.DeleteResource(ctx, &DelResourceTagParam{
ResourceCode: del,
ResourceType: authCertTagType,
}); err != nil {
return err
}
}
for _, unmodify := range unmodifys {
unmodifyAc := name2AuthCert[unmodify]
if unmodifyAc.Id == 0 {
continue
}
if err := r.UpdateById(ctx, unmodifyAc); err != nil {
return err
}
}
return nil
}
func (r *resourceAuthCertAppImpl) GetAuthCert(authCertName string) (*entity.ResourceAuthCert, error) {
authCert := &entity.ResourceAuthCert{Name: authCertName}
if err := r.GetBy(authCert); err != nil {
return nil, errorx.NewBiz("该授权凭证不存在")
}
return r.decryptAuthCert(authCert)
}
func (r *resourceAuthCertAppImpl) GetResourceAuthCert(resourceType entity.TagType, resourceCode string) (*entity.ResourceAuthCert, error) {
var resourceAuthCerts []*entity.ResourceAuthCert
if err := r.ListByCond(&entity.ResourceAuthCert{
ResourceType: int8(resourceType),
ResourceCode: resourceCode,
}, &resourceAuthCerts); err != nil {
return nil, err
}
if len(resourceAuthCerts) == 0 {
return nil, errorx.NewBiz("该资源不存在授权凭证账号")
}
for _, resourceAuthCert := range resourceAuthCerts {
if resourceAuthCert.Type == entity.AuthCertTypePrivileged {
return r.decryptAuthCert(resourceAuthCert)
}
}
return r.decryptAuthCert(resourceAuthCerts[0])
}
func (r *resourceAuthCertAppImpl) GetAccountAuthCert(accountId uint64, authCertTagType entity.TagType, tagPath ...string) []*entity.ResourceAuthCert {
// 获取用户有权限操作的授权凭证资源标签
tagQuery := &entity.TagTreeQuery{
Type: authCertTagType,
CodePathLikes: tagPath,
}
authCertTags := r.tagTreeApp.GetAccountTagResources(accountId, tagQuery)
// 获取所有授权凭证名称
authCertNames := collx.ArrayMap(authCertTags, func(tag *entity.TagTree) string {
return tag.Code
})
var authCerts []*entity.ResourceAuthCert
r.GetRepo().ListByWheres(collx.M{
"name in ?": collx.ArrayDeduplicate(authCertNames),
}, &authCerts)
return authCerts
}
func (r *resourceAuthCertAppImpl) FillAuthCert(authCerts []*entity.ResourceAuthCert, resources ...entity.IAuthCert) {
if len(resources) == 0 {
return
}
// 资源编号 -> 资源
resourceCode2Resouce := collx.ArrayToMap(resources, func(ac entity.IAuthCert) string {
return ac.GetCode()
})
for _, authCert := range authCerts {
resourceCode2Resouce[authCert.ResourceCode].SetAuthCert(entity.AuthCert{
Name: authCert.Name,
Username: authCert.Username,
Type: authCert.Type,
CiphertextType: authCert.CiphertextType,
})
}
}
// 解密授权凭证信息
func (r *resourceAuthCertAppImpl) decryptAuthCert(authCert *entity.ResourceAuthCert) (*entity.ResourceAuthCert, error) {
if authCert.CiphertextType == entity.AuthCertCiphertextTypePublic {
// 如果是公共授权凭证,则密文为公共授权凭证名称,需要使用该名称再去获取对应的授权凭证
authCert = &entity.ResourceAuthCert{Name: authCert.Ciphertext}
if err := r.GetBy(authCert); err != nil {
return nil, errorx.NewBiz("该公共授权凭证[%s]不存在", authCert.Ciphertext)
}
}
if err := authCert.CiphertextDecrypt(); err != nil {
return nil, err
}
return authCert, nil
}

View File

@@ -16,9 +16,19 @@ import (
type SaveResourceTagParam struct {
ResourceCode string
ResourceName string
ResourceType int8
ResourceType entity.TagType
TagIds []uint64 // 关联标签,相当于父标签 pid
TagIds []uint64 // 关联标签,相当于父标签 pid,空数组则为删除该资源绑定的标签
}
type DelResourceTagParam struct {
ResourceCode string
ResourceType entity.TagType
Pid uint64 //父标签 pid
// 要删除的子节点类型,若存在值,则为删除资源标签下的指定类型的子标签
ChildType entity.TagType
}
type TagTree interface {
@@ -34,7 +44,7 @@ type TagTree interface {
// @param accountId 账号id
// @param resourceType 资源类型
// @param tagPath 访问指定的标签路径下关联的资源
GetAccountTagResources(accountId uint64, resourceType int8, tagPath string) []entity.TagTree
GetAccountTagResources(accountId uint64, query *entity.TagTreeQuery) []*entity.TagTree
// 获取指定账号有权限操作的资源codes
GetAccountResourceCodes(accountId uint64, resourceType int8, tagPath string) []string
@@ -42,6 +52,9 @@ type TagTree interface {
// SaveResource 保存资源标签
SaveResource(ctx context.Context, req *SaveResourceTagParam) error
// DeleteResource 删除资源标签,会删除该资源下所有子节点信息
DeleteResource(ctx context.Context, param *DelResourceTagParam) error
// 根据资源信息获取对应的标签路径列表
ListTagPathByResource(resourceType int8, resourceCode string) []string
@@ -115,12 +128,12 @@ func (p *tagTreeAppImpl) ListByQuery(condition *entity.TagTreeQuery, toEntity an
p.GetRepo().SelectByCondition(condition, toEntity)
}
func (p *tagTreeAppImpl) GetAccountTagResources(accountId uint64, resourceType int8, tagPath string) []entity.TagTree {
func (p *tagTreeAppImpl) GetAccountTagResources(accountId uint64, query *entity.TagTreeQuery) []*entity.TagTree {
tagResourceQuery := &entity.TagTreeQuery{
Type: resourceType,
Type: query.Type,
}
var tagResources []entity.TagTree
var tagResources []*entity.TagTree
var accountTagPaths []string
if accountId != consts.AdminId {
@@ -131,16 +144,37 @@ func (p *tagTreeAppImpl) GetAccountTagResources(accountId uint64, resourceType i
}
}
tagResourceQuery.CodePathLike = tagPath
// 去除空字符串标签
tagPaths := collx.ArrayRemoveBlank(query.CodePathLikes)
// 如果需要查询指定标签下的资源标签,则需要与用户拥有的权限进行过滤,避免越权
if len(tagPaths) > 0 {
// admin 则直接赋值需要获取的标签
if len(accountTagPaths) == 0 {
accountTagPaths = tagPaths
} else {
accountTagPaths = collx.ArrayFilter[string](tagPaths, func(s string) bool {
for _, v := range accountTagPaths {
// 要过滤的权限需要在用户拥有的子标签下, accountTagPath: test/ tagPath: test/test1/ -> true
if strings.HasPrefix(v, s) {
return true
}
}
return false
})
}
}
// tagResourceQuery.CodePathLike = tagPath
tagResourceQuery.Codes = query.Codes
tagResourceQuery.CodePathLikes = accountTagPaths
p.ListByQuery(tagResourceQuery, &tagResources)
return tagResources
}
func (p *tagTreeAppImpl) GetAccountResourceCodes(accountId uint64, resourceType int8, tagPath string) []string {
tagResources := p.GetAccountTagResources(accountId, resourceType, tagPath)
tagResources := p.GetAccountTagResources(accountId, &entity.TagTreeQuery{Type: entity.TagType(resourceType), CodePathLikes: []string{tagPath}})
// resouce code去重
code2Resource := collx.ArrayToMap[entity.TagTree, string](tagResources, func(val entity.TagTree) string {
code2Resource := collx.ArrayToMap[*entity.TagTree, string](tagResources, func(val *entity.TagTree) string {
return val.Code
})
@@ -149,7 +183,7 @@ func (p *tagTreeAppImpl) GetAccountResourceCodes(accountId uint64, resourceType
func (p *tagTreeAppImpl) SaveResource(ctx context.Context, req *SaveResourceTagParam) error {
resourceCode := req.ResourceCode
resourceType := req.ResourceType
resourceType := entity.TagType(req.ResourceType)
resourceName := req.ResourceName
tagIds := req.TagIds
@@ -162,7 +196,10 @@ func (p *tagTreeAppImpl) SaveResource(ctx context.Context, req *SaveResourceTagP
// 如果tagIds为空数组则为删除该资源标签
if len(tagIds) == 0 {
return p.DeleteByCond(ctx, &entity.TagTree{Code: resourceCode, Type: resourceType})
return p.DeleteResource(ctx, &DelResourceTagParam{
ResourceType: resourceType,
ResourceCode: resourceCode,
})
}
if resourceName == "" {
@@ -205,10 +242,36 @@ func (p *tagTreeAppImpl) SaveResource(ctx context.Context, req *SaveResourceTagP
if len(delTagIds) > 0 {
for _, tagId := range delTagIds {
cond := &entity.TagTree{Code: resourceCode, Type: resourceType, Pid: tagId}
if err := p.DeleteByCond(ctx, cond); err != nil {
if err := p.DeleteResource(ctx, &DelResourceTagParam{
ResourceType: resourceType,
ResourceCode: resourceCode,
Pid: tagId,
}); err != nil {
return err
}
}
}
return nil
}
func (p *tagTreeAppImpl) DeleteResource(ctx context.Context, param *DelResourceTagParam) error {
// 获取资源编号对应的资源标签信息
var resourceTags []*entity.TagTree
p.ListByCond(&entity.TagTree{Type: param.ResourceType, Code: param.ResourceCode, Pid: param.Pid}, &resourceTags)
if len(resourceTags) == 0 {
return nil
}
delTagType := param.ChildType
for _, resourceTag := range resourceTags {
// 删除所有code_path下的子标签
if err := p.DeleteByWheres(ctx, collx.M{
"code_path LIKE ?": resourceTag.CodePath + "%",
"type = ?": delTagType,
}); err != nil {
return err
}
}
@@ -217,7 +280,7 @@ func (p *tagTreeAppImpl) SaveResource(ctx context.Context, req *SaveResourceTagP
func (p *tagTreeAppImpl) ListTagPathByResource(resourceType int8, resourceCode string) []string {
var trs []*entity.TagTree
p.ListByCond(&entity.TagTree{Type: resourceType, Code: resourceCode}, &trs)
p.ListByCond(&entity.TagTree{Type: entity.TagType(resourceType), Code: resourceCode}, &trs)
return collx.ArrayMap(trs, func(tr *entity.TagTree) string {
return tr.CodePath
})
@@ -260,7 +323,7 @@ func (p *tagTreeAppImpl) FillTagInfo(resources ...entity.ITagResource) {
for _, tr := range tagResources {
// 赋值标签信息
resourceCode2Resouce[tr.Code].SetTagInfo(entity.ResourceTag{TagId: tr.Pid, TagPath: tr.GetParentPath()})
resourceCode2Resouce[tr.Code].SetTagInfo(entity.ResourceTag{TagId: tr.Pid, TagPath: tr.GetParentPath(0)})
}
}

View File

@@ -6,8 +6,8 @@ type TagTreeQuery struct {
model.Model
Pid uint64
Type int8 `json:"type"`
Code string `json:"code"` // 标识
Type TagType `json:"type"`
Code string `json:"code"` // 标识
Codes []string
CodePath string `json:"codePath"` // 标识路径
CodePaths []string

View File

@@ -0,0 +1,123 @@
package entity
import (
"errors"
"mayfly-go/internal/common/utils"
"mayfly-go/pkg/model"
"github.com/may-fly/cast"
)
// 资源授权凭证
type ResourceAuthCert struct {
model.Model
Name string `json:"name"` // 名称(全局唯一)
ResourceCode string `json:"resourceCode"` // 资源编号
ResourceType int8 `json:"resourceType"` // 资源类型
Username string `json:"username"` // 用户名
Ciphertext string `json:"ciphertext"` // 密文
CiphertextType AuthCertCiphertextType `json:"ciphertextType"` // 密文类型
Extra model.Map[string, any] `json:"extra"` // 账号需要的其他额外信息(如秘钥口令等)
Type AuthCertType `json:"type"` // 凭证类型
Remark string `json:"remark"` // 备注
}
func (m *ResourceAuthCert) CiphertextEncrypt() error {
// 密码替换为加密后的密码
password, err := utils.PwdAesEncrypt(m.Ciphertext)
if err != nil {
return errors.New("加密密文失败")
}
m.Ciphertext = password
// 加密秘钥口令
if m.CiphertextType == AuthCertCiphertextTypePrivateKey {
passphrase := cast.ToString(m.Extra["passphrase"])
if passphrase != "" {
passphrase, err := utils.PwdAesEncrypt(passphrase)
if err != nil {
return errors.New("加密秘钥口令失败")
}
m.Extra["passphrase"] = passphrase
}
}
return nil
}
func (m *ResourceAuthCert) CiphertextDecrypt() error {
// 密码替换为解密后的密码
password, err := utils.PwdAesDecrypt(m.Ciphertext)
if err != nil {
return errors.New("解密密文失败")
}
m.Ciphertext = password
// 加密秘钥口令
if m.CiphertextType == AuthCertCiphertextTypePrivateKey {
passphrase := cast.ToString(m.Extra["passphrase"])
if passphrase != "" {
passphrase, err := utils.PwdAesDecrypt(passphrase)
if err != nil {
return errors.New("解密秘钥口令失败")
}
m.Extra["passphrase"] = passphrase
}
}
return nil
}
// 密文类型
type AuthCertCiphertextType int8
// 凭证类型
type AuthCertType int8
const (
AuthCertCiphertextTypePublic AuthCertCiphertextType = -1 // 公共授权凭证
AuthCertCiphertextTypePassword AuthCertCiphertextType = 1 // 密码
AuthCertCiphertextTypePrivateKey AuthCertCiphertextType = 2 // 私钥
AuthCertTypePublic AuthCertType = 2 // 公共凭证(可多个资源共享该授权凭证)
AuthCertTypePrivate AuthCertType = 1 // 普通私有凭证
AuthCertTypePrivileged AuthCertType = 11 // 特权私有凭证
AuthCertTypePrivateDefault AuthCertType = 12 // 默认私有凭证
)
// 授权凭证接口,填充资源授权凭证信息
type IAuthCert interface {
// 获取资源code
GetCode() string
// 设置授权信息
SetAuthCert(ac AuthCert)
}
// 资源关联的标签信息
type AuthCert struct {
Name string `json:"name" gorm:"-"` // 名称
Username string `json:"username" gorm:"-"` // 用户名
CiphertextType AuthCertCiphertextType `json:"ciphertextType" gorm:"-"` // 密文类型
Type AuthCertType `json:"type" gorm:"-"` // 凭证类型
}
func (r *AuthCert) SetAuthCert(ac AuthCert) {
r.Name = ac.Name
r.Username = ac.Username
r.Type = ac.Type
r.CiphertextType = ac.CiphertextType
}
// 资源标签列表
type AuthCerts struct {
AuthCerts []AuthCert `json:"authCerts" gorm:"-"`
}
func (r *AuthCerts) SetAuthCert(rt AuthCert) {
if r.AuthCerts == nil {
r.AuthCerts = make([]AuthCert, 0)
}
r.AuthCerts = append(r.AuthCerts, rt)
}

View File

@@ -1,6 +1,7 @@
package entity
import (
"mayfly-go/internal/common/consts"
"mayfly-go/pkg/model"
"strings"
)
@@ -9,17 +10,28 @@ import (
type TagTree struct {
model.Model
Pid uint64 `json:"pid"`
Type int8 `json:"type"` // 类型: -1.普通标签; 其他值则为对应的资源类型
Code string `json:"code"` // 标识编码, 若类型不为-1则为对应资源编码
CodePath string `json:"codePath"` // 标识路径
Name string `json:"name"` // 名称
Remark string `json:"remark"` // 备注说明
Pid uint64 `json:"pid"`
Type TagType `json:"type"` // 类型: -1.普通标签; 其他值则为对应的资源类型
Code string `json:"code"` // 标识编码, 若类型不为-1则为对应资源编码
CodePath string `json:"codePath"` // 标识路径
Name string `json:"name"` // 名称
Remark string `json:"remark"` // 备注说明
}
type TagType int8
const (
// 标识路径分隔符
CodePathSeparator = "/"
TagTypeTag TagType = -1
TagTypeMachine TagType = TagType(consts.TagResourceTypeMachine)
TagTypeDb TagType = TagType(consts.TagResourceTypeDb)
TagTypeRedis TagType = TagType(consts.TagResourceTypeRedis)
TagTypeMongo TagType = TagType(consts.TagResourceTypeMongo)
TagTypeMachineAuthCert TagType = 11 // 机器-授权凭证
TagTypeDbAuthCert TagType = 21 // DB-授权凭证
)
// GetRootCode 获取根路径信息
@@ -27,19 +39,25 @@ func (pt *TagTree) GetRootCode() string {
return strings.Split(pt.CodePath, CodePathSeparator)[0]
}
// GetParentPath 获取父标签路径, 如CodePath = test/test1/test2/ -> test/test1/
func (pt *TagTree) GetParentPath() string {
// 去末尾的分隔符
input := strings.TrimRight(pt.CodePath, CodePathSeparator)
// GetParentPath 获取父标签路径, 如CodePath = test/test1/test2/ -> index = 0 => test/test1/ index = 1 => test/
func (pt *TagTree) GetParentPath(index int) string {
// 去末尾的斜杠
codePath := strings.TrimSuffix(pt.CodePath, "/")
// 查找倒数第二个连字符位置
lastHyphenIndex := strings.LastIndex(input, CodePathSeparator)
if lastHyphenIndex == -1 {
return ""
// 使用 Split 方法将路径按斜杠分割成切片
paths := strings.Split(codePath, "/")
// 确保索引在有效范围内
if index < 0 {
index = 0
} else if index > len(paths)-2 {
index = len(paths) - 2
}
// 截取字符串
return input[:lastHyphenIndex+1]
// 按索引拼接父标签路径
parentPath := strings.Join(paths[:len(paths)-index-1], "/")
return parentPath + "/"
}
// 标签接口资源,如果要实现资源结构体填充标签信息,则资源结构体需要实现该接口

View File

@@ -0,0 +1,10 @@
package repository
import (
"mayfly-go/internal/tag/domain/entity"
"mayfly-go/pkg/base"
)
type ResourceAuthCert interface {
base.Repo[*entity.ResourceAuthCert]
}

View File

@@ -9,4 +9,5 @@ func InitIoc() {
ioc.Register(newTagTreeTeamRepo(), ioc.WithComponentName("TagTreeTeamRepo"))
ioc.Register(newTeamRepo(), ioc.WithComponentName("TeamRepo"))
ioc.Register(newTeamMemberRepo(), ioc.WithComponentName("TeamMemberRepo"))
ioc.Register(newResourceAuthCertRepoImpl(), ioc.WithComponentName("ResourceAuthCertRepo"))
}

View File

@@ -0,0 +1,15 @@
package persistence
import (
"mayfly-go/internal/tag/domain/entity"
"mayfly-go/internal/tag/domain/repository"
"mayfly-go/pkg/base"
)
type resourceAuthCertRepoImpl struct {
base.RepoImpl[*entity.ResourceAuthCert]
}
func newResourceAuthCertRepoImpl() repository.ResourceAuthCert {
return &resourceAuthCertRepoImpl{base.RepoImpl[*entity.ResourceAuthCert]{M: new(entity.ResourceAuthCert)}}
}

View File

@@ -0,0 +1,24 @@
package router
import (
"mayfly-go/internal/tag/api"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/ioc"
"mayfly-go/pkg/req"
"github.com/gin-gonic/gin"
)
func InitResourceAuthCertRouter(router *gin.RouterGroup) {
m := new(api.ResourceAuthCert)
biz.ErrIsNil(ioc.Inject(m))
resourceAuthCert := router.Group("/auth-certs")
{
reqs := [...]*req.Conf{
req.NewGet("", m.ListByQuery),
}
req.BatchSetGroup(resourceAuthCert, reqs[:])
}
}

View File

@@ -5,4 +5,5 @@ import "github.com/gin-gonic/gin"
func Init(router *gin.RouterGroup) {
InitTagTreeRouter(router)
InitTeamRouter(router)
InitResourceAuthCertRouter(router)
}

View File

@@ -6,6 +6,7 @@ import (
"mayfly-go/pkg/contextx"
"mayfly-go/pkg/global"
"mayfly-go/pkg/model"
"mayfly-go/pkg/utils/collx"
"gorm.io/gorm"
)
@@ -31,12 +32,28 @@ type App[T model.ModelI] interface {
// 使用指定gorm db执行主要用于事务执行
UpdateByIdWithDb(ctx context.Context, db *gorm.DB, e T) error
// UpdateByWheres 更新满足wheres条件的数据
// @param wheres key => "age > ?" value => 10等
UpdateByWheres(ctx context.Context, e T, wheres collx.M, columns ...string) error
// UpdateByWheresWithDb 使用指定gorm.Db更新满足wheres条件的数据
// @param wheres key => "age > ?" value => 10等
UpdateByWheresWithDb(ctx context.Context, db *gorm.DB, e T, wheres collx.M, columns ...string) error
// 根据实体主键删除实体
DeleteById(ctx context.Context, id uint64) error
// 使用指定gorm db执行主要用于事务执行
DeleteByIdWithDb(ctx context.Context, db *gorm.DB, id uint64) error
// DeleteByWheres 根据wheres条件进行删除
// @param wheres key -> "age > ?" value -> 10等
DeleteByWheres(ctx context.Context, wheres collx.M) error
// DeleteByWheresWithDb 使用指定gorm.Db根据wheres条件进行删除
// @param wheres key -> "age > ?" value -> 10等
DeleteByWheresWithDb(ctx context.Context, db *gorm.DB, wheres collx.M) error
// 根据实体条件更新参数udpateFields指定字段
Updates(ctx context.Context, cond any, udpateFields map[string]any) error
@@ -64,6 +81,9 @@ type App[T model.ModelI] interface {
// 根据条件查询数据映射至listModels
ListByCond(cond any, listModels any, cols ...string) error
// PageQuery 分页查询
PageQuery(cond any, pageParam *model.PageParam, toModels any) (*model.PageResult[any], error)
// 获取满足model中不为空的字段值条件的所有数据.
//
// @param list为数组类型 如 var users *[]User可指定为非model结构体
@@ -117,6 +137,14 @@ func (ai *AppImpl[T, R]) UpdateByIdWithDb(ctx context.Context, db *gorm.DB, e T)
return ai.GetRepo().UpdateByIdWithDb(ctx, db, e)
}
func (ai *AppImpl[T, R]) UpdateByWheres(ctx context.Context, e T, wheres collx.M, columns ...string) error {
return ai.GetRepo().UpdateByWheres(ctx, e, wheres, columns...)
}
func (ai *AppImpl[T, R]) UpdateByWheresWithDb(ctx context.Context, db *gorm.DB, e T, wheres collx.M, columns ...string) error {
return ai.GetRepo().UpdateByWheresWithDb(ctx, db, e, wheres, columns...)
}
// 根据实体条件更新参数udpateFields指定字段 (单纯更新,不做其他业务逻辑处理)
func (ai *AppImpl[T, R]) Updates(ctx context.Context, cond any, udpateFields map[string]any) error {
return ai.GetRepo().Updates(cond, udpateFields)
@@ -152,6 +180,14 @@ func (ai *AppImpl[T, R]) DeleteByCondWithDb(ctx context.Context, db *gorm.DB, co
return ai.GetRepo().DeleteByCondWithDb(ctx, db, cond)
}
func (ai *AppImpl[T, R]) DeleteByWheres(ctx context.Context, wheres collx.M) error {
return ai.GetRepo().DeleteByWheres(ctx, wheres)
}
func (ai *AppImpl[T, R]) DeleteByWheresWithDb(ctx context.Context, db *gorm.DB, wheres collx.M) error {
return ai.GetRepo().DeleteByWheresWithDb(ctx, db, wheres)
}
// 根据实体id查询
func (ai *AppImpl[T, R]) GetById(e T, id uint64, cols ...string) (T, error) {
if err := ai.GetRepo().GetById(e, id, cols...); err != nil {
@@ -174,6 +210,11 @@ func (ai *AppImpl[T, R]) ListByCond(cond any, listModels any, cols ...string) er
return ai.GetRepo().ListByCond(cond, listModels, cols...)
}
// PageQuery 分页查询
func (ai *AppImpl[T, R]) PageQuery(cond any, pageParam *model.PageParam, toModels any) (*model.PageResult[any], error) {
return ai.GetRepo().PageQuery(cond, pageParam, toModels)
}
// 获取满足model中不为空的字段值条件的所有数据.
//
// @param list为数组类型 如 var users *[]User可指定为非model结构体

View File

@@ -5,6 +5,7 @@ import (
"mayfly-go/pkg/contextx"
"mayfly-go/pkg/gormx"
"mayfly-go/pkg/model"
"mayfly-go/pkg/utils/collx"
"gorm.io/gorm"
)
@@ -33,6 +34,14 @@ type Repo[T model.ModelI] interface {
// 使用指定gorm db执行主要用于事务执行
UpdateByIdWithDb(ctx context.Context, db *gorm.DB, e T, columns ...string) error
// UpdateByWheres 更新满足wheres条件的数据
// @param wheres key => "age > ?" value => 10等
UpdateByWheres(ctx context.Context, e T, wheres collx.M, columns ...string) error
// UpdateByWheresWithDb 使用指定gorm.Db更新满足wheres条件的数据
// @param wheres key => "age > ?" value => 10等
UpdateByWheresWithDb(ctx context.Context, db *gorm.DB, e T, wheres collx.M, columns ...string) error
// 保存实体实体IsCreate返回true则新增否则更新
Save(ctx context.Context, e T) error
@@ -55,6 +64,14 @@ type Repo[T model.ModelI] interface {
// 使用指定gorm db执行主要用于事务执行
DeleteByCondWithDb(ctx context.Context, db *gorm.DB, cond any) error
// DeleteByWheres 根据wheres条件进行删除
// @param wheres key -> "age > ?" value -> 10等
DeleteByWheres(ctx context.Context, wheres collx.M) error
// DeleteByWheresWithDb 使用指定gorm.Db根据wheres条件进行删除
// @param wheres key -> "age > ?" value -> 10等
DeleteByWheresWithDb(ctx context.Context, db *gorm.DB, wheres collx.M) error
// 根据实体id查询
GetById(e T, id uint64, cols ...string) error
@@ -67,6 +84,13 @@ type Repo[T model.ModelI] interface {
// 根据实体条件查询数据映射至listModels
ListByCond(cond any, listModels any, cols ...string) error
// 根据wheres条件进行过滤
// @param wheres key -> "age > ?" value -> 10等
ListByWheres(wheres collx.M, listModels any, cols ...string) error
// PageQuery 分页查询
PageQuery(cond any, pageParam *model.PageParam, toModels any) (*model.PageResult[any], error)
// 获取满足model中不为空的字段值条件的所有数据.
//
// @param list为数组类型 如 var users *[]User可指定为非model结构体
@@ -123,6 +147,24 @@ func (br *RepoImpl[T]) UpdateByIdWithDb(ctx context.Context, db *gorm.DB, e T, c
return gormx.UpdateByIdWithDb(db, br.fillBaseInfo(ctx, e), columns...)
}
func (br *RepoImpl[T]) UpdateByWheres(ctx context.Context, e T, wheres collx.M, columns ...string) error {
if db := contextx.GetDb(ctx); db != nil {
return br.UpdateByWheresWithDb(ctx, db, e, wheres, columns...)
}
e = br.fillBaseInfo(ctx, e)
// model的主键值需为空否则会带上主键条件
e.SetId(0)
return gormx.UpdateByWheres(e, wheres)
}
func (br *RepoImpl[T]) UpdateByWheresWithDb(ctx context.Context, db *gorm.DB, e T, wheres collx.M, columns ...string) error {
e = br.fillBaseInfo(ctx, e)
// model的主键值需为空否则会带上主键条件
e.SetId(0)
return gormx.UpdateByWheresWithDb(db, br.fillBaseInfo(ctx, e), wheres, columns...)
}
func (br *RepoImpl[T]) Updates(cond any, udpateFields map[string]any) error {
return gormx.Updates(br.GetModel(), cond, udpateFields)
}
@@ -163,6 +205,23 @@ func (br *RepoImpl[T]) DeleteByCondWithDb(ctx context.Context, db *gorm.DB, cond
return gormx.DeleteByCondWithDb(db, br.GetModel(), cond)
}
func (br *RepoImpl[T]) DeleteByWheres(ctx context.Context, wheres collx.M) error {
if db := contextx.GetDb(ctx); db != nil {
return br.DeleteByWheresWithDb(ctx, db, wheres)
}
// model的主键值需为空否则会带上主键条件
e := br.GetModel()
e.SetId(0)
return gormx.DeleteByWheres(e, wheres)
}
func (br *RepoImpl[T]) DeleteByWheresWithDb(ctx context.Context, db *gorm.DB, wheres collx.M) error {
// model的主键值需为空否则会带上主键条件
e := br.GetModel()
e.SetId(0)
return gormx.DeleteByWheresWithDb(db, e, wheres)
}
func (br *RepoImpl[T]) GetById(e T, id uint64, cols ...string) error {
if err := gormx.GetById(e, id, cols...); err != nil {
return err
@@ -182,6 +241,15 @@ func (br *RepoImpl[T]) ListByCond(cond any, listModels any, cols ...string) erro
return gormx.ListByCond(br.GetModel(), cond, listModels, cols...)
}
func (br *RepoImpl[T]) ListByWheres(wheres collx.M, listModels any, cols ...string) error {
return gormx.ListByWheres(br.GetModel(), wheres, listModels, cols...)
}
func (br *RepoImpl[T]) PageQuery(cond any, pageParam *model.PageParam, toModels any) (*model.PageResult[any], error) {
qd := gormx.NewQuery(br.GetModel()).WithCondModel(cond)
return gormx.PageQuery(qd, pageParam, toModels)
}
func (br *RepoImpl[T]) ListByCondOrder(cond any, list any, order ...string) error {
return gormx.ListByCondOrder(br.GetModel(), cond, list, order...)
}

View File

@@ -4,6 +4,8 @@ import (
"fmt"
"mayfly-go/pkg/global"
"mayfly-go/pkg/model"
"mayfly-go/pkg/utils/anyx"
"mayfly-go/pkg/utils/collx"
"strings"
"time"
@@ -93,6 +95,19 @@ func ListByCond(model any, cond any, list any, cols ...string) error {
return global.Db.Model(model).Select(cols).Where(cond).Scopes(UndeleteScope).Order("id desc").Find(list).Error
}
// 获取满足cond中不为空的字段值条件的所有model表数据.
//
// @param wheres key -> "age > ?" value -> 10等
func ListByWheres(model any, wheres collx.M, list any, cols ...string) error {
gdb := global.Db.Model(model).Select(cols)
for k, v := range wheres {
if !anyx.IsBlank(v) {
gdb.Where(k, v)
}
}
return gdb.Scopes(UndeleteScope).Order("id desc").Find(list).Error
}
// 获取满足model中不为空的字段值条件的所有数据.
//
// @param list为数组类型 如 var users *[]User可指定为非model结构体
@@ -154,6 +169,24 @@ func UpdateByIdWithDb(db *gorm.DB, model any, columns ...string) error {
return db.Model(model).Select(columns).Updates(model).Error
}
// UpdateByWheres 更新满足wheres条件的数据(model的主键值需为空否则会带上主键条件)
// @param wheres key -> "age > ?" value -> 10等
func UpdateByWheres(model_ any, wheres collx.M) error {
return UpdateByWheresWithDb(global.Db, model_, wheres)
}
// UpdateByWheresWithDb 使用指定gorm.DB更新满足wheres条件的数据(model的主键值需为空否则会带上主键条件)
// @param wheres key -> "age > ?" value -> 10等
func UpdateByWheresWithDb(db *gorm.DB, model any, wheres collx.M, columns ...string) error {
gormDb := db.Model(model).Select(columns)
for k, v := range wheres {
if !anyx.IsBlank(v) {
gormDb.Where(k, v)
}
}
return gormDb.Updates(model).Error
}
// 根据实体条件更新参数udpateFields指定字段
func Updates(model any, condition any, updateFields map[string]any) error {
return global.Db.Model(model).Where(condition).Updates(updateFields).Error
@@ -190,6 +223,24 @@ func DeleteByWithDb(db *gorm.DB, model_ any) error {
return DeleteByCondWithDb(db, model_, model_)
}
// DeleteByWheres 使用指定wheres删除(model的主键值需为空否则会带上主键条件)
// @param wheres key -> "age > ?" value -> 10等
func DeleteByWheres(model_ any, wheres collx.M) error {
return DeleteByWheresWithDb(global.Db, model_, wheres)
}
// DeleteByWheresWithDb 使用指定gorm.Db根据wheres条件进行删除(model的主键值需为空否则会带上主键条件)
// @param wheres key -> "age > ?" value -> 10等
func DeleteByWheresWithDb(db *gorm.DB, model_ any, wheres collx.M) error {
gormDb := db.Model(model_)
for k, v := range wheres {
if !anyx.IsBlank(v) {
gormDb.Where(k, v)
}
}
return gormDb.Updates(getDeleteColumnValue()).Error
}
// 根据cond条件删除指定model表数据
//
// @param model 数据库映射实体模型

View File

@@ -22,11 +22,13 @@ const (
// 实体接口
type ModelI interface {
// SetId 设置id
SetId(id uint64)
// 是否为新建该实体模型, 默认 id == 0 为新建
// IsCreate 是否为新建该实体模型, 默认 id == 0 为新建
IsCreate() bool
// 使用当前登录账号信息赋值实体结构体的基础信息
// FillBaseInfo 使用当前登录账号信息赋值实体结构体的基础信息
//
// 如创建时间,修改时间,创建者,修改者信息等
FillBaseInfo(idGenType IdGenType, account *LoginAccount)
@@ -36,6 +38,10 @@ type IdModel struct {
Id uint64 `json:"id"`
}
func (m *IdModel) SetId(id uint64) {
m.Id = id
}
func (m *IdModel) IsCreate() bool {
return m.Id == 0
}
@@ -45,7 +51,7 @@ func (m *IdModel) FillBaseInfo(idGenType IdGenType, account *LoginAccount) {
if !m.IsCreate() {
return
}
m.Id = GetIdByGenType(idGenType)
m.SetId(GetIdByGenType(idGenType))
}
// 含有删除字段模型

View File

@@ -1,6 +1,9 @@
package collx
import "strings"
import (
"mayfly-go/pkg/utils/anyx"
"strings"
)
// 数组比较
// 依次返回,新增值,删除值,以及不变值
@@ -131,6 +134,13 @@ func ArrayRemoveFunc[T any](arr []T, isDeleteFunc func(T) bool) []T {
return newArr
}
// ArrayRemoveBlank 移除元素中的空元素
func ArrayRemoveBlank[T any](arr []T) []T {
return ArrayRemoveFunc(arr, func(val T) bool {
return anyx.IsBlank(val)
})
}
// 数组元素去重
func ArrayDeduplicate[T comparable](arr []T) []T {
encountered := map[T]bool{}
@@ -155,3 +165,14 @@ func ArrayAnyMatches(arr []string, subStr string) bool {
}
return false
}
// ArrayFilter 过滤函数,根据提供的条件函数将切片中的元素进行过滤
func ArrayFilter[T any](array []T, fn func(T) bool) []T {
var filtered []T
for _, val := range array {
if fn(val) {
filtered = append(filtered, val)
}
}
return filtered
}

View File

@@ -335,10 +335,6 @@ CREATE TABLE `t_machine` (
`ip` varchar(50) NOT NULL,
`port` int(12) NOT NULL,
`protocol` tinyint(2) NULL COMMENT '协议 1、SSH 2、RDP',
`username` varchar(12) NOT NULL,
`auth_method` tinyint(2) DEFAULT NULL COMMENT '1.密码登录2.publickey登录',
`password` varchar(100) DEFAULT NULL,
`auth_cert_id` bigint(20) DEFAULT NULL COMMENT '授权凭证id',
`ssh_tunnel_machine_id` bigint(20) DEFAULT NULL COMMENT 'ssh隧道的机器id',
`enable_recorder` tinyint(2) DEFAULT NULL COMMENT '是否启用终端回放记录',
`status` tinyint(2) NOT NULL COMMENT '状态: 1:启用; -1:禁用',
@@ -893,6 +889,7 @@ DROP TABLE IF EXISTS `t_tag_tree`;
CREATE TABLE `t_tag_tree` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`pid` bigint(20) NOT NULL DEFAULT '0',
`type` tinyint NOT NULL DEFAULT '-1' COMMENT '类型: -1.普通标签; 其他值则为对应的资源类型',
`code` varchar(36) NOT NULL COMMENT '标识符',
`code_path` varchar(255) NOT NULL COMMENT '标识符路径',
`name` varchar(36) DEFAULT NULL COMMENT '名称',
@@ -997,6 +994,31 @@ BEGIN;
INSERT INTO `t_team_member` VALUES (7, 3, 1, 'admin', '2022-10-26 20:04:36', 1, 'admin', '2022-10-26 20:04:36', 1, 'admin', 0, NULL);
COMMIT;
DROP TABLE IF EXISTS `t_resource_auth_cert`;
-- 资源授权凭证
CREATE TABLE `t_resource_auth_cert` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(100) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '账号名称',
`resource_code` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '资源编码',
`resource_type` tinyint NOT NULL COMMENT '资源类型',
`type` tinyint DEFAULT NULL COMMENT '凭证类型',
`username` varchar(100) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '用户名',
`ciphertext` varchar(5000) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '密文内容',
`ciphertext_type` tinyint NOT NULL COMMENT '密文类型(-1.公共授权凭证 1.密码 2.秘钥)',
`extra` varchar(200) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '账号需要的其他额外信息(如秘钥口令等)',
`remark` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '备注',
`create_time` datetime NOT NULL,
`creator_id` bigint NOT NULL,
`creator` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
`update_time` datetime NOT NULL,
`modifier_id` bigint NOT NULL,
`modifier` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
`is_deleted` tinyint DEFAULT '0',
`delete_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_resource_code` (`resource_code`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=43 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='资源授权凭证表';
DROP TABLE IF EXISTS `t_flow_procdef`;
-- 工单流程相关表
CREATE TABLE `t_flow_procdef` (

View File

@@ -55,6 +55,9 @@ update `t_machine` set `protocol` = 1 where `protocol` is NULL;
delete from `t_sys_config` where `key` = 'MachineConfig';
INSERT INTO t_sys_config ( name, `key`, params, value, remark, permission, create_time, creator_id, creator, update_time, modifier_id, modifier, is_deleted, delete_time) VALUES('机器相关配置', 'MachineConfig', '[{"name":"终端回放存储路径","model":"terminalRecPath","placeholder":"终端回放存储路径"},{"name":"uploadMaxFileSize","model":"uploadMaxFileSize","placeholder":"允许上传的最大文件大小(1MB、2GB等)"},{"model":"termOpSaveDays","name":"终端记录保存时间","placeholder":"终端记录保存时间(单位天)"},{"model":"guacdHost","name":"guacd服务ip","placeholder":"guacd服务ip默认 127.0.0.1","required":false},{"name":"guacd服务端口","model":"guacdPort","placeholder":"guacd服务端口默认 4822","required":false},{"model":"guacdFilePath","name":"guacd服务文件存储位置","placeholder":"guacd服务文件存储位置用于挂载RDP文件夹"},{"name":"guacd服务记录存储位置","model":"guacdRecPath","placeholder":"guacd服务记录存储位置用于记录rdp操作记录"}]', '{"terminalRecPath":"./rec","uploadMaxFileSize":"1000MB","termOpSaveDays":"30","guacdHost":"","guacdPort":"","guacdFilePath":"./guacd/rdp-file","guacdRecPath":"./guacd/rdp-rec"}', '机器相关配置,如终端回放路径等', 'all', '2023-07-13 16:26:44', 1, 'admin', '2024-04-06 12:25:03', 1, 'admin', 0, NULL);
ALTER TABLE t_tag_tree ADD `type` tinyint NOT NULL DEFAULT '-1' COMMENT '类型: -1.普通标签; 其他值则为对应的资源类型';
BEGIN;
INSERT
INTO
@@ -88,5 +91,34 @@ from
WHERE
is_deleted = 0;
DROP TABLE t_tag_tree;
COMMIT;
DROP TABLE t_tag_resource;
COMMIT;
-- 资源授权凭证
CREATE TABLE `t_resource_auth_cert` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(100) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '账号名称',
`resource_code` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '资源编码',
`resource_type` tinyint NOT NULL COMMENT '资源类型',
`type` tinyint DEFAULT NULL COMMENT '凭证类型',
`username` varchar(100) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '用户名',
`ciphertext` varchar(5000) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '密文内容',
`ciphertext_type` tinyint NOT NULL COMMENT '密文类型(-1.公共授权凭证 1.密码 2.秘钥)',
`extra` varchar(200) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '账号需要的其他额外信息(如秘钥口令等)',
`remark` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '备注',
`create_time` datetime NOT NULL,
`creator_id` bigint NOT NULL,
`creator` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
`update_time` datetime NOT NULL,
`modifier_id` bigint NOT NULL,
`modifier` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
`is_deleted` tinyint DEFAULT '0',
`delete_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_resource_code` (`resource_code`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=43 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='资源授权凭证表';
-- 删除机器表 账号相关字段
ALTER TABLE t_machine DROP COLUMN username;
ALTER TABLE t_machine DROP COLUMN password;
ALTER TABLE t_machine DROP COLUMN auth_cert_id;