From ca0e59e087685df4bd3dc117101ddbf85c53eed0 Mon Sep 17 00:00:00 2001
From: 潘志宝 <979469083@qq.com>
Date: 星期三, 18 九月 2024 12:01:00 +0800
Subject: [PATCH] Merge branch 'master' of ssh://172.16.8.100:29418/iailab-plat

---
 iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/service/impl/MpkFileServiceImpl.java                            |   77 +
 iailab-module-model/iailab-module-model-biz/src/main/resources/mapper/mpk/MpkFileDao.xml                                                              |   74 +
 iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/oauth2/OAuth2OpenController.java                |   28 
 iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/service/MethodSettingService.java                               |   14 
 iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/app/AppGroupService.java                                 |   31 
 iailab-module-system/iailab-module-system-api/src/main/java/com/iailab/module/system/api/app/AppApi.java                                              |   31 
 iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/app/AppGroupController.java                     |   83 +
 iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/convert/oauth2/OAuth2OpenConvert.java                            |    1 
 iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/app/AppController.java                          |   20 
 iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/app/vo/AppMenuListReqVO.java                    |   16 
 iailab-module-system/iailab-module-system-api/src/main/java/com/iailab/module/system/api/app/dto/AppMenuRespDTO.java                                  |   80 +
 iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/dal/dataobject/app/AppGroupDO.java                               |   57 +
 iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/tenant/TenantServiceImpl.java                            |    6 
 iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/user/AdminUserServiceImpl.java                           |    5 
 iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/service/impl/SettingSelectServiceImpl.java                      |   37 
 iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/permission/MenuService.java                              |   17 
 iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/dal/dataobject/permission/MenuDO.java                            |   11 
 iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/dal/mysql/app/AppGroupMapper.java                                |   24 
 iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/convert/auth/AuthConvert.java                                    |   96 +
 iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/app/AppGroupServiceImpl.java                             |   72 +
 iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/oauth2/vo/open/OAuth2OpenAccessTokenRespVO.java |    9 
 iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/convert/app/AppConvert.java                                      |   60 +
 iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/app/vo/AppMenuSimpleRespVO.java                 |   22 
 iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/app/vo/AppRespVO.java                           |   26 
 iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/app/vo/AppSaveReqVO.java                        |   11 
 iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/app/AppServiceImpl.java                                  |  216 ++++
 iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/permission/vo/menu/MenuRespVO.java              |    3 
 iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/entity/ModelMethodEntity.java                                   |    4 
 iailab-module-system/iailab-module-system-api/src/main/java/com/iailab/module/system/api/app/dto/AppRespDTO.java                                      |   75 +
 iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/permission/RoleServiceImpl.java                          |    8 
 iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/app/AppMenuService.java                                  |  118 ++
 iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/dto/ModelMethodDTO.java                                         |    7 
 iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/permission/vo/menu/MenuSaveVO.java              |    3 
 iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/tenant/TenantService.java                                |    7 
 iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/dal/mysql/permission/MenuMapper.java                             |   12 
 iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/app/vo/AppGroupPageReqVO.java                   |   23 
 iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/dal/dataobject/app/AppDO.java                                    |   28 
 iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/dto/MethodSettingDTO.java                                       |   64 +
 iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/dal/mysql/app/AppMenuMapper.java                                 |   37 
 iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/dal/mysql/user/AdminUserMapper.java                              |    3 
 iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/service/MpkFileService.java                                     |    2 
 iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/service/impl/MethodSettingServiceImpl.java                      |   22 
 iailab-module-system/iailab-module-system-api/src/main/java/com/iailab/module/system/api/app/AppMenuApi.java                                          |   32 
 iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/dao/SettingSelectDao.java                                       |   14 
 iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/dal/dataobject/app/AppMenuDO.java                                |  114 ++
 iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/app/AppMenuController.java                      |   87 +
 iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/permission/PermissionService.java                        |   26 
 iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/permission/RoleService.java                              |    2 
 iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/dto/SettingSelectDTO.java                                       |   36 
 iailab-module-model/iailab-module-model-biz/db/mysql.sql                                                                                              |  106 +
 iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/entity/SettingSelectEntity.java                                 |   38 
 iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/permission/PermissionServiceImpl.java                    |   74 +
 iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/auth/AuthController.java                        |   35 
 iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/app/vo/AppGroupRespVO.java                      |   48 
 iailab-module-system/iailab-module-system-api/src/main/java/com/iailab/module/system/api/app/dto/AppMenuSimpleRespDTO.java                            |   22 
 iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/app/vo/AppMenuRespVO.java                       |   69 +
 iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/permission/PermissionController.java            |   17 
 iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/dto/MpkFileDTO.java                                             |   10 
 iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/entity/MpkFileEntity.java                                       |   20 
 iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/app/vo/AppGroupSaveReqVO.java                   |   36 
 iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/controller/admin/MpkFileController.java                         |   26 
 iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/permission/MenuController.java                  |   53 +
 iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/permission/MenuServiceImpl.java                          |   65 +
 iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/tenant/TenantController.java                    |    8 
 iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/api/app/AppMenuApiImpl.java                                      |   59 +
 iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/entity/MethodSettingEntity.java                                 |   64 +
 iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/service/SettingSelectService.java                               |   16 
 iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/service/impl/ProjectServiceImpl.java                            |   19 
 iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/app/vo/AppMenuSaveVO.java                       |   65 +
 iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/oauth2/vo/open/OAuth2OpenLoginReqVO.java        |   54 +
 iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/app/AppMenuServiceImpl.java                              |  281 +++++
 iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/app/AppService.java                                      |    9 
 iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/dao/MethodSettingDao.java                                       |   14 
 iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/api/app/AppApiImpl.java                                          |   55 +
 74 files changed, 3,003 insertions(+), 111 deletions(-)

