feat: 模块注册自动创建Admin菜单 + Admin组织切换生成新JWT

- AppModuleServiceImpl: 模块注册时自动扫描Controller创建菜单树,回写menuIds
- AppOrgServiceImpl: Admin切换组织生成新JWT(含新tenantId);新增listForSwitch方法
- AdminAppOrgController: switchOrg返回token;新增/admin/org/listForSwitch端点
- AppOrgService: 接口增加listForSwitch声明
- AppOrgAuthFilter: proxy改为agent(moduleKey与包名一致)
This commit is contained in:
OpenCode
2026-07-27 15:25:28 +08:00
parent d5992198d2
commit a45704af72
5 changed files with 212 additions and 18 deletions
@@ -34,7 +34,7 @@ public class AppOrgAuthFilter extends OncePerRequestFilter {
"/app/book/", "book",
"/app/ticket/", "ticket",
"/app/product/", "product",
"/app/agent/", "proxy"
"/app/agent/", "agent"
);
@Override
@@ -63,7 +63,13 @@ public class AdminAppOrgController extends BaseController<AppOrgService, AppOrgE
public R switchOrg(@RequestBody JSONObject requestParams) {
Long orgId = requestParams.getLong("orgId");
Long userId = com.cool.core.util.CoolSecurityUtil.getCurrentUserId();
appOrgService.switchOrg(userId, orgId, true);
return R.ok();
return R.ok(appOrgService.switchOrg(userId, orgId, true));
}
@Operation(summary = "获取可切换的组织列表")
@PostMapping("/listForSwitch")
public R listForSwitch() {
Long userId = com.cool.core.util.CoolSecurityUtil.getCurrentUserId();
return R.ok(appOrgService.listForSwitch(userId));
}
}
@@ -13,4 +13,6 @@ public interface AppOrgService extends BaseService<AppOrgEntity> {
void authorizeModules(Long orgId, List<String> moduleKeys);
Dict switchOrg(Long userId, Long orgId, boolean isAdmin);
List<AppOrgEntity> listForSwitch(Long userId);
}
@@ -1,12 +1,134 @@
package com.cool.modules.org.service.impl;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import com.cool.core.base.BaseServiceImpl;
import com.cool.core.base.ModifyEnum;
import com.cool.core.util.CoolSecurityUtil;
import com.cool.modules.base.entity.sys.BaseSysMenuEntity;
import com.cool.modules.base.mapper.sys.BaseSysMenuMapper;
import com.cool.modules.base.service.sys.BaseSysPermsService;
import com.cool.modules.org.mapper.AppModuleMapper;
import com.cool.modules.org.entity.AppModuleEntity;
import com.cool.modules.org.service.AppModuleService;
import com.mybatisflex.annotation.Table;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.core.annotation.AnnotatedElementUtils;
@Slf4j
@Service
@RequiredArgsConstructor
public class AppModuleServiceImpl extends BaseServiceImpl<AppModuleMapper, AppModuleEntity> implements
AppModuleService {
private final BaseSysMenuMapper baseSysMenuMapper;
private final BaseSysPermsService baseSysPermsService;
@Override
public void modifyAfter(JSONObject requestParams, AppModuleEntity entity, ModifyEnum type) {
if (type == ModifyEnum.ADD) {
createMenusForModule(entity);
}
}
private void createMenusForModule(AppModuleEntity module) {
String moduleKey = module.getModuleKey();
if (StrUtil.isBlank(moduleKey)) {
return;
}
String basePackage = "com.cool.modules." + moduleKey + ".controller.admin";
Set<Class<?>> controllerClasses = ClassUtil.scanPackageBySuper(basePackage, Object.class);
if (controllerClasses.isEmpty()) {
log.info("[AppModuleService] 未找到模块 {} 的Admin Controller,跳过菜单创建", moduleKey);
return;
}
BaseSysMenuEntity dirMenu = new BaseSysMenuEntity();
dirMenu.setName(module.getName());
dirMenu.setType(0);
dirMenu.setIcon(module.getIcon());
dirMenu.setRouter("/" + moduleKey);
dirMenu.setOrderNum(0);
dirMenu.setKeepAlive(true);
dirMenu.setIsShow(true);
baseSysMenuMapper.insertSelective(dirMenu);
List<Long> menuIds = new ArrayList<>();
menuIds.add(dirMenu.getId());
for (Class<?> controllerClass : controllerClasses) {
String controllerName = controllerClass.getSimpleName();
if (!controllerName.startsWith("Admin")) {
continue;
}
String entityPath = resolveEntityPath(controllerName, moduleKey);
String menuName = resolveMenuName(controllerClass, entityPath);
BaseSysMenuEntity pageMenu = new BaseSysMenuEntity();
pageMenu.setName(menuName);
pageMenu.setType(1);
pageMenu.setRouter("/" + moduleKey + "/" + entityPath);
pageMenu.setViewPath(moduleKey + "/" + entityPath + "/list.vue");
pageMenu.setParentId(dirMenu.getId());
pageMenu.setOrderNum(0);
pageMenu.setKeepAlive(true);
pageMenu.setIsShow(true);
baseSysMenuMapper.insertSelective(pageMenu);
menuIds.add(pageMenu.getId());
}
module.setMenuIds(new cn.hutool.json.JSONArray(menuIds).toString());
this.update(module);
baseSysPermsService.refreshPermsByMenuId(null);
log.info("[AppModuleService] 模块 {} 自动创建菜单完成,menuIds={}", moduleKey, menuIds);
}
private String resolveEntityPath(String controllerName, String moduleKey) {
String name = controllerName;
if (name.startsWith("Admin")) {
name = name.substring(5);
}
if (name.endsWith("Controller")) {
name = name.substring(0, name.length() - 10);
}
String modulePrefix = StrUtil.upperFirst(moduleKey);
if (name.startsWith(modulePrefix)) {
name = name.substring(modulePrefix.length());
}
return StrUtil.toCamelCase(name).toLowerCase();
}
private String resolveMenuName(Class<?> controllerClass, String entityPath) {
io.swagger.v3.oas.annotations.tags.Tag tag =
AnnotatedElementUtils.findMergedAnnotation(controllerClass, io.swagger.v3.oas.annotations.tags.Tag.class);
if (tag != null && StrUtil.isNotBlank(tag.name())) {
return tag.name();
}
try {
java.lang.reflect.ParameterizedType pt =
(java.lang.reflect.ParameterizedType) controllerClass.getGenericSuperclass();
Class<?> entityClass = (Class<?>) pt.getActualTypeArguments()[1];
Table tableAnno = AnnotatedElementUtils.findMergedAnnotation(entityClass, Table.class);
if (tableAnno != null && StrUtil.isNotBlank(tableAnno.comment())) {
return tableAnno.comment().replace("表", "");
}
} catch (Exception ignored) {}
return entityPath;
}
}
@@ -150,14 +150,22 @@ public class AppOrgServiceImpl extends BaseServiceImpl<AppOrgMapper, AppOrgEntit
@Override
public Dict switchOrg(Long userId, Long orgId, boolean isAdmin) {
if (isAdmin) {
BaseSysUserRoleEntity userRole = baseSysUserRoleMapper.selectOneByQuery(
QueryWrapper.create().eq(BaseSysUserRoleEntity::getUserId, userId)
);
CoolPreconditions.checkEmpty(userRole, "无权切换到该组织");
BaseSysRoleEntity role = baseSysRoleMapper.selectOneById(userRole.getRoleId());
CoolPreconditions.checkEmpty(role, "角色不存在");
CoolPreconditions.check(!orgId.equals(role.getTenantId()), "无权切换到该组织");
if (com.cool.core.util.CoolSecurityUtil.isSuperAdmin()) {
// 超管可切换到任意组织
} else {
List<BaseSysUserRoleEntity> userRoles = baseSysUserRoleMapper.selectListByQuery(
QueryWrapper.create().eq(BaseSysUserRoleEntity::getUserId, userId)
);
boolean hasAccess = false;
for (BaseSysUserRoleEntity ur : userRoles) {
BaseSysRoleEntity role = baseSysRoleMapper.selectOneById(ur.getRoleId());
if (role != null && orgId.equals(role.getTenantId())) {
hasAccess = true;
break;
}
}
CoolPreconditions.check(!hasAccess, "无权切换到该组织");
}
} else {
CoolPreconditions.check(!appOrgUserService.isOrgMember(userId, orgId), "未加入该组织");
}
@@ -166,18 +174,29 @@ public class AppOrgServiceImpl extends BaseServiceImpl<AppOrgMapper, AppOrgEntit
CoolPreconditions.checkEmpty(org, "组织不存在");
CoolPreconditions.check(org.getStatus() == 0, "组织已停用");
if (!isAdmin) {
if (isAdmin) {
com.cool.modules.base.entity.sys.BaseSysUserEntity adminUser = baseSysUserMapper.selectOneById(userId);
CoolPreconditions.checkEmpty(adminUser, "用户不存在");
Dict tokenInfo = Dict.create()
.set("userType", UserTypeEnum.APP.name())
.set("userType", UserTypeEnum.ADMIN.name())
.set("userId", userId)
.set("orgId", orgId);
.set("username", adminUser.getUsername())
.set("tenantId", orgId);
String token = jwtTokenUtil.generateToken(tokenInfo);
String refreshToken = jwtTokenUtil.generateRefreshToken(tokenInfo);
JwtUser jwtUser = new JwtUser(userId,
List.of(new SimpleGrantedAuthority("ROLE_" + UserTypeEnum.APP.name())),
Long[] roleIds = baseSysPermsService.getRoles(userId);
List<org.springframework.security.core.GrantedAuthority> authorities = new java.util.ArrayList<>();
if (roleIds != null) {
for (Long rid : roleIds) {
authorities.add(new SimpleGrantedAuthority("ROLE_" + rid));
}
}
JwtUser jwtUser = new JwtUser(userId, adminUser.getUsername(), adminUser.getPassword(),
authorities,
true);
coolCache.set("app:userDetails:" + userId, jwtUser);
coolCache.set("admin:userDetails:" + adminUser.getUsername(), jwtUser);
return Dict.create()
.set("token", token)
@@ -186,7 +205,52 @@ public class AppOrgServiceImpl extends BaseServiceImpl<AppOrgMapper, AppOrgEntit
.set("refreshExpire", jwtTokenUtil.getRefreshExpire())
.set("orgId", orgId);
}
return Dict.create().set("orgId", orgId);
Dict tokenInfo = Dict.create()
.set("userType", UserTypeEnum.APP.name())
.set("userId", userId)
.set("orgId", orgId);
String token = jwtTokenUtil.generateToken(tokenInfo);
String refreshToken = jwtTokenUtil.generateRefreshToken(tokenInfo);
JwtUser jwtUser = new JwtUser(userId,
List.of(new SimpleGrantedAuthority("ROLE_" + UserTypeEnum.APP.name())),
true);
coolCache.set("app:userDetails:" + userId, jwtUser);
return Dict.create()
.set("token", token)
.set("expire", jwtTokenUtil.getExpire())
.set("refreshToken", refreshToken)
.set("refreshExpire", jwtTokenUtil.getRefreshExpire())
.set("orgId", orgId);
}
@Override
public List<AppOrgEntity> listForSwitch(Long userId) {
if (com.cool.core.util.CoolSecurityUtil.isSuperAdmin()) {
return this.list(QueryWrapper.create()
.eq(AppOrgEntity::getStatus, 1)
.orderBy(AppOrgEntity::getId, true));
}
List<BaseSysUserRoleEntity> userRoles = baseSysUserRoleMapper.selectListByQuery(
QueryWrapper.create().eq(BaseSysUserRoleEntity::getUserId, userId)
);
List<Long> orgIds = new java.util.ArrayList<>();
for (BaseSysUserRoleEntity ur : userRoles) {
BaseSysRoleEntity role = baseSysRoleMapper.selectOneById(ur.getRoleId());
if (role != null && role.getTenantId() != null && role.getStatus() != 0) {
orgIds.add(role.getTenantId());
}
}
if (orgIds.isEmpty()) {
return List.of();
}
return this.list(QueryWrapper.create()
.in(AppOrgEntity::getId, orgIds)
.eq(AppOrgEntity::getStatus, 1)
.orderBy(AppOrgEntity::getId, true));
}
private String generateInviteCode() {