diff --git a/README.md b/README.md index 520d0d1..add98f3 100644 --- a/README.md +++ b/README.md @@ -6,13 +6,13 @@

- goframe + goframe vue - naiveui + naiveui typescript @@ -83,24 +83,28 @@ - - + + - - + + - - + + - - + + - - + + + + + +
diff --git a/docs/guide-zh-CN/README.md b/docs/guide-zh-CN/README.md index 35d8450..4a5a6b9 100644 --- a/docs/guide-zh-CN/README.md +++ b/docs/guide-zh-CN/README.md @@ -20,17 +20,16 @@ - [WebHook](sys-webhook.md) - [权限控制](sys-auth.md) - [支付网关](sys-payment.md) -- [数据库](sys-db.md) -- [代码生成](sys-code.md) - [定时任务](sys-cron.md) - [消息队列](sys-queue.md) - [功能扩展库](sys-library.md) - [工具方法](sys-utility.md) - [WebSocket服务器](sys-websocket-server.md) - [TCP服务器](sys-tcp-server.md) +- [SaaS多租户](sys-tenant.md) - [单元测试](sys-test.md) - + #### 插件模块开发 - [模块介绍及目录](addon-introduce-catalog.md) @@ -38,6 +37,18 @@ - [模块辅助说明](addon-helper.md) +#### 生成代码 +- [使用前提](code-start.md) +- [数据库](sys-db.md) +- [生成配置](code-config.md) +- [生成CURD](code-curd.md) +- [生成关联表CURD](code-curd-join.md) +- [生成树型CURD](code-tree.md) +- [生成业务模板](code-business.md) +- [生成模板开发](code-template-dev.md) +- [生成常见问题](code-help.md) + + ### 前端开发 - [表单组件](web-form.md) - [WebSocket客户端](sys-websocket-client.md) diff --git a/docs/guide-zh-CN/addon-introduce-catalog.md b/docs/guide-zh-CN/addon-introduce-catalog.md index 3158900..c7f98f1 100644 --- a/docs/guide-zh-CN/addon-introduce-catalog.md +++ b/docs/guide-zh-CN/addon-introduce-catalog.md @@ -25,6 +25,7 @@ HotGo 入口文件->隐式注入(hotgo/addons/modules)->注册所有插件->初 │ ├── modules │ ├── xxx插件 │ | ├── api +│ | ├── consts │ | ├── controller │ | ├── crons │ | ├── global diff --git a/docs/guide-zh-CN/code-business.md b/docs/guide-zh-CN/code-business.md new file mode 100644 index 0000000..2564b47 --- /dev/null +++ b/docs/guide-zh-CN/code-business.md @@ -0,0 +1,5 @@ +## 生成业务模板 + +根据`api`接口文件一键生成业务模板:api、controller、logic、service + +待写。 diff --git a/docs/guide-zh-CN/code-config.md b/docs/guide-zh-CN/code-config.md new file mode 100644 index 0000000..11029a0 --- /dev/null +++ b/docs/guide-zh-CN/code-config.md @@ -0,0 +1,170 @@ +## 生成配置 + +目录 + +- 模板配置 +- CLI配置 +- 多数据库使用 + +### 模板配置 + +- 配置路径:server/manifest/config/config.yaml + +```yaml +# 生成代码 +hggen: + allowedIPs: [ "127.0.0.1", "*" ] # 白名单,*代表所有,只有允许的IP后台才能使用生成代码功能 + selectDbs: [ "default" ] # 可选生成表的数据库配置名称,支持多库 + disableTables: [ "hg_sys_gen_codes","hg_admin_role_casbin" ] # 禁用的表,禁用以后将不会在选择表中看到 + delimiters: [ "@{", "}" ] # 模板引擎变量分隔符号 + # 生成应用模型,所有生成模板允许自定义,可以参考default模板进行改造 + application: + # CRUD和关系树列表模板 + crud: + templates: + # 默认的主包模板 + - group: "default" # 分组名称 + isAddon: false # 是否为插件模板 false|true + masterPackage: "sys" # 主包名称,需和controllerPath、logicPath、inputPath保持关联 + templatePath: "./resource/generate/default/curd" # 模板路径 + apiPath: "./api/admin" # goApi生成路径 + controllerPath: "./internal/controller/admin/sys" # 控制器生成路径 + logicPath: "./internal/logic/sys" # 主要业务生成路径 + inputPath: "./internal/model/input/sysin" # 表单过滤器生成路径 + routerPath: "./internal/router/genrouter" # 生成路由表路径 + sqlPath: "./storage/data/generate" # 生成sql语句路径 + webApiPath: "../web/src/api" # webApi生成路径 + webViewsPath: "../web/src/views" # web页面生成路径 + + # 默认的插件包模板,{$name}会自动替换成实际的插件名称 + - group: "addon" # 分组名称 + isAddon: true # 是否为插件模板 false|true + masterPackage: "sys" # 主包名称,需和controllerPath、logicPath、inputPath保持关联 + templatePath: "./resource/generate/default/curd" # 模板路径 + apiPath: "./addons/{$name}/api/admin" # goApi生成路径 + controllerPath: "./addons/{$name}/controller/admin/sys" # 控制器生成路径 + logicPath: "./addons/{$name}/logic/sys" # 主要业务生成路径 + inputPath: "./addons/{$name}/model/input/sysin" # 表单过滤器生成路径 + routerPath: "./addons/{$name}/router/genrouter" # 生成路由表路径 + sqlPath: "./storage/data/generate/addons" # 生成sql语句路径 + webApiPath: "../web/src/api/addons/{$name}" # webApi生成路径 + webViewsPath: "../web/src/views/addons/{$name}" # web页面生成路径 + + # 消息队列模板 + queue: + templates: + - group: "default" + templatePath: "./resource/generate/default/queue" + + # 定时任务模板 + cron: + templates: + - group: "default" + templatePath: "./resource/generate/default/cron" + + # 生成插件模块,通过后台创建新插件时使用的模板,允许自定义,可以参考default模板进行改造 + addon: + srcPath: "./resource/generate/default/addon" # 生成模板路径 + webApiPath: "../web/src/api/addons/{$name}" # webApi生成路径 + webViewsPath: "../web/src/views/addons/{$name}" # web页面生成路径 +``` + +### CLI配置 + +- hotgo在生成dao、service配置时,默认了和gf官方一致的配置方式和代码生成规则。所以无论你是通过hotgo亦或gf命令生成,最终代码格式完全一致,遵循一致的代码规范。 + +- 配置路径:[server/hack/config.yaml](../../server/hack/config.yaml) + +```yaml +gfcli: + build: + name: "hotgo" # 编译后的可执行文件名称 + # arch: "all" #不填默认当前系统架构,可选:386,amd64,arm,all + # system: "all" #不填默认当前系统平台,可选:linux,darwin,windows,all + mod: "none" + cgo: 0 + packSrc: "resource" # 将resource目录打包进可执行文件,静态资源无需单独部署 + packDst: "internal/packed/packed.go" # 打包后生成的Go文件路径,一般使用相对路径指定到本项目目录中 + version: "" + output: "./temp/hotgo" # 可执行文件生成路径 + extra: "" + + gen: + dao: + - link: "mysql:hotgo:hg123456.@tcp(127.0.0.1:3306)/hotgo?loc=Local&parseTime=true" + group: "default" # 分组 使用hotgo代码生成功能时必须填 + # tables: "" # 指定当前数据库中需要执行代码生成的数据表。如果为空,表示数据库的所有表都会生成。 + tablesEx: "hg_sys_addons_install" # 指定当前数据库中需要排除代码生成的数据表。 + removePrefix: "hg_" + descriptionTag: true + noModelComment: true + jsonCase: "CamelLower" + gJsonSupport: true + clear: true + + service: # 生成业务配置 + srcFolder: "internal/logic" + dstFolder: "internal/service" + dstFileNameCase: "CamelLower" + clear: true +``` + +### 多数据库使用 + +- 假设我们要增加一个库名为`hotgo2`、分组为`default2`的数据库,并要为其生成代码 + +1. 配置[server/hack/config.yaml](../../server/hack/config.yaml) 如下: +```yaml + gen: + dao: + - link: "mysql:hotgo:hg123456.@tcp(127.0.0.1:3306)/hotgo?loc=Local&parseTime=true" + group: "default" # 分组 使用hotgo代码生成功能时必须填 + tablesEx: "hg_sys_addons_install" # 指定当前数据库中需要排除代码生成的数据表。 + removePrefix: "hg_" + descriptionTag: true + noModelComment: true + jsonCase: "CamelLower" + gJsonSupport: true + clear: false + - link: "mysql:hotgo2:hg123456.@tcp(127.0.0.1:3306)/hotgo2?loc=Local&parseTime=true" + group: "default2" # 分组 使用hotgo代码生成功能时必须填 + tablesEx: "hg_sys_addons_install" # 指定当前数据库中需要排除代码生成的数据表。 + removePrefix: "" + descriptionTag: true + noModelComment: true + jsonCase: "CamelLower" + gJsonSupport: true + clear: false +``` + +2. 配置`server/manifest/config/config.yaml`, + +`database`配置如下: +```yaml +database: + logger: + level: "all" + stdout: true + default: + link: "mysql:hotgo:hg123456.@tcp(127.0.0.1:3306)/hotgo?loc=Local&parseTime=true" + debug: true + Prefix: "hg_" + default2: + link: "mysql:hotgo2:hg123456.@tcp(127.0.0.1:3306)/hotgo2?loc=Local&parseTime=true" + debug: true + Prefix: "" +``` + +`hggen`配置如下: +```yaml +hggen: + allowedIPs: ["127.0.0.1", "*"] # 白名单,*代表所有,只有允许的IP后台才能使用生成代码功能 + selectDbs: [ "default", "default2" ] # 可选生成表的数据库配置名称,支持多库 + disableTables : ["hg_sys_gen_codes","hg_admin_role_casbin"] # 禁用的表,禁用以后将不会在选择表中看到 + delimiters: ["@{", "}"] # 模板引擎变量分隔符号 +``` + +3. 登录HotGo后台 -> 开发工具 -> 代码生成 -> 找到立即生成按钮并打开,就会发现`数据库`选项增加了一个`default2`,后续生成步骤和生成例子完全一样 + +> 注意:上述的配置中所有的`default2`名称必须保持一致 + diff --git a/docs/guide-zh-CN/code-curd-join.md b/docs/guide-zh-CN/code-curd-join.md new file mode 100644 index 0000000..88fdf3d --- /dev/null +++ b/docs/guide-zh-CN/code-curd-join.md @@ -0,0 +1,115 @@ +## 生成关联表CURD + +### 热编译启动 +- 推荐使用热编译方式启动HotGo,这样生成完成页面自动刷新即可看到新生成内容,无需手动重启 + +```shell +# 服务端 +cd server +gf run main.go + +# web端 +cd web +yarn dev +``` + +以下是一个关联表的CURD生成流程 + +### 创建表结构 +- 以下表结构和数据为了方便功能演示已经内置无需再次创建 + + +- 创建主表:hg_sys_gen_curd_demo +```sql +CREATE TABLE `hg_sys_gen_curd_demo` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID', + `category_id` bigint(20) DEFAULT '0' COMMENT '分类ID', + `title` varchar(64) NOT NULL COMMENT '标题', + `description` varchar(255) DEFAULT '' COMMENT '描述', + `content` text COMMENT '内容', + `image` varchar(255) DEFAULT NULL COMMENT '单图', + `attachfile` varchar(255) DEFAULT NULL COMMENT '附件', + `city_id` bigint(20) DEFAULT '0' COMMENT '所在城市', + `switch` int(11) DEFAULT '1' COMMENT '显示开关', + `sort` int(11) DEFAULT NULL COMMENT '排序', + `status` tinyint(1) DEFAULT '1' COMMENT '状态', + `created_by` bigint(20) DEFAULT '0' COMMENT '创建者', + `updated_by` bigint(20) DEFAULT '0' COMMENT '更新者', + `created_at` datetime DEFAULT NULL COMMENT '创建时间', + `updated_at` datetime DEFAULT NULL COMMENT '修改时间', + `deleted_at` datetime DEFAULT NULL COMMENT '删除时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8mb4 COMMENT='系统_生成curd演示'; +``` + +创建关联表:hg_test_category +```sql +CREATE TABLE `hg_test_category` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '分类ID', + `name` varchar(255) NOT NULL COMMENT '分类名称', + `short_name` varchar(128) DEFAULT NULL COMMENT '简称', + `description` varchar(255) DEFAULT NULL COMMENT '描述', + `sort` int(11) NOT NULL COMMENT '排序', + `remark` varchar(255) DEFAULT NULL COMMENT '备注', + `status` tinyint(1) DEFAULT '1' COMMENT '状态', + `created_at` datetime DEFAULT NULL COMMENT '创建时间', + `updated_at` datetime DEFAULT NULL COMMENT '修改时间', + `deleted_at` datetime DEFAULT NULL COMMENT '删除时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COMMENT='测试分类'; +``` + +### 表字段要求 +- 使用生成时,表中必须具有以下字段和属性 + +| 字段名称 | 字段含义 | 字段类型 | 可为空 | +|--------|----------------------|---------------------|-----| +| id | 主键ID | bigint(20) | 否 | + + + +### 创建生成配置 +- 登录HotGo后台 -> 开发工具 -> 代码生成 -> 找到立即生成按钮并打开,选择和填写如下参数: + +![](images/code/join-add.png) + + +### 基本设置 +- 确认无误后,点击生成配置会跳转到生成配置页面,如下: +- 你可以在该页面调整生成表格表头/表列的接口功能、菜单权限、高级设置和关联表设置 + +![](images/code/join-init.png) + +### 主表字段设置 + +- 在该页面你可以调整生成表格字段名称、表单组件、表单编辑/验证项、列表展示/查询项、字段排序、重置和同步字段 + +![](images/code/join-fields.png) + + +### 关联表字段设置 +- 在该页面你可以调整生成表格关联表字段名称、列表展示/查询项、字段排序、重置和同步字段 +- 如果存在多个关联表,也可以对多个关联表字段进行设置 + +![](images/code/join-fields2.png) + +### 预览并生成 + +点击`预览代码`查看生成的代码内容。如果无需继续调整直接点击`提交生成`即可,以下是预览代码效果: + +![](images/code/join-preview.png) + +- 如果你使用的热编译,那么页面会在生成成功后立即刷新,刷新完成你即可在后台菜单栏中看到`测试表格`菜单。如果不是使用热编译启动,请手动重启服务后刷新。 + + +### 生成完成 + +- 让我们看看生成的表格页面,效果如下: + +![](images/code/join-list.png) + +### 常见问题 + +- [生成常见问题](code-help.md) + + diff --git a/docs/guide-zh-CN/code-curd.md b/docs/guide-zh-CN/code-curd.md new file mode 100644 index 0000000..0d89392 --- /dev/null +++ b/docs/guide-zh-CN/code-curd.md @@ -0,0 +1,84 @@ +## 生成CURD + +### 热编译启动 +- 推荐使用热编译方式启动HotGo,这样生成完成页面自动刷新即可看到新生成内容,无需手动重启 + +```shell +# 服务端 +cd server +gf run main.go + +# web端 +cd web +yarn dev +``` + +以下是一个基本的CURD生成流程 + +### 创建表结构 +- 以下表结构和数据为了方便功能演示已经内置无需再次创建 + +```sql +CREATE TABLE `hg_test_category` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '分类ID', + `name` varchar(255) NOT NULL COMMENT '分类名称', + `short_name` varchar(128) DEFAULT NULL COMMENT '简称', + `description` varchar(255) DEFAULT NULL COMMENT '描述', + `sort` int(11) NOT NULL COMMENT '排序', + `remark` varchar(255) DEFAULT NULL COMMENT '备注', + `status` tinyint(1) DEFAULT '1' COMMENT '状态', + `created_at` datetime DEFAULT NULL COMMENT '创建时间', + `updated_at` datetime DEFAULT NULL COMMENT '修改时间', + `deleted_at` datetime DEFAULT NULL COMMENT '删除时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COMMENT='测试分类'; +``` + +### 表字段要求 +- 使用生成时,表中必须具有以下字段和属性 + +| 字段名称 | 字段含义 | 字段类型 | 可为空 | +|--------|----------------------|---------------------|-----| +| id | 主键ID | bigint(20) | 否 | + + +### 创建生成配置 +- 登录HotGo后台 -> 开发工具 -> 代码生成 -> 找到立即生成按钮并打开,选择和填写如下参数: + +![](images/code/curd-add.png) + + +### 基本设置 +- 确认无误后,点击生成配置会跳转到生成配置页面,如下: +- 你可以在该页面调整生成表格表头/表列的接口功能、菜单权限、高级设置和关联表设置 + +![](images/code/curd-init.png) + +### 表字段设置 + +- 在该页面你可以调整生成表格字段名称、表单组件、表单编辑/验证项、列表展示/查询项、字段排序、重置和同步字段 + +![](images/code/curd-fields.png) + +### 预览并生成 + +点击`预览代码`查看生成的代码内容。如果无需继续调整直接点击`提交生成`即可,以下是预览代码效果: + +![](images/code/curd-preview.png) + +- 如果你使用的热编译,那么页面会在生成成功后立即刷新,刷新完成你即可在后台菜单栏中看到`测试表格`菜单。如果不是使用热编译启动,请手动重启服务后刷新。 + + +### 生成完成 + +- 让我们看看生成的表格页面,效果如下: + +![](images/code/curd-list.png) + + +### 常见问题 + +- [生成常见问题](code-help.md) + + + diff --git a/docs/guide-zh-CN/code-help.md b/docs/guide-zh-CN/code-help.md new file mode 100644 index 0000000..8f220b1 --- /dev/null +++ b/docs/guide-zh-CN/code-help.md @@ -0,0 +1,18 @@ +## 生成常见问题 + +### 生成完后页面提示:服务器错误,请稍候重试! + +- 热编译环境下,web端往往会快于服务端重启并加载完成,此时接口访问会出现`服务器错误,请稍候重试!`或`404`。这是服务端正在重启导致的,一般稍等几秒就好,如果不行就手动重启下服务端。 + + +### fetching tables failed: SHOW TABLES: Error 1045 (28000): Access denied for user * (using password: YES) + +- 请去确认`server/manifest/config/config.yaml`和`server/hack/config.yaml`下的数据库配置一致并且权限正确 +- 参考:[生成配置](code-config.md) + + +### 为什么后台找不到开发工具菜单 + +- 请去确认`server/manifest/config/config.yaml`中的`system.mode`不为`product`。product模式下后台【开发工具】菜单自动隐藏 + + diff --git a/docs/guide-zh-CN/code-start.md b/docs/guide-zh-CN/code-start.md new file mode 100644 index 0000000..ddeea5e --- /dev/null +++ b/docs/guide-zh-CN/code-start.md @@ -0,0 +1,20 @@ +## 使用前提 + + +在HotGo中可以通过后台开发工具快速的一键生成CRUD,自动生成Api、控制器、业务逻辑、Web页面、表单组件、菜单权限等。 + + +- hotgo 版本号 >= 2.13.1 +- 使用前必须配置 [生成配置](code-config.md) +- 使用前必须了解 [数据库](sys-db.md) + + +相关目录 + +- [生成配置](code-config.md) +- [生成CURD](code-curd.md) +- [生成关联表CURD](code-curd-join.md) +- [生成树型CURD](code-tree.md) +- [生成业务模板](code-business.md) +- [生成模板开发](code-template-dev.md) +- [生成常见问题](code-help.md) \ No newline at end of file diff --git a/docs/guide-zh-CN/code-template-dev.md b/docs/guide-zh-CN/code-template-dev.md new file mode 100644 index 0000000..fe8b6fa --- /dev/null +++ b/docs/guide-zh-CN/code-template-dev.md @@ -0,0 +1,59 @@ +## 生成模板开发 + +### 自定义生成模板 + +- HotGo允许你新建新的模板分组来满足你的需求,模板可根据现有模板基础拷贝一份出来做改造,默认模板目录:[server/resource/generate/default](../../server/resource/generate/default) + +- 系统内置了两组CURD生成模板,请参考[生成模板配置](sys-code.md#生成模板配置)。default:是默认的生成到主模块下;addon:是默认生成到指定的插件下 + + + +### 内置gf-cli + +- 为了确保生成代码的依赖稳定性,在面对`gf`版本更新可能导致向下不兼容情况时,HotGo将`gf-cli`工具内置到系统中并进行在线执行调整,从而提供更可靠和一致的生成代码功能。 + +- 后续我们也将开放在线运行`gf gen ...`功能。在做插件开发时也会支持到在线生成插件下的service接口,这将会使得插件开发更加方便 + + + +### 指定gf-cli版本 + +- HotGo多数情况下会和最新版本的gf-cli保持同步,如果更新不及时或你不想使用最新版本的gf-cli来生成代码,可以找到自己想要的版本进行替换即可。 + +- 下面大致做一些替换步骤说明: + +1. 打开`github.com/gogf/gf` 找到你想要使用的版本`clone`下来 +2. 将`clone`代码中`gf/cmd/gf/internal/`目录覆盖到`server/internal/library/hggen/internal` +3. 将覆盖过来的目录文件中引入包名`github.com/gogf/gf/cmd/gf/v2/`批量改为`hotgo/internal/library/hggen/` +4. 运行`go mod tidy` +5. 运行`go run main.go`,如果没有报错,那么恭喜你已经完成了。如果有报错一般都是版本差异带来的影响,需要根据情况自行调整 + + + +### 指定数据库驱动 + +> HotGo默认使用mysql驱动,如果你想用其他数据库驱动打开下方文件中注释即可 + +- 修改文件路径:[server/internal/library/hggen/internal/cmd/cmd_gen_dao.go](../../server/internal/library/hggen/internal/cmd/cmd_gen_dao.go) + +```go +package cmd + +import ( + //_ "github.com/gogf/gf/contrib/drivers/clickhouse/v2" + //_ "github.com/gogf/gf/contrib/drivers/mssql/v2" + _ "github.com/gogf/gf/contrib/drivers/mysql/v2" + //_ "github.com/gogf/gf/contrib/drivers/oracle/v2" + //_ "github.com/gogf/gf/contrib/drivers/pgsql/v2" + //_ "github.com/gogf/gf/contrib/drivers/sqlite/v2" + + "hotgo/internal/library/hggen/internal/cmd/gendao" +) + +type ( + cGenDao = gendao.CGenDao +) + +``` + +修改完成后运行`go mod tidy` diff --git a/docs/guide-zh-CN/code-tree.md b/docs/guide-zh-CN/code-tree.md new file mode 100644 index 0000000..8d7cc22 --- /dev/null +++ b/docs/guide-zh-CN/code-tree.md @@ -0,0 +1,93 @@ +## 生成树型CURD + +### 热编译启动 +- 推荐使用热编译方式启动HotGo,这样生成完成页面自动刷新即可看到新生成内容,无需手动重启 + +```shell +# 服务端 +cd server +gf run main.go + +# web端 +cd web +yarn dev +``` + +以下是一个基本的树形CURD生成流程 + +### 创建表结构 +- 以下表结构和数据为了方便功能演示已经内置无需再次创建 + +```sql +CREATE TABLE `hg_sys_gen_tree_demo` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID', + `pid` bigint(20) DEFAULT NULL COMMENT '上级ID', + `level` int(11) DEFAULT '1' COMMENT '关系树级别', + `tree` varchar(512) COMMENT '关系树', + `category_id` bigint(20) DEFAULT '0' COMMENT '分类ID', + `title` varchar(64) NOT NULL COMMENT '标题', + `description` varchar(255) DEFAULT '' COMMENT '描述', + `sort` int(11) DEFAULT NULL COMMENT '排序', + `status` tinyint(1) DEFAULT '1' COMMENT '状态', + `created_by` bigint(20) DEFAULT '0' COMMENT '创建者', + `updated_by` bigint(20) DEFAULT '0' COMMENT '更新者', + `created_at` datetime DEFAULT NULL COMMENT '创建时间', + `updated_at` datetime DEFAULT NULL COMMENT '修改时间', + `deleted_at` datetime DEFAULT NULL COMMENT '删除时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=28 DEFAULT CHARSET=utf8mb4 COMMENT='系统_生成树表演示'; +``` + +### 表字段要求 +- 使用生成时,表中必须具有以下字段和属性 + +| 字段名称 | 字段含义 | 字段类型 | 可为空 | +|--------|----------------------|---------------------|-----| +| id | 主键ID | bigint(20) | 否 | +| pid | 上级树ID | bigint(20) | 是 | +| level | 关系树级别 | int(11) | 否 | +| tree | 关系树 | varchar(512) | 是 | + +### 创建生成配置 +- 登录HotGo后台 -> 开发工具 -> 代码生成 -> 找到立即生成按钮并打开,选择和填写如下参数: + +![](images/code/tree-add.png) + + +### 基本设置 +- 确认无误后,点击生成配置会跳转到生成配置页面,如下: +- 你可以在该页面调整生成表格表头/表列的接口功能、菜单权限、高级设置和关联表设置 +- 相比普通CURD,这里多了`树名称字段`和`树表格样式`选项 + +![](images/code/tree-init.png) + +### 表字段设置 + +- 在该页面你可以调整生成表格字段名称、表单组件、表单编辑/验证项、列表展示/查询项、字段排序、重置和同步字段 +- 树表中的pid、level、tree字段由系统维护,单独设置编辑表单功能无效! + +![](images/code/tree-fields.png) + +### 预览并生成 + +点击`预览代码`查看生成的代码内容。如果无需继续调整直接点击`提交生成`即可,以下是预览代码效果: + +![](images/code/tree-preview.png) + +- 如果你使用的热编译,那么页面会在生成成功后立即刷新,刷新完成你即可在后台菜单栏中看到`测试表格`菜单。如果不是使用热编译启动,请手动重启服务后刷新。 + + +### 生成完成 + +- 让我们看看生成的表格页面,效果如下: + +- 普通树表 +![](images/code/tree-list.png) + +- 选项式树表 +![](images/code/tree-list2.png) + +### 常见问题 + +- [生成常见问题](code-help.md) + diff --git a/docs/guide-zh-CN/images/code/curd-add.png b/docs/guide-zh-CN/images/code/curd-add.png new file mode 100644 index 0000000..14ef083 Binary files /dev/null and b/docs/guide-zh-CN/images/code/curd-add.png differ diff --git a/docs/guide-zh-CN/images/code/curd-fields.png b/docs/guide-zh-CN/images/code/curd-fields.png new file mode 100644 index 0000000..00fe790 Binary files /dev/null and b/docs/guide-zh-CN/images/code/curd-fields.png differ diff --git a/docs/guide-zh-CN/images/code/curd-init.png b/docs/guide-zh-CN/images/code/curd-init.png new file mode 100644 index 0000000..c0179f2 Binary files /dev/null and b/docs/guide-zh-CN/images/code/curd-init.png differ diff --git a/docs/guide-zh-CN/images/code/curd-list.png b/docs/guide-zh-CN/images/code/curd-list.png new file mode 100644 index 0000000..11ed610 Binary files /dev/null and b/docs/guide-zh-CN/images/code/curd-list.png differ diff --git a/docs/guide-zh-CN/images/code/curd-preview.png b/docs/guide-zh-CN/images/code/curd-preview.png new file mode 100644 index 0000000..1d73d27 Binary files /dev/null and b/docs/guide-zh-CN/images/code/curd-preview.png differ diff --git a/docs/guide-zh-CN/images/code/join-add.png b/docs/guide-zh-CN/images/code/join-add.png new file mode 100644 index 0000000..07cab88 Binary files /dev/null and b/docs/guide-zh-CN/images/code/join-add.png differ diff --git a/docs/guide-zh-CN/images/code/join-fields.png b/docs/guide-zh-CN/images/code/join-fields.png new file mode 100644 index 0000000..39dca79 Binary files /dev/null and b/docs/guide-zh-CN/images/code/join-fields.png differ diff --git a/docs/guide-zh-CN/images/code/join-fields2.png b/docs/guide-zh-CN/images/code/join-fields2.png new file mode 100644 index 0000000..e078942 Binary files /dev/null and b/docs/guide-zh-CN/images/code/join-fields2.png differ diff --git a/docs/guide-zh-CN/images/code/join-init.png b/docs/guide-zh-CN/images/code/join-init.png new file mode 100644 index 0000000..6e387e2 Binary files /dev/null and b/docs/guide-zh-CN/images/code/join-init.png differ diff --git a/docs/guide-zh-CN/images/code/join-list.png b/docs/guide-zh-CN/images/code/join-list.png new file mode 100644 index 0000000..5d60678 Binary files /dev/null and b/docs/guide-zh-CN/images/code/join-list.png differ diff --git a/docs/guide-zh-CN/images/code/join-preview.png b/docs/guide-zh-CN/images/code/join-preview.png new file mode 100644 index 0000000..741a44b Binary files /dev/null and b/docs/guide-zh-CN/images/code/join-preview.png differ diff --git a/docs/guide-zh-CN/images/code/tree-add.png b/docs/guide-zh-CN/images/code/tree-add.png new file mode 100644 index 0000000..b5dce67 Binary files /dev/null and b/docs/guide-zh-CN/images/code/tree-add.png differ diff --git a/docs/guide-zh-CN/images/code/tree-fields.png b/docs/guide-zh-CN/images/code/tree-fields.png new file mode 100644 index 0000000..a998a20 Binary files /dev/null and b/docs/guide-zh-CN/images/code/tree-fields.png differ diff --git a/docs/guide-zh-CN/images/code/tree-init.png b/docs/guide-zh-CN/images/code/tree-init.png new file mode 100644 index 0000000..d5c7bdc Binary files /dev/null and b/docs/guide-zh-CN/images/code/tree-init.png differ diff --git a/docs/guide-zh-CN/images/code/tree-list.png b/docs/guide-zh-CN/images/code/tree-list.png new file mode 100644 index 0000000..e93b4b3 Binary files /dev/null and b/docs/guide-zh-CN/images/code/tree-list.png differ diff --git a/docs/guide-zh-CN/images/code/tree-list2.png b/docs/guide-zh-CN/images/code/tree-list2.png new file mode 100644 index 0000000..ec4a7c4 Binary files /dev/null and b/docs/guide-zh-CN/images/code/tree-list2.png differ diff --git a/docs/guide-zh-CN/images/code/tree-preview.png b/docs/guide-zh-CN/images/code/tree-preview.png new file mode 100644 index 0000000..79d7115 Binary files /dev/null and b/docs/guide-zh-CN/images/code/tree-preview.png differ diff --git a/docs/guide-zh-CN/images/demo/1.png b/docs/guide-zh-CN/images/demo/1.png new file mode 100644 index 0000000..f140c18 Binary files /dev/null and b/docs/guide-zh-CN/images/demo/1.png differ diff --git a/docs/guide-zh-CN/images/demo/10.png b/docs/guide-zh-CN/images/demo/10.png new file mode 100644 index 0000000..90c4623 Binary files /dev/null and b/docs/guide-zh-CN/images/demo/10.png differ diff --git a/docs/guide-zh-CN/images/demo/11.png b/docs/guide-zh-CN/images/demo/11.png new file mode 100644 index 0000000..ef5d41c Binary files /dev/null and b/docs/guide-zh-CN/images/demo/11.png differ diff --git a/docs/guide-zh-CN/images/demo/12.png b/docs/guide-zh-CN/images/demo/12.png new file mode 100644 index 0000000..ff37ff4 Binary files /dev/null and b/docs/guide-zh-CN/images/demo/12.png differ diff --git a/docs/guide-zh-CN/images/demo/2.png b/docs/guide-zh-CN/images/demo/2.png new file mode 100644 index 0000000..722d49e Binary files /dev/null and b/docs/guide-zh-CN/images/demo/2.png differ diff --git a/docs/guide-zh-CN/images/demo/3.png b/docs/guide-zh-CN/images/demo/3.png new file mode 100644 index 0000000..c5580ca Binary files /dev/null and b/docs/guide-zh-CN/images/demo/3.png differ diff --git a/docs/guide-zh-CN/images/demo/4.png b/docs/guide-zh-CN/images/demo/4.png new file mode 100644 index 0000000..be6746c Binary files /dev/null and b/docs/guide-zh-CN/images/demo/4.png differ diff --git a/docs/guide-zh-CN/images/demo/5.png b/docs/guide-zh-CN/images/demo/5.png new file mode 100644 index 0000000..a1302b1 Binary files /dev/null and b/docs/guide-zh-CN/images/demo/5.png differ diff --git a/docs/guide-zh-CN/images/demo/6.png b/docs/guide-zh-CN/images/demo/6.png new file mode 100644 index 0000000..46501dc Binary files /dev/null and b/docs/guide-zh-CN/images/demo/6.png differ diff --git a/docs/guide-zh-CN/images/demo/7.png b/docs/guide-zh-CN/images/demo/7.png new file mode 100644 index 0000000..7d11c0e Binary files /dev/null and b/docs/guide-zh-CN/images/demo/7.png differ diff --git a/docs/guide-zh-CN/images/demo/8.png b/docs/guide-zh-CN/images/demo/8.png new file mode 100644 index 0000000..6e54af8 Binary files /dev/null and b/docs/guide-zh-CN/images/demo/8.png differ diff --git a/docs/guide-zh-CN/images/demo/9.png b/docs/guide-zh-CN/images/demo/9.png new file mode 100644 index 0000000..0dc54fb Binary files /dev/null and b/docs/guide-zh-CN/images/demo/9.png differ diff --git a/docs/guide-zh-CN/images/sys-code-add.png b/docs/guide-zh-CN/images/sys-code-add.png deleted file mode 100644 index 322ff80..0000000 Binary files a/docs/guide-zh-CN/images/sys-code-add.png and /dev/null differ diff --git a/docs/guide-zh-CN/images/sys-code-config-init.png b/docs/guide-zh-CN/images/sys-code-config-init.png deleted file mode 100644 index 9f395f1..0000000 Binary files a/docs/guide-zh-CN/images/sys-code-config-init.png and /dev/null differ diff --git a/docs/guide-zh-CN/images/sys-code-config-join-save.png b/docs/guide-zh-CN/images/sys-code-config-join-save.png deleted file mode 100644 index 8c639cf..0000000 Binary files a/docs/guide-zh-CN/images/sys-code-config-join-save.png and /dev/null differ diff --git a/docs/guide-zh-CN/images/sys-code-config-join.png b/docs/guide-zh-CN/images/sys-code-config-join.png deleted file mode 100644 index d7fff64..0000000 Binary files a/docs/guide-zh-CN/images/sys-code-config-join.png and /dev/null differ diff --git a/docs/guide-zh-CN/images/sys-code-config-post.png b/docs/guide-zh-CN/images/sys-code-config-post.png deleted file mode 100644 index e44337c..0000000 Binary files a/docs/guide-zh-CN/images/sys-code-config-post.png and /dev/null differ diff --git a/docs/guide-zh-CN/images/sys-code-list-edit-ok.png b/docs/guide-zh-CN/images/sys-code-list-edit-ok.png deleted file mode 100644 index 8eb9ab7..0000000 Binary files a/docs/guide-zh-CN/images/sys-code-list-edit-ok.png and /dev/null differ diff --git a/docs/guide-zh-CN/images/sys-code-list-ok.png b/docs/guide-zh-CN/images/sys-code-list-ok.png deleted file mode 100644 index e2d878d..0000000 Binary files a/docs/guide-zh-CN/images/sys-code-list-ok.png and /dev/null differ diff --git a/docs/guide-zh-CN/images/sys-code-list.png b/docs/guide-zh-CN/images/sys-code-list.png deleted file mode 100644 index 6cedd97..0000000 Binary files a/docs/guide-zh-CN/images/sys-code-list.png and /dev/null differ diff --git a/docs/guide-zh-CN/images/sys-code-master-save.png b/docs/guide-zh-CN/images/sys-code-master-save.png deleted file mode 100644 index ba72388..0000000 Binary files a/docs/guide-zh-CN/images/sys-code-master-save.png and /dev/null differ diff --git a/docs/guide-zh-CN/images/sys-code-master.png b/docs/guide-zh-CN/images/sys-code-master.png deleted file mode 100644 index 4017ddb..0000000 Binary files a/docs/guide-zh-CN/images/sys-code-master.png and /dev/null differ diff --git a/docs/guide-zh-CN/images/sys-code-preview.png b/docs/guide-zh-CN/images/sys-code-preview.png deleted file mode 100644 index b48c720..0000000 Binary files a/docs/guide-zh-CN/images/sys-code-preview.png and /dev/null differ diff --git a/docs/guide-zh-CN/images/sys-db-by.png b/docs/guide-zh-CN/images/sys-db-by.png new file mode 100644 index 0000000..4dd6c3e Binary files /dev/null and b/docs/guide-zh-CN/images/sys-db-by.png differ diff --git a/docs/guide-zh-CN/images/sys-db-by2.png b/docs/guide-zh-CN/images/sys-db-by2.png new file mode 100644 index 0000000..bf25dfa Binary files /dev/null and b/docs/guide-zh-CN/images/sys-db-by2.png differ diff --git a/docs/guide-zh-CN/start-environment.md b/docs/guide-zh-CN/start-environment.md index 275150d..2741e17 100644 --- a/docs/guide-zh-CN/start-environment.md +++ b/docs/guide-zh-CN/start-environment.md @@ -15,7 +15,7 @@ 5. 命令行运行 `yarn -v` 若控制台输出版本号则前端环境搭建成功 ### 后端环境 -1. 下载golang安装 版本号需>=1.19 +1. 下载golang安装 版本号需>=1.21 2. 国际: https://golang.org/dl/ 3. 国内: https://golang.google.cn/dl/ 4. 命令行运行 go 若控制台输出各类提示命令 则安装成功 输入 `go version` 确认版本大于1.19 @@ -26,6 +26,6 @@ > 需要本地具有 git node golang 环境 - node版本 >= 16.0.0 -- golang版本 >= 1.19 +- golang版本 >= 1.21 - mysql版本 >= 5.7,引擎需要是 innoDB - IDE推荐:Goland diff --git a/docs/guide-zh-CN/start-installation.md b/docs/guide-zh-CN/start-installation.md index 1831f13..4c05b19 100644 --- a/docs/guide-zh-CN/start-installation.md +++ b/docs/guide-zh-CN/start-installation.md @@ -8,8 +8,8 @@ ### 环境要求 - node版本 >= v16.0.0 -- golang版本 >= v1.19 -- goframe版本 >=v2.6.4 +- golang版本 >= v1.21 +- goframe版本 >=v2.7.0 - mysql版本 >=5.7 > 必须先看[环境搭建文档](start-environment.md),如果安装遇到问题务必先查看[常见问题文档](start-issue.md) @@ -28,18 +28,37 @@ git clone https://github.com/bufanyun/hotgo.git && cd hotgo 1、服务端: - 项目数据库文件 `storage/data/hotgo.sql` 创建数据库并导入 - 将配置文件 `manifest/config/config.yaml.bak` 复制后改为`manifest/config/config.yaml` -- 将`manifest/config/config.yaml`中的数据库配置改为你自己的: +- 将`manifest/config/config.yaml`中的`database.default.link`数据库配置改为你自己的: ```yaml +# Database. 配置参考:https://goframe.org/pages/viewpage.action?pageId=1114245 database: logger: - level: "all" + path: "logs/database" # 日志文件路径。默认为空,表示关闭,仅输出到终端 + <<: *defaultLogger stdout: true default: - link: "mysql:hotgo:hg123456.@tcp(127.0.0.1:3306)/hotgo?loc=Local&parseTime=true" + link: "mysql:hotgo:hg123456.@tcp(127.0.0.1:3306)/hotgo?loc=Local&parseTime=true&charset=utf8mb4" debug: true Prefix: "hg_" ``` +- 将`hack/config.yaml`中的`gfcli.gen.dao[0].link`数据库配置改为你自己的: +```yaml +gfcli: + gen: + dao: + - link: "mysql:hotgo:hg123456.@tcp(127.0.0.1:3306)/hotgo?loc=Local&parseTime=true&charset=utf8mb4" + group: "default" # 分组 使用hotgo代码生成功能时必须填 + # tables: "" # 指定当前数据库中需要执行代码生成的数据表。如果为空,表示数据库的所有表都会生成。 + tablesEx: "hg_sys_addons_install" # 指定当前数据库中需要排除代码生成的数据表。 + removePrefix: "hg_" + descriptionTag: true + noModelComment: true + jsonCase: "CamelLower" + gJsonSupport: true + clear: false +``` + 2、web前端: - 配置服务端地址,包含在以下文件中: * /hotgo/web/.env.development diff --git a/docs/guide-zh-CN/start-issue.md b/docs/guide-zh-CN/start-issue.md index cadd9a7..4434817 100644 --- a/docs/guide-zh-CN/start-issue.md +++ b/docs/guide-zh-CN/start-issue.md @@ -45,6 +45,13 @@ 详细请参考 - [系统安装](start-installation.md) +#### 3、cannot find "hack/config.yaml" in following paths: +> 报错信息:get cli configuration file:hack/config.yaml, err:cannot find "hack/config.yaml" in following paths: + +系统运行目录下配置hack/config.yaml文件。如果是生产环境运行,并且不需要开发工具相关功能,可以将`manifest/config/config.yaml`配置文件中的`system.mode`值改为`product`,这样启动时不会加载开发工具相关功能 + + + ### 四、前端相关 #### 1、Error: connect ECONNREFUSED ::1:8000 diff --git a/docs/guide-zh-CN/start-update-log.md b/docs/guide-zh-CN/start-update-log.md index 8e9fe0b..a307e3e 100644 --- a/docs/guide-zh-CN/start-update-log.md +++ b/docs/guide-zh-CN/start-update-log.md @@ -11,6 +11,31 @@ > 如果升级(覆盖)代码后打开会出现 sql 报错, 请检查更新的数据库格式或自行调整 +### v2.15.1 +updated 2024.4.22 + +- 增加:生成代码增加树表生成、字段排序、字段重置、操作人维护,调整关联表生成方式 +- 增加:角色菜单权限增加一键导入支持 +- 增加:访问日志增加忽略请求方式 +- 增加:插件生成增加`consts`目录维护 +- 增加:IP归属地定位增加默认重试,增加新的缓存机制 +- 增加:登录日志增加UA信息、IP归属信息,解藕访问日志 +- 增加:消息队列`rocketmq`驱动更新版本问题,增加延迟队列,支持自动创建主题 +- 增加:部门增加类型支持,通过部门类型实现多租户业务支持 +- 增加:增加多租户业务开发演示 +- 修复:修复部门列表搜索算法在部分情况下无效问题 +- 修复:修复短信、邮件IP发送数量限制 +- 修复:修复分布式锁内存泄漏问题 +- 优化:gf版本升级到v2.7.0 +- 优化:naive-ui版本升级到2.38.1 +- 优化:优化`cmd`服务退出流程 +- 优化:优化生成代码格式化和移除未使用的包 +- 优化:服务端配置文件参数`hotgo`改为`system` +- 优化:web端表格/表单增加自适应宽度计算 +- 优化:日期选择组件不再使用默认值 +- 优化:优化菜单添加/编辑表单,使其操作更加简单方便 + + ### v2.13.1 updated 2024.3.7 diff --git a/docs/guide-zh-CN/sys-catalog.md b/docs/guide-zh-CN/sys-catalog.md index e61d0a5..bf9d040 100644 --- a/docs/guide-zh-CN/sys-catalog.md +++ b/docs/guide-zh-CN/sys-catalog.md @@ -70,6 +70,7 @@ | --- --- --- api | 前台通用接口,包含PC端、移动端接口等 | | --- --- --- home | 前台PC端、H5端页面 | | --- --- --- websocket | 可同时为多应用提供websocket接口 | +| --- --- consts | 插件内主要的常量定义 | | --- --- controller | 接收/解析用户输入参数的入口/接口层,也可以理解为控制器 | | --- --- crons | 项目中由系统统一接管的定时任务处理 | | --- --- global | 项目内主要的全局变量和系统的一些初始化操作 | diff --git a/docs/guide-zh-CN/sys-code.md b/docs/guide-zh-CN/sys-code.md index 9dee11e..63c69be 100644 --- a/docs/guide-zh-CN/sys-code.md +++ b/docs/guide-zh-CN/sys-code.md @@ -3,13 +3,15 @@ 目录 - 使用条件 -- 生成配置 -- 一个生成增删改查列表例子 -- 多数据库生成配置 +- 模板配置 +- CLI配置 +- 生成CRUD表格 +- 生成树形表格 +- 多数据库使用 - 自定义生成模板 - 内置gf-cli - 指定gf-cli版本 -- 指定数据库驱动类型 +- 指定数据库驱动 > 在HotGo中可以通过后台开发工具快速的一键生成CRUD,自动生成Api、控制器、业务逻辑、Web页面、表单组件、菜单权限等。 @@ -17,33 +19,23 @@ ### 使用条件 -- hotgo 版本号 >= 2.2.10 +- hotgo 版本号 >= 2.13.1 - 使用前必须先看 [数据库](sys-db.md) -#### 增删改查列表 - -- 表自增长字段为 `id` - -#### 关系树列表 - -- 表自增长字段为 `id` - ### 生成模板配置 -- 注意:线上环境务必将`allowedIPs`参数设为空,考虑到项目安全问题请勿线上生成使用! - -- 默认配置路径:server/manifest/config/config.yaml - +- 配置路径:server/manifest/config/config.yaml ```yaml +# 生成代码 hggen: - allowedIPs: ["127.0.0.1", "*"] # 白名单,*代表所有,只有允许的IP后台才能使用生成代码功能 - selectDbs: [ "default" ] # 可选生成表的数据库配置名称,支持多库 - disableTables : ["hg_sys_gen_codes","hg_admin_role_casbin"] # 禁用的表,禁用以后将不会在选择表中看到 - delimiters: ["@{", "}"] # 模板引擎变量分隔符号 + allowedIPs: [ "127.0.0.1", "*" ] # 白名单,*代表所有,只有允许的IP后台才能使用生成代码功能 + selectDbs: [ "default" ] # 可选生成表的数据库配置名称,支持多库 + disableTables: [ "hg_sys_gen_codes","hg_admin_role_casbin" ] # 禁用的表,禁用以后将不会在选择表中看到 + delimiters: [ "@{", "}" ] # 模板引擎变量分隔符号 # 生成应用模型,所有生成模板允许自定义,可以参考default模板进行改造 application: - # CRUD模板 + # CRUD和关系树列表模板 crud: templates: # 默认的主包模板 @@ -51,34 +43,28 @@ hggen: isAddon: false # 是否为插件模板 false|true masterPackage: "sys" # 主包名称,需和controllerPath、logicPath、inputPath保持关联 templatePath: "./resource/generate/default/curd" # 模板路径 - apiPath: "./api/admin" # gfApi生成路径 + apiPath: "./api/admin" # goApi生成路径 controllerPath: "./internal/controller/admin/sys" # 控制器生成路径 - logicPath : "./internal/logic/sys" # 主要业务生成路径 + logicPath: "./internal/logic/sys" # 主要业务生成路径 inputPath: "./internal/model/input/sysin" # 表单过滤器生成路径 - routerPath : "./internal/router/genrouter" # 生成路由表路径 - sqlPath : "./storage/data/generate" # 生成sql语句路径 + routerPath: "./internal/router/genrouter" # 生成路由表路径 + sqlPath: "./storage/data/generate" # 生成sql语句路径 webApiPath: "../web/src/api" # webApi生成路径 - webViewsPath : "../web/src/views" # web页面生成路径 + webViewsPath: "../web/src/views" # web页面生成路径 - # 默认的插件包模板 + # 默认的插件包模板,{$name}会自动替换成实际的插件名称 - group: "addon" # 分组名称 isAddon: true # 是否为插件模板 false|true masterPackage: "sys" # 主包名称,需和controllerPath、logicPath、inputPath保持关联 templatePath: "./resource/generate/default/curd" # 模板路径 - apiPath: "./addons/{$name}/api/admin" # gfApi生成路径 + apiPath: "./addons/{$name}/api/admin" # goApi生成路径 controllerPath: "./addons/{$name}/controller/admin/sys" # 控制器生成路径 - logicPath : "./addons/{$name}/logic/sys" # 主要业务生成路径 + logicPath: "./addons/{$name}/logic/sys" # 主要业务生成路径 inputPath: "./addons/{$name}/model/input/sysin" # 表单过滤器生成路径 - routerPath : "./addons/{$name}/router/genrouter" # 生成路由表路径 - sqlPath : "./storage/data/generate/addons" # 生成sql语句路径 + routerPath: "./addons/{$name}/router/genrouter" # 生成路由表路径 + sqlPath: "./storage/data/generate/addons" # 生成sql语句路径 webApiPath: "../web/src/api/addons/{$name}" # webApi生成路径 - webViewsPath : "../web/src/views/addons/{$name}" # web页面生成路径 - - # 关系树列表模板 - tree: - templates: - - group: "default" - templatePath: "./resource/generate/default/tree" + webViewsPath: "../web/src/views/addons/{$name}" # web页面生成路径 # 消息队列模板 queue: @@ -91,14 +77,19 @@ hggen: templates: - group: "default" templatePath: "./resource/generate/default/cron" + + # 生成插件模块,通过后台创建新插件时使用的模板,允许自定义,可以参考default模板进行改造 + addon: + srcPath: "./resource/generate/default/addon" # 生成模板路径 + webApiPath: "../web/src/api/addons/{$name}" # webApi生成路径 + webViewsPath: "../web/src/views/addons/{$name}" # web页面生成路径 ``` -### 生成dao、service配置 - +### 生成CLI配置 - hotgo在生成dao、service配置时,默认了和gf官方一致的配置方式和代码生成规则。所以无论你是通过hotgo亦或gf命令生成,最终代码格式完全一致,遵循一致的代码规范。 -- 默认配置路径:server/hack/config.yaml +- 配置路径:[server/hack/config.yaml](../../server/hack/config.yaml) ```yaml gfcli: @@ -134,14 +125,14 @@ gfcli: clear: true ``` -### 一个生成增删改查列表例子 +### 生成CRUD表格 - 推荐使用热编译方式启动HotGo,这样生成完成页面自动刷新即可看到新生成内容,无需手动重启 - 服务端热编译启动:`gf run main.go`, web前端启动:`yarn dev` 1、创建数据表 -1.1 创建测试表格表`hg_test_table`: +1.1 创建测试表格表`hg_test_table` ```mysql CREATE TABLE `hg_test_table` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID', @@ -221,7 +212,7 @@ INSERT INTO `hg_test_table` (`id`, `category_id`, `title`, `description`, `conte - 如果你使用的热编译,那么页面会在生成成功后立即刷新,刷新完成你即可在后台菜单栏中看到`测试表格`菜单。如果不是使用热编译启动,请手动重启服务后刷新。 -#### 注意:热编译环境下,web端往往会快于服务端重启并加载完成,此时访问新生成的菜单页面可能会存在某接口请求超时或404问题,这是服务端正在重启导致的,属于正常现象,一般稍等几秒再试即可。 +- 注意:热编译环境下,web端往往会快于服务端重启并加载完成,此时访问新生成的菜单页面可能会存在某接口请求超时或404问题,这是服务端正在重启导致的,属于正常现象,一般稍等几秒再试即可。 - 接下来让我们看看生成的表格页面,效果如下: @@ -263,19 +254,20 @@ INSERT INTO `hg_test_table` (`id`, `category_id`, `title`, `description`, `conte - 至此生成增删改查列表示例结束! +### 生成树形表格 +待写。 -## 多数据库生成配置 +### 多数据库使用 -#### 假设我们要增加一个库名为`hotgo2`、分组为`default2`的数据库,并要为其生成代码 +- 假设我们要增加一个库名为`hotgo2`、分组为`default2`的数据库,并要为其生成代码 -1. 配置`server/hack/config.yaml`,如下: +1. 配置[server/hack/config.yaml](../../server/hack/config.yaml) 如下: ```yaml gen: dao: - link: "mysql:hotgo:hg123456.@tcp(127.0.0.1:3306)/hotgo?loc=Local&parseTime=true" group: "default" # 分组 使用hotgo代码生成功能时必须填 - # tables: "" # 指定当前数据库中需要执行代码生成的数据表。如果为空,表示数据库的所有表都会生成。 tablesEx: "hg_sys_addons_install" # 指定当前数据库中需要排除代码生成的数据表。 removePrefix: "hg_" descriptionTag: true @@ -285,8 +277,7 @@ INSERT INTO `hg_test_table` (`id`, `category_id`, `title`, `description`, `conte clear: false - link: "mysql:hotgo2:hg123456.@tcp(127.0.0.1:3306)/hotgo2?loc=Local&parseTime=true" group: "default2" # 分组 使用hotgo代码生成功能时必须填 - # tables: "" # 指定当前数据库中需要执行代码生成的数据表。如果为空,表示数据库的所有表都会生成。 - tablesEx: "hg_sys_addons_install" # 指定当前数据库中需要排除代码生成的数据表。 + tablesEx: "hg_sys_addons_install" # 指定当前数据库中需要排除代码生成的数据表。 removePrefix: "" descriptionTag: true noModelComment: true @@ -297,7 +288,6 @@ INSERT INTO `hg_test_table` (`id`, `category_id`, `title`, `description`, `conte 2. 配置`server/manifest/config/config.yaml`, - `database`配置如下: ```yaml database: @@ -318,21 +308,21 @@ database: ```yaml hggen: allowedIPs: ["127.0.0.1", "*"] # 白名单,*代表所有,只有允许的IP后台才能使用生成代码功能 - selectDbs: [ "default", "default2" ] # 可选生成表的数据库配置名称,支持多库 + selectDbs: [ "default", "default2" ] # 可选生成表的数据库配置名称,支持多库 disableTables : ["hg_sys_gen_codes","hg_admin_role_casbin"] # 禁用的表,禁用以后将不会在选择表中看到 delimiters: ["@{", "}"] # 模板引擎变量分隔符号 ``` 3. 登录HotGo后台 -> 开发工具 -> 代码生成 -> 找到立即生成按钮并打开,就会发现`数据库`选项增加了一个`default2`,后续生成步骤和生成例子完全一样 - > 注意:上述的配置中所有的`default2`名称必须保持一致 + ### 自定义生成模板 -> 系统内置了两组CURD生成模板,请参考[生成配置]。default:是默认的生成到主模块下;addon:是默认生成到指定的插件下 +> 系统内置了两组CURD生成模板,请参考[生成模板配置](sys-code.md#生成模板配置)。default:是默认的生成到主模块下;addon:是默认生成到指定的插件下 -- 如果你在实际的开发过程中默认模板需要调整的地方较多时,HotGo允许你新建新的模板分组来满足你的需求。新的模板可根据现有模板基础拷贝一份出来做改造,默认模板目录:[server/resource/generate/default](../../server/resource/generate/default) +- HotGo允许你新建新的模板分组来满足你的需求,模板可根据现有模板基础拷贝一份出来做改造,默认模板目录:[server/resource/generate/default](../../server/resource/generate/default) @@ -358,7 +348,7 @@ hggen: -### 指定数据库驱动类型 +### 指定数据库驱动 > HotGo默认使用mysql驱动,如果你想用其他数据库驱动打开下方文件中注释即可 diff --git a/docs/guide-zh-CN/sys-db.md b/docs/guide-zh-CN/sys-db.md index 7a96fed..369a132 100644 --- a/docs/guide-zh-CN/sys-db.md +++ b/docs/guide-zh-CN/sys-db.md @@ -6,8 +6,7 @@ - 特殊字段默认表单组件 - 特殊字段默认表单验证器 - SQL默认查询方式 -- 其他默认选项 -- 常见问题 +- 其他 ### 字段类型 @@ -85,7 +84,7 @@ -### 其他默认选项 +### 其他 #### 默认字典选项 @@ -94,8 +93,8 @@ #### 默认属性 - 默认必填,当数据库字段存在非空`IS_NULLABLE`属性时,默认勾选必填验证 -- 默认唯一,当数据库字段属性存在`UNI`时,默认勾选唯一值验证 -- 默认主键,当数据库字段属性存在`PRI`时,默认为主键,不允许编辑 +- 默认唯一,当数据库字段索引存在`UNI`时,默认勾选唯一值验证 +- 默认主键,当数据库字段索引存在`PRI`时,默认为主键,不允许编辑 - 默认排序,当数据库字段存在`sort`时,默认开启排序,添加表单自动获取最大排序增量值并填充表单 - 默认列名,默认使用字段注释作为表格的列名。当数据库字段未设置注释时,默认使用字段名称作为列名 @@ -106,10 +105,45 @@ - 软删除,表存在字段`deleted_at`时,使用表的Orm模型查询条件将会自动加入[ `deleted_at` IS NULL ],删除时只更新删除时间而不会真的删除数据 - 树表:不论更新插入,都会根据表中字段`pid`(上级ID)自动维护`level`(树等级)和`tree`(关系树) + +#### 操作人字段维护 + +- 生成列表中存在并且勾选展示字段`created_by`(创建者)、`updated_by`(修改者)、`deleted_by`(删除者)时,会自动到表`hg_admin_member`中获取操作人的基本信息摘要,并渲染到列表中,效果如下: + +![](images/sys-db-by.png) + +- 生成列表中存在并且勾选查询字段`created_by`(创建者)、`updated_by`(修改者)、`deleted_by`(删除者)时,会强制将查询表单改为关键词查询,从`hg_admin_member`查询操作人。效果如下: + +![](images/sys-db-by2.png) + +- 查询代码片段,参考路径:[server/internal/logic/admin/member.go](../../server/internal/logic/admin/member.go) +```go + +// 查询创建者 +if in.CreatedBy != "" { + ids, err := service.AdminMember().GetIdsByKeyword(ctx, in.CreatedBy) + if err != nil { + return nil, 0, err + } + mod = mod.WhereIn(dao.SysGenCurdDemo.Columns().CreatedBy, ids) +} + +// GetIdsByKeyword 根据关键词查找符合条件的用户ID +func (s *sAdminMember) GetIdsByKeyword(ctx context.Context, ks string) (res []int64, err error) { + ks = gstr.Trim(ks) + if len(ks) == 0 { + return + } + array, err := dao.AdminMember.Ctx(ctx).Fields("id"). + Where("`id` = ? or `real_name` = ? or `username` = ? or `mobile` = ?", ks, ks, ks, ks). + Array() + if err != nil { + err = gerror.Wrap(err, "根据关键词获取用户ID失败,请稍后重试!") + } + res = gvar.New(array).Int64s() + return +} +``` + + > 这里只列举了较为常用的默认规则,其他更多默认规则请参考:[server/internal/library/hggen/views/column_default.go](../../server/internal/library/hggen/views/column_default.go) - -#### 常见问题 - -#### 1、生成表格字段如何排序? - -表格字端排序默认使用的是数据库物理字段的排序位置,所以如果你对字段的位置顺序有要求,请在生成前调整数据库物理字段位置 diff --git a/docs/guide-zh-CN/sys-library.md b/docs/guide-zh-CN/sys-library.md index e359633..99471c1 100644 --- a/docs/guide-zh-CN/sys-library.md +++ b/docs/guide-zh-CN/sys-library.md @@ -154,7 +154,7 @@ func test(ctx context.Context) { ### 数据字典 -- hotgo增加了对枚举字典和自定义方法字典的内置支持,从而在系统中经常使用的一些特定数据维护基础上做出了增强。 +- hotgo增加了对枚举字典和自定义方法字典的内置支持,从而在系统中经常使用的一些特定数据上做出了增强。 #### 字典数据选项 - 文件路径:server/internal/model/dict.go diff --git a/docs/guide-zh-CN/sys-queue.md b/docs/guide-zh-CN/sys-queue.md index e22d849..f08b280 100644 --- a/docs/guide-zh-CN/sys-queue.md +++ b/docs/guide-zh-CN/sys-queue.md @@ -14,33 +14,33 @@ - 配置文件:server/manifest/config/config.yaml ```yaml -#消息队列 +# 消息队列 queue: switch: true # 队列开关,可选:true|false,默认为true - driver: "disk" # 队列驱动,可选:redis|rocketmq|kafka,默认为disk - retry: 2 # 重试次数,仅rocketmq|redis支持 + driver: "disk" # 队列驱动,可选:disk|redis|rocketmq|kafka,默认为disk groupName: "hotgo" # mq群组名称 - #磁盘队列 + # 磁盘队列 disk: path: "./storage/diskqueue" # 数据存放路径 batchSize: 100 # 每100条消息同步一次,batchSize和batchTime满足其一就会同步一次 batchTime: 1 # 每1秒消息同步一次 segmentSize: 10485760 # 每个topic分片数据文件最大字节,默认10M segmentLimit: 3000 # 每个topic最大分片数据文件数量,超出部分将会丢弃 + # redis,默认使用全局redis运行队列 redis: - address: "127.0.0.1:6379" # redis服务地址,默认为127.0.0.1:6379 - db: 2 # 指定redis库 - pass: "" # redis密码 - timeout: 0 # 队列超时时间(s) ,0为永不超时,当队列一直没有被消费到达超时时间则队列会被销毁 + timeout: 0 # 队列超时时间以秒为单位,0表示永不超时。如果队列在设定的超时时间内没有被消费,则会被销毁 rocketmq: - address: "127.0.0.1:9876" # brocker地址+端口 - logLevel: "all" # 系统日志级别,可选:all|close|debug|info|warn|error|fatal + nameSrvAdders: ["127.0.0.1:9876"] # nameSrvAdder+端口,支持多个 + accessKey: "" # 选填。如果开启了acl控制就必填 + secretKey: "" # 选填。如果开启了acl控制就必填 + brokerAddr: "127.0.0.1:10911" # brokerAddr+端口,选填。用于消费者订阅主题前会检查主题是否存在,不存在会自动创建。你也可以在rocketmq控制台手动创建主题 + retry: 0 # 重试次数 + logLevel: "info" # 系统日志级别,可选:all|close|debug|info|warn|error|fatal kafka: address: "127.0.0.1:9092" # kafka地址+端口 version: "2.0.0.0" # kafka专属配置,默认2.0.0.0 randClient: true # 开启随机生成clientID,可以实现启动多实例同时一起消费相同topic,加速消费能力的特性,默认为true multiConsumer: true # 是否支持创建多个消费者 - ``` ### 实现接口 @@ -123,7 +123,7 @@ func test() { ``` -延迟队列,目前只有redis驱动支持: +延迟队列,目前只有redis、rocketmq驱动支持: ```go package main @@ -140,10 +140,19 @@ func test() { //... } - // 延迟10秒 + // redis 延迟10秒 if err := queue.SendDelayMsg(consts.QueueLogTopic, data, 10); err != nil { fmt.Printf("queue.Push err:%+v", err) } + + // rocketmq 延迟5秒 + // 注意rocketmq这里传入的是延迟级别,而不是秒 + // 消息的延时级别level一共有18级,分别为: + // 1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h + // 参考:https://github.com/apache/rocketmq-client-go/blob/0e19ee654819bda396a08d950c883f9008b8222b/primitive/message.go#L174 + if err := queue.SendDelayMsg(consts.QueueLogTopic, data, 2); err != nil { + fmt.Printf("queue.Push err:%+v", err) + } } ``` @@ -178,10 +187,10 @@ type MqMsg struct { Body []byte `json:"body"` } - type MqProducer interface { SendMsg(topic string, body string) (mqMsg MqMsg, err error) SendByteMsg(topic string, body []byte) (mqMsg MqMsg, err error) + SendDelayMsg(topic string, body string, delay int64) (mqMsg MqMsg, err error) } type MqConsumer interface { diff --git a/docs/guide-zh-CN/sys-tenant.md b/docs/guide-zh-CN/sys-tenant.md new file mode 100644 index 0000000..931224e --- /dev/null +++ b/docs/guide-zh-CN/sys-tenant.md @@ -0,0 +1,172 @@ +## SaaS多租户 + + +目录 + +- 介绍 +- 职责划分 +- 如何在HotGo开发多租户业务 +- 多租户数据库设计 +- 多租户功能演示 + +### 介绍 + +SaaS系统多租户多应用设计,已成为互联网企业的重要发展建设方向。核心在于多租户SAAS系统独立前台、共享后台、共享数据库的平台应用架构。 + +> 目前HotGo的部分基础功能并不完全支持多租户设计,但这并不妨碍开发者基于HotGo构建自己的多租户产品 + + +### 职责划分 + +在SaaS系统多租户中,不同身份用户的职责功能划分假设可以是这样: + +> 这只是一个粗略的概念,实际开发时应根据业务来进行调整,每个身份也都不是必须的。 + +| 身份 | 标识 | 职责和功能划分 | +|-----------------|----------|-------------------------------------------------------| +| 公司| company | 管理整个平台,包括商户和用户账户、系统设置以及其他全局性业务流程。 | +| 租户 | tenant | 多租户系统中顶层实体客户、组织或实体。有自己的多个商户、用户、产品、订单等。拥有独立的数据隔离和安全边界。 | +| 商户 | merchant | 受租户的监管和管理,可独立经营的实体。提供产品或服务,管理自己的业务,包括库存管理、订单处理、结算等。 | +| 用户 | user | 真正购买产品或享受服务的人,与商户互动,管理个人信息等个性化功能。 | + + +### 如何在HotGo开发多租户业务 +#### 一、应用功能 + +根据角色来划分用户的后台功能,在创建用户时为其绑定角色,然后为不同角色分配不同的功能菜单 + +请参考: [权限控制](sys-auth.md) + + +#### 二、 数据隔离 + +根据部门来划定用户的数据权限范围,在创建用户时为其绑定部门 + +- 在用户登录成功后,server端可通过上下文来获取用户部门类型来确定用户身份 +- 文件路径:server/internal/library/contexts/context.go +```go +package contexts + +import ( + "context" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "hotgo/internal/consts" + "hotgo/internal/model" +) + +// GetDeptType 获取用户部门类型 +func GetDeptType(ctx context.Context) string { + user := GetUser(ctx) + if user == nil { + return "" + } + return user.DeptType +} + +// IsCompanyDept 是否为公司部门 +func IsCompanyDept(ctx context.Context) bool { + return GetDeptType(ctx) == consts.DeptTypeCompany +} + +// IsTenantDept 是否为租户部门 +func IsTenantDept(ctx context.Context) bool { + return GetDeptType(ctx) == consts.DeptTypeTenant +} + +// IsMerchantDept 是否为商户部门 +func IsMerchantDept(ctx context.Context) bool { + return GetDeptType(ctx) == consts.DeptTypeMerchant +} + +// IsUserDept 是否为普通用户部门 +func IsUserDept(ctx context.Context) bool { + return GetDeptType(ctx) == consts.DeptTypeUser +} +``` + +- 在用户登录成功后,web端可通`useUserStore`来获取用户部门类型来确定用户身份 +- 文件路径:web/src/store/modules/user.ts +```vue + +``` + +### 多租户数据库设计 + +HotGo定位是中小型应用开发,推荐采用一套数据库不同Schema。就是在多租户业务表中加入用户标识字段,来区分不同用户的数据,如:`tenant_id` + +- 参考文章:https://blog.csdn.net/haponchang/article/details/104246317 + + +### 多租户功能演示 + +请登录后台【插件应用】-【功能案例】-【多租户功能演示】查看 + +#### 自动维护租户关系 + +- 只需在表设计时包含以下字段,即可使用handler和hook实现租户权限过滤和租户关系维护 + +| 字段名称 | 数据类型 | 字段注释 | 必选 | +|------|----------|------|-----------------------------------------------------| +| tenant_id | bigint(20) | 租户ID | 否 | +| merchant_id | bigint(20) | 商户ID | 否 | +| user_id | bigint(20) | 用户ID | 否 | + +下面是多租户功能演示例子代码中的使用片段 + +- 封装查询Model +```go +// Model 多租户功能演示ORM模型 +func (s *sSysTenantOrder) Model(ctx context.Context, option ...*handler.Option) *gdb.Model { + if len(option) == 0 { + // 过滤多租户数据权限 + option = append(option, &handler.Option{ + FilterTenant: true, + //FilterAuth: true, // 如果还需要维护created_by、member_id等部门数据权限范围可放开注释 + }) + } + return handler.Model(dao.AddonHgexampleTenantOrder.Ctx(ctx), option...) +} +``` + +- 增改数据自动维护租户关系 +```go +// Edit 修改/新增多租户功能演示 +func (s *sSysTenantOrder) Edit(ctx context.Context, in *sysin.TenantOrderEditInp) (err error) { + return g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) (err error) { + + // 修改 + if in.Id > 0 { + if _, err = s.Model(ctx). + Fields(sysin.TenantOrderUpdateFields{}). + WherePri(in.Id).Data(in). + Hook(hook.SaveTenant). // 自动维护租户关系更新 + Update(); err != nil { + } + return + } + + // 新增 + if _, err = dao.AddonHgexampleTenantOrder.Ctx(ctx). + Fields(sysin.TenantOrderInsertFields{}). + Hook(hook.SaveTenant). // 自动维护租户关系更新 + Data(in). + Insert(); err != nil { + } + return + }) +} +``` + +相关代码文件:/server/addons/hgexample/logic/sys/tenant_order.go + diff --git a/server/addons/hgexample/api/admin/table/table.go b/server/addons/hgexample/api/admin/table/table.go index 32f8b85..368c4dc 100644 --- a/server/addons/hgexample/api/admin/table/table.go +++ b/server/addons/hgexample/api/admin/table/table.go @@ -13,18 +13,18 @@ import ( // ListReq 查询列表 type ListReq struct { - g.Meta `path:"/table/list" method:"get" tags:"表格" summary:"获取表格列表"` + g.Meta `path:"/table/list" method:"get" tags:"表格例子" summary:"获取表格列表"` sysin.TableListInp } type ListRes struct { form.PageRes - List []*sysin.TableListModel `json:"list" dc:"数据列表"` + List []*sysin.TableListModel `json:"list" dc:"数据列表"` } // ExportReq 导出列表 type ExportReq struct { - g.Meta `path:"/table/export" method:"get" tags:"表格" summary:"导出表格列表"` + g.Meta `path:"/table/export" method:"get" tags:"表格例子" summary:"导出表格列表"` sysin.TableListInp } @@ -32,7 +32,7 @@ type ExportRes struct{} // ViewReq 获取信息 type ViewReq struct { - g.Meta `path:"/table/view" method:"get" tags:"表格" summary:"获取指定信息"` + g.Meta `path:"/table/view" method:"get" tags:"表格例子" summary:"获取指定信息"` sysin.TableViewInp } @@ -42,7 +42,7 @@ type ViewRes struct { // EditReq 修改/新增 type EditReq struct { - g.Meta `path:"/table/edit" method:"post" tags:"表格" summary:"修改/新增表格"` + g.Meta `path:"/table/edit" method:"post" tags:"表格例子" summary:"修改/新增表格"` sysin.TableEditInp } @@ -50,7 +50,7 @@ type EditRes struct{} // DeleteReq 删除 type DeleteReq struct { - g.Meta `path:"/table/delete" method:"post" tags:"表格" summary:"删除表格"` + g.Meta `path:"/table/delete" method:"post" tags:"表格例子" summary:"删除表格"` sysin.TableDeleteInp } @@ -58,7 +58,7 @@ type DeleteRes struct{} // MaxSortReq 最大排序 type MaxSortReq struct { - g.Meta `path:"/table/maxSort" method:"get" tags:"表格" summary:"表格最大排序"` + g.Meta `path:"/table/maxSort" method:"get" tags:"表格例子" summary:"表格最大排序"` sysin.TableMaxSortInp } @@ -68,7 +68,7 @@ type MaxSortRes struct { // StatusReq 更新状态 type StatusReq struct { - g.Meta `path:"/table/status" method:"post" tags:"表格" summary:"更新表格状态"` + g.Meta `path:"/table/status" method:"post" tags:"表格例子" summary:"更新表格状态"` sysin.TableStatusInp } @@ -76,7 +76,7 @@ type StatusRes struct{} // SwitchReq 更新开关状态 type SwitchReq struct { - g.Meta `path:"/table/switch" method:"post" tags:"表格" summary:"更新表格状态"` + g.Meta `path:"/table/switch" method:"post" tags:"表格例子" summary:"更新表格状态"` sysin.TableSwitchInp } diff --git a/server/addons/hgexample/api/admin/tenantorder/tenantorder.go b/server/addons/hgexample/api/admin/tenantorder/tenantorder.go new file mode 100644 index 0000000..edb8c2e --- /dev/null +++ b/server/addons/hgexample/api/admin/tenantorder/tenantorder.go @@ -0,0 +1,59 @@ +// Package tenantorder +// @Link https://github.com/bufanyun/hotgo +// @Copyright Copyright (c) 2024 HotGo CLI +// @Author Ms <133814250@qq.com> +// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE +// @AutoGenerate Version 2.13.1 +package tenantorder + +import ( + "hotgo/addons/hgexample/model/input/sysin" + "hotgo/internal/model/input/form" + + "github.com/gogf/gf/v2/frame/g" +) + +// ListReq 查询多租户功能演示列表 +type ListReq struct { + g.Meta `path:"/tenantOrder/list" method:"get" tags:"多租户功能演示" summary:"获取多租户功能演示列表"` + sysin.TenantOrderListInp +} + +type ListRes struct { + form.PageRes + List []*sysin.TenantOrderListModel `json:"list" dc:"数据列表"` +} + +// ExportReq 导出多租户功能演示列表 +type ExportReq struct { + g.Meta `path:"/tenantOrder/export" method:"get" tags:"多租户功能演示" summary:"导出多租户功能演示列表"` + sysin.TenantOrderListInp +} + +type ExportRes struct{} + +// ViewReq 获取多租户功能演示指定信息 +type ViewReq struct { + g.Meta `path:"/tenantOrder/view" method:"get" tags:"多租户功能演示" summary:"获取多租户功能演示指定信息"` + sysin.TenantOrderViewInp +} + +type ViewRes struct { + *sysin.TenantOrderViewModel +} + +// EditReq 修改/新增多租户功能演示 +type EditReq struct { + g.Meta `path:"/tenantOrder/edit" method:"post" tags:"多租户功能演示" summary:"修改/新增多租户功能演示"` + sysin.TenantOrderEditInp +} + +type EditRes struct{} + +// DeleteReq 删除多租户功能演示 +type DeleteReq struct { + g.Meta `path:"/tenantOrder/delete" method:"post" tags:"多租户功能演示" summary:"删除多租户功能演示"` + sysin.TenantOrderDeleteInp +} + +type DeleteRes struct{} \ No newline at end of file diff --git a/server/addons/hgexample/api/admin/treetable/treetable.go b/server/addons/hgexample/api/admin/treetable/treetable.go index 93ebc3d..d5ebb96 100644 --- a/server/addons/hgexample/api/admin/treetable/treetable.go +++ b/server/addons/hgexample/api/admin/treetable/treetable.go @@ -13,7 +13,7 @@ import ( // ListReq 查询列表 type ListReq struct { - g.Meta `path:"/treeTable/list" method:"get" tags:"表格" summary:"获取表格列表"` + g.Meta `path:"/treeTable/list" method:"get" tags:"树形表格例子" summary:"获取表格列表"` sysin.TreeTableListInp } @@ -24,7 +24,7 @@ type ListRes struct { // ExportReq 导出列表 type ExportReq struct { - g.Meta `path:"/treeTable/export" method:"get" tags:"表格" summary:"导出表格列表"` + g.Meta `path:"/treeTable/export" method:"get" tags:"树形表格例子" summary:"导出表格列表"` sysin.TableListInp } @@ -32,7 +32,7 @@ type ExportRes struct{} // ViewReq 获取信息 type ViewReq struct { - g.Meta `path:"/treeTable/view" method:"get" tags:"表格" summary:"获取指定信息"` + g.Meta `path:"/treeTable/view" method:"get" tags:"树形表格例子" summary:"获取指定信息"` sysin.TableViewInp } @@ -42,7 +42,7 @@ type ViewRes struct { // EditReq 修改/新增 type EditReq struct { - g.Meta `path:"/treeTable/edit" method:"post" tags:"表格" summary:"修改/新增表格"` + g.Meta `path:"/treeTable/edit" method:"post" tags:"树形表格例子" summary:"修改/新增表格"` sysin.TableEditInp } @@ -50,7 +50,7 @@ type EditRes struct{} // DeleteReq 删除 type DeleteReq struct { - g.Meta `path:"/treeTable/delete" method:"post" tags:"表格" summary:"删除表格"` + g.Meta `path:"/treeTable/delete" method:"post" tags:"树形表格例子" summary:"删除表格"` sysin.TableDeleteInp } @@ -58,7 +58,7 @@ type DeleteRes struct{} // MaxSortReq 最大排序 type MaxSortReq struct { - g.Meta `path:"/treeTable/maxSort" method:"get" tags:"表格" summary:"表格最大排序"` + g.Meta `path:"/treeTable/maxSort" method:"get" tags:"树形表格例子" summary:"表格最大排序"` sysin.TableMaxSortInp } @@ -68,7 +68,7 @@ type MaxSortRes struct { // StatusReq 更新状态 type StatusReq struct { - g.Meta `path:"/treeTable/status" method:"post" tags:"表格" summary:"更新表格状态"` + g.Meta `path:"/treeTable/status" method:"post" tags:"树形表格例子" summary:"更新表格状态"` sysin.TableStatusInp } @@ -76,7 +76,7 @@ type StatusRes struct{} // SwitchReq 更新开关状态 type SwitchReq struct { - g.Meta `path:"/treeTable/switch" method:"post" tags:"表格" summary:"更新表格状态"` + g.Meta `path:"/treeTable/switch" method:"post" tags:"树形表格例子" summary:"更新表格状态"` sysin.TableSwitchInp } @@ -84,7 +84,7 @@ type SwitchRes struct{} // SelectReq 树形选项 type SelectReq struct { - g.Meta `path:"/treeTable/select" method:"get" tags:"表格" summary:"树形选项"` + g.Meta `path:"/treeTable/select" method:"get" tags:"树形表格例子" summary:"树形选项"` } type SelectRes struct { diff --git a/server/addons/hgexample/api/home/index/index.go b/server/addons/hgexample/api/home/index/index.go index 3a876ed..7a44e68 100644 --- a/server/addons/hgexample/api/home/index/index.go +++ b/server/addons/hgexample/api/home/index/index.go @@ -12,7 +12,7 @@ import ( // TestReq 测试 type TestReq struct { - g.Meta `path:"/index/test" method:"get" summary:"功能案例" tags:"测试首页"` + g.Meta `path:"/index/test" method:"get" tags:"功能案例" summary:"测试首页"` sysin.IndexTestInp } diff --git a/server/addons/hgexample/controller/admin/sys/comp.go b/server/addons/hgexample/controller/admin/sys/comp.go index 9eb5978..ba57d0b 100644 --- a/server/addons/hgexample/controller/admin/sys/comp.go +++ b/server/addons/hgexample/controller/admin/sys/comp.go @@ -20,10 +20,10 @@ type cComp struct{} // ImportExcel 导入Excel func (c *cComp) ImportExcel(ctx context.Context, req *comp.ImportExcelReq) (res *comp.ImportExcelRes, err error) { file, err := req.File.Open() - defer file.Close() if err != nil { return } + defer file.Close() excel, err := excelize.OpenReader(file) if err != nil { diff --git a/server/addons/hgexample/controller/admin/sys/tenant_order.go b/server/addons/hgexample/controller/admin/sys/tenant_order.go new file mode 100644 index 0000000..95977a3 --- /dev/null +++ b/server/addons/hgexample/controller/admin/sys/tenant_order.go @@ -0,0 +1,67 @@ +// Package sys +// @Link https://github.com/bufanyun/hotgo +// @Copyright Copyright (c) 2024 HotGo CLI +// @Author Ms <133814250@qq.com> +// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE +// @AutoGenerate Version 2.13.1 +package sys + +import ( + "context" + "hotgo/addons/hgexample/api/admin/tenantorder" + "hotgo/addons/hgexample/model/input/sysin" + "hotgo/addons/hgexample/service" +) + +var ( + TenantOrder = cTenantOrder{} +) + +type cTenantOrder struct{} + +// List 查看多租户功能演示列表 +func (c *cTenantOrder) List(ctx context.Context, req *tenantorder.ListReq) (res *tenantorder.ListRes, err error) { + list, totalCount, err := service.SysTenantOrder().List(ctx, &req.TenantOrderListInp) + if err != nil { + return + } + + if list == nil { + list = []*sysin.TenantOrderListModel{} + } + + res = new(tenantorder.ListRes) + res.List = list + res.PageRes.Pack(req, totalCount) + return +} + +// Export 导出多租户功能演示列表 +func (c *cTenantOrder) Export(ctx context.Context, req *tenantorder.ExportReq) (res *tenantorder.ExportRes, err error) { + err = service.SysTenantOrder().Export(ctx, &req.TenantOrderListInp) + return +} + +// Edit 更新多租户功能演示 +func (c *cTenantOrder) Edit(ctx context.Context, req *tenantorder.EditReq) (res *tenantorder.EditRes, err error) { + err = service.SysTenantOrder().Edit(ctx, &req.TenantOrderEditInp) + return +} + +// View 获取指定多租户功能演示信息 +func (c *cTenantOrder) View(ctx context.Context, req *tenantorder.ViewReq) (res *tenantorder.ViewRes, err error) { + data, err := service.SysTenantOrder().View(ctx, &req.TenantOrderViewInp) + if err != nil { + return + } + + res = new(tenantorder.ViewRes) + res.TenantOrderViewModel = data + return +} + +// Delete 删除多租户功能演示 +func (c *cTenantOrder) Delete(ctx context.Context, req *tenantorder.DeleteReq) (res *tenantorder.DeleteRes, err error) { + err = service.SysTenantOrder().Delete(ctx, &req.TenantOrderDeleteInp) + return +} \ No newline at end of file diff --git a/server/addons/hgexample/logic/sys/table.go b/server/addons/hgexample/logic/sys/table.go index ebf8c4b..71b58c5 100644 --- a/server/addons/hgexample/logic/sys/table.go +++ b/server/addons/hgexample/logic/sys/table.go @@ -119,7 +119,7 @@ func (s *sSysTable) Export(ctx context.Context, in *sysin.TableListInp) (err err } var ( - fileName = "表格例子导出-" + gctx.CtxId(ctx) + ".xlsx" + fileName = "表格例子导出-" + gctx.CtxId(ctx) sheetName = fmt.Sprintf("索引条件共%v行,共%v页,当前导出是第%v页,本页共%v行", totalCount, form.CalPageCount(totalCount, in.PerPage), in.Page, len(list)) exports []sysin.TableExportModel ) diff --git a/server/addons/hgexample/logic/sys/tenant_order.go b/server/addons/hgexample/logic/sys/tenant_order.go new file mode 100644 index 0000000..22e27ef --- /dev/null +++ b/server/addons/hgexample/logic/sys/tenant_order.go @@ -0,0 +1,176 @@ +// Package sys +// @Link https://github.com/bufanyun/hotgo +// @Copyright Copyright (c) 2024 HotGo CLI +// @Author Ms <133814250@qq.com> +// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE +// @AutoGenerate Version 2.13.1 +package sys + +import ( + "context" + "fmt" + "hotgo/addons/hgexample/model/input/sysin" + "hotgo/addons/hgexample/service" + "hotgo/internal/dao" + "hotgo/internal/library/hgorm/handler" + "hotgo/internal/library/hgorm/hook" + "hotgo/internal/model/input/form" + "hotgo/utility/convert" + "hotgo/utility/excel" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/util/gconv" +) + +type sSysTenantOrder struct{} + +func NewSysTenantOrder() *sSysTenantOrder { + return &sSysTenantOrder{} +} + +func init() { + service.RegisterSysTenantOrder(NewSysTenantOrder()) +} + +// Model 多租户功能演示ORM模型 +func (s *sSysTenantOrder) Model(ctx context.Context, option ...*handler.Option) *gdb.Model { + if len(option) == 0 { + // 过滤多租户数据权限 + option = append(option, &handler.Option{ + FilterTenant: true, + //FilterAuth: true, // 如果还需要维护created_by、member_id等部门数据权限范围可放开注释 + }) + } + return handler.Model(dao.AddonHgexampleTenantOrder.Ctx(ctx), option...) +} + +// List 获取多租户功能演示列表 +func (s *sSysTenantOrder) List(ctx context.Context, in *sysin.TenantOrderListInp) (list []*sysin.TenantOrderListModel, totalCount int, err error) { + mod := s.Model(ctx) + + // 字段过滤 + mod = mod.Fields(sysin.TenantOrderListModel{}) + + // 查询主键 + if in.Id > 0 { + mod = mod.Where(dao.AddonHgexampleTenantOrder.Columns().Id, in.Id) + } + + // 查询租户ID + if in.TenantId > 0 { + mod = mod.Where(dao.AddonHgexampleTenantOrder.Columns().TenantId, in.TenantId) + } + + // 查询商户ID + if in.MerchantId > 0 { + mod = mod.Where(dao.AddonHgexampleTenantOrder.Columns().MerchantId, in.MerchantId) + } + + // 查询用户ID + if in.UserId > 0 { + mod = mod.Where(dao.AddonHgexampleTenantOrder.Columns().UserId, in.UserId) + } + + // 查询关联订单号 + if in.OrderSn != "" { + mod = mod.Where(dao.AddonHgexampleTenantOrder.Columns().OrderSn, in.OrderSn) + } + + // 查询支付状态 + if in.Status > 0 { + mod = mod.Where(dao.AddonHgexampleTenantOrder.Columns().Status, in.Status) + } + + // 查询创建时间 + if len(in.CreatedAt) == 2 { + mod = mod.WhereBetween(dao.AddonHgexampleTenantOrder.Columns().CreatedAt, in.CreatedAt[0], in.CreatedAt[1]) + } + + // 分页 + mod = mod.Page(in.Page, in.PerPage) + + // 排序 + mod = mod.OrderDesc(dao.AddonHgexampleTenantOrder.Columns().Id) + + // 查询数据 + if err = mod.ScanAndCount(&list, &totalCount, false); err != nil { + err = gerror.Wrap(err, "获取多租户功能演示列表失败,请稍后重试!") + return + } + return +} + +// Export 导出多租户功能演示 +func (s *sSysTenantOrder) Export(ctx context.Context, in *sysin.TenantOrderListInp) (err error) { + list, totalCount, err := s.List(ctx, in) + if err != nil { + return + } + + // 字段的排序是依据tags的字段顺序,如果你不想使用默认的排序方式,可以直接定义 tags = []string{"字段名称", "字段名称2", ...} + tags, err := convert.GetEntityDescTags(sysin.TenantOrderExportModel{}) + if err != nil { + return + } + + var ( + fileName = "导出多租户功能演示-" + gctx.CtxId(ctx) + sheetName = fmt.Sprintf("索引条件共%v行,共%v页,当前导出是第%v页,本页共%v行", totalCount, form.CalPageCount(totalCount, in.PerPage), in.Page, len(list)) + exports []sysin.TenantOrderExportModel + ) + + if err = gconv.Scan(list, &exports); err != nil { + return + } + + err = excel.ExportByStructs(ctx, tags, exports, fileName, sheetName) + return +} + +// Edit 修改/新增多租户功能演示 +func (s *sSysTenantOrder) Edit(ctx context.Context, in *sysin.TenantOrderEditInp) (err error) { + return g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) (err error) { + + // 修改 + if in.Id > 0 { + if _, err = s.Model(ctx). + Fields(sysin.TenantOrderUpdateFields{}). + WherePri(in.Id).Data(in). + Hook(hook.SaveTenant). // 自动维护租户关系更新 + Update(); err != nil { + } + return + } + + // 新增 + if _, err = dao.AddonHgexampleTenantOrder.Ctx(ctx). + Fields(sysin.TenantOrderInsertFields{}). + Hook(hook.SaveTenant). // 自动维护租户关系更新 + Data(in). + Insert(); err != nil { + } + return + }) +} + +// Delete 删除多租户功能演示 +func (s *sSysTenantOrder) Delete(ctx context.Context, in *sysin.TenantOrderDeleteInp) (err error) { + + if _, err = s.Model(ctx).WherePri(in.Id).Delete(); err != nil { + err = gerror.Wrap(err, "删除多租户功能演示失败,请稍后重试!") + return + } + return +} + +// View 获取多租户功能演示指定信息 +func (s *sSysTenantOrder) View(ctx context.Context, in *sysin.TenantOrderViewInp) (res *sysin.TenantOrderViewModel, err error) { + if err = s.Model(ctx).WherePri(in.Id).Scan(&res); err != nil { + err = gerror.Wrap(err, "获取多租户功能演示信息,请稍后重试!") + return + } + return +} diff --git a/server/addons/hgexample/model/input/sysin/tenant_order.go b/server/addons/hgexample/model/input/sysin/tenant_order.go new file mode 100644 index 0000000..61b44a6 --- /dev/null +++ b/server/addons/hgexample/model/input/sysin/tenant_order.go @@ -0,0 +1,123 @@ +// Package sysin +// @Link https://github.com/bufanyun/hotgo +// @Copyright Copyright (c) 2024 HotGo CLI +// @Author Ms <133814250@qq.com> +// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE +// @AutoGenerate Version 2.13.1 +package sysin + +import ( + "context" + "hotgo/internal/model/entity" + "hotgo/internal/model/input/form" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gtime" +) + +// TenantOrderUpdateFields 修改多租户功能演示字段过滤 +type TenantOrderUpdateFields struct { + TenantId int64 `json:"tenantId" dc:"租户ID"` + MerchantId int64 `json:"merchantId" dc:"商户ID"` + UserId int64 `json:"userId" dc:"用户ID"` + ProductName string `json:"productName" dc:"购买产品"` + OrderSn string `json:"orderSn" dc:"关联订单号"` + Money float64 `json:"money" dc:"充值金额"` + Remark string `json:"remark" dc:"备注"` + Status int `json:"status" dc:"支付状态"` +} + +// TenantOrderInsertFields 新增多租户功能演示字段过滤 +type TenantOrderInsertFields struct { + TenantId int64 `json:"tenantId" dc:"租户ID"` + MerchantId int64 `json:"merchantId" dc:"商户ID"` + UserId int64 `json:"userId" dc:"用户ID"` + ProductName string `json:"productName" dc:"购买产品"` + OrderSn string `json:"orderSn" dc:"关联订单号"` + Money float64 `json:"money" dc:"充值金额"` + Remark string `json:"remark" dc:"备注"` + Status int `json:"status" dc:"支付状态"` +} + +// TenantOrderEditInp 修改/新增多租户功能演示 +type TenantOrderEditInp struct { + entity.AddonHgexampleTenantOrder +} + +func (in *TenantOrderEditInp) Filter(ctx context.Context) (err error) { + // 验证充值金额 + if err := g.Validator().Rules("required").Data(in.Money).Messages("充值金额不能为空").Run(ctx); err != nil { + return err.Current() + } + + return +} + +type TenantOrderEditModel struct{} + +// TenantOrderDeleteInp 删除多租户功能演示 +type TenantOrderDeleteInp struct { + Id interface{} `json:"id" v:"required#主键不能为空" dc:"主键"` +} + +func (in *TenantOrderDeleteInp) Filter(ctx context.Context) (err error) { + return +} + +type TenantOrderDeleteModel struct{} + +// TenantOrderViewInp 获取指定多租户功能演示信息 +type TenantOrderViewInp struct { + Id int64 `json:"id" v:"required#主键不能为空" dc:"主键"` +} + +func (in *TenantOrderViewInp) Filter(ctx context.Context) (err error) { + return +} + +type TenantOrderViewModel struct { + entity.AddonHgexampleTenantOrder +} + +// TenantOrderListInp 获取多租户功能演示列表 +type TenantOrderListInp struct { + form.PageReq + Id int64 `json:"id" dc:"主键"` + TenantId int64 `json:"tenantId" dc:"租户ID"` + MerchantId int64 `json:"merchantId" dc:"商户ID"` + UserId int64 `json:"userId" dc:"用户ID"` + OrderSn string `json:"orderSn" dc:"关联订单号"` + Status int `json:"status" dc:"支付状态"` + CreatedAt []*gtime.Time `json:"createdAt" dc:"创建时间"` +} + +func (in *TenantOrderListInp) Filter(ctx context.Context) (err error) { + return +} + +type TenantOrderListModel struct { + Id int64 `json:"id" dc:"主键"` + TenantId int64 `json:"tenantId" dc:"租户ID"` + MerchantId int64 `json:"merchantId" dc:"商户ID"` + UserId int64 `json:"userId" dc:"用户ID"` + ProductName string `json:"productName" dc:"购买产品"` + OrderSn string `json:"orderSn" dc:"关联订单号"` + Money float64 `json:"money" dc:"充值金额"` + Remark string `json:"remark" dc:"备注"` + Status int `json:"status" dc:"支付状态"` + CreatedAt *gtime.Time `json:"createdAt" dc:"创建时间"` +} + +// TenantOrderExportModel 导出多租户功能演示 +type TenantOrderExportModel struct { + Id int64 `json:"id" dc:"主键"` + TenantId int64 `json:"tenantId" dc:"租户ID"` + MerchantId int64 `json:"merchantId" dc:"商户ID"` + UserId int64 `json:"userId" dc:"用户ID"` + ProductName string `json:"productName" dc:"购买产品"` + OrderSn string `json:"orderSn" dc:"关联订单号"` + Money float64 `json:"money" dc:"充值金额"` + Remark string `json:"remark" dc:"备注"` + Status int `json:"status" dc:"支付状态"` + CreatedAt *gtime.Time `json:"createdAt" dc:"创建时间"` +} diff --git a/server/addons/hgexample/router/genrouter/tenant_order.go b/server/addons/hgexample/router/genrouter/tenant_order.go new file mode 100644 index 0000000..15aa5b0 --- /dev/null +++ b/server/addons/hgexample/router/genrouter/tenant_order.go @@ -0,0 +1,13 @@ +// Package genrouter +// @Link https://github.com/bufanyun/hotgo +// @Copyright Copyright (c) 2024 HotGo CLI +// @Author Ms <133814250@qq.com> +// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE +// @AutoGenerate Version 2.13.1 +package genrouter + +import "hotgo/addons/hgexample/controller/admin/sys" + +func init() { + LoginRequiredRouter = append(LoginRequiredRouter, sys.TenantOrder) // 多租户功能演示 +} \ No newline at end of file diff --git a/server/addons/hgexample/service/sys.go b/server/addons/hgexample/service/sys.go index b0eb4c7..9ad290d 100644 --- a/server/addons/hgexample/service/sys.go +++ b/server/addons/hgexample/service/sys.go @@ -47,6 +47,20 @@ type ( // View 获取指定信息 View(ctx context.Context, in *sysin.TableViewInp) (res *sysin.TableViewModel, err error) } + ISysTenantOrder interface { + // Model 多租户功能演示ORM模型 + Model(ctx context.Context, option ...*handler.Option) *gdb.Model + // List 获取多租户功能演示列表 + List(ctx context.Context, in *sysin.TenantOrderListInp) (list []*sysin.TenantOrderListModel, totalCount int, err error) + // Export 导出多租户功能演示 + Export(ctx context.Context, in *sysin.TenantOrderListInp) (err error) + // Edit 修改/新增多租户功能演示 + Edit(ctx context.Context, in *sysin.TenantOrderEditInp) (err error) + // Delete 删除多租户功能演示 + Delete(ctx context.Context, in *sysin.TenantOrderDeleteInp) (err error) + // View 获取多租户功能演示指定信息 + View(ctx context.Context, in *sysin.TenantOrderViewInp) (res *sysin.TenantOrderViewModel, err error) + } ISysTreeTable interface { // Model Orm模型 Model(ctx context.Context, option ...*handler.Option) *gdb.Model @@ -62,10 +76,11 @@ type ( ) var ( - localSysConfig ISysConfig - localSysIndex ISysIndex - localSysTable ISysTable - localSysTreeTable ISysTreeTable + localSysConfig ISysConfig + localSysIndex ISysIndex + localSysTable ISysTable + localSysTenantOrder ISysTenantOrder + localSysTreeTable ISysTreeTable ) func SysConfig() ISysConfig { @@ -101,6 +116,17 @@ func RegisterSysTable(i ISysTable) { localSysTable = i } +func SysTenantOrder() ISysTenantOrder { + if localSysTenantOrder == nil { + panic("implement not found for interface ISysTenantOrder, forgot register?") + } + return localSysTenantOrder +} + +func RegisterSysTenantOrder(i ISysTenantOrder) { + localSysTenantOrder = i +} + func SysTreeTable() ISysTreeTable { if localSysTreeTable == nil { panic("implement not found for interface ISysTreeTable, forgot register?") diff --git a/server/api/admin/common/upload.go b/server/api/admin/common/upload.go index 8771f1a..85f48a5 100644 --- a/server/api/admin/common/upload.go +++ b/server/api/admin/common/upload.go @@ -12,14 +12,14 @@ import ( // UploadFileReq 上传文件 type UploadFileReq struct { - g.Meta `path:"/upload/file" tags:"上传" method:"post" summary:"上传附件"` + g.Meta `path:"/upload/file" tags:"附件" method:"post" summary:"上传附件"` } type UploadFileRes *sysin.AttachmentListModel // CheckMultipartReq 检查文件分片 type CheckMultipartReq struct { - g.Meta `path:"/upload/checkMultipart" tags:"上传" method:"post" summary:"检查文件分片"` + g.Meta `path:"/upload/checkMultipart" tags:"附件" method:"post" summary:"检查文件分片"` sysin.CheckMultipartInp } @@ -29,7 +29,7 @@ type CheckMultipartRes struct { // UploadPartReq 分片上传 type UploadPartReq struct { - g.Meta `path:"/upload/uploadPart" tags:"上传" method:"post" summary:"分片上传"` + g.Meta `path:"/upload/uploadPart" tags:"附件" method:"post" summary:"分片上传"` sysin.UploadPartInp } diff --git a/server/api/admin/curddemo/curddemo.go b/server/api/admin/curddemo/curddemo.go index 6c0f1dc..5d7423b 100644 --- a/server/api/admin/curddemo/curddemo.go +++ b/server/api/admin/curddemo/curddemo.go @@ -3,7 +3,7 @@ // @Copyright Copyright (c) 2024 HotGo CLI // @Author Ms <133814250@qq.com> // @License https://github.com/bufanyun/hotgo/blob/master/LICENSE -// @AutoGenerate Version 2.12.10 +// @AutoGenerate Version 2.13.1 package curddemo import ( @@ -13,9 +13,9 @@ import ( "github.com/gogf/gf/v2/frame/g" ) -// ListReq 查询生成演示列表 +// ListReq 查询CURD列表列表 type ListReq struct { - g.Meta `path:"/curdDemo/list" method:"get" tags:"生成演示" summary:"获取生成演示列表"` + g.Meta `path:"/curdDemo/list" method:"get" tags:"CURD列表" summary:"获取CURD列表列表"` sysin.CurdDemoListInp } @@ -24,17 +24,17 @@ type ListRes struct { List []*sysin.CurdDemoListModel `json:"list" dc:"数据列表"` } -// ExportReq 导出生成演示列表 +// ExportReq 导出CURD列表列表 type ExportReq struct { - g.Meta `path:"/curdDemo/export" method:"get" tags:"生成演示" summary:"导出生成演示列表"` + g.Meta `path:"/curdDemo/export" method:"get" tags:"CURD列表" summary:"导出CURD列表列表"` sysin.CurdDemoListInp } type ExportRes struct{} -// ViewReq 获取生成演示指定信息 +// ViewReq 获取CURD列表指定信息 type ViewReq struct { - g.Meta `path:"/curdDemo/view" method:"get" tags:"生成演示" summary:"获取生成演示指定信息"` + g.Meta `path:"/curdDemo/view" method:"get" tags:"CURD列表" summary:"获取CURD列表指定信息"` sysin.CurdDemoViewInp } @@ -42,25 +42,25 @@ type ViewRes struct { *sysin.CurdDemoViewModel } -// EditReq 修改/新增生成演示 +// EditReq 修改/新增CURD列表 type EditReq struct { - g.Meta `path:"/curdDemo/edit" method:"post" tags:"生成演示" summary:"修改/新增生成演示"` + g.Meta `path:"/curdDemo/edit" method:"post" tags:"CURD列表" summary:"修改/新增CURD列表"` sysin.CurdDemoEditInp } type EditRes struct{} -// DeleteReq 删除生成演示 +// DeleteReq 删除CURD列表 type DeleteReq struct { - g.Meta `path:"/curdDemo/delete" method:"post" tags:"生成演示" summary:"删除生成演示"` + g.Meta `path:"/curdDemo/delete" method:"post" tags:"CURD列表" summary:"删除CURD列表"` sysin.CurdDemoDeleteInp } type DeleteRes struct{} -// MaxSortReq 获取生成演示最大排序 +// MaxSortReq 获取CURD列表最大排序 type MaxSortReq struct { - g.Meta `path:"/curdDemo/maxSort" method:"get" tags:"生成演示" summary:"获取生成演示最大排序"` + g.Meta `path:"/curdDemo/maxSort" method:"get" tags:"CURD列表" summary:"获取CURD列表最大排序"` sysin.CurdDemoMaxSortInp } @@ -68,17 +68,9 @@ type MaxSortRes struct { *sysin.CurdDemoMaxSortModel } -// StatusReq 更新生成演示状态 -type StatusReq struct { - g.Meta `path:"/curdDemo/status" method:"post" tags:"生成演示" summary:"更新生成演示状态"` - sysin.CurdDemoStatusInp -} - -type StatusRes struct{} - -// SwitchReq 更新生成演示开关状态 +// SwitchReq 更新CURD列表开关状态 type SwitchReq struct { - g.Meta `path:"/curdDemo/switch" method:"post" tags:"生成演示" summary:"更新生成演示状态"` + g.Meta `path:"/curdDemo/switch" method:"post" tags:"CURD列表" summary:"更新CURD列表状态"` sysin.CurdDemoSwitchInp } diff --git a/server/api/admin/dept/dept.go b/server/api/admin/dept/dept.go index 2467f0b..565ec74 100644 --- a/server/api/admin/dept/dept.go +++ b/server/api/admin/dept/dept.go @@ -9,6 +9,7 @@ import ( "github.com/gogf/gf/v2/frame/g" "hotgo/internal/model/input/adminin" "hotgo/internal/model/input/form" + "hotgo/utility/tree" ) // ListReq 查询列表 @@ -55,17 +56,9 @@ type MaxSortRes struct { *adminin.DeptMaxSortModel } -// StatusReq 更新部门状态 -type StatusReq struct { - g.Meta `path:"/dept/status" method:"post" tags:"部门" summary:"更新部门状态"` - adminin.DeptStatusInp -} - -type StatusRes struct{} - -// OptionReq 获取部门选项树 +// OptionReq 获取当前登录用户可选的部门选项 type OptionReq struct { - g.Meta `path:"/dept/option" method:"get" tags:"部门" summary:"获取部门选项树"` + g.Meta `path:"/dept/option" method:"get" tags:"部门" summary:"获取当前登录用户可选的部门选项"` adminin.DeptOptionInp } @@ -73,3 +66,10 @@ type OptionRes struct { *adminin.DeptOptionModel form.PageRes } + +// TreeOptionReq 获取部门关系树选项 +type TreeOptionReq struct { + g.Meta `path:"/dept/treeOption" method:"get" tags:"部门" summary:"获取部门关系树选项"` +} + +type TreeOptionRes []tree.Node diff --git a/server/api/admin/dict/dict_data.go b/server/api/admin/dict/dict_data.go index 3c7e4d9..8ecdea7 100644 --- a/server/api/admin/dict/dict_data.go +++ b/server/api/admin/dict/dict_data.go @@ -39,14 +39,14 @@ type DataListRes struct { } type DataSelectReq struct { - g.Meta `path:"/dictData/option/{Type}" method:"get" summary:"字典数据" tags:"获取指定字典选项"` + g.Meta `path:"/dictData/option/{Type}" method:"get" tags:"字典数据" summary:"获取指定字典选项"` sysin.DataSelectInp } type DataSelectRes sysin.DataSelectModel type DataSelectsReq struct { - g.Meta `path:"/dictData/options" method:"get" summary:"字典数据" tags:"获取多个字典选项"` + g.Meta `path:"/dictData/options" method:"get" tags:"字典数据" summary:"获取多个字典选项"` Types []string `json:"types"` } diff --git a/server/api/admin/log/log.go b/server/api/admin/log/log.go index d2c8ad3..981751d 100644 --- a/server/api/admin/log/log.go +++ b/server/api/admin/log/log.go @@ -13,14 +13,14 @@ import ( // ClearReq 清空日志 type ClearReq struct { - g.Meta `path:"/log/clear" method:"post" tags:"日志" summary:"清空日志"` + g.Meta `path:"/log/clear" method:"post" tags:"访问日志" summary:"清空日志"` } type ClearRes struct{} // ExportReq 导出 type ExportReq struct { - g.Meta `path:"/log/export" method:"get" tags:"日志" summary:"导出日志"` + g.Meta `path:"/log/export" method:"get" tags:"访问日志" summary:"导出日志"` sysin.LogListInp } @@ -28,18 +28,18 @@ type ExportRes struct{} // ListReq 获取菜单列表 type ListReq struct { - g.Meta `path:"/log/list" method:"get" tags:"日志" summary:"获取日志列表"` + g.Meta `path:"/log/list" method:"get" tags:"访问日志" summary:"获取日志列表"` sysin.LogListInp } type ListRes struct { - List []*sysin.LogListModel `json:"list" dc:"数据列表"` + List []*sysin.LogListModel `json:"list" dc:"数据列表"` form.PageRes } // DeleteReq 删除 type DeleteReq struct { - g.Meta `path:"/log/delete" method:"post" tags:"日志" summary:"删除日志"` + g.Meta `path:"/log/delete" method:"post" tags:"访问日志" summary:"删除日志"` sysin.LogDeleteInp } @@ -47,7 +47,7 @@ type DeleteRes struct{} // ViewReq 获取指定信息 type ViewReq struct { - g.Meta `path:"/log/view" method:"get" tags:"日志" summary:"获取指定信息"` + g.Meta `path:"/log/view" method:"get" tags:"访问日志" summary:"获取指定信息"` sysin.LogViewInp } diff --git a/server/api/admin/normaltreedemo/normaltreedemo.go b/server/api/admin/normaltreedemo/normaltreedemo.go new file mode 100644 index 0000000..37dc525 --- /dev/null +++ b/server/api/admin/normaltreedemo/normaltreedemo.go @@ -0,0 +1,69 @@ +// Package normaltreedemo +// @Link https://github.com/bufanyun/hotgo +// @Copyright Copyright (c) 2024 HotGo CLI +// @Author Ms <133814250@qq.com> +// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE +// @AutoGenerate Version 2.13.1 +package normaltreedemo + +import ( + "hotgo/internal/model/input/form" + "hotgo/internal/model/input/sysin" + "hotgo/utility/tree" + + "github.com/gogf/gf/v2/frame/g" +) + +// ListReq 查询普通树表列表 +type ListReq struct { + g.Meta `path:"/normalTreeDemo/list" method:"get" tags:"普通树表" summary:"获取普通树表列表"` + sysin.NormalTreeDemoListInp +} + +type ListRes struct { + form.PageRes + List []*sysin.NormalTreeDemoListModel `json:"list" dc:"数据列表"` +} + +// ViewReq 获取普通树表指定信息 +type ViewReq struct { + g.Meta `path:"/normalTreeDemo/view" method:"get" tags:"普通树表" summary:"获取普通树表指定信息"` + sysin.NormalTreeDemoViewInp +} + +type ViewRes struct { + *sysin.NormalTreeDemoViewModel +} + +// EditReq 修改/新增普通树表 +type EditReq struct { + g.Meta `path:"/normalTreeDemo/edit" method:"post" tags:"普通树表" summary:"修改/新增普通树表"` + sysin.NormalTreeDemoEditInp +} + +type EditRes struct{} + +// DeleteReq 删除普通树表 +type DeleteReq struct { + g.Meta `path:"/normalTreeDemo/delete" method:"post" tags:"普通树表" summary:"删除普通树表"` + sysin.NormalTreeDemoDeleteInp +} + +type DeleteRes struct{} + +// MaxSortReq 获取普通树表最大排序 +type MaxSortReq struct { + g.Meta `path:"/normalTreeDemo/maxSort" method:"get" tags:"普通树表" summary:"获取普通树表最大排序"` + sysin.NormalTreeDemoMaxSortInp +} + +type MaxSortRes struct { + *sysin.NormalTreeDemoMaxSortModel +} + +// TreeOptionReq 获取普通树表关系树选项 +type TreeOptionReq struct { + g.Meta `path:"/normalTreeDemo/treeOption" method:"get" tags:"普通树表" summary:"获取普通树表关系树选项"` +} + +type TreeOptionRes []tree.Node \ No newline at end of file diff --git a/server/api/admin/optiontreedemo/optiontreedemo.go b/server/api/admin/optiontreedemo/optiontreedemo.go new file mode 100644 index 0000000..0caf3cb --- /dev/null +++ b/server/api/admin/optiontreedemo/optiontreedemo.go @@ -0,0 +1,69 @@ +// Package optiontreedemo +// @Link https://github.com/bufanyun/hotgo +// @Copyright Copyright (c) 2024 HotGo CLI +// @Author Ms <133814250@qq.com> +// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE +// @AutoGenerate Version 2.13.1 +package optiontreedemo + +import ( + "hotgo/internal/model/input/form" + "hotgo/internal/model/input/sysin" + "hotgo/utility/tree" + + "github.com/gogf/gf/v2/frame/g" +) + +// ListReq 查询选项树表列表 +type ListReq struct { + g.Meta `path:"/optionTreeDemo/list" method:"get" tags:"选项树表" summary:"获取选项树表列表"` + sysin.OptionTreeDemoListInp +} + +type ListRes struct { + form.PageRes + List []*sysin.OptionTreeDemoListModel `json:"list" dc:"数据列表"` +} + +// ViewReq 获取选项树表指定信息 +type ViewReq struct { + g.Meta `path:"/optionTreeDemo/view" method:"get" tags:"选项树表" summary:"获取选项树表指定信息"` + sysin.OptionTreeDemoViewInp +} + +type ViewRes struct { + *sysin.OptionTreeDemoViewModel +} + +// EditReq 修改/新增选项树表 +type EditReq struct { + g.Meta `path:"/optionTreeDemo/edit" method:"post" tags:"选项树表" summary:"修改/新增选项树表"` + sysin.OptionTreeDemoEditInp +} + +type EditRes struct{} + +// DeleteReq 删除选项树表 +type DeleteReq struct { + g.Meta `path:"/optionTreeDemo/delete" method:"post" tags:"选项树表" summary:"删除选项树表"` + sysin.OptionTreeDemoDeleteInp +} + +type DeleteRes struct{} + +// MaxSortReq 获取选项树表最大排序 +type MaxSortReq struct { + g.Meta `path:"/optionTreeDemo/maxSort" method:"get" tags:"选项树表" summary:"获取选项树表最大排序"` + sysin.OptionTreeDemoMaxSortInp +} + +type MaxSortRes struct { + *sysin.OptionTreeDemoMaxSortModel +} + +// TreeOptionReq 获取选项树表关系树选项 +type TreeOptionReq struct { + g.Meta `path:"/optionTreeDemo/treeOption" method:"get" tags:"选项树表" summary:"获取选项树表关系树选项"` +} + +type TreeOptionRes []tree.Node \ No newline at end of file diff --git a/server/api/admin/provinces/provinces.go b/server/api/admin/provinces/provinces.go index 1c23fb1..d6bc9fd 100644 --- a/server/api/admin/provinces/provinces.go +++ b/server/api/admin/provinces/provinces.go @@ -98,7 +98,7 @@ type UniqueIdRes struct { // SelectReq 省市区选项 type SelectReq struct { - g.Meta `path:"/provinces/select" method:"get" summary:"省市区" tags:"省市区选项"` + g.Meta `path:"/provinces/select" method:"get" tags:"省市区" summary:"省市区选项"` sysin.ProvincesSelectInp } @@ -108,7 +108,7 @@ type SelectRes struct { // CityLabelReq 获取指定城市标签 type CityLabelReq struct { - g.Meta `path:"/provinces/cityLabel" method:"get" summary:"省市区" tags:"获取指定城市标签"` + g.Meta `path:"/provinces/cityLabel" method:"get" tags:"省市区" summary:"获取指定城市标签" ` sysin.ProvincesCityLabelInp } diff --git a/server/api/admin/role/role.go b/server/api/admin/role/role.go index 08719ac..bbae5a1 100644 --- a/server/api/admin/role/role.go +++ b/server/api/admin/role/role.go @@ -25,7 +25,7 @@ type ListRes struct { // DynamicReq 动态路由 type DynamicReq struct { - g.Meta `path:"/role/dynamic" method:"get" tags:"路由" summary:"获取动态路由" description:"获取登录用户动态路由"` + g.Meta `path:"/role/dynamic" method:"get" tags:"角色" summary:"获取动态路由" description:"获取登录用户动态路由"` } type DynamicRes struct { @@ -66,7 +66,7 @@ type DeleteRes struct{} // DataScopeSelectReq 获取数据权限选项 type DataScopeSelectReq struct { - g.Meta `path:"/role/dataScope/select" method:"get" summary:"角色" tags:"获取数据权限选项"` + g.Meta `path:"/role/dataScope/select" method:"get" tags:"角色" summary:"获取数据权限选项"` } type DataScopeSelectRes struct { diff --git a/server/api/admin/smslog/smslog.go b/server/api/admin/smslog/smslog.go index 004b31d..ed97c2d 100644 --- a/server/api/admin/smslog/smslog.go +++ b/server/api/admin/smslog/smslog.go @@ -13,7 +13,7 @@ import ( // ListReq 查询列表 type ListReq struct { - g.Meta `path:"/smsLog/list" method:"get" tags:"短信记录" summary:"获取短信记录列表"` + g.Meta `path:"/smsLog/list" method:"get" tags:"短信" summary:"获取短信记录列表"` sysin.SmsLogListInp } @@ -24,7 +24,7 @@ type ListRes struct { // ViewReq 获取指定信息 type ViewReq struct { - g.Meta `path:"/smsLog/view" method:"get" tags:"短信记录" summary:"获取指定信息"` + g.Meta `path:"/smsLog/view" method:"get" tags:"短信" summary:"获取指定短信信息"` sysin.SmsLogViewInp } @@ -32,26 +32,10 @@ type ViewRes struct { *sysin.SmsLogViewModel } -// EditReq 修改/新增数据 -type EditReq struct { - g.Meta `path:"/smsLog/edit" method:"post" tags:"短信记录" summary:"修改/新增短信记录"` - sysin.SmsLogEditInp -} - -type EditRes struct{} - // DeleteReq 删除 type DeleteReq struct { - g.Meta `path:"/smsLog/delete" method:"post" tags:"短信记录" summary:"删除短信记录"` + g.Meta `path:"/smsLog/delete" method:"post" tags:"短信" summary:"删除短信记录"` sysin.SmsLogDeleteInp } type DeleteRes struct{} - -// StatusReq 更新状态 -type StatusReq struct { - g.Meta `path:"/smsLog/status" method:"post" tags:"短信记录" summary:"更新短信记录状态"` - sysin.SmsLogStatusInp -} - -type StatusRes struct{} diff --git a/server/api/admin/testcategory/testcategory.go b/server/api/admin/testcategory/testcategory.go new file mode 100644 index 0000000..e4b9dbc --- /dev/null +++ b/server/api/admin/testcategory/testcategory.go @@ -0,0 +1,69 @@ +// Package testcategory +// @Link https://github.com/bufanyun/hotgo +// @Copyright Copyright (c) 2024 HotGo CLI +// @Author Ms <133814250@qq.com> +// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE +// @AutoGenerate Version 2.13.1 +package testcategory + +import ( + "hotgo/internal/model/input/form" + "hotgo/internal/model/input/sysin" + + "github.com/gogf/gf/v2/frame/g" +) + +// ListReq 查询测试分类列表 +type ListReq struct { + g.Meta `path:"/testCategory/list" method:"get" tags:"测试分类" summary:"获取测试分类列表"` + sysin.TestCategoryListInp +} + +type ListRes struct { + form.PageRes + List []*sysin.TestCategoryListModel `json:"list" dc:"数据列表"` +} + +// ViewReq 获取测试分类指定信息 +type ViewReq struct { + g.Meta `path:"/testCategory/view" method:"get" tags:"测试分类" summary:"获取测试分类指定信息"` + sysin.TestCategoryViewInp +} + +type ViewRes struct { + *sysin.TestCategoryViewModel +} + +// EditReq 修改/新增测试分类 +type EditReq struct { + g.Meta `path:"/testCategory/edit" method:"post" tags:"测试分类" summary:"修改/新增测试分类"` + sysin.TestCategoryEditInp +} + +type EditRes struct{} + +// DeleteReq 删除测试分类 +type DeleteReq struct { + g.Meta `path:"/testCategory/delete" method:"post" tags:"测试分类" summary:"删除测试分类"` + sysin.TestCategoryDeleteInp +} + +type DeleteRes struct{} + +// MaxSortReq 获取测试分类最大排序 +type MaxSortReq struct { + g.Meta `path:"/testCategory/maxSort" method:"get" tags:"测试分类" summary:"获取测试分类最大排序"` + sysin.TestCategoryMaxSortInp +} + +type MaxSortRes struct { + *sysin.TestCategoryMaxSortModel +} + +// StatusReq 更新测试分类状态 +type StatusReq struct { + g.Meta `path:"/testCategory/status" method:"post" tags:"测试分类" summary:"更新测试分类状态"` + sysin.TestCategoryStatusInp +} + +type StatusRes struct{} \ No newline at end of file diff --git a/server/api/api/user/hello.go b/server/api/api/user/hello.go deleted file mode 100644 index 40a4708..0000000 --- a/server/api/api/user/hello.go +++ /dev/null @@ -1,19 +0,0 @@ -// Package user -// @Link https://github.com/bufanyun/hotgo -// @Copyright Copyright (c) 2023 HotGo CLI -// @Author Ms <133814250@qq.com> -// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE -package user - -import ( - "github.com/gogf/gf/v2/frame/g" -) - -type HelloReq struct { - g.Meta `path:"/hello" tags:"Hello" method:"get" summary:"You first hello api"` - Name string `json:"name" d:"hotgo" dc:"名字"` -} - -type HelloRes struct { - Tips string `json:"tips"` -} diff --git a/server/go.mod b/server/go.mod index 58da87f..f2c9f78 100644 --- a/server/go.mod +++ b/server/go.mod @@ -1,6 +1,8 @@ module hotgo -go 1.19 +go 1.21 + +toolchain go1.22.1 require ( github.com/Shopify/sarama v1.34.1 @@ -9,20 +11,20 @@ require ( github.com/alibabacloud-go/tea v1.1.20 github.com/alibabacloud-go/tea-utils/v2 v2.0.1 github.com/aliyun/aliyun-oss-go-sdk v2.2.6+incompatible - github.com/apache/rocketmq-client-go/v2 v2.1.0 + github.com/apache/rocketmq-client-go/v2 v2.1.2 github.com/casbin/casbin/v2 v2.55.0 github.com/forgoer/openssl v1.4.0 github.com/go-pay/gopay v1.5.91 - github.com/gogf/gf/contrib/drivers/mysql/v2 v2.6.4 - github.com/gogf/gf/contrib/nosql/redis/v2 v2.6.4 - github.com/gogf/gf/contrib/trace/jaeger/v2 v2.6.4 - github.com/gogf/gf/v2 v2.6.4 + github.com/gogf/gf/contrib/drivers/mysql/v2 v2.7.0 + github.com/gogf/gf/contrib/nosql/redis/v2 v2.7.0 + github.com/gogf/gf/contrib/trace/jaeger/v2 v2.7.0 + github.com/gogf/gf/v2 v2.7.0 github.com/gogf/selfupdate v0.0.0-20231215043001-5c48c528462f github.com/golang-jwt/jwt/v5 v5.0.0 github.com/gorilla/websocket v1.5.1 github.com/kayon/iploc v0.0.0-20200312105652-bda3e968a794 github.com/minio/minio-go/v7 v7.0.63 - github.com/mojocn/base64Captcha v1.3.5 + github.com/mojocn/base64Captcha v1.3.6 github.com/olekukonko/tablewriter v0.0.5 github.com/qiniu/go-sdk/v7 v7.14.0 github.com/shirou/gopsutil/v3 v3.23.3 @@ -32,14 +34,16 @@ require ( github.com/tencentyun/cos-go-sdk-v5 v0.7.45 github.com/ufilesdk-dev/ufile-gosdk v1.0.3 github.com/xuri/excelize/v2 v2.6.0 - go.opentelemetry.io/otel v1.24.0 + go.opentelemetry.io/otel v1.25.0 golang.org/x/mod v0.9.0 + golang.org/x/net v0.24.0 golang.org/x/tools v0.7.0 gopkg.in/yaml.v3 v3.0.1 ) require ( aead.dev/minisign v0.2.0 // indirect + filippo.io/edwards25519 v1.1.0 // indirect github.com/BurntSushi/toml v1.3.2 // indirect github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible // indirect github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4 // indirect @@ -50,7 +54,7 @@ require ( github.com/alibabacloud-go/tea-xml v1.1.2 // indirect github.com/aliyun/credentials-go v1.1.2 // indirect github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/clbanning/mxj v1.8.4 // indirect github.com/clbanning/mxj/v2 v2.7.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect @@ -67,7 +71,7 @@ require ( github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-redis/redis/v8 v8.11.5 // indirect - github.com/go-sql-driver/mysql v1.7.1 // indirect + github.com/go-sql-driver/mysql v1.8.1 // indirect github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect github.com/golang/mock v1.6.0 // indirect github.com/golang/snappy v0.0.4 // indirect @@ -98,6 +102,7 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/mozillazg/go-httpheader v0.2.1 // indirect + github.com/patrickmn/go-cache v2.1.0+incompatible // indirect github.com/pierrec/lz4/v4 v4.1.18 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect @@ -120,17 +125,17 @@ require ( github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22 // indirect github.com/yusufpapurcu/wmi v1.2.2 // indirect go.opentelemetry.io/otel/exporters/jaeger v1.17.0 // indirect - go.opentelemetry.io/otel/metric v1.24.0 // indirect - go.opentelemetry.io/otel/sdk v1.24.0 // indirect - go.opentelemetry.io/otel/trace v1.24.0 // indirect + go.opentelemetry.io/otel/metric v1.25.0 // indirect + go.opentelemetry.io/otel/sdk v1.25.0 // indirect + go.opentelemetry.io/otel/trace v1.25.0 // indirect go.uber.org/atomic v1.7.0 // indirect - golang.org/x/crypto v0.21.0 // indirect - golang.org/x/image v0.1.0 // indirect - golang.org/x/net v0.22.0 // indirect + golang.org/x/crypto v0.22.0 // indirect + golang.org/x/image v0.13.0 // indirect golang.org/x/sync v0.1.0 // indirect - golang.org/x/sys v0.18.0 // indirect + golang.org/x/sys v0.19.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect stathat.com/c/consistent v1.0.0 // indirect ) diff --git a/server/go.sum b/server/go.sum index e0a15bd..3547699 100644 --- a/server/go.sum +++ b/server/go.sum @@ -33,7 +33,10 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= @@ -80,22 +83,24 @@ github.com/aliyun/aliyun-oss-go-sdk v2.2.6+incompatible h1:KXeJoM1wo9I/6xPTyt6qC github.com/aliyun/aliyun-oss-go-sdk v2.2.6+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= github.com/aliyun/credentials-go v1.1.2 h1:qU1vwGIBb3UJ8BwunHDRFtAhS6jnQLnde/yk0+Ih2GY= github.com/aliyun/credentials-go v1.1.2/go.mod h1:ozcZaMR5kLM7pwtCMEpVmQ242suV6qTJya2bDq4X1Tw= -github.com/apache/rocketmq-client-go/v2 v2.1.0 h1:3eABKfxc1WmS2lLTTbKMe1gZfZV6u1Sx9orFnOfABV0= -github.com/apache/rocketmq-client-go/v2 v2.1.0/go.mod h1:oEZKFDvS7sz/RWU0839+dQBupazyBV7WX5cP6nrio0Q= +github.com/apache/rocketmq-client-go/v2 v2.1.2 h1:yt73olKe5N6894Dbm+ojRf/JPiP0cxfDNNffKwhpJVg= +github.com/apache/rocketmq-client-go/v2 v2.1.2/go.mod h1:6I6vgxHR3hzrvn+6n/4mrhS+UTulzK/X9LB2Vk1U5gE= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d h1:pVrfxiGfwelyab6n21ZBkbkmbevaf+WvMIiR7sr97hw= github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= github.com/casbin/casbin/v2 v2.55.0 h1:RyU+OacnVzjxof1U3bmxHM7oCRdx9+gNnkclrvof/zI= github.com/casbin/casbin/v2 v2.55.0/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -166,18 +171,18 @@ github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl github.com/go-playground/validator/v10 v10.8.0/go.mod h1:9JhgTzTaE31GZDpH/HSvHiRJrJ3iKAgqqH0Bl/Ocjdk= github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= -github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= -github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= -github.com/gogf/gf/contrib/drivers/mysql/v2 v2.6.4 h1:ScG3YcYMDEP/UrwNtwQPt2noySa5ZpoV7BxrwaeBaro= -github.com/gogf/gf/contrib/drivers/mysql/v2 v2.6.4/go.mod h1:oFFE9u1zHkxVXk7ZkNipXQR9JFyDZDiixZy243ywhfQ= -github.com/gogf/gf/contrib/nosql/redis/v2 v2.6.4 h1:43517FF//GKgGpb4pxHl3NWLxW/inTAQ7rUFnfUIoYY= -github.com/gogf/gf/contrib/nosql/redis/v2 v2.6.4/go.mod h1:9qNdKgqB+tHC9XczIoMzfSHmWkphQMXqxJXF6g9Icr4= -github.com/gogf/gf/contrib/trace/jaeger/v2 v2.6.4 h1:PV0V2CPspwC+qgmqMC7qjCFkxH/SkfLwC0fg26ZTY54= -github.com/gogf/gf/contrib/trace/jaeger/v2 v2.6.4/go.mod h1:Wnu7ASD+BWWlPn9NlSNOmCip7tHnYSXRSSjFJ5cCTEo= -github.com/gogf/gf/v2 v2.6.4 h1:w7HXdH9mcTsn/aE13CkaDbRArmAL1KS3FuQqDi6u74Y= -github.com/gogf/gf/v2 v2.6.4/go.mod h1:x2XONYcI4hRQ/4gMNbWHmZrNzSEIg20s2NULbzom5k0= +github.com/gogf/gf/contrib/drivers/mysql/v2 v2.7.0 h1:5Igvtz4gy5UMvH+Ut4kLIpwSzggV9ZgDVBsIiOctH5E= +github.com/gogf/gf/contrib/drivers/mysql/v2 v2.7.0/go.mod h1:0+flZ0clMKjtH1sTI7YD2KG4FPr8xz0L9h1WMd5M2Z8= +github.com/gogf/gf/contrib/nosql/redis/v2 v2.7.0 h1:hJxshC0gZyFZaGo2HItXd5XMzIMbCRcgShr1ljMYwUc= +github.com/gogf/gf/contrib/nosql/redis/v2 v2.7.0/go.mod h1:is3Q3ItZSPMZ1RBJ3xIcEasyGZnOg8eNeG9dubOx/zc= +github.com/gogf/gf/contrib/trace/jaeger/v2 v2.7.0 h1:P75Jfq5rP8TUUTyobAooULJDDCaSOkgL14gnXudgY1E= +github.com/gogf/gf/contrib/trace/jaeger/v2 v2.7.0/go.mod h1:hRnwcGAFG8c6+9rg//zbr0Ve3XZVzAuALfzfbz3xBBk= +github.com/gogf/gf/v2 v2.7.0 h1:CjxhbMiE7oqf6K8ZtGuKt3dQEwK4vL6LhiI+dI7tJGU= +github.com/gogf/gf/v2 v2.7.0/go.mod h1:Qu8nimKt9aupJQcdUL85tWF4Mfxocz97zUt8UC4abVI= github.com/gogf/selfupdate v0.0.0-20231215043001-5c48c528462f h1:7xfXR/BhG3JDqO1s45n65Oyx9t4E/UqDOXep6jXdLCM= github.com/gogf/selfupdate v0.0.0-20231215043001-5c48c528462f/go.mod h1:HnYoio6S7VaFJdryKcD/r9HgX+4QzYfr00XiXUo/xz0= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= @@ -232,6 +237,7 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -288,7 +294,6 @@ github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZ github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -350,8 +355,8 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= -github.com/mojocn/base64Captcha v1.3.5 h1:Qeilr7Ta6eDtG4S+tQuZ5+hO+QHbiGAJdi4PfoagaA0= -github.com/mojocn/base64Captcha v1.3.5/go.mod h1:/tTTXn4WTpX9CfrmipqRytCpJ27Uw3G6I7NcP2WwcmY= +github.com/mojocn/base64Captcha v1.3.6 h1:gZEKu1nsKpttuIAQgWHO+4Mhhls8cAKyiV2Ew03H+Tw= +github.com/mojocn/base64Captcha v1.3.6/go.mod h1:i5CtHvm+oMbj1UzEPXaA8IH/xHFZ3DGY3Wh3dBpZ28E= github.com/mozillazg/go-httpheader v0.2.1 h1:geV7TrjbL8KXSyvghnFm+NyTux/hxwueTSrwhe88TQQ= github.com/mozillazg/go-httpheader v0.2.1/go.mod h1:jJ8xECTlalr6ValeXYdOF8fFUISeBAdw6E61aqQma60= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= @@ -374,6 +379,8 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= +github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pierrec/lz4/v4 v4.1.18 h1:xaKrnTkyoqfh1YItXl56+6KJNVYWlEEPuAQW9xsplYQ= github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= @@ -436,7 +443,7 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV github.com/silenceper/wechat/v2 v2.1.4 h1:X+G9C/EiBET5AK0zhrflX3ESCP/yxhJUvoRoSXHm0js= github.com/silenceper/wechat/v2 v2.1.4/go.mod h1:F0PKqImb15THnwoqRNrZO1z3vpwyWuiHr5zzfnjdECY= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= @@ -446,7 +453,6 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v1.1.0 h1:MkTeG1DMwsrdH7QtLXy5W+fUxWq+vmb6cLmyJ7aRtF0= github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= -github.com/smartystreets/goconvey v0.0.0-20190710185942-9d28bd7c0945/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA= @@ -466,7 +472,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.563/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.633 h1:Yj8s35IjbgaHp4Ic9BZLVGWdN2gXBMtwYi1JJ+qYbrc= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.633/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y= @@ -475,13 +482,11 @@ github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/sms v1.0.633 h1:rtgRqgZ github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/sms v1.0.633/go.mod h1:9q29WcGkZ7R0uQjoY10Tzb8A18C2cNggbq2ZC2HRXZE= github.com/tencentyun/cos-go-sdk-v5 v0.7.45 h1:5/ZGOv846tP6+2X7w//8QjLgH2KcUK+HciFbfjWquFU= github.com/tencentyun/cos-go-sdk-v5 v0.7.45/go.mod h1:DH9US8nB+AJXqwu/AMOrCFN1COv3dpytXuJWHgdg7kE= -github.com/tidwall/gjson v1.2.1/go.mod h1:c/nTNbUr0E0OrXEhq1pwa8iEgc2DOt4ZZqAt1HtCkPA= +github.com/tidwall/gjson v1.13.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/gjson v1.14.1 h1:iymTbGkQBhveq21bEvAQ81I0LEBork8BFe1CUZXdyuo= github.com/tidwall/gjson v1.14.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= -github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= -github.com/tidwall/pretty v0.0.0-20190325153808-1166b9ac2b65/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tjfoc/gmsm v1.3.2 h1:7JVkAn5bvUJ7HtU08iW6UiD+UTmJTIToHCfeFzkcCxM= @@ -516,16 +521,16 @@ go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= -go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= +go.opentelemetry.io/otel v1.25.0 h1:gldB5FfhRl7OJQbUHt/8s0a7cE8fbsPAtdpRaApKy4k= +go.opentelemetry.io/otel v1.25.0/go.mod h1:Wa2ds5NOXEMkCmUou1WA7ZBfLTHWIsp034OVD7AO+Vg= go.opentelemetry.io/otel/exporters/jaeger v1.17.0 h1:D7UpUy2Xc2wsi1Ras6V40q806WM07rqoCWzXu7Sqy+4= go.opentelemetry.io/otel/exporters/jaeger v1.17.0/go.mod h1:nPCqOnEH9rNLKqH/+rrUjiMzHJdV1BlpKcTwRTyKkKI= -go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= -go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= -go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw= -go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg= -go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= -go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= +go.opentelemetry.io/otel/metric v1.25.0 h1:LUKbS7ArpFL/I2jJHdJcqMGxkRdxpPHE0VU/D4NuEwA= +go.opentelemetry.io/otel/metric v1.25.0/go.mod h1:rkDLUSd2lC5lq2dFNrX9LGAbINP5B7WBkC78RXCpH5s= +go.opentelemetry.io/otel/sdk v1.25.0 h1:PDryEJPC8YJZQSyLY5eqLeafHtG+X7FWnf3aXMtxbqo= +go.opentelemetry.io/otel/sdk v1.25.0/go.mod h1:oFgzCM2zdsxKzz6zwpTZYLLQsFwc+K0daArPdIhuxkw= +go.opentelemetry.io/otel/trace v1.25.0 h1:tqukZGLwQYRIFtSQM2u2+yfMVTgGVeqRLPUYx1Dq6RM= +go.opentelemetry.io/otel/trace v1.25.0/go.mod h1:hCCs70XM/ljO+BeQkyFnbK28SBIJ/Emuha+ccrCRT7I= go.uber.org/atomic v1.5.1/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= @@ -544,8 +549,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220408190544-5352b0902921/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= +golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -557,11 +562,10 @@ golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190501045829-6d32002ffd75/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= -golang.org/x/image v0.1.0 h1:r8Oj8ZA2Xy12/b5KZYj3tuv7NG/fBz3TwQVvpJ9l8Rk= -golang.org/x/image v0.1.0/go.mod h1:iyPr49SD/G/TBxYVB/9RRtGUT5eNbo2u4NamWeQcD5c= +golang.org/x/image v0.13.0 h1:3cge/F/QTkNLauhf2QoE9zp+7sr+ZcL4HnoZmdwg9sg= +golang.org/x/image v0.13.0/go.mod h1:6mmbMOeV28HuMTgA6OSRkdXKYw/t5W9Uwn2Yv1r3Yxk= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -582,6 +586,7 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -623,8 +628,9 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20220407224826-aac1ed45d8e3/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= -golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= +golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -705,11 +711,12 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -717,7 +724,8 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -770,6 +778,7 @@ golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -865,6 +874,8 @@ gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaD gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= +gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/server/hack/config.yaml b/server/hack/config.yaml index d98f903..3e75ed9 100644 --- a/server/hack/config.yaml +++ b/server/hack/config.yaml @@ -22,7 +22,7 @@ gfcli: gen: dao: - - link: "mysql:hotgo:hg123456.@tcp(127.0.0.1:3306)/hotgo?loc=Local&parseTime=true" + - link: "mysql:hotgo:hg123456.@tcp(127.0.0.1:3306)/hotgo?loc=Local&parseTime=true&charset=utf8mb4" group: "default" # 分组 使用hotgo代码生成功能时必须填 # tables: "" # 指定当前数据库中需要执行代码生成的数据表。如果为空,表示数据库的所有表都会生成。 tablesEx: "hg_sys_addons_install" # 指定当前数据库中需要排除代码生成的数据表。 diff --git a/server/internal/cmd/cmd.go b/server/internal/cmd/cmd.go index 4e759ba..154016c 100644 --- a/server/internal/cmd/cmd.go +++ b/server/internal/cmd/cmd.go @@ -36,6 +36,8 @@ var ( --------------------------------------------------------------------------------- 工具 >> 释放casbin权限,用于清理无效的权限设置 [go run main.go tools -m=casbin -a1=refresh] + >> 打印所有打包的资源文件列表 [go run main.go tools -m=gres -a1=dump] + >> 打印指定打包的资源文件内容 [go run main.go tools -m=gres -a1=content -a2=resource/template/home/index.html] --------------------------------------------------------------------------------- 升级更新 >> 修复菜单关系树 [go run main.go up -m=fix -a1=menuTree] diff --git a/server/internal/cmd/handler_shutdown.go b/server/internal/cmd/handler_shutdown.go index 8a105ad..61a51ba 100644 --- a/server/internal/cmd/handler_shutdown.go +++ b/server/internal/cmd/handler_shutdown.go @@ -23,8 +23,8 @@ var ( // signalHandlerForOverall 关闭信号处理 func signalHandlerForOverall(sig os.Signal) { - serverCloseSignal <- struct{}{} serverCloseEvent(gctx.GetInitCtx()) + serverCloseSignal <- struct{}{} } // signalListen 信号监听 diff --git a/server/internal/cmd/tools.go b/server/internal/cmd/tools.go index 7f6dbbe..c098f08 100644 --- a/server/internal/cmd/tools.go +++ b/server/internal/cmd/tools.go @@ -7,9 +7,11 @@ package cmd import ( "context" + "fmt" "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/os/gcmd" + "github.com/gogf/gf/v2/os/gres" "hotgo/internal/library/casbin" ) @@ -35,6 +37,8 @@ var ( switch method { case "casbin": err = handleCasbin(ctx, args) + case "gres": + err = handleGRes(ctx, args) default: err = gerror.Newf("tools method[%v] does not exist", method) } @@ -66,3 +70,38 @@ func handleCasbin(ctx context.Context, args map[string]string) (err error) { } return } + +func handleGRes(ctx context.Context, args map[string]string) (err error) { + a1, ok := args["a1"] + if !ok { + err = gerror.New("gres args cannot be empty.") + return + } + + switch a1 { + case "dump": + gres.Dump() + case "content": + path, ok := args["a2"] + if !ok { + err = gerror.New("缺少查看文件路径参数:`a2`") + return + } + + if !gres.Contains(path) { + err = gerror.Newf("没有找到资源文件:%v", path) + return + } + content := string(gres.GetContent(path)) + + if len(content) == 0 { + err = gerror.Newf("没有找到资源文件内容,请确认传入`a2`参数是一个文件,a2:%v", path) + return + } + fmt.Println("以下是资源文件内容:") + fmt.Println(content) + default: + err = gerror.Newf("handleGRes a1 is invalid, a1:%v", a1) + } + return +} diff --git a/server/internal/consts/debris.go b/server/internal/consts/debris.go index 2153f5b..b512d20 100644 --- a/server/internal/consts/debris.go +++ b/server/internal/consts/debris.go @@ -22,3 +22,10 @@ const ( DefaultPageSize = 1 // 默认列表分页加载页码 MaxSortIncr = 10 // 最大排序值增量 ) + +// TenantField 租户字段 +const ( + TenantId = "tenant_id" // 租户ID + MerchantId = "merchant_id" // 商户ID + UserId = "user_id" // 用户ID +) diff --git a/server/internal/consts/dept.go b/server/internal/consts/dept.go new file mode 100644 index 0000000..7c2f2d6 --- /dev/null +++ b/server/internal/consts/dept.go @@ -0,0 +1,30 @@ +// Package consts +// @Link https://github.com/bufanyun/hotgo +// @Copyright Copyright (c) 2024 HotGo CLI +// @Author Ms <133814250@qq.com> +// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE +package consts + +import ( + "hotgo/internal/library/dict" + "hotgo/internal/model" +) + +func init() { + dict.RegisterEnums("deptType", "部门类型选项", DeptTypeOptions) +} + +const ( + DeptTypeCompany = "company" // 公司 + DeptTypeTenant = "tenant" // 租户 + DeptTypeMerchant = "merchant" // 商户 + DeptTypeUser = "user" // 用户 +) + +// DeptTypeOptions 部门类型选项 +var DeptTypeOptions = []*model.Option{ + dict.GenSuccessOption(DeptTypeCompany, "公司"), + dict.GenErrorOption(DeptTypeTenant, "租户"), + dict.GenInfoOption(DeptTypeMerchant, "商户"), + dict.GenWarningOption(DeptTypeUser, "用户"), +} diff --git a/server/internal/consts/gencodes.go b/server/internal/consts/gencodes.go index 5f87aaa..88da4e7 100644 --- a/server/internal/consts/gencodes.go +++ b/server/internal/consts/gencodes.go @@ -3,9 +3,13 @@ // @Copyright Copyright (c) 2023 HotGo CLI // @Author Ms <133814250@qq.com> // @License https://github.com/bufanyun/hotgo/blob/master/LICENSE -// package consts +import ( + "hotgo/internal/library/dict" + "hotgo/internal/model" +) + // 生成代码类型 const ( GenCodesTypeCurd = 10 // 增删改查列表 @@ -16,7 +20,7 @@ const ( var GenCodesTypeNameMap = map[int]string{ GenCodesTypeCurd: "增删改查列表", - GenCodesTypeTree: "关系树列表(未实现)", + GenCodesTypeTree: "关系树列表", GenCodesTypeQueue: "队列消费者(未实现)", GenCodesTypeCron: "定时任务(未实现)", } @@ -79,3 +83,14 @@ const ( GenCodesIndexPK = "PRI" // 主键索引 GenCodesIndexUNI = "UNI" // 唯一索引 ) + +const ( + GenCodesTreeStyleTypeNormal = 1 // 普通树表格 + GenCodesTreeStyleTypeOption = 2 // 选项式树表 +) + +// GenCodesTreeStyleTypeOptions 树表样式选项 +var GenCodesTreeStyleTypeOptions = []*model.Option{ + dict.GenSuccessOption(GenCodesTreeStyleTypeNormal, "普通树表格"), + dict.GenInfoOption(GenCodesTreeStyleTypeOption, "选项式树表"), +} diff --git a/server/internal/consts/pay.go b/server/internal/consts/pay.go index c6fba7e..084dd4c 100644 --- a/server/internal/consts/pay.go +++ b/server/internal/consts/pay.go @@ -14,6 +14,7 @@ import ( func init() { dict.RegisterEnums("payType", "支付方式", PayTypeOptions) + dict.RegisterEnums("payStatus", "支付状态", PayStatusOptions) } const ( @@ -72,6 +73,12 @@ const ( PayStatusOk = 2 // 已支付 ) +// PayStatusOptions 支付状态选项 +var PayStatusOptions = []*model.Option{ + dict.GenDefaultOption(PayStatusWait, "待支付"), + dict.GenSuccessOption(PayStatusOk, "已支付"), +} + // 退款状态 const ( diff --git a/server/internal/consts/version.go b/server/internal/consts/version.go index 4071a71..ed5c34e 100644 --- a/server/internal/consts/version.go +++ b/server/internal/consts/version.go @@ -7,5 +7,5 @@ package consts // VersionApp HotGo版本 const ( - VersionApp = "2.13.1" + VersionApp = "2.15.1" ) diff --git a/server/internal/controller/admin/admin/dept.go b/server/internal/controller/admin/admin/dept.go index 2b2fc3d..5730ce6 100644 --- a/server/internal/controller/admin/admin/dept.go +++ b/server/internal/controller/admin/admin/dept.go @@ -54,12 +54,6 @@ func (c *cDept) List(ctx context.Context, req *dept.ListReq) (res *dept.ListRes, return } -// Status 更新部门状态 -func (c *cDept) Status(ctx context.Context, req *dept.StatusReq) (res *dept.StatusRes, err error) { - err = service.AdminDept().Status(ctx, &req.DeptStatusInp) - return -} - // Option 获取部门选项树 func (c *cDept) Option(ctx context.Context, req *dept.OptionReq) (res *dept.OptionRes, err error) { list, totalCount, err := service.AdminDept().Option(ctx, &req.DeptOptionInp) @@ -72,3 +66,10 @@ func (c *cDept) Option(ctx context.Context, req *dept.OptionReq) (res *dept.Opti res.PageRes.Pack(req, totalCount) return } + +// TreeOption 获取部门管理关系树选项 +func (c *cDept) TreeOption(ctx context.Context, req *dept.TreeOptionReq) (res *dept.TreeOptionRes, err error) { + data, err := service.AdminDept().TreeOption(ctx) + res = (*dept.TreeOptionRes)(&data) + return +} diff --git a/server/internal/controller/admin/admin/monitor.go b/server/internal/controller/admin/admin/monitor.go index 702398a..ea8657a 100644 --- a/server/internal/controller/admin/admin/monitor.go +++ b/server/internal/controller/admin/admin/monitor.go @@ -8,7 +8,6 @@ package admin import ( "context" "github.com/gogf/gf/v2/errors/gerror" - "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/os/gtime" "github.com/gogf/gf/v2/text/gstr" "hotgo/api/admin/monitor" @@ -103,12 +102,10 @@ func (c *cMonitor) UserOnlineList(ctx context.Context, req *monitor.UserOnlineLi return clients[i].FirstTime < clients[j].FirstTime }) - isDemo := g.Cfg().MustGet(ctx, "hotgo.isDemo", false).Bool() _, perPage, offset := form.CalPage(req.Page, req.PerPage) - for k, v := range clients { if k >= offset && i <= perPage { - if isDemo { + if simple.IsDemo(ctx) { v.IP = consts.DemoTips } res.List = append(res.List, v) diff --git a/server/internal/controller/admin/sys/curd_demo.go b/server/internal/controller/admin/sys/curd_demo.go index 5cb7e84..38b1b8b 100644 --- a/server/internal/controller/admin/sys/curd_demo.go +++ b/server/internal/controller/admin/sys/curd_demo.go @@ -3,7 +3,7 @@ // @Copyright Copyright (c) 2024 HotGo CLI // @Author Ms <133814250@qq.com> // @License https://github.com/bufanyun/hotgo/blob/master/LICENSE -// @AutoGenerate Version 2.12.10 +// @AutoGenerate Version 2.13.1 package sys import ( @@ -19,7 +19,7 @@ var ( type cCurdDemo struct{} -// List 查看生成演示列表 +// List 查看CURD列表列表 func (c *cCurdDemo) List(ctx context.Context, req *curddemo.ListReq) (res *curddemo.ListRes, err error) { list, totalCount, err := service.SysCurdDemo().List(ctx, &req.CurdDemoListInp) if err != nil { @@ -36,19 +36,19 @@ func (c *cCurdDemo) List(ctx context.Context, req *curddemo.ListReq) (res *curdd return } -// Export 导出生成演示列表 +// Export 导出CURD列表列表 func (c *cCurdDemo) Export(ctx context.Context, req *curddemo.ExportReq) (res *curddemo.ExportRes, err error) { err = service.SysCurdDemo().Export(ctx, &req.CurdDemoListInp) return } -// Edit 更新生成演示 +// Edit 更新CURD列表 func (c *cCurdDemo) Edit(ctx context.Context, req *curddemo.EditReq) (res *curddemo.EditRes, err error) { err = service.SysCurdDemo().Edit(ctx, &req.CurdDemoEditInp) return } -// MaxSort 获取生成演示最大排序 +// MaxSort 获取CURD列表最大排序 func (c *cCurdDemo) MaxSort(ctx context.Context, req *curddemo.MaxSortReq) (res *curddemo.MaxSortRes, err error) { data, err := service.SysCurdDemo().MaxSort(ctx, &req.CurdDemoMaxSortInp) if err != nil { @@ -60,7 +60,7 @@ func (c *cCurdDemo) MaxSort(ctx context.Context, req *curddemo.MaxSortReq) (res return } -// View 获取指定生成演示信息 +// View 获取指定CURD列表信息 func (c *cCurdDemo) View(ctx context.Context, req *curddemo.ViewReq) (res *curddemo.ViewRes, err error) { data, err := service.SysCurdDemo().View(ctx, &req.CurdDemoViewInp) if err != nil { @@ -72,19 +72,13 @@ func (c *cCurdDemo) View(ctx context.Context, req *curddemo.ViewReq) (res *curdd return } -// Delete 删除生成演示 +// Delete 删除CURD列表 func (c *cCurdDemo) Delete(ctx context.Context, req *curddemo.DeleteReq) (res *curddemo.DeleteRes, err error) { err = service.SysCurdDemo().Delete(ctx, &req.CurdDemoDeleteInp) return } -// Status 更新生成演示状态 -func (c *cCurdDemo) Status(ctx context.Context, req *curddemo.StatusReq) (res *curddemo.StatusRes, err error) { - err = service.SysCurdDemo().Status(ctx, &req.CurdDemoStatusInp) - return -} - -// Switch 更新生成演示开关状态 +// Switch 更新CURD列表开关状态 func (c *cCurdDemo) Switch(ctx context.Context, req *curddemo.SwitchReq) (res *curddemo.SwitchRes, err error) { err = service.SysCurdDemo().Switch(ctx, &req.CurdDemoSwitchInp) return diff --git a/server/internal/controller/admin/sys/normal_tree_demo.go b/server/internal/controller/admin/sys/normal_tree_demo.go new file mode 100644 index 0000000..0a91fb9 --- /dev/null +++ b/server/internal/controller/admin/sys/normal_tree_demo.go @@ -0,0 +1,80 @@ +// Package sys +// @Link https://github.com/bufanyun/hotgo +// @Copyright Copyright (c) 2024 HotGo CLI +// @Author Ms <133814250@qq.com> +// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE +// @AutoGenerate Version 2.13.1 +package sys + +import ( + "context" + "hotgo/api/admin/normaltreedemo" + "hotgo/internal/model/input/sysin" + "hotgo/internal/service" +) + +var ( + NormalTreeDemo = cNormalTreeDemo{} +) + +type cNormalTreeDemo struct{} + +// List 查看普通树表列表 +func (c *cNormalTreeDemo) List(ctx context.Context, req *normaltreedemo.ListReq) (res *normaltreedemo.ListRes, err error) { + list, totalCount, err := service.SysNormalTreeDemo().List(ctx, &req.NormalTreeDemoListInp) + if err != nil { + return + } + + if list == nil { + list = []*sysin.NormalTreeDemoListModel{} + } + + res = new(normaltreedemo.ListRes) + res.List = list + res.PageRes.Pack(req, totalCount) + return +} + +// Edit 更新普通树表 +func (c *cNormalTreeDemo) Edit(ctx context.Context, req *normaltreedemo.EditReq) (res *normaltreedemo.EditRes, err error) { + err = service.SysNormalTreeDemo().Edit(ctx, &req.NormalTreeDemoEditInp) + return +} + +// MaxSort 获取普通树表最大排序 +func (c *cNormalTreeDemo) MaxSort(ctx context.Context, req *normaltreedemo.MaxSortReq) (res *normaltreedemo.MaxSortRes, err error) { + data, err := service.SysNormalTreeDemo().MaxSort(ctx, &req.NormalTreeDemoMaxSortInp) + if err != nil { + return + } + + res = new(normaltreedemo.MaxSortRes) + res.NormalTreeDemoMaxSortModel = data + return +} + +// View 获取指定普通树表信息 +func (c *cNormalTreeDemo) View(ctx context.Context, req *normaltreedemo.ViewReq) (res *normaltreedemo.ViewRes, err error) { + data, err := service.SysNormalTreeDemo().View(ctx, &req.NormalTreeDemoViewInp) + if err != nil { + return + } + + res = new(normaltreedemo.ViewRes) + res.NormalTreeDemoViewModel = data + return +} + +// Delete 删除普通树表 +func (c *cNormalTreeDemo) Delete(ctx context.Context, req *normaltreedemo.DeleteReq) (res *normaltreedemo.DeleteRes, err error) { + err = service.SysNormalTreeDemo().Delete(ctx, &req.NormalTreeDemoDeleteInp) + return +} + +// TreeOption 获取普通树表关系树选项 +func (c *cNormalTreeDemo) TreeOption(ctx context.Context, req *normaltreedemo.TreeOptionReq) (res *normaltreedemo.TreeOptionRes, err error) { + data, err := service.SysNormalTreeDemo().TreeOption(ctx) + res = (*normaltreedemo.TreeOptionRes)(&data) + return +} \ No newline at end of file diff --git a/server/internal/controller/admin/sys/option_tree_demo.go b/server/internal/controller/admin/sys/option_tree_demo.go new file mode 100644 index 0000000..00b003f --- /dev/null +++ b/server/internal/controller/admin/sys/option_tree_demo.go @@ -0,0 +1,80 @@ +// Package sys +// @Link https://github.com/bufanyun/hotgo +// @Copyright Copyright (c) 2024 HotGo CLI +// @Author Ms <133814250@qq.com> +// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE +// @AutoGenerate Version 2.13.1 +package sys + +import ( + "context" + "hotgo/api/admin/optiontreedemo" + "hotgo/internal/model/input/sysin" + "hotgo/internal/service" +) + +var ( + OptionTreeDemo = cOptionTreeDemo{} +) + +type cOptionTreeDemo struct{} + +// List 查看选项树表列表 +func (c *cOptionTreeDemo) List(ctx context.Context, req *optiontreedemo.ListReq) (res *optiontreedemo.ListRes, err error) { + list, totalCount, err := service.SysOptionTreeDemo().List(ctx, &req.OptionTreeDemoListInp) + if err != nil { + return + } + + if list == nil { + list = []*sysin.OptionTreeDemoListModel{} + } + + res = new(optiontreedemo.ListRes) + res.List = list + res.PageRes.Pack(req, totalCount) + return +} + +// Edit 更新选项树表 +func (c *cOptionTreeDemo) Edit(ctx context.Context, req *optiontreedemo.EditReq) (res *optiontreedemo.EditRes, err error) { + err = service.SysOptionTreeDemo().Edit(ctx, &req.OptionTreeDemoEditInp) + return +} + +// MaxSort 获取选项树表最大排序 +func (c *cOptionTreeDemo) MaxSort(ctx context.Context, req *optiontreedemo.MaxSortReq) (res *optiontreedemo.MaxSortRes, err error) { + data, err := service.SysOptionTreeDemo().MaxSort(ctx, &req.OptionTreeDemoMaxSortInp) + if err != nil { + return + } + + res = new(optiontreedemo.MaxSortRes) + res.OptionTreeDemoMaxSortModel = data + return +} + +// View 获取指定选项树表信息 +func (c *cOptionTreeDemo) View(ctx context.Context, req *optiontreedemo.ViewReq) (res *optiontreedemo.ViewRes, err error) { + data, err := service.SysOptionTreeDemo().View(ctx, &req.OptionTreeDemoViewInp) + if err != nil { + return + } + + res = new(optiontreedemo.ViewRes) + res.OptionTreeDemoViewModel = data + return +} + +// Delete 删除选项树表 +func (c *cOptionTreeDemo) Delete(ctx context.Context, req *optiontreedemo.DeleteReq) (res *optiontreedemo.DeleteRes, err error) { + err = service.SysOptionTreeDemo().Delete(ctx, &req.OptionTreeDemoDeleteInp) + return +} + +// TreeOption 获取选项树表关系树选项 +func (c *cOptionTreeDemo) TreeOption(ctx context.Context, req *optiontreedemo.TreeOptionReq) (res *optiontreedemo.TreeOptionRes, err error) { + data, err := service.SysOptionTreeDemo().TreeOption(ctx) + res = (*optiontreedemo.TreeOptionRes)(&data) + return +} \ No newline at end of file diff --git a/server/internal/controller/admin/sys/sms_log.go b/server/internal/controller/admin/sys/sms_log.go index f7f3872..b718efb 100644 --- a/server/internal/controller/admin/sys/sms_log.go +++ b/server/internal/controller/admin/sys/sms_log.go @@ -23,12 +23,6 @@ func (c *cSmsLog) Delete(ctx context.Context, req *smslog.DeleteReq) (res *smslo return } -// Edit 更新 -func (c *cSmsLog) Edit(ctx context.Context, req *smslog.EditReq) (res *smslog.EditRes, err error) { - err = service.SysSmsLog().Edit(ctx, &req.SmsLogEditInp) - return -} - // View 获取指定信息 func (c *cSmsLog) View(ctx context.Context, req *smslog.ViewReq) (res *smslog.ViewRes, err error) { data, err := service.SysSmsLog().View(ctx, &req.SmsLogViewInp) @@ -53,9 +47,3 @@ func (c *cSmsLog) List(ctx context.Context, req *smslog.ListReq) (res *smslog.Li res.PageRes.Pack(req, totalCount) return } - -// Status 更新状态 -func (c *cSmsLog) Status(ctx context.Context, req *smslog.StatusReq) (res *smslog.StatusRes, err error) { - err = service.SysSmsLog().Status(ctx, &req.SmsLogStatusInp) - return -} diff --git a/server/internal/controller/admin/sys/test_category.go b/server/internal/controller/admin/sys/test_category.go new file mode 100644 index 0000000..ac68ee1 --- /dev/null +++ b/server/internal/controller/admin/sys/test_category.go @@ -0,0 +1,79 @@ +// Package sys +// @Link https://github.com/bufanyun/hotgo +// @Copyright Copyright (c) 2024 HotGo CLI +// @Author Ms <133814250@qq.com> +// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE +// @AutoGenerate Version 2.13.1 +package sys + +import ( + "context" + "hotgo/api/admin/testcategory" + "hotgo/internal/model/input/sysin" + "hotgo/internal/service" +) + +var ( + TestCategory = cTestCategory{} +) + +type cTestCategory struct{} + +// List 查看测试分类列表 +func (c *cTestCategory) List(ctx context.Context, req *testcategory.ListReq) (res *testcategory.ListRes, err error) { + list, totalCount, err := service.SysTestCategory().List(ctx, &req.TestCategoryListInp) + if err != nil { + return + } + + if list == nil { + list = []*sysin.TestCategoryListModel{} + } + + res = new(testcategory.ListRes) + res.List = list + res.PageRes.Pack(req, totalCount) + return +} + +// Edit 更新测试分类 +func (c *cTestCategory) Edit(ctx context.Context, req *testcategory.EditReq) (res *testcategory.EditRes, err error) { + err = service.SysTestCategory().Edit(ctx, &req.TestCategoryEditInp) + return +} + +// MaxSort 获取测试分类最大排序 +func (c *cTestCategory) MaxSort(ctx context.Context, req *testcategory.MaxSortReq) (res *testcategory.MaxSortRes, err error) { + data, err := service.SysTestCategory().MaxSort(ctx, &req.TestCategoryMaxSortInp) + if err != nil { + return + } + + res = new(testcategory.MaxSortRes) + res.TestCategoryMaxSortModel = data + return +} + +// View 获取指定测试分类信息 +func (c *cTestCategory) View(ctx context.Context, req *testcategory.ViewReq) (res *testcategory.ViewRes, err error) { + data, err := service.SysTestCategory().View(ctx, &req.TestCategoryViewInp) + if err != nil { + return + } + + res = new(testcategory.ViewRes) + res.TestCategoryViewModel = data + return +} + +// Delete 删除测试分类 +func (c *cTestCategory) Delete(ctx context.Context, req *testcategory.DeleteReq) (res *testcategory.DeleteRes, err error) { + err = service.SysTestCategory().Delete(ctx, &req.TestCategoryDeleteInp) + return +} + +// Status 更新测试分类状态 +func (c *cTestCategory) Status(ctx context.Context, req *testcategory.StatusReq) (res *testcategory.StatusRes, err error) { + err = service.SysTestCategory().Status(ctx, &req.TestCategoryStatusInp) + return +} \ No newline at end of file diff --git a/server/internal/controller/api/user/hello.go b/server/internal/controller/api/user/hello.go deleted file mode 100644 index 015f753..0000000 --- a/server/internal/controller/api/user/hello.go +++ /dev/null @@ -1,26 +0,0 @@ -// Package user -// @Link https://github.com/bufanyun/hotgo -// @Copyright Copyright (c) 2023 HotGo CLI -// @Author Ms <133814250@qq.com> -// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE -package user - -import ( - "context" - "fmt" - "hotgo/api/api/user" - "hotgo/utility/simple" -) - -var ( - Hello = cHello{} -) - -type cHello struct{} - -func (c *cHello) Hello(ctx context.Context, req *user.HelloReq) (res *user.HelloRes, err error) { - res = &user.HelloRes{ - Tips: fmt.Sprintf("hello %v, this is the api for %v applications.", req.Name, simple.AppName(ctx)), - } - return -} diff --git a/server/internal/controller/websocket/handler/admin/monitor.go b/server/internal/controller/websocket/handler/admin/monitor.go index 33dfc77..f10ed93 100644 --- a/server/internal/controller/websocket/handler/admin/monitor.go +++ b/server/internal/controller/websocket/handler/admin/monitor.go @@ -21,6 +21,7 @@ import ( "hotgo/internal/websocket" "hotgo/utility/file" "hotgo/utility/format" + "hotgo/utility/simple" "os" "runtime" "time" @@ -76,8 +77,7 @@ func (c *cMonitor) RunInfo(client *websocket.Client, req *websocket.WRequest) { "goSize": file.DirSize(pwd), } - isDemo := g.Cfg().MustGet(client.Context(), "hotgo.isDemo", false).Bool() - if isDemo { + if simple.IsDemo(client.Context()) { data["rootPath"] = consts.DemoTips data["pwd"] = consts.DemoTips data["intranet_ip"] = consts.DemoTips diff --git a/server/internal/dao/addon_hgexample_tenant_order.go b/server/internal/dao/addon_hgexample_tenant_order.go new file mode 100644 index 0000000..b5a0354 --- /dev/null +++ b/server/internal/dao/addon_hgexample_tenant_order.go @@ -0,0 +1,27 @@ +// ================================================================================= +// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish. +// ================================================================================= + +package dao + +import ( + "hotgo/internal/dao/internal" +) + +// internalAddonHgexampleTenantOrderDao is internal type for wrapping internal DAO implements. +type internalAddonHgexampleTenantOrderDao = *internal.AddonHgexampleTenantOrderDao + +// addonHgexampleTenantOrderDao is the data access object for table hg_addon_hgexample_tenant_order. +// You can define custom methods on it to extend its functionality as you wish. +type addonHgexampleTenantOrderDao struct { + internalAddonHgexampleTenantOrderDao +} + +var ( + // AddonHgexampleTenantOrder is globally public accessible object for table hg_addon_hgexample_tenant_order operations. + AddonHgexampleTenantOrder = addonHgexampleTenantOrderDao{ + internal.NewAddonHgexampleTenantOrderDao(), + } +) + +// Fill with you ideas below. diff --git a/server/internal/dao/admin_dept.go b/server/internal/dao/admin_dept.go index d684b0f..22cf952 100644 --- a/server/internal/dao/admin_dept.go +++ b/server/internal/dao/admin_dept.go @@ -18,7 +18,7 @@ type adminDeptDao struct { } var ( - // AdminDept is globally common accessible object for table hg_admin_dept operations. + // AdminDept is globally public accessible object for table hg_admin_dept operations. AdminDept = adminDeptDao{ internal.NewAdminDeptDao(), } diff --git a/server/internal/dao/admin_member.go b/server/internal/dao/admin_member.go index d6d13ef..8c8d581 100644 --- a/server/internal/dao/admin_member.go +++ b/server/internal/dao/admin_member.go @@ -18,7 +18,7 @@ type adminMemberDao struct { } var ( - // AdminMember is globally common accessible object for table hg_admin_member operations. + // AdminMember is globally public accessible object for table hg_admin_member operations. AdminMember = adminMemberDao{ internal.NewAdminMemberDao(), } diff --git a/server/internal/dao/admin_member_post.go b/server/internal/dao/admin_member_post.go index 9dfa331..51b737c 100644 --- a/server/internal/dao/admin_member_post.go +++ b/server/internal/dao/admin_member_post.go @@ -18,7 +18,7 @@ type adminMemberPostDao struct { } var ( - // AdminMemberPost is globally common accessible object for table hg_admin_member_post operations. + // AdminMemberPost is globally public accessible object for table hg_admin_member_post operations. AdminMemberPost = adminMemberPostDao{ internal.NewAdminMemberPostDao(), } diff --git a/server/internal/dao/admin_member_role.go b/server/internal/dao/admin_member_role.go index 3966ca5..c762549 100644 --- a/server/internal/dao/admin_member_role.go +++ b/server/internal/dao/admin_member_role.go @@ -18,7 +18,7 @@ type adminMemberRoleDao struct { } var ( - // AdminMemberRole is globally common accessible object for table hg_admin_member_role operations. + // AdminMemberRole is globally public accessible object for table hg_admin_member_role operations. AdminMemberRole = adminMemberRoleDao{ internal.NewAdminMemberRoleDao(), } diff --git a/server/internal/dao/admin_menu.go b/server/internal/dao/admin_menu.go index 3699ed2..16816db 100644 --- a/server/internal/dao/admin_menu.go +++ b/server/internal/dao/admin_menu.go @@ -18,7 +18,7 @@ type adminMenuDao struct { } var ( - // AdminMenu is globally common accessible object for table hg_admin_menu operations. + // AdminMenu is globally public accessible object for table hg_admin_menu operations. AdminMenu = adminMenuDao{ internal.NewAdminMenuDao(), } diff --git a/server/internal/dao/admin_notice.go b/server/internal/dao/admin_notice.go index d722b58..399024b 100644 --- a/server/internal/dao/admin_notice.go +++ b/server/internal/dao/admin_notice.go @@ -18,7 +18,7 @@ type adminNoticeDao struct { } var ( - // AdminNotice is globally common accessible object for table hg_admin_notice operations. + // AdminNotice is globally public accessible object for table hg_admin_notice operations. AdminNotice = adminNoticeDao{ internal.NewAdminNoticeDao(), } diff --git a/server/internal/dao/admin_post.go b/server/internal/dao/admin_post.go index bb8cdc0..56c6735 100644 --- a/server/internal/dao/admin_post.go +++ b/server/internal/dao/admin_post.go @@ -18,7 +18,7 @@ type adminPostDao struct { } var ( - // AdminPost is globally common accessible object for table hg_admin_post operations. + // AdminPost is globally public accessible object for table hg_admin_post operations. AdminPost = adminPostDao{ internal.NewAdminPostDao(), } diff --git a/server/internal/dao/admin_role.go b/server/internal/dao/admin_role.go index 890b17e..39dbdc9 100644 --- a/server/internal/dao/admin_role.go +++ b/server/internal/dao/admin_role.go @@ -18,7 +18,7 @@ type adminRoleDao struct { } var ( - // AdminRole is globally common accessible object for table hg_admin_role operations. + // AdminRole is globally public accessible object for table hg_admin_role operations. AdminRole = adminRoleDao{ internal.NewAdminRoleDao(), } diff --git a/server/internal/dao/admin_role_menu.go b/server/internal/dao/admin_role_menu.go index 528ee5e..e967071 100644 --- a/server/internal/dao/admin_role_menu.go +++ b/server/internal/dao/admin_role_menu.go @@ -18,7 +18,7 @@ type adminRoleMenuDao struct { } var ( - // AdminRoleMenu is globally common accessible object for table hg_admin_role_menu operations. + // AdminRoleMenu is globally public accessible object for table hg_admin_role_menu operations. AdminRoleMenu = adminRoleMenuDao{ internal.NewAdminRoleMenuDao(), } diff --git a/server/internal/dao/internal/addon_hgexample_table.go b/server/internal/dao/internal/addon_hgexample_table.go index 8b7e93e..dd92a93 100644 --- a/server/internal/dao/internal/addon_hgexample_table.go +++ b/server/internal/dao/internal/addon_hgexample_table.go @@ -21,6 +21,9 @@ type AddonHgexampleTableDao struct { // AddonHgexampleTableColumns defines and stores column names for table hg_addon_hgexample_table. type AddonHgexampleTableColumns struct { Id string // ID + Pid string // 上级ID + Level string // 树等级 + Tree string // 关系树 CategoryId string // 分类ID Flag string // 标签 Title string // 标题 @@ -47,9 +50,6 @@ type AddonHgexampleTableColumns struct { Hobby string // 爱好 Channel string // 渠道 CityId string // 所在城市 - Pid string // 上级ID - Level string // 树等级 - Tree string // 关系树 Remark string // 备注 Status string // 状态 CreatedBy string // 创建者 @@ -62,6 +62,9 @@ type AddonHgexampleTableColumns struct { // addonHgexampleTableColumns holds the columns for table hg_addon_hgexample_table. var addonHgexampleTableColumns = AddonHgexampleTableColumns{ Id: "id", + Pid: "pid", + Level: "level", + Tree: "tree", CategoryId: "category_id", Flag: "flag", Title: "title", @@ -88,9 +91,6 @@ var addonHgexampleTableColumns = AddonHgexampleTableColumns{ Hobby: "hobby", Channel: "channel", CityId: "city_id", - Pid: "pid", - Level: "level", - Tree: "tree", Remark: "remark", Status: "status", CreatedBy: "created_by", diff --git a/server/internal/dao/internal/addon_hgexample_tenant_order.go b/server/internal/dao/internal/addon_hgexample_tenant_order.go new file mode 100644 index 0000000..84111b9 --- /dev/null +++ b/server/internal/dao/internal/addon_hgexample_tenant_order.go @@ -0,0 +1,93 @@ +// ========================================================================== +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ========================================================================== + +package internal + +import ( + "context" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" +) + +// AddonHgexampleTenantOrderDao is the data access object for table hg_addon_hgexample_tenant_order. +type AddonHgexampleTenantOrderDao struct { + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of current DAO. + columns AddonHgexampleTenantOrderColumns // columns contains all the column names of Table for convenient usage. +} + +// AddonHgexampleTenantOrderColumns defines and stores column names for table hg_addon_hgexample_tenant_order. +type AddonHgexampleTenantOrderColumns struct { + Id string // 主键 + TenantId string // 租户ID + MerchantId string // 商户ID + UserId string // 用户ID + ProductName string // 购买产品 + OrderSn string // 订单号 + Money string // 充值金额 + Remark string // 备注 + Status string // 订单状态 + CreatedAt string // 创建时间 + UpdatedAt string // 修改时间 +} + +// addonHgexampleTenantOrderColumns holds the columns for table hg_addon_hgexample_tenant_order. +var addonHgexampleTenantOrderColumns = AddonHgexampleTenantOrderColumns{ + Id: "id", + TenantId: "tenant_id", + MerchantId: "merchant_id", + UserId: "user_id", + ProductName: "product_name", + OrderSn: "order_sn", + Money: "money", + Remark: "remark", + Status: "status", + CreatedAt: "created_at", + UpdatedAt: "updated_at", +} + +// NewAddonHgexampleTenantOrderDao creates and returns a new DAO object for table data access. +func NewAddonHgexampleTenantOrderDao() *AddonHgexampleTenantOrderDao { + return &AddonHgexampleTenantOrderDao{ + group: "default", + table: "hg_addon_hgexample_tenant_order", + columns: addonHgexampleTenantOrderColumns, + } +} + +// DB retrieves and returns the underlying raw database management object of current DAO. +func (dao *AddonHgexampleTenantOrderDao) DB() gdb.DB { + return g.DB(dao.group) +} + +// Table returns the table name of current dao. +func (dao *AddonHgexampleTenantOrderDao) Table() string { + return dao.table +} + +// Columns returns all column names of current dao. +func (dao *AddonHgexampleTenantOrderDao) Columns() AddonHgexampleTenantOrderColumns { + return dao.columns +} + +// Group returns the configuration group name of database of current dao. +func (dao *AddonHgexampleTenantOrderDao) Group() string { + return dao.group +} + +// Ctx creates and returns the Model for current DAO, It automatically sets the context for current operation. +func (dao *AddonHgexampleTenantOrderDao) Ctx(ctx context.Context) *gdb.Model { + return dao.DB().Model(dao.table).Safe().Ctx(ctx) +} + +// Transaction wraps the transaction logic using function f. +// It rollbacks the transaction and returns the error from function f if it returns non-nil error. +// It commits the transaction and returns nil if function f returns nil. +// +// Note that, you should not Commit or Rollback the transaction in function f +// as it is automatically handled by this function. +func (dao *AddonHgexampleTenantOrderDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) { + return dao.Ctx(ctx).Transaction(ctx, f) +} diff --git a/server/internal/dao/internal/sys_gen_tree_demo.go b/server/internal/dao/internal/sys_gen_tree_demo.go new file mode 100644 index 0000000..583f210 --- /dev/null +++ b/server/internal/dao/internal/sys_gen_tree_demo.go @@ -0,0 +1,99 @@ +// ========================================================================== +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ========================================================================== + +package internal + +import ( + "context" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" +) + +// SysGenTreeDemoDao is the data access object for table hg_sys_gen_tree_demo. +type SysGenTreeDemoDao struct { + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of current DAO. + columns SysGenTreeDemoColumns // columns contains all the column names of Table for convenient usage. +} + +// SysGenTreeDemoColumns defines and stores column names for table hg_sys_gen_tree_demo. +type SysGenTreeDemoColumns struct { + Id string // ID + Pid string // 上级ID + Level string // 关系树级别 + Tree string // 关系树 + CategoryId string // 分类ID + Title string // 标题 + Description string // 描述 + Sort string // 排序 + Status string // 状态 + CreatedBy string // 创建者 + UpdatedBy string // 更新者 + CreatedAt string // 创建时间 + UpdatedAt string // 修改时间 + DeletedAt string // 删除时间 +} + +// sysGenTreeDemoColumns holds the columns for table hg_sys_gen_tree_demo. +var sysGenTreeDemoColumns = SysGenTreeDemoColumns{ + Id: "id", + Pid: "pid", + Level: "level", + Tree: "tree", + CategoryId: "category_id", + Title: "title", + Description: "description", + Sort: "sort", + Status: "status", + CreatedBy: "created_by", + UpdatedBy: "updated_by", + CreatedAt: "created_at", + UpdatedAt: "updated_at", + DeletedAt: "deleted_at", +} + +// NewSysGenTreeDemoDao creates and returns a new DAO object for table data access. +func NewSysGenTreeDemoDao() *SysGenTreeDemoDao { + return &SysGenTreeDemoDao{ + group: "default", + table: "hg_sys_gen_tree_demo", + columns: sysGenTreeDemoColumns, + } +} + +// DB retrieves and returns the underlying raw database management object of current DAO. +func (dao *SysGenTreeDemoDao) DB() gdb.DB { + return g.DB(dao.group) +} + +// Table returns the table name of current dao. +func (dao *SysGenTreeDemoDao) Table() string { + return dao.table +} + +// Columns returns all column names of current dao. +func (dao *SysGenTreeDemoDao) Columns() SysGenTreeDemoColumns { + return dao.columns +} + +// Group returns the configuration group name of database of current dao. +func (dao *SysGenTreeDemoDao) Group() string { + return dao.group +} + +// Ctx creates and returns the Model for current DAO, It automatically sets the context for current operation. +func (dao *SysGenTreeDemoDao) Ctx(ctx context.Context) *gdb.Model { + return dao.DB().Model(dao.table).Safe().Ctx(ctx) +} + +// Transaction wraps the transaction logic using function f. +// It rollbacks the transaction and returns the error from function f if it returns non-nil error. +// It commits the transaction and returns nil if function f returns nil. +// +// Note that, you should not Commit or Rollback the transaction in function f +// as it is automatically handled by this function. +func (dao *SysGenTreeDemoDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) { + return dao.Ctx(ctx).Transaction(ctx, f) +} diff --git a/server/internal/dao/internal/sys_login_log.go b/server/internal/dao/internal/sys_login_log.go index 01ad0fa..5d0cfac 100644 --- a/server/internal/dao/internal/sys_login_log.go +++ b/server/internal/dao/internal/sys_login_log.go @@ -20,32 +20,38 @@ type SysLoginLogDao struct { // SysLoginLogColumns defines and stores column names for table hg_sys_login_log. type SysLoginLogColumns struct { - Id string // 日志ID - ReqId string // 请求ID - MemberId string // 用户ID - Username string // 用户名 - Response string // 响应数据 - LoginAt string // 登录时间 - LoginIp string // 登录IP - ErrMsg string // 错误提示 - Status string // 状态 - CreatedAt string // 创建时间 - UpdatedAt string // 修改时间 + Id string // 日志ID + ReqId string // 请求ID + MemberId string // 用户ID + Username string // 用户名 + Response string // 响应数据 + LoginAt string // 登录时间 + LoginIp string // 登录IP + ProvinceId string // 省编码 + CityId string // 市编码 + UserAgent string // UA信息 + ErrMsg string // 错误提示 + Status string // 状态 + CreatedAt string // 创建时间 + UpdatedAt string // 修改时间 } // sysLoginLogColumns holds the columns for table hg_sys_login_log. var sysLoginLogColumns = SysLoginLogColumns{ - Id: "id", - ReqId: "req_id", - MemberId: "member_id", - Username: "username", - Response: "response", - LoginAt: "login_at", - LoginIp: "login_ip", - ErrMsg: "err_msg", - Status: "status", - CreatedAt: "created_at", - UpdatedAt: "updated_at", + Id: "id", + ReqId: "req_id", + MemberId: "member_id", + Username: "username", + Response: "response", + LoginAt: "login_at", + LoginIp: "login_ip", + ProvinceId: "province_id", + CityId: "city_id", + UserAgent: "user_agent", + ErrMsg: "err_msg", + Status: "status", + CreatedAt: "created_at", + UpdatedAt: "updated_at", } // NewSysLoginLogDao creates and returns a new DAO object for table data access. diff --git a/server/internal/dao/internal/test_category.go b/server/internal/dao/internal/test_category.go index 0a76820..d6d895a 100644 --- a/server/internal/dao/internal/test_category.go +++ b/server/internal/dao/internal/test_category.go @@ -22,6 +22,7 @@ type TestCategoryDao struct { type TestCategoryColumns struct { Id string // 分类ID Name string // 分类名称 + ShortName string // 简称 Description string // 描述 Sort string // 排序 Remark string // 备注 @@ -35,6 +36,7 @@ type TestCategoryColumns struct { var testCategoryColumns = TestCategoryColumns{ Id: "id", Name: "name", + ShortName: "short_name", Description: "description", Sort: "sort", Remark: "remark", diff --git a/server/internal/dao/sys_config.go b/server/internal/dao/sys_config.go index d91258f..626c315 100644 --- a/server/internal/dao/sys_config.go +++ b/server/internal/dao/sys_config.go @@ -18,7 +18,7 @@ type sysConfigDao struct { } var ( - // SysConfig is globally common accessible object for table hg_sys_config operations. + // SysConfig is globally public accessible object for table hg_sys_config operations. SysConfig = sysConfigDao{ internal.NewSysConfigDao(), } diff --git a/server/internal/dao/sys_dict_data.go b/server/internal/dao/sys_dict_data.go index b2c08ef..a6f66db 100644 --- a/server/internal/dao/sys_dict_data.go +++ b/server/internal/dao/sys_dict_data.go @@ -18,7 +18,7 @@ type sysDictDataDao struct { } var ( - // SysDictData is globally common accessible object for table hg_sys_dict_data operations. + // SysDictData is globally public accessible object for table hg_sys_dict_data operations. SysDictData = sysDictDataDao{ internal.NewSysDictDataDao(), } diff --git a/server/internal/dao/sys_dict_type.go b/server/internal/dao/sys_dict_type.go index 00b691f..42c3432 100644 --- a/server/internal/dao/sys_dict_type.go +++ b/server/internal/dao/sys_dict_type.go @@ -18,7 +18,7 @@ type sysDictTypeDao struct { } var ( - // SysDictType is globally common accessible object for table hg_sys_dict_type operations. + // SysDictType is globally public accessible object for table hg_sys_dict_type operations. SysDictType = sysDictTypeDao{ internal.NewSysDictTypeDao(), } diff --git a/server/internal/dao/sys_gen_tree_demo.go b/server/internal/dao/sys_gen_tree_demo.go new file mode 100644 index 0000000..e3a9bfb --- /dev/null +++ b/server/internal/dao/sys_gen_tree_demo.go @@ -0,0 +1,27 @@ +// ================================================================================= +// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish. +// ================================================================================= + +package dao + +import ( + "hotgo/internal/dao/internal" +) + +// internalSysGenTreeDemoDao is internal type for wrapping internal DAO implements. +type internalSysGenTreeDemoDao = *internal.SysGenTreeDemoDao + +// sysGenTreeDemoDao is the data access object for table hg_sys_gen_tree_demo. +// You can define custom methods on it to extend its functionality as you wish. +type sysGenTreeDemoDao struct { + internalSysGenTreeDemoDao +} + +var ( + // SysGenTreeDemo is globally public accessible object for table hg_sys_gen_tree_demo operations. + SysGenTreeDemo = sysGenTreeDemoDao{ + internal.NewSysGenTreeDemoDao(), + } +) + +// Fill with you ideas below. diff --git a/server/internal/dao/sys_log.go b/server/internal/dao/sys_log.go index eaaad61..2a427b3 100644 --- a/server/internal/dao/sys_log.go +++ b/server/internal/dao/sys_log.go @@ -18,7 +18,7 @@ type sysLogDao struct { } var ( - // SysLog is globally common accessible object for table hg_sys_log operations. + // SysLog is globally public accessible object for table hg_sys_log operations. SysLog = sysLogDao{ internal.NewSysLogDao(), } diff --git a/server/internal/dao/sys_provinces.go b/server/internal/dao/sys_provinces.go index ce46858..ce8d18f 100644 --- a/server/internal/dao/sys_provinces.go +++ b/server/internal/dao/sys_provinces.go @@ -18,7 +18,7 @@ type sysProvincesDao struct { } var ( - // SysProvinces is globally common accessible object for table hg_sys_provinces operations. + // SysProvinces is globally public accessible object for table hg_sys_provinces operations. SysProvinces = sysProvincesDao{ internal.NewSysProvincesDao(), } diff --git a/server/internal/global/cluster.go b/server/internal/global/cluster.go index f1bda98..6a12ca1 100644 --- a/server/internal/global/cluster.go +++ b/server/internal/global/cluster.go @@ -13,12 +13,12 @@ import ( "hotgo/internal/library/hgrds/lock" "hotgo/internal/library/hgrds/pubsub" "hotgo/internal/service" + "hotgo/utility/simple" ) // SubscribeClusterSync 订阅集群同步,可以用来集中同步数据、状态等 func SubscribeClusterSync(ctx context.Context) { - isCluster := g.Cfg().MustGet(ctx, "hotgo.isCluster").Bool() - if !isCluster { + if !simple.IsCluster(ctx) { return } @@ -35,8 +35,7 @@ func SubscribeClusterSync(ctx context.Context) { // PublishClusterSync 推送集群同步消息,如果没有开启集群部署,则不进行推送 func PublishClusterSync(ctx context.Context, channel string, message interface{}) { - isCluster := g.Cfg().MustGet(ctx, "hotgo.isCluster").Bool() - if !isCluster { + if !simple.IsCluster(ctx) { return } diff --git a/server/internal/global/init.go b/server/internal/global/init.go index 3071c8b..3666117 100644 --- a/server/internal/global/init.go +++ b/server/internal/global/init.go @@ -154,7 +154,7 @@ func InitTrace(ctx context.Context) { // SetGFMode 设置gf运行模式 func SetGFMode(ctx context.Context) { - mode := g.Cfg().MustGet(ctx, "hotgo.mode").String() + mode := g.Cfg().MustGet(ctx, "system.mode").String() if len(mode) == 0 { mode = gmode.NOT_SET } diff --git a/server/internal/library/addons/addons.go b/server/internal/library/addons/addons.go index 417b360..dd5f128 100644 --- a/server/internal/library/addons/addons.go +++ b/server/internal/library/addons/addons.go @@ -18,9 +18,9 @@ func GetResourcePath(ctx context.Context) string { if len(cacheResourcePath) > 0 { return cacheResourcePath } - basePath := g.Cfg().MustGet(ctx, "hotgo.addonsResourcePath").String() + basePath := g.Cfg().MustGet(ctx, "system.addonsResourcePath").String() if basePath == "" { - g.Log().Warning(ctx, "addons GetResourcePath not config found:'hotgo.addonsResourcePath', use default values:'resource'") + g.Log().Warning(ctx, "addons GetResourcePath not config found:'system.addonsResourcePath', use default values:'resource'") basePath = "resource" } diff --git a/server/internal/library/addons/build.go b/server/internal/library/addons/build.go index adf8caa..6fab466 100644 --- a/server/internal/library/addons/build.go +++ b/server/internal/library/addons/build.go @@ -45,7 +45,7 @@ func Build(ctx context.Context, option *BuildOption) (err error) { ) if resourcePath == "" { - err = gerror.New("请先设置一个有效的插件资源路径,配置名称:'hotgo.addonsResourcePath'") + err = gerror.New("请先设置一个有效的插件资源路径,配置名称:'system.addonsResourcePath'") return } diff --git a/server/internal/library/addons/module.go b/server/internal/library/addons/module.go index 32d66fb..092887f 100644 --- a/server/internal/library/addons/module.go +++ b/server/internal/library/addons/module.go @@ -11,7 +11,6 @@ import ( "github.com/gogf/gf/v2/net/ghttp" "github.com/gogf/gf/v2/os/gfile" "github.com/gogf/gf/v2/os/gres" - "github.com/gogf/gf/v2/os/gview" "hotgo/internal/model/input/form" "sort" "sync" @@ -27,16 +26,15 @@ type Option struct { // Skeleton 模块骨架 type Skeleton struct { - Label string `json:"label"` // 标识 - Name string `json:"name"` // 名称 - Group int `json:"group"` // 分组 - Logo string `json:"logo"` // logo - Brief string `json:"brief"` // 简介 - Description string `json:"description"` // 详细描述 - Author string `json:"author"` // 作者 - Version string `json:"version"` // 版本号 - RootPath string `json:"rootPath"` // 根路径 - View *gview.View `json:"view"` // 模板引擎 + Label string `json:"label"` // 标识 + Name string `json:"name"` // 名称 + Group int `json:"group"` // 分组 + Logo string `json:"logo"` // logo + Brief string `json:"brief"` // 简介 + Description string `json:"description"` // 详细描述 + Author string `json:"author"` // 作者 + Version string `json:"version"` // 版本号 + RootPath string `json:"rootPath"` // 根路径 } func (s *Skeleton) GetModule() Module { @@ -99,7 +97,6 @@ func RegisterModule(m Module) Module { } sk.RootPath = GetModulePath(name) - sk.View = NewView(m.Ctx(), name) modules[name] = m return m } @@ -139,40 +136,6 @@ func GetModuleRealPath(name string) string { return path } -// NewView 初始化一个插件的模板引擎 -func NewView(ctx context.Context, name string) *gview.View { - basePath := GetResourcePath(ctx) - if basePath == "" { - return nil - } - - view := gview.New() - path := ViewPath(name, basePath) - - if !gfile.IsDir(gfile.RealPath(path)) { - g.Log().Warningf(ctx, "NewView template path does not exist:%v,default use of main module template.", path) - return nil - } - - if err := view.SetPath(path); err != nil { - g.Log().Warningf(ctx, "NewView SetPath err:%+v", err) - return nil - } - - // 默认和主模块使用一致的变量分隔符号 - delimiters := g.Cfg().MustGet(ctx, "viewer.delimiters", []string{"@{", "}"}).Strings() - if len(delimiters) != 2 { - g.Log().Warning(ctx, "NewView delimiters config error") - return nil - } - view.SetDelimiters(delimiters[0], delimiters[1]) - - // 更多配置 - // view.SetI18n() - // ... - return view -} - // AddStaticPath 设置插件静态目录映射 func AddStaticPath(ctx context.Context, server *ghttp.Server) { basePath := GetResourcePath(ctx) diff --git a/server/internal/library/captcha/captcha.go b/server/internal/library/captcha/captcha.go index d914a6d..cdcccdc 100644 --- a/server/internal/library/captcha/captcha.go +++ b/server/internal/library/captcha/captcha.go @@ -48,7 +48,7 @@ func Generate(ctx context.Context) (id string, base64 string) { } c := base64Captcha.NewCaptcha(driver.ConvertFonts(), base64Captcha.DefaultMemStore) - id, base64, err := c.Generate() + id, base64, _, err := c.Generate() if err != nil { g.Log().Errorf(ctx, "captcha.Generate err:%+v", err) } diff --git a/server/internal/library/casbin/enforcer.go b/server/internal/library/casbin/enforcer.go index 7744397..797bf8a 100644 --- a/server/internal/library/casbin/enforcer.go +++ b/server/internal/library/casbin/enforcer.go @@ -8,8 +8,11 @@ package casbin import ( "context" "github.com/casbin/casbin/v2" + "github.com/casbin/casbin/v2/model" _ "github.com/gogf/gf/contrib/drivers/mysql/v2" "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gfile" + "github.com/gogf/gf/v2/os/gres" "hotgo/internal/consts" "net/http" "strings" @@ -37,12 +40,29 @@ func InitEnforcer(ctx context.Context) { return } - Enforcer, err = casbin.NewEnforcer("./manifest/config/casbin.conf", a) - if err != nil { - g.Log().Panicf(ctx, "casbin.NewEnforcer err . %v", err) - return + path := "manifest/config/casbin.conf" + + // 优先从本地加载casbin.conf,如果不存在就从资源文件中找 + modelContent := gfile.GetContents(path) + if len(modelContent) == 0 { + if !gres.IsEmpty() && gres.Contains(path) { + modelContent = string(gres.GetContent(path)) + } } + if len(modelContent) == 0 { + g.Log().Panicf(ctx, "casbin model file does not exist:%v", path) + } + + m, err := model.NewModelFromString(modelContent) + if err != nil { + g.Log().Panicf(ctx, "casbin NewModelFromString err:%v", err) + } + + Enforcer, err = casbin.NewEnforcer(m, a) + if err != nil { + g.Log().Panicf(ctx, "casbin NewEnforcer err:%v", err) + } loadPermissions(ctx) } diff --git a/server/internal/library/contexts/context.go b/server/internal/library/contexts/context.go index b6606ff..b9cd6bd 100644 --- a/server/internal/library/contexts/context.go +++ b/server/internal/library/contexts/context.go @@ -96,6 +96,35 @@ func GetRoleKey(ctx context.Context) string { return user.RoleKey } +// GetDeptType 获取用户部门类型 +func GetDeptType(ctx context.Context) string { + user := GetUser(ctx) + if user == nil { + return "" + } + return user.DeptType +} + +// IsCompanyDept 是否为公司部门 +func IsCompanyDept(ctx context.Context) bool { + return GetDeptType(ctx) == consts.DeptTypeCompany +} + +// IsTenantDept 是否为租户部门 +func IsTenantDept(ctx context.Context) bool { + return GetDeptType(ctx) == consts.DeptTypeTenant +} + +// IsMerchantDept 是否为商户部门 +func IsMerchantDept(ctx context.Context) bool { + return GetDeptType(ctx) == consts.DeptTypeMerchant +} + +// IsUserDept 是否为普通用户部门 +func IsUserDept(ctx context.Context) bool { + return GetDeptType(ctx) == consts.DeptTypeUser +} + // GetModule 获取应用模块 func GetModule(ctx context.Context) string { c := Get(ctx) diff --git a/server/internal/library/dict/dict.go b/server/internal/library/dict/dict.go index 105ab14..0bb04e5 100644 --- a/server/internal/library/dict/dict.go +++ b/server/internal/library/dict/dict.go @@ -9,6 +9,7 @@ import ( "context" "errors" "fmt" + "github.com/gogf/gf/v2/frame/g" "hash/fnv" "hotgo/internal/model" "strconv" @@ -40,6 +41,7 @@ func GetOptionsById(ctx context.Context, id int64) (opts []*model.Option, err er } for _, v := range GetAllFunc() { + g.Log().Warningf(ctx, "GetAllFunc GetOptionsById v:%v, %v", v.Id, v.Key) if v.Id == id { return LoadFuncOptions(ctx, v) } @@ -49,6 +51,24 @@ func GetOptionsById(ctx context.Context, id int64) (opts []*model.Option, err er return } +// GetTypeById 通过类型ID获取内置选项类型 +func GetTypeById(ctx context.Context, id int64) (typ string, err error) { + for _, v := range GetAllEnums() { + if v.Id == id { + return v.Key, nil + } + } + + for _, v := range GetAllFunc() { + if v.Id == id { + return v.Key, nil + } + } + + err = NotExistKeyError + return +} + // GenIdHash 生成字典id func GenIdHash(str string, t int64) int64 { prefix := 10000 * t diff --git a/server/internal/library/dict/dict_register_enums.go b/server/internal/library/dict/dict_register_enums.go index 5518c9c..434698b 100644 --- a/server/internal/library/dict/dict_register_enums.go +++ b/server/internal/library/dict/dict_register_enums.go @@ -56,9 +56,7 @@ func RegisterEnums(key, label string, opts []*model.Option) { func SaveEnums(key, label string, opts []*model.Option) { eLock.Lock() defer eLock.Unlock() - if _, ok := enumsOptions[key]; ok { - delete(enumsOptions, key) - } + delete(enumsOptions, key) RegisterEnums(key, label, opts) } diff --git a/server/internal/library/dict/dict_register_func.go b/server/internal/library/dict/dict_register_func.go index fcb8dc5..cf75dcd 100644 --- a/server/internal/library/dict/dict_register_func.go +++ b/server/internal/library/dict/dict_register_func.go @@ -67,9 +67,7 @@ func RegisterFunc(key, label string, fun FuncDict, cache ...bool) { func SaveFunc(key, label string, fun FuncDict, cache ...bool) { fLock.Lock() defer fLock.Unlock() - if _, ok := funcOptions[key]; ok { - delete(funcOptions, key) - } + delete(funcOptions, key) RegisterFunc(key, label, fun, cache...) } diff --git a/server/internal/library/hggen/hggen.go b/server/internal/library/hggen/hggen.go index 1ae1543..a42738e 100644 --- a/server/internal/library/hggen/hggen.go +++ b/server/internal/library/hggen/hggen.go @@ -6,7 +6,9 @@ package hggen import ( + "github.com/gogf/gf/v2/os/gfile" _ "hotgo/internal/library/hggen/internal/cmd/gendao" + "hotgo/internal/library/hggen/internal/utility/utils" _ "unsafe" "context" @@ -31,13 +33,37 @@ func doGenDaoForArray(ctx context.Context, index int, in gendao.CGenDaoInput) // Dao 生成数据库实体 func Dao(ctx context.Context) (err error) { + + // 在执行gf gen dao时,先将生成文件放在临时路径,生成完成后再拷贝到项目 + // 目的是希望减少触发gf热编译的几率,防止热编译运行时代码生成流程未结束被自动重启打断 + // gf gen dao 的执行时长主要取决于需要生成数据库表的数量,表越多速度越慢 + tempPathPrefix := views.GetTempGeneratePath(ctx) + "/dao" + for _, v := range daoConfig { inp := defaultGenDaoInput - err = gconv.Scan(v, &inp) - if err != nil { + if err = gconv.Scan(v, &inp); err != nil { return } + oldPath := inp.Path + inp.ImportPrefix = utils.GetImportPath(inp.Path) + inp.Path = tempPathPrefix + "/" + inp.Path + + if err = gfile.Remove(inp.Path); err != nil { + err = gerror.Newf("清理临时生成目录失败:%v", err) + return err + } + + if err = gfile.Mkdir(inp.Path); err != nil { + err = gerror.Newf("创建临时生成目录失败:%v", err) + return err + } + doGenDaoForArray(ctx, -1, inp) + + if err = gfile.CopyDir(inp.Path, gfile.Pwd()+"/"+oldPath); err != nil { + err = gerror.Newf("拷贝生成文件失败:%v", err) + return err + } } return } @@ -129,8 +155,16 @@ func TableSelects(ctx context.Context, in *sysin.GenCodesSelectsInp) (res *sysin Label: v, }) } + for _, v := range views.TableAligns { + res.TableAlign = append(res.TableAlign, &form.Select{ + Value: v, + Name: views.TableAlignMap[v], + Label: views.TableAlignMap[v], + }) + } res.Addons = addons.ModuleSelect() + res.TreeStyleType = consts.GenCodesTreeStyleTypeOptions return } @@ -196,15 +230,12 @@ func Preview(ctx context.Context, in *sysin.GenCodesPreviewInp) (res *sysin.GenC } switch in.GenType { - case consts.GenCodesTypeCurd: + case consts.GenCodesTypeCurd, consts.GenCodesTypeTree: return views.Curd.DoPreview(ctx, &views.CurdPreviewInput{ In: in, DaoConfig: GetDaoConfig(in.DbName), Config: genConfig, }) - case consts.GenCodesTypeTree: - err = gerror.Newf("生成类型开发中!") - return case consts.GenCodesTypeQueue: err = gerror.Newf("生成类型开发中!") return @@ -222,7 +253,7 @@ func Build(ctx context.Context, in *sysin.GenCodesBuildInp) (err error) { } switch in.GenType { - case consts.GenCodesTypeCurd: + case consts.GenCodesTypeCurd, consts.GenCodesTypeTree: pin := &sysin.GenCodesPreviewInp{SysGenCodes: in.SysGenCodes} return views.Curd.DoBuild(ctx, &views.CurdBuildInput{ PreviewIn: &views.CurdPreviewInput{ @@ -233,25 +264,17 @@ func Build(ctx context.Context, in *sysin.GenCodesBuildInp) (err error) { BeforeEvent: views.CurdBuildEvent{"runDao": Dao}, AfterEvent: views.CurdBuildEvent{"runService": func(ctx context.Context) (err error) { cfg := GetServiceConfig() - if err = ServiceWithCfg(ctx, cfg); err != nil { - return - } - // 插件模块,同时运行模块下的gen service + // 插件模块,切换到插件下运行gen service if genConfig.Application.Crud.Templates[pin.GenTemplate].IsAddon { // 依然使用配置中的参数,只是将生成路径指向插件模块路径 cfg.SrcFolder = "addons/" + pin.AddonName + "/logic" cfg.DstFolder = "addons/" + pin.AddonName + "/service" - if err = ServiceWithCfg(ctx, cfg); err != nil { - return - } } + err = ServiceWithCfg(ctx, cfg) return }}, }) - case consts.GenCodesTypeTree: - err = gerror.Newf("生成类型开发中!") - return case consts.GenCodesTypeQueue: err = gerror.Newf("生成类型开发中!") return diff --git a/server/internal/library/hggen/internal/cmd/cmd_init.go b/server/internal/library/hggen/internal/cmd/cmd_init.go index fc08f81..db93862 100644 --- a/server/internal/library/hggen/internal/cmd/cmd_init.go +++ b/server/internal/library/hggen/internal/cmd/cmd_init.go @@ -25,6 +25,7 @@ import ( ) var ( + // Init . Init = cInit{} ) @@ -64,14 +65,13 @@ type cInitInput struct { Name string `name:"NAME" arg:"true" v:"required" brief:"{cInitNameBrief}"` Mono bool `name:"mono" short:"m" brief:"initialize a mono-repo instead a single-repo" orphan:"true"` Update bool `name:"update" short:"u" brief:"update to the latest goframe version" orphan:"true"` + Module string `name:"module" short:"g" brief:"custom go module"` } type cInitOutput struct{} func (c cInit) Index(ctx context.Context, in cInitInput) (out *cInitOutput, err error) { - var ( - overwrote = false - ) + var overwrote = false if !gfile.IsEmpty(in.Name) && !allyes.Check() { s := gcmd.Scanf(`the folder "%s" is not empty, files might be overwrote, continue? [y/n]: `, in.Name) if strings.EqualFold(s, "n") { @@ -105,7 +105,7 @@ func (c cInit) Index(ctx context.Context, in cInitInput) (out *cInitOutput, err err = gfile.ReadLines(gitignoreFile, func(line string) error { // Add only hidden files or directories // If other directories are added, it may cause the entire directory to be ignored - // such as 'main' in the .gitignore file, but the path is 'D:\main\my-project' + // such as 'main' in the .gitignore file, but the path is ' D:\main\my-project ' if line != "" && strings.HasPrefix(line, ".") { ignoreFiles = append(ignoreFiles, line) } @@ -118,6 +118,11 @@ func (c cInit) Index(ctx context.Context, in cInitInput) (out *cInitOutput, err } } + // Replace module name. + if in.Module == "" { + in.Module = gfile.Basename(gfile.RealPath(in.Name)) + } + // Replace template name to project name. err = gfile.ReplaceDirFunc(func(path, content string) string { for _, ignoreFile := range ignoreFiles { @@ -125,7 +130,7 @@ func (c cInit) Index(ctx context.Context, in cInitInput) (out *cInitOutput, err return content } } - return gstr.Replace(gfile.GetContents(path), cInitRepoPrefix+templateRepoName, gfile.Basename(gfile.RealPath(in.Name))) + return gstr.Replace(gfile.GetContents(path), cInitRepoPrefix+templateRepoName, in.Module) }, in.Name, "*", true) if err != nil { return diff --git a/server/internal/library/hggen/internal/cmd/gendao/gendao_structure.go b/server/internal/library/hggen/internal/cmd/gendao/gendao_structure.go index c4398c8..f6c585e 100644 --- a/server/internal/library/hggen/internal/cmd/gendao/gendao_structure.go +++ b/server/internal/library/hggen/internal/cmd/gendao/gendao_structure.go @@ -135,9 +135,14 @@ func generateStructFieldDefinition( " #" + gstr.CaseCamel(newFiledName), " #" + localTypeNameStr, } - attrLines = append(attrLines, " #"+fmt.Sprintf(tagKey+`json:"%s"`, jsonTag)) - attrLines = append(attrLines, " #"+fmt.Sprintf(`description:"%s"`+tagKey, descriptionTag)) - attrLines = append(attrLines, " #"+fmt.Sprintf(`// %s`, formatComment(field.Comment))) + attrLines = append(attrLines, fmt.Sprintf(` #%sjson:"%s"`, tagKey, jsonTag)) + // orm tag + if !in.IsDo { + // entity + attrLines = append(attrLines, fmt.Sprintf(` #orm:"%s"`, field.Name)) + } + attrLines = append(attrLines, fmt.Sprintf(` #description:"%s"%s`, descriptionTag, tagKey)) + attrLines = append(attrLines, fmt.Sprintf(` #// %s`, formatComment(field.Comment))) for k, v := range attrLines { if in.NoJsonTag { diff --git a/server/internal/library/hggen/views/column_default.go b/server/internal/library/hggen/views/column_default.go index e207d6e..040f9c2 100644 --- a/server/internal/library/hggen/views/column_default.go +++ b/server/internal/library/hggen/views/column_default.go @@ -181,12 +181,12 @@ func setDefaultFormMode(field *sysin.GenCodesColumnListModel) { return } - if field.GoType == GoTypeString && field.Length >= 200 && field.Length <= 500 { + if field.GoType == GoTypeString && field.Length >= 256 && field.Length <= 512 { field.FormMode = FormModeInputTextarea return } - if field.GoType == GoTypeString && field.Length > 500 { + if field.GoType == GoTypeString && field.Length > 512 { field.FormMode = FormModeInputEditor return } diff --git a/server/internal/library/hggen/views/column_map.go b/server/internal/library/hggen/views/column_map.go index 43d03da..6e9d28b 100644 --- a/server/internal/library/hggen/views/column_map.go +++ b/server/internal/library/hggen/views/column_map.go @@ -3,12 +3,12 @@ // @Copyright Copyright (c) 2023 HotGo CLI // @Author Ms <133814250@qq.com> // @License https://github.com/bufanyun/hotgo/blob/master/LICENSE -// package views import ( "github.com/gogf/gf/v2/text/gstr" "hotgo/internal/model/input/sysin" + "hotgo/utility/validate" ) // 字段映射关系 @@ -109,6 +109,8 @@ const ( FormModeCheckbox = "Checkbox" // 复选按钮 FormModeSelect = "Select" // 单选下拉框 FormModeSelectMultiple = "SelectMultiple" // 多选下拉框 + FormModeTreeSelect = "TreeSelect" // 树型选择 + FormModeCascader = "Cascader" // 级联选择 FormModeUploadImage = "UploadImage" // 单图上传 FormModeUploadImages = "UploadImages" // 多图上传 FormModeUploadFile = "UploadFile" // 单文件上传 @@ -116,12 +118,13 @@ const ( FormModeSwitch = "Switch" // 开关 FormModeRate = "Rate" // 评分 FormModeCitySelector = "CitySelector" // 省市区选择 + FormModePidTreeSelect = "PidTreeSelect" // 树型上级选择,树表生成专用 ) var FormModes = []string{ FormModeInput, FormModeInputNumber, FormModeInputTextarea, FormModeInputEditor, FormModeInputDynamic, FormModeDate, FormModeDateRange, FormModeTime, FormModeTimeRange, - FormModeRadio, FormModeCheckbox, FormModeSelect, FormModeSelectMultiple, + FormModeRadio, FormModeCheckbox, FormModeSelect, FormModeSelectMultiple, FormModeTreeSelect, FormModeCascader, FormModeUploadImage, FormModeUploadImages, FormModeUploadFile, FormModeUploadFiles, FormModeSwitch, FormModeRate, @@ -142,6 +145,8 @@ var FormModeMap = map[string]string{ FormModeCheckbox: "复选按钮", FormModeSelect: "单选下拉框", FormModeSelectMultiple: "多选下拉框", + FormModeTreeSelect: "树型选择", + FormModeCascader: "级联选择", FormModeUploadImage: "单图上传", FormModeUploadImages: "多图上传", FormModeUploadFile: "单文件上传", @@ -190,20 +195,20 @@ var FormRoleMap = map[string]string{ // 查询条件 const ( - WhereModeEq = "=" // = - WhereModeNeq = "!=" // != - WhereModeGt = ">" // > - WhereModeGte = ">=" // >= - WhereModeLt = "<" // < - WhereModeLte = "<=" // <= - WhereModeIn = "IN" // IN (...) - WhereModeNotIn = "NOT IN" // NOT IN (...) - WhereModeBetween = "BETWEEN" // BETWEEN - WhereModeNotBetween = "NOT BETWEEN" // NOT BETWEEN - WhereModeLike = "LIKE" // LIKE - WhereModeLikeAll = "LIKE %...%" // LIKE %...% - WhereModeNotLike = "NOT LIKE" // NOT LIKE - WhereModeJsonContains = "JSON_CONTAINS(json_doc, val)" // JSON_CONTAINS(json_doc, val[, path]) // 判断是否包含某个json值 + WhereModeEq = "=" // = + WhereModeNeq = "!=" // != + WhereModeGt = ">" // > + WhereModeGte = ">=" // >= + WhereModeLt = "<" // < + WhereModeLte = "<=" // <= + WhereModeIn = "IN" // IN (...) + WhereModeNotIn = "NOT IN" // NOT IN (...) + WhereModeBetween = "BETWEEN" // BETWEEN + WhereModeNotBetween = "NOT BETWEEN" // NOT BETWEEN + WhereModeLike = "LIKE" // LIKE + WhereModeLikeAll = "LIKE %...%" // LIKE %...% + WhereModeNotLike = "NOT LIKE" // NOT LIKE + WhereModeJsonContains = "JSON_CONTAINS(doc, val)" // JSON_CONTAINS(json_doc, val[, path]) // 判断是否包含某个json值 ) var WhereModes = []string{WhereModeEq, @@ -214,6 +219,21 @@ var WhereModes = []string{WhereModeEq, WhereModeJsonContains, } +// 表格列的排序方式 +const ( + TableAlignLeft = "left" + TableAlignRight = "right" + TableAlignCenter = "center" +) + +var TableAligns = []string{TableAlignLeft, TableAlignRight, TableAlignCenter} + +var TableAlignMap = map[string]string{ + TableAlignLeft: "居左", + TableAlignRight: "居右", + TableAlignCenter: "居中", +} + // IsNumberType 是否是数字类型 func IsNumberType(goType string) bool { switch goType { @@ -225,8 +245,17 @@ func IsNumberType(goType string) bool { return false } -func HasColumn(masterFields []*sysin.GenCodesColumnListModel, column string) bool { - for _, field := range masterFields { +// IsSelectFormMode 是否是选择器组件 +func IsSelectFormMode(formMode string) bool { + switch formMode { + case FormModeRadio, FormModeCheckbox, FormModeSelect, FormModeSelectMultiple, FormModeCitySelector, FormModeTreeSelect, FormModeCascader: + return true + } + return false +} + +func HasColumn(fields []*sysin.GenCodesColumnListModel, column string) bool { + for _, field := range fields { if field.GoName == column { return true } @@ -234,29 +263,77 @@ func HasColumn(masterFields []*sysin.GenCodesColumnListModel, column string) boo return false } -func HasColumnWithFormMode(masterFields []*sysin.GenCodesColumnListModel, column string) bool { - for _, field := range masterFields { - if field.FormMode == column { +func HasColumnWithFormMode(fields []*sysin.GenCodesColumnListModel, formMode string) bool { + for _, field := range fields { + if field.FormMode == formMode { return true } } return false } -func HasMaxSort(masterFields []*sysin.GenCodesColumnListModel) bool { - return HasColumn(masterFields, "Sort") +func HasMaxSort(fields []*sysin.GenCodesColumnListModel) bool { + return HasColumn(fields, "Sort") } -func HasStatus(headOps []string, masterFields []*sysin.GenCodesColumnListModel) bool { +func HasStatus(headOps []string, fields []*sysin.GenCodesColumnListModel) bool { if !gstr.InArray(headOps, "status") { return false } - return HasColumn(masterFields, "Status") + return HasColumn(fields, "Status") } -func HasSwitch(headOps []string, masterFields []*sysin.GenCodesColumnListModel) bool { - if !gstr.InArray(headOps, "switch") { - return false - } - return HasColumnWithFormMode(masterFields, "Switch") +func HasSwitch(fields []*sysin.GenCodesColumnListModel) bool { + return HasColumnWithFormMode(fields, FormModeSwitch) +} + +func HasHookMemberSummary(fields []*sysin.GenCodesColumnListModel) bool { + for _, field := range fields { + if IsMemberSummaryField(field.Name) { + if field.IsList { + return true + } + } + } + return false +} + +func HasQueryMemberSummary(fields []*sysin.GenCodesColumnListModel) bool { + for _, field := range fields { + if IsMemberSummaryField(field.Name) { + if field.IsQuery { + return true + } + } + } + return false +} + +func IsMemberSummaryField(name string) bool { + switch name { + case "created_by", "updated_by", "deleted_by": + return true + } + return false +} + +// ReviseFields 校正字段值,兼容版本升级前的老数据格式 +func ReviseFields(fields []*sysin.GenCodesColumnListModel) []*sysin.GenCodesColumnListModel { + for _, field := range fields { + if !validate.InSlice(TableAligns, field.Align) { + field.Align = TableAlignLeft + } + + if field.Width < 1 { + field.Width = -1 + } + if field.Width > 2000 { + field.Width = 2000 + } + + if field.FormGridSpan < 1 { + field.FormGridSpan = 1 + } + } + return fields } diff --git a/server/internal/library/hggen/views/curd.go b/server/internal/library/hggen/views/curd.go index af590d8..9acde1b 100644 --- a/server/internal/library/hggen/views/curd.go +++ b/server/internal/library/hggen/views/curd.go @@ -7,6 +7,7 @@ package views import ( "context" + "github.com/gogf/gf/v2/container/gvar" "github.com/gogf/gf/v2/database/gdb" "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/frame/g" @@ -17,12 +18,13 @@ import ( "hotgo/internal/consts" "hotgo/internal/dao" "hotgo/internal/library/hggen/internal/cmd/gendao" - "hotgo/internal/library/hggen/internal/utility/utils" "hotgo/internal/library/hgorm" "hotgo/internal/model" "hotgo/internal/model/input/sysin" + "hotgo/internal/service" "hotgo/utility/convert" "hotgo/utility/file" + "hotgo/utility/tree" "runtime" "strings" ) @@ -32,18 +34,41 @@ var Curd = gCurd{} type gCurd struct{} type CurdStep struct { - HasMaxSort bool `json:"hasMaxSort"` - HasAdd bool `json:"hasAdd"` - HasBatchDel bool `json:"hasBatchDel"` - HasExport bool `json:"hasExport"` - HasNotFilterAuth bool `json:"hasNotFilterAuth"` - HasEdit bool `json:"hasEdit"` - HasDel bool `json:"hasDel"` - HasView bool `json:"hasView"` - HasStatus bool `json:"hasStatus"` - HasSwitch bool `json:"hasSwitch"` - HasCheck bool `json:"hasCheck"` - HasMenu bool `json:"hasMenu"` + HasMaxSort bool // 最大排序 + HasAdd bool // 表单添加 + HasBatchDel bool // 批量删除 + HasExport bool // 表格导出 + HasNotFilterAuth bool // 不过滤认证权限 + HasEdit bool // 表单编辑 + HasDel bool // 删除 + HasView bool // 查看详情 + HasStatus bool // 修改状态 + HasSwitch bool // 数值开关 + HasCheck bool // 勾选列 + HasMenu bool // 菜单权限 + IsTreeTable bool // 树型列表 + IsOptionTreeTable bool // 选项式树型列表 + HasRules bool // 表单验证规则 + HasRulesValidator bool // 表单验证器 + HasSearchForm bool // 列表搜索 + HasDict bool // 字典 + HasFuncDict bool // 注册方法字典 + HasQueryMemberSummary bool // 查询用户摘要 + HasHookMemberSummary bool // hook用户摘要 + ImportModel ImportModel // 公用导包 - model.ts + ActionColumnWidth int64 // 列表操作栏宽度 + IsAddon bool // 是否是插件 +} + +// ImportModel 导包 - model.ts +type ImportModel struct { + NaiveUI []string + UtilsIs []string + UtilsUrl []string + UtilsDate []string + UtilsValidate []string + UtilsHotGo []string + UtilsIndex []string } type CurdOptionsJoin struct { @@ -63,18 +88,52 @@ type CurdOptionsMenu struct { Sort int `json:"sort"` } +type OptionsTree struct { + TitleColumn string `json:"titleColumn"` + StyleType int `json:"styleType"` + TitleField *sysin.GenCodesColumnListModel +} + +// PresetStep 预设生成流程参数 +type PresetStep struct { + FormGridCols int `json:"formGridCols" dc:"表单显示的栅格数量"` +} + type CurdOptions struct { AutoOps []string `json:"autoOps"` ColumnOps []string `json:"columnOps"` HeadOps []string `json:"headOps"` Join []*CurdOptionsJoin `json:"join"` Menu *CurdOptionsMenu `json:"menu"` + Tree *OptionsTree `json:"tree"` TemplateGroup string `json:"templateGroup"` ApiPrefix string `json:"apiPrefix"` + ImportWebApi string `json:"importWebApi"` + FuncDict *FuncDict `json:"funcDict"` + PresetStep *PresetStep `json:"presetStep"` Step *CurdStep // 转换后的流程控制条件 + DictOps CurdOptionsDict // 字典选项 dictMap g.Map // 字典选项 -> 字段映射关系 } +type FuncDict struct { + ValueColumn string // 选项值 + LabelColumn string //选项名称 + Value *sysin.GenCodesColumnListModel + Label *sysin.GenCodesColumnListModel +} + +type CurdOptionsDict struct { + Has bool + Types []string + Schemas []*OptionsSchemasField +} + +type OptionsSchemasField struct { + Field string + Type string +} + type CurdPreviewInput struct { In *sysin.GenCodesPreviewInp // 提交参数 DaoConfig gendao.CGenDaoInput // 生成dao配置 @@ -98,31 +157,44 @@ func (l *gCurd) initInput(ctx context.Context, in *CurdPreviewInput) (err error) in.content = new(sysin.GenCodesPreviewModel) in.content.Views = make(map[string]*sysin.GenFile) - // 加载主表配置 - if err = in.In.MasterColumns.Scan(&in.masterFields); err != nil { - return + // 初始化生成选项 + if err = initOptions(in); err != nil { + return err } - if len(in.masterFields) == 0 { - if in.masterFields, err = DoTableColumns(ctx, &sysin.GenCodesColumnListInp{Name: in.In.DbName, Table: in.In.TableName}, in.DaoConfig); err != nil { - return - } + // 初始化表字段配置 + if err = initTableField(ctx, in); err != nil { + return err } - // 主键属性 - in.pk = l.getPkField(in) - if in.pk == nil { - return gerror.New("initInput no primary key is set in the table!") - } - - // 加载选项 - if err = in.In.Options.Scan(&in.options); err != nil { - return + // 初始化树表 + if err = initTableTree(in); err != nil { + return err } initStep(in) - in.options.dictMap = make(g.Map) + // 初始化方法字典 + if err = initFuncDict(in); err != nil { + return err + } + + // 初始化生成模板 + if err = initTemplate(in); err != nil { + return err + } + return +} + +func initOptions(in *CurdPreviewInput) (err error) { + if err = in.In.Options.Scan(&in.options); err != nil { + return + } + in.options.dictMap = make(g.Map) + return +} + +func initTemplate(in *CurdPreviewInput) (err error) { if len(in.Config.Application.Crud.Templates)-1 < in.In.GenTemplate { return gerror.New("没有找到生成模板的配置,请检查!") } @@ -141,20 +213,126 @@ func (l *gCurd) initInput(ctx context.Context, in *CurdPreviewInput) (err error) return } +func initFuncDict(in *CurdPreviewInput) (err error) { + if !in.options.Step.HasFuncDict || in.options.FuncDict == nil { + return + } + + if len(in.options.FuncDict.LabelColumn) == 0 || len(in.options.FuncDict.ValueColumn) == 0 { + err = gerror.New("生成字典选项必须设置选项值和选项名称") + return err + } + + for _, field := range in.masterFields { + if field.Name == in.options.FuncDict.ValueColumn { + in.options.FuncDict.Value = field + } + + if field.Name == in.options.FuncDict.LabelColumn { + in.options.FuncDict.Label = field + } + } + return +} + +func initTableField(ctx context.Context, in *CurdPreviewInput) (err error) { + // 加载主表配置 + if err = in.In.MasterColumns.Scan(&in.masterFields); err != nil { + return + } + + if len(in.masterFields) == 0 { + if in.masterFields, err = DoTableColumns(ctx, &sysin.GenCodesColumnListInp{Name: in.In.DbName, Table: in.In.TableName}, in.DaoConfig); err != nil { + return + } + } + + // 主键属性 + in.pk = getPkField(in) + if in.pk == nil { + return gerror.New("initInput no primary key is set in the table!") + } + + in.masterFields = ReviseFields(in.masterFields) + + // 检查表命名 + var names = []string{in.In.DaoName} + for _, v := range in.options.Join { + v.Columns = ReviseFields(v.Columns) + names = append(names, v.DaoName) + } + if err = CheckIllegalName("数据库表名", names...); err != nil { + return + } + + if err = CheckIllegalName("实体命名", in.In.VarName); err != nil { + return + } + return +} + +func initTableTree(in *CurdPreviewInput) (err error) { + // 检查树表字段 + if in.In.GenType == consts.GenCodesTypeTree { + if err = CheckTreeTableFields(in.masterFields); err != nil { + return err + } + + // 解析选项树名称字段 + has := false + for _, field := range in.masterFields { + if in.options.Tree.TitleColumn == field.Name { + in.options.Tree.TitleField = field + has = true + break + } + } + if !has { + err = gerror.New("请选择一个有效的树名称字段") + return + } + } + return err +} + func initStep(in *CurdPreviewInput) { in.options.Step = new(CurdStep) in.options.Step.HasMaxSort = HasMaxSort(in.masterFields) in.options.Step.HasAdd = gstr.InArray(in.options.HeadOps, "add") - in.options.Step.HasBatchDel = gstr.InArray(in.options.HeadOps, "batchDel") + in.options.Step.HasBatchDel = gstr.InArray(in.options.HeadOps, "batchDel") && gstr.InArray(in.options.ColumnOps, "check") in.options.Step.HasExport = gstr.InArray(in.options.HeadOps, "export") in.options.Step.HasNotFilterAuth = gstr.InArray(in.options.ColumnOps, "notFilterAuth") in.options.Step.HasEdit = gstr.InArray(in.options.ColumnOps, "edit") in.options.Step.HasDel = gstr.InArray(in.options.ColumnOps, "del") in.options.Step.HasView = gstr.InArray(in.options.ColumnOps, "view") in.options.Step.HasStatus = HasStatus(in.options.ColumnOps, in.masterFields) - in.options.Step.HasSwitch = HasSwitch(in.options.ColumnOps, in.masterFields) + in.options.Step.HasSwitch = HasSwitch(in.masterFields) in.options.Step.HasCheck = gstr.InArray(in.options.ColumnOps, "check") in.options.Step.HasMenu = gstr.InArray(in.options.AutoOps, "genMenuPermissions") + in.options.Step.HasQueryMemberSummary = HasQueryMemberSummary(in.masterFields) + in.options.Step.HasHookMemberSummary = HasHookMemberSummary(in.masterFields) + in.options.Step.IsTreeTable = in.In.GenType == consts.GenCodesTypeTree + if in.options.Step.IsTreeTable { + in.options.Step.IsOptionTreeTable = in.options.Tree.StyleType == consts.GenCodesTreeStyleTypeOption + } + in.options.Step.HasFuncDict = gstr.InArray(in.options.AutoOps, "genFuncDict") + in.options.Step.IsAddon = in.Config.Application.Crud.Templates[in.In.GenTemplate].IsAddon + if in.options.PresetStep.FormGridCols < 1 { + in.options.PresetStep.FormGridCols = 1 + } +} + +// getPkField 获取主键 +func getPkField(in *CurdPreviewInput) *sysin.GenCodesColumnListModel { + if len(in.masterFields) == 0 { + panic("getPkField masterFields uninitialized.") + } + for _, field := range in.masterFields { + if IsIndexPK(field.Index) { + return field + } + } + return nil } func (l *gCurd) loadView(ctx context.Context, in *CurdPreviewInput) (err error) { @@ -170,14 +348,14 @@ func (l *gCurd) loadView(ctx context.Context, in *CurdPreviewInput) (err error) now := gtime.Now() view.BindFuncMap(g.Map{ - "NowYear": now.Year, // 当前年 - "ToLower": strings.ToLower, // 全部小写 - "LcFirst": gstr.LcFirst, // 首字母小写 - "UcFirst": gstr.UcFirst, // 首字母大写 + "NowYear": now.Year, // 当前年 + "ToLower": strings.ToLower, // 全部小写 + "LcFirst": gstr.LcFirst, // 首字母小写 + "UcFirst": gstr.UcFirst, // 首字母大写 + "ToTSArray": ToTSArray, // 转为ts数组格式 }) - dictOptions, err := l.generateWebModelDictOptions(ctx, in) - if err != nil { + if err = l.generateWebModelDictOptions(ctx, in); err != nil { return } @@ -193,9 +371,9 @@ func (l *gCurd) loadView(ctx context.Context, in *CurdPreviewInput) (err error) importService = "hotgo/addons/" + in.In.AddonName + "/service" } - importWebApi := "@/api/" + gstr.LcFirst(in.In.VarName) + in.options.ImportWebApi = "@/api/" + gstr.LcFirst(in.In.VarName) if temp.IsAddon { - importWebApi = "@/api/addons/" + in.In.AddonName + "/" + gstr.LcFirst(in.In.VarName) + in.options.ImportWebApi = "@/api/addons/" + in.In.AddonName + "/" + gstr.LcFirst(in.In.VarName) } componentPrefix := gstr.LcFirst(in.In.VarName) @@ -216,12 +394,12 @@ func (l *gCurd) loadView(ctx context.Context, in *CurdPreviewInput) (err error) "masterFields": in.masterFields, // 主表字段 "pk": in.pk, // 主键属性 "options": in.options, // 提交选项 - "dictOptions": dictOptions, // web字典选项 + "dictOptions": in.options.DictOps, // web字典选项 "importApi": importApi, // 导入goApi包 "importInput": importInput, // 导入input包 "importController": importController, // 导入控制器包 "importService": importService, // 导入业务服务 - "importWebApi": importWebApi, // 导入webApi + "importWebApi": in.options.ImportWebApi, // 导入webApi "apiPrefix": in.options.ApiPrefix, // api前缀 "componentPrefix": componentPrefix, // vue子组件前缀 }) @@ -231,6 +409,7 @@ func (l *gCurd) loadView(ctx context.Context, in *CurdPreviewInput) (err error) } func (l *gCurd) DoBuild(ctx context.Context, in *CurdBuildInput) (err error) { + st := gtime.Now() preview, err := l.DoPreview(ctx, in.PreviewIn) if err != nil { return @@ -296,10 +475,6 @@ func (l *gCurd) DoBuild(ctx context.Context, in *CurdBuildInput) (err error) { if err = gfile.PutContents(vi.Path, strings.TrimSpace(vi.Content)); err != nil { return gerror.Newf("writing content to '%s' failed: %v", vi.Path, err) } - - if gstr.Str(vi.Path, `.`) == ".go" { - utils.GoFmt(vi.Path) - } } // 后置操作 @@ -312,6 +487,7 @@ func (l *gCurd) DoBuild(ctx context.Context, in *CurdBuildInput) (err error) { } } } + g.Log().Debugf(ctx, "generate code operation completed, %vms", gtime.Now().Sub(st).Milliseconds()) return } @@ -386,6 +562,11 @@ func (l *gCurd) generateApiContent(ctx context.Context, in *CurdPreviewInput) (e return err } + genFile.Content, err = FormatGo(ctx, name, genFile.Content) + if err != nil { + return err + } + genFile.Path = file.MergeAbs(in.Config.Application.Crud.Templates[in.In.GenTemplate].ApiPath, strings.ToLower(in.In.VarName), strings.ToLower(in.In.VarName)+".go") genFile.Meth = consts.GenCodesBuildMethCreate if gfile.Exists(genFile.Path) { @@ -417,6 +598,12 @@ func (l *gCurd) generateInputContent(ctx context.Context, in *CurdPreviewInput) if err != nil { return err } + + genFile.Content, err = FormatGo(ctx, name, genFile.Content) + if err != nil { + return err + } + genFile.Path = file.MergeAbs(in.Config.Application.Crud.Templates[in.In.GenTemplate].InputPath, convert.CamelCaseToUnderline(in.In.VarName)+".go") genFile.Meth = consts.GenCodesBuildMethCreate if gfile.Exists(genFile.Path) { @@ -443,6 +630,12 @@ func (l *gCurd) generateControllerContent(ctx context.Context, in *CurdPreviewIn if err != nil { return err } + + genFile.Content, err = FormatGo(ctx, name, genFile.Content) + if err != nil { + return err + } + genFile.Path = file.MergeAbs(in.Config.Application.Crud.Templates[in.In.GenTemplate].ControllerPath, convert.CamelCaseToUnderline(in.In.VarName)+".go") genFile.Meth = consts.GenCodesBuildMethCreate if gfile.Exists(genFile.Path) { @@ -473,6 +666,12 @@ func (l *gCurd) generateLogicContent(ctx context.Context, in *CurdPreviewInput) if err != nil { return err } + + genFile.Content, err = FormatGo(ctx, name, genFile.Content) + if err != nil { + return err + } + genFile.Path = file.MergeAbs(in.Config.Application.Crud.Templates[in.In.GenTemplate].LogicPath, convert.CamelCaseToUnderline(in.In.VarName)+".go") genFile.Meth = consts.GenCodesBuildMethCreate if gfile.Exists(genFile.Path) { @@ -499,6 +698,11 @@ func (l *gCurd) generateRouterContent(ctx context.Context, in *CurdPreviewInput) return err } + genFile.Content, err = FormatGo(ctx, name, genFile.Content) + if err != nil { + return err + } + genFile.Path = file.MergeAbs(in.Config.Application.Crud.Templates[in.In.GenTemplate].RouterPath, convert.CamelCaseToUnderline(in.In.VarName)+".go") genFile.Meth = consts.GenCodesBuildMethCreate if gfile.Exists(genFile.Path) { @@ -525,6 +729,8 @@ func (l *gCurd) generateWebApiContent(ctx context.Context, in *CurdPreviewInput) return err } + genFile.Content = FormatTs(genFile.Content) + genFile.Path = file.MergeAbs(in.Config.Application.Crud.Templates[in.In.GenTemplate].WebApiPath, gstr.LcFirst(in.In.VarName), "index.ts") genFile.Meth = consts.GenCodesBuildMethCreate if gfile.Exists(genFile.Path) { @@ -556,6 +762,8 @@ func (l *gCurd) generateWebModelContent(ctx context.Context, in *CurdPreviewInpu return } + genFile.Content = FormatTs(genFile.Content) + genFile.Path = file.MergeAbs(in.Config.Application.Crud.Templates[in.In.GenTemplate].WebViewsPath, gstr.LcFirst(in.In.VarName), "model.ts") genFile.Meth = consts.GenCodesBuildMethCreate if gfile.Exists(genFile.Path) { @@ -586,6 +794,8 @@ func (l *gCurd) generateWebIndexContent(ctx context.Context, in *CurdPreviewInpu return err } + genFile.Content = FormatVue(genFile.Content) + genFile.Path = file.MergeAbs(in.Config.Application.Crud.Templates[in.In.GenTemplate].WebViewsPath, gstr.LcFirst(in.In.VarName), "index.vue") genFile.Meth = consts.GenCodesBuildMethCreate if gfile.Exists(genFile.Path) { @@ -616,6 +826,8 @@ func (l *gCurd) generateWebEditContent(ctx context.Context, in *CurdPreviewInput return err } + genFile.Content = FormatVue(genFile.Content) + genFile.Path = file.MergeAbs(in.Config.Application.Crud.Templates[in.In.GenTemplate].WebViewsPath, gstr.LcFirst(in.In.VarName), "edit.vue") genFile.Meth = consts.GenCodesBuildMethCreate if gfile.Exists(genFile.Path) { @@ -651,6 +863,8 @@ func (l *gCurd) generateWebViewContent(ctx context.Context, in *CurdPreviewInput return err } + genFile.Content = FormatVue(genFile.Content) + genFile.Path = file.MergeAbs(in.Config.Application.Crud.Templates[in.In.GenTemplate].WebViewsPath, gstr.LcFirst(in.In.VarName), "view.vue") genFile.Meth = consts.GenCodesBuildMethCreate if gfile.Exists(genFile.Path) { @@ -683,6 +897,11 @@ func (l *gCurd) generateSqlContent(ctx context.Context, in *CurdPreviewInput) (e genFile = new(sysin.GenFile) ) + menus, err := service.AdminMenu().GetFastList(ctx) + if err != nil { + return err + } + tplData["dirPid"], tplData["dirLevel"], tplData["dirTree"], err = hgorm.AutoUpdateTree(ctx, &dao.AdminMenu, 0, int64(in.options.Menu.Pid)) if err != nil { return err @@ -692,9 +911,30 @@ func (l *gCurd) generateSqlContent(ctx context.Context, in *CurdPreviewInput) (e tplData["btnLevel"] = tplData["dirLevel"].(int) + 2 tplData["sortLevel"] = tplData["dirLevel"].(int) + 3 + pageRedirect := "" if in.options.Menu.Pid > 0 { tplData["mainComponent"] = "ParentLayout" + menu, ok := menus[int64(in.options.Menu.Pid)] + if !ok { + err = gerror.New("选择的上级菜单不存在") + return + } + for _, id := range tree.GetIds(menu.Tree) { + if v, ok2 := menus[id]; ok2 { + if !gstr.HasSuffix(pageRedirect, "/") && !gstr.HasPrefix(v.Path, "/") { + pageRedirect += "/" + } + pageRedirect += v.Path + } + } + + if !gstr.HasSuffix(pageRedirect, "/") && !gstr.HasPrefix(menu.Path, "/") { + pageRedirect += "/" + } + pageRedirect += menu.Path } + pageRedirect += "/" + gstr.LcFirst(in.In.VarName) + "/index" + tplData["pageRedirect"] = pageRedirect genFile.Path = file.MergeAbs(in.Config.Application.Crud.Templates[in.In.GenTemplate].SqlPath, convert.CamelCaseToUnderline(in.In.VarName)+"_menu.sql") genFile.Meth = consts.GenCodesBuildMethCreate @@ -708,6 +948,48 @@ func (l *gCurd) generateSqlContent(ctx context.Context, in *CurdPreviewInput) (e genFile.Required = false } + // 需要生成时,检查菜单命名是否存在 + if genFile.Meth == consts.GenCodesBuildMethCreate { + menuNamePrefix := gstr.LcFirst(in.In.VarName) + menuNames := []string{menuNamePrefix, menuNamePrefix + "Index"} + if in.options.Step.HasEdit { + menuNames = append(menuNames, menuNamePrefix+"Edit") + menuNames = append(menuNames, menuNamePrefix+"View") + } + if in.options.Step.HasView { + menuNames = append(menuNames, menuNamePrefix+"View") + } + if in.options.Step.HasMaxSort { + menuNames = append(menuNames, menuNamePrefix+"MaxSort") + } + if in.options.Step.HasDel { + menuNames = append(menuNames, menuNamePrefix+"Delete") + } + if in.options.Step.HasStatus { + menuNames = append(menuNames, menuNamePrefix+"Status") + } + if in.options.Step.HasSwitch { + menuNames = append(menuNames, menuNamePrefix+"Switch") + } + if in.options.Step.HasExport { + menuNames = append(menuNames, menuNamePrefix+"Export") + } + if in.options.Step.IsTreeTable { + menuNames = append(menuNames, menuNamePrefix+"TreeOption") + } + + menuNames = convert.UniqueSlice(menuNames) + hasMenus, err := service.AdminMenu().Model(ctx).Fields("name").WhereIn("name", menuNames).Array() + if err != nil { + return err + } + + if len(hasMenus) > 0 { + err = gerror.Newf("要生成的菜单中有已存在的路由别名,请检查并删除:%v", strings.Join(gvar.New(hasMenus).Strings(), `、`)) + return err + } + } + tplData["generatePath"] = genFile.Path genFile.Content, err = in.view.Parse(ctx, name+".template", tplData) if err != nil { diff --git a/server/internal/library/hggen/views/curd_generate_input.go b/server/internal/library/hggen/views/curd_generate_input.go index 1d98abd..2861b0c 100644 --- a/server/internal/library/hggen/views/curd_generate_input.go +++ b/server/internal/library/hggen/views/curd_generate_input.go @@ -28,6 +28,7 @@ const ( InputTypeEditInpValidator = 4 // 添加&编辑验证器 InputTypeUpdateFields = 5 // 编辑修改过滤字段 InputTypeInsertFields = 6 // 编辑新增过滤字段 + InputTypeTreeOptionFields = 7 // 关系树查询字段 EditInpValidatorGenerally = "if err := g.Validator().Rules(\"%s\").Data(in.%s).Messages(\"%s\").Run(ctx); err != nil {\n\t\treturn err.Current()\n\t}\n" ) @@ -39,21 +40,79 @@ func (l *gCurd) inputTplData(ctx context.Context, in *CurdPreviewInput) (data g. data["editInpValidator"] = l.generateInputListColumns(ctx, in, InputTypeEditInpValidator) data["updateFieldsColumns"] = l.generateInputListColumns(ctx, in, InputTypeUpdateFields) data["insertFieldsColumns"] = l.generateInputListColumns(ctx, in, InputTypeInsertFields) + data["viewModelColumns"] = l.generateInputViewColumns(ctx, in) + if in.options.Step.IsTreeTable { + data["treeOptionFields"] = l.generateInputListColumns(ctx, in, InputTypeTreeOptionFields) + } return } +func (l *gCurd) generateInputViewColumns(ctx context.Context, in *CurdPreviewInput) string { + buffer := bytes.NewBuffer(nil) + + index := 0 + array := make([][]string, 1000) + // 主表 + for _, field := range in.masterFields { + // 查询用户摘要 + if field.IsList && in.options.Step.HasHookMemberSummary && IsMemberSummaryField(field.Name) { + tagKey := "`" + descriptionTag := gstr.Replace(formatComment(field.Dc)+"摘要信息", `"`, `\"`) + result := []string{" #" + field.GoName + "Summa"} + result = append(result, " #*hook.MemberSumma") + result = append(result, " #"+fmt.Sprintf(tagKey+`json:"%s"`, field.TsName+"Summa")) + result = append(result, " #"+fmt.Sprintf(`dc:"%s"`+tagKey, descriptionTag)) + array[index] = result + index++ + } + } + + tw := tablewriter.NewWriter(buffer) + tw.SetBorder(false) + tw.SetRowLine(false) + tw.SetAutoWrapText(false) + tw.SetColumnSeparator("") + tw.AppendBulk(array) + tw.Render() + stContent := buffer.String() + // Let's do this hack of table writer for indent! + stContent = gstr.Replace(stContent, " #", "") + stContent = gstr.Replace(stContent, "` ", "`") + stContent = gstr.Replace(stContent, "``", "") + stContent = removeEndWrap(stContent) + + buffer.Reset() + buffer.WriteString(stContent) + return "\tentity." + in.In.DaoName + "\n" + buffer.String() +} + func (l *gCurd) generateInputListColumns(ctx context.Context, in *CurdPreviewInput, inputType int) string { buffer := bytes.NewBuffer(nil) index := 0 array := make([][]string, 1000) // 主表 for _, field := range in.masterFields { - row := l.generateStructFieldDefinition(field, inputType) + row := l.generateStructFieldDefinition(in, field, inputType, true) if row == nil { continue } array[index] = row index++ + + switch inputType { + case InputTypeListModel: + // 查询用户摘要 + if field.IsList && in.options.Step.HasHookMemberSummary && IsMemberSummaryField(field.Name) { + tagKey := "`" + descriptionTag := gstr.Replace(formatComment(field.Dc)+"摘要信息", `"`, `\"`) + result := []string{" #" + field.GoName + "Summa"} + result = append(result, " #*hook.MemberSumma") + result = append(result, " #"+fmt.Sprintf(tagKey+`json:"%s"`, field.TsName+"Summa")) + result = append(result, " #"+fmt.Sprintf(`dc:"%s"`+tagKey, descriptionTag)) + array[index] = result + index++ + } + } } // 关联表 @@ -63,7 +122,7 @@ func (l *gCurd) generateInputListColumns(ctx context.Context, in *CurdPreviewInp continue } for _, field := range v.Columns { - row := l.generateStructFieldDefinition(field, inputType) + row := l.generateStructFieldDefinition(in, field, inputType, false) if row != nil { array[index] = row index++ @@ -92,43 +151,62 @@ func (l *gCurd) generateInputListColumns(ctx context.Context, in *CurdPreviewInp } // generateStructFieldForModel generates and returns the attribute definition for specified field. -func (l *gCurd) generateStructFieldDefinition(field *sysin.GenCodesColumnListModel, inputType int) []string { +func (l *gCurd) generateStructFieldDefinition(in *CurdPreviewInput, field *sysin.GenCodesColumnListModel, inputType int, isMaster bool) []string { var ( tagKey = "`" result = []string{" #" + field.GoName} descriptionTag = gstr.Replace(formatComment(field.Dc), `"`, `\"`) ) + addResult := func() []string { + result = append(result, " #"+field.GoType) + result = append(result, " #"+fmt.Sprintf(tagKey+`json:"%s"`, field.TsName)) + result = append(result, " #"+fmt.Sprintf(`dc:"%s"`+tagKey, descriptionTag)) + return result + } + + isQuery := false + switch inputType { case InputTypeListInp: - if !field.IsQuery { + if in.options.Step.IsTreeTable && IsPidName(field.Name) { + isQuery = true + field.QueryWhere = WhereModeEq + } + if !field.IsQuery && !isQuery { return nil } if field.QueryWhere == WhereModeBetween { result = append(result, " #[]"+field.GoType) } else { - result = append(result, " #"+field.GoType) + // 查询用户摘要时,固定接收字符串类型 + if field.IsQuery && in.options.Step.HasQueryMemberSummary && IsMemberSummaryField(field.Name) { + result = append(result, " #string") + } else { + result = append(result, " #"+field.GoType) + } } result = append(result, " #"+fmt.Sprintf(tagKey+`json:"%s"`, field.TsName)) result = append(result, " #"+fmt.Sprintf(`dc:"%s"`+tagKey, descriptionTag)) case InputTypeListModel: - if !field.IsList { + // 主表的主键 + if IsIndexPK(field.Index) && isMaster { + addResult() + // 树表的pid字段 + } else if in.options.Step.IsTreeTable && IsPidName(field.Name) { + addResult() + } else if field.IsList { + addResult() + } else { return nil } - - result = append(result, " #"+field.GoType) - result = append(result, " #"+fmt.Sprintf(tagKey+`json:"%s"`, field.TsName)) - result = append(result, " #"+fmt.Sprintf(`dc:"%s"`+tagKey, descriptionTag)) case InputTypeExportModel: if !field.IsExport { return nil } - - result = append(result, " #"+field.GoType) - result = append(result, " #"+fmt.Sprintf(tagKey+`json:"%s"`, field.TsName)) - result = append(result, " #"+fmt.Sprintf(`dc:"%s"`+tagKey, descriptionTag)) + addResult() case InputTypeEditInpValidator: if !field.IsEdit { return nil @@ -150,18 +228,23 @@ func (l *gCurd) generateStructFieldDefinition(field *sysin.GenCodesColumnListMod if !field.IsEdit && field.GoName != "UpdatedBy" { return nil } - - result = append(result, " #"+field.GoType) - result = append(result, " #"+fmt.Sprintf(tagKey+`json:"%s"`, field.TsName)) - result = append(result, " #"+fmt.Sprintf(`dc:"%s"`+tagKey, descriptionTag)) + addResult() case InputTypeInsertFields: if !field.IsEdit && field.GoName != "CreatedBy" { return nil } - - result = append(result, " #"+field.GoType) - result = append(result, " #"+fmt.Sprintf(tagKey+`json:"%s"`, field.TsName)) - result = append(result, " #"+fmt.Sprintf(`dc:"%s"`+tagKey, descriptionTag)) + addResult() + case InputTypeTreeOptionFields: + if IsIndexPK(field.Index) { + return addResult() + } + if IsPidName(field.Name) { + return addResult() + } + if in.options.Tree.TitleColumn == field.Name { + return addResult() + } + return nil default: panic("inputType is invalid") } diff --git a/server/internal/library/hggen/views/curd_generate_logic.go b/server/internal/library/hggen/views/curd_generate_logic.go index fd889d9..08db2de 100644 --- a/server/internal/library/hggen/views/curd_generate_logic.go +++ b/server/internal/library/hggen/views/curd_generate_logic.go @@ -16,22 +16,20 @@ import ( ) const ( - LogicWhereComments = "\n\t// 查询%s\n" - LogicWhereNoSupport = "\t// TODO 暂不支持生成[ %s ]查询方式,请自行补充此处代码!" - LogicListSimpleSelect = "\tfields, err := hgorm.GenSelect(ctx, sysin.%sListModel{}, dao.%s)\n\tif err != nil {\n\t\treturn\n\t}" - LogicListJoinSelect = "\t// 关联表select\n\tfields, err := hgorm.GenJoinSelect(ctx, %sin.%sListModel{}, &dao.%s, []*hgorm.Join{\n%v\t})\n\n\tif err != nil {\n\t\terr = gerror.Wrap(err, \"获取%s关联字段失败,请稍后重试!\")\n\t\treturn\n\t}" - LogicListJoinOnRelation = "\t// 关联表%s\n\tmod = mod.%s(hgorm.GenJoinOnRelation(\n\t\tdao.%s.Table(), dao.%s.Columns().%s, // 主表表名,关联字段\n\t\tdao.%s.Table(), \"%s\", dao.%s.Columns().%s, // 关联表表名,别名,关联字段\n\t)...)\n\n" - LogicEditUpdate = "\tif _, err = s.Model(ctx%s).\n\t\t\tFields(%sin.%sUpdateFields{}).\n\t\t\tWherePri(in.%s).Data(in).Update(); err != nil {\n\t\t\terr = gerror.Wrap(err, \"修改%s失败,请稍后重试!\")\n\t\t}\n\t\treturn" - LogicEditInsert = "\tif _, err = s.Model(ctx, &handler.Option{FilterAuth: false}).\n\t\tFields(%sin.%sInsertFields{}).\n\t\tData(in).Insert(); err != nil {\n\t\terr = gerror.Wrap(err, \"新增%s失败,请稍后重试!\")\n\t}" - LogicEditUnique = "\t// 验证'%s'唯一\n\tif err = hgorm.IsUnique(ctx, &dao.%s, g.Map{dao.%s.Columns().%s: in.%s}, \"%s已存在\", in.Id); err != nil {\n\t\treturn\n\t}\n" - LogicSwitchUpdate = "g.Map{\n\t\tin.Key: in.Value,\n%s}" - LogicStatusUpdate = "g.Map{\n\t\tdao.%s.Columns().Status: in.Status,\n%s}" + LogicWhereComments = "\n\t// 查询%s\n" + LogicWhereNoSupport = "\t// TODO 暂不支持生成[ %s ]查询方式,请自行补充此处代码!" + LogicEditUpdate = "\tif _, err = s.Model(ctx%s).\n\t\t\tFields(%sin.%sUpdateFields{}).\n\t\t\tWherePri(in.%s).Data(in).Update(); err != nil {\n\t\t\terr = gerror.Wrap(err, \"修改%s失败,请稍后重试!\")\n\t\t}\n\t\treturn" + LogicEditInsert = "\tif _, err = s.Model(ctx, &handler.Option{FilterAuth: false}).\n\t\tFields(%sin.%sInsertFields{}).\n\t\tData(in).Insert(); err != nil {\n\t\terr = gerror.Wrap(err, \"新增%s失败,请稍后重试!\")\n\t}" + LogicEditUnique = "\t// 验证'%s'唯一\n\tif err = hgorm.IsUnique(ctx, &dao.%s, g.Map{dao.%s.Columns().%s: in.%s}, \"%s已存在\", in.Id); err != nil {\n\t\treturn\n\t}\n" + LogicSwitchUpdate = "g.Map{\n\t\tin.Key: in.Value,\n%s}" + LogicStatusUpdate = "g.Map{\n\t\tdao.%s.Columns().Status: in.Status,\n%s}" ) func (l *gCurd) logicTplData(ctx context.Context, in *CurdPreviewInput) (data g.Map, err error) { data = make(g.Map) data["listWhere"] = l.generateLogicListWhere(ctx, in) data["listJoin"] = l.generateLogicListJoin(ctx, in) + data["listFields"] = l.generateLogicListFields(ctx, in) data["listOrder"] = l.generateLogicListOrder(ctx, in) data["edit"] = l.generateLogicEdit(ctx, in) data["switchFields"] = l.generateLogicSwitchFields(ctx, in) @@ -113,76 +111,109 @@ func (l *gCurd) generateLogicEdit(ctx context.Context, in *CurdPreviewInput) g.M } func (l *gCurd) generateLogicListOrder(ctx context.Context, in *CurdPreviewInput) string { + statement := "" + if hasEffectiveJoins(in.options.Join) { + statement = "dao." + in.In.DaoName + ".Table() + \".\" +" + } buffer := bytes.NewBuffer(nil) if in.options.Step.HasMaxSort { - buffer.WriteString("OrderAsc(dao." + in.In.DaoName + ".Columns().Sort).") + buffer.WriteString("OrderAsc(" + statement + "dao." + in.In.DaoName + ".Columns().Sort).") } - buffer.WriteString("OrderDesc(dao." + in.In.DaoName + ".Columns()." + in.pk.GoName + ")") + buffer.WriteString("OrderDesc(" + statement + "dao." + in.In.DaoName + ".Columns()." + in.pk.GoName + ")") return buffer.String() } -func (l *gCurd) generateLogicListJoin(ctx context.Context, in *CurdPreviewInput) g.Map { - var data = make(g.Map) - data["link"] = "" +func (l *gCurd) generateLogicListJoin(ctx context.Context, in *CurdPreviewInput) (link string) { + connector := `"="` if hasEffectiveJoins(in.options.Join) { - var ( - selectBuffer = bytes.NewBuffer(nil) - linkBuffer = bytes.NewBuffer(nil) - joinSelectRows string - ) - + linkBuffer := bytes.NewBuffer(nil) for _, join := range in.options.Join { if isEffectiveJoin(join) { - joinSelectRows = joinSelectRows + fmt.Sprintf("\t\t{Dao: &dao.%s, Alias: \"%s\"},\n", join.DaoName, join.Alias) - linkBuffer.WriteString(fmt.Sprintf(LogicListJoinOnRelation, join.Alias, consts.GenCodesJoinLinkMap[join.LinkMode], in.In.DaoName, in.In.DaoName, gstr.CaseCamel(join.MasterField), join.DaoName, join.Alias, join.DaoName, gstr.CaseCamel(join.Field))) + linkBuffer.WriteString("\tmod = mod." + consts.GenCodesJoinLinkMap[join.LinkMode] + "OnFields(dao." + join.DaoName + ".Table(), dao." + in.In.DaoName + ".Columns()." + gstr.CaseCamel(join.MasterField) + "," + connector + ", dao." + join.DaoName + ".Columns()." + gstr.CaseCamel(join.Field) + ")\n") } } - - selectBuffer.WriteString(fmt.Sprintf(LogicListJoinSelect, in.options.TemplateGroup, in.In.VarName, in.In.DaoName, joinSelectRows, in.In.TableComment)) - - data["select"] = selectBuffer.String() - data["fields"] = "fields" - data["link"] = linkBuffer.String() - } else { - data["fields"] = fmt.Sprintf("%sin.%sListModel{}", in.options.TemplateGroup, in.In.VarName) + link = linkBuffer.String() } - return data + return +} + +func (l *gCurd) generateLogicListFields(ctx context.Context, in *CurdPreviewInput) (fields string) { + selectBuffer := bytes.NewBuffer(nil) + if hasEffectiveJoins(in.options.Join) { + selectBuffer.WriteString("mod = mod.FieldsPrefix(dao." + in.In.DaoName + ".Table(), " + in.options.TemplateGroup + "in." + in.In.VarName + "ListModel{})\n") + for _, join := range in.options.Join { + if isEffectiveJoin(join) { + selectBuffer.WriteString("mod = mod.Fields(hgorm.JoinFields(ctx, " + in.options.TemplateGroup + "in." + in.In.VarName + "ListModel{}, &dao." + join.DaoName + ", \"" + join.Alias + "\"))\n") + } + } + fields = selectBuffer.String() + } else { + fields = fmt.Sprintf("mod = mod.Fields(%sin.%sListModel{})", in.options.TemplateGroup, in.In.VarName) + } + return } func (l *gCurd) generateLogicListWhere(ctx context.Context, in *CurdPreviewInput) string { buffer := bytes.NewBuffer(nil) // 主表 - l.generateLogicListWhereEach(buffer, in.masterFields, in.In.DaoName, "") + l.generateLogicListWhereEach(buffer, in, in.masterFields, in.In.DaoName, "") // 关联表 if hasEffectiveJoins(in.options.Join) { for _, v := range in.options.Join { if isEffectiveJoin(v) { - l.generateLogicListWhereEach(buffer, v.Columns, v.DaoName, v.Alias) + l.generateLogicListWhereEach(buffer, in, v.Columns, v.DaoName, v.Alias) } } } return buffer.String() } -func (l *gCurd) generateLogicListWhereEach(buffer *bytes.Buffer, fields []*sysin.GenCodesColumnListModel, daoName string, alias string) { +func (l *gCurd) generateLogicListWhereEach(buffer *bytes.Buffer, in *CurdPreviewInput, fields []*sysin.GenCodesColumnListModel, daoName string, alias string) { isLink := false if alias != "" { alias = `"` + alias + `."+` isLink = true } + + tablePrefix := "" + wherePrefix := "Where" + if isLink { + wherePrefix = "WherePrefix" + tablePrefix = "dao." + daoName + ".Table(), " + } + for _, field := range fields { - if !field.IsQuery || field.QueryWhere == "" { + isQuery := false + // 树表查询上级 + if in.options.Step.IsTreeTable && IsPidName(field.Name) { + isQuery = true + field.QueryWhere = WhereModeEq + } + + if (!field.IsQuery && !isQuery) || field.QueryWhere == "" { continue } + buffer.WriteString(fmt.Sprintf(LogicWhereComments, field.Dc)) + var ( linkMode string whereTag string columnName string ) + // 查询用户摘要 + if field.IsQuery && in.options.Step.HasQueryMemberSummary && IsMemberSummaryField(field.Name) { + servicePackName := "service" + if in.options.Step.IsAddon { + servicePackName = "isc" + } + buffer.WriteString(fmt.Sprintf("if in.%v != \"\" {\n\t\t\t\tids, err := %v.AdminMember().GetIdsByKeyword(ctx, in.%v)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, 0, err\n\t\t\t\t}\n\t\t\t\tmod = mod.WhereIn(dao.%v.Columns().%v, ids)\n\t\t\t}\n", field.GoName, servicePackName, field.GoName, in.In.DaoName, field.GoName)) + continue + } + if IsNumberType(field.GoType) { linkMode = `in.` + field.GoName + ` > 0` } else if field.GoType == GoTypeGTime { @@ -197,8 +228,6 @@ func (l *gCurd) generateLogicListWhereEach(buffer *bytes.Buffer, fields []*sysin linkMode = `len(in.` + field.GoName + `) == 2` } - buffer.WriteString(fmt.Sprintf(LogicWhereComments, field.Dc)) - // 如果是关联表重新转换字段 columnName = field.GoName if isLink { @@ -207,35 +236,35 @@ func (l *gCurd) generateLogicListWhereEach(buffer *bytes.Buffer, fields []*sysin switch field.QueryWhere { case WhereModeEq: - whereTag = "\tif " + linkMode + " {\n\t\tmod = mod.Where(" + alias + "dao." + daoName + ".Columns()." + columnName + ", in." + field.GoName + ")\n\t}" + whereTag = "\tif " + linkMode + " {\n\t\tmod = mod." + wherePrefix + "(" + tablePrefix + "dao." + daoName + ".Columns()." + columnName + ", in." + field.GoName + ")\n\t}" case WhereModeNeq: - whereTag = "\tif " + linkMode + " {\n\t\tmod = mod.WhereNot(dao." + daoName + ".Columns()." + columnName + ", in." + field.GoName + ")\n\t}" + whereTag = "\tif " + linkMode + " {\n\t\tmod = mod." + wherePrefix + "Not(" + tablePrefix + "dao." + daoName + ".Columns()." + columnName + ", in." + field.GoName + ")\n\t}" case WhereModeGt: - whereTag = "\tif " + linkMode + " {\n\t\tmod = mod.WhereGT(dao." + daoName + ".Columns()." + columnName + ", in." + field.GoName + ")\n\t}" + whereTag = "\tif " + linkMode + " {\n\t\tmod = mod." + wherePrefix + "GT(" + tablePrefix + "dao." + daoName + ".Columns()." + columnName + ", in." + field.GoName + ")\n\t}" case WhereModeGte: - whereTag = "\tif " + linkMode + " {\n\t\tmod = mod.WhereGTE(dao." + daoName + ".Columns()." + columnName + ", in." + field.GoName + ")\n\t}" + whereTag = "\tif " + linkMode + " {\n\t\tmod = mod." + wherePrefix + "GTE(" + tablePrefix + "dao." + daoName + ".Columns()." + columnName + ", in." + field.GoName + ")\n\t}" case WhereModeLt: - whereTag = "\tif " + linkMode + " {\n\t\tmod = mod.WhereLT(dao." + daoName + ".Columns()." + columnName + ", in." + field.GoName + ")\n\t}" + whereTag = "\tif " + linkMode + " {\n\t\tmod = mod." + wherePrefix + "LT(" + tablePrefix + "dao." + daoName + ".Columns()." + columnName + ", in." + field.GoName + ")\n\t}" case WhereModeLte: - whereTag = "\tif " + linkMode + " {\n\t\tmod = mod.WhereLTE(dao." + daoName + ".Columns()." + columnName + ", in." + field.GoName + ")\n\t}" + whereTag = "\tif " + linkMode + " {\n\t\tmod = mod." + wherePrefix + "LTE(" + tablePrefix + "dao." + daoName + ".Columns()." + columnName + ", in." + field.GoName + ")\n\t}" case WhereModeIn: - whereTag = "\tif " + linkMode + " {\n\t\tmod = mod.WhereIn(dao." + daoName + ".Columns()." + columnName + ", in." + field.GoName + ")\n\t}" + whereTag = "\tif " + linkMode + " {\n\t\tmod = mod." + wherePrefix + "In(" + tablePrefix + "dao." + daoName + ".Columns()." + columnName + ", in." + field.GoName + ")\n\t}" case WhereModeNotIn: - whereTag = "\tif " + linkMode + " {\n\t\tmod = mod.WhereNotIn(dao." + daoName + ".Columns()." + columnName + ", in." + field.GoName + ")\n\t}" + whereTag = "\tif " + linkMode + " {\n\t\tmod = mod." + wherePrefix + "NotIn(" + tablePrefix + "dao." + daoName + ".Columns()." + columnName + ", in." + field.GoName + ")\n\t}" case WhereModeBetween: - whereTag = "\tif " + linkMode + " {\n\t\tmod = mod.WhereBetween(dao." + daoName + ".Columns()." + columnName + ", in." + field.GoName + "[0], in." + field.GoName + "[1])\n\t}" + whereTag = "\tif " + linkMode + " {\n\t\tmod = mod." + wherePrefix + "Between(" + tablePrefix + "dao." + daoName + ".Columns()." + columnName + ", in." + field.GoName + "[0], in." + field.GoName + "[1])\n\t}" case WhereModeNotBetween: - whereTag = "\tif " + linkMode + " {\n\t\tmod = mod.WhereNotBetween(dao." + daoName + ".Columns()." + columnName + ", in." + field.GoName + "[0], in." + field.GoName + "[1])\n\t}" + whereTag = "\tif " + linkMode + " {\n\t\tmod = mod." + wherePrefix + "NotBetween(" + tablePrefix + "dao." + daoName + ".Columns()." + columnName + ", in." + field.GoName + "[0], in." + field.GoName + "[1])\n\t}" case WhereModeLike: - whereTag = "\tif " + linkMode + " {\n\t\tmod = mod.WhereLike(dao." + daoName + ".Columns()." + columnName + ", in." + field.GoName + ")\n\t}" + whereTag = "\tif " + linkMode + " {\n\t\tmod = mod." + wherePrefix + "Like(" + tablePrefix + "dao." + daoName + ".Columns()." + columnName + ", in." + field.GoName + ")\n\t}" case WhereModeLikeAll: val := `"%"+in.` + field.GoName + `+"%"` - whereTag = "\tif " + linkMode + " {\n\t\tmod = mod.WhereLike(dao." + daoName + ".Columns()." + columnName + ", " + val + ")\n\t}" + whereTag = "\tif " + linkMode + " {\n\t\tmod = mod." + wherePrefix + "Like(" + tablePrefix + "dao." + daoName + ".Columns()." + columnName + ", " + val + ")\n\t}" case WhereModeNotLike: - whereTag = "\tif " + linkMode + " {\n\t\tmod = mod.WhereNotLike(dao." + daoName + ".Columns()." + columnName + ", in." + field.GoName + ")\n\t}" + whereTag = "\tif " + linkMode + " {\n\t\tmod = mod." + wherePrefix + "NotLike(" + tablePrefix + "dao." + daoName + ".Columns()." + columnName + ", in." + field.GoName + ")\n\t}" case WhereModeJsonContains: - val := "fmt.Sprintf(`JSON_CONTAINS(%s,'%v')`, dao." + daoName + ".Columns()." + columnName + ", in." + field.GoName + ")" - whereTag = "\tif in." + field.GoName + linkMode + " {\n\t\tmod = mod.Where(" + val + ")\n\t}" + val := tablePrefix + "fmt.Sprintf(`JSON_CONTAINS(%s,'%v')`, dao." + daoName + ".Columns()." + columnName + ", in." + field.GoName + ")" + whereTag = "\tif in." + field.GoName + linkMode + " {\n\t\tmod = mod." + wherePrefix + "(" + val + ")\n\t}" default: buffer.WriteString(fmt.Sprintf(LogicWhereNoSupport, field.QueryWhere)) diff --git a/server/internal/library/hggen/views/curd_generate_web_edit.go b/server/internal/library/hggen/views/curd_generate_web_edit.go index fc6cb72..dd4ceb9 100644 --- a/server/internal/library/hggen/views/curd_generate_web_edit.go +++ b/server/internal/library/hggen/views/curd_generate_web_edit.go @@ -22,7 +22,7 @@ func (l *gCurd) webEditTplData(ctx context.Context, in *CurdPreviewInput) (data func (l *gCurd) generateWebEditFormItem(ctx context.Context, in *CurdPreviewInput) string { buffer := bytes.NewBuffer(nil) - for k, field := range in.masterFields { + for _, field := range in.masterFields { if !field.IsEdit { continue } @@ -36,6 +36,10 @@ func (l *gCurd) generateWebEditFormItem(ctx context.Context, in *CurdPreviewInpu component string ) + if in.options.Step.IsTreeTable && IsPidName(field.Name) { + field.FormMode = FormModePidTreeSelect + } + switch field.FormMode { case FormModeInput: component = defaultComponent @@ -98,16 +102,45 @@ func (l *gCurd) generateWebEditFormItem(ctx context.Context, in *CurdPreviewInpu case FormModeCitySelector: component = fmt.Sprintf("\n \n ", field.Dc, field.TsName, field.TsName) - + case FormModePidTreeSelect: + component = fmt.Sprintf(` + + `, field.Dc, in.pk.TsName, in.options.Tree.TitleField.TsName) + case FormModeTreeSelect: + component = fmt.Sprintf(` + + `, field.Dc, field.TsName, field.Dc, field.TsName) + case FormModeCascader: + component = fmt.Sprintf(` + + `, field.Dc, field.TsName, field.Dc, field.TsName) default: component = defaultComponent } - if len(in.masterFields) == k { - buffer.WriteString(" " + component) - } else { - buffer.WriteString(" " + component + "\n\n") - } + buffer.WriteString(fmt.Sprintf("%v\n\n", field.FormGridSpan, component)) } return buffer.String() } @@ -119,23 +152,25 @@ func (l *gCurd) generateWebEditScript(ctx context.Context, in *CurdPreviewInput) setupBuffer = bytes.NewBuffer(nil) ) + importBuffer.WriteString(" import { ref, computed } from 'vue';\n") + + // 导入api + var importApiMethod = []string{"Edit", "View"} if in.options.Step.HasMaxSort { - importBuffer.WriteString(" import { ref } from 'vue';\n") - if in.Config.Application.Crud.Templates[in.In.GenTemplate].IsAddon { - importBuffer.WriteString(" import { Edit, MaxSort, View } from '@/api/addons/" + in.In.AddonName + "/" + gstr.LcFirst(in.In.VarName) + "';\n") - } else { - importBuffer.WriteString(" import { Edit, MaxSort, View } from '@/api/" + gstr.LcFirst(in.In.VarName) + "';\n") - } - setupBuffer.WriteString(" function openModal(state: State) {\n adaModalWidth(dialogWidth);\n showModal.value = true;\n loading.value = true;\n\n // 新增\n if (!state || state.id < 1) {\n formValue.value = newState(state);\n MaxSort()\n .then((res) => {\n formValue.value.sort = res.sort;\n })\n .finally(() => {\n loading.value = false;\n });\n return;\n }\n\n // 编辑\n View({ id: state.id })\n .then((res) => {\n formValue.value = res;\n })\n .finally(() => {\n loading.value = false;\n });\n }") - } else { - importBuffer.WriteString(" import { ref } from 'vue';\n") - if in.Config.Application.Crud.Templates[in.In.GenTemplate].IsAddon { - importBuffer.WriteString(" import { Edit, View } from '@/api/addons/" + in.In.AddonName + "/" + gstr.LcFirst(in.In.VarName) + "';\n") - } else { - importBuffer.WriteString(" import { Edit, View } from '@/api/" + gstr.LcFirst(in.In.VarName) + "';\n") - } - setupBuffer.WriteString(" function openModal(state: State) {\n adaModalWidth(dialogWidth);\n showModal.value = true;\n loading.value = true;\n\n // 新增\n if (!state || state.id < 1) {\n formValue.value = newState(state);\n return;\n }\n\n // 编辑\n View({ id: state.id })\n .then((res) => {\n formValue.value = res;\n })\n .finally(() => {\n loading.value = false;\n });\n }") + importApiMethod = append(importApiMethod, "MaxSort") } + importBuffer.WriteString(" import " + ImportWebMethod(importApiMethod) + " from '" + in.options.ImportWebApi + "';\n") + + // 导入model + var importModelMethod = []string{"options", "State", "newState"} + if in.options.Step.IsTreeTable { + importModelMethod = append(importModelMethod, []string{"treeOption", "loadTreeOption"}...) + } + + if in.options.Step.HasRules { + importModelMethod = append(importModelMethod, "rules") + } + importBuffer.WriteString(" import " + ImportWebMethod(importModelMethod) + " from './model';\n") for _, field := range in.masterFields { if !field.IsEdit { diff --git a/server/internal/library/hggen/views/curd_generate_web_index.go b/server/internal/library/hggen/views/curd_generate_web_index.go index 4b6c262..6674765 100644 --- a/server/internal/library/hggen/views/curd_generate_web_index.go +++ b/server/internal/library/hggen/views/curd_generate_web_index.go @@ -6,59 +6,135 @@ package views import ( + "bytes" "context" - "fmt" - "github.com/gogf/gf/v2/frame/g" - "github.com/gogf/gf/v2/text/gstr" -) - -const ( - IndexApiImport = " import {%v } from '@/api/%s';" // 这里将导入的包路径写死了,后面可以优化成根据配置动态读取 - IndexApiAddonsImport = " import {%v } from '@/api/addons/%s/%s';" - IndexIconsImport = " import {%v } from '@vicons/antd';" ) func (l *gCurd) webIndexTplData(ctx context.Context, in *CurdPreviewInput) (g.Map, error) { var ( - data = make(g.Map) - apiImport = []string{" List"} - iconsImport []string + data = make(g.Map) + importBuffer = bytes.NewBuffer(nil) + importVueMethod = []string{"h", "reactive", "ref", "computed"} + importApiMethod = []string{"List"} + importModelMethod = []string{"columns", "schemas"} + importUtilsMethod = []string{"adaTableScrollX"} + importIcons []string + actionWidth int64 = 72 ) // 添加 if in.options.Step.HasAdd { - iconsImport = append(iconsImport, " PlusOutlined") + importIcons = append(importIcons, "PlusOutlined") } // 编辑 - // if in.options.Step.HasEdit { - // } + if in.options.Step.HasEdit { + in.options.Step.ActionColumnWidth += actionWidth + if in.options.Step.IsTreeTable && !in.options.Step.IsOptionTreeTable { + in.options.Step.ActionColumnWidth += actionWidth + } + if in.options.Step.IsOptionTreeTable { + importIcons = append(importIcons, "EditOutlined") + } + } // 导出 if in.options.Step.HasExport { - iconsImport = append(iconsImport, " ExportOutlined") - apiImport = append(apiImport, " Export") + importIcons = append(importIcons, "ExportOutlined") + importApiMethod = append(importApiMethod, "Export") } // 删除 - if in.options.Step.HasDel || in.options.Step.HasBatchDel { - iconsImport = append(iconsImport, " DeleteOutlined") - apiImport = append(apiImport, " Delete") + if in.options.Step.HasDel { + importApiMethod = append(importApiMethod, "Delete") + in.options.Step.ActionColumnWidth += actionWidth } - // 导出 + // 批量删除 + if in.options.Step.HasBatchDel { + importIcons = append(importIcons, "DeleteOutlined") + importApiMethod = append(importApiMethod, "Delete") + } + + // 修改状态 if in.options.Step.HasStatus { - apiImport = append(apiImport, " Status") + importApiMethod = append(importApiMethod, "Status") + importUtilsMethod = append(importUtilsMethod, "getOptionLabel") + importModelMethod = append(importModelMethod, "options") + in.options.Step.ActionColumnWidth += actionWidth } - if in.Config.Application.Crud.Templates[in.In.GenTemplate].IsAddon { - data["apiImport"] = fmt.Sprintf(IndexApiAddonsImport, gstr.Implode(",", apiImport), in.In.AddonName, gstr.LcFirst(in.In.VarName)) - } else { - data["apiImport"] = fmt.Sprintf(IndexApiImport, gstr.Implode(",", apiImport), gstr.LcFirst(in.In.VarName)) + // 更多 + // 查看详情 + if in.options.Step.HasView { + in.options.Step.ActionColumnWidth += actionWidth } - if len(iconsImport) > 0 { - data["iconsImport"] = fmt.Sprintf(IndexIconsImport, gstr.Implode(",", iconsImport)) + + // 展开树 + if in.options.Step.IsTreeTable { + importIcons = append(importIcons, "AlignLeftOutlined") + } + + // 存在字典数据选项 + if in.options.DictOps.Has { + importVueMethod = append(importVueMethod, "onMounted") + importModelMethod = append(importModelMethod, "loadOptions") + } + + // 普通树表 + if in.options.Step.IsTreeTable && !in.options.Step.IsOptionTreeTable { + importUtilsMethod = append(importUtilsMethod, "convertListToTree") + } + + // 选项式树表 + if in.options.Step.IsOptionTreeTable { + importVueMethod = append(importVueMethod, []string{"onMounted", "unref"}...) + importIcons = append(importIcons, []string{"FormOutlined", "SearchOutlined"}...) + importApiMethod = append(importApiMethod, "TreeOption") + importUtilsMethod = append(importUtilsMethod, "getTreeKeys") + importModelMethod = append(importModelMethod, []string{"loadTreeOption", "treeOption", "State"}...) + } + + // 操作按钮宽度最小值 + if in.options.Step.ActionColumnWidth > 0 && in.options.Step.ActionColumnWidth < actionWidth*2 { + in.options.Step.ActionColumnWidth = 100 + } + + // 导入基础包 + importBuffer.WriteString(" import " + ImportWebMethod(importVueMethod) + " from 'vue';\n") + importBuffer.WriteString(" import { useDialog, useMessage } from 'naive-ui';\n") + importBuffer.WriteString(" import { BasicTable, TableAction } from '@/components/Table';\n") + importBuffer.WriteString(" import { BasicForm, useForm } from '@/components/Form/index';\n") + importBuffer.WriteString(" import { usePermission } from '@/hooks/web/usePermission';\n") + + // 导入api + importBuffer.WriteString(" import " + ImportWebMethod(importApiMethod) + " from '" + in.options.ImportWebApi + "';\n") + + // 导入icons + if len(importIcons) > 0 { + importBuffer.WriteString(" import " + ImportWebMethod(importIcons) + " from '@vicons/antd';\n") + } + + // 导入model + if in.options.Step.IsTreeTable { + importModelMethod = append(importModelMethod, "newState") + } + importBuffer.WriteString(" import " + ImportWebMethod(importModelMethod) + " from './model';\n") + + // 导入utils + if len(importUtilsMethod) > 0 { + importBuffer.WriteString(" import " + ImportWebMethod(importUtilsMethod) + " from '@/utils/hotgo';\n") + } + + // 导入edit组件 + if in.options.Step.HasEdit { + importBuffer.WriteString(" import Edit from './edit.vue';\n") + } + + // 导入view组件 + if in.options.Step.HasView { + importBuffer.WriteString(" import View from './view.vue';\n") } // 没有需要查询的字段则隐藏搜索表单 @@ -83,5 +159,6 @@ func (l *gCurd) webIndexTplData(ctx context.Context, in *CurdPreviewInput) (g.Ma } } data["isSearchForm"] = isSearchForm + data["import"] = importBuffer.String() return data, nil } diff --git a/server/internal/library/hggen/views/curd_generate_web_model.go b/server/internal/library/hggen/views/curd_generate_web_model.go index 33b87e5..6331050 100644 --- a/server/internal/library/hggen/views/curd_generate_web_model.go +++ b/server/internal/library/hggen/views/curd_generate_web_model.go @@ -12,61 +12,148 @@ import ( "fmt" "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/util/gconv" "hotgo/internal/library/dict" "hotgo/internal/model/input/sysin" "hotgo/utility/convert" ) -const ( - ModelLoadOptionsTemplate = "async function loadOptions() {\n options.value = await Dicts({\n types: [\n %v ],\n });\n for (const item of schemas.value) {\n switch (item.field) {\n%v }\n }\n}\n\nawait loadOptions();" -) +type StateItem struct { + Name string + DefaultValue interface{} + Dc string +} func (l *gCurd) webModelTplData(ctx context.Context, in *CurdPreviewInput) (data g.Map, err error) { data = make(g.Map) - data["state"] = l.generateWebModelState(ctx, in) + data["stateItems"] = l.generateWebModelStateItems(ctx, in) data["rules"] = l.generateWebModelRules(ctx, in) data["formSchema"] = l.generateWebModelFormSchema(ctx, in) if data["columns"], err = l.generateWebModelColumns(ctx, in); err != nil { return nil, err } + + // 根据表单生成情况,按需导包 + data["import"] = l.generateWebModelImport(ctx, in) return } -func (l *gCurd) generateWebModelState(ctx context.Context, in *CurdPreviewInput) string { - buffer := bytes.NewBuffer(nil) - buffer.WriteString("export class State {\n") +func (l *gCurd) generateWebModelImport(ctx context.Context, in *CurdPreviewInput) string { + importBuffer := bytes.NewBuffer(nil) + + importBuffer.WriteString("import { h, ref } from 'vue';\n") + + // 导入基础组件 + if len(in.options.Step.ImportModel.NaiveUI) > 0 { + importBuffer.WriteString("import " + ImportWebMethod(in.options.Step.ImportModel.NaiveUI) + " from 'naive-ui';\n") + } + + importBuffer.WriteString("import { cloneDeep } from 'lodash-es';\n") + + // 导入表单搜索 + if in.options.Step.HasSearchForm { + importBuffer.WriteString("import { FormSchema } from '@/components/Form';\n") + } + + // 导入字典选项 + if in.options.DictOps.Has { + importBuffer.WriteString("import { Dicts } from '@/api/dict/dict';\n") + } + + // 导入工具类 + if len(in.options.Step.ImportModel.UtilsIs) > 0 { + importBuffer.WriteString("import " + ImportWebMethod(in.options.Step.ImportModel.UtilsIs) + " from '@/utils/is';\n") + } + + if len(in.options.Step.ImportModel.UtilsUrl) > 0 { + importBuffer.WriteString("import " + ImportWebMethod(in.options.Step.ImportModel.UtilsUrl) + " from '@/utils/urlUtils';\n") + } + + if len(in.options.Step.ImportModel.UtilsDate) > 0 { + importBuffer.WriteString("import " + ImportWebMethod(in.options.Step.ImportModel.UtilsDate) + " from '@/utils/dateUtil';\n") + } + + if in.options.Step.HasRulesValidator { + importBuffer.WriteString("import { validate } from '@/utils/validateUtil';\n") + } + + if len(in.options.Step.ImportModel.UtilsHotGo) > 0 { + importBuffer.WriteString("import " + ImportWebMethod(in.options.Step.ImportModel.UtilsHotGo) + " from '@/utils/hotgo';\n") + } + + if len(in.options.Step.ImportModel.UtilsIndex) > 0 { + importBuffer.WriteString("import " + ImportWebMethod(in.options.Step.ImportModel.UtilsIndex) + " from '@/utils';\n") + } + + // 导入api + var importApiMethod []string + if in.options.Step.HasSwitch { + importApiMethod = append(importApiMethod, "Switch") + } + if in.options.Step.IsTreeTable { + importApiMethod = append(importApiMethod, "TreeOption") + } + if len(importApiMethod) > 0 { + importBuffer.WriteString("import " + ImportWebMethod(importApiMethod) + " from '" + in.options.ImportWebApi + "';\n") + } + + if in.options.Step.HasSwitch { + importBuffer.WriteString("import { usePermission } from '@/hooks/web/usePermission';\n") + importBuffer.WriteString("const { hasPermission } = usePermission();\n") + importBuffer.WriteString("const $message = window['$message'];\n") + } + return importBuffer.String() +} + +func (l *gCurd) generateWebModelStateItems(ctx context.Context, in *CurdPreviewInput) (items []*StateItem) { for _, field := range in.masterFields { var value = field.DefaultValue if value == nil { value = "null" } if value == "" { - value = "''" + value = `''` } + + // 选项组件默认值调整 + if gconv.Int(value) == 0 && IsSelectFormMode(field.FormMode) { + value = "null" + } + if field.Name == "status" { value = 1 } - if field.FormMode == "Switch" { + if field.FormMode == FormModeSwitch { value = 2 } - if field.FormMode == "InputDynamic" { + if field.FormMode == FormModeInputDynamic { value = "[]" } - buffer.WriteString(fmt.Sprintf(" public %s = %v; // %s\n", field.TsName, value, field.Dc)) + items = append(items, &StateItem{ + Name: field.TsName, + DefaultValue: value, + Dc: field.Dc, + }) + + // 查询用户摘要 + if field.IsList && in.options.Step.HasHookMemberSummary && IsMemberSummaryField(field.Name) { + items = append(items, &StateItem{ + Name: field.TsName + "Summa?: null | MemberSumma", + DefaultValue: "null", + Dc: field.Dc + "摘要信息", + }) + } } - buffer.WriteString("\n constructor(state?: Partial) {\n if (state) {\n Object.assign(this, state);\n }\n }") - buffer.WriteString("}") - return buffer.String() + return } -func (l *gCurd) generateWebModelDictOptions(ctx context.Context, in *CurdPreviewInput) (g.Map, error) { +func (l *gCurd) generateWebModelDictOptions(ctx context.Context, in *CurdPreviewInput) error { type DictType struct { Id int64 `json:"id"` Type string `json:"type"` } var ( - options = make(g.Map) dictTypeIds []int64 dictTypeList []*DictType builtinDictTypeIds []int64 @@ -87,8 +174,7 @@ func (l *gCurd) generateWebModelDictOptions(ctx context.Context, in *CurdPreview builtinDictTypeIds = convert.UniqueSlice(builtinDictTypeIds) if len(dictTypeIds) == 0 && len(builtinDictTypeIds) == 0 { - options["has"] = false - return options, nil + return nil } if len(dictTypeIds) > 0 { @@ -97,71 +183,52 @@ func (l *gCurd) generateWebModelDictOptions(ctx context.Context, in *CurdPreview WhereIn("id", dictTypeIds). Scan(&dictTypeList) if err != nil { - return nil, err + return err } } if len(builtinDictTypeIds) > 0 { for _, id := range builtinDictTypeIds { - opts, err := dict.GetOptionsById(ctx, id) + typ, err := dict.GetTypeById(ctx, id) if err != nil && !errors.Is(err, dict.NotExistKeyError) { - return nil, err + return err } - - if len(opts) > 0 { + if len(typ) > 0 { row := new(DictType) row.Id = id - row.Type = opts[0].Type + row.Type = typ builtinDictTypeList = append(builtinDictTypeList, row) } } } if len(dictTypeList) == 0 && len(builtinDictTypeList) == 0 { - options["has"] = false - return options, nil + return nil } if len(builtinDictTypeList) > 0 { dictTypeList = append(dictTypeList, builtinDictTypeList...) } - options["has"] = true + in.options.DictOps.Has = true - var ( - awaitLoadOptions string - switchLoadOptions string - ) - - interfaceOptionsBuffer := bytes.NewBuffer(nil) - interfaceOptionsBuffer.WriteString("export interface IOptions extends Options {\n") - constOptionsBuffer := bytes.NewBuffer(nil) - constOptionsBuffer.WriteString("export const options = ref({\n") + // 导入选项包 + in.options.Step.ImportModel.UtilsHotGo = append(in.options.Step.ImportModel.UtilsHotGo, "Option") for _, v := range dictTypeList { // 字段映射字典 for _, field := range in.masterFields { if field.DictType != 0 && v.Id == field.DictType { in.options.dictMap[field.TsName] = v.Type - switchLoadOptions = fmt.Sprintf("%s case '%s':\n item.componentProps.options = options.value.%s;\n break;\n", switchLoadOptions, field.TsName, v.Type) + in.options.DictOps.Schemas = append(in.options.DictOps.Schemas, &OptionsSchemasField{ + Field: field.TsName, + Type: v.Type, + }) } } - - awaitLoadOptions = fmt.Sprintf("%s '%s',\n", awaitLoadOptions, v.Type) - interfaceOptionsBuffer.WriteString(" " + v.Type + ": Option[]; \n") - constOptionsBuffer.WriteString(" " + v.Type + ": [],\n") + in.options.DictOps.Types = append(in.options.DictOps.Types, v.Type) } - - interfaceOptionsBuffer.WriteString("};\n") - constOptionsBuffer.WriteString("});\n") - - loadOptionsBuffer := bytes.NewBuffer(nil) - loadOptionsBuffer.WriteString(fmt.Sprintf(ModelLoadOptionsTemplate, awaitLoadOptions, switchLoadOptions)) - - options["interface"] = interfaceOptionsBuffer.String() - options["const"] = constOptionsBuffer.String() - options["load"] = loadOptionsBuffer.String() - return options, nil + return nil } func (l *gCurd) generateWebModelRules(ctx context.Context, in *CurdPreviewInput) string { @@ -172,9 +239,11 @@ func (l *gCurd) generateWebModelRules(ctx context.Context, in *CurdPreviewInput) continue } + in.options.Step.HasRules = true if field.FormRole == "" || field.FormRole == FormRoleNone || field.FormRole == "required" { buffer.WriteString(fmt.Sprintf(" %s: {\n required: %v,\n trigger: ['blur', 'input'],\n type: '%s',\n message: '请输入%s',\n },\n", field.TsName, field.Required, field.TsType, field.Dc)) } else { + in.options.Step.HasRulesValidator = true buffer.WriteString(fmt.Sprintf(" %s: {\n required: %v,\n trigger: ['blur', 'input'],\n type: '%s',\n validator: validate.%v,\n },\n", field.TsName, field.Required, field.TsType, field.FormRole)) } } @@ -187,7 +256,7 @@ func (l *gCurd) generateWebModelFormSchema(ctx context.Context, in *CurdPreviewI buffer.WriteString("export const schemas = ref([\n") // 主表 - l.generateWebModelFormSchemaEach(buffer, in.masterFields) + l.generateWebModelFormSchemaEach(buffer, in.masterFields, in) // 关联表 if len(in.options.Join) > 0 { @@ -195,7 +264,7 @@ func (l *gCurd) generateWebModelFormSchema(ctx context.Context, in *CurdPreviewI if !isEffectiveJoin(v) { continue } - l.generateWebModelFormSchemaEach(buffer, v.Columns) + l.generateWebModelFormSchemaEach(buffer, v.Columns, in) } } @@ -203,11 +272,18 @@ func (l *gCurd) generateWebModelFormSchema(ctx context.Context, in *CurdPreviewI return buffer.String() } -func (l *gCurd) generateWebModelFormSchemaEach(buffer *bytes.Buffer, fields []*sysin.GenCodesColumnListModel) { +func (l *gCurd) generateWebModelFormSchemaEach(buffer *bytes.Buffer, fields []*sysin.GenCodesColumnListModel, in *CurdPreviewInput) { for _, field := range fields { if !field.IsQuery { continue } + in.options.Step.HasSearchForm = true + + // 查询用户摘要 + if field.IsQuery && in.options.Step.HasQueryMemberSummary && IsMemberSummaryField(field.Name) { + buffer.WriteString(fmt.Sprintf(" {\n field: '%s',\n component: '%s',\n label: '%s',\n componentProps: {\n placeholder: '请输入ID|用户名|姓名|手机号',\n onUpdateValue: (e: any) => {\n console.log(e);\n },\n },\n },\n", field.TsName, "NInput", field.Dc)) + continue + } var ( defaultComponent = fmt.Sprintf(" {\n field: '%s',\n component: '%s',\n label: '%s',\n componentProps: {\n placeholder: '请输入%s',\n onUpdateValue: (e: any) => {\n console.log(e);\n },\n },\n },\n", field.TsName, "NInput", field.Dc, field.Dc) @@ -224,15 +300,19 @@ func (l *gCurd) generateWebModelFormSchemaEach(buffer *bytes.Buffer, fields []*s case FormModeDate: component = fmt.Sprintf(" {\n field: '%s',\n component: '%s',\n label: '%s',\n componentProps: {\n type: '%s',\n clearable: true,\n shortcuts: %s,\n onUpdateValue: (e: any) => {\n console.log(e);\n },\n },\n },\n", field.TsName, "NDatePicker", field.Dc, "date", "defShortcuts()") + in.options.Step.ImportModel.UtilsDate = append(in.options.Step.ImportModel.UtilsDate, "defShortcuts") case FormModeDateRange: component = fmt.Sprintf(" {\n field: '%s',\n component: '%s',\n label: '%s',\n componentProps: {\n type: '%s',\n clearable: true,\n shortcuts: %s,\n onUpdateValue: (e: any) => {\n console.log(e);\n },\n },\n },\n", field.TsName, "NDatePicker", field.Dc, "daterange", "defRangeShortcuts()") + in.options.Step.ImportModel.UtilsDate = append(in.options.Step.ImportModel.UtilsDate, "defRangeShortcuts") case FormModeTime: component = fmt.Sprintf(" {\n field: '%s',\n component: '%s',\n label: '%s',\n componentProps: {\n type: '%s',\n clearable: true,\n shortcuts: %s,\n onUpdateValue: (e: any) => {\n console.log(e);\n },\n },\n },\n", field.TsName, "NDatePicker", field.Dc, "datetime", "defShortcuts()") + in.options.Step.ImportModel.UtilsDate = append(in.options.Step.ImportModel.UtilsDate, "defShortcuts") case FormModeTimeRange: component = fmt.Sprintf(" {\n field: '%s',\n component: '%s',\n label: '%s',\n componentProps: {\n type: '%s',\n clearable: true,\n shortcuts: %s,\n onUpdateValue: (e: any) => {\n console.log(e);\n },\n },\n },\n", field.TsName, "NDatePicker", field.Dc, "datetimerange", "defRangeShortcuts()") + in.options.Step.ImportModel.UtilsDate = append(in.options.Step.ImportModel.UtilsDate, "defRangeShortcuts") case FormModeSwitch: fallthrough @@ -287,14 +367,22 @@ func (l *gCurd) generateWebModelColumnsEach(buffer *bytes.Buffer, in *CurdPrevie continue } var ( - defaultComponent = fmt.Sprintf(" {\n title: '%s',\n key: '%s',\n },\n", field.Dc, field.TsName) + defaultComponent = fmt.Sprintf(" {\n title: '%s',\n key: '%s',\n align: '%v',\n width: %v,\n },\n", field.Dc, field.TsName, field.Align, field.Width) component string ) + // 查询用户摘要 + if in.options.Step.HasHookMemberSummary && IsMemberSummaryField(field.Name) { + buffer.WriteString(fmt.Sprintf(" {\n title: '%v',\n key: '%v',\n align: '%v',\n width: %v,\n render(row) {\n return renderPopoverMemberSumma(row.%vSumma);\n },\n },\n", field.Dc, field.TsName, field.Align, field.Width, field.TsName)) + in.options.Step.ImportModel.UtilsIndex = append(in.options.Step.ImportModel.UtilsIndex, []string{"renderPopoverMemberSumma", "MemberSumma"}...) + continue + } + // 这里根据编辑表单组件来进行推断,如果没有则使用默认input,这可能会导致和查询条件所需参数不符的情况 switch field.FormMode { case FormModeDate: - component = fmt.Sprintf(" {\n title: '%s',\n key: '%s',\n render(row) {\n return formatToDate(row.%s);\n },\n },\n", field.Dc, field.TsName, field.TsName) + component = fmt.Sprintf(" {\n title: '%s',\n key: '%s',\n align: '%v',\n width: %v,\n render(row) {\n return formatToDate(row.%s);\n },\n },\n", field.Dc, field.TsName, field.Align, field.Width, field.TsName) + in.options.Step.ImportModel.UtilsDate = append(in.options.Step.ImportModel.UtilsDate, "formatToDate") case FormModeRadio: fallthrough @@ -303,32 +391,50 @@ func (l *gCurd) generateWebModelColumnsEach(buffer *bytes.Buffer, in *CurdPrevie err = gerror.Newf("设置单选下拉框选项时,必须选择字典类型,字段名称:%v", field.Name) return } - component = fmt.Sprintf(" {\n title: '%s',\n key: '%s',\n render(row) {\n if (isNullObject(row.%s)) {\n return ``;\n }\n return h(\n NTag,\n {\n style: {\n marginRight: '6px',\n },\n type: getOptionTag(options.value.%s, row.%s),\n bordered: false,\n },\n {\n default: () => getOptionLabel(options.value.%s, row.%s),\n }\n );\n },\n },\n", field.Dc, field.TsName, field.TsName, in.options.dictMap[field.TsName], field.TsName, in.options.dictMap[field.TsName], field.TsName) + component = fmt.Sprintf(" {\n title: '%s',\n key: '%s',\n align: '%v',\n width: %v,\n render(row) {\n if (isNullObject(row.%s)) {\n return ``;\n }\n return h(\n NTag,\n {\n style: {\n marginRight: '6px',\n },\n type: getOptionTag(options.value.%s, row.%s),\n bordered: false,\n },\n {\n default: () => getOptionLabel(options.value.%s, row.%s),\n }\n );\n },\n },\n", field.Dc, field.TsName, field.Align, field.Width, field.TsName, in.options.dictMap[field.TsName], field.TsName, in.options.dictMap[field.TsName], field.TsName) + in.options.Step.ImportModel.NaiveUI = append(in.options.Step.ImportModel.NaiveUI, "NTag") + in.options.Step.ImportModel.UtilsIs = append(in.options.Step.ImportModel.UtilsIs, "isNullObject") + in.options.Step.ImportModel.UtilsHotGo = append(in.options.Step.ImportModel.UtilsHotGo, []string{"getOptionLabel", "getOptionTag"}...) case FormModeSelectMultiple: if g.IsEmpty(in.options.dictMap[field.TsName]) { err = gerror.Newf("设置多选下拉框选项时,必须选择字典类型,字段名称:%v", field.Name) return } - component = fmt.Sprintf(" {\n title: '%s',\n key: '%s',\n render(row) {\n if (isNullObject(row.%s) || !isArray(row.%s)) {\n return ``;\n }\n return row.%s.map((tagKey) => {\n return h(\n NTag,\n {\n style: {\n marginRight: '6px',\n },\n type: getOptionTag(options.value.%s, tagKey),\n bordered: false,\n },\n {\n default: () => getOptionLabel(options.value.%s, tagKey),\n }\n );\n });\n },\n },\n", field.Dc, field.TsName, field.TsName, field.TsName, field.TsName, in.options.dictMap[field.TsName], in.options.dictMap[field.TsName]) + component = fmt.Sprintf(" {\n title: '%s',\n key: '%s',\n align: '%v',\n width: %v,\n render(row) {\n if (isNullObject(row.%s) || !isArray(row.%s)) {\n return ``;\n }\n return row.%s.map((tagKey) => {\n return h(\n NTag,\n {\n style: {\n marginRight: '6px',\n },\n type: getOptionTag(options.value.%s, tagKey),\n bordered: false,\n },\n {\n default: () => getOptionLabel(options.value.%s, tagKey),\n }\n );\n });\n },\n },\n", field.Dc, field.TsName, field.Align, field.Width, field.TsName, field.TsName, field.TsName, in.options.dictMap[field.TsName], in.options.dictMap[field.TsName]) + in.options.Step.ImportModel.NaiveUI = append(in.options.Step.ImportModel.NaiveUI, "NTag") + in.options.Step.ImportModel.UtilsIs = append(in.options.Step.ImportModel.UtilsIs, "isNullObject") + in.options.Step.ImportModel.UtilsHotGo = append(in.options.Step.ImportModel.UtilsHotGo, []string{"getOptionLabel", "getOptionTag"}...) case FormModeUploadImage: - component = fmt.Sprintf(" {\n title: '%s',\n key: '%s',\n render(row) {\n return h(%s, {\n width: 32,\n height: 32,\n src: row.%s,\n onError: errorImg,\n style: {\n width: '32px',\n height: '32px',\n 'max-width': '100%%',\n 'max-height': '100%%',\n },\n });\n },\n },\n", field.Dc, field.TsName, "NImage", field.TsName) + component = fmt.Sprintf(" {\n title: '%s',\n key: '%s',\n align: '%v',\n width: %v,\n render(row) {\n return h(%s, {\n width: 32,\n height: 32,\n src: row.%s,\n fallbackSrc: errorImg,\n onError: errorImg,\n style: {\n width: '32px',\n height: '32px',\n 'max-width': '100%%',\n 'max-height': '100%%',\n },\n });\n },\n },\n", field.Dc, field.TsName, field.Align, field.Width, "NImage", field.TsName) + in.options.Step.ImportModel.NaiveUI = append(in.options.Step.ImportModel.NaiveUI, "NImage") + in.options.Step.ImportModel.UtilsHotGo = append(in.options.Step.ImportModel.UtilsHotGo, "errorImg") case FormModeUploadImages: - component = fmt.Sprintf(" {\n title: '%s',\n key: '%s',\n render(row) {\n if (isNullObject(row.%s)) {\n return ``;\n }\n return row.%s.map((image) => {\n return h(%s, {\n width: 32,\n height: 32,\n src: image,\n onError: errorImg,\n style: {\n width: '32px',\n height: '32px',\n 'max-width': '100%%',\n 'max-height': '100%%',\n 'margin-left': '2px',\n },\n });\n });\n },\n },\n", field.Dc, field.TsName, field.TsName, field.TsName, "NImage") + component = fmt.Sprintf(" {\n title: '%s',\n key: '%s',\n align: '%v',\n width: %v,\n render(row) {\n if (isNullObject(row.%s)) {\n return ``;\n }\n return row.%s.map((image) => {\n return h(%s, {\n width: 32,\n height: 32,\n src: image,\n onError: errorImg,\n style: {\n width: '32px',\n height: '32px',\n 'max-width': '100%%',\n 'max-height': '100%%',\n 'margin-left': '2px',\n },\n });\n });\n },\n },\n", field.Dc, field.TsName, field.Align, field.Width, field.TsName, field.TsName, "NImage") + in.options.Step.ImportModel.NaiveUI = append(in.options.Step.ImportModel.NaiveUI, "NImage") + in.options.Step.ImportModel.UtilsIs = append(in.options.Step.ImportModel.UtilsIs, "isArray") + in.options.Step.ImportModel.UtilsHotGo = append(in.options.Step.ImportModel.UtilsHotGo, "errorImg") case FormModeUploadFile: - component = fmt.Sprintf(" {\n title: '%s',\n key: '%s',\n render(row) {\n if (row.%s === '') {\n return ``;\n }\n return h(\n %s,\n {\n size: 'small',\n },\n {\n default: () => getFileExt(row.%s),\n }\n );\n },\n },\n", field.Dc, field.TsName, field.TsName, "NAvatar", field.TsName) + component = fmt.Sprintf(" {\n title: '%s',\n key: '%s',\n align: '%v',\n width: %v,\n render(row) {\n if (row.%s === '') {\n return ``;\n }\n return h(\n %s,\n {\n size: 'small',\n },\n {\n default: () => getFileExt(row.%s),\n }\n );\n },\n },\n", field.Dc, field.TsName, field.Align, field.Width, field.TsName, "NAvatar", field.TsName) + in.options.Step.ImportModel.NaiveUI = append(in.options.Step.ImportModel.NaiveUI, "NAvatar") + in.options.Step.ImportModel.UtilsUrl = append(in.options.Step.ImportModel.UtilsUrl, "getFileExt") case FormModeUploadFiles: - component = fmt.Sprintf(" {\n title: '%s',\n key: '%s',\n render(row) {\n if (isNullObject(row.%s)) {\n return ``;\n }\n return row.%s.map((attachfile) => {\n return h(\n %s,\n {\n size: 'small',\n style: {\n 'margin-left': '2px',\n },\n },\n {\n default: () => getFileExt(attachfile),\n }\n );\n });\n },\n },\n", field.Dc, field.TsName, field.TsName, field.TsName, "NAvatar") + component = fmt.Sprintf(" {\n title: '%s',\n key: '%s',\n align: '%v',\n width: %v,\n render(row) {\n if (isNullObject(row.%s)) {\n return ``;\n }\n return row.%s.map((attachfile) => {\n return h(\n %s,\n {\n size: 'small',\n style: {\n 'margin-left': '2px',\n },\n },\n {\n default: () => getFileExt(attachfile),\n }\n );\n });\n },\n },\n", field.Dc, field.TsName, field.Align, field.Width, field.TsName, field.TsName, "NAvatar") + in.options.Step.ImportModel.NaiveUI = append(in.options.Step.ImportModel.NaiveUI, "NAvatar") + in.options.Step.ImportModel.UtilsIs = append(in.options.Step.ImportModel.UtilsIs, "isNullObject") + in.options.Step.ImportModel.UtilsUrl = append(in.options.Step.ImportModel.UtilsUrl, "getFileExt") case FormModeSwitch: - component = fmt.Sprintf(" {\n title: '%s',\n key: '%s',\n width: 100,\n render(row) {\n return h(%s, {\n value: row.%s === 1,\n checked: '开启',\n unchecked: '关闭',\n disabled: !hasPermission(['%s']),\n onUpdateValue: function (e) {\n console.log('onUpdateValue e:' + JSON.stringify(e));\n row.%s = e ? 1 : 2;\n Switch({ %s: row.%s, key: '%s', value: row.%s }).then((_res) => {\n $message.success('操作成功');\n });\n },\n });\n },\n },\n", field.Dc, field.TsName, "NSwitch", field.TsName, "/"+in.options.ApiPrefix+"/switch", field.TsName, in.pk.TsName, in.pk.TsName, convert.CamelCaseToUnderline(field.TsName), field.TsName) + component = fmt.Sprintf(" {\n title: '%s',\n key: '%s',\n align: '%v',\n width: %v,\n render(row) {\n return h(%s, {\n value: row.%s === 1,\n checked: '开启',\n unchecked: '关闭',\n disabled: !hasPermission(['%s']),\n onUpdateValue: function (e) {\n console.log('onUpdateValue e:' + JSON.stringify(e));\n row.%s = e ? 1 : 2;\n Switch({ %s: row.%s, key: '%s', value: row.%s }).then((_res) => {\n $message.success('操作成功');\n });\n },\n });\n },\n },\n", field.Dc, field.TsName, field.Align, field.Width, "NSwitch", field.TsName, "/"+in.options.ApiPrefix+"/switch", field.TsName, in.pk.TsName, in.pk.TsName, convert.CamelCaseToUnderline(field.TsName), field.TsName) + in.options.Step.ImportModel.NaiveUI = append(in.options.Step.ImportModel.NaiveUI, "NSwitch") case FormModeRate: - component = fmt.Sprintf(" {\n title: '%s',\n key: '%s',\n render(row) {\n return h(%s, {\n allowHalf: true,\n readonly: true,\n defaultValue: row.%s,\n });\n },\n },\n", field.Dc, field.TsName, "NRate", field.TsName) + component = fmt.Sprintf(" {\n title: '%s',\n key: '%s',\n align: '%v',\n width: %v,\n render(row) {\n return h(%s, {\n allowHalf: true,\n readonly: true,\n defaultValue: row.%s,\n });\n },\n },\n", field.Dc, field.TsName, field.Align, field.Width, "NRate", field.TsName) + in.options.Step.ImportModel.NaiveUI = append(in.options.Step.ImportModel.NaiveUI, "NRate") default: component = defaultComponent @@ -336,6 +442,5 @@ func (l *gCurd) generateWebModelColumnsEach(buffer *bytes.Buffer, in *CurdPrevie buffer.WriteString(component) } - return } diff --git a/server/internal/library/hggen/views/curd_generate_web_view.go b/server/internal/library/hggen/views/curd_generate_web_view.go index 1d9166a..39363a7 100644 --- a/server/internal/library/hggen/views/curd_generate_web_view.go +++ b/server/internal/library/hggen/views/curd_generate_web_view.go @@ -44,25 +44,25 @@ func (l *gCurd) generateWebViewItem(ctx context.Context, in *CurdPreviewInput) s component = defaultComponent case FormModeRadio, FormModeSelect: - component = fmt.Sprintf("\n {{ getOptionLabel(options.%s, formValue?.%s) }}\n ", field.Dc, in.options.dictMap[field.TsName], field.TsName, in.options.dictMap[field.TsName], field.TsName) + component = fmt.Sprintf("\n {{ getOptionLabel(options.%s, formValue?.%s) }}\n ", field.Dc, in.options.dictMap[field.TsName], field.TsName, in.options.dictMap[field.TsName], field.TsName) case FormModeCheckbox, FormModeSelectMultiple: - component = fmt.Sprintf("\n \n ", field.Dc, field.TsName, in.options.dictMap[field.TsName], in.options.dictMap[field.TsName]) + component = fmt.Sprintf("\n \n ", field.Dc, field.TsName, in.options.dictMap[field.TsName], in.options.dictMap[field.TsName]) case FormModeUploadImage: - component = fmt.Sprintf("\n \n ", field.Dc, field.TsName) + component = fmt.Sprintf("\n \n ", field.Dc, field.TsName) case FormModeUploadImages: component = fmt.Sprintf("\n \n \n \n \n \n \n \n \n ", field.Dc, field.TsName) case FormModeUploadFile: - component = fmt.Sprintf("\n \n \n

\n
\n
\n {{ getFileExt(formValue.%s) }}\n
\n
\n
\n \n ", field.Dc, field.TsName, field.TsName, field.TsName) + component = fmt.Sprintf("\n \n
\n
\n
\n
\n {{ getFileExt(formValue.%s) }}\n
\n
\n
\n
\n
", field.Dc, field.TsName, field.TsName, field.TsName) case FormModeUploadFiles: component = fmt.Sprintf("\n \n
\n \n \n
\n
\n {{\n getFileExt(item)\n }}\n
\n
\n
\n \n \n
", field.Dc, field.TsName) case FormModeSwitch: - component = fmt.Sprintf("\n ", field.Dc, field.TsName) + component = fmt.Sprintf("\n ", field.Dc, field.TsName) case FormModeRate: component = fmt.Sprintf("", field.Dc, field.TsName) diff --git a/server/internal/library/hggen/views/gohtml/consts.go b/server/internal/library/hggen/views/gohtml/consts.go new file mode 100644 index 0000000..625118c --- /dev/null +++ b/server/internal/library/hggen/views/gohtml/consts.go @@ -0,0 +1,7 @@ +package gohtml + +const ( + defaultIndentString = " " + startIndent = 0 + defaultLastElement = "" +) diff --git a/server/internal/library/hggen/views/gohtml/doc.go b/server/internal/library/hggen/views/gohtml/doc.go new file mode 100644 index 0000000..f787ed3 --- /dev/null +++ b/server/internal/library/hggen/views/gohtml/doc.go @@ -0,0 +1,2 @@ +// Package gohtml provides an HTML formatting function. +package gohtml diff --git a/server/internal/library/hggen/views/gohtml/element.go b/server/internal/library/hggen/views/gohtml/element.go new file mode 100644 index 0000000..a41be68 --- /dev/null +++ b/server/internal/library/hggen/views/gohtml/element.go @@ -0,0 +1,7 @@ +package gohtml + +// An element represents an HTML element. +type element interface { + isInline() bool + write(*formattedBuffer, bool) bool +} diff --git a/server/internal/library/hggen/views/gohtml/formatter.go b/server/internal/library/hggen/views/gohtml/formatter.go new file mode 100644 index 0000000..baed679 --- /dev/null +++ b/server/internal/library/hggen/views/gohtml/formatter.go @@ -0,0 +1,37 @@ +package gohtml + +import ( + "bytes" + "strconv" + "strings" +) + +// Format parses the input HTML string, formats it and returns the result. +func Format(s string) string { + return parse(strings.NewReader(s)).html() +} + +// FormatBytes parses input HTML as bytes, formats it and returns the result. +func FormatBytes(b []byte) []byte { + return parse(bytes.NewReader(b)).bytes() +} + +// Format parses the input HTML string, formats it and returns the result with line no. +func FormatWithLineNo(s string) string { + return AddLineNo(Format(s)) +} + +func AddLineNo(s string) string { + lines := strings.Split(s, "\n") + maxLineNoStrLen := len(strconv.Itoa(len(lines))) + bf := &bytes.Buffer{} + for i, line := range lines { + lineNoStr := strconv.Itoa(i + 1) + if i > 0 { + bf.WriteString("\n") + } + bf.WriteString(strings.Repeat(" ", maxLineNoStrLen-len(lineNoStr)) + lineNoStr + " " + line) + } + return bf.String() + +} diff --git a/server/internal/library/hggen/views/gohtml/html_document.go b/server/internal/library/hggen/views/gohtml/html_document.go new file mode 100644 index 0000000..3d9e6ea --- /dev/null +++ b/server/internal/library/hggen/views/gohtml/html_document.go @@ -0,0 +1,54 @@ +package gohtml + +import ( + "bytes" + "regexp" +) + +// Column to wrap lines to (disabled by default) +var LineWrapColumn = 0 + +// Maxmimum characters a long word can extend past LineWrapColumn without wrapping +var LineWrapMaxSpillover = 5 + +// An htmlDocument represents an HTML document. +type htmlDocument struct { + elements []element +} + +// html generates an HTML source code and returns it. +func (htmlDoc *htmlDocument) html() string { + str := string(htmlDoc.bytes()) + str = replaceMultipleNewlinesWithSpaceAndTabs(str) + return str +} + +func replaceMultipleNewlinesWithSpaceAndTabs(input string) string { + re := regexp.MustCompile(`\n\s*\n+`) + formattedString := re.ReplaceAllString(input, "\n") + return formattedString +} + +// bytes reads from htmlDocument's internal array of elements and returns HTML source code +func (htmlDoc *htmlDocument) bytes() []byte { + bf := &formattedBuffer{ + buffer: &bytes.Buffer{}, + + lineWrapColumn: LineWrapColumn, + lineWrapMaxSpillover: LineWrapMaxSpillover, + + indentString: defaultIndentString, + indentLevel: startIndent, + } + + isPreviousNodeInline := true + for _, child := range htmlDoc.elements { + isPreviousNodeInline = child.write(bf, isPreviousNodeInline) + } + return bf.buffer.Bytes() +} + +// append appends an element to the htmlDocument. +func (htmlDoc *htmlDocument) append(e element) { + htmlDoc.elements = append(htmlDoc.elements, e) +} diff --git a/server/internal/library/hggen/views/gohtml/parser.go b/server/internal/library/hggen/views/gohtml/parser.go new file mode 100644 index 0000000..3442ad5 --- /dev/null +++ b/server/internal/library/hggen/views/gohtml/parser.go @@ -0,0 +1,127 @@ +package gohtml + +import ( + "golang.org/x/net/html" + "io" + "regexp" + "strings" +) + +// parse parses a stirng and converts it into an html. +func parse(r io.Reader) *htmlDocument { + htmlDoc := &htmlDocument{} + tokenizer := html.NewTokenizer(r) + for { + if errorToken, _, _ := parseToken(tokenizer, htmlDoc, nil); errorToken { + break + } + } + return htmlDoc +} + +// Function that identifies which tags will be treated as containing preformatted +// content. Such tags will have the formatting of all its contents preserved +// unchanged. +// The opening tag html.Token is passed to this function. +// By default, only
 and 
     
       
-        
-          {{ view.tag.label }}
-          
-          {{ view.path }}
-        
+        
+          
+            {{ view.tag.label }}
+            
+            {{ view.path }}
+          
+          复制本页代码
+        
         
-          
+          
         
       
     
@@ -18,9 +30,12 @@
 
 
 
 
 
diff --git a/web/src/views/develop/code/components/SetFuncDict.vue b/web/src/views/develop/code/components/SetFuncDict.vue
new file mode 100644
index 0000000..2573335
--- /dev/null
+++ b/web/src/views/develop/code/components/SetFuncDict.vue
@@ -0,0 +1,133 @@
+
+
+
diff --git a/web/src/views/develop/code/components/model.ts b/web/src/views/develop/code/components/model.ts
index 0501a18..f595172 100644
--- a/web/src/views/develop/code/components/model.ts
+++ b/web/src/views/develop/code/components/model.ts
@@ -1,4 +1,5 @@
 import { cloneDeep } from 'lodash-es';
+import { isJsonString } from '@/utils/is';
 
 export const genFileObj = {
   meth: 1,
@@ -24,7 +25,7 @@ export const genInfoObj = {
   varName: '',
   options: {
     headOps: ['add', 'batchDel', 'export'],
-    columnOps: ['edit', 'del', 'view', 'status', 'switch', 'check'],
+    columnOps: ['edit', 'del', 'view', 'status', 'check'],
     autoOps: ['genMenuPermissions', 'runDao', 'runService'],
     join: [],
     menu: {
@@ -32,6 +33,17 @@ export const genInfoObj = {
       icon: 'MenuOutlined',
       sort: 0,
     },
+    tree: {
+      titleColumn: null,
+      styleType: 1,
+    },
+    funcDict: {
+      valueColumn: null,
+      labelColumn: null,
+    },
+    presetStep: {
+      formGridCols: 1,
+    },
   },
   dbName: '',
   tableName: '',
@@ -54,6 +66,8 @@ export const selectListObj = {
   dictMode: [],
   whereMode: [],
   buildMeth: [],
+  tableAlign: [],
+  treeStyleType: [],
 };
 
 export function newState(state) {
@@ -62,3 +76,67 @@ export function newState(state) {
   }
   return cloneDeep(genInfoObj);
 }
+
+export const formGridColsOptions = [
+  {
+    value: 1,
+    label: '一行一列',
+  },
+  {
+    value: 2,
+    label: '一行两列',
+  },
+  {
+    value: 3,
+    label: '一行三列',
+  },
+  {
+    value: 4,
+    label: '一行四列',
+  },
+];
+
+export const formGridSpanOptions = [
+  {
+    value: 1,
+    label: '占一列位置',
+  },
+  {
+    value: 2,
+    label: '占两列位置',
+  },
+  {
+    value: 3,
+    label: '占三列位置',
+  },
+  {
+    value: 4,
+    label: '占四列位置',
+  },
+];
+
+// 格式化列字段
+export function formatColumns(columns: any) {
+  if (columns === undefined || columns.length === 0) {
+    columns = [];
+  }
+
+  if (isJsonString(columns)) {
+    columns = JSON.parse(columns);
+  }
+
+  if (columns.length > 0) {
+    for (let i = 0; i < columns.length; i++) {
+      if (!columns[i].formGridSpan) {
+        columns[i].formGridSpan = 1;
+      }
+      if (!columns[i].align) {
+        columns[i].align = 'left';
+      }
+      if (!columns[i].width || columns[i].width < 1) {
+        columns[i].width = null;
+      }
+    }
+  }
+  return columns;
+}
diff --git a/web/src/views/develop/code/deploy.vue b/web/src/views/develop/code/deploy.vue
index bb43a9b..20eb5d0 100644
--- a/web/src/views/develop/code/deploy.vue
+++ b/web/src/views/develop/code/deploy.vue
@@ -40,7 +40,10 @@
 
           
 
 
+
+
\ No newline at end of file
diff --git a/web/src/views/normalTreeDemo/index.vue b/web/src/views/normalTreeDemo/index.vue
new file mode 100644
index 0000000..32cafec
--- /dev/null
+++ b/web/src/views/normalTreeDemo/index.vue
@@ -0,0 +1,202 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/web/src/views/normalTreeDemo/model.ts b/web/src/views/normalTreeDemo/model.ts
new file mode 100644
index 0000000..b19ae07
--- /dev/null
+++ b/web/src/views/normalTreeDemo/model.ts
@@ -0,0 +1,222 @@
+import { h, ref } from 'vue';
+import { NTag } from 'naive-ui';
+import { cloneDeep } from 'lodash-es';
+import { FormSchema } from '@/components/Form';
+import { Dicts } from '@/api/dict/dict';
+import { isNullObject } from '@/utils/is';
+import { defRangeShortcuts } from '@/utils/dateUtil';
+import { Option, getOptionLabel, getOptionTag } from '@/utils/hotgo';
+import { renderPopoverMemberSumma, MemberSumma } from '@/utils';
+import { TreeOption } from '@/api/normalTreeDemo';
+
+export class State {
+  public title = ''; // 标题
+  public id = 0; // ID
+  public pid = 0; // 上级
+  public level = 1; // 关系树级别
+  public tree = ''; // 关系树
+  public categoryId = null; // 测试分类
+  public description = ''; // 描述
+  public sort = 0; // 排序
+  public status = 1; // 状态
+  public createdBy = 0; // 创建者
+  public createdBySumma?: null | MemberSumma = null; // 创建者摘要信息
+  public updatedBy = 0; // 更新者
+  public createdAt = ''; // 创建时间
+  public updatedAt = ''; // 修改时间
+  public deletedAt = ''; // 删除时间
+
+  constructor(state?: Partial) {
+    if (state) {
+      Object.assign(this, state);
+    }
+  }
+}
+
+export function newState(state: State | Record | null): State {
+  if (state !== null) {
+    if (state instanceof State) {
+      return cloneDeep(state);
+    }
+    return new State(state);
+  }
+  return new State();
+}
+
+// 表单验证规则
+export const rules = {
+  title: {
+    required: true,
+    trigger: ['blur', 'input'],
+    type: 'string',
+    message: '请输入标题',
+  },
+};
+
+// 表格搜索表单
+export const schemas = ref([
+  {
+    field: 'title',
+    component: 'NInput',
+    label: '标题',
+    componentProps: {
+      placeholder: '请输入标题',
+      onUpdateValue: (e: any) => {
+        console.log(e);
+      },
+    },
+  },
+  {
+    field: 'categoryId',
+    component: 'NSelect',
+    label: '测试分类',
+    defaultValue: null,
+    componentProps: {
+      placeholder: '请选择测试分类',
+      options: [],
+      onUpdateValue: (e: any) => {
+        console.log(e);
+      },
+    },
+  },
+  {
+    field: 'status',
+    component: 'NSelect',
+    label: '状态',
+    defaultValue: null,
+    componentProps: {
+      placeholder: '请选择状态',
+      options: [],
+      onUpdateValue: (e: any) => {
+        console.log(e);
+      },
+    },
+  },
+  {
+    field: 'createdAt',
+    component: 'NDatePicker',
+    label: '创建时间',
+    componentProps: {
+      type: 'datetimerange',
+      clearable: true,
+      shortcuts: defRangeShortcuts(),
+      onUpdateValue: (e: any) => {
+        console.log(e);
+      },
+    },
+  },
+]);
+
+// 表格列
+export const columns = [
+  {
+    title: '标题',
+    key: 'title',
+    align: 'left',
+    width: 200,
+  },
+  {
+    title: '测试分类',
+    key: 'categoryId',
+    align: 'left',
+    width: 100,
+    render(row) {
+      if (isNullObject(row.categoryId)) {
+        return ``;
+      }
+      return h(
+        NTag,
+        {
+          style: {
+            marginRight: '6px',
+          },
+          type: getOptionTag(options.value.testCategoryOption, row.categoryId),
+          bordered: false,
+        },
+        {
+          default: () => getOptionLabel(options.value.testCategoryOption, row.categoryId),
+        }
+      );
+    },
+  },
+  {
+    title: '描述',
+    key: 'description',
+    align: 'left',
+    width: 300,
+  },
+  {
+    title: '状态',
+    key: 'status',
+    align: 'left',
+    width: 150,
+    render(row) {
+      if (isNullObject(row.status)) {
+        return ``;
+      }
+      return h(
+        NTag,
+        {
+          style: {
+            marginRight: '6px',
+          },
+          type: getOptionTag(options.value.sys_normal_disable, row.status),
+          bordered: false,
+        },
+        {
+          default: () => getOptionLabel(options.value.sys_normal_disable, row.status),
+        }
+      );
+    },
+  },
+  {
+    title: '创建者',
+    key: 'createdBy',
+    align: 'left',
+    width: 100,
+    render(row) {
+      return renderPopoverMemberSumma(row.createdBySumma);
+    },
+  },
+  {
+    title: '创建时间',
+    key: 'createdAt',
+    align: 'left',
+    width: 180,
+  },
+];
+
+// 字典数据选项
+export const options = ref({
+  sys_normal_disable: [] as Option[],
+  testCategoryOption: [] as Option[],
+});
+
+// 加载字典数据选项
+export function loadOptions() {
+  Dicts({
+    types: ['sys_normal_disable', 'testCategoryOption'],
+  }).then((res) => {
+    options.value = res;
+    for (const item of schemas.value) {
+      switch (item.field) {
+        case 'status':
+          item.componentProps.options = options.value.sys_normal_disable;
+          break;
+        case 'categoryId':
+          item.componentProps.options = options.value.testCategoryOption;
+          break;
+      }
+    }
+  });
+}
+
+// 关系树选项
+export const treeOption = ref([]);
+
+// 加载关系树选项
+export function loadTreeOption() {
+  TreeOption().then((res) => {
+    treeOption.value = res;
+  });
+}
\ No newline at end of file
diff --git a/web/src/views/normalTreeDemo/view.vue b/web/src/views/normalTreeDemo/view.vue
new file mode 100644
index 0000000..c6e6847
--- /dev/null
+++ b/web/src/views/normalTreeDemo/view.vue
@@ -0,0 +1,92 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/web/src/views/optionTreeDemo/edit.vue b/web/src/views/optionTreeDemo/edit.vue
new file mode 100644
index 0000000..f4a7bf2
--- /dev/null
+++ b/web/src/views/optionTreeDemo/edit.vue
@@ -0,0 +1,163 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/web/src/views/optionTreeDemo/index.vue b/web/src/views/optionTreeDemo/index.vue
new file mode 100644
index 0000000..a60cdf1
--- /dev/null
+++ b/web/src/views/optionTreeDemo/index.vue
@@ -0,0 +1,314 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/web/src/views/optionTreeDemo/model.ts b/web/src/views/optionTreeDemo/model.ts
new file mode 100644
index 0000000..cadf1d6
--- /dev/null
+++ b/web/src/views/optionTreeDemo/model.ts
@@ -0,0 +1,216 @@
+import { h, ref } from 'vue';
+import { NTag } from 'naive-ui';
+import { cloneDeep } from 'lodash-es';
+import { FormSchema } from '@/components/Form';
+import { Dicts } from '@/api/dict/dict';
+import { isNullObject } from '@/utils/is';
+import { defRangeShortcuts } from '@/utils/dateUtil';
+import { Option, getOptionLabel, getOptionTag } from '@/utils/hotgo';
+import { renderPopoverMemberSumma, MemberSumma } from '@/utils';
+import { TreeOption } from '@/api/optionTreeDemo';
+
+export class State {
+  public title = ''; // 标题
+  public id = 0; // ID
+  public pid = 0; // 上级
+  public level = 1; // 关系树级别
+  public tree = ''; // 关系树
+  public categoryId = null; // 测试分类
+  public description = ''; // 描述
+  public sort = 0; // 排序
+  public status = 1; // 状态
+  public createdBy = 0; // 创建者
+  public createdBySumma?: null | MemberSumma = null; // 创建者摘要信息
+  public updatedBy = 0; // 更新者
+  public createdAt = ''; // 创建时间
+  public updatedAt = ''; // 修改时间
+  public deletedAt = ''; // 删除时间
+
+  constructor(state?: Partial) {
+    if (state) {
+      Object.assign(this, state);
+    }
+  }
+}
+
+export function newState(state: State | Record | null): State {
+  if (state !== null) {
+    if (state instanceof State) {
+      return cloneDeep(state);
+    }
+    return new State(state);
+  }
+  return new State();
+}
+
+// 表单验证规则
+export const rules = {
+  title: {
+    required: true,
+    trigger: ['blur', 'input'],
+    type: 'string',
+    message: '请输入标题',
+  },
+};
+
+// 表格搜索表单
+export const schemas = ref([
+  {
+    field: 'title',
+    component: 'NInput',
+    label: '标题',
+    componentProps: {
+      placeholder: '请输入标题',
+      onUpdateValue: (e: any) => {
+        console.log(e);
+      },
+    },
+  },
+  {
+    field: 'categoryId',
+    component: 'NSelect',
+    label: '测试分类',
+    defaultValue: null,
+    componentProps: {
+      placeholder: '请选择测试分类',
+      options: [],
+      onUpdateValue: (e: any) => {
+        console.log(e);
+      },
+    },
+  },
+  {
+    field: 'status',
+    component: 'NSelect',
+    label: '状态',
+    defaultValue: null,
+    componentProps: {
+      placeholder: '请选择状态',
+      options: [],
+      onUpdateValue: (e: any) => {
+        console.log(e);
+      },
+    },
+  },
+  {
+    field: 'createdAt',
+    component: 'NDatePicker',
+    label: '创建时间',
+    componentProps: {
+      type: 'datetimerange',
+      clearable: true,
+      shortcuts: defRangeShortcuts(),
+      onUpdateValue: (e: any) => {
+        console.log(e);
+      },
+    },
+  },
+]);
+
+// 表格列
+export const columns = [
+  {
+    title: '标题',
+    key: 'title',
+    align: 'left',
+    width: 100,
+  },
+  {
+    title: '测试分类',
+    key: 'categoryId',
+    align: 'left',
+    width: 100,
+    render(row) {
+      if (isNullObject(row.categoryId)) {
+        return ``;
+      }
+      return h(
+        NTag,
+        {
+          style: {
+            marginRight: '6px',
+          },
+          type: getOptionTag(options.value.testCategoryOption, row.categoryId),
+          bordered: false,
+        },
+        {
+          default: () => getOptionLabel(options.value.testCategoryOption, row.categoryId),
+        }
+      );
+    },
+  },
+  {
+    title: '状态',
+    key: 'status',
+    align: 'left',
+    width: 150,
+    render(row) {
+      if (isNullObject(row.status)) {
+        return ``;
+      }
+      return h(
+        NTag,
+        {
+          style: {
+            marginRight: '6px',
+          },
+          type: getOptionTag(options.value.sys_normal_disable, row.status),
+          bordered: false,
+        },
+        {
+          default: () => getOptionLabel(options.value.sys_normal_disable, row.status),
+        }
+      );
+    },
+  },
+  {
+    title: '创建者',
+    key: 'createdBy',
+    align: 'left',
+    width: 100,
+    render(row) {
+      return renderPopoverMemberSumma(row.createdBySumma);
+    },
+  },
+  {
+    title: '创建时间',
+    key: 'createdAt',
+    align: 'left',
+    width: 180,
+  },
+];
+
+// 字典数据选项
+export const options = ref({
+  sys_normal_disable: [] as Option[],
+  testCategoryOption: [] as Option[],
+});
+
+// 加载字典数据选项
+export function loadOptions() {
+  Dicts({
+    types: ['sys_normal_disable', 'testCategoryOption'],
+  }).then((res) => {
+    options.value = res;
+    for (const item of schemas.value) {
+      switch (item.field) {
+        case 'status':
+          item.componentProps.options = options.value.sys_normal_disable;
+          break;
+        case 'categoryId':
+          item.componentProps.options = options.value.testCategoryOption;
+          break;
+      }
+    }
+  });
+}
+
+// 关系树选项
+export const treeOption = ref([]);
+
+// 加载关系树选项
+export function loadTreeOption() {
+  TreeOption().then((res) => {
+    treeOption.value = res;
+  });
+}
\ No newline at end of file
diff --git a/web/src/views/optionTreeDemo/view.vue b/web/src/views/optionTreeDemo/view.vue
new file mode 100644
index 0000000..e6c1894
--- /dev/null
+++ b/web/src/views/optionTreeDemo/view.vue
@@ -0,0 +1,92 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/web/src/views/org/dept/dept.vue b/web/src/views/org/dept/dept.vue
index e04f48f..f77a05a 100644
--- a/web/src/views/org/dept/dept.vue
+++ b/web/src/views/org/dept/dept.vue
@@ -1,20 +1,44 @@
 
 
 
diff --git a/web/src/views/permission/menu/addModal.vue b/web/src/views/permission/menu/addModal.vue
new file mode 100644
index 0000000..26f49c6
--- /dev/null
+++ b/web/src/views/permission/menu/addModal.vue
@@ -0,0 +1,84 @@
+
+
+
+
+
diff --git a/web/src/views/permission/menu/editForm.vue b/web/src/views/permission/menu/editForm.vue
new file mode 100644
index 0000000..bba393a
--- /dev/null
+++ b/web/src/views/permission/menu/editForm.vue
@@ -0,0 +1,417 @@
+
+
+
+
+
diff --git a/web/src/views/permission/menu/menu.vue b/web/src/views/permission/menu/menu.vue
index 588730d..16e7ee9 100644
--- a/web/src/views/permission/menu/menu.vue
+++ b/web/src/views/permission/menu/menu.vue
@@ -1,11 +1,16 @@
 
 
@@ -161,77 +35,24 @@
   import { h, onMounted, reactive, ref } from 'vue';
   import { NButton, useDialog, useMessage } from 'naive-ui';
   import { BasicColumn, TableAction } from '@/components/Table';
-  import {
-    Delete,
-    Edit,
-    GetPermissions,
-    getRoleList,
-    UpdatePermissions,
-    DataScopeSelect,
-    DataScopeEdit,
-  } from '@/api/system/role';
-  import { EditMenu, getMenuList } from '@/api/system/menu';
+  import { Delete, getRoleList } from '@/api/system/role';
   import { columns } from './columns';
   import { PlusOutlined } from '@vicons/antd';
-  import { getAllExpandKeys, getTreeAll } from '@/utils';
-  import { statusOptions } from '@/enums/optionsiEnum';
-  import { cloneDeep } from 'lodash-es';
-  import { getDeptList } from '@/api/org/dept';
+  import EditRole from './editRole.vue';
+  import EditMenuAuth from './editMenuAuth.vue';
+  import EditDataAuth from './editDataAuth.vue';
+  import { newState } from '@/views/permission/role/model';
 
-  const formRef: any = ref(null);
   const message = useMessage();
   const dialog = useDialog();
-  const showModal2 = ref(false);
-  const showModal = ref(false);
-  const formBtnLoading = ref(false);
-  const formBtnLoading2 = ref(false);
-  const checkedAll = ref(false);
-  const editRoleTitle = ref('');
-  const treeData = ref([]);
-  const expandedKeys = ref([]);
-  const checkedKeys = ref([]);
-  const updatePermissionsParams = ref({});
-  const optionTreeData = ref([]);
-  const dataScopeOption = ref();
-  const deptList = ref([]);
-  const dataFormRef = ref();
-  const dataFormBtnLoading = ref(false);
-  const showDataModal = ref(false);
-  const dataForm = ref();
   const loading = ref(false);
-  const data = ref([]);
-
-  const rules = {
-    name: {
-      required: true,
-      trigger: ['blur', 'input'],
-      message: '请输入名称',
-    },
-    key: {
-      required: true,
-      trigger: ['blur', 'input'],
-      message: '请输入角色编码',
-    },
-  };
-
-  const defaultState = {
-    id: 0,
-    pid: 0,
-    level: 1,
-    tree: '',
-    name: '',
-    key: '',
-    remark: null,
-    status: 1,
-    sort: 0,
-    dataScope: 1,
-    customDept: [],
-  };
-
-  const formParams = ref(cloneDeep(defaultState));
+  const dataSource = ref([]);
+  const editRoleRef = ref();
+  const editMenuAuthRef = ref();
+  const editDataAuthRef = ref();
 
   const actionColumn = reactive({
-    width: 300,
+    width: 200,
     title: '操作',
     key: 'action',
     fixed: 'right',
@@ -255,12 +76,13 @@
             },
             type: 'default',
           },
+          {
+            label: '添加',
+            onClick: handleAddSub.bind(null, record),
+          },
           {
             label: '编辑',
             onClick: handleEdit.bind(null, record),
-            ifShow: () => {
-              return record.id !== 1;
-            },
           },
           {
             label: '删除',
@@ -274,62 +96,30 @@
     },
   });
 
-  const loadDataTable = async (res: any) => {
+  function loadDataTable() {
     loading.value = true;
-    const tmp = await getRoleList({ ...res, ...{ pageSize: 100, page: 1 } });
-    data.value = tmp.list ?? [];
-    loading.value = false;
-  };
+    getRoleList({ pageSize: 100, page: 1 }).then((res) => {
+      dataSource.value = res.list ?? [];
+      loading.value = false;
+    });
+  }
 
   function reloadTable() {
-    loadDataList();
-    loadDataTable({});
-  }
-
-  function confirmForm(e: any) {
-    e.preventDefault();
-    formBtnLoading.value = true;
-    UpdatePermissions({
-      ...{
-        id: updatePermissionsParams.value.id,
-        menuIds:
-          checkedKeys.value === undefined || checkedKeys.value == null ? [] : checkedKeys.value,
-      },
-    }).then((_res) => {
-      message.success('操作成功');
-      reloadTable();
-      showModal.value = false;
-      formBtnLoading.value = false;
-    });
-  }
-
-  function confirmForm2(e) {
-    e.preventDefault();
-    formBtnLoading2.value = true;
-    formRef.value.validate((errors) => {
-      if (!errors) {
-        Edit(formParams.value).then((_res) => {
-          message.success('操作成功');
-          setTimeout(() => {
-            showModal2.value = false;
-            reloadTable();
-          });
-        });
-      } else {
-        message.error('请填写完整信息');
-      }
-      formBtnLoading2.value = false;
-    });
+    loadDataTable();
   }
 
   function addTable() {
-    showModal2.value = true;
-    formParams.value = cloneDeep(defaultState);
+    editRoleRef.value.openModal(null);
   }
 
   function handleEdit(record: Recordable) {
-    showModal2.value = true;
-    formParams.value = cloneDeep(record);
+    editRoleRef.value.openModal(record);
+  }
+
+  function handleAddSub(record: Recordable) {
+    let state = newState(null);
+    state.pid = record.id;
+    editRoleRef.value.openModal(state);
   }
 
   function handleDelete(record: Recordable) {
@@ -348,118 +138,16 @@
   }
 
   async function handleMenuAuth(record: Recordable) {
-    editRoleTitle.value = `分配 ${record.name} 的菜单权限`;
-    checkedKeys.value = [];
-    checkedAll.value = false;
-    const data = await GetPermissions({ ...{ id: record.id } });
-    checkedKeys.value = data.menuIds;
-    updatePermissionsParams.value.id = record.id;
-    showModal.value = true;
+    editMenuAuthRef.value.openModal(record);
   }
 
   function handleDataAuth(record: Recordable) {
-    dataForm.value = cloneDeep(record);
-    showDataModal.value = true;
+    editDataAuthRef.value.openModal(record);
   }
 
-  function handleUpdateDeptValue(value: string | number | Array | null) {
-    dataForm.value.customDept = value;
-  }
-
-  function confirmDataForm(e) {
-    e.preventDefault();
-    dataFormBtnLoading.value = true;
-    dataFormRef.value.validate((errors) => {
-      if (!errors) {
-        DataScopeEdit(dataForm.value).then((_res) => {
-          message.success('操作成功');
-          setTimeout(() => {
-            showDataModal.value = false;
-            reloadTable();
-          });
-        });
-      } else {
-        message.error('请填写完整信息');
-      }
-      dataFormBtnLoading.value = false;
-    });
-  }
-
-  function checkedTree(keys) {
-    checkedKeys.value = keys;
-  }
-
-  function onExpandedKeys(keys) {
-    expandedKeys.value = keys;
-  }
-
-  function packHandle() {
-    if (expandedKeys.value.length) {
-      expandedKeys.value = [];
-    } else {
-      expandedKeys.value = getAllExpandKeys(treeData) as [];
-    }
-  }
-
-  function checkedAllHandle() {
-    if (!checkedAll.value) {
-      checkedKeys.value = getTreeAll(treeData.value);
-      checkedAll.value = true;
-    } else {
-      checkedKeys.value = [];
-      checkedAll.value = false;
-    }
-  }
-
-  onMounted(async () => {
-    loadDataList();
-    loadMenuList();
-    loadDeptList();
-    loadDataScopeSelect();
-    await loadDataTable({});
+  onMounted(() => {
+    loadDataTable();
   });
-
-  function loadDataList() {
-    getRoleList({ pageSize: 100, page: 1 }).then((res) => {
-      optionTreeData.value = [
-        {
-          id: 0,
-          key: 0,
-          label: '顶级角色',
-          pid: 0,
-          name: '顶级角色',
-        },
-      ];
-      optionTreeData.value = optionTreeData.value.concat(res.list);
-    });
-  }
-
-  function loadMenuList() {
-    getMenuList().then((res) => {
-      expandedKeys.value = getAllExpandKeys(res.list) as [];
-      treeData.value = res.list;
-    });
-  }
-
-  function loadDeptList() {
-    getDeptList({}).then((res) => {
-      if (res.list) {
-        deptList.value = res.list;
-      }
-    });
-  }
-
-  function loadDataScopeSelect() {
-    DataScopeSelect().then((res) => {
-      if (res.list) {
-        dataScopeOption.value = res.list;
-      }
-    });
-  }
-
-  function onUpdateValuePid(value: string | number) {
-    formParams.value.pid = value;
-  }
 
 
 
diff --git a/web/src/views/system/config/LoginSetting.vue b/web/src/views/system/config/LoginSetting.vue
index 892a494..89a58b6 100644
--- a/web/src/views/system/config/LoginSetting.vue
+++ b/web/src/views/system/config/LoginSetting.vue
@@ -68,7 +68,7 @@
           />
         
 
-        
+        
           
         
 
@@ -182,22 +182,17 @@
 
   function load() {
     show.value = true;
-    new Promise((_resolve, _reject) => {
-      getConfig({ group: group.value })
-        .then((res) => {
-          show.value = false;
-          formValue.value = res.list;
-        })
-        .catch((error) => {
-          show.value = false;
-          message.error(error.toString());
-        });
-    });
+    getConfig({ group: group.value })
+      .then((res) => {
+        formValue.value = res.list;
+      })
+      .finally(() => {
+        show.value = false;
+      });
   }
 
   onMounted(async () => {
-    show.value = true;
-    await loadOptions();
     load();
+    await loadOptions();
   });
 
diff --git a/web/src/views/system/cron/columns.ts b/web/src/views/system/cron/columns.ts
index e98aed6..35f5c33 100644
--- a/web/src/views/system/cron/columns.ts
+++ b/web/src/views/system/cron/columns.ts
@@ -12,18 +12,22 @@ export const columns = [
   {
     title: 'ID',
     key: 'id',
+    width: 80,
   },
   {
     title: '任务标题',
     key: 'title',
+    width: 150,
   },
   {
     title: '任务分组',
     key: 'groupName',
+    width: 100,
   },
   {
     title: '执行方法',
     key: 'name',
+    width: 100,
   },
   // {
   //   title: '执行参数',
@@ -50,10 +54,12 @@ export const columns = [
         }
       );
     },
+    width: 100,
   },
   {
     title: '表达式',
     key: 'pattern',
+    width: 150,
   },
   // {
   //   title: '执行次数',
@@ -77,9 +83,11 @@ export const columns = [
         }
       );
     },
+    width: 100,
   },
   {
     title: '创建时间',
     key: 'createdAt',
+    width: 180,
   },
 ];
diff --git a/web/src/views/system/cron/index.vue b/web/src/views/system/cron/index.vue
index e7bf797..4195c5b 100644
--- a/web/src/views/system/cron/index.vue
+++ b/web/src/views/system/cron/index.vue
@@ -21,7 +21,8 @@
         ref="actionRef"
         :actionColumn="actionColumn"
         @update:checked-row-keys="onCheckedRow"
-        :scroll-x="1090"
+        :scroll-x="scrollX"
+        :resizeHeightOffset="-10000"
       >
         
 
 
+
+
\ No newline at end of file
diff --git a/web/src/views/testCategory/index.vue b/web/src/views/testCategory/index.vue
new file mode 100644
index 0000000..bed1b62
--- /dev/null
+++ b/web/src/views/testCategory/index.vue
@@ -0,0 +1,190 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/web/src/views/testCategory/model.ts b/web/src/views/testCategory/model.ts
new file mode 100644
index 0000000..ffc0e48
--- /dev/null
+++ b/web/src/views/testCategory/model.ts
@@ -0,0 +1,184 @@
+import { h, ref } from 'vue';
+import { NTag } from 'naive-ui';
+import { cloneDeep } from 'lodash-es';
+import { FormSchema } from '@/components/Form';
+import { Dicts } from '@/api/dict/dict';
+import { isNullObject } from '@/utils/is';
+import { defRangeShortcuts } from '@/utils/dateUtil';
+import { Option, getOptionLabel, getOptionTag } from '@/utils/hotgo';
+
+export class State {
+  public id = 0; // 分类ID
+  public name = ''; // 分类名称
+  public shortName = ''; // 简称
+  public description = ''; // 描述
+  public sort = 0; // 排序
+  public status = 1; // 状态
+  public remark = ''; // 备注
+  public createdAt = ''; // 创建时间
+  public updatedAt = ''; // 修改时间
+  public deletedAt = ''; // 删除时间
+
+  constructor(state?: Partial) {
+    if (state) {
+      Object.assign(this, state);
+    }
+  }
+}
+
+export function newState(state: State | Record | null): State {
+  if (state !== null) {
+    if (state instanceof State) {
+      return cloneDeep(state);
+    }
+    return new State(state);
+  }
+  return new State();
+}
+
+// 表单验证规则
+export const rules = {
+  name: {
+    required: true,
+    trigger: ['blur', 'input'],
+    type: 'string',
+    message: '请输入分类名称',
+  },
+  sort: {
+    required: true,
+    trigger: ['blur', 'input'],
+    type: 'number',
+    message: '请输入排序',
+  },
+};
+
+// 表格搜索表单
+export const schemas = ref([
+  {
+    field: 'id',
+    component: 'NInputNumber',
+    label: '分类ID',
+    componentProps: {
+      placeholder: '请输入分类ID',
+      onUpdateValue: (e: any) => {
+        console.log(e);
+      },
+    },
+  },
+  {
+    field: 'name',
+    component: 'NInput',
+    label: '分类名称',
+    componentProps: {
+      placeholder: '请输入分类名称',
+      onUpdateValue: (e: any) => {
+        console.log(e);
+      },
+    },
+  },
+  {
+    field: 'status',
+    component: 'NSelect',
+    label: '状态',
+    defaultValue: null,
+    componentProps: {
+      placeholder: '请选择状态',
+      options: [],
+      onUpdateValue: (e: any) => {
+        console.log(e);
+      },
+    },
+  },
+  {
+    field: 'createdAt',
+    component: 'NDatePicker',
+    label: '创建时间',
+    componentProps: {
+      type: 'datetimerange',
+      clearable: true,
+      shortcuts: defRangeShortcuts(),
+      onUpdateValue: (e: any) => {
+        console.log(e);
+      },
+    },
+  },
+]);
+
+// 表格列
+export const columns = [
+  {
+    title: '分类ID',
+    key: 'id',
+    align: 'left',
+    width: 80,
+  },
+  {
+    title: '分类名称',
+    key: 'name',
+    align: 'left',
+    width: -1,
+  },
+  {
+    title: '简称',
+    key: 'shortName',
+    align: 'left',
+    width: 80,
+  },
+  {
+    title: '描述',
+    key: 'description',
+    align: 'left',
+    width: 300,
+  },
+  {
+    title: '状态',
+    key: 'status',
+    align: 'left',
+    width: -1,
+    render(row) {
+      if (isNullObject(row.status)) {
+        return ``;
+      }
+      return h(
+        NTag,
+        {
+          style: {
+            marginRight: '6px',
+          },
+          type: getOptionTag(options.value.sys_normal_disable, row.status),
+          bordered: false,
+        },
+        {
+          default: () => getOptionLabel(options.value.sys_normal_disable, row.status),
+        }
+      );
+    },
+  },
+  {
+    title: '创建时间',
+    key: 'createdAt',
+    align: 'left',
+    width: 180,
+  },
+];
+
+// 字典数据选项
+export const options = ref({
+  sys_normal_disable: [] as Option[],
+});
+
+// 加载字典数据选项
+export function loadOptions() {
+  Dicts({
+    types: ['sys_normal_disable'],
+  }).then((res) => {
+    options.value = res;
+    for (const item of schemas.value) {
+      switch (item.field) {
+        case 'status':
+          item.componentProps.options = options.value.sys_normal_disable;
+          break;
+      }
+    }
+  });
+}
\ No newline at end of file
diff --git a/web/vite.config.ts b/web/vite.config.ts
index 5de2973..fd14c78 100644
--- a/web/vite.config.ts
+++ b/web/vite.config.ts
@@ -22,7 +22,7 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
   const root = process.cwd();
   const env = loadEnv(mode, root);
   const viteEnv = wrapperEnv(env);
-  const { VITE_PUBLIC_PATH, VITE_DROP_CONSOLE, VITE_PORT, VITE_PROXY } = viteEnv;
+  const { VITE_PUBLIC_PATH, VITE_PORT, VITE_PROXY } = viteEnv;
   const isBuild = command === 'build';
   return {
     base: VITE_PUBLIC_PATH,
@@ -76,15 +76,8 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
       target: 'es2015',
       cssTarget: 'chrome80',
       outDir: OUTPUT_DIR,
-      minify: 'terser',
-      terserOptions: {
-        compress: {
-          keep_infinity: true,
-          drop_console: VITE_DROP_CONSOLE,
-        },
-      },
-      brotliSize: false,
-      chunkSizeWarningLimit: 3000,
+      reportCompressedSize: false,
+      chunkSizeWarningLimit: 2000,
     },
   };
 };