diff --git a/iailab-module-model/iailab-module-model-biz/db/mysql.sql b/iailab-module-model/iailab-module-model-biz/db/mysql.sql
index 8b33e9b..c8e5d06 100644
--- a/iailab-module-model/iailab-module-model-biz/db/mysql.sql
+++ b/iailab-module-model/iailab-module-model-biz/db/mysql.sql
@@ -419,18 +419,43 @@
 -- config
 INSERT INTO `iailab_plat_system`.`infra_config` (`id`, `category`, `type`, `name`, `config_key`, `value`, `visible`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (13, 'model', 2, 'model文件备份路径', 'mpkBakFilePath', 'C:\\DLUT\\mpkBakFile', b'1', 'model文件备份路径', '1', '2024-09-12 11:10:25', '1', '2024-09-12 11:10:25', b'0');
 
--- ----------------------------
--- Table structure for t_mpk_file
--- ----------------------------
+-- dist
+INSERT INTO `iailab_plat_system`.`system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (618, '模型方法', 'model_method', 0, '', '1', '2024-09-09 16:11:55', '1', '2024-09-09 16:11:55', b'0', '1970-01-01 00:00:00');
+INSERT INTO `iailab_plat_system`.`system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (619, '模型类型', 'model_type', 0, '', '1', '2024-09-13 14:14:26', '1', '2024-09-13 14:14:26', b'0', '1970-01-01 00:00:00');
+INSERT INTO `iailab_plat_system`.`system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (620, '模型方法输入类型', 'model_method_setting_type', 0, '', '1', '2024-09-13 15:41:38', '1', '2024-09-13 15:41:38', b'0', '1970-01-01 00:00:00');
+INSERT INTO `iailab_plat_system`.`system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (621, '模型方法参数类型', 'model_method_setting_value_type', 0, '', '1', '2024-09-13 15:42:27', '1', '2024-09-13 15:42:27', b'0', '1970-01-01 00:00:00');
+
+
+INSERT INTO `iailab_plat_system`.`system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1536, 1, 'train', 'train', 'model_method', 0, '', '', '', '1', '2024-09-09 16:12:42', '1', '2024-09-09 16:12:42', b'0');
+INSERT INTO `iailab_plat_system`.`system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1537, 3, 'control', 'control', 'model_method', 0, '', '', '', '1', '2024-09-09 16:12:54', '1', '2024-09-09 16:13:10', b'0');
+INSERT INTO `iailab_plat_system`.`system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1538, 2, 'predict', 'predict', 'model_method', 0, '', '', '', '1', '2024-09-09 16:13:05', '1', '2024-09-09 16:13:05', b'0');
+INSERT INTO `iailab_plat_system`.`system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1539, 1, '预测模型', 'predict', 'model_type', 0, '', '', '', '1', '2024-09-13 14:14:58', '1', '2024-09-13 14:14:58', b'0');
+INSERT INTO `iailab_plat_system`.`system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1540, 2, '调度模型', 'schedul', 'model_type', 0, '', '', '', '1', '2024-09-13 14:17:53', '1', '2024-09-13 14:17:53', b'0');
+INSERT INTO `iailab_plat_system`.`system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1541, 1, 'input', 'input', 'model_method_setting_type', 0, '', '', '', '1', '2024-09-13 15:44:08', '1', '2024-09-13 15:44:08', b'0');
+INSERT INTO `iailab_plat_system`.`system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1542, 2, 'select', 'select', 'model_method_setting_type', 0, '', '', '', '1', '2024-09-13 15:44:17', '1', '2024-09-13 15:44:17', b'0');
+INSERT INTO `iailab_plat_system`.`system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1543, 3, 'file', 'file', 'model_method_setting_type', 0, '', '', '', '1', '2024-09-13 15:44:24', '1', '2024-09-13 15:44:24', b'0');
+INSERT INTO `iailab_plat_system`.`system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1544, 1, 'int', 'int', 'model_method_setting_value_type', 0, '', '', '', '1', '2024-09-13 15:44:42', '1', '2024-09-13 15:44:42', b'0');
+INSERT INTO `iailab_plat_system`.`system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1545, 5, 'file', 'file', 'model_method_setting_value_type', 0, '', '', '', '1', '2024-09-13 15:44:57', '1', '2024-09-14 14:16:24', b'0');
+INSERT INTO `iailab_plat_system`.`system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1546, 3, 'decimal', 'decimal', 'model_method_setting_value_type', 0, '', '', '', '1', '2024-09-13 15:45:21', '1', '2024-09-13 15:45:21', b'0');
+INSERT INTO `iailab_plat_system`.`system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1547, 4, 'decimalArray', 'decimalArray', 'model_method_setting_value_type', 0, '', '', '', '1', '2024-09-13 15:45:26', '1', '2024-09-13 15:45:26', b'0');
+INSERT INTO `iailab_plat_system`.`system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1548, 2, 'string', 'string', 'model_method_setting_value_type', 0, '', '', '', '1', '2024-09-13 15:45:36', '1', '2024-09-14 14:16:30', b'0');
+INSERT INTO `iailab_plat_system`.`system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1549, 4, 'schedul', 'schedul', 'model_method', 0, '', '', '', '1', '2024-09-14 14:56:44', '1', '2024-09-14 14:56:44', b'0');
+
+
+-- 业务表
 DROP TABLE IF EXISTS `t_mpk_file`;
 CREATE TABLE `t_mpk_file`  (
                                `id` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT 'id',
                                `py_name` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '模型名称',
+                               `py_chinese_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '模型中文名称',
                                `file_path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '源文件保存路径',
                                `py_type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '模型类型',
                                `pkg_name` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '包名',
                                `class_name` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '类名',
                                `py_module` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '模型路径',
+                               `icon` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT 'icon图片名',
+                               `menu_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '所属菜单',
+                               `group_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '所属组',
                                `remark` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '备注',
                                `creator` bigint NULL DEFAULT NULL COMMENT '创建者',
                                `create_date` datetime NULL DEFAULT NULL COMMENT '创建时间',
@@ -441,20 +466,41 @@
                                INDEX `idx_create_date`(`create_date` ASC) USING BTREE
 ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = 'MDK模型文件' ROW_FORMAT = DYNAMIC;
 
-
 -- ----------------------------
 -- Table structure for t_mpk_generator_code_history
 -- ----------------------------
 DROP TABLE IF EXISTS `t_mpk_generator_code_history`;
 CREATE TABLE `t_mpk_generator_code_history`  (
                                                  `id` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT 'id',
-                                                 `mdk_id` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT 'mdk_id',
+                                                 `mdk_id` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT 'mdk_id',
                                                  `file_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '文件名',
                                                  `file_path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '文件保存路径',
                                                  `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '备注',
                                                  `create_time` timestamp NULL DEFAULT NULL COMMENT '生成时间',
-                                                 PRIMARY KEY (`id`) USING BTREE
+                                                 PRIMARY KEY (`id`, `mdk_id`) USING BTREE,
+                                                 INDEX `del_code_history`(`mdk_id` ASC) USING BTREE,
+                                                 CONSTRAINT `del_code_history` FOREIGN KEY (`mdk_id`) REFERENCES `t_mpk_file` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT
 ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '生成代码记录表' ROW_FORMAT = DYNAMIC;
+
+-- ----------------------------
+-- Table structure for t_mpk_method_setting
+-- ----------------------------
+DROP TABLE IF EXISTS `t_mpk_method_setting`;
+CREATE TABLE `t_mpk_method_setting`  (
+                                         `id` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT 'id',
+                                         `method_id` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '方法id',
+                                         `setting_key` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT 'key',
+                                         `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '参数名称',
+                                         `value` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '参数默认值',
+                                         `type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '输入类型',
+                                         `value_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '参数类型',
+                                         `max` int NULL DEFAULT NULL COMMENT '最大值',
+                                         `min` int NULL DEFAULT NULL COMMENT '最小值',
+                                         PRIMARY KEY (`id`, `method_id`) USING BTREE,
+                                         INDEX `del_setting`(`method_id` ASC) USING BTREE,
+                                         INDEX `id`(`id` ASC) USING BTREE,
+                                         CONSTRAINT `del_setting` FOREIGN KEY (`method_id`) REFERENCES `t_mpk_model_method` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT
+) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '方法参数关联表' ROW_FORMAT = Dynamic;
 
 -- ----------------------------
 -- Table structure for t_mpk_model_method
@@ -462,11 +508,15 @@
 DROP TABLE IF EXISTS `t_mpk_model_method`;
 CREATE TABLE `t_mpk_model_method`  (
                                        `id` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT 'id',
-                                       `mpk_file_id` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '模型文件id',
+                                       `mpk_file_id` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '模型文件id',
                                        `method_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '模型方法名',
                                        `data_length` int NULL DEFAULT 1 COMMENT '输入个数',
                                        `model` int NULL DEFAULT 0 COMMENT '是否有model(0:否,1:是)',
-                                       PRIMARY KEY (`id`) USING BTREE
+                                       `result_key` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '结果key',
+                                       PRIMARY KEY (`id`, `mpk_file_id`) USING BTREE,
+                                       INDEX `id`(`id` ASC) USING BTREE,
+                                       INDEX `del_method`(`mpk_file_id` ASC) USING BTREE,
+                                       CONSTRAINT `del_method` FOREIGN KEY (`mpk_file_id`) REFERENCES `t_mpk_file` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT
 ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
 
 -- ----------------------------
@@ -488,9 +538,13 @@
 DROP TABLE IF EXISTS `t_mpk_project_model`;
 CREATE TABLE `t_mpk_project_model`  (
                                         `id` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT 'id',
-                                        `project_id` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '项目id',
-                                        `model_id` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '模型id',
-                                        PRIMARY KEY (`id`) USING BTREE
+                                        `project_id` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '项目id',
+                                        `model_id` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '模型id',
+                                        PRIMARY KEY (`id`, `project_id`, `model_id`) USING BTREE,
+                                        INDEX `del_p`(`project_id` ASC) USING BTREE,
+                                        INDEX `del_m`(`model_id` ASC) USING BTREE,
+                                        CONSTRAINT `del_m` FOREIGN KEY (`model_id`) REFERENCES `t_mpk_file` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT,
+                                        CONSTRAINT `del_p` FOREIGN KEY (`project_id`) REFERENCES `t_mpk_project` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT
 ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '项目模型关联表' ROW_FORMAT = DYNAMIC;
 
 -- ----------------------------
@@ -499,14 +553,16 @@
 DROP TABLE IF EXISTS `t_mpk_project_package_history`;
 CREATE TABLE `t_mpk_project_package_history`  (
                                                   `id` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT 'id',
-                                                  `project_id` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '项目id',
+                                                  `project_id` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '项目id',
                                                   `file_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '文件名',
                                                   `file_path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '文件路径',
                                                   `version` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '版本号',
                                                   `log` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '更新日志',
                                                   `model_names` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '打包模型名称(“,”分割)',
                                                   `create_time` timestamp NULL DEFAULT NULL COMMENT '创建时间',
-                                                  PRIMARY KEY (`id`) USING BTREE
+                                                  PRIMARY KEY (`id`, `project_id`) USING BTREE,
+                                                  INDEX `del_package_history`(`project_id` ASC) USING BTREE,
+                                                  CONSTRAINT `del_package_history` FOREIGN KEY (`project_id`) REFERENCES `t_mpk_project` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT
 ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '项目打包历史记录表' ROW_FORMAT = DYNAMIC;
 
 -- ----------------------------
@@ -515,12 +571,28 @@
 DROP TABLE IF EXISTS `t_mpk_project_package_history_model`;
 CREATE TABLE `t_mpk_project_package_history_model`  (
                                                         `id` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT 'id',
-                                                        `project_id` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '项目id',
+                                                        `project_id` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '项目id',
                                                         `package_history_id` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '打包历史id',
                                                         `py_name` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '模型名称',
                                                         `pkg_name` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '包名',
                                                         `py_module` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '模型路径',
                                                         `remark` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '模型备注',
-                                                        `method_info` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '模型方法信息',
-                                                        PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '打包历史模型关联表' ROW_FORMAT = Dynamic;
\ No newline at end of file
+                                                        `method_info` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '模型方法信息',
+                                                        PRIMARY KEY (`id`, `project_id`) USING BTREE,
+                                                        INDEX `del_package_model`(`project_id` ASC) USING BTREE,
+                                                        CONSTRAINT `del_package_model` FOREIGN KEY (`project_id`) REFERENCES `t_mpk_project` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT
+) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '打包历史模型关联表' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for t_mpk_setting_select
+-- ----------------------------
+DROP TABLE IF EXISTS `t_mpk_setting_select`;
+CREATE TABLE `t_mpk_setting_select`  (
+                                         `id` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT 'id',
+                                         `setting_id` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '参数id',
+                                         `select_key` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT 'key',
+                                         `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '名称',
+                                         PRIMARY KEY (`id`, `setting_id`) USING BTREE,
+                                         INDEX `del_select`(`setting_id` ASC) USING BTREE,
+                                         CONSTRAINT `del_select` FOREIGN KEY (`setting_id`) REFERENCES `t_mpk_method_setting` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT
+) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '参数选项关联表' ROW_FORMAT = Dynamic;
\ No newline at end of file
diff --git a/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/controller/admin/MpkFileController.java b/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/controller/admin/MpkFileController.java
index bba6bb7..ad7352f 100644
--- a/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/controller/admin/MpkFileController.java
+++ b/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/controller/admin/MpkFileController.java
@@ -1,16 +1,12 @@
 package com.iailab.module.model.mpk.controller.admin;
 
-import com.alibaba.fastjson.JSON;
 import com.iailab.framework.common.page.PageData;
 import com.iailab.framework.common.pojo.CommonResult;
-import com.iailab.framework.common.util.date.DateUtils;
-import com.iailab.module.model.mpk.common.utils.Readtxt;
 import com.iailab.module.model.mpk.dto.MpkFileDTO;
-import com.iailab.module.model.mpk.service.MdkFileService;
+import com.iailab.module.model.mpk.service.MpkFileService;
 import io.swagger.v3.oas.annotations.Operation;
 import org.apache.commons.io.IOUtils;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.util.CollectionUtils;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
 
@@ -30,51 +26,51 @@
 @RequestMapping("/model/mpk/file")
 public class MpkFileController {
     @Autowired
-    private MdkFileService mdkFileService;
+    private MpkFileService mpkFileService;
 
     @GetMapping("page")
     @Operation(summary = "分页")
     public CommonResult<PageData<MpkFileDTO>> page(@RequestParam Map<String, Object> params) {
-        PageData<MpkFileDTO> page = mdkFileService.page(params);
+        PageData<MpkFileDTO> page = mpkFileService.page(params);
 
         return success(page);
     }
 
     @GetMapping("{id}")
     public CommonResult<MpkFileDTO> info(@PathVariable("id") String id) {
-        MpkFileDTO schedule = mdkFileService.get(id);
+        MpkFileDTO schedule = mpkFileService.get(id);
 
         return success(schedule);
     }
 
     @GetMapping("list")
     public CommonResult<List<MpkFileDTO>> list() {
-        List<MpkFileDTO> list = mdkFileService.list(new HashMap<>());
+        List<MpkFileDTO> list = mpkFileService.list(new HashMap<>());
 
         return success(list);
     }
 
     @PostMapping
     public CommonResult save(@RequestBody MpkFileDTO dto) {
-        mdkFileService.save(dto);
+        mpkFileService.save(dto);
         return CommonResult.success();
     }
 
     @DeleteMapping
     public CommonResult delete(String id) {
-        mdkFileService.delete(id);
+        mpkFileService.delete(id);
         return CommonResult.success();
     }
 
     @PutMapping
     public CommonResult update(@RequestBody MpkFileDTO dto) {
-        mdkFileService.update(dto);
+        mpkFileService.update(dto);
         return CommonResult.success();
     }
 
     @GetMapping("generat")
     public void generat(String id, String remark,String zipFileName, HttpServletResponse response) throws IOException {
-        byte[] data = mdkFileService.generatorCode(id, remark,zipFileName);
+        byte[] data = mpkFileService.generatorCode(id, remark,zipFileName);
 
         response.reset();
         response.setHeader("Content-Disposition", "attachment; filename=\"" + URLEncoder.encode(zipFileName, "UTF-8") + "\"");
@@ -88,7 +84,7 @@
     public void packageModel(String ids ,String projectId,String log ,String projectName,String version,String zipFileName,HttpServletResponse response) throws IOException {
         byte[] data;
         try {
-            data = mdkFileService.packageModel(Arrays.asList(ids.split(",")),projectId,projectName,zipFileName,log,version);
+            data = mpkFileService.packageModel(Arrays.asList(ids.split(",")),projectId,projectName,zipFileName,log,version);
         } catch (InterruptedException e) {
             throw new RuntimeException("模型打包失败",e);
         }
@@ -104,7 +100,7 @@
     @PostMapping("/upload")
     @Operation(summary = "python文件上传")
     public CommonResult<Map<String,String>> importExcel(@RequestParam("file") MultipartFile file) throws Exception {
-        Map<String,String> result = mdkFileService.savePyFile(file);
+        Map<String,String> result = mpkFileService.savePyFile(file);
         return success(result);
     }
 }
\ No newline at end of file
diff --git a/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/dao/MethodSettingDao.java b/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/dao/MethodSettingDao.java
new file mode 100644
index 0000000..69421f8
--- /dev/null
+++ b/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/dao/MethodSettingDao.java
@@ -0,0 +1,14 @@
+package com.iailab.module.model.mpk.dao;
+
+import com.iailab.framework.common.dao.BaseDao;
+import com.iailab.module.model.mpk.entity.MethodSettingEntity;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * @description:
+ * @author: dzd
+ * @date: 2024/9/14 15:11
+ **/
+@Mapper
+public interface MethodSettingDao extends BaseDao<MethodSettingEntity> {
+}
diff --git a/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/dao/SettingSelectDao.java b/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/dao/SettingSelectDao.java
new file mode 100644
index 0000000..731ce69
--- /dev/null
+++ b/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/dao/SettingSelectDao.java
@@ -0,0 +1,14 @@
+package com.iailab.module.model.mpk.dao;
+
+import com.iailab.framework.common.dao.BaseDao;
+import com.iailab.module.model.mpk.entity.SettingSelectEntity;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * @description:
+ * @author: dzd
+ * @date: 2024/9/14 15:11
+ **/
+@Mapper
+public interface SettingSelectDao extends BaseDao<SettingSelectEntity> {
+}
diff --git a/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/dto/MethodSettingDTO.java b/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/dto/MethodSettingDTO.java
new file mode 100644
index 0000000..fce3f21
--- /dev/null
+++ b/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/dto/MethodSettingDTO.java
@@ -0,0 +1,64 @@
+package com.iailab.module.model.mpk.dto;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * @description: 方法参数关联表
+ * @author: dzd
+ * @date: 2024/9/13 15:49
+ **/
+@Data
+public class MethodSettingDTO implements Serializable {
+
+    /**
+     * id
+     */
+    private String id;
+
+    /**
+     * '方法id'
+     */
+    private String methodId;
+
+    /**
+     * key
+     */
+    private String settingKey;
+
+    /**
+     * 参数名称
+     */
+    private String name;
+
+    /**
+     * 参数默认值
+     */
+    private String value;
+
+    /**
+     * 输入类型
+     */
+    private String type;
+
+    /**
+     * 参数类型
+     */
+    private String valueType;
+
+    /**
+     * 最大值
+     */
+    private Integer max;
+
+    /**
+     * 最小值
+     */
+    private Integer min;
+
+    private List<SettingSelectDTO> settingSelects;
+}
\ No newline at end of file
diff --git a/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/dto/ModelMethodDTO.java b/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/dto/ModelMethodDTO.java
index 44681c3..84df9c0 100644
--- a/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/dto/ModelMethodDTO.java
+++ b/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/dto/ModelMethodDTO.java
@@ -1,10 +1,9 @@
 package com.iailab.module.model.mpk.dto;
 
-import com.baomidou.mybatisplus.annotation.TableId;
-import com.baomidou.mybatisplus.annotation.TableName;
 import lombok.Data;
 
 import java.io.Serializable;
+import java.util.List;
 
 /**
  * @description: MPK模型方法
@@ -24,4 +23,8 @@
     private Integer dataLength;
 
     private Integer model;
+
+    private String resultKey;
+
+    private List<MethodSettingDTO> methodSettings;
 }
\ No newline at end of file
diff --git a/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/dto/MpkFileDTO.java b/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/dto/MpkFileDTO.java
index 815a5ad..e47d22f 100644
--- a/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/dto/MpkFileDTO.java
+++ b/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/dto/MpkFileDTO.java
@@ -20,6 +20,8 @@
 
     private String pyName;
 
+    private String pyChineseName;
+
     private String filePath;
 
     private String pyType;
@@ -29,6 +31,12 @@
     private String className;
 
     private String pyModule;
+
+    private String icon;
+
+    private String menuName;
+
+    private String groupName;
 
     private String remark;
 
@@ -40,5 +48,5 @@
 
     private Date createDate;
 
-    private List<ModelMethodEntity> modelMethods;
+    private List<ModelMethodDTO> modelMethods;
 }
\ No newline at end of file
diff --git a/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/dto/SettingSelectDTO.java b/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/dto/SettingSelectDTO.java
new file mode 100644
index 0000000..d9e69af
--- /dev/null
+++ b/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/dto/SettingSelectDTO.java
@@ -0,0 +1,36 @@
+package com.iailab.module.model.mpk.dto;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * @description: 参数选项关联表
+ * @author: dzd
+ * @date: 2024/9/13 15:49
+ **/
+@Data
+public class SettingSelectDTO implements Serializable {
+
+    /**
+     * id
+     */
+    private String id;
+
+    /**
+     * '参数id'
+     */
+    private String settingId;
+
+    /**
+     * key
+     */
+    private String selectKey;
+
+    /**
+     * 名称
+     */
+    private String name;
+}
\ No newline at end of file
diff --git a/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/entity/MethodSettingEntity.java b/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/entity/MethodSettingEntity.java
new file mode 100644
index 0000000..2a7b1e6
--- /dev/null
+++ b/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/entity/MethodSettingEntity.java
@@ -0,0 +1,64 @@
+package com.iailab.module.model.mpk.entity;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import org.checkerframework.checker.units.qual.min;
+
+import java.io.Serializable;
+
+/**
+ * @description: 方法参数关联表
+ * @author: dzd
+ * @date: 2024/9/13 15:49
+ **/
+@Data
+@TableName("t_mpk_method_setting")
+public class MethodSettingEntity implements Serializable {
+
+    /**
+     * id
+     */
+    @TableId
+    private String id;
+
+    /**
+     * 方法id
+     */
+    private String methodId;
+
+    /**
+     * key
+     */
+    private String settingKey;
+
+    /**
+     * 参数名称
+     */
+    private String name;
+
+    /**
+     * 参数默认值
+     */
+    private String value;
+
+    /**
+     * 输入类型
+     */
+    private String type;
+
+    /**
+     * 参数类型
+     */
+    private String valueType;
+
+    /**
+     * 最大值
+     */
+    private Integer max;
+
+    /**
+     * 最小值
+     */
+    private Integer min;
+}
\ No newline at end of file
diff --git a/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/entity/ModelMethodEntity.java b/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/entity/ModelMethodEntity.java
index 117b220..5b9b24d 100644
--- a/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/entity/ModelMethodEntity.java
+++ b/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/entity/ModelMethodEntity.java
@@ -43,4 +43,8 @@
      * 是否有model(0:否,1:是)
      */
     private Integer model;
+    /**
+     * 结果key
+     */
+    private String resultKey;
 }
\ No newline at end of file
diff --git a/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/entity/MpkFileEntity.java b/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/entity/MpkFileEntity.java
index 146a966..29a1770 100644
--- a/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/entity/MpkFileEntity.java
+++ b/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/entity/MpkFileEntity.java
@@ -32,6 +32,11 @@
     private String pyName;
 
     /**
+     * 模型中文名称
+     */
+    private String pyChineseName;
+
+    /**
      * 源文件保存路径
      */
     private String filePath;
@@ -57,6 +62,21 @@
     private String pyModule;
 
     /**
+     * icon图片名
+     */
+        private String icon;
+
+    /**
+     * 所属菜单
+     */
+    private String menuName;
+
+    /**
+     * 所属组
+     */
+    private String groupName;
+
+    /**
      * 备注
      */
     private String remark;
diff --git a/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/entity/SettingSelectEntity.java b/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/entity/SettingSelectEntity.java
new file mode 100644
index 0000000..e636a46
--- /dev/null
+++ b/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/entity/SettingSelectEntity.java
@@ -0,0 +1,38 @@
+package com.iailab.module.model.mpk.entity;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * @description: 参数选项关联表
+ * @author: dzd
+ * @date: 2024/9/13 15:49
+ **/
+@Data
+@TableName("t_mpk_setting_select")
+public class SettingSelectEntity implements Serializable {
+
+    /**
+     * id
+     */
+    @TableId
+    private String id;
+
+    /**
+     * '参数id'
+     */
+    private String settingId;
+
+    /**
+     * key
+     */
+    private String selectKey;
+
+    /**
+     * 名称
+     */
+    private String name;
+}
\ No newline at end of file
diff --git a/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/service/MethodSettingService.java b/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/service/MethodSettingService.java
new file mode 100644
index 0000000..cce6830
--- /dev/null
+++ b/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/service/MethodSettingService.java
@@ -0,0 +1,14 @@
+package com.iailab.module.model.mpk.service;
+
+import com.iailab.framework.common.service.BaseService;
+import com.iailab.module.model.mpk.entity.MethodSettingEntity;
+
+import java.util.Map;
+
+/**
+ * @description:
+ * @author: dzd
+ * @date: 2024/9/14 15:10
+ **/
+public interface MethodSettingService extends BaseService<MethodSettingEntity> {
+}
\ No newline at end of file
diff --git a/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/service/MdkFileService.java b/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/service/MpkFileService.java
similarity index 94%
rename from iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/service/MdkFileService.java
rename to iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/service/MpkFileService.java
index 9f684a4..53da080 100644
--- a/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/service/MdkFileService.java
+++ b/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/service/MpkFileService.java
@@ -15,7 +15,7 @@
  * @Description
  * @createTime 2024年08月14日
  */
-public interface MdkFileService extends BaseService<MpkFileEntity> {
+public interface MpkFileService extends BaseService<MpkFileEntity> {
 
     PageData<MpkFileDTO> page(Map<String, Object> params);
 
diff --git a/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/service/SettingSelectService.java b/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/service/SettingSelectService.java
new file mode 100644
index 0000000..d29955b
--- /dev/null
+++ b/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/service/SettingSelectService.java
@@ -0,0 +1,16 @@
+package com.iailab.module.model.mpk.service;
+
+import com.iailab.framework.common.service.BaseService;
+import com.iailab.module.model.mpk.entity.SettingSelectEntity;
+
+import java.util.Map;
+
+/**
+ * @description:
+ * @author: dzd
+ * @date: 2024/9/14 15:10
+ **/
+public interface SettingSelectService extends BaseService<SettingSelectEntity> {
+
+    void deleteByMap(Map<String, Object> map);
+}
\ No newline at end of file
diff --git a/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/service/impl/MethodSettingServiceImpl.java b/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/service/impl/MethodSettingServiceImpl.java
new file mode 100644
index 0000000..1010171
--- /dev/null
+++ b/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/service/impl/MethodSettingServiceImpl.java
@@ -0,0 +1,22 @@
+package com.iailab.module.model.mpk.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.iailab.framework.common.service.impl.BaseServiceImpl;
+import com.iailab.module.model.mpk.dao.MethodSettingDao;
+import com.iailab.module.model.mpk.entity.MethodSettingEntity;
+import com.iailab.module.model.mpk.service.MethodSettingService;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.stereotype.Service;
+
+import java.util.Map;
+
+/**
+ * @description:
+ * @author: dzd
+ * @date: 2024/9/14 15:12
+ **/
+@Slf4j
+@Service
+public class MethodSettingServiceImpl extends BaseServiceImpl<MethodSettingDao, MethodSettingEntity> implements MethodSettingService {
+}
\ No newline at end of file
diff --git a/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/service/impl/MdkFileServiceImpl.java b/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/service/impl/MpkFileServiceImpl.java
similarity index 88%
rename from iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/service/impl/MdkFileServiceImpl.java
rename to iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/service/impl/MpkFileServiceImpl.java
index b37817a..d0bb2f8 100644
--- a/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/service/impl/MdkFileServiceImpl.java
+++ b/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/service/impl/MpkFileServiceImpl.java
@@ -15,13 +15,8 @@
 import com.iailab.module.model.mpk.common.MdkConstant;
 import com.iailab.module.model.mpk.common.utils.GenUtils;
 import com.iailab.module.model.mpk.dao.MpkFileDao;
-import com.iailab.module.model.mpk.dto.GeneratorCodeHistoryDTO;
-import com.iailab.module.model.mpk.dto.MpkFileDTO;
-import com.iailab.module.model.mpk.dto.ProjectPackageHistoryDTO;
-import com.iailab.module.model.mpk.entity.GeneratorCodeHistoryEntity;
-import com.iailab.module.model.mpk.entity.ModelMethodEntity;
-import com.iailab.module.model.mpk.entity.MpkFileEntity;
-import com.iailab.module.model.mpk.entity.ProjectPackageHistoryModelEntity;
+import com.iailab.module.model.mpk.dto.*;
+import com.iailab.module.model.mpk.entity.*;
 import com.iailab.module.model.mpk.service.*;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.io.FileUtils;
@@ -36,7 +31,6 @@
 import org.springframework.web.multipart.MultipartFile;
 
 import javax.annotation.PostConstruct;
-import javax.annotation.Resource;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.IOException;
@@ -53,7 +47,7 @@
  */
 @Slf4j
 @Service
-public class MdkFileServiceImpl extends BaseServiceImpl<MpkFileDao, MpkFileEntity> implements MdkFileService {
+public class MpkFileServiceImpl extends BaseServiceImpl<MpkFileDao, MpkFileEntity> implements MpkFileService {
 
     @Autowired
     private GeneratorCodeHistoryService generatorCodeHistoryService;
@@ -63,6 +57,10 @@
     private ProjectPackageHistoryService projectPackageHistoryService;
     @Autowired
     private ModelMethodService modelMethodService;
+    @Autowired
+    private MethodSettingService methodSettingService;
+    @Autowired
+    private SettingSelectService settingSelectService;
     @Autowired
     private ProjectPackageHistoryModelService projectPackageHistoryModelService;
 
@@ -135,22 +133,56 @@
         updateById(entity);
 
         String mpkId = dto.getId();
-        // 删除模型方法
+        // 删除模型方法 会级联删除setting和select
         deleteModelMethod(mpkId);
 
         // 添加模型方法
         insertModelMethod(dto.getModelMethods(),mpkId);
     }
 
-    private void insertModelMethod(List<ModelMethodEntity> modelMethods,String mpkId) {
+    private void insertModelMethod(List<ModelMethodDTO> modelMethods, String mpkId) {
+        List<MethodSettingDTO> methodSettingList = new ArrayList<>();
         if (!CollectionUtils.isEmpty(modelMethods)) {
             modelMethods.forEach(e -> {
-                e.setId(UUID.randomUUID().toString());
+                String methodId = UUID.randomUUID().toString();
+                e.setId(methodId);
                 e.setMpkFileId(mpkId);
+
+                e.getMethodSettings().forEach(s -> {
+                    s.setId(UUID.randomUUID().toString());
+                    s.setMethodId(methodId);
+                    methodSettingList.add(s);
+                });
+
             });
-            modelMethodService.insertBatch(modelMethods);
+            modelMethodService.insertBatch(ConvertUtils.sourceToTarget(modelMethods, ModelMethodEntity.class));
+
+            //添加setting
+            insertMethodSetting(methodSettingList);
         }
     }
+
+    private void insertMethodSetting(List<MethodSettingDTO> methodSettings) {
+        List<SettingSelectEntity> settingSelectList = new ArrayList<>();
+        if (!CollectionUtils.isEmpty(methodSettings)) {
+            methodSettings.forEach(e -> {
+                String settingId = UUID.randomUUID().toString();
+                e.setId(settingId);
+
+                e.getSettingSelects().forEach(s -> {
+                    s.setId(UUID.randomUUID().toString());
+                    s.setSettingId(settingId);
+                    settingSelectList.add(ConvertUtils.sourceToTarget(s,SettingSelectEntity.class));
+                });
+
+            });
+            methodSettingService.insertBatch(ConvertUtils.sourceToTarget(methodSettings, MethodSettingEntity.class));
+
+            //添加select
+            settingSelectService.insertBatch(settingSelectList);
+        }
+    }
+
     private void deleteModelMethod(String mpkId) {
         Map<String,Object> map = new HashMap<>();
         map.put("mpkFileId", mpkId);
@@ -171,9 +203,6 @@
             }
         }
 
-        //删除
-        deleteById(id);
-
         //删除备份文件
         Map<String,Object> map1 = new HashMap<>();
         map1.put("mdkId",id);
@@ -186,17 +215,19 @@
             }
         });
 
-        //删除生成历史
+        //删除 会级联删除掉关联表
+        deleteById(id);
 
-        generatorCodeHistoryService.deleteByMap(map1);
+        //删除生成历史
+//        generatorCodeHistoryService.deleteByMap(map1);
 
         //删除关联项目
-        Map<String,Object> map = new HashMap<>();
-        map.put("modelId",id);
-        projectModelService.deleteByMap(map);
+//        Map<String,Object> map = new HashMap<>();
+//        map.put("modelId",id);
+//        projectModelService.deleteByMap(map);
 
         //删除模型方法
-        deleteModelMethod(id);
+//        deleteModelMethod(id);
 
     }
 
@@ -354,7 +385,7 @@
             entity.setPkgName(e.getPkgName());
             entity.setPyModule(e.getPyModule());
             entity.setRemark(e.getRemark());
-            List<ModelMethodEntity> methods = e.getModelMethods();
+            List<ModelMethodDTO> methods = e.getModelMethods();
             if (!CollectionUtils.isEmpty(methods)) {
                 entity.setMethodInfo(JSON.toJSONString(methods));
             }
diff --git a/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/service/impl/ProjectServiceImpl.java b/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/service/impl/ProjectServiceImpl.java
index 5e45ff9..dc0a2fb 100644
--- a/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/service/impl/ProjectServiceImpl.java
+++ b/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/service/impl/ProjectServiceImpl.java
@@ -137,16 +137,9 @@
     @Override
     @Transactional(rollbackFor = Exception.class)
     public void delete(String id) {
-
-        //删除
-        baseDao.deleteById(id);
-
-        //删除模型关联
+        //删除备份文件
         Map<String,Object> map = new HashMap<>();
         map.put("projectId",id);
-        projectModelService.deleteByMap(map);
-
-        //删除备份文件
         List<ProjectPackageHistoryDTO> list = projectPackageHistoryService.list(map);
         list.forEach(e -> {
             File file = new File(e.getFilePath());
@@ -156,11 +149,17 @@
             }
         });
 
+        //删除 (级联删除)
+        baseDao.deleteById(id);
+
+        //删除模型关联
+//        projectModelService.deleteByMap(map);
+
         //删除打包历史
-        projectPackageHistoryService.deleteByMap(map);
+//        projectPackageHistoryService.deleteByMap(map);
 
         //删除打包历史模型关联
-        projectPackageHistoryModelService.deleteByMap(map);
+//        projectPackageHistoryModelService.deleteByMap(map);
     }
 
     @Override
diff --git a/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/service/impl/SettingSelectServiceImpl.java b/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/service/impl/SettingSelectServiceImpl.java
new file mode 100644
index 0000000..1bfba31
--- /dev/null
+++ b/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/service/impl/SettingSelectServiceImpl.java
@@ -0,0 +1,37 @@
+package com.iailab.module.model.mpk.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.iailab.framework.common.service.impl.BaseServiceImpl;
+import com.iailab.module.model.mpk.dao.SettingSelectDao;
+import com.iailab.module.model.mpk.entity.SettingSelectEntity;
+import com.iailab.module.model.mpk.service.SettingSelectService;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.stereotype.Service;
+
+import java.util.Map;
+
+/**
+ * @description:
+ * @author: dzd
+ * @date: 2024/9/14 15:13
+ **/
+@Slf4j
+@Service
+public class SettingSelectServiceImpl extends BaseServiceImpl<SettingSelectDao, SettingSelectEntity> implements SettingSelectService {
+
+
+    @Override
+    public void deleteByMap(Map<String, Object> map) {
+        baseDao.delete(getWrapper(map));
+    }
+
+    private QueryWrapper<SettingSelectEntity> getWrapper(Map<String, Object> params) {
+        String settingId = (String) params.get("settingId");
+
+        QueryWrapper<SettingSelectEntity> wrapper = new QueryWrapper<>();
+        wrapper.eq(StringUtils.isNotBlank(settingId), "setting_id", settingId);
+
+        return wrapper;
+    }
+}
\ No newline at end of file
diff --git a/iailab-module-model/iailab-module-model-biz/src/main/resources/mapper/mpk/MpkFileDao.xml b/iailab-module-model/iailab-module-model-biz/src/main/resources/mapper/mpk/MpkFileDao.xml
index 32ca7eb..3451506 100644
--- a/iailab-module-model/iailab-module-model-biz/src/main/resources/mapper/mpk/MpkFileDao.xml
+++ b/iailab-module-model/iailab-module-model-biz/src/main/resources/mapper/mpk/MpkFileDao.xml
@@ -5,20 +5,41 @@
     <resultMap id="mpkFile" type="com.iailab.module.model.mpk.dto.MpkFileDTO">
         <id property="id" column="id"/>
         <result property="pyName" column="py_name"/>
+        <result property="pyChineseName" column="py_chinese_name"/>
         <result property="filePath" column="file_path"/>
+        <result property="pyType" column="py_type"/>
         <result property="pkgName" column="pkg_name"/>
         <result property="className" column="class_name"/>
         <result property="pyModule" column="py_module"/>
+        <result property="icon" column="icon"/>
+        <result property="menuName" column="menu_name"/>
+        <result property="groupName" column="group_name"/>
         <result property="remark" column="remark"/>
         <result property="creator" column="creator"/>
         <result property="createDate" column="create_date"/>
         <result property="updater" column="updater"/>
         <result property="updateDate" column="update_date"/>
-        <collection property="modelMethods" ofType="com.iailab.module.model.mpk.entity.ModelMethodEntity">
+        <collection property="modelMethods" ofType="com.iailab.module.model.mpk.dto.ModelMethodDTO">
             <id property="id" column="method_id"/>
             <result property="methodName" column="method_name"/>
             <result property="dataLength" column="data_length"/>
             <result property="model" column="model"/>
+            <result property="resultKey" column="result_key"/>
+            <collection property="methodSettings" ofType="com.iailab.module.model.mpk.dto.MethodSettingDTO">
+                <id property="id" column="setting_id"/>
+                <result property="settingKey" column="setting_key"/>
+                <result property="name" column="setting_name"/>
+                <result property="value" column="value"/>
+                <result property="type" column="type"/>
+                <result property="valueType" column="value_type"/>
+                <result property="max" column="max"/>
+                <result property="min" column="min"/>
+                <collection property="settingSelects" ofType="com.iailab.module.model.mpk.dto.SettingSelectDTO">
+                    <id property="id" column="select_id"/>
+                    <result property="selectKey" column="select_key"/>
+                    <result property="name" column="select_name"/>
+                </collection>
+            </collection>
         </collection>
     </resultMap>
 
@@ -28,10 +49,24 @@
             b.id method_id,
             b.method_name,
             b.data_length,
-            b.model
+            b.model,
+            b.result_key,
+            c.id setting_id,
+            c.setting_key,
+            c.name setting_name,
+            c.value,
+            c.type,
+            c.value_type,
+            c.max,
+            c.min,
+            d.id select_id,
+            d.select_key,
+            d.name select_name
         FROM
             t_mpk_file a
             LEFT JOIN t_mpk_model_method b ON a.id = b.mpk_file_id
+            LEFT JOIN t_mpk_method_setting c ON b.id = c.method_id
+            LEFT JOIN t_mpk_setting_select d ON c.id = d.setting_id
         WHERE a.id = #{id}
     </select>
     <select id="selectByIds" resultMap="mpkFile">
@@ -40,10 +75,24 @@
             b.id method_id,
             b.method_name,
             b.data_length,
-            b.model
+            b.model,
+            b.result_key,
+            c.id setting_id,
+            c.setting_key,
+            c.name setting_name,
+            c.value,
+            c.type,
+            c.value_type,
+            c.max,
+            c.min,
+            d.id select_id,
+            d.select_key,
+            d.name select_name
         FROM
             t_mpk_file a
                 LEFT JOIN t_mpk_model_method b ON a.id = b.mpk_file_id
+                LEFT JOIN t_mpk_method_setting c ON b.id = c.method_id
+                LEFT JOIN t_mpk_setting_select d ON c.id = d.setting_id
         WHERE a.id in
         <foreach collection="ids" item="item" open="(" close=")" separator=",">
             #{item}
@@ -59,7 +108,22 @@
     </select>
     <select id="getProjectModel" resultMap="mpkFile">
         SELECT
-            t3.*,t4.method_name,t4.data_length,t4.model
+            t3.*,
+            t4.method_name,
+            t4.data_length,
+            t4.model,
+            t4.result_key,
+            t5.id setting_id,
+            t5.setting_key,
+            t5.name setting_name,
+            t5.value,
+            t5.type,
+            t5.value_type,
+            t5.max,
+            t5.min,
+            t6.id select_id,
+            t6.select_key,
+            t6.name select_name
         FROM
             (
                 SELECT
@@ -82,5 +146,7 @@
                     LIMIT #{params.offset},#{params.pageSize}
             ) t3
                 LEFT JOIN t_mpk_model_method t4 ON t3.id = t4.mpk_file_id
+                LEFT JOIN t_mpk_method_setting t5 ON t4.id = t5.method_id
+                LEFT JOIN t_mpk_setting_select t6 ON t5.id = t6.setting_id
     </select>
 </mapper>
\ No newline at end of file
diff --git a/iailab-module-system/iailab-module-system-api/src/main/java/com/iailab/module/system/api/app/AppApi.java b/iailab-module-system/iailab-module-system-api/src/main/java/com/iailab/module/system/api/app/AppApi.java
new file mode 100644
index 0000000..ff3c870
--- /dev/null
+++ b/iailab-module-system/iailab-module-system-api/src/main/java/com/iailab/module/system/api/app/AppApi.java
@@ -0,0 +1,31 @@
+package com.iailab.module.system.api.app;
+
+import com.iailab.framework.common.pojo.CommonResult;
+import com.iailab.module.system.api.app.dto.AppMenuRespDTO;
+import com.iailab.module.system.api.app.dto.AppRespDTO;
+import com.iailab.module.system.enums.ApiConstants;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+
+import java.util.List;
+
+@FeignClient(name = ApiConstants.NAME)
+@Tag(name = "RPC 服务 - 应用菜单")
+public interface AppApi {
+
+    String PREFIX = ApiConstants.PREFIX + "/app";
+
+//    @GetMapping(PREFIX + "/get-menu")
+//    @Operation(summary = "获得应用信息")
+//    @Parameter(name = "id", description = "应用编号", example = "1024", required = true)
+//    CommonResult<List<AppMenuRespDTO>> getAppMenuList(@RequestParam("id") Long id);
+
+    @GetMapping(PREFIX + "/list")
+    @Operation(summary = "获得应用菜单列表")
+    CommonResult<List<AppRespDTO>> getAppList();
+
+}
diff --git a/iailab-module-system/iailab-module-system-api/src/main/java/com/iailab/module/system/api/app/AppMenuApi.java b/iailab-module-system/iailab-module-system-api/src/main/java/com/iailab/module/system/api/app/AppMenuApi.java
new file mode 100644
index 0000000..66adc90
--- /dev/null
+++ b/iailab-module-system/iailab-module-system-api/src/main/java/com/iailab/module/system/api/app/AppMenuApi.java
@@ -0,0 +1,32 @@
+package com.iailab.module.system.api.app;
+
+import com.iailab.framework.common.pojo.CommonResult;
+import com.iailab.module.system.api.app.dto.AppMenuRespDTO;
+import com.iailab.module.system.api.app.dto.AppRespDTO;
+import com.iailab.module.system.enums.ApiConstants;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.List;
+
+@FeignClient(name = ApiConstants.NAME)
+@Tag(name = "RPC 服务 - 应用菜单")
+public interface AppMenuApi {
+
+    String PREFIX = ApiConstants.PREFIX + "/app-menu";
+
+    @GetMapping(PREFIX + "/get-menu")
+    @Operation(summary = "获得应用信息")
+    @Parameter(name = "id", description = "应用编号", example = "1024", required = true)
+    CommonResult<List<AppMenuRespDTO>> getAppMenuList(@RequestParam("id") Long id);
+
+    @GetMapping(PREFIX + "/list")
+    @Operation(summary = "获得应用菜单列表")
+    CommonResult<List<AppRespDTO>> getAppList();
+
+}
diff --git a/iailab-module-system/iailab-module-system-api/src/main/java/com/iailab/module/system/api/app/dto/AppMenuRespDTO.java b/iailab-module-system/iailab-module-system-api/src/main/java/com/iailab/module/system/api/app/dto/AppMenuRespDTO.java
new file mode 100644
index 0000000..855e543
--- /dev/null
+++ b/iailab-module-system/iailab-module-system-api/src/main/java/com/iailab/module/system/api/app/dto/AppMenuRespDTO.java
@@ -0,0 +1,80 @@
+package com.iailab.module.system.api.app.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Schema(description = "RPC 服务 - 应用菜单 Response DTO")
+@Data
+public class AppMenuRespDTO {
+
+    @Schema(description = "菜单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    private Long id;
+
+    @Schema(description = "菜单名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "平台")
+    @NotBlank(message = "菜单名称不能为空")
+    @Size(max = 50, message = "菜单名称长度不能超过50个字符")
+    private String name;
+
+    @Schema(description = "权限标识,仅菜单类型为按钮时,才需要传递", example = "sys:menu:add")
+    @Size(max = 100)
+    private String permission;
+
+    @Schema(description = "类型,参见 MenuTypeEnum 枚举类", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "菜单类型不能为空")
+    private Integer type;
+
+    @Schema(description = "显示顺序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    @NotNull(message = "显示顺序不能为空")
+    private Integer sort;
+
+    @Schema(description = "父菜单 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    @NotNull(message = "父菜单 ID 不能为空")
+    private Long parentId;
+
+    @Schema(description = "路由地址,仅菜单类型为菜单或者目录时,才需要传", example = "post")
+    @Size(max = 200, message = "路由地址不能超过200个字符")
+    private String path;
+
+    @Schema(description = "菜单图标,仅菜单类型为菜单或者目录时,才需要传", example = "/menu/list")
+    private String icon;
+
+    @Schema(description = "组件路径,仅菜单类型为菜单时,才需要传", example = "system/post/index")
+    @Size(max = 200, message = "组件路径不能超过255个字符")
+    private String component;
+
+    @Schema(description = "组件名", example = "SystemUser")
+    private String componentName;
+
+    @Schema(description = "状态,见 CommonStatusEnum 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "状态不能为空")
+    private Integer status;
+
+    @Schema(description = "是否可见", example = "false")
+    private Boolean visible;
+
+    @Schema(description = "是否缓存", example = "false")
+    private Boolean keepAlive;
+
+    @Schema(description = "是否总是显示", example = "false")
+    private Boolean alwaysShow;
+
+    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "时间戳格式")
+    private LocalDateTime createTime;
+
+    /**
+     * 应用类型(1-系统菜单, 2-应用菜单)
+     */
+    private Integer appType;
+
+    /**
+     * 子路由
+     */
+    private List<AppMenuRespDTO> children;
+
+}
diff --git a/iailab-module-system/iailab-module-system-api/src/main/java/com/iailab/module/system/api/app/dto/AppMenuSimpleRespDTO.java b/iailab-module-system/iailab-module-system-api/src/main/java/com/iailab/module/system/api/app/dto/AppMenuSimpleRespDTO.java
new file mode 100644
index 0000000..91f8b0f
--- /dev/null
+++ b/iailab-module-system/iailab-module-system-api/src/main/java/com/iailab/module/system/api/app/dto/AppMenuSimpleRespDTO.java
@@ -0,0 +1,22 @@
+package com.iailab.module.system.api.app.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "应用菜单精简信息 Response VO")
+@Data
+public class AppMenuSimpleRespDTO {
+
+    @Schema(description = "菜单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    private Long id;
+
+    @Schema(description = "菜单名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "平台")
+    private String name;
+
+    @Schema(description = "父菜单 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    private Long parentId;
+
+    @Schema(description = "类型,参见 MenuTypeEnum 枚举类", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Integer type;
+
+}
diff --git a/iailab-module-system/iailab-module-system-api/src/main/java/com/iailab/module/system/api/app/dto/AppRespDTO.java b/iailab-module-system/iailab-module-system-api/src/main/java/com/iailab/module/system/api/app/dto/AppRespDTO.java
new file mode 100644
index 0000000..88a7a23
--- /dev/null
+++ b/iailab-module-system/iailab-module-system-api/src/main/java/com/iailab/module/system/api/app/dto/AppRespDTO.java
@@ -0,0 +1,75 @@
+package com.iailab.module.system.api.app.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * @author PanZhibao
+ * @Description
+ * @createTime 2024年08月18日
+ */
+@Schema(description = "管理后台 - 应用 Response VO")
+@Data
+public class AppRespDTO {
+
+    @Schema(description = "应用编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    private Long id;
+
+    @Schema(description = "应用编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "应用编号")
+    private String appCode;
+
+    @Schema(description = "应用名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "应用名称")
+    private String appName;
+
+    @Schema(description = "应用域名", requiredMode = Schema.RequiredMode.REQUIRED, example = "应用域名")
+    private String appDomain;
+
+    @Schema(description = "接口域名", requiredMode = Schema.RequiredMode.REQUIRED, example = "接口域名")
+    private String apiDomain;
+
+    @Schema(description = "应用账号", requiredMode = Schema.RequiredMode.REQUIRED, example = "应用账号")
+    private String appKey;
+
+    @Schema(description = "应用密码", requiredMode = Schema.RequiredMode.REQUIRED, example = "应用密码")
+    private String appSecret;
+
+    @Schema(description = "应用分组", requiredMode = Schema.RequiredMode.REQUIRED, example = "应用分组")
+    private String appGroup;
+
+    @Schema(description = "应用加载类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "应用加载类型")
+    private Integer loadType;
+
+    @Schema(description = "应用图标", requiredMode = Schema.RequiredMode.REQUIRED, example = "应用图标")
+    private String icon;
+
+    @Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "排序")
+    private Integer orderNum;
+
+    @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "状态")
+    private Integer status;
+
+    @Schema(description = "开发者ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "开发者ID")
+    private String devId;
+
+    @Schema(description = "开发者名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "开发者名称")
+    private String devName;
+
+    @Schema(description = "备注", requiredMode = Schema.RequiredMode.REQUIRED, example = "备注")
+    private String remark;
+
+    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime createTime;
+
+    @Schema(description = "租户ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "租户ID")
+    private Long tenantId;
+
+    @Schema(description = "应用菜单ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "应用菜单ID")
+    private Long appMenuId;
+
+    /**
+     * 应用类型(1-系统菜单, 2-应用菜单)
+     */
+    private Integer appType;
+}
diff --git a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/api/app/AppApiImpl.java b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/api/app/AppApiImpl.java
new file mode 100644
index 0000000..d66168a
--- /dev/null
+++ b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/api/app/AppApiImpl.java
@@ -0,0 +1,55 @@
+package com.iailab.module.system.api.app;
+
+import cn.hutool.core.collection.CollUtil;
+import com.iailab.framework.common.pojo.CommonResult;
+import com.iailab.framework.common.util.object.BeanUtils;
+import com.iailab.module.system.api.app.dto.AppMenuRespDTO;
+import com.iailab.module.system.api.app.dto.AppRespDTO;
+import com.iailab.module.system.convert.app.AppConvert;
+import com.iailab.module.system.dal.dataobject.app.AppDO;
+import com.iailab.module.system.dal.dataobject.app.AppMenuDO;
+import com.iailab.module.system.service.app.AppMenuService;
+import com.iailab.module.system.service.app.AppService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.util.*;
+
+import static com.iailab.framework.common.pojo.CommonResult.success;
+import static com.iailab.framework.common.util.collection.CollectionUtils.convertSet;
+
+@RestController // 提供 RESTful API 接口,给 Feign 调用
+@Validated
+public class AppApiImpl implements AppApi {
+
+    @Resource
+    private AppService appService;
+
+//    @Override
+//    public CommonResult<List<AppMenuRespDTO>> getAppMenuList(Long id) {
+//        List<AppMenuDO> children = new LinkedList<>();
+//        // 遍历每一层
+//        Collection<Long> parentIds = Collections.singleton(id);
+//        for (int i = 0; i < Short.MAX_VALUE; i++) { // 使用 Short.MAX_VALUE 避免 bug 场景下,存在死循环
+//            // 查询当前层,所有的子应用菜单
+//            List<AppMenuDO> menus = appMenuService.selectListByParentId(parentIds);
+//            // 1. 如果没有子菜单,则结束遍历
+//            if (CollUtil.isEmpty(menus)) {
+//                break;
+//            }
+//            // 2. 如果有子应用菜单,继续遍历
+//            children.addAll(menus);
+//            parentIds = convertSet(menus, AppMenuDO::getId);
+//        }
+////        children = appMenuService.filterDisableMenus(children);
+//        return success(AppConvert.INSTANCE.buildMenuTree(id, children));
+//    }
+
+    @Override
+    public CommonResult<List<AppRespDTO>> getAppList() {
+        List<AppDO> list = appService.getList();
+        list.sort(Comparator.comparing(AppDO::getOrderNum));
+        return success(BeanUtils.toBean(list, AppRespDTO.class));
+    }
+}
diff --git a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/api/app/AppMenuApiImpl.java b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/api/app/AppMenuApiImpl.java
new file mode 100644
index 0000000..0b103b2
--- /dev/null
+++ b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/api/app/AppMenuApiImpl.java
@@ -0,0 +1,59 @@
+package com.iailab.module.system.api.app;
+
+import cn.hutool.core.collection.CollUtil;
+import com.iailab.framework.common.pojo.CommonResult;
+import com.iailab.framework.common.util.object.BeanUtils;
+import com.iailab.module.system.api.app.dto.AppMenuRespDTO;
+import com.iailab.module.system.api.app.dto.AppRespDTO;
+import com.iailab.module.system.controller.admin.app.vo.AppRespVO;
+import com.iailab.module.system.convert.app.AppConvert;
+import com.iailab.module.system.dal.dataobject.app.AppDO;
+import com.iailab.module.system.dal.dataobject.app.AppMenuDO;
+import com.iailab.module.system.service.app.AppMenuService;
+import com.iailab.module.system.service.app.AppService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.util.*;
+
+import static com.iailab.framework.common.pojo.CommonResult.success;
+import static com.iailab.framework.common.util.collection.CollectionUtils.convertSet;
+
+@RestController // 提供 RESTful API 接口,给 Feign 调用
+@Validated
+public class AppMenuApiImpl implements AppMenuApi {
+
+    @Resource
+    private AppMenuService appMenuService;
+
+    @Resource
+    private AppService appService;
+
+    @Override
+    public CommonResult<List<AppMenuRespDTO>> getAppMenuList(Long id) {
+        List<AppMenuDO> children = new LinkedList<>();
+        // 遍历每一层
+        Collection<Long> parentIds = Collections.singleton(id);
+        for (int i = 0; i < Short.MAX_VALUE; i++) { // 使用 Short.MAX_VALUE 避免 bug 场景下,存在死循环
+            // 查询当前层,所有的子应用菜单
+            List<AppMenuDO> menus = appMenuService.selectListByParentId(parentIds);
+            // 1. 如果没有子菜单,则结束遍历
+            if (CollUtil.isEmpty(menus)) {
+                break;
+            }
+            // 2. 如果有子应用菜单,继续遍历
+            children.addAll(menus);
+            parentIds = convertSet(menus, AppMenuDO::getId);
+        }
+//        children = appMenuService.filterDisableMenus(children);
+        return success(AppConvert.INSTANCE.buildMenuTree(id, children));
+    }
+
+    @Override
+    public CommonResult<List<AppRespDTO>> getAppList() {
+        List<AppDO> list = appService.getList();
+        list.sort(Comparator.comparing(AppDO::getOrderNum));
+        return success(BeanUtils.toBean(list, AppRespDTO.class));
+    }
+}
diff --git a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/app/AppController.java b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/app/AppController.java
index c0a75be..5438a35 100644
--- a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/app/AppController.java
+++ b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/app/AppController.java
@@ -6,6 +6,7 @@
 import com.iailab.framework.common.pojo.PageResult;
 import com.iailab.framework.common.util.object.BeanUtils;
 import com.iailab.framework.excel.core.util.ExcelUtils;
+import com.iailab.module.system.api.app.dto.AppMenuRespDTO;
 import com.iailab.module.system.controller.admin.app.vo.AppPageReqVO;
 import com.iailab.module.system.controller.admin.app.vo.AppRespVO;
 import com.iailab.module.system.controller.admin.app.vo.AppSaveReqVO;
@@ -71,11 +72,28 @@
     @GetMapping("/page")
     @Operation(summary = "获得分页")
     @PreAuthorize("@ss.hasPermission('system:app:query')")
-    public CommonResult<PageResult<AppRespVO>> getTenantPage(@Valid AppPageReqVO pageVO) {
+    public CommonResult<PageResult<AppRespVO>> getAppPage(@Valid AppPageReqVO pageVO) {
         PageResult<AppDO> pageResult = appService.getPage(pageVO);
         return success(BeanUtils.toBean(pageResult, AppRespVO.class));
     }
 
+    @GetMapping("/getAppList")
+    @Operation(summary = "获得应用列表")
+    @PreAuthorize("@ss.hasPermission('system:app-menu:query')")
+    public CommonResult<List<AppRespVO>> getAppList() {
+        List<AppDO> appDOS = appService.getList();
+        return success(BeanUtils.toBean(appDOS, AppRespVO.class));
+    }
+
+//    @GetMapping("/getAppMenu")
+//    @Operation(summary = "获得应用菜单列表")
+//    @PreAuthorize("@ss.hasPermission('system:app-menu:query')")
+//    @Parameter(name = "id", description = "ID", required = true, example = "1024")
+//    public CommonResult<List<AppRespVO>> getAppMenu(@RequestParam("id") Long id) {
+//        List<AppMenuRespDTO> appDOS = appService.getAppMenu(id);
+//        return success(BeanUtils.toBean(appDOS, AppRespVO.class));
+//    }
+
     @GetMapping("/export-excel")
     @Operation(summary = "导出租户 Excel")
     @PreAuthorize("@ss.hasPermission('system:tenant:export')")
diff --git a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/app/AppGroupController.java b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/app/AppGroupController.java
new file mode 100644
index 0000000..e31e9c6
--- /dev/null
+++ b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/app/AppGroupController.java
@@ -0,0 +1,83 @@
+package com.iailab.module.system.controller.admin.app;
+
+import com.iailab.framework.common.pojo.CommonResult;
+import com.iailab.framework.common.pojo.PageResult;
+import com.iailab.framework.common.util.object.BeanUtils;
+import com.iailab.module.system.controller.admin.app.vo.*;
+import com.iailab.module.system.dal.dataobject.app.AppGroupDO;
+import com.iailab.module.system.service.app.AppGroupService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+
+import java.util.List;
+
+import static com.iailab.framework.common.pojo.CommonResult.success;
+
+/**
+ * @author Houzhongjian
+ * @Description
+ * @createTime 2024年09月20日
+ */
+@Tag(name = "管理后台 - 应用分组")
+@RestController
+@RequestMapping("/system/app-group")
+public class AppGroupController {
+
+    @Resource
+    private AppGroupService appGroupService;
+
+    @PostMapping("/create")
+    @Operation(summary = "创建应用分组")
+    @PreAuthorize("@ss.hasPermission('system:app-group:create')")
+    public CommonResult<Long> createApp(@Valid @RequestBody AppGroupSaveReqVO createReqVO) {
+        return success(appGroupService.create(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "更新应用分组")
+    @PreAuthorize("@ss.hasPermission('system:app-group:update')")
+    public CommonResult<Boolean> updateApp(@Valid @RequestBody AppGroupSaveReqVO updateReqVO) {
+        appGroupService.update(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "删除应用分组")
+    @Parameter(name = "id", description = "ID", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('system:app-group:delete')")
+    public CommonResult<Boolean> deleteAppGroup(@RequestParam("id") Long id) {
+        appGroupService.delete(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "获得应用分组")
+    @Parameter(name = "id", description = "ID", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('system:app-group:query')")
+    public CommonResult<AppGroupRespVO> getInfo(@RequestParam("id") Long id) {
+        AppGroupDO data = appGroupService.getInfo(id);
+        return success(BeanUtils.toBean(data, AppGroupRespVO.class));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "获得分页")
+    @PreAuthorize("@ss.hasPermission('system:app-group:query')")
+    public CommonResult<PageResult<AppGroupRespVO>> getAppGroupPage(@Valid AppGroupPageReqVO pageVO) {
+        PageResult<AppGroupDO> pageResult = appGroupService.getPage(pageVO);
+        return success(BeanUtils.toBean(pageResult, AppGroupRespVO.class));
+    }
+
+    @GetMapping("/getAppGroupList")
+    @Operation(summary = "获得应用分组列表")
+    @PreAuthorize("@ss.hasPermission('system:app-group:query')")
+    public CommonResult<List<AppGroupRespVO>> getAppList() {
+        List<AppGroupDO> appGroupDOS = appGroupService.getList();
+        return success(BeanUtils.toBean(appGroupDOS, AppGroupRespVO.class));
+    }
+}
\ No newline at end of file
diff --git a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/app/AppMenuController.java b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/app/AppMenuController.java
new file mode 100644
index 0000000..5a3623a
--- /dev/null
+++ b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/app/AppMenuController.java
@@ -0,0 +1,87 @@
+//package com.iailab.module.system.controller.admin.app;
+//
+//import com.iailab.framework.common.enums.CommonStatusEnum;
+//import com.iailab.framework.common.pojo.CommonResult;
+//import com.iailab.framework.common.util.object.BeanUtils;
+//import com.iailab.module.system.controller.admin.app.vo.AppMenuRespVO;
+//import com.iailab.module.system.controller.admin.permission.vo.menu.MenuListReqVO;
+//import com.iailab.module.system.controller.admin.permission.vo.menu.MenuSaveVO;
+//import com.iailab.module.system.controller.admin.permission.vo.menu.MenuSimpleRespVO;
+//import com.iailab.module.system.dal.dataobject.app.AppMenuDO;
+//import com.iailab.module.system.service.app.AppMenuService;
+//import io.swagger.v3.oas.annotations.Operation;
+//import io.swagger.v3.oas.annotations.Parameter;
+//import io.swagger.v3.oas.annotations.tags.Tag;
+//import org.springframework.security.access.prepost.PreAuthorize;
+//import org.springframework.validation.annotation.Validated;
+//import org.springframework.web.bind.annotation.*;
+//
+//import javax.annotation.Resource;
+//import javax.validation.Valid;
+//import java.util.Comparator;
+//import java.util.List;
+//
+//import static com.iailab.framework.common.pojo.CommonResult.success;
+//
+//@Tag(name = "应用菜单")
+//@RestController
+//@RequestMapping("/system/app-menu")
+//@Validated
+//public class AppMenuController {
+//
+//    @Resource
+//    private AppMenuService appMenuService;
+//
+//    @PostMapping("/create")
+//    @Operation(summary = "创建菜单")
+//    @PreAuthorize("@ss.hasPermission('system:app-menu:create')")
+//    public CommonResult<Long> createMenu(@Valid @RequestBody MenuSaveVO createReqVO) {
+//        Long menuId = appMenuService.createMenu(createReqVO);
+//        return success(menuId);
+//    }
+//
+//    @PutMapping("/update")
+//    @Operation(summary = "修改菜单")
+//    @PreAuthorize("@ss.hasPermission('system:app-menu:update')")
+//    public CommonResult<Boolean> updateMenu(@Valid @RequestBody MenuSaveVO updateReqVO) {
+//        appMenuService.updateMenu(updateReqVO);
+//        return success(true);
+//    }
+//
+//    @DeleteMapping("/delete")
+//    @Operation(summary = "删除菜单")
+//    @Parameter(name = "id", description = "菜单编号", required= true, example = "1024")
+//    @PreAuthorize("@ss.hasPermission('system:app-menu:delete')")
+//    public CommonResult<Boolean> deleteMenu(@RequestParam("id") Long id) {
+//        appMenuService.deleteMenu(id);
+//        return success(true);
+//    }
+//
+//    @GetMapping("/list")
+//    @Operation(summary = "获取菜单列表", description = "用于【菜单管理】界面")
+//    @PreAuthorize("@ss.hasPermission('system:app-menu:query')")
+//    public CommonResult<List<AppMenuRespVO>> getMenuList(MenuListReqVO reqVO) {
+//        List<AppMenuDO> list = appMenuService.getMenuList(reqVO);
+//        list.sort(Comparator.comparing(AppMenuDO::getSort));
+//        return success(BeanUtils.toBean(list, AppMenuRespVO.class));
+//    }
+//
+//    @GetMapping({"/list-all-simple", "simple-list"})
+//    @Operation(summary = "获取菜单精简信息列表", description = "只包含被开启的菜单,用于【角色分配菜单】功能的选项。" +
+//            "在多租户的场景下,会只返回租户所在套餐有的菜单")
+//    public CommonResult<List<MenuSimpleRespVO>> getSimpleMenuList() {
+//        List<AppMenuDO> list = appMenuService.getMenuListByTenant(
+//                new MenuListReqVO().setStatus(CommonStatusEnum.ENABLE.getStatus()));
+//        list.sort(Comparator.comparing(AppMenuDO::getSort));
+//        return success(BeanUtils.toBean(list, MenuSimpleRespVO.class));
+//    }
+//
+//    @GetMapping("/get")
+//    @Operation(summary = "获取菜单信息")
+//    @PreAuthorize("@ss.hasPermission('system:app-menu:query')")
+//    public CommonResult<AppMenuRespVO> getMenu(Long id) {
+//        AppMenuDO menu = appMenuService.getMenu(id);
+//        return success(BeanUtils.toBean(menu, AppMenuRespVO.class));
+//    }
+//
+//}
diff --git a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/app/vo/AppGroupPageReqVO.java b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/app/vo/AppGroupPageReqVO.java
new file mode 100644
index 0000000..1151c61
--- /dev/null
+++ b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/app/vo/AppGroupPageReqVO.java
@@ -0,0 +1,23 @@
+package com.iailab.module.system.controller.admin.app.vo;
+
+import com.iailab.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+/**
+ * @author Houzhongjian
+ * @Description
+ * @createTime 2024年09月20日
+ */
+@Schema(description = "管理后台 - 应用分组分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class AppGroupPageReqVO extends PageParam {
+
+    private String code;
+
+    private String name;
+}
\ No newline at end of file
diff --git a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/app/vo/AppGroupRespVO.java b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/app/vo/AppGroupRespVO.java
new file mode 100644
index 0000000..8d25a50
--- /dev/null
+++ b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/app/vo/AppGroupRespVO.java
@@ -0,0 +1,48 @@
+package com.iailab.module.system.controller.admin.app.vo;
+
+import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
+import com.alibaba.excel.annotation.ExcelProperty;
+import com.baomidou.mybatisplus.annotation.TableField;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * @author Houzhongjian
+ * @Description
+ * @createTime 2024年09月20日
+ */
+@Schema(description = "管理后台 - 应用分组 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class AppGroupRespVO {
+
+    @Schema(description = "应用分组id", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    @ExcelProperty("应用分组id")
+    private Long id;
+
+    @Schema(description = "应用分组编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "应用分组编号")
+    @ExcelProperty("应用分组编号")
+    private String code;
+
+    @Schema(description = "应用分组名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "应用分组名称")
+    @ExcelProperty("应用分组名称")
+    private String name;
+
+    @Schema(description = "应用分组图标", requiredMode = Schema.RequiredMode.REQUIRED, example = "应用分组图标")
+    @ExcelProperty("应用分组图标")
+    private String icon;
+
+    @Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "排序")
+    @ExcelProperty("排序")
+    private Integer sort;
+
+    @Schema(description = "备注", requiredMode = Schema.RequiredMode.REQUIRED, example = "备注")
+    @ExcelProperty("备注")
+    private String remark;
+
+    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("创建时间")
+    private LocalDateTime createTime;
+}
diff --git a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/app/vo/AppGroupSaveReqVO.java b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/app/vo/AppGroupSaveReqVO.java
new file mode 100644
index 0000000..08afc59
--- /dev/null
+++ b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/app/vo/AppGroupSaveReqVO.java
@@ -0,0 +1,36 @@
+package com.iailab.module.system.controller.admin.app.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * @author PanZhibao
+ * @Description
+ * @createTime 2024年08月17日
+ */
+@Schema(description = "管理后台 - 应用创建/修改 Request VO")
+@Data
+public class AppGroupSaveReqVO {
+
+    @Schema(description = "ID")
+    private Long id;
+
+    @Schema(description = "应用编号", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "应用编号不能为空")
+    private String code;
+
+    @Schema(description = "应用名称", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "应用名称不能为空")
+    private String name;
+
+    @Schema(description = "应用图标", example = "")
+    private String icon;
+
+    @Schema(description = "排序", example = "")
+    private Integer sort;
+
+    @Schema(description = "备注", example = "")
+    private String remark;
+}
\ No newline at end of file
diff --git a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/app/vo/AppMenuListReqVO.java b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/app/vo/AppMenuListReqVO.java
new file mode 100644
index 0000000..dabee4a
--- /dev/null
+++ b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/app/vo/AppMenuListReqVO.java
@@ -0,0 +1,16 @@
+package com.iailab.module.system.controller.admin.app.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "应用菜单列表 Request VO")
+@Data
+public class AppMenuListReqVO {
+
+    @Schema(description = "菜单名称,模糊匹配", example = "平台")
+    private String name;
+
+    @Schema(description = "展示状态,参见 CommonStatusEnum 枚举类", example = "1")
+    private Integer status;
+
+}
diff --git a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/app/vo/AppMenuRespVO.java b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/app/vo/AppMenuRespVO.java
new file mode 100644
index 0000000..6eaccb4
--- /dev/null
+++ b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/app/vo/AppMenuRespVO.java
@@ -0,0 +1,69 @@
+package com.iailab.module.system.controller.admin.app.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+import java.time.LocalDateTime;
+
+@Schema(description = "应用菜单信息 Response VO")
+@Data
+public class AppMenuRespVO {
+
+    @Schema(description = "菜单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    private Long id;
+
+    @Schema(description = "菜单名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "平台")
+    @NotBlank(message = "菜单名称不能为空")
+    @Size(max = 50, message = "菜单名称长度不能超过50个字符")
+    private String name;
+
+    @Schema(description = "权限标识,仅菜单类型为按钮时,才需要传递", example = "sys:menu:add")
+    @Size(max = 100)
+    private String permission;
+
+    @Schema(description = "类型,参见 MenuTypeEnum 枚举类", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "菜单类型不能为空")
+    private Integer type;
+
+    @Schema(description = "显示顺序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    @NotNull(message = "显示顺序不能为空")
+    private Integer sort;
+
+    @Schema(description = "父菜单 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    @NotNull(message = "父菜单 ID 不能为空")
+    private Long parentId;
+
+    @Schema(description = "路由地址,仅菜单类型为菜单或者目录时,才需要传", example = "post")
+    @Size(max = 200, message = "路由地址不能超过200个字符")
+    private String path;
+
+    @Schema(description = "菜单图标,仅菜单类型为菜单或者目录时,才需要传", example = "/menu/list")
+    private String icon;
+
+    @Schema(description = "组件路径,仅菜单类型为菜单时,才需要传", example = "system/post/index")
+    @Size(max = 200, message = "组件路径不能超过255个字符")
+    private String component;
+
+    @Schema(description = "组件名", example = "SystemUser")
+    private String componentName;
+
+    @Schema(description = "状态,见 CommonStatusEnum 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "状态不能为空")
+    private Integer status;
+
+    @Schema(description = "是否可见", example = "false")
+    private Boolean visible;
+
+    @Schema(description = "是否缓存", example = "false")
+    private Boolean keepAlive;
+
+    @Schema(description = "是否总是显示", example = "false")
+    private Boolean alwaysShow;
+
+    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "时间戳格式")
+    private LocalDateTime createTime;
+
+}
diff --git a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/app/vo/AppMenuSaveVO.java b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/app/vo/AppMenuSaveVO.java
new file mode 100644
index 0000000..57dd66b
--- /dev/null
+++ b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/app/vo/AppMenuSaveVO.java
@@ -0,0 +1,65 @@
+package com.iailab.module.system.controller.admin.app.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+
+@Schema(description = "应用菜单创建/修改 Request VO")
+@Data
+public class AppMenuSaveVO {
+
+    @Schema(description = "菜单编号", example = "1024")
+    private Long id;
+
+    @Schema(description = "菜单名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "平台")
+    @NotBlank(message = "菜单名称不能为空")
+    @Size(max = 50, message = "菜单名称长度不能超过50个字符")
+    private String name;
+
+    @Schema(description = "权限标识,仅菜单类型为按钮时,才需要传递", example = "sys:menu:add")
+    @Size(max = 100)
+    private String permission;
+
+    @Schema(description = "类型,参见 MenuTypeEnum 枚举类", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "菜单类型不能为空")
+    private Integer type;
+
+    @Schema(description = "显示顺序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    @NotNull(message = "显示顺序不能为空")
+    private Integer sort;
+
+    @Schema(description = "父菜单 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    @NotNull(message = "父菜单 ID 不能为空")
+    private Long parentId;
+
+    @Schema(description = "路由地址,仅菜单类型为菜单或者目录时,才需要传", example = "post")
+    @Size(max = 200, message = "路由地址不能超过200个字符")
+    private String path;
+
+    @Schema(description = "菜单图标,仅菜单类型为菜单或者目录时,才需要传", example = "/menu/list")
+    private String icon;
+
+    @Schema(description = "组件路径,仅菜单类型为菜单时,才需要传", example = "system/post/index")
+    @Size(max = 200, message = "组件路径不能超过255个字符")
+    private String component;
+
+    @Schema(description = "组件名", example = "SystemUser")
+    private String componentName;
+
+    @Schema(description = "状态,见 CommonStatusEnum 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "状态不能为空")
+    private Integer status;
+
+    @Schema(description = "是否可见", example = "false")
+    private Boolean visible;
+
+    @Schema(description = "是否缓存", example = "false")
+    private Boolean keepAlive;
+
+    @Schema(description = "是否总是显示", example = "false")
+    private Boolean alwaysShow;
+
+}
diff --git a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/app/vo/AppMenuSimpleRespVO.java b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/app/vo/AppMenuSimpleRespVO.java
new file mode 100644
index 0000000..5f8162b
--- /dev/null
+++ b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/app/vo/AppMenuSimpleRespVO.java
@@ -0,0 +1,22 @@
+package com.iailab.module.system.controller.admin.app.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "应用菜单精简信息 Response VO")
+@Data
+public class AppMenuSimpleRespVO {
+
+    @Schema(description = "菜单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    private Long id;
+
+    @Schema(description = "菜单名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "平台")
+    private String name;
+
+    @Schema(description = "父菜单 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    private Long parentId;
+
+    @Schema(description = "类型,参见 MenuTypeEnum 枚举类", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Integer type;
+
+}
diff --git a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/app/vo/AppRespVO.java b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/app/vo/AppRespVO.java
index b7431b6..7a14ec9 100644
--- a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/app/vo/AppRespVO.java
+++ b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/app/vo/AppRespVO.java
@@ -2,6 +2,7 @@
 
 import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
 import com.alibaba.excel.annotation.ExcelProperty;
+import com.baomidou.mybatisplus.annotation.TableField;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
@@ -17,8 +18,8 @@
 @ExcelIgnoreUnannotated
 public class AppRespVO {
 
-    @Schema(description = "应用编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
-    @ExcelProperty("应用编号")
+    @Schema(description = "应用ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    @ExcelProperty("应用ID")
     private Long id;
 
     @Schema(description = "应用编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "应用编号")
@@ -28,6 +29,10 @@
     @Schema(description = "应用名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "应用名称")
     @ExcelProperty("应用名称")
     private String appName;
+
+    @Schema(description = "应用类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "应用类型")
+    @ExcelProperty("应用类型")
+    private Integer type;
 
     @Schema(description = "应用域名", requiredMode = Schema.RequiredMode.REQUIRED, example = "应用域名")
     @ExcelProperty("应用域名")
@@ -80,4 +85,21 @@
     @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
     @ExcelProperty("创建时间")
     private LocalDateTime createTime;
+
+    @Schema(description = "租户ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "租户ID")
+    @ExcelProperty("开发者ID")
+    private Long tenantId;
+
+    @Schema(description = "应用菜单ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "应用菜单ID")
+    private Long appMenuId;
+
+    @Schema(description = "应用分组ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "应用分组ID")
+    private Long groupId;
+
+    /**
+     * 应用类型(1-系统菜单, 2-应用菜单)
+     */
+    @TableField(exist = false)
+    private Integer appType;
+
 }
diff --git a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/app/vo/AppSaveReqVO.java b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/app/vo/AppSaveReqVO.java
index 8e11b46..95924ca 100644
--- a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/app/vo/AppSaveReqVO.java
+++ b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/app/vo/AppSaveReqVO.java
@@ -1,5 +1,6 @@
 package com.iailab.module.system.controller.admin.app.vo;
 
+import com.alibaba.excel.annotation.ExcelProperty;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
@@ -24,6 +25,10 @@
     @Schema(description = "应用名称", requiredMode = Schema.RequiredMode.REQUIRED)
     @NotNull(message = "应用名称不能为空")
     private String appName;
+
+    @Schema(description = "应用类型", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("应用类型")
+    private Integer type;
 
     @Schema(description = "应用域名", example = "")
     private String appDomain;
@@ -60,4 +65,10 @@
 
     @Schema(description = "备注", example = "")
     private String remark;
+
+    @Schema(description = "租户ID", example = "")
+    private Long tenantId;
+
+    @Schema(description = "分组ID", example = "")
+    private Long groupId;
 }
\ No newline at end of file
diff --git a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/auth/AuthController.java b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/auth/AuthController.java
index 9aa2241..1637ed5 100644
--- a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/auth/AuthController.java
+++ b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/auth/AuthController.java
@@ -8,11 +8,14 @@
 import com.iailab.framework.security.config.SecurityProperties;
 import com.iailab.framework.security.core.util.SecurityFrameworkUtils;
 import com.iailab.module.system.controller.admin.auth.vo.*;
+import com.iailab.module.system.controller.admin.permission.vo.menu.MenuListReqVO;
 import com.iailab.module.system.convert.auth.AuthConvert;
+import com.iailab.module.system.dal.dataobject.app.AppDO;
 import com.iailab.module.system.dal.dataobject.permission.MenuDO;
 import com.iailab.module.system.dal.dataobject.permission.RoleDO;
 import com.iailab.module.system.dal.dataobject.user.AdminUserDO;
 import com.iailab.module.system.enums.logger.LoginLogTypeEnum;
+import com.iailab.module.system.service.app.AppService;
 import com.iailab.module.system.service.auth.AdminAuthService;
 import com.iailab.module.system.service.permission.MenuService;
 import com.iailab.module.system.service.permission.PermissionService;
@@ -38,6 +41,7 @@
 import static com.iailab.framework.common.pojo.CommonResult.success;
 import static com.iailab.framework.common.util.collection.CollectionUtils.convertSet;
 import static com.iailab.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+import static com.iailab.framework.tenant.core.context.TenantContextHolder.getTenantId;
 
 
 @Tag(name = "管理后台 - 认证")
@@ -59,9 +63,10 @@
     private PermissionService permissionService;
     @Resource
     private SocialClientService socialClientService;
-
     @Resource
     private SecurityProperties securityProperties;
+    @Resource
+    private AppService appService;
 
     @PostMapping("/login")
     @PermitAll
@@ -116,6 +121,34 @@
         return success(AuthConvert.INSTANCE.convert(user, roles, menuList));
     }
 
+    @GetMapping("/get-app-permission-info")
+    @Operation(summary = "获取登录用户的app权限信息")
+    public CommonResult<AuthPermissionInfoRespVO> getAppPermissionInfo() {
+        // 1.1 获得用户信息
+        AdminUserDO user = userService.getUser(getLoginUserId());
+        if (user == null) {
+            return success(null);
+        }
+
+        // 1.2 获得角色列表
+        Set<Long> roleIds = permissionService.getUserRoleIdListByUserId(getLoginUserId());
+        if (CollUtil.isEmpty(roleIds)) {
+            return success(AuthConvert.INSTANCE.convert(user, Collections.emptyList(), Collections.emptyList()));
+        }
+        List<RoleDO> roles = roleService.getRoleList(roleIds);
+        roles.removeIf(role -> !CommonStatusEnum.ENABLE.getStatus().equals(role.getStatus())); // 移除禁用的角色
+
+        // 1.3 获得应用菜单列表
+        MenuListReqVO reqVO = new MenuListReqVO();
+        List<MenuDO> appMenuList = menuService.getAppMenuList(reqVO);
+        Set<Long> menuIds = permissionService.getRoleMenuListByRoleId(convertSet(roles, RoleDO::getId));
+        List<MenuDO> menuList = menuService.getMenuList(menuIds);
+        menuList.retainAll(appMenuList);
+        menuList = menuService.filterDisableMenus(menuList);
+        // 2. 拼接结果返回
+        return success(AuthConvert.INSTANCE.convertAppMenu(user, roles, menuList));
+    }
+
     // ========== 短信登录相关 ==========
 
     @PostMapping("/sms-login")
diff --git a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/oauth2/OAuth2OpenController.java b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/oauth2/OAuth2OpenController.java
index 44791bf..a1fe484 100644
--- a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/oauth2/OAuth2OpenController.java
+++ b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/oauth2/OAuth2OpenController.java
@@ -11,6 +11,7 @@
 import com.iailab.module.system.controller.admin.oauth2.vo.open.OAuth2OpenAccessTokenRespVO;
 import com.iailab.module.system.controller.admin.oauth2.vo.open.OAuth2OpenAuthorizeInfoRespVO;
 import com.iailab.module.system.controller.admin.oauth2.vo.open.OAuth2OpenCheckTokenRespVO;
+import com.iailab.module.system.controller.admin.oauth2.vo.open.OAuth2OpenLoginReqVO;
 import com.iailab.module.system.convert.oauth2.OAuth2OpenConvert;
 import com.iailab.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;
 import com.iailab.module.system.dal.dataobject.oauth2.OAuth2ApproveDO;
@@ -84,25 +85,16 @@
     @PostMapping("/token")
     @PermitAll
     @Operation(summary = "获得访问令牌", description = "适合 code 授权码模式,或者 implicit 简化模式;在 sso.vue 单点登录界面被【获取】调用")
-    @Parameters({
-            @Parameter(name = "grant_type", required = true, description = "授权类型", example = "code"),
-            @Parameter(name = "code", description = "授权范围", example = "userinfo.read"),
-            @Parameter(name = "redirect_uri", description = "重定向 URI", example = "https://www.baidu.com"),
-            @Parameter(name = "state", description = "状态", example = "1"),
-            @Parameter(name = "username", example = "tudou"),
-            @Parameter(name = "password", example = "cai"), // 多个使用空格分隔
-            @Parameter(name = "scope", example = "user_info"),
-            @Parameter(name = "refresh_token", example = "123424233"),
-    })
     public CommonResult<OAuth2OpenAccessTokenRespVO> postAccessToken(HttpServletRequest request,
-                                                                     @RequestParam("grant_type") String grantType,
-                                                                     @RequestParam(value = "code", required = false) String code, // 授权码模式
-                                                                     @RequestParam(value = "redirect_uri", required = false) String redirectUri, // 授权码模式
-                                                                     @RequestParam(value = "state", required = false) String state, // 授权码模式
-                                                                     @RequestParam(value = "username", required = false) String username, // 密码模式
-                                                                     @RequestParam(value = "password", required = false) String password, // 密码模式
-                                                                     @RequestParam(value = "scope", required = false) String scope, // 密码模式
-                                                                     @RequestParam(value = "refresh_token", required = false) String refreshToken) { // 刷新模式
+                                                                     @RequestBody OAuth2OpenLoginReqVO openLoginReqVO) {
+        String code = openLoginReqVO.getCode();
+        String scope = openLoginReqVO.getScope();
+        String grantType = openLoginReqVO.getGrantType();
+        String redirectUri = openLoginReqVO.getRedirectUri();
+        String state = openLoginReqVO.getState();
+        String username = openLoginReqVO.getUsername();
+        String password = openLoginReqVO.getPassword();
+        String refreshToken = openLoginReqVO.getRefreshToken();
         List<String> scopes = OAuth2Utils.buildScopes(scope);
         // 1.1 校验授权类型
         OAuth2GrantTypeEnum grantTypeEnum = OAuth2GrantTypeEnum.getByGrantType(grantType);
diff --git a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/oauth2/vo/open/OAuth2OpenAccessTokenRespVO.java b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/oauth2/vo/open/OAuth2OpenAccessTokenRespVO.java
index bf5ba85..d297a21 100644
--- a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/oauth2/vo/open/OAuth2OpenAccessTokenRespVO.java
+++ b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/oauth2/vo/open/OAuth2OpenAccessTokenRespVO.java
@@ -12,21 +12,20 @@
 @AllArgsConstructor
 public class OAuth2OpenAccessTokenRespVO {
 
+    @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    private Long userId;
+
     @Schema(description = "访问令牌", requiredMode = Schema.RequiredMode.REQUIRED, example = "tudou")
-    @JsonProperty("access_token")
     private String accessToken;
 
     @Schema(description = "刷新令牌", requiredMode = Schema.RequiredMode.REQUIRED, example = "nice")
-    @JsonProperty("refresh_token")
     private String refreshToken;
 
     @Schema(description = "令牌类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "bearer")
-    @JsonProperty("token_type")
     private String tokenType;
 
     @Schema(description = "过期时间,单位:秒", requiredMode = Schema.RequiredMode.REQUIRED, example = "42430")
-    @JsonProperty("expires_in")
-    private Long expiresIn;
+    private Long expiresTime;
 
     @Schema(description = "授权范围,如果多个授权范围,使用空格分隔", example = "user_info")
     private String scope;
diff --git a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/oauth2/vo/open/OAuth2OpenLoginReqVO.java b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/oauth2/vo/open/OAuth2OpenLoginReqVO.java
new file mode 100644
index 0000000..f67ff9e
--- /dev/null
+++ b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/oauth2/vo/open/OAuth2OpenLoginReqVO.java
@@ -0,0 +1,54 @@
+package com.iailab.module.system.controller.admin.oauth2.vo.open;
+
+import cn.hutool.core.util.StrUtil;
+import com.iailab.framework.common.validation.InEnum;
+import com.iailab.module.system.enums.social.SocialTypeEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.hibernate.validator.constraints.Length;
+
+import javax.validation.constraints.AssertTrue;
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.Pattern;
+
+@Schema(description = "管理后台 - 账号密码授权登录 Request VO")
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class OAuth2OpenLoginReqVO {
+
+    @Schema(description = "授权类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "password")
+    private String grantType;
+
+    //授权码模式
+    @Schema(description = "授权范围", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
+    private String code;
+
+    @Schema(description = "重定向 URI", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
+    private String redirectUri;
+
+    @Schema(description = "状态", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
+    private String state;
+
+    //密码模式
+    @Schema(description = "授权范围", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
+    private String scope;
+
+    @Schema(description = "账号", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "iailabyuanma")
+    @Length(min = 4, max = 16, message = "账号长度为 4-16 位")
+    @Pattern(regexp = "^[A-Za-z0-9]+$", message = "账号格式为数字以及字母")
+    private String username;
+
+    @Schema(description = "密码", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "buzhidao")
+    @Length(min = 4, max = 16, message = "密码长度为 4-16 位")
+    private String password;
+
+    //刷新模式
+    @Schema(description = "刷新token", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
+    private String refreshToken;
+
+}
diff --git a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/permission/MenuController.java b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/permission/MenuController.java
index d7eba44..67553f1 100644
--- a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/permission/MenuController.java
+++ b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/permission/MenuController.java
@@ -40,10 +40,26 @@
         return success(menuId);
     }
 
+    @PostMapping("/createAppMenu")
+    @Operation(summary = "创建菜单")
+    @PreAuthorize("@ss.hasPermission('system:app-menu:create')")
+    public CommonResult<Long> createAppMenu(@Valid @RequestBody MenuSaveVO createReqVO) {
+        Long menuId = menuService.createMenu(createReqVO);
+        return success(menuId);
+    }
+
     @PutMapping("/update")
     @Operation(summary = "修改菜单")
     @PreAuthorize("@ss.hasPermission('system:menu:update')")
     public CommonResult<Boolean> updateMenu(@Valid @RequestBody MenuSaveVO updateReqVO) {
+        menuService.updateMenu(updateReqVO);
+        return success(true);
+    }
+
+    @PutMapping("/updateAppMenu")
+    @Operation(summary = "修改菜单")
+    @PreAuthorize("@ss.hasPermission('system:app-menu:update')")
+    public CommonResult<Boolean> updateAppMenu(@Valid @RequestBody MenuSaveVO updateReqVO) {
         menuService.updateMenu(updateReqVO);
         return success(true);
     }
@@ -57,11 +73,30 @@
         return success(true);
     }
 
+    @DeleteMapping("/deleteAppMenu")
+    @Operation(summary = "删除菜单")
+    @Parameter(name = "id", description = "菜单编号", required= true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('system:app-menu:delete')")
+    public CommonResult<Boolean> deleteAppMenu(@RequestParam("id") Long id) {
+        menuService.deleteMenu(id);
+        return success(true);
+    }
+
+
     @GetMapping("/list")
     @Operation(summary = "获取菜单列表", description = "用于【菜单管理】界面")
     @PreAuthorize("@ss.hasPermission('system:menu:query')")
     public CommonResult<List<MenuRespVO>> getMenuList(MenuListReqVO reqVO) {
         List<MenuDO> list = menuService.getMenuList(reqVO);
+        list.sort(Comparator.comparing(MenuDO::getSort));
+        return success(BeanUtils.toBean(list, MenuRespVO.class));
+    }
+
+    @GetMapping("/app-menu-list")
+    @Operation(summary = "获取应用菜单列表", description = "用于【应用菜单】界面")
+    @PreAuthorize("@ss.hasPermission('system:app-menu:query')")
+    public CommonResult<List<MenuRespVO>> getAppMenuList(MenuListReqVO reqVO) {
+        List<MenuDO> list = menuService.getAppMenuList(reqVO);
         list.sort(Comparator.comparing(MenuDO::getSort));
         return success(BeanUtils.toBean(list, MenuRespVO.class));
     }
@@ -76,6 +111,16 @@
         return success(BeanUtils.toBean(list, MenuSimpleRespVO.class));
     }
 
+    @GetMapping({"simple-app-menus"})
+    @Operation(summary = "获取应用菜单精简信息列表", description = "只包含被开启的菜单,用于【角色分配菜单】功能的选项。" +
+            "在多租户的场景下,会只返回租户所在套餐有的菜单")
+    public CommonResult<List<MenuSimpleRespVO>> getSimpleAppMenuList() {
+        List<MenuDO> list = menuService.getAppMenuListByTenant(
+                new MenuListReqVO().setStatus(CommonStatusEnum.ENABLE.getStatus()));
+        list.sort(Comparator.comparing(MenuDO::getSort));
+        return success(BeanUtils.toBean(list, MenuSimpleRespVO.class));
+    }
+
     @GetMapping("/get")
     @Operation(summary = "获取菜单信息")
     @PreAuthorize("@ss.hasPermission('system:menu:query')")
@@ -84,4 +129,12 @@
         return success(BeanUtils.toBean(menu, MenuRespVO.class));
     }
 
+    @GetMapping("/getAppMenu")
+    @Operation(summary = "获取菜单信息")
+    @PreAuthorize("@ss.hasPermission('system:app-menu:query')")
+    public CommonResult<MenuRespVO> getAppMenu(Long id) {
+        MenuDO menu = menuService.getMenu(id);
+        return success(BeanUtils.toBean(menu, MenuRespVO.class));
+    }
+
 }
diff --git a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/permission/PermissionController.java b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/permission/PermissionController.java
index 26ff774..0bf9e24 100644
--- a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/permission/PermissionController.java
+++ b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/permission/PermissionController.java
@@ -43,6 +43,14 @@
         return success(permissionService.getRoleMenuListByRoleId(roleId));
     }
 
+    @Operation(summary = "获得角色拥有的菜单编号")
+    @Parameter(name = "roleId", description = "角色编号", required = true)
+    @GetMapping("/list-role-app-menus")
+    @PreAuthorize("@ss.hasPermission('system:permission:assign-role-menu')")
+    public CommonResult<Set<Long>> getRoleAppMenuList(Long roleId) {
+        return success(permissionService.getRoleAppMenuListByRoleId(roleId));
+    }
+
     @PostMapping("/assign-role-menu")
     @Operation(summary = "赋予角色菜单")
     @PreAuthorize("@ss.hasPermission('system:permission:assign-role-menu')")
@@ -55,6 +63,15 @@
         return success(true);
     }
 
+    @PostMapping("/assign-role-app-menu")
+    @Operation(summary = "赋予角色菜单")
+    @PreAuthorize("@ss.hasPermission('system:permission:assign-role-menu')")
+    public CommonResult<Boolean> assignRoleAppMenu(@Validated @RequestBody PermissionAssignRoleMenuReqVO reqVO) {
+        // 执行菜单的分配
+        permissionService.assignRoleAppMenu(reqVO.getRoleId(), reqVO.getMenuIds());
+        return success(true);
+    }
+
     @PostMapping("/assign-role-data-scope")
     @Operation(summary = "赋予角色数据权限")
     @PreAuthorize("@ss.hasPermission('system:permission:assign-role-data-scope')")
diff --git a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/permission/vo/menu/MenuRespVO.java b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/permission/vo/menu/MenuRespVO.java
index f6916ae..a986432 100644
--- a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/permission/vo/menu/MenuRespVO.java
+++ b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/permission/vo/menu/MenuRespVO.java
@@ -66,4 +66,7 @@
     @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "时间戳格式")
     private LocalDateTime createTime;
 
+    @Schema(description = "应用ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+    private Long appId;
+
 }
diff --git a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/permission/vo/menu/MenuSaveVO.java b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/permission/vo/menu/MenuSaveVO.java
index a9f5e24..7068a12 100644
--- a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/permission/vo/menu/MenuSaveVO.java
+++ b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/permission/vo/menu/MenuSaveVO.java
@@ -62,4 +62,7 @@
     @Schema(description = "是否总是显示", example = "false")
     private Boolean alwaysShow;
 
+    @Schema(description = "应用ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+    private Long appId;
+
 }
diff --git a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/tenant/TenantController.java b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/tenant/TenantController.java
index eebfc3b..1e0aede 100644
--- a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/tenant/TenantController.java
+++ b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/tenant/TenantController.java
@@ -95,6 +95,14 @@
         return success(BeanUtils.toBean(pageResult, TenantRespVO.class));
     }
 
+    @GetMapping("/simple-list")
+    @Operation(summary = "获得租户精简列表")
+    @PreAuthorize("@ss.hasPermission('system:tenant:query')")
+    public CommonResult<List<TenantSimpleRespVO>> getSimpleTenant() {
+        List<TenantDO> listResult = tenantService.getSimpleTenant();
+        return success(BeanUtils.toBean(listResult, TenantSimpleRespVO.class));
+    }
+
     @GetMapping("/export-excel")
     @Operation(summary = "导出租户 Excel")
     @PreAuthorize("@ss.hasPermission('system:tenant:export')")
diff --git a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/convert/app/AppConvert.java b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/convert/app/AppConvert.java
new file mode 100644
index 0000000..a05c3cc
--- /dev/null
+++ b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/convert/app/AppConvert.java
@@ -0,0 +1,60 @@
+package com.iailab.module.system.convert.app;
+
+import cn.hutool.core.collection.CollUtil;
+import com.iailab.module.system.api.app.dto.AppMenuRespDTO;
+import com.iailab.module.system.dal.dataobject.app.AppMenuDO;
+import com.iailab.module.system.enums.permission.MenuTypeEnum;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+import org.slf4j.LoggerFactory;
+
+import java.util.*;
+
+import static com.iailab.framework.common.util.collection.CollectionUtils.filterList;
+
+@Mapper
+public interface AppConvert {
+
+    AppConvert INSTANCE = Mappers.getMapper(AppConvert.class);
+
+    AppMenuRespDTO convertTreeNode(AppMenuDO menu);
+
+    /**
+     * 将菜单列表,构建成菜单树
+     *
+     * @param menuList 菜单列表
+     * @return 菜单树
+     */
+    default List<AppMenuRespDTO> buildMenuTree(Long id, List<AppMenuDO> menuList) {
+        if (CollUtil.isEmpty(menuList)) {
+            return Collections.emptyList();
+        }
+        // 移除按钮
+        menuList.removeIf(menu -> menu.getType().equals(MenuTypeEnum.BUTTON.getType()));
+        // 排序,保证菜单的有序性
+        menuList.sort(Comparator.comparing(AppMenuDO::getSort));
+
+        // 构建菜单树
+        // 使用 LinkedHashMap 的原因,是为了排序 。实际也可以用 Stream API ,就是太丑了。
+        Map<Long, AppMenuRespDTO> treeNodeMap = new LinkedHashMap<>();
+        menuList.forEach(menu -> treeNodeMap.put(menu.getId(), AppConvert.INSTANCE.convertTreeNode(menu)));
+        // 处理父子关系
+        treeNodeMap.values().stream().filter(node -> !node.getParentId().equals(id)).forEach(childNode -> {
+            // 获得父节点
+            AppMenuRespDTO parentNode = treeNodeMap.get(childNode.getParentId());
+            if (parentNode == null) {
+                LoggerFactory.getLogger(getClass()).error("[buildRouterTree][resource({}) 找不到父资源({})]",
+                        childNode.getId(), childNode.getParentId());
+                return;
+            }
+            // 将自己添加到父节点中
+            if (parentNode.getChildren() == null) {
+                parentNode.setChildren(new ArrayList<>());
+            }
+            parentNode.getChildren().add(childNode);
+        });
+        // 获得到所有的根节点
+        return filterList(treeNodeMap.values(), node -> id.equals(node.getParentId()));
+    }
+
+}
diff --git a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/convert/auth/AuthConvert.java b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/convert/auth/AuthConvert.java
index bbf1c50..446a341 100644
--- a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/convert/auth/AuthConvert.java
+++ b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/convert/auth/AuthConvert.java
@@ -28,7 +28,7 @@
 
     AuthLoginRespVO convert(OAuth2AccessTokenDO bean);
 
-    default AuthPermissionInfoRespVO convert(AdminUserDO user, List<RoleDO> roleList, List<MenuDO> menuList) {
+    default AuthPermissionInfoRespVO  convert(AdminUserDO user, List<RoleDO> roleList, List<MenuDO> menuList) {
         return AuthPermissionInfoRespVO.builder()
                 .user(BeanUtils.toBean(user, AuthPermissionInfoRespVO.UserVO.class))
                 .roles(convertSet(roleList, RoleDO::getCode))
@@ -36,6 +36,17 @@
                 .permissions(convertSet(menuList, MenuDO::getPermission))
                 // 菜单树
                 .menus(buildMenuTree(menuList))
+                .build();
+    }
+
+    default AuthPermissionInfoRespVO convertAppMenu(AdminUserDO user, List<RoleDO> roleList, List<MenuDO> menuList) {
+        return AuthPermissionInfoRespVO.builder()
+                .user(BeanUtils.toBean(user, AuthPermissionInfoRespVO.UserVO.class))
+                .roles(convertSet(roleList, RoleDO::getCode))
+                // 权限标识信息
+                .permissions(convertSet(menuList, MenuDO::getPermission))
+                // 菜单树
+                .menus(buildAppMenuTree(menuList))
                 .build();
     }
 
@@ -51,7 +62,7 @@
         if (CollUtil.isEmpty(menuList)) {
             return Collections.emptyList();
         }
-        // 移除按钮
+        // 移除按钮和应用菜单
         menuList.removeIf(menu -> menu.getType().equals(MenuTypeEnum.BUTTON.getType()));
         // 排序,保证菜单的有序性
         menuList.sort(Comparator.comparing(MenuDO::getSort));
@@ -79,6 +90,87 @@
         return filterList(treeNodeMap.values(), node -> ID_ROOT.equals(node.getParentId()));
     }
 
+    /**
+     * 将菜单列表,构建成菜单树
+     *
+     * @param menuList 菜单列表
+     * @return 菜单树
+     */
+    default List<AuthPermissionInfoRespVO.MenuVO> buildAppMenuTree(List<MenuDO> menuList) {
+        if (CollUtil.isEmpty(menuList)) {
+            return Collections.emptyList();
+        }
+        // 移除按钮和应用菜单
+        menuList.removeIf(menu -> menu.getType().equals(MenuTypeEnum.BUTTON.getType()));
+        // 排序,保证菜单的有序性
+        menuList.sort(Comparator.comparing(MenuDO::getSort));
+
+        // 构建菜单树
+        // 使用 LinkedHashMap 的原因,是为了排序 。实际也可以用 Stream API ,就是太丑了。
+        Map<Long, AuthPermissionInfoRespVO.MenuVO> treeNodeMap = new LinkedHashMap<>();
+        menuList.forEach(menu -> treeNodeMap.put(menu.getId(), AuthConvert.INSTANCE.convertTreeNode(menu)));
+        // 处理父子关系
+        treeNodeMap.values().stream().filter(node -> !node.getParentId().equals(ID_ROOT)).forEach(childNode -> {
+            // 获得父节点
+            AuthPermissionInfoRespVO.MenuVO parentNode = treeNodeMap.get(childNode.getParentId());
+            if (parentNode == null) {
+                LoggerFactory.getLogger(getClass()).error("[buildRouterTree][resource({}) 找不到父资源({})]",
+                        childNode.getId(), childNode.getParentId());
+                return;
+            }
+            // 将自己添加到父节点中
+            if (parentNode.getChildren() == null) {
+                parentNode.setChildren(new ArrayList<>());
+            }
+            parentNode.getChildren().add(childNode);
+        });
+        // 获得到所有的根节点
+        return filterList(treeNodeMap.values(), node -> ID_ROOT.equals(node.getParentId()));
+    }
+
+    /**
+     * 将菜单列表,构建成菜单树
+     *
+     * @param menuList 菜单列表
+     * @return 菜单树
+     */
+    default List<AuthPermissionInfoRespVO.MenuVO> buildMenuTree(List<MenuDO> menuList, Long id, String parentPath) {
+        if (CollUtil.isEmpty(menuList)) {
+            return Collections.emptyList();
+        }
+        // 移除按钮
+        menuList.removeIf(menu -> menu.getType().equals(MenuTypeEnum.BUTTON.getType()));
+        // 排序,保证菜单的有序性
+        menuList.sort(Comparator.comparing(MenuDO::getSort));
+
+        // 构建菜单树
+        // 使用 LinkedHashMap 的原因,是为了排序 。实际也可以用 Stream API ,就是太丑了。
+        Map<Long, AuthPermissionInfoRespVO.MenuVO> treeNodeMap = new LinkedHashMap<>();
+        menuList.forEach(menu -> treeNodeMap.put(menu.getId(), AuthConvert.INSTANCE.convertTreeNode(menu)));
+        // 处理父子关系
+        treeNodeMap.values().stream().filter(node -> !node.getParentId().equals(id)).forEach(childNode -> {
+            // 获得父节点
+            AuthPermissionInfoRespVO.MenuVO parentNode = treeNodeMap.get(childNode.getParentId());
+            if (parentNode == null) {
+                LoggerFactory.getLogger(getClass()).error("[buildRouterTree][resource({}) 找不到父资源({})]",
+                        childNode.getId(), childNode.getParentId());
+                return;
+            }
+            // 将自己添加到父节点中
+            if (parentNode.getChildren() == null) {
+                parentNode.setChildren(new ArrayList<>());
+            }
+            parentNode.getChildren().add(childNode);
+
+        });
+        // 获得到所有的根节点
+        List<AuthPermissionInfoRespVO.MenuVO> menuVOS = filterList(treeNodeMap.values(), node -> id.equals(node.getParentId()));
+        menuVOS.stream().forEach(menuVO -> {
+            menuVO.setPath(parentPath + "/" + menuVO.getPath());
+        });
+        return menuVOS;
+    }
+
     SocialUserBindReqDTO convert(Long userId, Integer userType, AuthSocialLoginReqVO reqVO);
 
     SmsCodeSendReqDTO convert(AuthSmsSendReqVO reqVO);
diff --git a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/convert/oauth2/OAuth2OpenConvert.java b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/convert/oauth2/OAuth2OpenConvert.java
index fec67ac..fc97c28 100644
--- a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/convert/oauth2/OAuth2OpenConvert.java
+++ b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/convert/oauth2/OAuth2OpenConvert.java
@@ -28,7 +28,6 @@
     default OAuth2OpenAccessTokenRespVO convert(OAuth2AccessTokenDO bean) {
         OAuth2OpenAccessTokenRespVO respVO = BeanUtils.toBean(bean, OAuth2OpenAccessTokenRespVO.class);
         respVO.setTokenType(SecurityFrameworkUtils.AUTHORIZATION_BEARER.toLowerCase());
-        respVO.setExpiresIn(OAuth2Utils.getExpiresIn(bean.getExpiresTime()));
         respVO.setScope(OAuth2Utils.buildScopeStr(bean.getScopes()));
         return respVO;
     }
diff --git a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/dal/dataobject/app/AppDO.java b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/dal/dataobject/app/AppDO.java
index 2198bc7..d3b27c1 100644
--- a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/dal/dataobject/app/AppDO.java
+++ b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/dal/dataobject/app/AppDO.java
@@ -1,6 +1,7 @@
 package com.iailab.module.system.dal.dataobject.app;
 
 import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
 import com.iailab.framework.mybatis.core.dataobject.BaseDO;
@@ -38,6 +39,11 @@
      * 应用名称
      */
     private String appName;
+
+    /**
+     * 应用类型
+     */
+    private Integer type;
 
     /**
      * 应用域名
@@ -99,4 +105,26 @@
      */
     private String remark;
 
+    /**
+     * 租户ID
+     */
+    private Long tenantId;
+
+    /**
+     * 分组ID
+     */
+    private Long groupId;
+
+    /**
+     * 应用类型(1-系统菜单, 2-应用菜单)
+     */
+    @TableField(exist = false)
+    private Integer appType;
+
+    /**
+     * 应用菜单id
+     */
+    @TableField(exist = false)
+    private Long appMenuId;
+
 }
\ No newline at end of file
diff --git a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/dal/dataobject/app/AppGroupDO.java b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/dal/dataobject/app/AppGroupDO.java
new file mode 100644
index 0000000..9c50371
--- /dev/null
+++ b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/dal/dataobject/app/AppGroupDO.java
@@ -0,0 +1,57 @@
+package com.iailab.module.system.dal.dataobject.app;
+
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.iailab.framework.mybatis.core.dataobject.BaseDO;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 分组分组表
+ *
+ * @author Houzhongjian
+ * @Description
+ * @createTime 2024年09月20日
+ */
+@TableName("system_app_group")
+@KeySequence("system_app_group_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class AppGroupDO extends BaseDO {
+
+    public static final Long PARENT_ID_ROOT = 0L;
+
+    /**
+     * ID
+     */
+    @TableId
+    private Long id;
+
+    /**
+     * 分组编号
+     */
+    private String code;
+
+    /**
+     * 分组名称
+     */
+    private String name;
+
+    /**
+     * 分组图标
+     */
+    private String icon;
+
+    /**
+     * 排序
+     */
+    private Integer sort;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+}
\ No newline at end of file
diff --git a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/dal/dataobject/app/AppMenuDO.java b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/dal/dataobject/app/AppMenuDO.java
new file mode 100644
index 0000000..969b8d2
--- /dev/null
+++ b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/dal/dataobject/app/AppMenuDO.java
@@ -0,0 +1,114 @@
+package com.iailab.module.system.dal.dataobject.app;
+
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.iailab.framework.common.enums.CommonStatusEnum;
+import com.iailab.framework.mybatis.core.dataobject.BaseDO;
+import com.iailab.framework.tenant.core.db.TenantBaseDO;
+import com.iailab.module.system.enums.permission.MenuTypeEnum;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 菜单 DO
+ *
+ * @author ruoyi
+ */
+@TableName("system_app_menu")
+@KeySequence("system_app_menu_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class AppMenuDO extends BaseDO {
+
+    /**
+     * 菜单编号 - 根节点
+     */
+    public static final Long ID_ROOT = 0L;
+
+    /**
+     * 菜单编号
+     */
+    @TableId
+    private Long id;
+    /**
+     * 菜单名称
+     */
+    private String name;
+    /**
+     * 权限标识
+     *
+     * 一般格式为:${系统}:${模块}:${操作}
+     * 例如说:system:admin:add,即 system 服务的添加管理员。
+     *
+     * 当我们把该 MenuDO 赋予给角色后,意味着该角色有该资源:
+     * - 对于后端,配合 @PreAuthorize 注解,配置 API 接口需要该权限,从而对 API 接口进行权限控制。
+     * - 对于前端,配合前端标签,配置按钮是否展示,避免用户没有该权限时,结果可以看到该操作。
+     */
+    private String permission;
+    /**
+     * 菜单类型
+     *
+     * 枚举 {@link MenuTypeEnum}
+     */
+    private Integer type;
+    /**
+     * 显示顺序
+     */
+    private Integer sort;
+    /**
+     * 父菜单ID
+     */
+    private Long parentId;
+    /**
+     * 路由地址
+     *
+     * 如果 path 为 http(s) 时,则它是外链
+     */
+    private String path;
+    /**
+     * 菜单图标
+     */
+    private String icon;
+    /**
+     * 组件路径
+     */
+    private String component;
+    /**
+     * 组件名
+     */
+    private String componentName;
+    /**
+     * 状态
+     *
+     * 枚举 {@link CommonStatusEnum}
+     */
+    private Integer status;
+    /**
+     * 是否可见
+     *
+     * 只有菜单、目录使用
+     * 当设置为 true 时,该菜单不会展示在侧边栏,但是路由还是存在。例如说,一些独立的编辑页面 /edit/1024 等等
+     */
+    private Boolean visible;
+    /**
+     * 是否缓存
+     *
+     * 只有菜单、目录使用,否使用 Vue 路由的 keep-alive 特性
+     * 注意:如果开启缓存,则必须填写 {@link #componentName} 属性,否则无法缓存
+     */
+    private Boolean keepAlive;
+    /**
+     * 是否总是显示
+     *
+     * 如果为 false 时,当该菜单只有一个子菜单时,不展示自己,直接展示子菜单
+     */
+    private Boolean alwaysShow;
+
+    /**
+     * 应用ID
+     */
+    private Long appId;
+
+
+}
diff --git a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/dal/dataobject/permission/MenuDO.java b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/dal/dataobject/permission/MenuDO.java
index ae0465b..fd8dc73 100644
--- a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/dal/dataobject/permission/MenuDO.java
+++ b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/dal/dataobject/permission/MenuDO.java
@@ -104,4 +104,15 @@
      */
     private Boolean alwaysShow;
 
+    /**
+     * 应用ID
+     */
+    private Long appId;
+
+    /**
+     * 租户ID
+     */
+    private Long tenantId;
+
+
 }
diff --git a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/dal/mysql/app/AppGroupMapper.java b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/dal/mysql/app/AppGroupMapper.java
new file mode 100644
index 0000000..a3dbc64
--- /dev/null
+++ b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/dal/mysql/app/AppGroupMapper.java
@@ -0,0 +1,24 @@
+package com.iailab.module.system.dal.mysql.app;
+
+import com.iailab.framework.common.pojo.PageResult;
+import com.iailab.framework.mybatis.core.mapper.BaseMapperX;
+import com.iailab.framework.mybatis.core.query.LambdaQueryWrapperX;
+import com.iailab.framework.tenant.core.aop.TenantIgnore;
+import com.iailab.module.system.controller.admin.app.vo.AppGroupPageReqVO;
+import com.iailab.module.system.dal.dataobject.app.AppGroupDO;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * @author Houzhongjian
+ * @Description
+ * @createTime 2024年09月10日
+ */
+@Mapper
+public interface AppGroupMapper extends BaseMapperX<AppGroupDO> {
+    default PageResult<AppGroupDO> selectPage(AppGroupPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<AppGroupDO>()
+                .likeIfPresent(AppGroupDO::getCode, reqVO.getCode())
+                .likeIfPresent(AppGroupDO::getName, reqVO.getName())
+                .orderByDesc(AppGroupDO::getId));
+    }
+}
diff --git a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/dal/mysql/app/AppMenuMapper.java b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/dal/mysql/app/AppMenuMapper.java
new file mode 100644
index 0000000..a0fc545
--- /dev/null
+++ b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/dal/mysql/app/AppMenuMapper.java
@@ -0,0 +1,37 @@
+package com.iailab.module.system.dal.mysql.app;
+
+import com.iailab.framework.mybatis.core.mapper.BaseMapperX;
+import com.iailab.framework.mybatis.core.query.LambdaQueryWrapperX;
+import com.iailab.module.system.controller.admin.permission.vo.menu.MenuListReqVO;
+import com.iailab.module.system.dal.dataobject.app.AppMenuDO;
+import com.iailab.module.system.dal.dataobject.dept.DeptDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.Collection;
+import java.util.List;
+
+@Mapper
+public interface AppMenuMapper extends BaseMapperX<AppMenuDO> {
+
+    default AppMenuDO selectByParentIdAndName(Long parentId, String name) {
+        return selectOne(AppMenuDO::getParentId, parentId, AppMenuDO::getName, name);
+    }
+
+    default Long selectCountByParentId(Long parentId) {
+        return selectCount(AppMenuDO::getParentId, parentId);
+    }
+
+    default List<AppMenuDO> selectListByParentId(Collection<Long> parentIds) {
+        return selectList(AppMenuDO::getParentId, parentIds);
+    }
+
+    default List<AppMenuDO> selectList(MenuListReqVO reqVO) {
+        return selectList(new LambdaQueryWrapperX<AppMenuDO>()
+                .likeIfPresent(AppMenuDO::getName, reqVO.getName())
+                .eqIfPresent(AppMenuDO::getStatus, reqVO.getStatus()));
+    }
+
+    default List<AppMenuDO> selectListByPermission(String permission) {
+        return selectList(AppMenuDO::getPermission, permission);
+    }
+}
diff --git a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/dal/mysql/permission/MenuMapper.java b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/dal/mysql/permission/MenuMapper.java
index 488688e..670d3ed 100644
--- a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/dal/mysql/permission/MenuMapper.java
+++ b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/dal/mysql/permission/MenuMapper.java
@@ -6,6 +6,7 @@
 import com.iailab.module.system.dal.dataobject.permission.MenuDO;
 import org.apache.ibatis.annotations.Mapper;
 
+import java.util.Collection;
 import java.util.List;
 
 @Mapper
@@ -25,7 +26,18 @@
                 .eqIfPresent(MenuDO::getStatus, reqVO.getStatus()));
     }
 
+    default List<MenuDO> selectAppMenuList(Long tenantId, MenuListReqVO reqVO) {
+        return selectList(new LambdaQueryWrapperX<MenuDO>()
+                .likeIfPresent(MenuDO::getName, reqVO.getName())
+                .eqIfPresent(MenuDO::getStatus, reqVO.getStatus())
+                .eq(MenuDO::getTenantId, tenantId));
+    }
+
     default List<MenuDO> selectListByPermission(String permission) {
         return selectList(MenuDO::getPermission, permission);
     }
+
+    default List<MenuDO> selectListByParentId(Collection<Long> parentIds) {
+        return selectList(MenuDO::getParentId, parentIds);
+    }
 }
diff --git a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/dal/mysql/user/AdminUserMapper.java b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/dal/mysql/user/AdminUserMapper.java
index e0a47b9..b178be9 100644
--- a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/dal/mysql/user/AdminUserMapper.java
+++ b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/dal/mysql/user/AdminUserMapper.java
@@ -1,8 +1,10 @@
 package com.iailab.module.system.dal.mysql.user;
 
+import com.baomidou.dynamic.datasource.annotation.Master;
 import com.iailab.framework.common.pojo.PageResult;
 import com.iailab.framework.mybatis.core.mapper.BaseMapperX;
 import com.iailab.framework.mybatis.core.query.LambdaQueryWrapperX;
+import com.iailab.framework.tenant.core.db.dynamic.TenantDS;
 import com.iailab.module.system.controller.admin.user.vo.user.UserPageReqVO;
 import com.iailab.module.system.dal.dataobject.user.AdminUserDO;
 import org.apache.ibatis.annotations.Mapper;
@@ -11,6 +13,7 @@
 import java.util.List;
 
 @Mapper
+@Master
 public interface AdminUserMapper extends BaseMapperX<AdminUserDO> {
 
     default AdminUserDO selectByUsername(String username) {
diff --git a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/app/AppGroupService.java b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/app/AppGroupService.java
new file mode 100644
index 0000000..6391ba9
--- /dev/null
+++ b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/app/AppGroupService.java
@@ -0,0 +1,31 @@
+package com.iailab.module.system.service.app;
+
+import com.iailab.framework.common.pojo.PageResult;
+import com.iailab.module.system.controller.admin.app.vo.AppGroupPageReqVO;
+import com.iailab.module.system.controller.admin.app.vo.AppGroupSaveReqVO;
+import com.iailab.module.system.controller.admin.app.vo.AppPageReqVO;
+import com.iailab.module.system.dal.dataobject.app.AppDO;
+import com.iailab.module.system.dal.dataobject.app.AppGroupDO;
+
+import java.util.List;
+
+/**
+ * @author Houzhongjian
+ * @Description
+ * @createTime 2024年09月20日
+ */
+public interface AppGroupService {
+
+    Long create(AppGroupSaveReqVO createReqVO);
+
+    Long update(AppGroupSaveReqVO createReqVO);
+
+    void delete(Long id);
+
+    AppGroupDO getInfo(Long id);
+
+    PageResult<AppGroupDO> getPage(AppGroupPageReqVO pageReqVO);
+
+    List<AppGroupDO> getList();
+
+}
diff --git a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/app/AppGroupServiceImpl.java b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/app/AppGroupServiceImpl.java
new file mode 100644
index 0000000..318481b
--- /dev/null
+++ b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/app/AppGroupServiceImpl.java
@@ -0,0 +1,72 @@
+package com.iailab.module.system.service.app;
+
+import com.iailab.framework.common.pojo.PageResult;
+import com.iailab.framework.common.util.object.BeanUtils;
+import com.iailab.framework.tenant.core.aop.TenantIgnore;
+import com.iailab.module.system.controller.admin.app.vo.AppGroupPageReqVO;
+import com.iailab.module.system.controller.admin.app.vo.AppGroupSaveReqVO;
+import com.iailab.module.system.dal.dataobject.app.AppGroupDO;
+import com.iailab.module.system.dal.mysql.app.AppGroupMapper;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+/**
+ * @author Houzhongjian
+ * @Description
+ * @createTime 2024年09月20日
+ */
+@Service
+@Slf4j
+public class AppGroupServiceImpl implements AppGroupService {
+
+    @Resource
+    private AppGroupMapper appGroupMapper;
+
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    @TenantIgnore
+    public Long create(AppGroupSaveReqVO createReqVO) {
+        AppGroupDO appGroup = BeanUtils.toBean(createReqVO, AppGroupDO.class);
+        appGroupMapper.insert(appGroup);
+        return appGroup.getId();
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    @TenantIgnore
+    public Long update(AppGroupSaveReqVO createReqVO) {
+        AppGroupDO appGroup = BeanUtils.toBean(createReqVO, AppGroupDO.class);
+        appGroupMapper.updateById(appGroup);
+        return appGroup.getId();
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    @TenantIgnore
+    public void delete(Long id) {
+        AppGroupDO appGroup = new AppGroupDO();
+        appGroup.setId(id);
+        appGroupMapper.deleteById(id);
+    }
+
+    @Override
+    public AppGroupDO getInfo(Long id) {
+        return appGroupMapper.selectById(id);
+    }
+
+    @Override
+    public PageResult<AppGroupDO> getPage(AppGroupPageReqVO pageReqVO) {
+        return appGroupMapper.selectPage(pageReqVO);
+    }
+
+    @Override
+    public List<AppGroupDO> getList(){
+        return appGroupMapper.selectList();
+    }
+
+}
\ No newline at end of file
diff --git a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/app/AppMenuService.java b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/app/AppMenuService.java
new file mode 100644
index 0000000..53aab58
--- /dev/null
+++ b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/app/AppMenuService.java
@@ -0,0 +1,118 @@
+package com.iailab.module.system.service.app;
+
+
+import com.iailab.module.system.controller.admin.permission.vo.menu.MenuListReqVO;
+import com.iailab.module.system.controller.admin.permission.vo.menu.MenuSaveVO;
+import com.iailab.module.system.dal.dataobject.app.AppMenuDO;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 应用菜单 Service 接口
+ *
+ * @author iailab
+ */
+public interface AppMenuService {
+
+    /**
+     * 创建应用菜单
+     *
+     * @param createReqVO 应用菜单信息
+     * @return 创建出来的应用菜单编号
+     */
+    Long createMenu(MenuSaveVO createReqVO);
+
+    /**
+     * 更新应用菜单
+     *
+     * @param updateReqVO 应用菜单信息
+     */
+    void updateMenu(MenuSaveVO updateReqVO);
+
+    /**
+     * 删除应用菜单
+     *
+     * @param id 应用菜单编号
+     */
+    void deleteMenu(Long id);
+
+    /**
+     * 获得所有应用菜单列表
+     *
+     * @return 应用菜单列表
+     */
+    List<AppMenuDO> getMenuList();
+
+    /**
+     * 获得所有应用菜单列表
+     *
+     * @return 应用菜单列表(API使用)
+     */
+    List<AppMenuDO> getMenuList(Integer type);
+
+    /**
+     * 基于租户,筛选应用菜单列表
+     * 注意,如果是系统租户,返回的还是全应用菜单
+     *
+     * @return 应用菜单列表(API使用)
+     */
+    List<AppMenuDO> getMenuListByTenant(Integer type);
+
+    /**
+     * 根据父菜单ID查询所有子菜单
+     *
+     * @return 应用菜单列表(API使用)
+     */
+    List<AppMenuDO> selectListByParentId(Collection<Long> parentIds);
+
+    /**
+     * 基于租户,筛选应用菜单列表
+     * 注意,如果是系统租户,返回的还是全应用菜单
+     *
+     * @param reqVO 筛选条件请求 VO
+     * @return 应用菜单列表
+     */
+    List<AppMenuDO> getMenuListByTenant(MenuListReqVO reqVO);
+
+    /**
+     * 过滤掉关闭的应用菜单及其子应用菜单
+     *
+     * @param list 应用菜单列表
+     * @return 过滤后的应用菜单列表
+     */
+    List<AppMenuDO> filterDisableMenus(List<AppMenuDO> list);
+
+    /**
+     * 筛选应用菜单列表
+     *
+     * @param reqVO 筛选条件请求 VO
+     * @return 应用菜单列表
+     */
+    List<AppMenuDO> getMenuList(MenuListReqVO reqVO);
+
+    /**
+     * 获得权限对应的应用菜单编号数组
+     *
+     * @param permission 权限标识
+     * @return 数组
+     */
+    List<Long> getMenuIdListByPermissionFromCache(String permission);
+
+    /**
+     * 获得应用菜单
+     *
+     * @param id 应用菜单编号
+     * @return 应用菜单
+     */
+    AppMenuDO getMenu(Long id);
+
+    /**
+     * 获得应用菜单数组
+     *
+     * @param ids 应用菜单编号数组
+     * @return 应用菜单数组
+     */
+    List<AppMenuDO> getMenuList(Collection<Long> ids);
+
+}
diff --git a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/app/AppMenuServiceImpl.java b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/app/AppMenuServiceImpl.java
new file mode 100644
index 0000000..25ccd1f
--- /dev/null
+++ b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/app/AppMenuServiceImpl.java
@@ -0,0 +1,281 @@
+package com.iailab.module.system.service.app;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.Lists;
+import com.iailab.framework.common.enums.CommonStatusEnum;
+import com.iailab.framework.common.util.object.BeanUtils;
+import com.iailab.module.system.controller.admin.permission.vo.menu.MenuListReqVO;
+import com.iailab.module.system.controller.admin.permission.vo.menu.MenuSaveVO;
+import com.iailab.module.system.dal.dataobject.app.AppMenuDO;
+import com.iailab.module.system.dal.mysql.app.AppMenuMapper;
+import com.iailab.module.system.dal.redis.RedisKeyConstants;
+import com.iailab.module.system.enums.permission.MenuTypeEnum;
+import com.iailab.module.system.service.tenant.TenantService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.annotation.Resource;
+import java.util.*;
+
+import static com.iailab.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static com.iailab.framework.common.util.collection.CollectionUtils.convertList;
+import static com.iailab.framework.common.util.collection.CollectionUtils.convertMap;
+import static com.iailab.module.system.dal.dataobject.app.AppMenuDO.ID_ROOT;
+import static com.iailab.module.system.enums.ErrorCodeConstants.*;
+
+
+/**
+ * 菜单 Service 实现
+ *
+ * @author iailab
+ */
+@Service
+@Slf4j
+public class AppMenuServiceImpl implements AppMenuService {
+
+    @Resource
+    private AppMenuMapper appMenuMapper;
+    @Resource
+    @Lazy // 延迟,避免循环依赖报错
+    private TenantService tenantService;
+
+    @Override
+    @CacheEvict(value = RedisKeyConstants.PERMISSION_MENU_ID_LIST, key = "#createReqVO.permission",
+            condition = "#createReqVO.permission != null")
+    public Long createMenu(MenuSaveVO createReqVO) {
+        // 校验父菜单存在
+        validateParentMenu(createReqVO.getParentId(), null);
+        // 校验菜单(自己)
+        validateMenu(createReqVO.getParentId(), createReqVO.getName(), null);
+
+        // 插入数据库
+        AppMenuDO menu = BeanUtils.toBean(createReqVO, AppMenuDO.class);
+        initMenuProperty(menu);
+        appMenuMapper.insert(menu);
+        // 返回
+        return menu.getId();
+    }
+
+    @Override
+    @CacheEvict(value = RedisKeyConstants.PERMISSION_MENU_ID_LIST,
+            allEntries = true) // allEntries 清空所有缓存,因为 permission 如果变更,涉及到新老两个 permission。直接清理,简单有效
+    public void updateMenu(MenuSaveVO updateReqVO) {
+        // 校验更新的菜单是否存在
+        if (appMenuMapper.selectById(updateReqVO.getId()) == null) {
+            throw exception(MENU_NOT_EXISTS);
+        }
+        // 校验父菜单存在
+        validateParentMenu(updateReqVO.getParentId(), updateReqVO.getId());
+        // 校验菜单(自己)
+        validateMenu(updateReqVO.getParentId(), updateReqVO.getName(), updateReqVO.getId());
+
+        // 更新到数据库
+        AppMenuDO updateObj = BeanUtils.toBean(updateReqVO, AppMenuDO.class);
+        initMenuProperty(updateObj);
+        appMenuMapper.updateById(updateObj);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    @CacheEvict(value = RedisKeyConstants.PERMISSION_MENU_ID_LIST,
+            allEntries = true) // allEntries 清空所有缓存,因为此时不知道 id 对应的 permission 是多少。直接清理,简单有效
+    public void deleteMenu(Long id) {
+        // 校验是否还有子菜单
+        if (appMenuMapper.selectCountByParentId(id) > 0) {
+            throw exception(MENU_EXISTS_CHILDREN);
+        }
+        // 校验删除的菜单是否存在
+        if (appMenuMapper.selectById(id) == null) {
+            throw exception(MENU_NOT_EXISTS);
+        }
+        // 标记删除
+        appMenuMapper.deleteById(id);
+    }
+
+    @Override
+    public List<AppMenuDO> getMenuList() {
+        return appMenuMapper.selectList();
+    }
+
+    @Override
+    public List<AppMenuDO> getMenuList(Integer type) {
+        LambdaQueryWrapper<AppMenuDO> queryWrapper = new LambdaQueryWrapper<>();
+        if (type == 1) {
+            queryWrapper.eq(AppMenuDO::getType, type);
+        }
+        return appMenuMapper.selectList(queryWrapper);
+    }
+
+    @Override
+    public List<AppMenuDO> getMenuListByTenant(Integer type) {
+        // 查询所有菜单,并过滤掉关闭的节点
+        List<AppMenuDO> menus = getMenuList(type);
+        // 开启多租户的情况下,需要过滤掉未开通的菜单
+        tenantService.handleTenantMenu(menuIds -> menus.removeIf(menu -> !CollUtil.contains(menuIds, menu.getId())));
+        return menus;
+    }
+
+    @Override
+    public List<AppMenuDO> selectListByParentId(Collection<Long> parentIds) {
+        return appMenuMapper.selectListByParentId(parentIds);
+    }
+
+    @Override
+    public List<AppMenuDO> getMenuListByTenant(MenuListReqVO reqVO) {
+        // 查询所有菜单,并过滤掉关闭的节点
+        List<AppMenuDO> menus = getMenuList(reqVO);
+        // 开启多租户的情况下,需要过滤掉未开通的菜单
+        tenantService.handleTenantMenu(menuIds -> menus.removeIf(menu -> !CollUtil.contains(menuIds, menu.getId())));
+        return menus;
+    }
+
+    @Override
+    public List<AppMenuDO> filterDisableMenus(List<AppMenuDO> menuList) {
+        if (CollUtil.isEmpty(menuList)){
+            return Collections.emptyList();
+        }
+        Map<Long, AppMenuDO> menuMap = convertMap(menuList, AppMenuDO::getId);
+
+        // 遍历 menu 菜单,查找不是禁用的菜单,添加到 enabledMenus 结果
+        List<AppMenuDO> enabledMenus = new ArrayList<>();
+        Set<Long> disabledMenuCache = new HashSet<>(); // 存下递归搜索过被禁用的菜单,防止重复的搜索
+        for (AppMenuDO menu : menuList) {
+            if (isMenuDisabled(menu, menuMap, disabledMenuCache)) {
+                continue;
+            }
+            enabledMenus.add(menu);
+        }
+        return enabledMenus;
+    }
+
+    private boolean isMenuDisabled(AppMenuDO node, Map<Long, AppMenuDO> menuMap, Set<Long> disabledMenuCache) {
+        // 如果已经判定是禁用的节点,直接结束
+        if (disabledMenuCache.contains(node.getId())) {
+            return true;
+        }
+
+        // 1. 遍历到 parentId 为根节点,则无需判断
+        Long parentId = node.getParentId();
+        if (ObjUtil.equal(parentId, ID_ROOT)) {
+            if (CommonStatusEnum.isDisable(node.getStatus())) {
+                disabledMenuCache.add(node.getId());
+                return true;
+            }
+            return false;
+        }
+
+        // 2. 继续遍历 parent 节点
+        AppMenuDO parent = menuMap.get(parentId);
+        if (parent == null || isMenuDisabled(parent, menuMap, disabledMenuCache)) {
+            disabledMenuCache.add(node.getId());
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public List<AppMenuDO> getMenuList(MenuListReqVO reqVO) {
+        return appMenuMapper.selectList(reqVO);
+    }
+
+    @Override
+    @Cacheable(value = RedisKeyConstants.PERMISSION_MENU_ID_LIST, key = "#permission")
+    public List<Long> getMenuIdListByPermissionFromCache(String permission) {
+        List<AppMenuDO> menus = appMenuMapper.selectListByPermission(permission);
+        return convertList(menus, AppMenuDO::getId);
+    }
+
+    @Override
+    public AppMenuDO getMenu(Long id) {
+        return appMenuMapper.selectById(id);
+    }
+
+    @Override
+    public List<AppMenuDO> getMenuList(Collection<Long> ids) {
+        // 当 ids 为空时,返回一个空的实例对象
+        if (CollUtil.isEmpty(ids)) {
+            return Lists.newArrayList();
+        }
+        return appMenuMapper.selectBatchIds(ids);
+    }
+
+    /**
+     * 校验父菜单是否合法
+     * <p>
+     * 1. 不能设置自己为父菜单
+     * 2. 父菜单不存在
+     * 3. 父菜单必须是 {@link MenuTypeEnum#MENU} 菜单类型
+     *
+     * @param parentId 父菜单编号
+     * @param childId  当前菜单编号
+     */
+    @VisibleForTesting
+    void validateParentMenu(Long parentId, Long childId) {
+        if (parentId == null || ID_ROOT.equals(parentId)) {
+            return;
+        }
+        // 不能设置自己为父菜单
+        if (parentId.equals(childId)) {
+            throw exception(MENU_PARENT_ERROR);
+        }
+        AppMenuDO menu = appMenuMapper.selectById(parentId);
+        // 父菜单不存在
+        if (menu == null) {
+            throw exception(MENU_PARENT_NOT_EXISTS);
+        }
+        // 父菜单必须是目录或者菜单类型
+        if (!MenuTypeEnum.DIR.getType().equals(menu.getType())
+                && !MenuTypeEnum.MENU.getType().equals(menu.getType())) {
+            throw exception(MENU_PARENT_NOT_DIR_OR_MENU);
+        }
+    }
+
+    /**
+     * 校验菜单是否合法
+     * <p>
+     * 1. 校验相同父菜单编号下,是否存在相同的菜单名
+     *
+     * @param name     菜单名字
+     * @param parentId 父菜单编号
+     * @param id       菜单编号
+     */
+    @VisibleForTesting
+    void validateMenu(Long parentId, String name, Long id) {
+        AppMenuDO menu = appMenuMapper.selectByParentIdAndName(parentId, name);
+        if (menu == null) {
+            return;
+        }
+        // 如果 id 为空,说明不用比较是否为相同 id 的菜单
+        if (id == null) {
+            throw exception(MENU_NAME_DUPLICATE);
+        }
+        if (!menu.getId().equals(id)) {
+            throw exception(MENU_NAME_DUPLICATE);
+        }
+    }
+
+    /**
+     * 初始化菜单的通用属性。
+     * <p>
+     * 例如说,只有目录或者菜单类型的菜单,才设置 icon
+     *
+     * @param menu 菜单
+     */
+    private void initMenuProperty(AppMenuDO menu) {
+        // 菜单为按钮类型时,无需 component、icon、path 属性,进行置空
+        if (MenuTypeEnum.BUTTON.getType().equals(menu.getType())) {
+            menu.setComponent("");
+            menu.setComponentName("");
+            menu.setIcon("");
+            menu.setPath("");
+        }
+    }
+
+}
diff --git a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/app/AppService.java b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/app/AppService.java
index 8c9bd4e..e903e4f 100644
--- a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/app/AppService.java
+++ b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/app/AppService.java
@@ -5,6 +5,8 @@
 import com.iailab.module.system.controller.admin.app.vo.AppSaveReqVO;
 import com.iailab.module.system.dal.dataobject.app.AppDO;
 
+import java.util.List;
+
 /**
  * @author PanZhibao
  * @Description
@@ -21,4 +23,11 @@
     AppDO getInfo(Long id);
 
     PageResult<AppDO> getPage(AppPageReqVO pageReqVO);
+
+    List<AppDO> getList();
+
+    AppDO getAppByTenantId(Long tenantId);
+
+//    List<AppMenuRespDTO> getAppMenu(Long id);
+
 }
diff --git a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/app/AppServiceImpl.java b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/app/AppServiceImpl.java
index cbfe760..c090de3 100644
--- a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/app/AppServiceImpl.java
+++ b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/app/AppServiceImpl.java
@@ -1,15 +1,40 @@
 package com.iailab.module.system.service.app;
 
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.iailab.framework.common.pojo.PageResult;
 import com.iailab.framework.common.util.object.BeanUtils;
+import com.iailab.framework.mybatis.core.query.LambdaQueryWrapperX;
+import com.iailab.framework.security.core.util.SecurityFrameworkUtils;
+import com.iailab.framework.tenant.core.aop.TenantIgnore;
 import com.iailab.module.system.controller.admin.app.vo.AppPageReqVO;
 import com.iailab.module.system.controller.admin.app.vo.AppSaveReqVO;
 import com.iailab.module.system.dal.dataobject.app.AppDO;
+import com.iailab.module.system.dal.dataobject.permission.MenuDO;
+import com.iailab.module.system.dal.dataobject.permission.RoleDO;
+import com.iailab.module.system.dal.dataobject.permission.RoleMenuDO;
+import com.iailab.module.system.dal.dataobject.tenant.TenantDO;
+import com.iailab.module.system.dal.dataobject.tenant.TenantPackageDO;
 import com.iailab.module.system.dal.mysql.app.AppMapper;
+import com.iailab.module.system.dal.mysql.permission.MenuMapper;
+import com.iailab.module.system.dal.mysql.permission.RoleMapper;
+import com.iailab.module.system.dal.mysql.permission.RoleMenuMapper;
+import com.iailab.module.system.dal.mysql.tenant.TenantMapper;
+import com.iailab.module.system.dal.mysql.tenant.TenantPackageMapper;
+import com.iailab.module.system.enums.permission.MenuTypeEnum;
+import com.iailab.module.system.service.permission.PermissionService;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.ObjectUtils;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 
 import javax.annotation.Resource;
+import java.util.*;
+
+import static com.iailab.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static com.iailab.framework.tenant.core.context.TenantContextHolder.getTenantId;
+import static com.iailab.module.system.enums.ErrorCodeConstants.MENU_EXISTS_CHILDREN;
+import static com.iailab.module.system.enums.ErrorCodeConstants.MENU_NOT_EXISTS;
 
 /**
  * @author PanZhibao
@@ -23,23 +48,54 @@
     @Resource
     private AppMapper appMapper;
 
+    @Resource
+    private MenuMapper menuMapper;
+
+    @Resource
+    private RoleMapper roleMapper;
+
+    @Resource
+    private RoleMenuMapper roleMenuMapper;
+
+    @Resource
+    private PermissionService permissionService;
+
+    @Resource
+    private TenantPackageMapper tenantPackageMapper;
+
+    @Resource
+    private TenantMapper tenantMapper;
+
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
+    @TenantIgnore
     public Long create(AppSaveReqVO createReqVO) {
         AppDO app = BeanUtils.toBean(createReqVO, AppDO.class);
         appMapper.insert(app);
+//        //为应用创建默认菜单并授权
+        dealAppMenu(1, app);
         return app.getId();
     }
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
+    @TenantIgnore
     public Long update(AppSaveReqVO createReqVO) {
         AppDO app = BeanUtils.toBean(createReqVO, AppDO.class);
         appMapper.updateById(app);
+//        //修改默认菜单并授权
+        dealAppMenu(2, app);
         return app.getId();
     }
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
+    @TenantIgnore
     public void delete(Long id) {
+        AppDO appDO = new AppDO();
+        appDO.setId(id);
+        dealAppMenu(3, appDO);
         appMapper.deleteById(id);
     }
 
@@ -53,4 +109,164 @@
         return appMapper.selectPage(pageReqVO);
     }
 
+    @Override
+    public List<AppDO> getList() {
+        Long tenantId = getTenantId();
+        LambdaQueryWrapperX<MenuDO> menuDOLambdaQueryWrapperX = new LambdaQueryWrapperX<>();
+        menuDOLambdaQueryWrapperX.eq(MenuDO::getParentId, 0)
+                .eq(MenuDO::getAppId, 0);
+        if (tenantId != 1) {
+            //非系统租户查租户套餐
+            TenantDO tenantDO = tenantMapper.selectById(tenantId);
+            TenantPackageDO tenantPackageDO = tenantPackageMapper.selectById(tenantDO.getPackageId());
+            menuDOLambdaQueryWrapperX.in(MenuDO::getId, tenantPackageDO.getMenuIds());
+        }
+        //查询系统应用菜单
+//        List<MenuDO> menuDOS = menuMapper.selectList(menuDOLambdaQueryWrapperX);
+//        List<AppDO> systemApps = convertMenuToApp(menuDOS);
+        //创建一个系统管理应用菜单
+        AppDO aDo = new AppDO();
+        aDo.setAppType(1);
+        aDo.setAppName("系统管理");
+        aDo.setOrderNum(0);
+        List<AppDO> systemApps = new ArrayList<>();
+        systemApps.add(aDo);
+        List<AppDO> appDOS = appMapper.selectList();
+        //暂时先遍历处理应用菜单和应用类型
+        appDOS.stream().forEach(appDO -> {
+            List<MenuDO> menuDOS = menuMapper.selectList(new LambdaQueryWrapper<MenuDO>().eq(MenuDO::getParentId, 0)
+                    .eq(MenuDO::getAppId, appDO.getId()));
+            appDO.setAppMenuId(menuDOS.get(0).getId());
+            appDO.setAppType(2);
+        });
+        systemApps.addAll(appDOS);
+        return systemApps;
+    }
+
+    @Override
+    public AppDO getAppByTenantId(Long tenantId) {
+        //暂时支持一个租户对应一个应用
+        List<AppDO> appDOS = appMapper.selectList(new LambdaQueryWrapper<AppDO>().eq(AppDO::getTenantId, tenantId));
+        if(ObjectUtils.isNotEmpty(appDOS)) {
+            return appDOS.get(0);
+        } else {
+            AppDO appDO = new AppDO();
+            appDO.setTenantId(tenantId);
+            appDO.setId(0L);
+            return appDO;
+        }
+    }
+
+//    @Override
+//    public List<MenuRespDTO> getAppMenu(Long id) {
+//
+//        List<MenuDO> children = new LinkedList<>();
+//        // 遍历每一层
+//        Collection<Long> parentIds = Collections.singleton(id);
+//        for (int i = 0; i < Short.MAX_VALUE; i++) { // 使用 Short.MAX_VALUE 避免 bug 场景下,存在死循环
+//            // 查询当前层,所有的子应用菜单
+//            List<MenuDO> menus = menuMapper.selectListByParentId(parentIds);
+//            // 1. 如果没有子菜单,则结束遍历
+//            if (CollUtil.isEmpty(menus)) {
+//                break;
+//            }
+//            // 2. 如果有子应用菜单,继续遍历
+//            children.addAll(menus);
+//            parentIds = convertSet(menus, MenuDO::getId);
+//        }
+//        children = menuService.filterDisableMenus(children);
+//        return AuthConvert.INSTANCE.buildMenuTree(id, children);
+//
+//    }
+
+    private void dealAppMenu(Integer type, AppDO app){
+        String loginUserNickname = SecurityFrameworkUtils.getLoginUserNickname();
+        MenuDO menuDO = new MenuDO();
+        menuDO.setAppId(app.getId());
+        menuDO.setName(app.getAppName());
+        menuDO.setType(MenuTypeEnum.DIR.getType());
+        menuDO.setSort(app.getOrderNum());
+        menuDO.setPath("/" + app.getAppCode());
+        menuDO.setTenantId(app.getTenantId());
+        if(type == 1){
+            menuDO.setCreator(loginUserNickname);
+            menuDO.setCreateTime(app.getCreateTime());
+            menuMapper.insert(menuDO);
+            //内置租户角色分配菜单
+            assignRoleMenu(menuDO.getId(), app.getTenantId());
+        } else if(type == 2){
+            LambdaUpdateWrapper<MenuDO> updateWrapper = new LambdaUpdateWrapper<>();
+            updateWrapper.eq(MenuDO::getAppId, app.getId());
+            updateWrapper.eq(MenuDO::getParentId, 0L);
+            List<MenuDO> menuDOS = menuMapper.selectList(updateWrapper);
+            if(menuDOS.size() > 0){
+                menuDO.setUpdater(loginUserNickname);
+                menuDO.setUpdateTime(app.getUpdateTime());
+                menuMapper.update(menuDO, updateWrapper);
+            } else {
+                menuDO.setCreator(loginUserNickname);
+                menuDO.setCreateTime(app.getCreateTime());
+                menuMapper.insert(menuDO);
+                //内置租户角色分配菜单
+                assignRoleMenu(menuDO.getId(), app.getTenantId());
+            }
+        } else if(type == 3){
+            //删除租户、角色权限
+            app = appMapper.selectById(app.getId());
+            LambdaQueryWrapperX<MenuDO> menuWrapper = new LambdaQueryWrapperX<>();
+            menuWrapper.eq(MenuDO::getAppId, app.getId());
+            menuWrapper.eq(MenuDO::getType, MenuTypeEnum.DIR.getType());
+            MenuDO menu = menuMapper.selectOne(menuWrapper);
+            TenantDO tenantDO = tenantMapper.selectById(app.getTenantId());
+            TenantPackageDO tenantPackageDO = tenantPackageMapper.selectById(tenantDO.getPackageId());
+            Set<Long> menuIds = tenantPackageDO.getMenuIds();
+            menuIds.remove(menu.getId());
+            // 校验是否还有子菜单
+            if (menuMapper.selectCountByParentId(menu.getId()) > 0) {
+                throw exception(MENU_EXISTS_CHILDREN);
+            }
+            // 校验删除的菜单是否存在
+            if (menuMapper.selectById(menu.getId()) == null) {
+                throw exception(MENU_NOT_EXISTS);
+            }
+            // 标记删除
+            menuMapper.deleteById(menu.getId());
+            // 删除授予给角色的权限
+            permissionService.processMenuDeleted(menu.getId());
+            //删除菜单
+            menuMapper.delete(menuWrapper);
+        }
+    }
+
+    private void assignRoleMenu(Long menuId, Long tenantId) {
+        //查询内置租户管理员
+        LambdaQueryWrapperX<RoleDO> roleQueryWrapper = new LambdaQueryWrapperX<>();
+        roleQueryWrapper.eq(RoleDO::getCode, "tenant_admin");
+        roleQueryWrapper.eq(RoleDO::getTenantId, tenantId);
+        RoleDO roleDO = roleMapper.selectOne(roleQueryWrapper);
+        RoleMenuDO entity = new RoleMenuDO();
+        entity.setRoleId(roleDO.getId());
+        entity.setMenuId(menuId);
+        entity.setTenantId(tenantId);
+        roleMenuMapper.insert(entity);
+        TenantDO tenantDO = tenantMapper.selectById(tenantId);
+        TenantPackageDO tenantPackageDO = tenantPackageMapper.selectById(tenantDO.getPackageId());
+        Set<Long> menuIds = tenantPackageDO.getMenuIds();
+        menuIds.add(menuId);
+        tenantPackageMapper.updateById(tenantPackageDO);
+    }
+
+    private List<AppDO> convertMenuToApp(List<MenuDO> menuDOS) {
+        List<AppDO> appDOS = new ArrayList<>();
+        menuDOS.stream().forEach(menuDO -> {
+            AppDO appDO = new AppDO();
+            appDO.setAppName(menuDO.getName());
+            appDO.setOrderNum(menuDO.getSort());
+            appDO.setAppMenuId(menuDO.getId());
+            appDO.setAppType(1);
+            appDOS.add(appDO);
+        });
+        return appDOS;
+    }
+
 }
\ No newline at end of file
diff --git a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/permission/MenuService.java b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/permission/MenuService.java
index 705bd83..2ab458b 100644
--- a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/permission/MenuService.java
+++ b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/permission/MenuService.java
@@ -54,6 +54,15 @@
     List<MenuDO> getMenuListByTenant(MenuListReqVO reqVO);
 
     /**
+     * 基于租户,筛选应用菜单列表
+     * 注意,如果是系统租户,返回的还是全菜单
+     *
+     * @param reqVO 筛选条件请求 VO
+     * @return 应用菜单列表
+     */
+    List<MenuDO> getAppMenuListByTenant(MenuListReqVO reqVO);
+
+    /**
      * 过滤掉关闭的菜单及其子菜单
      *
      * @param list 菜单列表
@@ -70,6 +79,14 @@
     List<MenuDO> getMenuList(MenuListReqVO reqVO);
 
     /**
+     * 筛选菜单列表
+     *
+     * @param reqVO 筛选条件请求 VO
+     * @return 菜单列表
+     */
+    List<MenuDO> getAppMenuList(MenuListReqVO reqVO);
+
+    /**
      * 获得权限对应的菜单编号数组
      *
      * @param permission 权限标识
diff --git a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/permission/MenuServiceImpl.java b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/permission/MenuServiceImpl.java
index b93065a..17d7e40 100644
--- a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/permission/MenuServiceImpl.java
+++ b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/permission/MenuServiceImpl.java
@@ -8,12 +8,20 @@
 import com.iailab.framework.common.util.object.BeanUtils;
 import com.iailab.module.system.controller.admin.permission.vo.menu.MenuListReqVO;
 import com.iailab.module.system.controller.admin.permission.vo.menu.MenuSaveVO;
+import com.iailab.module.system.controller.admin.tenant.vo.packages.TenantPackageSaveReqVO;
+import com.iailab.module.system.dal.dataobject.app.AppDO;
 import com.iailab.module.system.dal.dataobject.permission.MenuDO;
+import com.iailab.module.system.dal.dataobject.permission.RoleDO;
+import com.iailab.module.system.dal.dataobject.tenant.TenantDO;
+import com.iailab.module.system.dal.dataobject.tenant.TenantPackageDO;
 import com.iailab.module.system.dal.mysql.permission.MenuMapper;
 import com.iailab.module.system.dal.redis.RedisKeyConstants;
 import com.iailab.module.system.enums.permission.MenuTypeEnum;
+import com.iailab.module.system.service.app.AppService;
+import com.iailab.module.system.service.tenant.TenantPackageService;
 import com.iailab.module.system.service.tenant.TenantService;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.ObjectUtils;
 import org.springframework.cache.annotation.CacheEvict;
 import org.springframework.cache.annotation.Cacheable;
 import org.springframework.context.annotation.Lazy;
@@ -26,6 +34,7 @@
 import static com.iailab.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static com.iailab.framework.common.util.collection.CollectionUtils.convertList;
 import static com.iailab.framework.common.util.collection.CollectionUtils.convertMap;
+import static com.iailab.framework.tenant.core.context.TenantContextHolder.getTenantId;
 import static com.iailab.module.system.dal.dataobject.permission.MenuDO.ID_ROOT;
 import static com.iailab.module.system.enums.ErrorCodeConstants.*;
 
@@ -47,9 +56,19 @@
     @Lazy // 延迟,避免循环依赖报错
     private TenantService tenantService;
 
+    @Resource
+    private TenantPackageService tenantPackageService;
+
+    @Resource
+    private AppService appService;
+
+    @Resource
+    private RoleService roleService;
+
     @Override
     @CacheEvict(value = RedisKeyConstants.PERMISSION_MENU_ID_LIST, key = "#createReqVO.permission",
             condition = "#createReqVO.permission != null")
+    @Transactional(rollbackFor = Exception.class)
     public Long createMenu(MenuSaveVO createReqVO) {
         // 校验父菜单存在
         validateParentMenu(createReqVO.getParentId(), null);
@@ -59,7 +78,15 @@
         // 插入数据库
         MenuDO menu = BeanUtils.toBean(createReqVO, MenuDO.class);
         initMenuProperty(menu);
+
+        //菜单归属租户和应用
+        Long tenantId = getTenantId();
+        menu.setTenantId(tenantId);
+        menu.setAppId(createReqVO.getAppId());
         menuMapper.insert(menu);
+        if(tenantId != 1L) {
+            dealPermission(menu);
+        }
         // 返回
         return menu.getId();
     }
@@ -67,6 +94,7 @@
     @Override
     @CacheEvict(value = RedisKeyConstants.PERMISSION_MENU_ID_LIST,
             allEntries = true) // allEntries 清空所有缓存,因为 permission 如果变更,涉及到新老两个 permission。直接清理,简单有效
+    @Transactional(rollbackFor = Exception.class)
     public void updateMenu(MenuSaveVO updateReqVO) {
         // 校验更新的菜单是否存在
         if (menuMapper.selectById(updateReqVO.getId()) == null) {
@@ -80,6 +108,11 @@
         // 更新到数据库
         MenuDO updateObj = BeanUtils.toBean(updateReqVO, MenuDO.class);
         initMenuProperty(updateObj);
+        //菜单归属租户和应用
+        Long tenantId = getTenantId();
+        AppDO appDO = appService.getAppByTenantId(tenantId);
+        updateObj.setTenantId(tenantId);
+        updateObj.setAppId(appDO.getId());
         menuMapper.updateById(updateObj);
     }
 
@@ -111,6 +144,15 @@
     public List<MenuDO> getMenuListByTenant(MenuListReqVO reqVO) {
         // 查询所有菜单,并过滤掉关闭的节点
         List<MenuDO> menus = getMenuList(reqVO);
+        // 开启多租户的情况下,需要过滤掉未开通的菜单
+        tenantService.handleTenantMenu(menuIds -> menus.removeIf(menu -> !CollUtil.contains(menuIds, menu.getId())));
+        return menus;
+    }
+
+    @Override
+    public List<MenuDO> getAppMenuListByTenant(MenuListReqVO reqVO) {
+        // 查询所有菜单,并过滤掉关闭的节点
+        List<MenuDO> menus = getAppMenuList(reqVO);
         // 开启多租户的情况下,需要过滤掉未开通的菜单
         tenantService.handleTenantMenu(menuIds -> menus.removeIf(menu -> !CollUtil.contains(menuIds, menu.getId())));
         return menus;
@@ -163,6 +205,13 @@
     @Override
     public List<MenuDO> getMenuList(MenuListReqVO reqVO) {
         return menuMapper.selectList(reqVO);
+    }
+
+    @Override
+    public List<MenuDO> getAppMenuList(MenuListReqVO reqVO) {
+        // 获取 tenantId
+        Long tenantId = getTenantId();
+        return menuMapper.selectAppMenuList(tenantId, reqVO);
     }
 
     @Override
@@ -258,4 +307,20 @@
         }
     }
 
+    /**
+     * 新创建菜单赋权给租户管理员
+     */
+
+    private void dealPermission(MenuDO menu) {
+        Long tenantId = menu.getTenantId();
+        RoleDO role = roleService.getTenantAdminRole(tenantId);
+        TenantDO tenant = tenantService.getTenant(tenantId);
+        TenantPackageDO tenantPackage = tenantPackageService.getTenantPackage(tenant.getPackageId());
+        Set<Long> menuIds = tenantPackage.getMenuIds();
+        menuIds.add(menu.getId());
+        tenantPackage.setMenuIds(menuIds);
+        tenantPackageService.updateTenantPackage(BeanUtils.toBean(tenantPackage, TenantPackageSaveReqVO.class));
+        permissionService.assignRoleMenu(role.getId(), menuIds);
+    }
+
 }
diff --git a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/permission/PermissionService.java b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/permission/PermissionService.java
index 6bcb967..dfa9825 100644
--- a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/permission/PermissionService.java
+++ b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/permission/PermissionService.java
@@ -44,6 +44,14 @@
     void assignRoleMenu(Long roleId, Set<Long> menuIds);
 
     /**
+     * 设置角色应用菜单
+     *
+     * @param roleId  角色编号
+     * @param menuIds 菜单编号集合
+     */
+    void assignRoleAppMenu(Long roleId, Set<Long> menuIds);
+
+    /**
      * 处理角色删除时,删除关联授权数据
      *
      * @param roleId 角色编号
@@ -68,6 +76,16 @@
     }
 
     /**
+     * 获得角色拥有的应用菜单编号集合
+     *
+     * @param roleId 角色编号
+     * @return 菜单编号集合
+     */
+    default Set<Long> getRoleAppMenuListByRoleId(Long roleId) {
+        return getRoleAppMenuListByRoleId(singleton(roleId));
+    }
+
+    /**
      * 获得角色们拥有的菜单编号集合
      *
      * @param roleIds 角色编号数组
@@ -76,6 +94,14 @@
     Set<Long> getRoleMenuListByRoleId(Collection<Long> roleIds);
 
     /**
+     * 获得角色们拥有的应用菜单编号集合
+     *
+     * @param roleIds 角色编号数组
+     * @return 菜单编号集合
+     */
+    Set<Long> getRoleAppMenuListByRoleId(Collection<Long> roleIds);
+
+    /**
      * 获得拥有指定菜单的角色编号数组,从缓存中获取
      *
      * @param menuId 菜单编号
diff --git a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/permission/PermissionServiceImpl.java b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/permission/PermissionServiceImpl.java
index ad8e0a9..a4deaf4 100644
--- a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/permission/PermissionServiceImpl.java
+++ b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/permission/PermissionServiceImpl.java
@@ -8,15 +8,22 @@
 import com.iailab.framework.common.util.collection.CollectionUtils;
 import com.iailab.framework.datapermission.core.annotation.DataPermission;
 import com.iailab.module.system.api.permission.dto.DeptDataPermissionRespDTO;
+import com.iailab.module.system.controller.admin.permission.vo.menu.MenuListReqVO;
+import com.iailab.module.system.dal.dataobject.app.AppDO;
 import com.iailab.module.system.dal.dataobject.permission.MenuDO;
 import com.iailab.module.system.dal.dataobject.permission.RoleDO;
 import com.iailab.module.system.dal.dataobject.permission.RoleMenuDO;
 import com.iailab.module.system.dal.dataobject.permission.UserRoleDO;
+import com.iailab.module.system.dal.dataobject.tenant.TenantDO;
+import com.iailab.module.system.dal.dataobject.tenant.TenantPackageDO;
 import com.iailab.module.system.dal.mysql.permission.RoleMenuMapper;
 import com.iailab.module.system.dal.mysql.permission.UserRoleMapper;
 import com.iailab.module.system.dal.redis.RedisKeyConstants;
 import com.iailab.module.system.enums.permission.DataScopeEnum;
+import com.iailab.module.system.service.app.AppService;
 import com.iailab.module.system.service.dept.DeptService;
+import com.iailab.module.system.service.tenant.TenantPackageService;
+import com.iailab.module.system.service.tenant.TenantService;
 import com.iailab.module.system.service.user.AdminUserService;
 import com.baomidou.dynamic.datasource.annotation.DSTransactional;
 import com.google.common.annotations.VisibleForTesting;
@@ -35,6 +42,7 @@
 
 import static com.iailab.framework.common.util.collection.CollectionUtils.convertSet;
 import static com.iailab.framework.common.util.json.JsonUtils.toJsonString;
+import static com.iailab.framework.tenant.core.context.TenantContextHolder.getTenantId;
 
 /**
  * 权限 Service 实现类
@@ -58,6 +66,13 @@
     private DeptService deptService;
     @Resource
     private AdminUserService userService;
+    @Resource
+    private TenantService tenantService;
+    @Resource
+    private TenantPackageService tenantPackageService;
+    @Resource
+    private AppService appService;
+
 
     @Override
     public boolean hasAnyPermissions(Long userId, String... permissions) {
@@ -155,6 +170,37 @@
         }
     }
 
+    // ========== 角色-菜单的相关方法  ==========
+
+    @Override
+    @DSTransactional // 多数据源,使用 @DSTransactional 保证本地事务,以及数据源的切换
+    @CacheEvict(value = RedisKeyConstants.MENU_ROLE_ID_LIST,
+            allEntries = true) // allEntries 清空所有缓存,主要一次更新涉及到的 menuIds 较多,反倒批量会更快
+    public void assignRoleAppMenu(Long roleId, Set<Long> menuIds) {
+        // 获得角色拥有应用菜单编号
+        MenuListReqVO reqVO = new MenuListReqVO();
+        List<MenuDO> appMenuList = menuService.getAppMenuList(reqVO);
+        Set<Long> appMenuIds = convertSet(appMenuList, MenuDO::getId);
+        Set<Long> dbMenuIds = convertSet(roleMenuMapper.selectListByRoleId(roleId), RoleMenuDO::getMenuId);
+        dbMenuIds.retainAll(appMenuIds);
+        // 计算新增和删除的菜单编号
+        Set<Long> menuIdList = CollUtil.emptyIfNull(menuIds);
+        Collection<Long> createMenuIds = CollUtil.subtract(menuIdList, dbMenuIds);
+        Collection<Long> deleteMenuIds = CollUtil.subtract(dbMenuIds, menuIdList);
+        // 执行新增和删除。对于已经授权的菜单,不用做任何处理
+        if (CollUtil.isNotEmpty(createMenuIds)) {
+            roleMenuMapper.insertBatch(CollectionUtils.convertList(createMenuIds, menuId -> {
+                RoleMenuDO entity = new RoleMenuDO();
+                entity.setRoleId(roleId);
+                entity.setMenuId(menuId);
+                return entity;
+            }));
+        }
+        if (CollUtil.isNotEmpty(deleteMenuIds)) {
+            roleMenuMapper.deleteListByRoleIdAndMenuIds(roleId, deleteMenuIds);
+        }
+    }
+
     @Override
     @Transactional(rollbackFor = Exception.class)
     @Caching(evict = {
@@ -181,16 +227,32 @@
         if (CollUtil.isEmpty(roleIds)) {
             return Collections.emptySet();
         }
-
-        // 如果是管理员的情况下,获取全部菜单编号
-        if (roleService.hasAnySuperAdmin(roleIds)) {
-            return convertSet(menuService.getMenuList(), MenuDO::getId);
-        }
-        // 如果是非管理员的情况下,获得拥有的菜单编号
         return convertSet(roleMenuMapper.selectListByRoleId(roleIds), RoleMenuDO::getMenuId);
     }
 
     @Override
+    public Set<Long> getRoleAppMenuListByRoleId(Collection<Long> roleIds) {
+        if (CollUtil.isEmpty(roleIds)) {
+            return Collections.emptySet();
+        }
+
+        // 如果是管理员的情况下,获取全部应用菜单编号
+        if (roleService.hasAnySuperAdmin(roleIds)) {
+            MenuListReqVO reqVO = new MenuListReqVO();
+            return convertSet(menuService.getAppMenuList(reqVO), MenuDO::getId);
+        }
+        // 如果是非管理员的情况下,获得拥有的应用菜单编号
+        // 获取 tenantId
+        Long tenantId = getTenantId();
+        TenantDO tenant = tenantService.getTenant(tenantId);
+        TenantPackageDO tenantPackage = tenantPackageService.getTenantPackage(tenant.getPackageId());
+        Set<Long> menuIds = tenantPackage.getMenuIds();
+        Set<Long> longs = convertSet(roleMenuMapper.selectListByRoleId(roleIds), RoleMenuDO::getMenuId);
+        longs.retainAll(menuIds);
+        return longs;
+    }
+
+    @Override
     @Cacheable(value = RedisKeyConstants.MENU_ROLE_ID_LIST, key = "#menuId")
     public Set<Long> getMenuRoleIdListByMenuIdFromCache(Long menuId) {
         return convertSet(roleMenuMapper.selectListByMenuId(menuId), RoleMenuDO::getRoleId);
diff --git a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/permission/RoleService.java b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/permission/RoleService.java
index ebeb949..0c9e8d4 100644
--- a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/permission/RoleService.java
+++ b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/permission/RoleService.java
@@ -130,4 +130,6 @@
     RoleDO getRoleByName(String name);
 
     void insert(RoleDO role);
+
+    RoleDO getTenantAdminRole(Long tenantId);
 }
diff --git a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/permission/RoleServiceImpl.java b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/permission/RoleServiceImpl.java
index b4ae471..d77d9b3 100644
--- a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/permission/RoleServiceImpl.java
+++ b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/permission/RoleServiceImpl.java
@@ -269,4 +269,12 @@
         return SpringUtil.getBean(getClass());
     }
 
+    /**
+     * 查询租户管理员
+     */
+    public RoleDO getTenantAdminRole(Long tenantId) {
+        RoleDO roleDO = roleMapper.selectOne(new LambdaQueryWrapperX<RoleDO>().eq(RoleDO::getType, 1L).eq(RoleDO::getTenantId, tenantId));
+        return roleDO;
+    }
+
 }
diff --git a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/tenant/TenantService.java b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/tenant/TenantService.java
index 35fc598..fba01cb 100644
--- a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/tenant/TenantService.java
+++ b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/tenant/TenantService.java
@@ -66,6 +66,13 @@
     PageResult<TenantDO> getTenantPage(TenantPageReqVO pageReqVO);
 
     /**
+     * 获得租户列表
+     *
+     * @return 租户列表
+     */
+    List<TenantDO> getSimpleTenant();
+
+    /**
      * 获得名字对应的租户
      *
      * @param name 租户名
diff --git a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/tenant/TenantServiceImpl.java b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/tenant/TenantServiceImpl.java
index f0e3da3..4a6154e 100644
--- a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/tenant/TenantServiceImpl.java
+++ b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/tenant/TenantServiceImpl.java
@@ -37,6 +37,7 @@
 import org.springframework.validation.annotation.Validated;
 
 import javax.annotation.Resource;
+import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
@@ -246,6 +247,11 @@
     }
 
     @Override
+    public List<TenantDO> getSimpleTenant() {
+        return tenantMapper.selectList();
+    }
+
+    @Override
     public TenantDO getTenantByName(String name) {
         return tenantMapper.selectByName(name);
     }
diff --git a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/user/AdminUserServiceImpl.java b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/user/AdminUserServiceImpl.java
index 3c2648e..cfa0743 100644
--- a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/user/AdminUserServiceImpl.java
+++ b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/user/AdminUserServiceImpl.java
@@ -4,6 +4,7 @@
 import cn.hutool.core.collection.CollectionUtil;
 import cn.hutool.core.io.IoUtil;
 import cn.hutool.core.util.StrUtil;
+import com.baomidou.dynamic.datasource.annotation.DSTransactional;
 import com.iailab.framework.common.enums.CommonStatusEnum;
 import com.iailab.framework.common.exception.ServiceException;
 import com.iailab.framework.common.pojo.PageResult;
@@ -85,7 +86,7 @@
     private ConfigApi configApi;
 
     @Override
-    @Transactional(rollbackFor = Exception.class)
+    @DSTransactional
     @LogRecord(type = SYSTEM_USER_TYPE, subType = SYSTEM_USER_CREATE_SUB_TYPE, bizNo = "{{#user.id}}",
             success = SYSTEM_USER_CREATE_SUCCESS)
     public Long createUser(UserSaveReqVO createReqVO) {
@@ -116,7 +117,7 @@
     }
 
     @Override
-    @Transactional(rollbackFor = Exception.class)
+    @DSTransactional
     @LogRecord(type = SYSTEM_USER_TYPE, subType = SYSTEM_USER_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}",
             success = SYSTEM_USER_UPDATE_SUCCESS)
     public void updateUser(UserSaveReqVO updateReqVO) {

--
Gitblit v1.9.3