From 0da830a6bc666e9d4f0b866ca9f0a20e4c6a14c6 Mon Sep 17 00:00:00 2001 From: tjq Date: Mon, 4 Sep 2023 00:20:06 +0800 Subject: [PATCH] feat: add namespace to manage app --- .../powerjob/common/utils/CommonUtils.java | 8 ++ .../tech/powerjob/server/auth/RoleScope.java | 22 +++ .../{anno => interceptor}/ApiPermission.java | 12 +- .../interceptor/PowerJobAuthInterceptor.java | 3 +- .../interceptor/dp/DynamicPermission.java | 15 +++ .../dp/EmptyDynamicPermission.java | 18 +++ .../dp/ModifyOrCreateDynamicPermission.java | 19 +++ .../auth/service/PowerJobAuthService.java | 5 +- .../auth/service/PowerJobAuthServiceImpl.java | 79 ++++++++--- .../common/constants/SwitchableStatus.java | 2 +- .../persistence/remote/model/AppInfoDO.java | 5 + .../persistence/remote/model/NamespaceDO.java | 54 ++++++++ .../persistence/remote/model/UserRoleDO.java | 14 +- .../repository/NamespaceRepository.java | 13 ++ .../remote/repository/UserRoleRepository.java | 2 + .../web/controller/NamespaceController.java | 126 ++++++++++++++++++ .../web/controller/UserInfoController.java | 19 +-- .../web/converter/NamespaceConverter.java | 26 ++++ .../server/web/converter/UserConverter.java | 22 +++ .../web/request/ModifyAppInfoRequest.java | 1 + .../web/request/ModifyNamespaceRequest.java | 42 ++++++ .../server/web/response/NamespaceBaseVO.java | 52 ++++++++ .../web/response/NamespaceDetailVO.java | 29 ++++ .../server/web/response/UserBaseVO.java | 20 +++ 24 files changed, 566 insertions(+), 42 deletions(-) create mode 100644 powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/RoleScope.java rename powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/{anno => interceptor}/ApiPermission.java (53%) create mode 100644 powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/interceptor/dp/DynamicPermission.java create mode 100644 powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/interceptor/dp/EmptyDynamicPermission.java create mode 100644 powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/interceptor/dp/ModifyOrCreateDynamicPermission.java create mode 100644 powerjob-server/powerjob-server-persistence/src/main/java/tech/powerjob/server/persistence/remote/model/NamespaceDO.java create mode 100644 powerjob-server/powerjob-server-persistence/src/main/java/tech/powerjob/server/persistence/remote/repository/NamespaceRepository.java create mode 100644 powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/web/controller/NamespaceController.java create mode 100644 powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/web/converter/NamespaceConverter.java create mode 100644 powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/web/converter/UserConverter.java create mode 100644 powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/web/request/ModifyNamespaceRequest.java create mode 100644 powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/web/response/NamespaceBaseVO.java create mode 100644 powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/web/response/NamespaceDetailVO.java create mode 100644 powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/web/response/UserBaseVO.java diff --git a/powerjob-common/src/main/java/tech/powerjob/common/utils/CommonUtils.java b/powerjob-common/src/main/java/tech/powerjob/common/utils/CommonUtils.java index 498d5b73..60507c6d 100644 --- a/powerjob-common/src/main/java/tech/powerjob/common/utils/CommonUtils.java +++ b/powerjob-common/src/main/java/tech/powerjob/common/utils/CommonUtils.java @@ -7,6 +7,7 @@ import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.time.DateFormatUtils; import java.util.Collection; +import java.util.Date; import java.util.UUID; import java.util.function.Supplier; @@ -147,6 +148,13 @@ public class CommonUtils { return OmsConstant.NONE; } + public static String formatTime(Date date) { + if (date == null) { + return OmsConstant.NONE; + } + return formatTime(date.getTime()); + } + /** * 格式化字符串,如果是 null 或空则显示 N/A * @param str 字符串 diff --git a/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/RoleScope.java b/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/RoleScope.java new file mode 100644 index 00000000..28199ca1 --- /dev/null +++ b/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/RoleScope.java @@ -0,0 +1,22 @@ +package tech.powerjob.server.auth; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 权限范围 + * + * @author tjq + * @since 2023/9/3 + */ +@Getter +@AllArgsConstructor +public enum RoleScope { + + NAMESPACE(1), + + APP(10) + ; + + private final int v; +} diff --git a/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/anno/ApiPermission.java b/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/interceptor/ApiPermission.java similarity index 53% rename from powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/anno/ApiPermission.java rename to powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/interceptor/ApiPermission.java index e30f7fb3..099447e5 100644 --- a/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/anno/ApiPermission.java +++ b/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/interceptor/ApiPermission.java @@ -1,6 +1,8 @@ -package tech.powerjob.server.auth.anno; +package tech.powerjob.server.auth.interceptor; import tech.powerjob.server.auth.Permission; +import tech.powerjob.server.auth.interceptor.dp.DynamicPermission; +import tech.powerjob.server.auth.interceptor.dp.EmptyDynamicPermission; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -26,5 +28,11 @@ public @interface ApiPermission { * 需要的权限 * @return 权限 */ - Permission requiredPermission(); + Permission requiredPermission() default Permission.GLOBAL_SU; + + /** + * 固定权限不支持的场景,需要使用动态权限 + * @return 动态权限 + */ + Class dynamicPermissionPlugin() default EmptyDynamicPermission.class; } diff --git a/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/interceptor/PowerJobAuthInterceptor.java b/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/interceptor/PowerJobAuthInterceptor.java index 69e5ac94..96fb8374 100644 --- a/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/interceptor/PowerJobAuthInterceptor.java +++ b/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/interceptor/PowerJobAuthInterceptor.java @@ -9,7 +9,6 @@ import tech.powerjob.common.Loggers; import tech.powerjob.common.exception.PowerJobException; import tech.powerjob.server.auth.LoginUserHolder; import tech.powerjob.server.auth.PowerJobUser; -import tech.powerjob.server.auth.anno.ApiPermission; import tech.powerjob.server.auth.service.PowerJobAuthService; import javax.annotation.Resource; @@ -59,7 +58,7 @@ public class PowerJobAuthInterceptor implements HandlerInterceptor { // 写入上下文 LoginUserHolder.set(powerJobUser); - final boolean hasPermission = powerJobAuthService.hasPermission(request, powerJobUser, apiPermissionAnno); + final boolean hasPermission = powerJobAuthService.hasPermission(request, handler, powerJobUser, apiPermissionAnno); if (hasPermission) { return true; } diff --git a/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/interceptor/dp/DynamicPermission.java b/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/interceptor/dp/DynamicPermission.java new file mode 100644 index 00000000..baa803d7 --- /dev/null +++ b/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/interceptor/dp/DynamicPermission.java @@ -0,0 +1,15 @@ +package tech.powerjob.server.auth.interceptor.dp; + +import tech.powerjob.server.auth.Permission; + +import javax.servlet.http.HttpServletRequest; + +/** + * 动态权限 + * + * @author tjq + * @since 2023/9/3 + */ +public interface DynamicPermission { + Permission calculate(HttpServletRequest request, Object handler); +} diff --git a/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/interceptor/dp/EmptyDynamicPermission.java b/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/interceptor/dp/EmptyDynamicPermission.java new file mode 100644 index 00000000..ccc3f2fd --- /dev/null +++ b/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/interceptor/dp/EmptyDynamicPermission.java @@ -0,0 +1,18 @@ +package tech.powerjob.server.auth.interceptor.dp; + +import tech.powerjob.server.auth.Permission; + +import javax.servlet.http.HttpServletRequest; + +/** + * NotUseDynamicPermission + * + * @author tjq + * @since 2023/9/3 + */ +public class EmptyDynamicPermission implements DynamicPermission { + @Override + public Permission calculate(HttpServletRequest request, Object handler) { + return null; + } +} diff --git a/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/interceptor/dp/ModifyOrCreateDynamicPermission.java b/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/interceptor/dp/ModifyOrCreateDynamicPermission.java new file mode 100644 index 00000000..93da4daf --- /dev/null +++ b/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/interceptor/dp/ModifyOrCreateDynamicPermission.java @@ -0,0 +1,19 @@ +package tech.powerjob.server.auth.interceptor.dp; + +import tech.powerjob.server.auth.Permission; + +import javax.servlet.http.HttpServletRequest; + +/** + * 创建不需要权限,修改需要校验权限 + * + * @author tjq + * @since 2023/9/3 + */ +public class ModifyOrCreateDynamicPermission implements DynamicPermission { + @Override + public Permission calculate(HttpServletRequest request, Object handler) { + // TODO: 动态权限判断,新建不需要权限 + return Permission.WRITE; + } +} diff --git a/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/service/PowerJobAuthService.java b/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/service/PowerJobAuthService.java index 5eab9876..a3e7da9c 100644 --- a/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/service/PowerJobAuthService.java +++ b/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/service/PowerJobAuthService.java @@ -2,7 +2,7 @@ package tech.powerjob.server.auth.service; import tech.powerjob.server.auth.LoginContext; import tech.powerjob.server.auth.PowerJobUser; -import tech.powerjob.server.auth.anno.ApiPermission; +import tech.powerjob.server.auth.interceptor.ApiPermission; import javax.servlet.http.HttpServletRequest; import java.util.List; @@ -43,9 +43,10 @@ public interface PowerJobAuthService { /** * 判断用户是否有访问权限 * @param request 上下文请求 + * @param handler hander * @param user 用户 * @param apiPermission 权限描述 * @return true or false */ - boolean hasPermission(HttpServletRequest request, PowerJobUser user, ApiPermission apiPermission); + boolean hasPermission(HttpServletRequest request, Object handler, PowerJobUser user, ApiPermission apiPermission); } diff --git a/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/service/PowerJobAuthServiceImpl.java b/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/service/PowerJobAuthServiceImpl.java index f716f93a..ed28829c 100644 --- a/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/service/PowerJobAuthServiceImpl.java +++ b/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/service/PowerJobAuthServiceImpl.java @@ -4,21 +4,25 @@ import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.exception.ExceptionUtils; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import tech.powerjob.common.Loggers; -import tech.powerjob.server.auth.LoginContext; -import tech.powerjob.server.auth.Permission; -import tech.powerjob.server.auth.PowerJobUser; -import tech.powerjob.server.auth.Role; -import tech.powerjob.server.auth.anno.ApiPermission; +import tech.powerjob.common.exception.ImpossibleException; +import tech.powerjob.server.auth.*; +import tech.powerjob.server.auth.interceptor.ApiPermission; +import tech.powerjob.server.auth.interceptor.dp.DynamicPermission; +import tech.powerjob.server.auth.interceptor.dp.EmptyDynamicPermission; import tech.powerjob.server.auth.jwt.JwtService; import tech.powerjob.server.auth.login.BizLoginService; import tech.powerjob.server.auth.login.BizUser; +import tech.powerjob.server.persistence.remote.model.AppInfoDO; import tech.powerjob.server.persistence.remote.model.UserInfoDO; import tech.powerjob.server.persistence.remote.model.UserRoleDO; +import tech.powerjob.server.persistence.remote.repository.AppInfoRepository; import tech.powerjob.server.persistence.remote.repository.UserInfoRepository; import tech.powerjob.server.persistence.remote.repository.UserRoleRepository; @@ -32,10 +36,12 @@ import java.util.*; * @author tjq * @since 2023/3/21 */ +@Slf4j @Service public class PowerJobAuthServiceImpl implements PowerJobAuthService { private final JwtService jwtService; + private final AppInfoRepository appInfoRepository; private final UserInfoRepository userInfoRepository; private final UserRoleRepository userRoleRepository; private final Map type2LoginService = Maps.newHashMap(); @@ -45,8 +51,9 @@ public class PowerJobAuthServiceImpl implements PowerJobAuthService { private static final String KEY_USERNAME = "userName"; @Autowired - public PowerJobAuthServiceImpl(List loginServices, JwtService jwtService, UserInfoRepository userInfoRepository, UserRoleRepository userRoleRepository) { + public PowerJobAuthServiceImpl(List loginServices, JwtService jwtService, AppInfoRepository appInfoRepository, UserInfoRepository userInfoRepository, UserRoleRepository userRoleRepository) { this.jwtService = jwtService; + this.appInfoRepository = appInfoRepository; this.userInfoRepository = userInfoRepository; this.userRoleRepository = userRoleRepository; loginServices.forEach(k -> type2LoginService.put(k.type(), k)); @@ -109,33 +116,52 @@ public class PowerJobAuthServiceImpl implements PowerJobAuthService { } @Override - public boolean hasPermission(HttpServletRequest request, PowerJobUser user, ApiPermission apiPermission) { + public boolean hasPermission(HttpServletRequest request, Object handler, PowerJobUser user, ApiPermission apiPermission) { final List userRoleList = Optional.ofNullable(userRoleRepository.findAllByUserId(user.getId())).orElse(Collections.emptyList()); - Multimap appId2Role = ArrayListMultimap.create(); + Multimap appId2Role = ArrayListMultimap.create(); + Multimap namespaceId2Role = ArrayListMultimap.create(); for (UserRoleDO userRole : userRoleList) { - if (userRole.getRole().equalsIgnoreCase(String.valueOf(Role.GOD.getV()))) { + if (Objects.equals(Role.GOD.getV(), userRole.getRole())) { return true; } - // 除了上帝角色,其他任何角色都是 roleId_appId 的形式 - final String[] split = userRole.getRole().split("_"); - final Role role = Role.of(Integer.parseInt(split[0])); - appId2Role.put(split[1], role); + final Role role = Role.of(userRole.getRole()); + if (Objects.equals(userRole.getScope(), RoleScope.NAMESPACE.getV())) { + namespaceId2Role.put(userRole.getTarget(), role); + } + if (Objects.equals(userRole.getScope(), RoleScope.APP.getV())) { + appId2Role.put(userRole.getTarget(), role); + } } // 无超级管理员权限,校验普通权限 - final String appId = request.getHeader("appId"); - if (StringUtils.isEmpty(appId)) { - throw new IllegalArgumentException("can't find appId in header, please login again!"); + final String appIdStr = request.getHeader("appId"); + if (StringUtils.isEmpty(appIdStr)) { + throw new IllegalArgumentException("can't find appId in header, please refresh and try again!"); } - final Permission requiredPermission = apiPermission.requiredPermission(); + Long appId = Long.valueOf(appIdStr); - final Collection roleCollection = appId2Role.get(appId); - for (Role role : roleCollection) { + final Permission requiredPermission = parsePermission(request, handler, apiPermission); + + final Collection appRoles = appId2Role.get(appId); + for (Role role : appRoles) { + if (role.getPermissions().contains(requiredPermission)) { + return true; + } + } + + // 校验 namespace 穿透权限 + Optional appInfoOpt = appInfoRepository.findById(appId); + if (!appInfoOpt.isPresent()) { + throw new IllegalArgumentException("can't find appInfo by appId in permission check: " + appId); + } + Long namespaceId = Optional.ofNullable(appInfoOpt.get().getNamespaceId()).orElse(-1L); + Collection namespaceRoles = namespaceId2Role.get(namespaceId); + for (Role role : namespaceRoles) { if (role.getPermissions().contains(requiredPermission)) { return true; } @@ -184,4 +210,19 @@ public class PowerJobAuthServiceImpl implements PowerJobAuthService { powerJobUser.setJwtToken(jwtService.build(jwtMap, null)); } + + private static Permission parsePermission(HttpServletRequest request, Object handler, ApiPermission apiPermission) { + Class dynamicPermissionPlugin = apiPermission.dynamicPermissionPlugin(); + if (EmptyDynamicPermission.class.equals(dynamicPermissionPlugin)) { + return apiPermission.requiredPermission(); + } + try { + DynamicPermission dynamicPermission = dynamicPermissionPlugin.getDeclaredConstructor().newInstance(); + return dynamicPermission.calculate(request, handler); + } catch (Throwable t) { + log.error("[PowerJobAuthService] process dynamicPermissionPlugin failed!", t); + ExceptionUtils.rethrow(t); + } + throw new ImpossibleException(); + } } diff --git a/powerjob-server/powerjob-server-common/src/main/java/tech/powerjob/server/common/constants/SwitchableStatus.java b/powerjob-server/powerjob-server-common/src/main/java/tech/powerjob/server/common/constants/SwitchableStatus.java index e143553c..77bc6311 100644 --- a/powerjob-server/powerjob-server-common/src/main/java/tech/powerjob/server/common/constants/SwitchableStatus.java +++ b/powerjob-server/powerjob-server-common/src/main/java/tech/powerjob/server/common/constants/SwitchableStatus.java @@ -13,7 +13,7 @@ import lombok.Getter; @AllArgsConstructor public enum SwitchableStatus { /** - * + * 启用 */ ENABLE(1), DISABLE(2), diff --git a/powerjob-server/powerjob-server-persistence/src/main/java/tech/powerjob/server/persistence/remote/model/AppInfoDO.java b/powerjob-server/powerjob-server-persistence/src/main/java/tech/powerjob/server/persistence/remote/model/AppInfoDO.java index 7395af38..33165261 100644 --- a/powerjob-server/powerjob-server-persistence/src/main/java/tech/powerjob/server/persistence/remote/model/AppInfoDO.java +++ b/powerjob-server/powerjob-server-persistence/src/main/java/tech/powerjob/server/persistence/remote/model/AppInfoDO.java @@ -22,6 +22,11 @@ public class AppInfoDO { @GenericGenerator(name = "native", strategy = "native") private Long id; + /** + * 命名空间ID,外键关联 + */ + private Long namespaceId; + private String appName; /** * 应用分组密码 diff --git a/powerjob-server/powerjob-server-persistence/src/main/java/tech/powerjob/server/persistence/remote/model/NamespaceDO.java b/powerjob-server/powerjob-server-persistence/src/main/java/tech/powerjob/server/persistence/remote/model/NamespaceDO.java new file mode 100644 index 00000000..c1089926 --- /dev/null +++ b/powerjob-server/powerjob-server-persistence/src/main/java/tech/powerjob/server/persistence/remote/model/NamespaceDO.java @@ -0,0 +1,54 @@ +package tech.powerjob.server.persistence.remote.model; + +import lombok.Data; +import org.hibernate.annotations.GenericGenerator; + +import javax.persistence.*; +import java.util.Date; + +/** + * 命名空间,用于组织管理 App + * + * @author tjq + * @since 2023/9/3 + */ +@Data +@Entity +@Table(uniqueConstraints = {@UniqueConstraint(name = "uidx01_namespace", columnNames = {"code"})}) +public class NamespaceDO { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO, generator = "native") + @GenericGenerator(name = "native", strategy = "native") + private Long id; + + /** + * 空间唯一标识 + */ + private String code; + + /** + * 空间名称,比如中文描述(XX部门XX空间) + */ + private String name; + + /** + * 鉴权 token,后续 OpenAPI 调用需要 + */ + private String token; + + private Integer status; + + /** + * 扩展字段 + */ + private String extra; + + private Date gmtCreate; + + private Date gmtModified; + + private String creator; + + private String modifier; +} diff --git a/powerjob-server/powerjob-server-persistence/src/main/java/tech/powerjob/server/persistence/remote/model/UserRoleDO.java b/powerjob-server/powerjob-server-persistence/src/main/java/tech/powerjob/server/persistence/remote/model/UserRoleDO.java index fd416296..22d7b629 100644 --- a/powerjob-server/powerjob-server-persistence/src/main/java/tech/powerjob/server/persistence/remote/model/UserRoleDO.java +++ b/powerjob-server/powerjob-server-persistence/src/main/java/tech/powerjob/server/persistence/remote/model/UserRoleDO.java @@ -28,10 +28,20 @@ public class UserRoleDO { * 授予角色的用户ID */ private Long userId; + /** - * 角色,${role}_${appId},比如 Observer_277 + * 权限范围,namespace 还是 app */ - private String role; + private Integer scope; + /** + * 和 scope 一起组成授权目标,比如某个 app 或 某个 namespace + */ + private Long target; + + /** + * 角色,比如 Observer + */ + private Integer role; /** * 扩展字段 */ diff --git a/powerjob-server/powerjob-server-persistence/src/main/java/tech/powerjob/server/persistence/remote/repository/NamespaceRepository.java b/powerjob-server/powerjob-server-persistence/src/main/java/tech/powerjob/server/persistence/remote/repository/NamespaceRepository.java new file mode 100644 index 00000000..bf4956fd --- /dev/null +++ b/powerjob-server/powerjob-server-persistence/src/main/java/tech/powerjob/server/persistence/remote/repository/NamespaceRepository.java @@ -0,0 +1,13 @@ +package tech.powerjob.server.persistence.remote.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import tech.powerjob.server.persistence.remote.model.NamespaceDO; + +/** + * 命名空间 + * + * @author tjq + * @since 2023/9/3 + */ +public interface NamespaceRepository extends JpaRepository { +} diff --git a/powerjob-server/powerjob-server-persistence/src/main/java/tech/powerjob/server/persistence/remote/repository/UserRoleRepository.java b/powerjob-server/powerjob-server-persistence/src/main/java/tech/powerjob/server/persistence/remote/repository/UserRoleRepository.java index d3fca9ea..18949f08 100644 --- a/powerjob-server/powerjob-server-persistence/src/main/java/tech/powerjob/server/persistence/remote/repository/UserRoleRepository.java +++ b/powerjob-server/powerjob-server-persistence/src/main/java/tech/powerjob/server/persistence/remote/repository/UserRoleRepository.java @@ -14,4 +14,6 @@ import java.util.List; public interface UserRoleRepository extends JpaRepository { List findAllByUserId(Long userId); + + List findAllByScopeAndTarget(Integer scope, Long target); } diff --git a/powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/web/controller/NamespaceController.java b/powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/web/controller/NamespaceController.java new file mode 100644 index 00000000..9ba0e261 --- /dev/null +++ b/powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/web/controller/NamespaceController.java @@ -0,0 +1,126 @@ +package tech.powerjob.server.web.controller; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.BeanUtils; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import tech.powerjob.common.response.ResultDTO; +import tech.powerjob.server.auth.Permission; +import tech.powerjob.server.auth.Role; +import tech.powerjob.server.auth.RoleScope; +import tech.powerjob.server.auth.interceptor.ApiPermission; +import tech.powerjob.server.auth.interceptor.dp.ModifyOrCreateDynamicPermission; +import tech.powerjob.server.persistence.remote.model.NamespaceDO; +import tech.powerjob.server.persistence.remote.model.UserInfoDO; +import tech.powerjob.server.persistence.remote.model.UserRoleDO; +import tech.powerjob.server.persistence.remote.repository.NamespaceRepository; +import tech.powerjob.server.persistence.remote.repository.UserInfoRepository; +import tech.powerjob.server.persistence.remote.repository.UserRoleRepository; +import tech.powerjob.server.web.converter.NamespaceConverter; +import tech.powerjob.server.web.converter.UserConverter; +import tech.powerjob.server.web.request.ModifyNamespaceRequest; +import tech.powerjob.server.web.response.NamespaceBaseVO; +import tech.powerjob.server.web.response.NamespaceDetailVO; +import tech.powerjob.server.web.response.UserBaseVO; + +import javax.annotation.Resource; +import java.util.*; +import java.util.stream.Collectors; + +/** + * 命名空间 Controller + * + * @author tjq + * @since 2023/9/3 + */ +@Slf4j +@RestController +@RequestMapping("/namespace") +public class NamespaceController { + + @Resource + private NamespaceRepository namespaceRepository; + @Resource + private UserInfoRepository userInfoRepository; + @Resource + private UserRoleRepository userRoleRepository; + + @PostMapping("/save") + @ApiPermission(name = "Namespace-Save", dynamicPermissionPlugin = ModifyOrCreateDynamicPermission.class) + public ResultDTO save(ModifyNamespaceRequest req) { + + req.valid(); + + Long id = req.getId(); + NamespaceDO namespaceDO; + if (id == null) { + namespaceDO = new NamespaceDO(); + namespaceDO.setGmtCreate(new Date()); + + // code 单独拷贝 + namespaceDO.setCode(req.getCode()); + // 创建时生成 token + namespaceDO.setToken(UUID.randomUUID().toString()); + + } else { + namespaceDO = fetchById(id); + } + + // 拷贝通用变更属性(code 不允许更改) + namespaceDO.setName(req.getName()); + namespaceDO.setExtra(req.getExtra()); + namespaceDO.setStatus(req.getStatus()); + + namespaceDO.setGmtModified(new Date()); + namespaceRepository.save(namespaceDO); + return ResultDTO.success(null); + } + + @GetMapping("/listAll") + public ResultDTO> listAllNamespace() { + List allDos = namespaceRepository.findAll(); + return ResultDTO.success(allDos.stream().map(NamespaceConverter::do2BaseVo).collect(Collectors.toList())); + } + + @GetMapping("/detail") + @ApiPermission(name = "Namespace-DetailInfo", requiredPermission = Permission.READ) + public ResultDTO queryNamespaceDetail(Long id) { + + NamespaceDO namespaceDO = fetchById(id); + NamespaceDetailVO namespaceDetailVO = new NamespaceDetailVO(); + + // 拷贝基础字段 + NamespaceBaseVO namespaceBaseVO = NamespaceConverter.do2BaseVo(namespaceDO); + BeanUtils.copyProperties(namespaceBaseVO, namespaceDetailVO); + + // 处理 token + namespaceDetailVO.setToken(namespaceDO.getToken()); + + // 处理权限视图 + Map> privilegedUsers = Maps.newHashMap(); + namespaceDetailVO.setPrivilegedUsers(privilegedUsers); + List permissionUserList = userRoleRepository.findAllByScopeAndTarget(RoleScope.NAMESPACE.getV(), namespaceDO.getId()); + permissionUserList.forEach(r -> { + Role role = Role.of(r.getRole()); + List userBaseVOList = privilegedUsers.computeIfAbsent(role.name(), ignore -> Lists.newArrayList()); + + Optional userInfoDoOpt = userInfoRepository.findById(r.getUserId()); + userInfoDoOpt.ifPresent(userInfoDO -> userBaseVOList.add(UserConverter.do2BaseVo(userInfoDO))); + }); + + return ResultDTO.success(namespaceDetailVO); + } + + private NamespaceDO fetchById(Long id) { + Optional namespaceDoOpt = namespaceRepository.findById(id); + if (!namespaceDoOpt.isPresent()) { + throw new IllegalArgumentException("can't find namespace by id: " + id); + } + return namespaceDoOpt.get(); + } + +} diff --git a/powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/web/controller/UserInfoController.java b/powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/web/controller/UserInfoController.java index 2db5da8b..af02f66b 100644 --- a/powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/web/controller/UserInfoController.java +++ b/powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/web/controller/UserInfoController.java @@ -1,9 +1,6 @@ package tech.powerjob.server.web.controller; import com.google.common.collect.Lists; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.BeanUtils; import org.springframework.util.CollectionUtils; @@ -12,7 +9,9 @@ import tech.powerjob.common.response.ResultDTO; import tech.powerjob.server.core.service.UserService; import tech.powerjob.server.persistence.remote.model.UserInfoDO; import tech.powerjob.server.persistence.remote.repository.UserInfoRepository; +import tech.powerjob.server.web.converter.UserConverter; import tech.powerjob.server.web.request.ModifyUserInfoRequest; +import tech.powerjob.server.web.response.UserBaseVO; import javax.annotation.Resource; import java.util.List; @@ -41,7 +40,7 @@ public class UserInfoController { } @GetMapping("list") - public ResultDTO> list(@RequestParam(required = false) String name) { + public ResultDTO> list(@RequestParam(required = false) String name) { List result; if (StringUtils.isEmpty(name)) { @@ -52,18 +51,10 @@ public class UserInfoController { return ResultDTO.success(convert(result)); } - private static List convert(List data) { + private static List convert(List data) { if (CollectionUtils.isEmpty(data)) { return Lists.newLinkedList(); } - return data.stream().map(x -> new UserItemVO(x.getId(), x.getUsername())).collect(Collectors.toList()); - } - - @Getter - @NoArgsConstructor - @AllArgsConstructor - public static final class UserItemVO { - private Long id; - private String username; + return data.stream().map(UserConverter::do2BaseVo).collect(Collectors.toList()); } } diff --git a/powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/web/converter/NamespaceConverter.java b/powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/web/converter/NamespaceConverter.java new file mode 100644 index 00000000..01f968f8 --- /dev/null +++ b/powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/web/converter/NamespaceConverter.java @@ -0,0 +1,26 @@ +package tech.powerjob.server.web.converter; + +import org.springframework.beans.BeanUtils; +import tech.powerjob.common.utils.CommonUtils; +import tech.powerjob.server.common.constants.SwitchableStatus; +import tech.powerjob.server.persistence.remote.model.NamespaceDO; +import tech.powerjob.server.web.response.NamespaceBaseVO; + +/** + * NamespaceConverter + * + * @author tjq + * @since 2023/9/4 + */ +public class NamespaceConverter { + + public static NamespaceBaseVO do2BaseVo(NamespaceDO d) { + NamespaceBaseVO v = new NamespaceBaseVO(); + BeanUtils.copyProperties(d, v); + v.setGmtCreateStr(CommonUtils.formatTime(d.getGmtCreate())); + v.setGmtModifiedStr(CommonUtils.formatTime(d.getGmtModified())); + v.setStatusStr(SwitchableStatus.of(d.getStatus()).name()); + return v; + } + +} diff --git a/powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/web/converter/UserConverter.java b/powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/web/converter/UserConverter.java new file mode 100644 index 00000000..74deec7c --- /dev/null +++ b/powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/web/converter/UserConverter.java @@ -0,0 +1,22 @@ +package tech.powerjob.server.web.converter; + +import tech.powerjob.server.persistence.remote.model.UserInfoDO; +import tech.powerjob.server.web.response.UserBaseVO; + +/** + * UserConverter + * + * @author tjq + * @since 2023/9/4 + */ +public class UserConverter { + + public static UserBaseVO do2BaseVo(UserInfoDO x) { + UserBaseVO userBaseVO = new UserBaseVO(); + userBaseVO.setId(x.getId()); + userBaseVO.setUsername(x.getUsername()); + userBaseVO.setNick(x.getNick()); + return userBaseVO; + } + +} diff --git a/powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/web/request/ModifyAppInfoRequest.java b/powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/web/request/ModifyAppInfoRequest.java index 2b5aca6f..325b71ec 100644 --- a/powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/web/request/ModifyAppInfoRequest.java +++ b/powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/web/request/ModifyAppInfoRequest.java @@ -15,6 +15,7 @@ import org.apache.commons.lang3.StringUtils; public class ModifyAppInfoRequest { private Long id; + private Long namespaceId; private String oldPassword; private String appName; private String password; diff --git a/powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/web/request/ModifyNamespaceRequest.java b/powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/web/request/ModifyNamespaceRequest.java new file mode 100644 index 00000000..3291446f --- /dev/null +++ b/powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/web/request/ModifyNamespaceRequest.java @@ -0,0 +1,42 @@ +package tech.powerjob.server.web.request; + +import lombok.Data; +import org.apache.commons.lang3.StringUtils; +import tech.powerjob.common.exception.PowerJobException; +import tech.powerjob.common.utils.CommonUtils; + +/** + * ModifyNamespaceRequest + * + * @author tjq + * @since 2023/9/3 + */ +@Data +public class ModifyNamespaceRequest { + + private Long id; + + /** + * 空间唯一标识 + */ + private String code; + + /** + * 空间名称,比如中文描述(XX部门XX空间) + */ + private String name; + + private Integer status; + + /** + * 扩展字段 + */ + private String extra; + + public void valid() { + CommonUtils.requireNonNull(code, "namespace code can't be empty"); + if (StringUtils.containsWhitespace(code)) { + throw new PowerJobException("namespace code can't contains white space!"); + } + } +} diff --git a/powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/web/response/NamespaceBaseVO.java b/powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/web/response/NamespaceBaseVO.java new file mode 100644 index 00000000..b0e85d75 --- /dev/null +++ b/powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/web/response/NamespaceBaseVO.java @@ -0,0 +1,52 @@ +package tech.powerjob.server.web.response; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +import java.io.Serializable; +import java.util.Date; + +/** + * 基础版本的命名空间 + * + * @author tjq + * @since 2023/9/3 + */ +@Getter +@Setter +@ToString +public class NamespaceBaseVO implements Serializable { + + private Long id; + + /** + * 空间唯一标识 + */ + private String code; + + /** + * 空间名称,比如中文描述(XX部门XX空间) + */ + private String name; + + private Integer status; + private String statusStr; + + /** + * 扩展字段 + */ + private String extra; + + private Date gmtCreate; + + private String gmtCreateStr; + + private Date gmtModified; + + private String gmtModifiedStr; + + private String creator; + + private String modifier; +} diff --git a/powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/web/response/NamespaceDetailVO.java b/powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/web/response/NamespaceDetailVO.java new file mode 100644 index 00000000..2a2eb96f --- /dev/null +++ b/powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/web/response/NamespaceDetailVO.java @@ -0,0 +1,29 @@ +package tech.powerjob.server.web.response; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +import java.util.List; +import java.util.Map; + +/** + * 详细命名空间信息,需要权限访问 + * + * @author tjq + * @since 2023/9/3 + */ +@Getter +@Setter +@ToString(callSuper = true) +public class NamespaceDetailVO extends NamespaceBaseVO { + + /** + * 访问 token + */ + private String token; + /** + * 有权限的用户 + */ + private Map> privilegedUsers; +} diff --git a/powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/web/response/UserBaseVO.java b/powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/web/response/UserBaseVO.java new file mode 100644 index 00000000..aa514a8d --- /dev/null +++ b/powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/web/response/UserBaseVO.java @@ -0,0 +1,20 @@ +package tech.powerjob.server.web.response; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * 用户基础信息 + * + * @author tjq + * @since 2023/9/3 + */ +@Getter +@Setter +@NoArgsConstructor +public class UserBaseVO { + private Long id; + private String username; + private String nick; +}