diff --git a/README.md b/README.md index 9901a466..b6d9f193 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ ## 前言 -Web版 **统一管理操作平台**,集成了对Linux系统的全面操作支持(包括终端管理[终端回放、命令过滤]、文件管理、脚本执行、进程监控及计划任务设置),同时提供了多种数据库(如 MySQL、PostgreSQL、Oracle、SQL Server、达梦、高斯、SQLite 等)的数据操作、数据同步与数据迁移功能。此外,还支持 Redis(单机、哨兵、集群模式)以及 MongoDB 的操作管理,并结合工单流程审批功能,为企业提供一站式的运维与管理解决方案。 +Web 版 **统一管理操作平台**,集成了对 Linux 系统的全面操作支持(包括终端管理[终端回放、命令过滤]、文件管理、脚本执行、进程监控及计划任务设置),同时提供了多种数据库(如 MySQL、PostgreSQL、Oracle、SQL Server、达梦、高斯、SQLite 等)的数据操作、数据同步与数据迁移功能。此外,还支持 Redis(单机、哨兵、集群模式)、 MongoDB 、Es 的操作管理,并结合工单流程审批功能,为企业提供一站式的运维与管理解决方案。 ## 开发语言与主要框架 diff --git a/README_EN.md b/README_EN.md index fd4016b0..cccccb8e 100644 --- a/README_EN.md +++ b/README_EN.md @@ -28,7 +28,7 @@ ## Preface -Web-based **Unified Management and Operation Platform**, integrating comprehensive operation support for Linux systems (including terminal management [terminal playback, command filtering], file management, script execution, process monitoring, and cronjob settings). It also provides data operation, data synchronization, and data migration for multiple databases (such as MySQL, PostgreSQL, Oracle, SQL Server, Dameng, Gauss, SQLite, etc.). Additionally, it supports Redis operations (standalone, sentinel, and cluster modes) and MongoDB management, combined with work order process approval functionality to offer enterprises an all-in-one solution for operations and management. +Web-based **Unified Management and Operation Platform**, integrating comprehensive operation support for Linux systems (including terminal management [terminal playback, command filtering], file management, script execution, process monitoring, and cronjob settings). It also provides data operation, data synchronization, and data migration for multiple databases (such as MySQL, PostgreSQL, Oracle, SQL Server, Dameng, Gauss, SQLite, etc.). Additionally, it supports Redis operations (standalone, sentinel, and cluster modes) and MongoDB、Es management, combined with work order process approval functionality to offer enterprises an all-in-one solution for operations and management. ## Development languages and major frameworks diff --git a/frontend/package.json b/frontend/package.json index 53322574..0ddd3946 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -24,7 +24,7 @@ "crypto-js": "^4.2.0", "dayjs": "^1.11.13", "echarts": "^5.6.0", - "element-plus": "^2.9.10", + "element-plus": "^2.9.11", "js-base64": "^3.7.7", "jsencrypt": "^3.3.2", "mitt": "^3.0.1", diff --git a/frontend/src/common/commonEnum.ts b/frontend/src/common/commonEnum.ts index 5ed55897..b08b15d1 100644 --- a/frontend/src/common/commonEnum.ts +++ b/frontend/src/common/commonEnum.ts @@ -16,7 +16,7 @@ export const ResourceTypeEnum = { Redis: EnumValue.of(3, 'redis').setExtra({ icon: 'icon redis/redis', iconColor: 'var(--el-color-danger)' }).tagTypeInfo(), Mongo: EnumValue.of(4, 'mongo').setExtra({ icon: 'icon mongo/mongo', iconColor: 'var(--el-color-success)' }).tagTypeDanger(), AuthCert: EnumValue.of(5, '授权凭证').setExtra({ icon: 'Ticket', iconColor: 'var(--el-color-success)' }), - Es: EnumValue.of(6, 'ES实例').setExtra({ icon: 'Coin', iconColor: 'var(--el-color-warning)' }).tagTypeWarning(), + Es: EnumValue.of(6, 'ES实例').setExtra({ icon: 'icon es/es-color', iconColor: 'var(--el-color-warning)' }).tagTypeWarning(), }; // 标签关联的资源类型 diff --git a/frontend/src/views/ops/component/TagTreeSelect.vue b/frontend/src/views/ops/component/TagTreeSelect.vue index b73581d1..7b3d0208 100644 --- a/frontend/src/views/ops/component/TagTreeSelect.vue +++ b/frontend/src/views/ops/component/TagTreeSelect.vue @@ -1,8 +1,7 @@ diff --git a/frontend/src/views/ops/machine/MachineEdit.vue b/frontend/src/views/ops/machine/MachineEdit.vue index 8476514f..f13acab6 100644 --- a/frontend/src/views/ops/machine/MachineEdit.vue +++ b/frontend/src/views/ops/machine/MachineEdit.vue @@ -1,23 +1,14 @@ diff --git a/frontend/src/views/ops/mongo/MongoList.vue b/frontend/src/views/ops/mongo/MongoList.vue index 858987a2..bae86a9d 100644 --- a/frontend/src/views/ops/mongo/MongoList.vue +++ b/frontend/src/views/ops/mongo/MongoList.vue @@ -12,7 +12,7 @@ lazy > diff --git a/frontend/src/views/ops/redis/RedisEdit.vue b/frontend/src/views/ops/redis/RedisEdit.vue index 574781f1..30d7eb65 100644 --- a/frontend/src/views/ops/redis/RedisEdit.vue +++ b/frontend/src/views/ops/redis/RedisEdit.vue @@ -1,22 +1,13 @@ diff --git a/frontend/src/views/ops/tag/AuthCertList.vue b/frontend/src/views/ops/tag/AuthCertList.vue index add5121e..101683f8 100755 --- a/frontend/src/views/ops/tag/AuthCertList.vue +++ b/frontend/src/views/ops/tag/AuthCertList.vue @@ -8,7 +8,7 @@ :columns="state.columns" > @@ -30,7 +30,7 @@ :title="editor.title" v-model:visible="editor.visible" :auth-cert="editor.authcert" - @confirm="confirmSave" + @confirm="onConfirmSave" @cancel="editor.authcert = {}" :disable-type="state.disableAuthCertType" :disable-ciphertext-type="state.disableAuthCertCiphertextType" @@ -102,7 +102,7 @@ const search = async () => { pageTableRef.value.search(); }; -const edit = (data: any) => { +const onEdit = (data: any) => { state.disableAuthCertType = []; state.disableAuthCertCiphertextType = []; if (data) { @@ -128,14 +128,14 @@ const edit = (data: any) => { state.editor.visible = true; }; -const confirmSave = async (authCert: any) => { +const onConfirmSave = async (authCert: any) => { await resourceAuthCertApi.save.request(authCert); useI18nSaveSuccessMsg(); state.editor.visible = false; search(); }; -const deleteAc = async (data: any) => { +const onDeleteAc = async (data: any) => { try { await useI18nDeleteConfirm(data.name); await resourceAuthCertApi.delete.request({ id: data.id }); diff --git a/frontend/src/views/ops/tag/TagTreeList.vue b/frontend/src/views/ops/tag/TagTreeList.vue index 30dd9787..4de4c3ec 100644 --- a/frontend/src/views/ops/tag/TagTreeList.vue +++ b/frontend/src/views/ops/tag/TagTreeList.vue @@ -10,7 +10,7 @@ v-auth="'tag:save'" type="primary" icon="plus" - @click="showSaveTagDialog(null)" + @click="onShowSaveTagDialog(null)" >
@@ -33,15 +33,15 @@ highlight-current :props="props" :data="data" - @node-expand="handleNodeExpand" - @node-collapse="handleNodeCollapse" - @node-contextmenu="nodeContextmenu" - @node-click="treeNodeClick" + @node-expand="onNodeExpand" + @node-collapse="onNodeCollapse" + @node-contextmenu="onNodeContextmenu" + @node-click="onTreeNodeClick" :default-expanded-keys="defaultExpandedKeys" draggable :allow-drop="allowDrop" :allow-drag="allowDrag" - @node-drop="handleDrop" + @node-drop="onNodeDrop" :expand-on-click-node="false" :filter-node-method="filterNode" > @@ -67,7 +67,7 @@
- + @@ -137,7 +137,7 @@ - + @@ -151,8 +151,8 @@ @@ -223,7 +223,7 @@ const contextmenuAdd = new ContextmenuItem('addTag', 'tag.createSubTag') // 非标签类型不可添加子标签 return data.type != TagResourceTypeEnum.Tag.value || (data.children && data.children?.[0].type != TagResourceTypeEnum.Tag.value); }) - .withOnClick((data: any) => showSaveTagDialog(data)); + .withOnClick((data: any) => onShowSaveTagDialog(data)); const contextmenuEdit = new ContextmenuItem('edit', 'common.edit') .withIcon('edit') @@ -231,7 +231,7 @@ const contextmenuEdit = new ContextmenuItem('edit', 'common.edit') .withHideFunc((data: any) => { return data.type != TagResourceTypeEnum.Tag.value; }) - .withOnClick((data: any) => showEditTagDialog(data)); + .withOnClick((data: any) => onShowEditTagDialog(data)); const contextmenuDel = new ContextmenuItem('delete', 'common.delete') .withIcon('delete') @@ -240,7 +240,7 @@ const contextmenuDel = new ContextmenuItem('delete', 'common.delete') // 存在子标签,则不允许删除 return data.children || data.type != TagResourceTypeEnum.Tag.value; }) - .withOnClick((data: any) => deleteTag(data)); + .withOnClick((data: any) => onDeleteTag(data)); const state = reactive({ data: [], @@ -364,7 +364,7 @@ const allowDrag = (node: any) => { ); }; -const handleDrop = async (draggingNode: any, dropNode: any) => { +const onNodeDrop = async (draggingNode: any, dropNode: any) => { const draggingData = draggingNode.data; const dropData = dropNode.data; @@ -378,7 +378,7 @@ const handleDrop = async (draggingNode: any, dropNode: any) => { } }; -const tabChange = () => { +const onTabChange = () => { setNowTabData(); }; @@ -420,20 +420,21 @@ const getDetail = async (id: number) => { }; // 树节点右击事件 -const nodeContextmenu = (event: any, data: any) => { +const onNodeContextmenu = (event: any, data: any) => { const { clientX, clientY } = event; state.contextmenu.dropdown.x = clientX; state.contextmenu.dropdown.y = clientY; contextmenuRef.value.openContextmenu(data); }; -const treeNodeClick = async (data: any) => { +const onTreeNodeClick = async (data: any) => { state.currentTag = await getDetail(data.id); + state.activeTabName = TagDetail; // 关闭可能存在的右击菜单 contextmenuRef.value.closeContextmenu(); }; -const showSaveTagDialog = (data: any) => { +const onShowSaveTagDialog = (data: any) => { if (data) { state.saveTabDialog.form.pid = data.id; state.saveTabDialog.title = t('tag.createSubTagTitle', { codePath: data.codePath }); @@ -443,7 +444,7 @@ const showSaveTagDialog = (data: any) => { state.saveTabDialog.visible = true; }; -const showEditTagDialog = (data: any) => { +const onShowEditTagDialog = (data: any) => { state.saveTabDialog.form.id = data.id; state.saveTabDialog.form.code = data.code; state.saveTabDialog.form.name = data.name; @@ -452,23 +453,23 @@ const showEditTagDialog = (data: any) => { state.saveTabDialog.visible = true; }; -const saveTag = async () => { +const onSaveTag = async () => { await useI18nFormValidate(tagForm); const form = state.saveTabDialog.form; await tagApi.saveTagTree.request(form); useI18nSaveSuccessMsg(); search(); - cancelSaveTag(); + onCancelSaveTag(); state.currentTag = null; }; -const cancelSaveTag = () => { +const onCancelSaveTag = () => { state.saveTabDialog.visible = false; state.saveTabDialog.form = {} as any; tagForm.value.resetFields(); }; -const deleteTag = async (data: any) => { +const onDeleteTag = async (data: any) => { await useI18nDeleteConfirm(data.codePath); await tagApi.delTagTree.request({ id: data.id }); useI18nDeleteSuccessMsg(); @@ -476,7 +477,7 @@ const deleteTag = async (data: any) => { }; // 节点被展开时触发的事件 -const handleNodeExpand = (data: any, node: any) => { +const onNodeExpand = (data: any, node: any) => { const id: any = node.data.id; if (!state.defaultExpandedKeys.includes(id)) { state.defaultExpandedKeys.push(id); @@ -484,7 +485,7 @@ const handleNodeExpand = (data: any, node: any) => { }; // 关闭节点 -const handleNodeCollapse = (data: any, node: any) => { +const onNodeCollapse = (data: any, node: any) => { removeDeafultExpandId(node.data.id); let childNodes = node.childNodes; @@ -493,7 +494,7 @@ const handleNodeCollapse = (data: any, node: any) => { removeDeafultExpandId(cn.data.id); } // 递归删除展开的子节点节点id - handleNodeCollapse(data, cn); + onNodeCollapse(data, cn); } }; diff --git a/frontend/src/views/ops/tag/TeamList.vue b/frontend/src/views/ops/tag/TeamList.vue index 776288e1..5b0c8a48 100755 --- a/frontend/src/views/ops/tag/TeamList.vue +++ b/frontend/src/views/ops/tag/TeamList.vue @@ -10,8 +10,8 @@ :columns="columns" > @@ -23,22 +23,22 @@ @@ -69,8 +69,8 @@ @@ -85,22 +85,22 @@ :columns="showMemDialog.columns" > - + @@ -207,7 +207,7 @@ const search = async () => { pageTableRef.value.search(); }; -const showSaveTeamDialog = async (data: any) => { +const onShowSaveTeamDialog = async (data: any) => { if (data) { state.addTeamDialog.title = useI18nEditTitle('team.team'); state.addTeamDialog.form.id = data.id; @@ -225,7 +225,7 @@ const showSaveTeamDialog = async (data: any) => { state.addTeamDialog.visible = true; }; -const saveTeam = async () => { +const onSaveTeam = async () => { await useI18nFormValidate(teamForm); const form = state.addTeamDialog.form; form.validityStartDate = formatDate(form.validityDate[0]); @@ -233,10 +233,10 @@ const saveTeam = async () => { await tagApi.saveTeam.request(form); useI18nSaveSuccessMsg(); search(); - cancelSaveTeam(); + onCancelSaveTeam(); }; -const cancelSaveTeam = () => { +const onCancelSaveTeam = () => { state.addTeamDialog.visible = false; teamForm.value.resetFields(); setTimeout(() => { @@ -244,7 +244,7 @@ const cancelSaveTeam = () => { }, 500); }; -const deleteTeam = async () => { +const onDeleteTeam = async () => { await useI18nDeleteConfirm(state.selectionData.map((x: any) => x.name).join('、')); await tagApi.delTeam.request({ id: state.selectionData.map((x: any) => x.id).join(',') }); useI18nDeleteSuccessMsg(); @@ -253,13 +253,13 @@ const deleteTeam = async () => { /********** 团队成员相关 ***********/ -const showMembers = async (team: any) => { +const onShowMembers = async (team: any) => { state.showMemDialog.query.teamId = team.id; state.showMemDialog.visible = true; state.showMemDialog.title = t('team.teamMember', { teamName: team.name }); }; -const deleteMember = async (data: any) => { +const onDeleteMember = async (data: any) => { await tagApi.delTeamMem.request(data); useI18nOperateSuccessMsg(); // 重新赋值成员列表 @@ -273,11 +273,11 @@ const setMemebers = async () => { showMemPageTableRef.value.search(); }; -const showAddMemberDialog = () => { +const onShowAddMemberDialog = () => { state.showMemDialog.addVisible = true; }; -const addMember = async () => { +const onAddMember = async () => { const memForm = state.showMemDialog.memForm; memForm.teamId = state.showMemDialog.query.teamId; notBlank(memForm.accountIds, t('team.selectAccountTips')); @@ -285,10 +285,10 @@ const addMember = async () => { await tagApi.saveTeamMem.request(memForm); useI18nSaveSuccessMsg(); setMemebers(); - cancelAddMember(); + onCancelAddMember(); }; -const cancelAddMember = () => { +const onCancelAddMember = () => { state.showMemDialog.memForm = {} as any; state.showMemDialog.addVisible = false; }; diff --git a/frontend/src/views/system/account/AccountEdit.vue b/frontend/src/views/system/account/AccountEdit.vue index 6c711b7e..90ba56e8 100755 --- a/frontend/src/views/system/account/AccountEdit.vue +++ b/frontend/src/views/system/account/AccountEdit.vue @@ -1,7 +1,7 @@ diff --git a/frontend/src/views/system/config/ConfigList.vue b/frontend/src/views/system/config/ConfigList.vue index e37bae8c..421cad0f 100755 --- a/frontend/src/views/system/config/ConfigList.vue +++ b/frontend/src/views/system/config/ConfigList.vue @@ -9,7 +9,7 @@ :data-handler-fn="handleData" > - +
@@ -143,7 +143,7 @@ const showSetConfigDialog = (row: any) => { state.paramsDialog.visible = true; }; -const closeSetConfigDialog = () => { +const onCloseSetConfigDialog = () => { state.paramsDialog.visible = false; setTimeout(() => { state.paramsDialog.config = {}; @@ -182,7 +182,7 @@ const setConfig = async () => { value: paramsValue, }); useI18nSaveSuccessMsg(); - closeSetConfigDialog(); + onCloseSetConfigDialog(); search(); }; @@ -195,12 +195,12 @@ const hasParam = (paramKey: string, paramItems: any) => { return false; }; -const configEditChange = () => { +const onConfigEditChange = () => { useI18nSaveSuccessMsg(); search(); }; -const editConfig = (data: any) => { +const onEditConfig = (data: any) => { if (data) { state.configEdit.title = 'common.edit'; state.configEdit.config = data; diff --git a/frontend/src/views/system/resource/ResourceEdit.vue b/frontend/src/views/system/resource/ResourceEdit.vue index 229772d6..c0e4cc93 100644 --- a/frontend/src/views/system/resource/ResourceEdit.vue +++ b/frontend/src/views/system/resource/ResourceEdit.vue @@ -1,7 +1,7 @@ diff --git a/server/internal/auth/api/account_login.go b/server/internal/auth/api/account_login.go index 5a48bdb1..7de6e9e8 100644 --- a/server/internal/auth/api/account_login.go +++ b/server/internal/auth/api/account_login.go @@ -47,7 +47,7 @@ func (a *AccountLogin) ReqConfs() *req.Confs { // @router /auth/accounts/login [post] func (a *AccountLogin) Login(rc *req.Ctx) { - loginForm := req.BindJsonAndValid(rc, new(form.LoginForm)) + loginForm := req.BindJsonAndValid[*form.LoginForm](rc) ctx := rc.MetaCtx accountLoginSecurity := config.GetAccountLoginSecurity() @@ -96,8 +96,7 @@ type OtpVerifyInfo struct { // OTP双因素校验 func (a *AccountLogin) OtpVerify(rc *req.Ctx) { - otpVerify := new(form.OtpVerfiy) - req.BindJsonAndValid(rc, otpVerify) + otpVerify := req.BindJsonAndValid[*form.OtpVerfiy](rc) ctx := rc.MetaCtx tokenKey := fmt.Sprintf("otp:token:%s", otpVerify.OtpToken) diff --git a/server/internal/auth/api/ldap_login.go b/server/internal/auth/api/ldap_login.go index 282a594d..d9193e65 100644 --- a/server/internal/auth/api/ldap_login.go +++ b/server/internal/auth/api/ldap_login.go @@ -47,7 +47,7 @@ func (a *LdapLogin) GetLdapEnabled(rc *req.Ctx) { // @router /auth/ldap/login [post] func (a *LdapLogin) Login(rc *req.Ctx) { - loginForm := req.BindJsonAndValid(rc, new(form.LoginForm)) + loginForm := req.BindJsonAndValid[*form.LoginForm](rc) ctx := rc.MetaCtx accountLoginSecurity := config.GetAccountLoginSecurity() // 判断是否有开启登录验证码校验 diff --git a/server/internal/db/api/db.go b/server/internal/db/api/db.go index f826f145..c4aa8847 100644 --- a/server/internal/db/api/db.go +++ b/server/internal/db/api/db.go @@ -77,7 +77,7 @@ func (d *Db) ReqConfs() *req.Confs { // @router /api/dbs [get] func (d *Db) Dbs(rc *req.Ctx) { - queryCond := req.BindQuery[*entity.DbQuery](rc, new(entity.DbQuery)) + queryCond := req.BindQuery[*entity.DbQuery](rc) // 不存在可访问标签id,即没有可操作数据 tags := d.tagApp.GetAccountTags(rc.GetLoginAccount().Id, &tagentity.TagTreeQuery{ @@ -115,9 +115,7 @@ func (d *Db) Dbs(rc *req.Ctx) { } func (d *Db) Save(rc *req.Ctx) { - form := &form.DbForm{} - db := req.BindJsonAndCopyTo[*entity.Db](rc, form, new(entity.Db)) - + form, db := req.BindJsonAndCopyTo[*form.DbForm, *entity.Db](rc) rc.ReqParam = form biz.ErrIsNil(d.dbApp.SaveDb(rc.MetaCtx, db)) @@ -137,7 +135,7 @@ func (d *Db) DeleteDb(rc *req.Ctx) { /** 数据库操作相关、执行sql等 ***/ func (d *Db) ExecSql(rc *req.Ctx) { - form := req.BindJsonAndValid(rc, new(form.DbSqlExecForm)) + form := req.BindJsonAndValid[*form.DbSqlExecForm](rc) ctx, cancel := context.WithTimeout(rc.MetaCtx, time.Duration(config.GetDbms().SqlExecTl)*time.Second) defer cancel() @@ -351,8 +349,7 @@ func (d *Db) GetSchemas(rc *req.Ctx) { } func (d *Db) CopyTable(rc *req.Ctx) { - form := &form.DbCopyTableForm{} - copy := req.BindJsonAndCopyTo[*dbi.DbCopyTable](rc, form, new(dbi.DbCopyTable)) + form, copy := req.BindJsonAndCopyTo[*form.DbCopyTableForm, *dbi.DbCopyTable](rc) conn, err := d.dbApp.GetDbConn(rc.MetaCtx, form.Id, form.Db) biz.ErrIsNilAppendErr(err, "copy table error: %s") diff --git a/server/internal/db/api/db_data_sync.go b/server/internal/db/api/db_data_sync.go index 9411426f..1af279c0 100644 --- a/server/internal/db/api/db_data_sync.go +++ b/server/internal/db/api/db_data_sync.go @@ -50,22 +50,21 @@ func (d *DataSyncTask) ReqConfs() *req.Confs { } func (d *DataSyncTask) Tasks(rc *req.Ctx) { - queryCond := req.BindQuery[*entity.DataSyncTaskQuery](rc, new(entity.DataSyncTaskQuery)) + queryCond := req.BindQuery[*entity.DataSyncTaskQuery](rc) res, err := d.dataSyncTaskApp.GetPageList(queryCond) biz.ErrIsNil(err) rc.ResData = model.PageResultConv[*entity.DataSyncTask, *vo.DataSyncTaskListVO](res) } func (d *DataSyncTask) Logs(rc *req.Ctx) { - queryCond := req.BindQuery(rc, new(entity.DataSyncLogQuery)) + queryCond := req.BindQuery[*entity.DataSyncLogQuery](rc) res, err := d.dataSyncTaskApp.GetTaskLogList(queryCond) biz.ErrIsNil(err) rc.ResData = model.PageResultConv[*entity.DataSyncLog, *vo.DataSyncLogListVO](res) } func (d *DataSyncTask) SaveTask(rc *req.Ctx) { - form := &form.DataSyncTaskForm{} - task := req.BindJsonAndCopyTo[*entity.DataSyncTask](rc, form, new(entity.DataSyncTask)) + form, task := req.BindJsonAndCopyTo[*form.DataSyncTaskForm, *entity.DataSyncTask](rc) // 解码base64 sql sqlStr, err := utils.AesDecryptByLa(task.DataSql, rc.GetLoginAccount()) @@ -89,8 +88,7 @@ func (d *DataSyncTask) DeleteTask(rc *req.Ctx) { } func (d *DataSyncTask) ChangeStatus(rc *req.Ctx) { - form := &form.DataSyncTaskStatusForm{} - task := req.BindJsonAndCopyTo[*entity.DataSyncTask](rc, form, new(entity.DataSyncTask)) + form, task := req.BindJsonAndCopyTo[*form.DataSyncTaskStatusForm, *entity.DataSyncTask](rc) _ = d.dataSyncTaskApp.UpdateById(rc.MetaCtx, task) if task.Status == entity.DataSyncTaskStatusEnable { diff --git a/server/internal/db/api/db_instance.go b/server/internal/db/api/db_instance.go index 3eb90289..f634f41d 100644 --- a/server/internal/db/api/db_instance.go +++ b/server/internal/db/api/db_instance.go @@ -55,7 +55,7 @@ func (d *Instance) ReqConfs() *req.Confs { // Instances 获取数据库实例信息 // @router /api/instances [get] func (d *Instance) Instances(rc *req.Ctx) { - queryCond := req.BindQuery(rc, new(entity.InstanceQuery)) + queryCond := req.BindQuery[*entity.InstanceQuery](rc) tags := d.tagApp.GetAccountTags(rc.GetLoginAccount().Id, &tagentity.TagTreeQuery{ TypePaths: collx.AsArray(tagentity.NewTypePaths(tagentity.TagTypeDbInstance, tagentity.TagTypeAuthCert)), @@ -90,17 +90,14 @@ func (d *Instance) Instances(rc *req.Ctx) { } func (d *Instance) TestConn(rc *req.Ctx) { - form := &form.InstanceForm{} - instance := req.BindJsonAndCopyTo[*entity.DbInstance](rc, form, new(entity.DbInstance)) - + form, instance := req.BindJsonAndCopyTo[*form.InstanceForm, *entity.DbInstance](rc) biz.ErrIsNil(d.instanceApp.TestConn(rc.MetaCtx, instance, form.AuthCerts[0])) } // SaveInstance 保存数据库实例信息 // @router /api/instances [post] func (d *Instance) SaveInstance(rc *req.Ctx) { - form := &form.InstanceForm{} - instance := req.BindJsonAndCopyTo[*entity.DbInstance](rc, form, new(entity.DbInstance)) + form, instance := req.BindJsonAndCopyTo[*form.InstanceForm, *entity.DbInstance](rc) rc.ReqParam = form id, err := d.instanceApp.SaveDbInstance(rc.MetaCtx, &dto.SaveDbInstance{ @@ -135,8 +132,7 @@ func (d *Instance) DeleteInstance(rc *req.Ctx) { // 获取数据库实例的所有数据库名 func (d *Instance) GetDatabaseNames(rc *req.Ctx) { - form := &form.InstanceDbNamesForm{} - instance := req.BindJsonAndCopyTo[*entity.DbInstance](rc, form, new(entity.DbInstance)) + form, instance := req.BindJsonAndCopyTo[*form.InstanceDbNamesForm, *entity.DbInstance](rc) res, err := d.instanceApp.GetDatabases(rc.MetaCtx, instance, form.AuthCert) biz.ErrIsNil(err) rc.ResData = res diff --git a/server/internal/db/api/db_sql.go b/server/internal/db/api/db_sql.go index 42ffdf5a..56f316f7 100644 --- a/server/internal/db/api/db_sql.go +++ b/server/internal/db/api/db_sql.go @@ -30,8 +30,7 @@ func (d *DbSql) ReqConfs() *req.Confs { // @router /api/db/:dbId/sql [post] func (d *DbSql) SaveSql(rc *req.Ctx) { - dbSqlForm := &form.DbSqlSaveForm{} - req.BindJsonAndValid(rc, dbSqlForm) + dbSqlForm := req.BindJsonAndValid[*form.DbSqlSaveForm](rc) rc.ReqParam = dbSqlForm dbId := getDbId(rc) diff --git a/server/internal/db/api/db_sql_exec.go b/server/internal/db/api/db_sql_exec.go index d417ff5f..5b93b508 100644 --- a/server/internal/db/api/db_sql_exec.go +++ b/server/internal/db/api/db_sql_exec.go @@ -26,7 +26,7 @@ func (d *DbSqlExec) ReqConfs() *req.Confs { } func (d *DbSqlExec) DbSqlExecs(rc *req.Ctx) { - queryCond := req.BindQuery(rc, new(entity.DbSqlExecQuery)) + queryCond := req.BindQuery[*entity.DbSqlExecQuery](rc) if statusStr := rc.Query("status"); statusStr != "" { queryCond.Status = collx.ArrayMap[string, int8](strings.Split(statusStr, ","), func(val string) int8 { return cast.ToInt8(val) diff --git a/server/internal/db/api/db_transfer.go b/server/internal/db/api/db_transfer.go index d4281916..0be07f41 100644 --- a/server/internal/db/api/db_transfer.go +++ b/server/internal/db/api/db_transfer.go @@ -61,7 +61,7 @@ func (d *DbTransferTask) ReqConfs() *req.Confs { } func (d *DbTransferTask) Tasks(rc *req.Ctx) { - queryCond := req.BindQuery(rc, new(entity.DbTransferTaskQuery)) + queryCond := req.BindQuery[*entity.DbTransferTaskQuery](rc) res, err := d.dbTransferTask.GetPageList(queryCond) biz.ErrIsNil(err) @@ -78,8 +78,7 @@ func (d *DbTransferTask) Tasks(rc *req.Ctx) { } func (d *DbTransferTask) SaveTask(rc *req.Ctx) { - reqForm := &form.DbTransferTaskForm{} - task := req.BindJsonAndCopyTo[*entity.DbTransferTask](rc, reqForm, new(entity.DbTransferTask)) + reqForm, task := req.BindJsonAndCopyTo[*form.DbTransferTaskForm, *entity.DbTransferTask](rc) rc.ReqParam = reqForm biz.ErrIsNil(d.dbTransferTask.Save(rc.MetaCtx, task)) @@ -98,8 +97,7 @@ func (d *DbTransferTask) DeleteTask(rc *req.Ctx) { } func (d *DbTransferTask) ChangeStatus(rc *req.Ctx) { - form := &form.DbTransferTaskStatusForm{} - task := req.BindJsonAndCopyTo[*entity.DbTransferTask](rc, form, new(entity.DbTransferTask)) + form, task := req.BindJsonAndCopyTo[*form.DbTransferTaskStatusForm, *entity.DbTransferTask](rc) _ = d.dbTransferTask.UpdateById(rc.MetaCtx, task) task, err := d.dbTransferTask.GetById(task.Id) @@ -122,7 +120,7 @@ func (d *DbTransferTask) Stop(rc *req.Ctx) { } func (d *DbTransferTask) Files(rc *req.Ctx) { - queryCond := req.BindQuery(rc, new(entity.DbTransferFileQuery)) + queryCond := req.BindQuery[*entity.DbTransferFileQuery](rc) res, err := d.dbTransferFile.GetPageList(queryCond) biz.ErrIsNil(err) @@ -142,7 +140,7 @@ func (d *DbTransferTask) FileDel(rc *req.Ctx) { } func (d *DbTransferTask) FileRun(rc *req.Ctx) { - fm := req.BindJsonAndValid(rc, &form.DbTransferFileRunForm{}) + fm := req.BindJsonAndValid[*form.DbTransferFileRunForm](rc) rc.ReqParam = fm diff --git a/server/internal/db/application/db_sql_exec.go b/server/internal/db/application/db_sql_exec.go index 89add493..0ba2a3ed 100644 --- a/server/internal/db/application/db_sql_exec.go +++ b/server/internal/db/application/db_sql_exec.go @@ -277,7 +277,7 @@ func (d *dbSqlExecAppImpl) FlowBizHandle(ctx context.Context, bizHandleParam *fl return nil, nil } - execSqlBizForm, err := jsonx.To(procinst.BizForm, new(FlowDbExecSqlBizForm)) + execSqlBizForm, err := jsonx.To[*FlowDbExecSqlBizForm](procinst.BizForm) if err != nil { return nil, errorx.NewBiz("failed to parse the business form information: %s", err.Error()) } diff --git a/server/internal/es/api/es_instance.go b/server/internal/es/api/es_instance.go index 00f87796..9e118061 100644 --- a/server/internal/es/api/es_instance.go +++ b/server/internal/es/api/es_instance.go @@ -51,7 +51,7 @@ func (d *Instance) ReqConfs() *req.Confs { } func (d *Instance) Instances(rc *req.Ctx) { - queryCond := req.BindQuery(rc, new(entity.InstanceQuery)) + queryCond := req.BindQuery[*entity.InstanceQuery](rc) // 只查询实例,兼容没有录入密码的实例 instTags := d.tagApp.GetAccountTags(rc.GetLoginAccount().Id, &tagentity.TagTreeQuery{ @@ -92,8 +92,7 @@ func (d *Instance) Instances(rc *req.Ctx) { } func (d *Instance) TestConn(rc *req.Ctx) { - fm := &form.InstanceForm{} - instance := req.BindJsonAndCopyTo[*entity.EsInstance](rc, fm, new(entity.EsInstance)) + fm, instance := req.BindJsonAndCopyTo[*form.InstanceForm, *entity.EsInstance](rc) var ac *tagentity.ResourceAuthCert if len(fm.AuthCerts) > 0 { @@ -105,8 +104,7 @@ func (d *Instance) TestConn(rc *req.Ctx) { rc.ResData = res } func (d *Instance) SaveInstance(rc *req.Ctx) { - fm := &form.InstanceForm{} - instance := req.BindJsonAndCopyTo[*entity.EsInstance](rc, fm, new(entity.EsInstance)) + fm, instance := req.BindJsonAndCopyTo[*form.InstanceForm, *entity.EsInstance](rc) rc.ReqParam = fm id, err := d.inst.SaveInst(rc.MetaCtx, &dto.SaveEsInstance{ diff --git a/server/internal/es/api/form/instance.go b/server/internal/es/api/form/instance.go index 6b9e6c0e..b8f2d5d0 100644 --- a/server/internal/es/api/form/instance.go +++ b/server/internal/es/api/form/instance.go @@ -5,13 +5,13 @@ import ( ) type InstanceForm struct { - Id uint64 `json:"id"` - Name string `binding:"required" json:"name"` - Host string `binding:"required" json:"host"` - Port int `binding:"required" json:"port"` - Version string `json:"version"` - Remark string `json:"remark"` - SshTunnelMachineId int `json:"sshTunnelMachineId"` + Id uint64 `json:"id"` + Name string `binding:"required" json:"name"` + Host string `binding:"required" json:"host"` + Port int `binding:"required" json:"port"` + Version string `json:"version"` + Remark *string `json:"remark"` + SshTunnelMachineId int `json:"sshTunnelMachineId"` AuthCerts []*tagentity.ResourceAuthCert `json:"authCerts"` // 资产授权凭证信息列表 TagCodePaths []string `binding:"required" json:"tagCodePaths"` diff --git a/server/internal/es/api/vo/instance.go b/server/internal/es/api/vo/instance.go index 880ed877..3852a722 100644 --- a/server/internal/es/api/vo/instance.go +++ b/server/internal/es/api/vo/instance.go @@ -9,16 +9,17 @@ type InstanceListVO struct { tagentity.AuthCerts // 授权凭证信息 tagentity.ResourceTags - Id *int64 `json:"id"` - Code string `json:"code"` - Name *string `json:"name"` - Host *string `json:"host"` - Port *int `json:"port"` - Version *string `json:"version"` + Id *int64 `json:"id"` + Code string `json:"code"` + Name *string `json:"name"` + Host *string `json:"host"` + Port *int `json:"port"` + Version *string `json:"version"` + Remark *string `json:"remark"` + CreateTime *time.Time `json:"createTime"` Creator *string `json:"creator"` CreatorId *int64 `json:"creatorId"` - UpdateTime *time.Time `json:"updateTime"` Modifier *string `json:"modifier"` ModifierId *int64 `json:"modifierId"` diff --git a/server/internal/es/domain/entity/es_instance.go b/server/internal/es/domain/entity/es_instance.go index f1c9f41d..cecdf395 100644 --- a/server/internal/es/domain/entity/es_instance.go +++ b/server/internal/es/domain/entity/es_instance.go @@ -8,14 +8,14 @@ import ( type EsInstance struct { model.Model - Code string `json:"code" gorm:"size:32;not null;"` - Name string `json:"name" gorm:"size:32;not null;"` - Host string `json:"host" gorm:"size:255;not null;"` - Port int `json:"port"` - Network string `json:"network" gorm:"size:20;"` - Version string `json:"version" gorm:"size:50;"` - AuthCertName string `json:"authCertName" gorm:"size:255;"` - SshTunnelMachineId int `json:"sshTunnelMachineId"` // ssh隧道机器id + Code string `json:"code" gorm:"size:32;not null;"` + Name string `json:"name" gorm:"size:32;not null;"` + Host string `json:"host" gorm:"size:255;not null;"` + Port int `json:"port"` + Network string `json:"network" gorm:"size:20;"` + Version string `json:"version" gorm:"size:50;"` + Remark *string `json:"remark" gorm:"size:255;"` + SshTunnelMachineId int `json:"sshTunnelMachineId"` // ssh隧道机器id } func (d *EsInstance) TableName() string { diff --git a/server/internal/flow/api/procdef.go b/server/internal/flow/api/procdef.go index f83539c6..3bb37a05 100644 --- a/server/internal/flow/api/procdef.go +++ b/server/internal/flow/api/procdef.go @@ -48,7 +48,7 @@ func (p *Procdef) ReqConfs() *req.Confs { } func (p *Procdef) GetProcdefPage(rc *req.Ctx) { - cond, page := req.BindQueryAndPage(rc, new(entity.Procdef)) + cond, page := req.BindQueryAndPage[*entity.Procdef](rc) res, err := p.procdefApp.GetPageList(cond, page) biz.ErrIsNil(err) @@ -87,8 +87,7 @@ func (p *Procdef) GetProcdef(rc *req.Ctx) { } func (a *Procdef) Save(rc *req.Ctx) { - form := &form.Procdef{} - procdef := req.BindJsonAndCopyTo(rc, form, new(entity.Procdef)) + form, procdef := req.BindJsonAndCopyTo[*form.Procdef, *entity.Procdef](rc) rc.ReqParam = form biz.ErrIsNil(a.procdefApp.SaveProcdef(rc.MetaCtx, &dto.SaveProcdef{ Procdef: procdef, @@ -98,7 +97,7 @@ func (a *Procdef) Save(rc *req.Ctx) { } func (a *Procdef) SaveFlowDef(rc *req.Ctx) { - form := req.BindJsonAndValid(rc, &form.ProcdefFlow{}) + form := req.BindJsonAndValid[*form.ProcdefFlow](rc) rc.ReqParam = form biz.ErrIsNil(a.procdefApp.SaveFlowDef(rc.MetaCtx, &dto.SaveFlowDef{ diff --git a/server/internal/flow/api/procinst.go b/server/internal/flow/api/procinst.go index 81f25100..b0b25b6e 100644 --- a/server/internal/flow/api/procinst.go +++ b/server/internal/flow/api/procinst.go @@ -35,7 +35,7 @@ func (p *Procinst) ReqConfs() *req.Confs { } func (p *Procinst) GetProcinstPage(rc *req.Ctx) { - cond := req.BindQuery(rc, new(entity.ProcinstQuery)) + cond := req.BindQuery[*entity.ProcinstQuery](rc) // 非管理员只能获取自己申请的流程 if laId := rc.GetLoginAccount().Id; laId != consts.AdminId { cond.CreatorId = laId @@ -47,8 +47,7 @@ func (p *Procinst) GetProcinstPage(rc *req.Ctx) { } func (p *Procinst) ProcinstStart(rc *req.Ctx) { - startForm := new(form.ProcinstStart) - req.BindJsonAndValid(rc, startForm) + startForm := req.BindJsonAndValid[*form.ProcinstStart](rc) _, err := p.procinstApp.StartProc(rc.MetaCtx, startForm.ProcdefId, &dto.StarProc{ BizType: startForm.BizType, BizForm: jsonx.ToStr(startForm.BizForm), diff --git a/server/internal/flow/api/procinst_task.go b/server/internal/flow/api/procinst_task.go index 1ec63530..e314cce1 100644 --- a/server/internal/flow/api/procinst_task.go +++ b/server/internal/flow/api/procinst_task.go @@ -41,7 +41,7 @@ func (p *ProcinstTask) ReqConfs() *req.Confs { } func (p *ProcinstTask) GetTasks(rc *req.Ctx) { - instTaskQuery := req.BindQuery(rc, new(entity.ProcinstTaskQuery)) + instTaskQuery := req.BindQuery[*entity.ProcinstTaskQuery](rc) if laId := rc.GetLoginAccount().Id; laId != consts.AdminId { // 赋值操作人为当前登录账号 instTaskQuery.Assignee = fmt.Sprintf("%d", rc.GetLoginAccount().Id) @@ -74,7 +74,7 @@ func (p *ProcinstTask) GetTasks(rc *req.Ctx) { } func (p *ProcinstTask) PassTask(rc *req.Ctx) { - auditForm := req.BindJsonAndValid(rc, new(form.ProcinstTaskAudit)) + auditForm := req.BindJsonAndValid[*form.ProcinstTaskAudit](rc) rc.ReqParam = auditForm la := rc.GetLoginAccount() @@ -84,7 +84,7 @@ func (p *ProcinstTask) PassTask(rc *req.Ctx) { } func (p *ProcinstTask) RejectTask(rc *req.Ctx) { - auditForm := req.BindJsonAndValid(rc, new(form.ProcinstTaskAudit)) + auditForm := req.BindJsonAndValid[*form.ProcinstTaskAudit](rc) rc.ReqParam = auditForm la := rc.GetLoginAccount() @@ -94,7 +94,7 @@ func (p *ProcinstTask) RejectTask(rc *req.Ctx) { } func (p *ProcinstTask) BackTask(rc *req.Ctx) { - auditForm := req.BindJsonAndValid(rc, new(form.ProcinstTaskAudit)) + auditForm := req.BindJsonAndValid[*form.ProcinstTaskAudit](rc) rc.ReqParam = auditForm biz.ErrIsNil(p.procinstTaskApp.BackTask(rc.MetaCtx, dto.UserTaskOp{TaskId: auditForm.Id, Remark: auditForm.Remark})) } diff --git a/server/internal/flow/application/execution.go b/server/internal/flow/application/execution.go index d371a5a3..b6744fc3 100644 --- a/server/internal/flow/application/execution.go +++ b/server/internal/flow/application/execution.go @@ -104,7 +104,9 @@ func (e *executionAppImpl) MoveTo(ctx *ExecutionCtx, nextNode *entity.FlowNode) } // 记录当前节点结束 - e.hisProcinstOpApp.RecordEnd(ctx, "copmpleted") + if err := e.hisProcinstOpApp.RecordEnd(ctx, "copmpleted"); err != nil { + return err + } // 下一个节点为空,说明流程已结束 if nextNode == nil { @@ -163,7 +165,9 @@ func (e *executionAppImpl) executeNode(ctx *ExecutionCtx) error { } // 节点开始操作记录 - e.hisProcinstOpApp.RecordStart(ctx) + if err := e.hisProcinstOpApp.RecordStart(ctx); err != nil { + return err + } // 执行节点逻辑 return node.Execute(ctx) diff --git a/server/internal/flow/domain/entity/procdef.go b/server/internal/flow/domain/entity/procdef.go index c61817e8..6d9f931b 100644 --- a/server/internal/flow/domain/entity/procdef.go +++ b/server/internal/flow/domain/entity/procdef.go @@ -61,7 +61,7 @@ func (p *Procdef) GetFlowDef() *FlowDef { if p.FlowDef == "" { return nil } - flow, err := jsonx.To(p.FlowDef, new(FlowDef)) + flow, err := jsonx.To[*FlowDef](p.FlowDef) if err != nil { logx.ErrorTrace("parse flow def failed", err) return flow diff --git a/server/internal/flow/domain/entity/procinst.go b/server/internal/flow/domain/entity/procinst.go index 9105b519..79e289d1 100644 --- a/server/internal/flow/domain/entity/procinst.go +++ b/server/internal/flow/domain/entity/procinst.go @@ -43,7 +43,7 @@ func (a *Procinst) SetEnd() { // GetProcdefFlow 获取流程定义信息 func (p *Procinst) GetFlowDef() *FlowDef { - flow, err := jsonx.To(p.FlowDef, new(FlowDef)) + flow, err := jsonx.To[*FlowDef](p.FlowDef) if err != nil { logx.ErrorTrace("parse procdef flow failed", err) return flow diff --git a/server/internal/machine/api/form/form.go b/server/internal/machine/api/form/form.go index 71d46103..688b6b32 100644 --- a/server/internal/machine/api/form/form.go +++ b/server/internal/machine/api/form/form.go @@ -50,12 +50,12 @@ type MachineCronJobForm struct { } type MachineCmdConfForm struct { - Id uint64 `json:"id"` - Name string `json:"name"` - Cmds []string `json:"cmds"` // 命令配置 - Status int8 `json:"execCmds"` // 状态 - Stratege string `json:"stratege"` // 策略,空禁用 - Remark string `json:"remark"` // 备注 + Id uint64 `json:"id"` + Name string `json:"name"` + Cmds model.Slice[string] `json:"cmds"` // 命令配置 + Status int8 `json:"execCmds"` // 状态 + Stratege string `json:"stratege"` // 策略,空禁用 + Remark string `json:"remark"` // 备注 CodePaths []string `json:"codePaths"` } diff --git a/server/internal/machine/api/machine.go b/server/internal/machine/api/machine.go index 59acf925..06061627 100644 --- a/server/internal/machine/api/machine.go +++ b/server/internal/machine/api/machine.go @@ -76,7 +76,7 @@ func (m *Machine) ReqConfs() *req.Confs { } func (m *Machine) Machines(rc *req.Ctx) { - condition := req.BindQuery(rc, new(entity.MachineQuery)) + condition := req.BindQuery[*entity.MachineQuery](rc) tags := m.tagTreeApp.GetAccountTags(rc.GetLoginAccount().Id, &tagentity.TagTreeQuery{ TypePaths: collx.AsArray(tagentity.NewTypePaths(tagentity.TagTypeMachine, tagentity.TagTypeAuthCert)), @@ -143,8 +143,7 @@ func (m *Machine) MachineStats(rc *req.Ctx) { // 保存机器信息 func (m *Machine) SaveMachine(rc *req.Ctx) { - machineForm := new(form.MachineForm) - me := req.BindJsonAndCopyTo(rc, machineForm, new(entity.Machine)) + machineForm, me := req.BindJsonAndCopyTo[*form.MachineForm, *entity.Machine](rc) rc.ReqParam = machineForm @@ -156,8 +155,7 @@ func (m *Machine) SaveMachine(rc *req.Ctx) { } func (m *Machine) TestConn(rc *req.Ctx) { - machineForm := new(form.MachineForm) - me := req.BindJsonAndCopyTo(rc, machineForm, new(entity.Machine)) + machineForm, me := req.BindJsonAndCopyTo[*form.MachineForm, *entity.Machine](rc) // 测试连接 biz.ErrIsNilAppendErr(m.machineApp.TestConn(rc.MetaCtx, me, machineForm.AuthCerts[0]), "connection error: %s") } diff --git a/server/internal/machine/api/machine_cmd_conf.go b/server/internal/machine/api/machine_cmd_conf.go index 541f7578..3efd6b9f 100644 --- a/server/internal/machine/api/machine_cmd_conf.go +++ b/server/internal/machine/api/machine_cmd_conf.go @@ -33,7 +33,7 @@ func (mcc *MachineCmdConf) ReqConfs() *req.Confs { } func (m *MachineCmdConf) MachineCmdConfs(rc *req.Ctx) { - cond := req.BindQuery(rc, new(entity.MachineCmdConf)) + cond := req.BindQuery[*entity.MachineCmdConf](rc) var vos []*vo.MachineCmdConfVO err := m.machineCmdConfApp.ListByCondToAny(cond, &vos) @@ -47,8 +47,7 @@ func (m *MachineCmdConf) MachineCmdConfs(rc *req.Ctx) { } func (m *MachineCmdConf) Save(rc *req.Ctx) { - cmdForm := new(form.MachineCmdConfForm) - mcj := req.BindJsonAndCopyTo[*entity.MachineCmdConf](rc, cmdForm, new(entity.MachineCmdConf)) + cmdForm, mcj := req.BindJsonAndCopyTo[*form.MachineCmdConfForm, *entity.MachineCmdConf](rc) rc.ReqParam = cmdForm err := m.machineCmdConfApp.SaveCmdConf(rc.MetaCtx, &dto.SaveMachineCmdConf{ diff --git a/server/internal/machine/api/machine_cronjob.go b/server/internal/machine/api/machine_cronjob.go index 60cffb8b..4bee4ced 100644 --- a/server/internal/machine/api/machine_cronjob.go +++ b/server/internal/machine/api/machine_cronjob.go @@ -43,7 +43,7 @@ func (mcj *MachineCronJob) ReqConfs() *req.Confs { } func (m *MachineCronJob) MachineCronJobs(rc *req.Ctx) { - cond, pageParam := req.BindQueryAndPage(rc, new(entity.MachineCronJob)) + cond, pageParam := req.BindQueryAndPage[*entity.MachineCronJob](rc) pageRes, err := m.machineCronJobApp.GetPageList(cond, pageParam) biz.ErrIsNil(err) @@ -62,8 +62,7 @@ func (m *MachineCronJob) MachineCronJobs(rc *req.Ctx) { } func (m *MachineCronJob) Save(rc *req.Ctx) { - jobForm := new(form.MachineCronJobForm) - mcj := req.BindJsonAndCopyTo[*entity.MachineCronJob](rc, jobForm, new(entity.MachineCronJob)) + jobForm, mcj := req.BindJsonAndCopyTo[*form.MachineCronJobForm, *entity.MachineCronJob](rc) rc.ReqParam = jobForm err := m.machineCronJobApp.SaveMachineCronJob(rc.MetaCtx, &dto.SaveMachineCronJob{ @@ -90,7 +89,7 @@ func (m *MachineCronJob) RunCronJob(rc *req.Ctx) { } func (m *MachineCronJob) CronJobExecs(rc *req.Ctx) { - cond, pageParam := req.BindQueryAndPage[*entity.MachineCronJobExec](rc, new(entity.MachineCronJobExec)) + cond, pageParam := req.BindQueryAndPage[*entity.MachineCronJobExec](rc) res, err := m.machineCronJobApp.GetExecPageList(cond, pageParam) biz.ErrIsNil(err) rc.ResData = res diff --git a/server/internal/machine/api/machine_file.go b/server/internal/machine/api/machine_file.go index 7a8e5ae5..b3d3c2b6 100644 --- a/server/internal/machine/api/machine_file.go +++ b/server/internal/machine/api/machine_file.go @@ -93,8 +93,7 @@ func (m *MachineFile) MachineFiles(rc *req.Ctx) { } func (m *MachineFile) SaveMachineFiles(rc *req.Ctx) { - fileForm := new(form.MachineFileForm) - entity := req.BindJsonAndCopyTo[*entity.MachineFile](rc, fileForm, new(entity.MachineFile)) + fileForm, entity := req.BindJsonAndCopyTo[*form.MachineFileForm, *entity.MachineFile](rc) rc.ReqParam = fileForm biz.ErrIsNil(m.machineFileApp.Save(rc.MetaCtx, entity)) @@ -107,7 +106,7 @@ func (m *MachineFile) DeleteFile(rc *req.Ctx) { /*** sftp相关操作 */ func (m *MachineFile) CreateFile(rc *req.Ctx) { - opForm := req.BindJsonAndValid(rc, new(form.CreateFileForm)) + opForm := req.BindJsonAndValid[*form.CreateFileForm](rc) path := opForm.Path attrs := collx.Kvs("path", path) @@ -126,7 +125,7 @@ func (m *MachineFile) CreateFile(rc *req.Ctx) { } func (m *MachineFile) ReadFileContent(rc *req.Ctx) { - opForm := req.BindQuery(rc, new(dto.MachineFileOp)) + opForm := req.BindQuery[*dto.MachineFileOp](rc) readPath := opForm.Path ctx := rc.MetaCtx @@ -158,7 +157,7 @@ func (m *MachineFile) ReadFileContent(rc *req.Ctx) { } func (m *MachineFile) DownloadFile(rc *req.Ctx) { - opForm := req.BindQuery(rc, new(dto.MachineFileOp)) + opForm := req.BindQuery[*dto.MachineFileOp](rc) readPath := opForm.Path @@ -186,7 +185,7 @@ func (m *MachineFile) DownloadFile(rc *req.Ctx) { } func (m *MachineFile) GetDirEntry(rc *req.Ctx) { - opForm := req.BindQuery(rc, new(dto.MachineFileOp)) + opForm := req.BindQuery[*dto.MachineFileOp](rc) readPath := opForm.Path rc.ReqParam = fmt.Sprintf("path: %s", readPath) @@ -225,7 +224,7 @@ func (m *MachineFile) GetDirEntry(rc *req.Ctx) { } func (m *MachineFile) GetDirSize(rc *req.Ctx) { - opForm := req.BindQuery(rc, new(dto.MachineFileOp)) + opForm := req.BindQuery[*dto.MachineFileOp](rc) size, err := m.machineFileApp.GetDirSize(rc.MetaCtx, opForm) biz.ErrIsNil(err) @@ -233,14 +232,14 @@ func (m *MachineFile) GetDirSize(rc *req.Ctx) { } func (m *MachineFile) GetFileStat(rc *req.Ctx) { - opForm := req.BindQuery(rc, new(dto.MachineFileOp)) + opForm := req.BindQuery[*dto.MachineFileOp](rc) res, err := m.machineFileApp.FileStat(rc.MetaCtx, opForm) biz.ErrIsNil(err, res) rc.ResData = res } func (m *MachineFile) WriteFileContent(rc *req.Ctx) { - opForm := req.BindJsonAndValid(rc, new(form.WriteFileContentForm)) + opForm := req.BindJsonAndValid[*form.WriteFileContentForm](rc) path := opForm.Path mi, err := m.machineFileApp.WriteFileContent(rc.MetaCtx, opForm.MachineFileOp, []byte(opForm.Content)) @@ -401,7 +400,7 @@ func (m *MachineFile) UploadFolder(rc *req.Ctx) { } func (m *MachineFile) RemoveFile(rc *req.Ctx) { - opForm := req.BindJsonAndValid(rc, new(form.RemoveFileForm)) + opForm := req.BindJsonAndValid[*form.RemoveFileForm](rc) mi, err := m.machineFileApp.RemoveFile(rc.MetaCtx, opForm.MachineFileOp, opForm.Paths...) rc.ReqParam = collx.Kvs("machine", mi, "path", opForm) @@ -409,21 +408,21 @@ func (m *MachineFile) RemoveFile(rc *req.Ctx) { } func (m *MachineFile) CopyFile(rc *req.Ctx) { - opForm := req.BindJsonAndValid(rc, new(form.CopyFileForm)) + opForm := req.BindJsonAndValid[*form.CopyFileForm](rc) mi, err := m.machineFileApp.Copy(rc.MetaCtx, opForm.MachineFileOp, opForm.ToPath, opForm.Paths...) biz.ErrIsNilAppendErr(err, "file copy error: %s") rc.ReqParam = collx.Kvs("machine", mi, "cp", opForm) } func (m *MachineFile) MvFile(rc *req.Ctx) { - opForm := req.BindJsonAndValid(rc, new(form.CopyFileForm)) + opForm := req.BindJsonAndValid[*form.CopyFileForm](rc) mi, err := m.machineFileApp.Mv(rc.MetaCtx, opForm.MachineFileOp, opForm.ToPath, opForm.Paths...) rc.ReqParam = collx.Kvs("machine", mi, "mv", opForm) biz.ErrIsNilAppendErr(err, "file move error: %s") } func (m *MachineFile) Rename(rc *req.Ctx) { - renameForm := req.BindJsonAndValid(rc, new(form.RenameForm)) + renameForm := req.BindJsonAndValid[*form.RenameForm](rc) mi, err := m.machineFileApp.Rename(rc.MetaCtx, renameForm.MachineFileOp, renameForm.Newname) rc.ReqParam = collx.Kvs("machine", mi, "rename", renameForm) biz.ErrIsNilAppendErr(err, "file rename error: %s") diff --git a/server/internal/machine/api/machine_script.go b/server/internal/machine/api/machine_script.go index 83e6e812..aad72589 100644 --- a/server/internal/machine/api/machine_script.go +++ b/server/internal/machine/api/machine_script.go @@ -46,8 +46,7 @@ func (m *MachineScript) MachineScripts(rc *req.Ctx) { } func (m *MachineScript) SaveMachineScript(rc *req.Ctx) { - form := new(form.MachineScriptForm) - machineScript := req.BindJsonAndCopyTo(rc, form, new(entity.MachineScript)) + form, machineScript := req.BindJsonAndCopyTo[*form.MachineScriptForm, *entity.MachineScript](rc) rc.ReqParam = form biz.ErrIsNil(m.machineScriptApp.Save(rc.MetaCtx, machineScript)) diff --git a/server/internal/machine/api/vo/vo.go b/server/internal/machine/api/vo/vo.go index 9bb1b9a7..e989325b 100644 --- a/server/internal/machine/api/vo/vo.go +++ b/server/internal/machine/api/vo/vo.go @@ -111,10 +111,10 @@ type MachineCmdConfVO struct { model.Model Name string `json:"name"` - Cmds model.Slice[string] `json:"cmds"` // 命令配置 - Status int8 `json:"execCmds"` // 状态 - Stratege string `json:"stratege"` // 策略,空禁用 - Remark string `json:"remark"` // 备注 + Cmds model.Slice[string] `json:"cmds" gorm:"type:varchar"` // 命令配置,要加gorm标签才会正确解析model.Slice + Status int8 `json:"execCmds"` // 状态 + Stratege string `json:"stratege"` // 策略,空禁用 + Remark string `json:"remark"` // 备注 } func (mcc *MachineCmdConfVO) GetRelateId() uint64 { diff --git a/server/internal/machine/infrastructure/cache/machine_stats.go b/server/internal/machine/infrastructure/cache/machine_stats.go index dd91547e..be70a623 100644 --- a/server/internal/machine/infrastructure/cache/machine_stats.go +++ b/server/internal/machine/infrastructure/cache/machine_stats.go @@ -20,5 +20,5 @@ func GetMachineStats(machineId uint64) (*mcm.Stats, error) { if cacheStr == "" { return nil, errors.New("不存在该值") } - return jsonx.To(cacheStr, new(mcm.Stats)) + return jsonx.To[*mcm.Stats](cacheStr) } diff --git a/server/internal/mongo/api/mongo.go b/server/internal/mongo/api/mongo.go index 48e023c5..40c251d6 100644 --- a/server/internal/mongo/api/mongo.go +++ b/server/internal/mongo/api/mongo.go @@ -68,7 +68,7 @@ func (ma *Mongo) ReqConfs() *req.Confs { } func (m *Mongo) Mongos(rc *req.Ctx) { - queryCond := req.BindQuery(rc, new(entity.MongoQuery)) + queryCond := req.BindQuery[*entity.MongoQuery](rc) // 不存在可访问标签id,即没有可操作数据 tags := m.tagTreeApp.GetAccountTags(rc.GetLoginAccount().Id, &tagentity.TagTreeQuery{ @@ -95,14 +95,12 @@ func (m *Mongo) Mongos(rc *req.Ctx) { } func (m *Mongo) TestConn(rc *req.Ctx) { - form := &form.Mongo{} - mongo := req.BindJsonAndCopyTo[*entity.Mongo](rc, form, new(entity.Mongo)) + _, mongo := req.BindJsonAndCopyTo[*form.Mongo, *entity.Mongo](rc) biz.ErrIsNilAppendErr(m.mongoApp.TestConn(mongo), "connection error: %s") } func (m *Mongo) Save(rc *req.Ctx) { - form := &form.Mongo{} - mongo := req.BindJsonAndCopyTo[*entity.Mongo](rc, form, new(entity.Mongo)) + form, mongo := req.BindJsonAndCopyTo[*form.Mongo, *entity.Mongo](rc) // 密码脱敏记录日志 form.Uri = func(str string) string { @@ -148,8 +146,7 @@ func (m *Mongo) Collections(rc *req.Ctx) { } func (m *Mongo) RunCommand(rc *req.Ctx) { - commandForm := new(form.MongoRunCommand) - req.BindJsonAndValid(rc, commandForm) + commandForm := req.BindJsonAndValid[*form.MongoRunCommand](rc) conn, err := m.mongoApp.GetMongoConn(rc.MetaCtx, m.GetMongoId(rc)) biz.ErrIsNil(err) @@ -179,7 +176,7 @@ func (m *Mongo) RunCommand(rc *req.Ctx) { } func (m *Mongo) FindCommand(rc *req.Ctx) { - commandForm := req.BindJsonAndValid(rc, new(form.MongoFindCommand)) + commandForm := req.BindJsonAndValid[*form.MongoFindCommand](rc) conn, err := m.mongoApp.GetMongoConn(rc.MetaCtx, m.GetMongoId(rc)) biz.ErrIsNil(err) @@ -214,7 +211,7 @@ func (m *Mongo) FindCommand(rc *req.Ctx) { } func (m *Mongo) UpdateByIdCommand(rc *req.Ctx) { - commandForm := req.BindJsonAndValid(rc, new(form.MongoUpdateByIdCommand)) + commandForm := req.BindJsonAndValid[*form.MongoUpdateByIdCommand](rc) conn, err := m.mongoApp.GetMongoConn(rc.MetaCtx, m.GetMongoId(rc)) biz.ErrIsNil(err) @@ -238,7 +235,7 @@ func (m *Mongo) UpdateByIdCommand(rc *req.Ctx) { } func (m *Mongo) DeleteByIdCommand(rc *req.Ctx) { - commandForm := req.BindJsonAndValid(rc, new(form.MongoUpdateByIdCommand)) + commandForm := req.BindJsonAndValid[*form.MongoUpdateByIdCommand](rc) conn, err := m.mongoApp.GetMongoConn(rc.MetaCtx, m.GetMongoId(rc)) biz.ErrIsNil(err) @@ -261,7 +258,7 @@ func (m *Mongo) DeleteByIdCommand(rc *req.Ctx) { } func (m *Mongo) InsertOneCommand(rc *req.Ctx) { - commandForm := req.BindJsonAndValid(rc, new(form.MongoInsertCommand)) + commandForm := req.BindJsonAndValid[*form.MongoInsertCommand](rc) conn, err := m.mongoApp.GetMongoConn(rc.MetaCtx, m.GetMongoId(rc)) biz.ErrIsNil(err) diff --git a/server/internal/msg/api/msg_channel.go b/server/internal/msg/api/msg_channel.go index 2b85399b..60eb5861 100644 --- a/server/internal/msg/api/msg_channel.go +++ b/server/internal/msg/api/msg_channel.go @@ -36,9 +36,8 @@ func (m *MsgChannel) GetMsgChannels(rc *req.Ctx) { } func (m *MsgChannel) SaveMsgChannels(rc *req.Ctx) { - form := &form.MsgChannel{} + form, channel := req.BindJsonAndCopyTo[*form.MsgChannel, *entity.MsgChannel](rc) rc.ReqParam = form - channel := req.BindJsonAndCopyTo(rc, form, new(entity.MsgChannel)) err := m.msgChannelApp.SaveChannel(rc.MetaCtx, channel) biz.ErrIsNil(err) } diff --git a/server/internal/msg/api/msg_tmpl.go b/server/internal/msg/api/msg_tmpl.go index 83663f72..367f7ba0 100644 --- a/server/internal/msg/api/msg_tmpl.go +++ b/server/internal/msg/api/msg_tmpl.go @@ -57,9 +57,8 @@ func (m *MsgTmpl) GetMsgTmplChannels(rc *req.Ctx) { } func (m *MsgTmpl) SaveMsgTmpl(rc *req.Ctx) { - form := &form.MsgTmpl{} + form, channel := req.BindJsonAndCopyTo[*form.MsgTmpl, *dto.MsgTmplSave](rc) rc.ReqParam = form - channel := req.BindJsonAndCopyTo(rc, form, new(dto.MsgTmplSave)) biz.ErrIsNil(m.msgTmplApp.SaveTmpl(rc.MetaCtx, channel)) } @@ -75,7 +74,7 @@ func (m *MsgTmpl) DelMsgTmpls(rc *req.Ctx) { func (m *MsgTmpl) SendMsg(rc *req.Ctx) { code := rc.PathParam("code") - form := req.BindJsonAndValid(rc, new(form.SendMsg)) + form := req.BindJsonAndValid[*form.SendMsg](rc) rc.ReqParam = form diff --git a/server/internal/redis/api/cmd.go b/server/internal/redis/api/cmd.go index 755e4a06..7b838dd3 100644 --- a/server/internal/redis/api/cmd.go +++ b/server/internal/redis/api/cmd.go @@ -11,8 +11,7 @@ import ( ) func (r *Redis) RunCmd(rc *req.Ctx) { - var cmdReq form.RunCmdForm - runCmdParam := req.BindJsonAndCopyTo(rc, &cmdReq, new(dto.RunCmd)) + cmdReq, runCmdParam := req.BindJsonAndCopyTo[*form.RunCmdForm, *dto.RunCmd](rc) biz.IsTrue(len(cmdReq.Cmd) > 0, "redis cmd cannot be empty") redisConn := r.getRedisConn(rc) diff --git a/server/internal/redis/api/key.go b/server/internal/redis/api/key.go index 93a979c7..c4a773c8 100644 --- a/server/internal/redis/api/key.go +++ b/server/internal/redis/api/key.go @@ -17,7 +17,7 @@ import ( func (r *Redis) ScanKeys(rc *req.Ctx) { ri := r.getRedisConn(rc) - form := req.BindJsonAndValid(rc, new(form.RedisScanForm)) + form := req.BindJsonAndValid[*form.RedisScanForm](rc) cmd := ri.GetCmdable() ctx := context.Background() diff --git a/server/internal/redis/api/redis.go b/server/internal/redis/api/redis.go index 065ab718..9ede4622 100644 --- a/server/internal/redis/api/redis.go +++ b/server/internal/redis/api/redis.go @@ -60,7 +60,7 @@ func (rs *Redis) ReqConfs() *req.Confs { } func (r *Redis) RedisList(rc *req.Ctx) { - queryCond := req.BindQuery(rc, new(entity.RedisQuery)) + queryCond := req.BindQuery[*entity.RedisQuery](rc) // 不存在可访问标签id,即没有可操作数据 tags := r.tagApp.GetAccountTags(rc.GetLoginAccount().Id, &tagentity.TagTreeQuery{ @@ -87,8 +87,7 @@ func (r *Redis) RedisList(rc *req.Ctx) { } func (r *Redis) TestConn(rc *req.Ctx) { - form := &form.Redis{} - redis := req.BindJsonAndCopyTo[*entity.Redis](rc, form, new(entity.Redis)) + form, redis := req.BindJsonAndCopyTo[*form.Redis, *entity.Redis](rc) authCert := &tagentity.ResourceAuthCert{ Username: form.Username, @@ -110,8 +109,7 @@ func (r *Redis) TestConn(rc *req.Ctx) { } func (r *Redis) Save(rc *req.Ctx) { - form := &form.Redis{} - redis := req.BindJsonAndCopyTo[*entity.Redis](rc, form, new(entity.Redis)) + form, redis := req.BindJsonAndCopyTo[*form.Redis, *entity.Redis](rc) redisParam := &dto.SaveRedis{ Redis: redis, diff --git a/server/internal/redis/application/redis.go b/server/internal/redis/application/redis.go index e381dfad..dc2489f4 100644 --- a/server/internal/redis/application/redis.go +++ b/server/internal/redis/application/redis.go @@ -253,7 +253,7 @@ func (r *redisAppImpl) FlowBizHandle(ctx context.Context, bizHandleParam *flowap return nil, nil } - runCmdParam, err := jsonx.To(procinst.BizForm, new(FlowRedisRunCmdBizForm)) + runCmdParam, err := jsonx.To[*FlowRedisRunCmdBizForm](procinst.BizForm) if err != nil { return nil, errorx.NewBiz("failed to parse the business form information: %s", err.Error()) } diff --git a/server/internal/sys/api/account.go b/server/internal/sys/api/account.go index 6688e574..f9e6618a 100644 --- a/server/internal/sys/api/account.go +++ b/server/internal/sys/api/account.go @@ -108,7 +108,7 @@ func (a *Account) GetPermissions(rc *req.Ctx) { func (a *Account) ChangePassword(rc *req.Ctx) { ctx := rc.MetaCtx - form := req.BindJsonAndValid(rc, new(form.AccountChangePasswordForm)) + form := req.BindJsonAndValid[*form.AccountChangePasswordForm](rc) originOldPwd, err := utils.DefaultRsaDecrypt(form.OldPassword, true) biz.ErrIsNilAppendErr(err, "Wrong to decrypt old password: %s") @@ -145,9 +145,10 @@ func (a *Account) AccountInfo(rc *req.Ctx) { // 更新个人账号信息 func (a *Account) UpdateAccount(rc *req.Ctx) { - updateAccount := req.BindJsonAndCopyTo[*entity.Account](rc, new(form.AccountUpdateForm), new(entity.Account)) + form, updateAccount := req.BindJsonAndCopyTo[*form.AccountUpdateForm, *entity.Account](rc) // 账号id为登录者账号 updateAccount.Id = rc.GetLoginAccount().Id + rc.ReqParam = form ctx := rc.MetaCtx if updateAccount.Password != "" { @@ -210,8 +211,7 @@ func (a *Account) AccountDetail(rc *req.Ctx) { // @router /accounts func (a *Account) SaveAccount(rc *req.Ctx) { - form := &form.AccountCreateForm{} - account := req.BindJsonAndCopyTo(rc, form, new(entity.Account)) + form, account := req.BindJsonAndCopyTo[*form.AccountCreateForm, *entity.Account](rc) form.Password = "*****" rc.ReqParam = form @@ -307,7 +307,7 @@ func (a *Account) AccountResources(rc *req.Ctx) { // 关联账号角色 func (a *Account) RelateRole(rc *req.Ctx) { - form := req.BindJsonAndValid(rc, new(form.AccountRoleForm)) + form := req.BindJsonAndValid[*form.AccountRoleForm](rc) rc.ReqParam = form biz.ErrIsNil(a.roleApp.RelateAccountRole(rc.MetaCtx, form.Id, form.RoleId, consts.AccountRoleRelateType(form.RelateType))) } diff --git a/server/internal/sys/api/config.go b/server/internal/sys/api/config.go index adf45512..1392609b 100644 --- a/server/internal/sys/api/config.go +++ b/server/internal/sys/api/config.go @@ -55,8 +55,7 @@ func (c *Config) GetConfigValueByKey(rc *req.Ctx) { } func (c *Config) SaveConfig(rc *req.Ctx) { - form := &form.ConfigForm{} - config := req.BindJsonAndCopyTo(rc, form, new(entity.Config)) + form, config := req.BindJsonAndCopyTo[*form.ConfigForm, *entity.Config](rc) rc.ReqParam = form biz.ErrIsNil(c.configApp.Save(rc.MetaCtx, config)) } diff --git a/server/internal/sys/api/resource.go b/server/internal/sys/api/resource.go index 01d70a55..bc10a327 100644 --- a/server/internal/sys/api/resource.go +++ b/server/internal/sys/api/resource.go @@ -50,8 +50,7 @@ func (r *Resource) GetById(rc *req.Ctx) { } func (r *Resource) SaveResource(rc *req.Ctx) { - form := new(form.ResourceForm) - entity := req.BindJsonAndCopyTo(rc, form, new(entity.Resource)) + form, entity := req.BindJsonAndCopyTo[*form.ResourceForm, *entity.Resource](rc) rc.ReqParam = form diff --git a/server/internal/sys/api/role.go b/server/internal/sys/api/role.go index 489182f6..89e942aa 100644 --- a/server/internal/sys/api/role.go +++ b/server/internal/sys/api/role.go @@ -39,7 +39,7 @@ func (r *Role) ReqConfs() *req.Confs { } func (r *Role) Roles(rc *req.Ctx) { - cond := req.BindQuery(rc, new(entity.RoleQuery)) + cond := req.BindQuery[*entity.RoleQuery](rc) notIdsStr := rc.Query("notIds") if notIdsStr != "" { @@ -61,8 +61,7 @@ func (r *Role) Roles(rc *req.Ctx) { // 保存角色信息 func (r *Role) SaveRole(rc *req.Ctx) { - form := &form.RoleForm{} - role := req.BindJsonAndCopyTo(rc, form, new(entity.Role)) + form, role := req.BindJsonAndCopyTo[*form.RoleForm, *entity.Role](rc) rc.ReqParam = form r.roleApp.SaveRole(rc.MetaCtx, role) @@ -93,8 +92,7 @@ func (r *Role) RoleResource(rc *req.Ctx) { // 保存角色资源 func (r *Role) SaveResource(rc *req.Ctx) { - var form form.RoleResourceForm - req.BindJsonAndValid(rc, &form) + form := req.BindJsonAndValid[*form.RoleResourceForm](rc) rc.ReqParam = form // 将,拼接的字符串进行切割并转换 @@ -107,7 +105,7 @@ func (r *Role) SaveResource(rc *req.Ctx) { // 查看角色关联的用户 func (r *Role) RoleAccount(rc *req.Ctx) { - cond := req.BindQuery(rc, new(entity.RoleAccountQuery)) + cond := req.BindQuery[*entity.RoleAccountQuery](rc) cond.RoleId = uint64(rc.PathParamInt("id")) res, err := r.roleApp.GetRoleAccountPage(cond) biz.ErrIsNil(err) diff --git a/server/internal/sys/api/syslog.go b/server/internal/sys/api/syslog.go index 9591fcd0..e2058204 100644 --- a/server/internal/sys/api/syslog.go +++ b/server/internal/sys/api/syslog.go @@ -21,7 +21,7 @@ func (s *Syslog) ReqConfs() *req.Confs { } func (r *Syslog) Syslogs(rc *req.Ctx) { - queryCond := req.BindQuery(rc, new(entity.SysLogQuery)) + queryCond := req.BindQuery[*entity.SysLogQuery](rc) res, err := r.syslogApp.GetPageList(queryCond, "create_time DESC") biz.ErrIsNil(err) rc.ResData = res diff --git a/server/internal/tag/api/resource_auth_cert.go b/server/internal/tag/api/resource_auth_cert.go index 907fb28b..1adb834a 100644 --- a/server/internal/tag/api/resource_auth_cert.go +++ b/server/internal/tag/api/resource_auth_cert.go @@ -72,8 +72,7 @@ func (r *ResourceAuthCert) GetCompleteAuthCert(rc *req.Ctx) { } func (c *ResourceAuthCert) SaveAuthCert(rc *req.Ctx) { - acForm := &form.AuthCertForm{} - ac := req.BindJsonAndCopyTo(rc, acForm, new(entity.ResourceAuthCert)) + acForm, ac := req.BindJsonAndCopyTo[*form.AuthCertForm, *entity.ResourceAuthCert](rc) // 脱敏记录日志 acForm.Ciphertext = "***" diff --git a/server/internal/tag/api/tag_tree.go b/server/internal/tag/api/tag_tree.go index 1c667c57..1b105575 100644 --- a/server/internal/tag/api/tag_tree.go +++ b/server/internal/tag/api/tag_tree.go @@ -119,8 +119,7 @@ func (p *TagTree) ListByQuery(rc *req.Ctx) { } func (p *TagTree) SaveTagTree(rc *req.Ctx) { - tagForm := &form.TagTree{} - tagTree := req.BindJsonAndCopyTo(rc, tagForm, new(entity.TagTree)) + tagForm, tagTree := req.BindJsonAndCopyTo[*form.TagTree, *entity.TagTree](rc) rc.ReqParam = fmt.Sprintf("tagTreeId: %d, tagName: %s, code: %s", tagTree.Id, tagTree.Name, tagTree.Code) @@ -132,8 +131,7 @@ func (p *TagTree) DelTagTree(rc *req.Ctx) { } func (p *TagTree) MovingTag(rc *req.Ctx) { - movingForm := &form.MovingTag{} - req.BindJsonAndValid(rc, movingForm) + movingForm := req.BindJsonAndValid[*form.MovingTag](rc) rc.ReqParam = movingForm biz.ErrIsNil(p.tagTreeApp.MovingTag(rc.MetaCtx, movingForm.FromPath, movingForm.ToPath)) } diff --git a/server/internal/tag/api/team.go b/server/internal/tag/api/team.go index bde3329a..24756c82 100644 --- a/server/internal/tag/api/team.go +++ b/server/internal/tag/api/team.go @@ -46,7 +46,7 @@ func (t *Team) ReqConfs() *req.Confs { } func (p *Team) GetTeams(rc *req.Ctx) { - queryCond := req.BindQuery(rc, new(entity.TeamQuery)) + queryCond := req.BindQuery[*entity.TeamQuery](rc) res, err := p.teamApp.GetPageList(queryCond) biz.ErrIsNil(err) @@ -60,7 +60,7 @@ func (p *Team) GetTeams(rc *req.Ctx) { } func (p *Team) SaveTeam(rc *req.Ctx) { - team := req.BindJsonAndValid(rc, new(dto.SaveTeam)) + team := req.BindJsonAndValid[*dto.SaveTeam](rc) rc.ReqParam = team biz.ErrIsNil(p.teamApp.SaveTeam(rc.MetaCtx, team)) } @@ -87,7 +87,7 @@ func (p *Team) GetTeamMembers(rc *req.Ctx) { // 保存团队信息 func (p *Team) SaveTeamMember(rc *req.Ctx) { - teamMems := req.BindJsonAndValid(rc, new(form.TeamMember)) + teamMems := req.BindJsonAndValid[*form.TeamMember](rc) teamId := teamMems.TeamId diff --git a/server/internal/tag/api/vo/resource_auth_cert.go b/server/internal/tag/api/vo/resource_auth_cert.go index af218555..727cb727 100644 --- a/server/internal/tag/api/vo/resource_auth_cert.go +++ b/server/internal/tag/api/vo/resource_auth_cert.go @@ -7,6 +7,8 @@ import ( ) type ResourceAuthCert struct { + model.ExtraData + Id uint64 `json:"id"` Name string `json:"name"` // 名称 ResourceCode string `json:"resourceCode"` // 资源编号 @@ -14,7 +16,6 @@ type ResourceAuthCert struct { 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"` // 备注 diff --git a/server/pkg/cache/cache.go b/server/pkg/cache/cache.go index 1e783f44..532b5770 100644 --- a/server/pkg/cache/cache.go +++ b/server/pkg/cache/cache.go @@ -1,8 +1,8 @@ package cache import ( + "encoding/json" "mayfly-go/pkg/utils/anyx" - "mayfly-go/pkg/utils/jsonx" "time" "github.com/may-fly/cast" @@ -65,7 +65,7 @@ func (dc *defaultCache) GetInt(k string) (int, bool) { func (dc *defaultCache) GetJson(k string, valPtr any) bool { if val, ok := dc.GetStr(k); ok { - jsonx.To(val, valPtr) + json.Unmarshal([]byte(val), valPtr) return true } return false diff --git a/server/pkg/model/page.go b/server/pkg/model/page.go index a37650c0..bc30b47b 100644 --- a/server/pkg/model/page.go +++ b/server/pkg/model/page.go @@ -1,7 +1,6 @@ package model import ( - "mayfly-go/pkg/utils/collx" "mayfly-go/pkg/utils/structx" ) @@ -27,13 +26,8 @@ func PageResultConv[F any, T any](pageResult *PageResult[F]) *PageResult[T] { if pageResult == nil { return NewEmptyPageResult[T]() } - return &PageResult[T]{ Total: pageResult.Total, - List: collx.ArrayMap(pageResult.List, func(item F) T { - t := structx.NewInstance[T]() - structx.Copy(t, item) - return t - }), + List: structx.CopySliceTo[F, T](pageResult.List), } } diff --git a/server/pkg/req/util.go b/server/pkg/req/util.go index 227c34e2..317f4f12 100644 --- a/server/pkg/req/util.go +++ b/server/pkg/req/util.go @@ -10,7 +10,8 @@ import ( ) // 绑定并校验请求结构体参数 -func BindJsonAndValid[T any](rc *Ctx, data T) T { +func BindJsonAndValid[T any](rc *Ctx) T { + data := structx.NewInstance[T]() if err := rc.BindJSON(data); err != nil { panic(ConvBindValidationError(data, err)) } else { @@ -18,15 +19,15 @@ func BindJsonAndValid[T any](rc *Ctx, data T) T { } } -// 绑定请求体中的json至form结构体,并拷贝至另一结构体 -func BindJsonAndCopyTo[T any](rc *Ctx, form any, toStruct T) T { - BindJsonAndValid(rc, form) - structx.Copy(toStruct, form) - return toStruct +// 绑定请求体中的json至form结构体,并拷贝至指定结构体 +func BindJsonAndCopyTo[F, T any](rc *Ctx) (F, T) { + f := BindJsonAndValid[F](rc) + return f, structx.CopyTo[T](f) } // 绑定查询字符串到指定结构体 -func BindQuery[T any](rc *Ctx, data T) T { +func BindQuery[T any](rc *Ctx) T { + data := structx.NewInstance[T]() if err := rc.BindQuery(data); err != nil { panic(ConvBindValidationError(data, err)) } else { @@ -35,7 +36,8 @@ func BindQuery[T any](rc *Ctx, data T) T { } // 绑定查询字符串到指定结构体,并将分页信息也返回 -func BindQueryAndPage[T any](rc *Ctx, data T) (T, model.PageParam) { +func BindQueryAndPage[T any](rc *Ctx) (T, model.PageParam) { + data := structx.NewInstance[T]() if err := rc.BindQuery(data); err != nil { panic(ConvBindValidationError(data, err)) } else { diff --git a/server/pkg/utils/jsonx/jsonx.go b/server/pkg/utils/jsonx/jsonx.go index 5d1fd170..c080f354 100644 --- a/server/pkg/utils/jsonx/jsonx.go +++ b/server/pkg/utils/jsonx/jsonx.go @@ -4,6 +4,7 @@ import ( "encoding/json" "mayfly-go/pkg/logx" "mayfly-go/pkg/utils/collx" + "mayfly-go/pkg/utils/structx" "github.com/tidwall/gjson" ) @@ -16,9 +17,10 @@ func ToMap(jsonStr string) (collx.M, error) { return ToMapByBytes([]byte(jsonStr)) } -// json字符串转结构体 -func To[T any](jsonStr string, res T) (T, error) { - return res, json.Unmarshal([]byte(jsonStr), &res) +// json字符串转结构体, T需为指针类型 +func To[T any](jsonStr string) (T, error) { + res := structx.NewInstance[T]() + return res, json.Unmarshal([]byte(jsonStr), res) } // json字节数组转map diff --git a/server/pkg/utils/structx/copier.go b/server/pkg/utils/structx/copier.go new file mode 100644 index 00000000..638b12d5 --- /dev/null +++ b/server/pkg/utils/structx/copier.go @@ -0,0 +1,874 @@ +package structx + +import ( + "database/sql" + "database/sql/driver" + "errors" + "fmt" + "reflect" + "strings" + "sync" + "unicode" +) + +// github.com/jinzhu/copier + +var ( + ErrInvalidCopyDestination = errors.New("copy destination must be non-nil and addressable") + ErrInvalidCopyFrom = errors.New("copy from must be non-nil and addressable") + ErrMapKeyNotMatch = errors.New("map's key type doesn't match") + ErrNotSupported = errors.New("not supported") + ErrFieldNameTagStartNotUpperCase = errors.New("copier field name tag must be start upper case") +) + +// These flags define options for tag handling +const ( + // Denotes that a destination field must be copied to. If copying fails then a panic will ensue. + tagMust uint8 = 1 << iota + + // Denotes that the program should not panic when the must flag is on and + // value is not copied. The program will return an error instead. + tagNoPanic + + // Ignore a destination field from being copied to. + tagIgnore + + // Denotes the fact that the field should be overridden, no matter if the IgnoreEmpty is set + tagOverride + + // Denotes that the value as been copied + hasCopied + + // Some default converter types for a nicer syntax + String string = "" + Bool bool = false + Int int = 0 + Float32 float32 = 0 + Float64 float64 = 0 +) + +// Option sets copy options +type Option struct { + // setting this value to true will ignore copying zero values of all the fields, including bools, as well as a + // struct having all it's fields set to their zero values respectively (see IsZero() in reflect/value.go) + IgnoreEmpty bool + CaseSensitive bool + DeepCopy bool + Converters []TypeConverter + // Custom field name mappings to copy values with different names in `fromValue` and `toValue` types. + // Examples can be found in `copier_field_name_mapping_test.go`. + FieldNameMapping []FieldNameMapping +} + +func (opt Option) converters() map[converterPair]TypeConverter { + var converters = map[converterPair]TypeConverter{} + + // save converters into map for faster lookup + for i := range opt.Converters { + pair := converterPair{ + SrcType: reflect.TypeOf(opt.Converters[i].SrcType), + DstType: reflect.TypeOf(opt.Converters[i].DstType), + } + + converters[pair] = opt.Converters[i] + } + + return converters +} + +type TypeConverter struct { + SrcType interface{} + DstType interface{} + Fn func(src interface{}) (dst interface{}, err error) +} + +type converterPair struct { + SrcType reflect.Type + DstType reflect.Type +} + +func (opt Option) fieldNameMapping() map[converterPair]FieldNameMapping { + var mapping = map[converterPair]FieldNameMapping{} + + for i := range opt.FieldNameMapping { + pair := converterPair{ + SrcType: reflect.TypeOf(opt.FieldNameMapping[i].SrcType), + DstType: reflect.TypeOf(opt.FieldNameMapping[i].DstType), + } + + mapping[pair] = opt.FieldNameMapping[i] + } + + return mapping +} + +type FieldNameMapping struct { + SrcType interface{} + DstType interface{} + Mapping map[string]string +} + +// Tag Flags +type flags struct { + BitFlags map[string]uint8 + SrcNames tagNameMapping + DestNames tagNameMapping +} + +// Field Tag name mapping +type tagNameMapping struct { + FieldNameToTag map[string]string + TagToFieldName map[string]string +} + +// Copy copy things +func Copy(toValue interface{}, fromValue interface{}) (err error) { + return copier(toValue, fromValue, Option{}) +} + +// CopyWithOption copy with option +func CopyWithOption(toValue interface{}, fromValue interface{}, opt Option) (err error) { + return copier(toValue, fromValue, opt) +} + +func copier(toValue interface{}, fromValue interface{}, opt Option) (err error) { + var ( + isSlice bool + amount = 1 + from = indirect(reflect.ValueOf(fromValue)) + to = indirect(reflect.ValueOf(toValue)) + converters = opt.converters() + mappings = opt.fieldNameMapping() + ) + + if !to.CanAddr() { + return ErrInvalidCopyDestination + } + + // Return is from value is invalid + if !from.IsValid() { + return ErrInvalidCopyFrom + } + + fromType, isPtrFrom := indirectType(from.Type()) + toType, _ := indirectType(to.Type()) + + if fromType.Kind() == reflect.Interface { + fromType = reflect.TypeOf(from.Interface()) + } + + if toType.Kind() == reflect.Interface { + toType, _ = indirectType(reflect.TypeOf(to.Interface())) + oldTo := to + to = reflect.New(reflect.TypeOf(to.Interface())).Elem() + defer func() { + oldTo.Set(to) + }() + } + + // Just set it if possible to assign for normal types + if from.Kind() != reflect.Slice && from.Kind() != reflect.Struct && from.Kind() != reflect.Map && (from.Type().AssignableTo(to.Type()) || from.Type().ConvertibleTo(to.Type())) { + if !isPtrFrom || !opt.DeepCopy { + to.Set(from.Convert(to.Type())) + } else { + fromCopy := reflect.New(from.Type()) + fromCopy.Set(from.Elem()) + to.Set(fromCopy.Convert(to.Type())) + } + return + } + + if from.Kind() != reflect.Slice && fromType.Kind() == reflect.Map && toType.Kind() == reflect.Map { + if !fromType.Key().ConvertibleTo(toType.Key()) { + return ErrMapKeyNotMatch + } + + if to.IsNil() { + to.Set(reflect.MakeMapWithSize(toType, from.Len())) + } + + for _, k := range from.MapKeys() { + toKey := indirect(reflect.New(toType.Key())) + isSet, err := set(toKey, k, opt.DeepCopy, converters) + if err != nil { + return err + } + if !isSet { + return fmt.Errorf("%w map, old key: %v, new key: %v", ErrNotSupported, k.Type(), toType.Key()) + } + + elemType := toType.Elem() + if elemType.Kind() != reflect.Slice { + elemType, _ = indirectType(elemType) + } + toValue := indirect(reflect.New(elemType)) + isSet, err = set(toValue, from.MapIndex(k), opt.DeepCopy, converters) + if err != nil { + return err + } + if !isSet { + if err = copier(toValue.Addr().Interface(), from.MapIndex(k).Interface(), opt); err != nil { + return err + } + } + + for { + if elemType == toType.Elem() { + to.SetMapIndex(toKey, toValue) + break + } + elemType = reflect.PointerTo(elemType) + toValue = toValue.Addr() + } + } + return + } + + if from.Kind() == reflect.Slice && to.Kind() == reflect.Slice { + // Return directly if both slices are nil + if from.IsNil() && to.IsNil() { + return + } + if to.IsNil() { + slice := reflect.MakeSlice(reflect.SliceOf(to.Type().Elem()), from.Len(), from.Cap()) + to.Set(slice) + } + if fromType.ConvertibleTo(toType) { + for i := 0; i < from.Len(); i++ { + if to.Len() < i+1 { + to.Set(reflect.Append(to, reflect.New(to.Type().Elem()).Elem())) + } + isSet, err := set(to.Index(i), from.Index(i), opt.DeepCopy, converters) + if err != nil { + return err + } + if !isSet { + // ignore error while copy slice element + err = copier(to.Index(i).Addr().Interface(), from.Index(i).Interface(), opt) + if err != nil { + continue + } + } + } + + if to.Len() > from.Len() { + to.SetLen(from.Len()) + } + + return + } + } + + if fromType.Kind() != reflect.Struct || toType.Kind() != reflect.Struct { + // skip not supported type + return + } + + if len(converters) > 0 { + if ok, e := set(to, from, opt.DeepCopy, converters); e == nil && ok { + // converter supported + return + } + } + + if from.Kind() == reflect.Slice || to.Kind() == reflect.Slice { + isSlice = true + if from.Kind() == reflect.Slice { + amount = from.Len() + } + } + + for i := 0; i < amount; i++ { + var dest, source reflect.Value + + if isSlice { + // source + if from.Kind() == reflect.Slice { + source = indirect(from.Index(i)) + } else { + source = indirect(from) + } + // dest + dest = indirect(reflect.New(toType).Elem()) + } else { + source = indirect(from) + dest = indirect(to) + } + + if len(converters) > 0 { + if ok, e := set(dest, source, opt.DeepCopy, converters); e == nil && ok { + if isSlice { + // FIXME: maybe should check the other types? + if to.Type().Elem().Kind() == reflect.Ptr { + to.Index(i).Set(dest.Addr()) + } else { + if to.Len() < i+1 { + reflect.Append(to, dest) + } else { + to.Index(i).Set(dest) + } + } + } else { + to.Set(dest) + } + + continue + } + } + + destKind := dest.Kind() + initDest := false + if destKind == reflect.Interface { + initDest = true + dest = indirect(reflect.New(toType)) + } + + // Get tag options + flgs, err := getFlags(dest, source, toType, fromType) + if err != nil { + return err + } + + // check source + if source.IsValid() { + copyUnexportedStructFields(dest, source) + + // Copy from source field to dest field or method + fromTypeFields := deepFields(fromType) + for _, field := range fromTypeFields { + name := field.Name + + // Get bit flags for field + fieldFlags := flgs.BitFlags[name] + + // Check if we should ignore copying + if (fieldFlags & tagIgnore) != 0 { + continue + } + + fieldNamesMapping := getFieldNamesMapping(mappings, fromType, toType) + + srcFieldName, destFieldName := getFieldName(name, flgs, fieldNamesMapping) + + if fromField := fieldByNameOrZeroValue(source, srcFieldName); fromField.IsValid() && !shouldIgnore(fromField, fieldFlags, opt.IgnoreEmpty) { + // process for nested anonymous field + destFieldNotSet := false + if f, ok := dest.Type().FieldByName(destFieldName); ok { + // only initialize parent embedded struct pointer in the path + for idx := range f.Index[:len(f.Index)-1] { + destField := dest.FieldByIndex(f.Index[:idx+1]) + + if destField.Kind() != reflect.Ptr { + continue + } + + if !destField.IsNil() { + continue + } + if !destField.CanSet() { + destFieldNotSet = true + break + } + + // destField is a nil pointer that can be set + newValue := reflect.New(destField.Type().Elem()) + destField.Set(newValue) + } + } + + if destFieldNotSet { + break + } + + toField := fieldByName(dest, destFieldName, opt.CaseSensitive) + if toField.IsValid() { + if toField.CanSet() { + isSet, err := set(toField, fromField, opt.DeepCopy, converters) + if err != nil { + return err + } + if !isSet { + if err := copier(toField.Addr().Interface(), fromField.Interface(), opt); err != nil { + return err + } + } + if fieldFlags != 0 { + // Note that a copy was made + flgs.BitFlags[name] = fieldFlags | hasCopied + } + } + } else { + // try to set to method + var toMethod reflect.Value + if dest.CanAddr() { + toMethod = dest.Addr().MethodByName(destFieldName) + } else { + toMethod = dest.MethodByName(destFieldName) + } + + if toMethod.IsValid() && toMethod.Type().NumIn() == 1 && fromField.Type().AssignableTo(toMethod.Type().In(0)) { + toMethod.Call([]reflect.Value{fromField}) + } + } + } + } + + // Copy from from method to dest field + for _, field := range deepFields(toType) { + name := field.Name + srcFieldName, destFieldName := getFieldName(name, flgs, getFieldNamesMapping(mappings, fromType, toType)) + + var fromMethod reflect.Value + if source.CanAddr() { + fromMethod = source.Addr().MethodByName(srcFieldName) + } else { + fromMethod = source.MethodByName(srcFieldName) + } + + if fromMethod.IsValid() && fromMethod.Type().NumIn() == 0 && fromMethod.Type().NumOut() == 1 && !shouldIgnore(fromMethod, flgs.BitFlags[name], opt.IgnoreEmpty) { + if toField := fieldByName(dest, destFieldName, opt.CaseSensitive); toField.IsValid() && toField.CanSet() { + values := fromMethod.Call([]reflect.Value{}) + if len(values) >= 1 { + set(toField, values[0], opt.DeepCopy, converters) + } + } + } + } + } + + if isSlice && to.Kind() == reflect.Slice { + if dest.Addr().Type().AssignableTo(to.Type().Elem()) { + if to.Len() < i+1 { + to.Set(reflect.Append(to, dest.Addr())) + } else { + isSet, err := set(to.Index(i), dest.Addr(), opt.DeepCopy, converters) + if err != nil { + return err + } + if !isSet { + // ignore error while copy slice element + err = copier(to.Index(i).Addr().Interface(), dest.Addr().Interface(), opt) + if err != nil { + continue + } + } + } + } else if dest.Type().AssignableTo(to.Type().Elem()) { + if to.Len() < i+1 { + to.Set(reflect.Append(to, dest)) + } else { + isSet, err := set(to.Index(i), dest, opt.DeepCopy, converters) + if err != nil { + return err + } + if !isSet { + // ignore error while copy slice element + err = copier(to.Index(i).Addr().Interface(), dest.Interface(), opt) + if err != nil { + continue + } + } + } + } + } else if initDest { + to.Set(dest) + } + + err = checkBitFlags(flgs.BitFlags) + } + + return +} + +func getFieldNamesMapping(mappings map[converterPair]FieldNameMapping, fromType reflect.Type, toType reflect.Type) map[string]string { + var fieldNamesMapping map[string]string + + if len(mappings) > 0 { + pair := converterPair{ + SrcType: fromType, + DstType: toType, + } + if v, ok := mappings[pair]; ok { + fieldNamesMapping = v.Mapping + } + } + return fieldNamesMapping +} + +func fieldByNameOrZeroValue(source reflect.Value, fieldName string) (value reflect.Value) { + defer func() { + if err := recover(); err != nil { + value = reflect.Value{} + } + }() + + return source.FieldByName(fieldName) +} + +func copyUnexportedStructFields(to, from reflect.Value) { + if from.Kind() != reflect.Struct || to.Kind() != reflect.Struct || !from.Type().AssignableTo(to.Type()) { + return + } + + // create a shallow copy of 'to' to get all fields + tmp := indirect(reflect.New(to.Type())) + tmp.Set(from) + + // revert exported fields + for i := 0; i < to.NumField(); i++ { + if tmp.Field(i).CanSet() { + tmp.Field(i).Set(to.Field(i)) + } + } + to.Set(tmp) +} + +func shouldIgnore(v reflect.Value, bitFlags uint8, ignoreEmpty bool) bool { + return ignoreEmpty && bitFlags&tagOverride == 0 && v.IsZero() +} + +var deepFieldsLock sync.RWMutex +var deepFieldsMap = make(map[reflect.Type][]reflect.StructField) + +func deepFields(reflectType reflect.Type) []reflect.StructField { + deepFieldsLock.RLock() + cache, ok := deepFieldsMap[reflectType] + deepFieldsLock.RUnlock() + if ok { + return cache + } + var res []reflect.StructField + if reflectType, _ = indirectType(reflectType); reflectType.Kind() == reflect.Struct { + fields := make([]reflect.StructField, 0, reflectType.NumField()) + + for i := 0; i < reflectType.NumField(); i++ { + v := reflectType.Field(i) + // PkgPath is the package path that qualifies a lower case (unexported) + // field name. It is empty for upper case (exported) field names. + // See https://golang.org/ref/spec#Uniqueness_of_identifiers + if v.PkgPath == "" { + fields = append(fields, v) + if v.Anonymous { + // also consider fields of anonymous fields as fields of the root + fields = append(fields, deepFields(v.Type)...) + } + } + } + res = fields + } + + deepFieldsLock.Lock() + deepFieldsMap[reflectType] = res + deepFieldsLock.Unlock() + return res +} + +func indirect(reflectValue reflect.Value) reflect.Value { + for reflectValue.Kind() == reflect.Ptr { + reflectValue = reflectValue.Elem() + } + return reflectValue +} + +func indirectType(reflectType reflect.Type) (_ reflect.Type, isPtr bool) { + for reflectType.Kind() == reflect.Ptr || reflectType.Kind() == reflect.Slice { + reflectType = reflectType.Elem() + isPtr = true + } + return reflectType, isPtr +} + +func set(to, from reflect.Value, deepCopy bool, converters map[converterPair]TypeConverter) (bool, error) { + if !from.IsValid() { + return true, nil + } + if ok, err := lookupAndCopyWithConverter(to, from, converters); err != nil { + return false, err + } else if ok { + return true, nil + } + + if to.Kind() == reflect.Ptr { + // set `to` to nil if from is nil + if from.Kind() == reflect.Ptr && from.IsNil() { + to.Set(reflect.Zero(to.Type())) + return true, nil + } else if to.IsNil() { + // `from` -> `to` + // sql.NullString -> *string + if fromValuer, ok := driverValuer(from); ok { + v, err := fromValuer.Value() + if err != nil { + return true, nil + } + // if `from` is not valid do nothing with `to` + if v == nil { + return true, nil + } + } + // allocate new `to` variable with default value (eg. *string -> new(string)) + to.Set(reflect.New(to.Type().Elem())) + } else if from.Kind() != reflect.Ptr && from.IsZero() { + to.Set(reflect.Zero(to.Type())) + return true, nil + } + // depointer `to` + to = to.Elem() + } + + if deepCopy { + toKind := to.Kind() + if toKind == reflect.Interface && to.IsNil() { + if reflect.TypeOf(from.Interface()) != nil { + to.Set(reflect.New(reflect.TypeOf(from.Interface())).Elem()) + toKind = reflect.TypeOf(to.Interface()).Kind() + } + } + if from.Kind() == reflect.Ptr && from.IsNil() { + to.Set(reflect.Zero(to.Type())) + return true, nil + } + if _, ok := to.Addr().Interface().(sql.Scanner); !ok && (toKind == reflect.Struct || toKind == reflect.Map || toKind == reflect.Slice) { + return false, nil + } + } + + // try convert directly + if from.Type().ConvertibleTo(to.Type()) { + to.Set(from.Convert(to.Type())) + return true, nil + } + + // try Scanner + if toScanner, ok := to.Addr().Interface().(sql.Scanner); ok { + // `from` -> `to` + // *string -> sql.NullString + if from.Kind() == reflect.Ptr { + // if `from` is nil do nothing with `to` + if from.IsNil() { + return true, nil + } + // depointer `from` + from = indirect(from) + } + // `from` -> `to` + // string -> sql.NullString + // set `to` by invoking method Scan(`from`) + err := toScanner.Scan(from.Interface()) + if err == nil { + return true, nil + } + } + + // try Valuer + if fromValuer, ok := driverValuer(from); ok { + // `from` -> `to` + // sql.NullString -> string + v, err := fromValuer.Value() + if err != nil { + return false, nil + } + // if `from` is not valid do nothing with `to` + if v == nil { + return true, nil + } + rv := reflect.ValueOf(v) + if rv.Type().AssignableTo(to.Type()) { + to.Set(rv) + return true, nil + } + if to.CanSet() && rv.Type().ConvertibleTo(to.Type()) { + to.Set(rv.Convert(to.Type())) + return true, nil + } + return false, nil + } + + // from is ptr + if from.Kind() == reflect.Ptr { + return set(to, from.Elem(), deepCopy, converters) + } + + return false, nil +} + +// lookupAndCopyWithConverter looks up the type pair, on success the TypeConverter Fn func is called to copy src to dst field. +func lookupAndCopyWithConverter(to, from reflect.Value, converters map[converterPair]TypeConverter) (copied bool, err error) { + pair := converterPair{ + SrcType: from.Type(), + DstType: to.Type(), + } + + if cnv, ok := converters[pair]; ok { + result, err := cnv.Fn(from.Interface()) + if err != nil { + return false, err + } + + if result != nil { + to.Set(reflect.ValueOf(result)) + } else { + // in case we've got a nil value to copy + to.Set(reflect.Zero(to.Type())) + } + + return true, nil + } + + return false, nil +} + +// parseTags Parses struct tags and returns uint8 bit flags. +func parseTags(tag string) (flg uint8, name string, err error) { + for _, t := range strings.Split(tag, ",") { + switch t { + case "-": + flg = tagIgnore + return + case "must": + flg = flg | tagMust + case "nopanic": + flg = flg | tagNoPanic + case "override": + flg = flg | tagOverride + default: + if unicode.IsUpper([]rune(t)[0]) { + name = strings.TrimSpace(t) + } else { + err = ErrFieldNameTagStartNotUpperCase + } + } + } + return +} + +// getTagFlags Parses struct tags for bit flags, field name. +func getFlags(dest, src reflect.Value, toType, fromType reflect.Type) (flags, error) { + flgs := flags{ + BitFlags: map[string]uint8{}, + SrcNames: tagNameMapping{ + FieldNameToTag: map[string]string{}, + TagToFieldName: map[string]string{}, + }, + DestNames: tagNameMapping{ + FieldNameToTag: map[string]string{}, + TagToFieldName: map[string]string{}, + }, + } + + var toTypeFields, fromTypeFields []reflect.StructField + if dest.IsValid() { + toTypeFields = deepFields(toType) + } + if src.IsValid() { + fromTypeFields = deepFields(fromType) + } + + // Get a list dest of tags + for _, field := range toTypeFields { + tags := field.Tag.Get("copier") + if tags != "" { + var name string + var err error + if flgs.BitFlags[field.Name], name, err = parseTags(tags); err != nil { + return flags{}, err + } else if name != "" { + flgs.DestNames.FieldNameToTag[field.Name] = name + flgs.DestNames.TagToFieldName[name] = field.Name + } + } + } + + // Get a list source of tags + for _, field := range fromTypeFields { + tags := field.Tag.Get("copier") + if tags != "" { + var name string + var err error + + if _, name, err = parseTags(tags); err != nil { + return flags{}, err + } else if name != "" { + flgs.SrcNames.FieldNameToTag[field.Name] = name + flgs.SrcNames.TagToFieldName[name] = field.Name + } + } + } + + return flgs, nil +} + +// checkBitFlags Checks flags for error or panic conditions. +func checkBitFlags(flagsList map[string]uint8) (err error) { + // Check flag conditions were met + for name, flgs := range flagsList { + if flgs&hasCopied == 0 { + switch { + case flgs&tagMust != 0 && flgs&tagNoPanic != 0: + err = fmt.Errorf("field %s has must tag but was not copied", name) + return + case flgs&(tagMust) != 0: + panic(fmt.Sprintf("Field %s has must tag but was not copied", name)) + } + } + } + return +} + +func getFieldName(fieldName string, flgs flags, fieldNameMapping map[string]string) (srcFieldName string, destFieldName string) { + // get dest field name + if name, ok := fieldNameMapping[fieldName]; ok { + srcFieldName = fieldName + destFieldName = name + return + } + + if srcTagName, ok := flgs.SrcNames.FieldNameToTag[fieldName]; ok { + destFieldName = srcTagName + if destTagName, ok := flgs.DestNames.TagToFieldName[srcTagName]; ok { + destFieldName = destTagName + } + } else { + if destTagName, ok := flgs.DestNames.TagToFieldName[fieldName]; ok { + destFieldName = destTagName + } + } + if destFieldName == "" { + destFieldName = fieldName + } + + // get source field name + if destTagName, ok := flgs.DestNames.FieldNameToTag[fieldName]; ok { + srcFieldName = destTagName + if srcField, ok := flgs.SrcNames.TagToFieldName[destTagName]; ok { + srcFieldName = srcField + } + } else { + if srcField, ok := flgs.SrcNames.TagToFieldName[fieldName]; ok { + srcFieldName = srcField + } + } + + if srcFieldName == "" { + srcFieldName = fieldName + } + return +} + +func driverValuer(v reflect.Value) (i driver.Valuer, ok bool) { + if !v.CanAddr() { + i, ok = v.Interface().(driver.Valuer) + return + } + + i, ok = v.Addr().Interface().(driver.Valuer) + return +} + +func fieldByName(v reflect.Value, name string, caseSensitive bool) reflect.Value { + if caseSensitive { + return v.FieldByName(name) + } + + return v.FieldByNameFunc(func(n string) bool { return strings.EqualFold(n, name) }) +} diff --git a/server/pkg/utils/structx/structx.go b/server/pkg/utils/structx/structx.go index 0b008aae..081eb376 100644 --- a/server/pkg/utils/structx/structx.go +++ b/server/pkg/utils/structx/structx.go @@ -1,7 +1,6 @@ package structx import ( - "database/sql" "encoding/json" "errors" "fmt" @@ -11,126 +10,18 @@ import ( "strings" ) -// Copy copy things,引用至copier -func Copy(toValue any, fromValue any) (err error) { - var ( - isSlice bool - amount = 1 - from = Indirect(reflect.ValueOf(fromValue)) - to = Indirect(reflect.ValueOf(toValue)) - ) +// CopyTo 将fromValue转为T类型并返回 +func CopyTo[T any](fromValue any) T { + t := NewInstance[T]() + Copy(t, fromValue) + return t +} - if !to.CanAddr() { - return errors.New("copy to value is unaddressable") - } - - // Return is from value is invalid - if !from.IsValid() { - return - } - - fromType := IndirectType(from.Type()) - toType := IndirectType(to.Type()) - - // Just set it if possible to assign - // And need to do copy anyway if the type is struct - if fromType.Kind() != reflect.Struct && from.Type().AssignableTo(to.Type()) { - to.Set(from) - return - } - - if fromType.Kind() != reflect.Struct || toType.Kind() != reflect.Struct { - return - } - - if to.Kind() == reflect.Slice { - isSlice = true - if from.Kind() == reflect.Slice { - amount = from.Len() - } - } - - for i := 0; i < amount; i++ { - var dest, source reflect.Value - - if isSlice { - // source - if from.Kind() == reflect.Slice { - source = Indirect(from.Index(i)) - } else { - source = Indirect(from) - } - // dest - dest = Indirect(reflect.New(toType).Elem()) - } else { - source = Indirect(from) - dest = Indirect(to) - } - - // check source - if source.IsValid() { - fromTypeFields := deepFields(fromType) - //fmt.Printf("%#v", fromTypeFields) - // Copy from field to field or method - for _, field := range fromTypeFields { - name := field.Name - - if fromField := source.FieldByName(name); fromField.IsValid() { - // has field - if toField := dest.FieldByName(name); toField.IsValid() { - if toField.CanSet() { - if !set(toField, fromField) { - if err := Copy(toField.Addr().Interface(), fromField.Interface()); err != nil { - return err - } - } - } - } else { - // try to set to method - var toMethod reflect.Value - if dest.CanAddr() { - toMethod = dest.Addr().MethodByName(name) - } else { - toMethod = dest.MethodByName(name) - } - - if toMethod.IsValid() && toMethod.Type().NumIn() == 1 && fromField.Type().AssignableTo(toMethod.Type().In(0)) { - toMethod.Call([]reflect.Value{fromField}) - } - } - } - } - - // Copy from method to field - for _, field := range deepFields(toType) { - name := field.Name - - var fromMethod reflect.Value - if source.CanAddr() { - fromMethod = source.Addr().MethodByName(name) - } else { - fromMethod = source.MethodByName(name) - } - - if fromMethod.IsValid() && fromMethod.Type().NumIn() == 0 && fromMethod.Type().NumOut() == 1 { - if toField := dest.FieldByName(name); toField.IsValid() && toField.CanSet() { - values := fromMethod.Call([]reflect.Value{}) - if len(values) >= 1 { - set(toField, values[0]) - } - } - } - } - } - if isSlice { - if dest.Addr().Type().AssignableTo(to.Type().Elem()) { - to.Set(reflect.Append(to, dest.Addr())) - } else if dest.Type().AssignableTo(to.Type().Elem()) { - to.Set(reflect.Append(to, dest)) - } - } - } - return +// CopySliceTo 将fromValue转为[]T类型并返回 +func CopySliceTo[F, T any](fromValue []F) []T { + var to []T + Copy(&to, fromValue) + return to } // 对结构体的每个字段以及字段值执行doWith回调函数, 包括匿名属性的字段 @@ -158,23 +49,6 @@ func DoWithFields(str any, doWith func(fType reflect.StructField, fValue reflect return nil } -func deepFields(reflectType reflect.Type) []reflect.StructField { - var fields []reflect.StructField - - if reflectType = IndirectType(reflectType); reflectType.Kind() == reflect.Struct { - for i := 0; i < reflectType.NumField(); i++ { - v := reflectType.Field(i) - if v.Anonymous { - fields = append(fields, deepFields(v.Type)...) - } else { - fields = append(fields, v) - } - } - } - - return fields -} - func Indirect(reflectValue reflect.Value) reflect.Value { for reflectValue.Kind() == reflect.Ptr { reflectValue = reflectValue.Elem() @@ -189,35 +63,6 @@ func IndirectType(reflectType reflect.Type) reflect.Type { return reflectType } -func set(to, from reflect.Value) bool { - if from.IsValid() { - if to.Kind() == reflect.Ptr { - //set `to` to nil if from is nil - if from.Kind() == reflect.Ptr && from.IsNil() { - to.Set(reflect.Zero(to.Type())) - return true - } else if to.IsNil() { - to.Set(reflect.New(to.Type().Elem())) - } - to = to.Elem() - } - - if from.Type().ConvertibleTo(to.Type()) { - to.Set(from.Convert(to.Type())) - } else if scanner, ok := to.Addr().Interface().(sql.Scanner); ok { - err := scanner.Scan(from.Interface()) - if err != nil { - return false - } - } else if from.Kind() == reflect.Ptr { - return set(to, from.Elem()) - } else { - return false - } - } - return true -} - func Map2Struct(m map[string]any, s any) error { toValue := Indirect(reflect.ValueOf(s)) if !toValue.CanAddr() {