{
// 非标签类型不可添加子标签
- return data.type != -1;
+ return data.type != TagResourceTypeEnum.Tag.value || (data.children && data.children?.[0].type != TagResourceTypeEnum.Tag.value);
})
.withOnClick((data: any) => showSaveTagDialog(data));
@@ -180,7 +179,7 @@ const contextmenuDel = new ContextmenuItem('delete', '删除')
.withPermission('tag:del')
.withHideFunc((data: any) => {
// 存在子标签,则不允许删除
- return data.children || data.type != -1;
+ return data.children || data.type != TagResourceTypeEnum.Tag.value;
})
.withOnClick((data: any) => deleteTag(data));
@@ -339,15 +338,6 @@ const deleteTag = (data: any) => {
});
};
-// const changeStatus = async (data: any, status: any) => {
-// await resourceApi.changeStatus.request({
-// id: data.id,
-// status: status,
-// });
-// data.status = status;
-// ElMessage.success((status === 1 ? '启用' : '禁用') + '成功!');
-// };
-
// 节点被展开时触发的事件
const handleNodeExpand = (data: any, node: any) => {
const id: any = node.data.id;
@@ -393,14 +383,4 @@ const removeDeafultExpandId = (id: any) => {
min-width: 100%;
}
}
-
-.none-select {
- moz-user-select: -moz-none;
- -moz-user-select: none;
- -o-user-select: none;
- -khtml-user-select: none;
- -webkit-user-select: none;
- -ms-user-select: none;
- user-select: none;
-}
diff --git a/mayfly_go_web/src/views/ops/tag/api.ts b/mayfly_go_web/src/views/ops/tag/api.ts
index 62999cb8..91217c53 100644
--- a/mayfly_go_web/src/views/ops/tag/api.ts
+++ b/mayfly_go_web/src/views/ops/tag/api.ts
@@ -20,3 +20,7 @@ export const tagApi = {
getTeamTagIds: Api.newGet('/teams/{teamId}/tags'),
saveTeamTags: Api.newPost('/teams/{teamId}/tags'),
};
+
+export const resourceAuthCertApi = {
+ listByQuery: Api.newGet('/auth-certs'),
+};
diff --git a/mayfly_go_web/src/views/ops/tag/enums.ts b/mayfly_go_web/src/views/ops/tag/enums.ts
new file mode 100644
index 00000000..dc278973
--- /dev/null
+++ b/mayfly_go_web/src/views/ops/tag/enums.ts
@@ -0,0 +1,15 @@
+import { EnumValue } from '@/common/Enum';
+
+// 授权凭证类型
+export const AuthCertTypeEnum = {
+ Private: EnumValue.of(1, '普通账号').tagTypeSuccess(),
+ Privileged: EnumValue.of(11, '特权账号').tagTypeSuccess(),
+ PrivateDefault: EnumValue.of(12, '默认账号').tagTypeSuccess(),
+};
+
+// 授权凭证密文类型
+export const AuthCertCiphertextTypeEnum = {
+ Password: EnumValue.of(1, '密码').tagTypeSuccess(),
+ PrivateKey: EnumValue.of(2, '秘钥').tagTypeSuccess(),
+ Public: EnumValue.of(-1, '公共凭证').tagTypeSuccess(),
+};
diff --git a/server/internal/common/consts/consts.go b/server/internal/common/consts/consts.go
index 37b85e18..0eba158b 100644
--- a/server/internal/common/consts/consts.go
+++ b/server/internal/common/consts/consts.go
@@ -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"
diff --git a/server/internal/db/application/db.go b/server/internal/db/application/db.go
index 37be8288..eaa45d6b 100644
--- a/server/internal/db/application/db.go
+++ b/server/internal/db/application/db.go
@@ -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,
})
})
}
diff --git a/server/internal/machine/api/dashbord.go b/server/internal/machine/api/dashbord.go
index 935d0893..519e4f67 100644
--- a/server/internal/machine/api/dashbord.go
+++ b/server/internal/machine/api/dashbord.go
@@ -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,
diff --git a/server/internal/machine/api/form/form.go b/server/internal/machine/api/form/form.go
index 62505c5a..0d4bdb6e 100644
--- a/server/internal/machine/api/form/form.go
+++ b/server/internal/machine/api/form/form.go
@@ -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"`
diff --git a/server/internal/machine/api/form/machine_file.go b/server/internal/machine/api/form/machine_file.go
new file mode 100644
index 00000000..06f03216
--- /dev/null
+++ b/server/internal/machine/api/form/machine_file.go
@@ -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"`
+}
diff --git a/server/internal/machine/api/machine.go b/server/internal/machine/api/machine.go
index 9dd3bb6f..38cc721c 100644
--- a/server/internal/machine/api/machine.go
+++ b/server/internal/machine/api/machine.go
@@ -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
+}
diff --git a/server/internal/machine/api/machine_file.go b/server/internal/machine/api/machine_file.go
index 8fec5585..baba59d3 100644
--- a/server/internal/machine/api/machine_file.go
+++ b/server/internal/machine/api/machine_file.go
@@ -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")
}
diff --git a/server/internal/machine/api/vo/vo.go b/server/internal/machine/api/vo/vo.go
index e7e2ed11..0715d866 100644
--- a/server/internal/machine/api/vo/vo.go
+++ b/server/internal/machine/api/vo/vo.go
@@ -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:"-"`
}
diff --git a/server/internal/machine/application/machine.go b/server/internal/machine/application/machine.go
index 42800147..d8a4f960 100644
--- a/server/internal/machine/application/machine.go
+++ b/server/internal/machine/application/machine.go
@@ -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
}
diff --git a/server/internal/machine/application/machine_file.go b/server/internal/machine/application/machine_file.go
index 0ab745c8..016ccb2e 100644
--- a/server/internal/machine/application/machine_file.go
+++ b/server/internal/machine/application/machine_file.go
@@ -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)
}
diff --git a/server/internal/machine/domain/entity/machine.go b/server/internal/machine/domain/entity/machine.go
index fe93dc27..87a55c88 100644
--- a/server/internal/machine/domain/entity/machine.go
+++ b/server/internal/machine/domain/entity/machine.go
@@ -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
-}
diff --git a/server/internal/machine/guac/guac.go b/server/internal/machine/guac/guac.go
index a1d6f4f9..f32d87a9 100644
--- a/server/internal/machine/guac/guac.go
+++ b/server/internal/machine/guac/guac.go
@@ -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
diff --git a/server/internal/machine/guac/mem_session.go b/server/internal/machine/guac/mem_session.go
index 5c441315..9e96f650 100644
--- a/server/internal/machine/guac/mem_session.go
+++ b/server/internal/machine/guac/mem_session.go
@@ -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]
diff --git a/server/internal/machine/mcm/client_cache.go b/server/internal/machine/mcm/client_cache.go
index f40f1a12..cfb12181 100644
--- a/server/internal/machine/mcm/client_cache.go
+++ b/server/internal/machine/mcm/client_cache.go
@@ -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)
+ }
+ }
}
// 检查缓存中的客户端是否可用,不可用则关闭客户端连接
diff --git a/server/internal/machine/mcm/machine.go b/server/internal/machine/mcm/machine.go
index f6c90c9f..a841ca6f 100644
--- a/server/internal/machine/mcm/machine.go
+++ b/server/internal/machine/mcm/machine.go
@@ -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
diff --git a/server/internal/machine/router/machine.go b/server/internal/machine/router/machine.go
index 0393c8c2..88487cf5 100644
--- a/server/internal/machine/router/machine.go
+++ b/server/internal/machine/router/machine.go
@@ -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)
}
}
diff --git a/server/internal/mongo/application/mongo.go b/server/internal/mongo/application/mongo.go
index 1f01b019..7046cf0e 100644
--- a/server/internal/mongo/application/mongo.go
+++ b/server/internal/mongo/application/mongo.go
@@ -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,
})
diff --git a/server/internal/redis/application/redis.go b/server/internal/redis/application/redis.go
index dbc2c8e8..cd166c5d 100644
--- a/server/internal/redis/application/redis.go
+++ b/server/internal/redis/application/redis.go
@@ -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,
})
})
diff --git a/server/internal/tag/api/resource_auth_cert.go b/server/internal/tag/api/resource_auth_cert.go
new file mode 100644
index 00000000..6a321fef
--- /dev/null
+++ b/server/internal/tag/api/resource_auth_cert.go
@@ -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
+}
diff --git a/server/internal/tag/api/tag_tree.go b/server/internal/tag/api/tag_tree.go
index 1f3850d1..a91f586f 100644
--- a/server/internal/tag/api/tag_tree.go
+++ b/server/internal/tag/api/tag_tree.go
@@ -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)
diff --git a/server/internal/tag/api/vo/resource_auth_cert.go b/server/internal/tag/api/vo/resource_auth_cert.go
new file mode 100644
index 00000000..115877da
--- /dev/null
+++ b/server/internal/tag/api/vo/resource_auth_cert.go
@@ -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"`
+}
diff --git a/server/internal/tag/application/application.go b/server/internal/tag/application/application.go
index d6ff107b..0873dca8 100644
--- a/server/internal/tag/application/application.go
+++ b/server/internal/tag/application/application.go
@@ -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"))
}
diff --git a/server/internal/tag/application/resouce_auth_cert.go b/server/internal/tag/application/resouce_auth_cert.go
new file mode 100644
index 00000000..81ff332b
--- /dev/null
+++ b/server/internal/tag/application/resouce_auth_cert.go
@@ -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
+}
diff --git a/server/internal/tag/application/tag_tree.go b/server/internal/tag/application/tag_tree.go
index f422d755..ba1a4f07 100644
--- a/server/internal/tag/application/tag_tree.go
+++ b/server/internal/tag/application/tag_tree.go
@@ -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)})
}
}
diff --git a/server/internal/tag/domain/entity/query.go b/server/internal/tag/domain/entity/query.go
index 2dad011e..a42feab5 100644
--- a/server/internal/tag/domain/entity/query.go
+++ b/server/internal/tag/domain/entity/query.go
@@ -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
diff --git a/server/internal/tag/domain/entity/resource_auth_cert.go b/server/internal/tag/domain/entity/resource_auth_cert.go
new file mode 100644
index 00000000..ded1c38d
--- /dev/null
+++ b/server/internal/tag/domain/entity/resource_auth_cert.go
@@ -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)
+}
diff --git a/server/internal/tag/domain/entity/tag_tree.go b/server/internal/tag/domain/entity/tag_tree.go
index d535214b..e3a54503 100644
--- a/server/internal/tag/domain/entity/tag_tree.go
+++ b/server/internal/tag/domain/entity/tag_tree.go
@@ -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 + "/"
}
// 标签接口资源,如果要实现资源结构体填充标签信息,则资源结构体需要实现该接口
diff --git a/server/internal/tag/domain/repository/resource_auth_cert.go b/server/internal/tag/domain/repository/resource_auth_cert.go
new file mode 100644
index 00000000..24ae3cde
--- /dev/null
+++ b/server/internal/tag/domain/repository/resource_auth_cert.go
@@ -0,0 +1,10 @@
+package repository
+
+import (
+ "mayfly-go/internal/tag/domain/entity"
+ "mayfly-go/pkg/base"
+)
+
+type ResourceAuthCert interface {
+ base.Repo[*entity.ResourceAuthCert]
+}
diff --git a/server/internal/tag/infrastructure/persistence/persistence.go b/server/internal/tag/infrastructure/persistence/persistence.go
index 0f7f4df1..3f7f9432 100644
--- a/server/internal/tag/infrastructure/persistence/persistence.go
+++ b/server/internal/tag/infrastructure/persistence/persistence.go
@@ -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"))
}
diff --git a/server/internal/tag/infrastructure/persistence/resource_auth_cert.go b/server/internal/tag/infrastructure/persistence/resource_auth_cert.go
new file mode 100644
index 00000000..636bfdb3
--- /dev/null
+++ b/server/internal/tag/infrastructure/persistence/resource_auth_cert.go
@@ -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)}}
+}
diff --git a/server/internal/tag/router/resource_auth_cert.go b/server/internal/tag/router/resource_auth_cert.go
new file mode 100644
index 00000000..a490fa68
--- /dev/null
+++ b/server/internal/tag/router/resource_auth_cert.go
@@ -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[:])
+ }
+}
diff --git a/server/internal/tag/router/router.go b/server/internal/tag/router/router.go
index 4356c36e..617eb1d0 100644
--- a/server/internal/tag/router/router.go
+++ b/server/internal/tag/router/router.go
@@ -5,4 +5,5 @@ import "github.com/gin-gonic/gin"
func Init(router *gin.RouterGroup) {
InitTagTreeRouter(router)
InitTeamRouter(router)
+ InitResourceAuthCertRouter(router)
}
diff --git a/server/pkg/base/app.go b/server/pkg/base/app.go
index aee5b5ec..75795930 100644
--- a/server/pkg/base/app.go
+++ b/server/pkg/base/app.go
@@ -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结构体
diff --git a/server/pkg/base/repo.go b/server/pkg/base/repo.go
index 13b64a5f..93a0561a 100644
--- a/server/pkg/base/repo.go
+++ b/server/pkg/base/repo.go
@@ -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...)
}
diff --git a/server/pkg/gormx/gormx.go b/server/pkg/gormx/gormx.go
index 61a8eed5..e1a4654e 100644
--- a/server/pkg/gormx/gormx.go
+++ b/server/pkg/gormx/gormx.go
@@ -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 数据库映射实体模型
diff --git a/server/pkg/model/model.go b/server/pkg/model/model.go
index a8d179b6..ac1608bf 100644
--- a/server/pkg/model/model.go
+++ b/server/pkg/model/model.go
@@ -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))
}
// 含有删除字段模型
diff --git a/server/pkg/utils/collx/array.go b/server/pkg/utils/collx/array.go
index 2c3453a1..180612bd 100644
--- a/server/pkg/utils/collx/array.go
+++ b/server/pkg/utils/collx/array.go
@@ -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
+}
diff --git a/server/resources/script/sql/mayfly-go.sql b/server/resources/script/sql/mayfly-go.sql
index 9eada2ec..bbb0d583 100644
--- a/server/resources/script/sql/mayfly-go.sql
+++ b/server/resources/script/sql/mayfly-go.sql
@@ -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` (
diff --git a/server/resources/script/sql/v1.7/v1.7.5.sql b/server/resources/script/sql/v1.7/v1.7.5.sql
index 65c1adc9..b43d5544 100644
--- a/server/resources/script/sql/v1.7/v1.7.5.sql
+++ b/server/resources/script/sql/v1.7/v1.7.5.sql
@@ -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;
\ No newline at end of file
+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;