潘志宝
2024-09-18 ca0e59e087685df4bd3dc117101ddbf85c53eed0
Merge branch 'master' of ssh://172.16.8.100:29418/iailab-plat
已修改36个文件
已重命名2个文件
已添加36个文件
3114 ■■■■■ 文件已修改
iailab-module-model/iailab-module-model-biz/db/mysql.sql 106 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/controller/admin/MpkFileController.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/dao/MethodSettingDao.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/dao/SettingSelectDao.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/dto/MethodSettingDTO.java 64 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/dto/ModelMethodDTO.java 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/dto/MpkFileDTO.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/dto/SettingSelectDTO.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/entity/MethodSettingEntity.java 64 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/entity/ModelMethodEntity.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/entity/MpkFileEntity.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/entity/SettingSelectEntity.java 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/service/MethodSettingService.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/service/MpkFileService.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/service/SettingSelectService.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/service/impl/MethodSettingServiceImpl.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/service/impl/MpkFileServiceImpl.java 77 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/service/impl/ProjectServiceImpl.java 19 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/service/impl/SettingSelectServiceImpl.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-model/iailab-module-model-biz/src/main/resources/mapper/mpk/MpkFileDao.xml 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-system/iailab-module-system-api/src/main/java/com/iailab/module/system/api/app/AppApi.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-system/iailab-module-system-api/src/main/java/com/iailab/module/system/api/app/AppMenuApi.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-system/iailab-module-system-api/src/main/java/com/iailab/module/system/api/app/dto/AppMenuRespDTO.java 80 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-system/iailab-module-system-api/src/main/java/com/iailab/module/system/api/app/dto/AppMenuSimpleRespDTO.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-system/iailab-module-system-api/src/main/java/com/iailab/module/system/api/app/dto/AppRespDTO.java 75 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/api/app/AppApiImpl.java 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/api/app/AppMenuApiImpl.java 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/app/AppController.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/app/AppGroupController.java 83 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/app/AppMenuController.java 87 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/app/vo/AppGroupPageReqVO.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/app/vo/AppGroupRespVO.java 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/app/vo/AppGroupSaveReqVO.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/app/vo/AppMenuListReqVO.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/app/vo/AppMenuRespVO.java 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/app/vo/AppMenuSaveVO.java 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/app/vo/AppMenuSimpleRespVO.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/app/vo/AppRespVO.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/app/vo/AppSaveReqVO.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/auth/AuthController.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/oauth2/OAuth2OpenController.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/oauth2/vo/open/OAuth2OpenAccessTokenRespVO.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/oauth2/vo/open/OAuth2OpenLoginReqVO.java 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/permission/MenuController.java 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/permission/PermissionController.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/permission/vo/menu/MenuRespVO.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/permission/vo/menu/MenuSaveVO.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/controller/admin/tenant/TenantController.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/convert/app/AppConvert.java 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/convert/auth/AuthConvert.java 96 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/convert/oauth2/OAuth2OpenConvert.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/dal/dataobject/app/AppDO.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/dal/dataobject/app/AppGroupDO.java 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/dal/dataobject/app/AppMenuDO.java 114 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/dal/dataobject/permission/MenuDO.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/dal/mysql/app/AppGroupMapper.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/dal/mysql/app/AppMenuMapper.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/dal/mysql/permission/MenuMapper.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/dal/mysql/user/AdminUserMapper.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/app/AppGroupService.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/app/AppGroupServiceImpl.java 72 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/app/AppMenuService.java 118 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/app/AppMenuServiceImpl.java 281 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/app/AppService.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/app/AppServiceImpl.java 216 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/permission/MenuService.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/permission/MenuServiceImpl.java 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/permission/PermissionService.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/permission/PermissionServiceImpl.java 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/permission/RoleService.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/permission/RoleServiceImpl.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/tenant/TenantService.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/tenant/TenantServiceImpl.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/user/AdminUserServiceImpl.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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;
                                                        `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;
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);
    }
}
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> {
}
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> {
}
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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
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;
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;
}
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> {
}
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/service/MpkFileService.java
文件名从 iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/service/MdkFileService.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);
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);
}
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 {
}
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/service/impl/MpkFileServiceImpl.java
文件名从 iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/service/impl/MdkFileServiceImpl.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));
            }
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
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;
    }
}
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>
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();
}
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();
}
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;
}
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;
}
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;
}
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));
    }
}
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));
    }
}
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')")
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));
    }
}
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));
//    }
//
//}
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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
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")
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);
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;
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;
}
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));
    }
}
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')")
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;
}
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;
}
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')")
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()));
    }
}
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);
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;
    }
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;
}
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;
}
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;
}
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;
}
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));
    }
}
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);
    }
}
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);
    }
}
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) {
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();
}
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();
    }
}
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);
}
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("");
        }
    }
}
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);
}
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;
    }
}
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 权限标识
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);
    }
}
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 菜单编号
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);
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);
}
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;
    }
}
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 租户名
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);
    }
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) {