houzhongjian
2024-09-14 818a0170d8f2950d52cc7300a302356bbc523236
应用管理、租户管理、菜单权限管理等修改
已修改28个文件
已添加21个文件
2210 ■■■■■ 文件已修改
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-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) {