diff --git a/powerjob-common/src/main/java/tech/powerjob/common/PowerJobDKey.java b/powerjob-common/src/main/java/tech/powerjob/common/PowerJobDKey.java index 97f859f6..92d595f4 100644 --- a/powerjob-common/src/main/java/tech/powerjob/common/PowerJobDKey.java +++ b/powerjob-common/src/main/java/tech/powerjob/common/PowerJobDKey.java @@ -64,6 +64,10 @@ public class PowerJobDKey { public static final String WORKER_RUNTIME_SWAP_TASK_SCHEDULE_INTERVAL_MS = "powerjob.worker.swap.scan-interval"; - public static final String SERVER_TEST_ACCOUNT_USERNAME = "powerjob.server.test-accounts"; + public static final String SERVER_TEST_ACCOUNT_USERNAME = "powerjob.server.test.user.accounts"; + public static final String SERVER_TEST_ACCOUNT_PASSWORD = "powerjob.server.test.user.password"; + public static final String SERVER_TEST_APP_USERNAME = "powerjob.server.test.app.name"; + public static final String SERVER_TEST_APP_PASSWORD = "powerjob.server.test.app.password"; + public static final String SERVER_TEST_MODE = "powerjob.server.test.mode"; } diff --git a/powerjob-common/src/main/java/tech/powerjob/common/enums/ErrorCodes.java b/powerjob-common/src/main/java/tech/powerjob/common/enums/ErrorCodes.java index dfd42ce5..2542f5fb 100644 --- a/powerjob-common/src/main/java/tech/powerjob/common/enums/ErrorCodes.java +++ b/powerjob-common/src/main/java/tech/powerjob/common/enums/ErrorCodes.java @@ -53,6 +53,10 @@ public enum ErrorCodes { * 非法参数 */ ILLEGAL_ARGS_ERROR("-501", "ILLEGAL_ARGS_ERROR"), + /** + * 不允许操作 + */ + OPERATION_NOT_PERMITTED("-502", "OPERATION_NOT_PERMITTED"), /** * OPENAPI 错误码号段 -10XX diff --git a/powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/auth/plugin/ModifyOrCreateDynamicPermission.java b/powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/auth/plugin/ModifyOrCreateDynamicPermission.java index 44962eaa..9c662d26 100644 --- a/powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/auth/plugin/ModifyOrCreateDynamicPermission.java +++ b/powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/auth/plugin/ModifyOrCreateDynamicPermission.java @@ -35,7 +35,7 @@ public class ModifyOrCreateDynamicPermission implements DynamicPermissionPlugin return Permission.NONE; } - return Permission.WRITE; + return Permission.SU; } catch (Exception e) { log.error("[ModifyOrCreateDynamicPermission] check permission failed, please fix the bug!!!", e); } diff --git a/powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/initializer/NewSystemInitializer.java b/powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/initializer/NewSystemInitializer.java index 130b18fd..3f0549e2 100644 --- a/powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/initializer/NewSystemInitializer.java +++ b/powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/initializer/NewSystemInitializer.java @@ -40,16 +40,22 @@ public class NewSystemInitializer implements CommandLineRunner { @Override public void run(String... args) throws Exception { - initSystemAdmin(); - initDefaultNamespace(); + SystemInitializerContext context = new SystemInitializerContext(); + initSystemAdmin(context); + initDefaultNamespace(context); + initTestEnvironment(context); } - private void initSystemAdmin() { - clusterInit(SystemInitializeService.GOAL_INIT_ADMIN, Void -> systemInitializeService.initAdmin()); + private void initSystemAdmin(SystemInitializerContext context) { + clusterInit(SystemInitializeService.GOAL_INIT_ADMIN, Void -> systemInitializeService.initAdmin(context)); } - private void initDefaultNamespace() { - clusterInit(SystemInitializeService.GOAL_INIT_NAMESPACE, Void -> systemInitializeService.initNamespace()); + private void initDefaultNamespace(SystemInitializerContext context) { + clusterInit(SystemInitializeService.GOAL_INIT_NAMESPACE, Void -> systemInitializeService.initNamespace(context)); + } + + private void initTestEnvironment(SystemInitializerContext context) { + clusterInit(SystemInitializeService.GOAL_INIT_TEST_ENV, Void -> systemInitializeService.initTestEnvironment(context)); } private void clusterInit(String name, Consumer initFunc) { diff --git a/powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/initializer/SystemInitializeService.java b/powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/initializer/SystemInitializeService.java index e5ec2ceb..4cf34e01 100644 --- a/powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/initializer/SystemInitializeService.java +++ b/powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/initializer/SystemInitializeService.java @@ -10,15 +10,21 @@ public interface SystemInitializeService { String GOAL_INIT_ADMIN = "goal_init_admin"; String GOAL_INIT_NAMESPACE = "goal_init_namespace"; + String GOAL_INIT_TEST_ENV = "goal_init_test_env"; /** * 初始化超级管理员 */ - void initAdmin(); + void initAdmin(SystemInitializerContext context); /** * 初始化 namespace */ - void initNamespace(); + void initNamespace(SystemInitializerContext context); + + /** + * 初始化测试环境 + */ + void initTestEnvironment(SystemInitializerContext context); } diff --git a/powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/initializer/SystemInitializeServiceImpl.java b/powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/initializer/SystemInitializeServiceImpl.java index 00a717c7..435c17e6 100644 --- a/powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/initializer/SystemInitializeServiceImpl.java +++ b/powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/initializer/SystemInitializeServiceImpl.java @@ -1,11 +1,13 @@ package tech.powerjob.server.initializer; +import com.google.common.collect.Lists; import com.google.common.collect.Maps; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; +import tech.powerjob.common.PowerJobDKey; import tech.powerjob.common.serialize.JsonUtils; import tech.powerjob.server.auth.PowerJobUser; import tech.powerjob.server.auth.Role; @@ -14,16 +16,23 @@ import tech.powerjob.server.auth.common.AuthConstants; import tech.powerjob.server.auth.service.login.LoginRequest; import tech.powerjob.server.auth.service.login.PowerJobLoginService; import tech.powerjob.server.auth.service.permission.PowerJobPermissionService; +import tech.powerjob.server.common.SJ; +import tech.powerjob.server.persistence.remote.model.AppInfoDO; import tech.powerjob.server.persistence.remote.model.NamespaceDO; import tech.powerjob.server.persistence.remote.model.PwjbUserInfoDO; +import tech.powerjob.server.web.request.ComponentUserRoleInfo; +import tech.powerjob.server.web.request.ModifyAppInfoRequest; import tech.powerjob.server.web.request.ModifyNamespaceRequest; import tech.powerjob.server.web.request.ModifyUserInfoRequest; +import tech.powerjob.server.web.service.AppWebService; import tech.powerjob.server.web.service.NamespaceWebService; import tech.powerjob.server.web.service.PwjbUserWebService; import javax.annotation.Resource; import javax.transaction.Transactional; +import java.util.List; import java.util.Map; +import java.util.stream.Collectors; /** * 初始化 PowerJob 首次部署相关的内容 @@ -39,6 +48,8 @@ public class SystemInitializeServiceImpl implements SystemInitializeService { @Value("${oms.auth.initiliaze.admin.password:#{null}}") private String defaultAdminPassword; @Resource + private AppWebService appWebService; + @Resource private PwjbUserWebService pwjbUserWebService; @Resource private NamespaceWebService namespaceWebService; @@ -54,36 +65,18 @@ public class SystemInitializeServiceImpl implements SystemInitializeService { @Override @Transactional(rollbackOn = Exception.class) - public void initAdmin() { + public void initAdmin(SystemInitializerContext context) { String username = SYSTEM_ADMIN_NAME; String password = StringUtils.isEmpty(defaultAdminPassword) ? RandomStringUtils.randomAlphabetic(8) : defaultAdminPassword; - // STEP1: 创建 PWJB 用户 - ModifyUserInfoRequest createUser = new ModifyUserInfoRequest(); - createUser.setUsername(username); - createUser.setNick(username); - createUser.setPassword(password); + // 创建用户 + PowerJobUser powerJobUser = createUser(username, password, true); + context.setAdminUserId(powerJobUser.getId()); - log.info("[SystemInitializeService] [S1] create default PWJB user by request: {}", createUser); - PwjbUserInfoDO savedPwjbUser = pwjbUserWebService.save(createUser); - log.info("[SystemInitializeService] [S1] create default PWJB user successfully: {}", savedPwjbUser); - - Map params = Maps.newHashMap(); - params.put(AuthConstants.PARAM_KEY_USERNAME, username); - params.put(AuthConstants.PARAM_KEY_PASSWORD, password); - - // STEP2: 创建 USER 对象 - LoginRequest loginRequest = new LoginRequest() - .setLoginType(AuthConstants.ACCOUNT_TYPE_POWER_JOB) - .setOriginParams(JsonUtils.toJSONString(params)); - log.info("[SystemInitializeService] [S2] createPowerJobUser user by request: {}", loginRequest); - PowerJobUser powerJobUser = powerJobLoginService.doLogin(loginRequest); - log.info("[SystemInitializeService] [S2] createPowerJobUser successfully: {}", powerJobUser); - - // STEP3: 授予全局管理员权限 + // 授予全局管理员权限 powerJobPermissionService.grantRole(RoleScope.GLOBAL, AuthConstants.GLOBAL_ADMIN_TARGET_ID, powerJobUser.getId(), Role.ADMIN, null); - log.info("[SystemInitializeService] [S3] GRANT ADMIN successfully!"); + log.info("[SystemInitializeService] GRANT ADMIN to user[{}] successfully!", powerJobUser); // 循环10遍,强提醒用户,第一次使用必须更改 admin 密码 for (int i = 0; i < 10; i++) { @@ -93,13 +86,99 @@ public class SystemInitializeServiceImpl implements SystemInitializeService { @Override @Transactional(rollbackOn = Exception.class) - public void initNamespace() { + public void initNamespace(SystemInitializerContext context) { ModifyNamespaceRequest saveNamespaceReq = new ModifyNamespaceRequest(); saveNamespaceReq.setName(SYSTEM_DEFAULT_NAMESPACE); saveNamespaceReq.setCode(SYSTEM_DEFAULT_NAMESPACE); log.info("[SystemInitializeService] create default namespace by request: {}", saveNamespaceReq); NamespaceDO savedNamespaceDO = namespaceWebService.save(saveNamespaceReq); + context.setDefaultNamespaceId(savedNamespaceDO.getId()); log.info("[SystemInitializeService] create default namespace successfully: {}", savedNamespaceDO); } + + @Override + @Transactional(rollbackOn = Exception.class) + public void initTestEnvironment(SystemInitializerContext context) { + String testMode = System.getProperty(PowerJobDKey.SERVER_TEST_MODE); + if (!Boolean.TRUE.toString().equalsIgnoreCase(testMode)) { + return; + } + log.warn("[SystemInitializeService] [TestEnv] Detect test mode and start initializing the test environment"); + + // 初始化测试账号 + String testAccountsStr = System.getProperty(PowerJobDKey.SERVER_TEST_ACCOUNT_USERNAME, "powerjob"); + String testAccountsPwd = System.getProperty(PowerJobDKey.SERVER_TEST_ACCOUNT_PASSWORD, "powerjob"); + List testAccounts = Lists.newArrayList(SJ.COMMA_SPLITTER.split(testAccountsStr)); + + List testUsers = testAccounts.stream().map(un -> createUser(un, testAccountsPwd, false)).collect(Collectors.toList()); + List testUserIds = testUsers.stream().map(PowerJobUser::getId).collect(Collectors.toList()); + + log.info("[SystemInitializeService] [TestEnv] test user: {}", testUsers); + + String testAppName = System.getProperty(PowerJobDKey.SERVER_TEST_APP_USERNAME, "powerjob-worker-samples"); + String testAppPassword = System.getProperty(PowerJobDKey.SERVER_TEST_APP_PASSWORD, "powerjob123"); + + AppInfoDO testApp = createApp(testAppName, testAppPassword, context.getDefaultNamespaceId(), testUserIds); + log.info("[SystemInitializeService] [TestEnv] test app: {}", testApp); + } + + private PowerJobUser createUser(String username, String password, boolean allowedChangePwd) { + // STEP1: 创建 PWJB 用户 + ModifyUserInfoRequest createUser = new ModifyUserInfoRequest(); + createUser.setUsername(username); + createUser.setNick(username); + createUser.setPassword(password); + + if (!allowedChangePwd) { + Map extra = Maps.newHashMap(); + extra.put("allowedChangePwd", false); + createUser.setExtra(JsonUtils.toJSONString(extra)); + } + + log.info("[SystemInitializeService] [username:{}] create PWJB user by request: {}", username, createUser); + PwjbUserInfoDO savedPwjbUser = pwjbUserWebService.save(createUser); + log.info("[SystemInitializeService] [username:{}] create PWJB user successfully: {}", username, savedPwjbUser); + + Map params = Maps.newHashMap(); + params.put(AuthConstants.PARAM_KEY_USERNAME, username); + params.put(AuthConstants.PARAM_KEY_PASSWORD, password); + + // STEP2: 创建 USER 对象 + LoginRequest loginRequest = new LoginRequest() + .setLoginType(AuthConstants.ACCOUNT_TYPE_POWER_JOB) + .setOriginParams(JsonUtils.toJSONString(params)); + log.info("[SystemInitializeService] [username:{}] create PowerJobUser by request: {}", username, loginRequest); + PowerJobUser powerJobUser = powerJobLoginService.doLogin(loginRequest); + log.info("[SystemInitializeService] [username:{}] create PowerJobUser successfully: {}", username, powerJobUser); + return powerJobUser; + } + + private AppInfoDO createApp(String appName, String password, Long namespaceId, List developers) { + ModifyAppInfoRequest modifyAppInfoRequest = new ModifyAppInfoRequest(); + + modifyAppInfoRequest.setAppName(appName); + modifyAppInfoRequest.setTitle(appName); + + modifyAppInfoRequest.setPassword(password); + modifyAppInfoRequest.setNamespaceId(namespaceId); + + modifyAppInfoRequest.setTags("test_app"); + + // 禁用靠密码成为管理员 + Map extra = Maps.newHashMap(); + extra.put("allowedBecomeAdminByPassword", false); + modifyAppInfoRequest.setExtra(JsonUtils.toJSONString(extra)); + + ComponentUserRoleInfo componentUserRoleInfo = new ComponentUserRoleInfo(); + modifyAppInfoRequest.setComponentUserRoleInfo(componentUserRoleInfo); + + componentUserRoleInfo.setDeveloper(developers); + + log.info("[SystemInitializeService] [app:{}] create App by request: {}", appName, modifyAppInfoRequest); + AppInfoDO appInfoDO = appWebService.save(modifyAppInfoRequest); + log.info("[SystemInitializeService] [app:{}] create App successfully: {}", appName, appInfoDO); + + return appInfoDO; + } } diff --git a/powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/initializer/SystemInitializerContext.java b/powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/initializer/SystemInitializerContext.java new file mode 100644 index 00000000..a13e6d08 --- /dev/null +++ b/powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/initializer/SystemInitializerContext.java @@ -0,0 +1,21 @@ +package tech.powerjob.server.initializer; + +import lombok.Data; + +import java.util.Map; + +/** + * 系统初始化上下文 + * + * @author tjq + * @since 2024/12/8 + */ +@Data +public class SystemInitializerContext { + + private Long adminUserId; + + private Long defaultNamespaceId; + + private Map extra; +} diff --git a/powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/web/controller/AppInfoController.java b/powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/web/controller/AppInfoController.java index 3206548c..991bf58b 100644 --- a/powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/web/controller/AppInfoController.java +++ b/powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/web/controller/AppInfoController.java @@ -2,27 +2,18 @@ package tech.powerjob.server.web.controller; import com.google.common.collect.Lists; import com.google.common.collect.Maps; -import com.google.common.collect.Sets; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.lang3.BooleanUtils; -import org.apache.commons.lang3.StringUtils; import org.springframework.beans.BeanUtils; import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.data.jpa.domain.Specification; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import tech.powerjob.common.enums.ErrorCodes; -import tech.powerjob.common.exception.PowerJobExceptionLauncher; import tech.powerjob.common.response.ResultDTO; import tech.powerjob.common.serialize.JsonUtils; import tech.powerjob.common.utils.CommonUtils; -import tech.powerjob.server.auth.LoginUserHolder; import tech.powerjob.server.auth.Permission; import tech.powerjob.server.auth.Role; import tech.powerjob.server.auth.RoleScope; @@ -31,14 +22,10 @@ import tech.powerjob.server.auth.interceptor.ApiPermission; import tech.powerjob.server.auth.plugin.ModifyOrCreateDynamicPermission; import tech.powerjob.server.auth.plugin.SaveAppGrantPermissionPlugin; import tech.powerjob.server.auth.service.WebAuthService; -import tech.powerjob.server.common.module.WorkerInfo; import tech.powerjob.server.core.service.AppInfoService; import tech.powerjob.server.persistence.PageResult; -import tech.powerjob.server.persistence.QueryConvertUtils; import tech.powerjob.server.persistence.remote.model.AppInfoDO; import tech.powerjob.server.persistence.remote.model.NamespaceDO; -import tech.powerjob.server.persistence.remote.repository.AppInfoRepository; -import tech.powerjob.server.remote.worker.WorkerClusterQueryService; import tech.powerjob.server.web.converter.NamespaceConverter; import tech.powerjob.server.web.request.AppAssertRequest; import tech.powerjob.server.web.request.ComponentUserRoleInfo; @@ -47,11 +34,13 @@ import tech.powerjob.server.web.request.QueryAppInfoRequest; import tech.powerjob.server.web.response.AppInfoVO; import tech.powerjob.server.web.response.NamespaceBaseVO; import tech.powerjob.server.web.response.UserBaseVO; +import tech.powerjob.server.web.service.AppWebService; import tech.powerjob.server.web.service.NamespaceWebService; import tech.powerjob.server.web.service.UserWebService; -import javax.persistence.criteria.Predicate; -import java.util.*; +import java.util.List; +import java.util.Map; +import java.util.Optional; import java.util.stream.Collectors; /** @@ -67,62 +56,20 @@ import java.util.stream.Collectors; @RequiredArgsConstructor public class AppInfoController { + private final AppWebService appWebService; private final WebAuthService webAuthService; private final UserWebService userWebService; private final AppInfoService appInfoService; - private final AppInfoRepository appInfoRepository; private final NamespaceWebService namespaceWebService; - private final WorkerClusterQueryService workerClusterQueryService; - @PostMapping("/save") @ApiPermission(name = "App-Save", roleScope = RoleScope.APP, dynamicPermissionPlugin = ModifyOrCreateDynamicPermission.class, grandPermissionPlugin = SaveAppGrantPermissionPlugin.class) public ResultDTO saveAppInfo(@RequestBody ModifyAppInfoRequest req) { - // 根据 ns code 填充 namespaceId(自动化创建过程中,固定的 namespace-code 对用户更友好) - if (StringUtils.isNotEmpty(req.getNamespaceCode())) { - namespaceWebService.findByCode(req.getNamespaceCode()).ifPresent(x -> req.setNamespaceId(x.getId())); - } - - req.valid(); - AppInfoDO appInfoDO; - - Long id = req.getId(); - if (id == null) { - - // 前置校验,防止部分没加唯一索引的 DB 重复创建记录导致异常 - appInfoRepository.findByAppName(req.getAppName()).ifPresent(x -> new PowerJobExceptionLauncher(ErrorCodes.ILLEGAL_ARGS_ERROR, String.format("App[%s] already exists", req.getAppName()))); - - appInfoDO = new AppInfoDO(); - appInfoDO.setGmtCreate(new Date()); - appInfoDO.setCreator(LoginUserHolder.getUserId()); - - } else { - appInfoDO = appInfoService.findById(id, false).orElseThrow(() -> new IllegalArgumentException("can't find appInfo by id:" + id)); - - // 不允许修改 appName - if (!appInfoDO.getAppName().equalsIgnoreCase(req.getAppName())) { - throw new IllegalArgumentException("NOT_ALLOW_CHANGE_THE_APP_NAME"); - } - } - - appInfoDO.setAppName(req.getAppName()); - appInfoDO.setTitle(req.getTitle()); - appInfoDO.setPassword(req.getPassword()); - appInfoDO.setNamespaceId(req.getNamespaceId()); - appInfoDO.setTags(req.getTags()); - appInfoDO.setExtra(req.getExtra()); - - appInfoDO.setGmtModified(new Date()); - appInfoDO.setModifier(LoginUserHolder.getUserId()); - - AppInfoDO savedAppInfo = appInfoService.save(appInfoDO); - - // 重现授权 - webAuthService.processPermissionOnSave(RoleScope.APP, savedAppInfo.getId(), req.getComponentUserRoleInfo()); + AppInfoDO savedAppInfo = appWebService.save(req); return ResultDTO.success(convert(Lists.newArrayList(savedAppInfo), false).get(0)); } @@ -131,15 +78,7 @@ public class AppInfoController { @ApiPermission(name = "App-Delete", roleScope = RoleScope.APP, requiredPermission = Permission.SU) public ResultDTO deleteApp(Long appId) { - log.warn("[AppInfoController] try to delete app: {}", appId); - - List allAliveWorkers = workerClusterQueryService.getAllAliveWorkers(appId); - if (CollectionUtils.isNotEmpty(allAliveWorkers)) { - return ResultDTO.failed("Unable to delete apps with live workers, Please remove the worker dependency first!"); - } - - appInfoService.deleteById(appId); - log.warn("[AppInfoController] delete app[id={}] successfully!", appId); + appWebService.delete(appId); return ResultDTO.success(null); } @@ -147,54 +86,7 @@ public class AppInfoController { @ApiPermission(name = "App-List", roleScope = RoleScope.APP, requiredPermission = Permission.NONE) public ResultDTO> listAppInfoByQuery(@RequestBody QueryAppInfoRequest queryAppInfoRequest) { - Pageable pageable = PageRequest.of(queryAppInfoRequest.getIndex(), queryAppInfoRequest.getPageSize()); - - // 相关权限(先查处关联 ids) - Set queryAppIds; - Boolean showMyRelated = queryAppInfoRequest.getShowMyRelated(); - if (BooleanUtils.isTrue(showMyRelated)) { - Set targetIds = Sets.newHashSet(); - webAuthService.fetchMyPermissionTargets(RoleScope.APP).values().forEach(targetIds::addAll); - queryAppIds = targetIds; - - if (CollectionUtils.isEmpty(queryAppIds)) { - return ResultDTO.success(new PageResult<>()); - } - - } else { - queryAppIds = Collections.emptySet(); - } - - Specification specification = (root, query, criteriaBuilder) -> { - List predicates = Lists.newArrayList(); - - Long appId = queryAppInfoRequest.getAppId(); - Long namespaceId = queryAppInfoRequest.getNamespaceId(); - - if (appId != null) { - predicates.add(criteriaBuilder.equal(root.get("id"), appId)); - } - - if (namespaceId != null) { - predicates.add(criteriaBuilder.equal(root.get("namespaceId"), namespaceId)); - } - - if (StringUtils.isNotEmpty(queryAppInfoRequest.getAppNameLike())) { - predicates.add(criteriaBuilder.like(root.get("appName"), QueryConvertUtils.convertLikeParams(queryAppInfoRequest.getAppNameLike()))); - } - - if (StringUtils.isNotEmpty(queryAppInfoRequest.getTagLike())) { - predicates.add(criteriaBuilder.like(root.get("tags"), QueryConvertUtils.convertLikeParams(queryAppInfoRequest.getTagLike()))); - } - - if (!queryAppIds.isEmpty()) { - predicates.add(criteriaBuilder.in(root.get("id")).value(queryAppIds)); - } - - return query.where(predicates.toArray(new Predicate[0])).getRestriction(); - }; - - Page pageAppInfoResult = appInfoRepository.findAll(specification, pageable); + Page pageAppInfoResult = appWebService.list(queryAppInfoRequest); PageResult pageRet = new PageResult<>(pageAppInfoResult); diff --git a/powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/web/service/AppWebService.java b/powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/web/service/AppWebService.java new file mode 100644 index 00000000..5c996a75 --- /dev/null +++ b/powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/web/service/AppWebService.java @@ -0,0 +1,21 @@ +package tech.powerjob.server.web.service; + +import org.springframework.data.domain.Page; +import tech.powerjob.server.persistence.remote.model.AppInfoDO; +import tech.powerjob.server.web.request.ModifyAppInfoRequest; +import tech.powerjob.server.web.request.QueryAppInfoRequest; + +/** + * AppWebService + * + * @author tjq + * @since 2024/12/8 + */ +public interface AppWebService { + + AppInfoDO save(ModifyAppInfoRequest request); + + void delete(Long id); + + Page list(QueryAppInfoRequest queryAppInfoRequest); +} diff --git a/powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/web/service/impl/AppWebServiceImpl.java b/powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/web/service/impl/AppWebServiceImpl.java new file mode 100644 index 00000000..ae951d7f --- /dev/null +++ b/powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/web/service/impl/AppWebServiceImpl.java @@ -0,0 +1,168 @@ +package tech.powerjob.server.web.service.impl; + +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.BooleanUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.stereotype.Service; +import tech.powerjob.common.enums.ErrorCodes; +import tech.powerjob.common.exception.PowerJobException; +import tech.powerjob.common.exception.PowerJobExceptionLauncher; +import tech.powerjob.server.auth.LoginUserHolder; +import tech.powerjob.server.auth.RoleScope; +import tech.powerjob.server.auth.service.WebAuthService; +import tech.powerjob.server.common.module.WorkerInfo; +import tech.powerjob.server.core.service.AppInfoService; +import tech.powerjob.server.persistence.QueryConvertUtils; +import tech.powerjob.server.persistence.remote.model.AppInfoDO; +import tech.powerjob.server.persistence.remote.repository.AppInfoRepository; +import tech.powerjob.server.remote.worker.WorkerClusterQueryService; +import tech.powerjob.server.web.request.ModifyAppInfoRequest; +import tech.powerjob.server.web.request.QueryAppInfoRequest; +import tech.powerjob.server.web.service.AppWebService; +import tech.powerjob.server.web.service.NamespaceWebService; + +import javax.persistence.criteria.Predicate; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Set; + +/** + * AppWebService + * + * @author tjq + * @since 2024/12/8 + */ +@Slf4j +@Service +@AllArgsConstructor +public class AppWebServiceImpl implements AppWebService { + + private final WebAuthService webAuthService; + + private final AppInfoService appInfoService; + private final AppInfoRepository appInfoRepository; + + private final NamespaceWebService namespaceWebService; + + private final WorkerClusterQueryService workerClusterQueryService; + + @Override + public AppInfoDO save(ModifyAppInfoRequest req) { + // 根据 ns code 填充 namespaceId(自动化创建过程中,固定的 namespace-code 对用户更友好) + if (StringUtils.isNotEmpty(req.getNamespaceCode())) { + namespaceWebService.findByCode(req.getNamespaceCode()).ifPresent(x -> req.setNamespaceId(x.getId())); + } + + req.valid(); + AppInfoDO appInfoDO; + + Long id = req.getId(); + if (id == null) { + + // 前置校验,防止部分没加唯一索引的 DB 重复创建记录导致异常 + appInfoRepository.findByAppName(req.getAppName()).ifPresent(x -> new PowerJobExceptionLauncher(ErrorCodes.ILLEGAL_ARGS_ERROR, String.format("App[%s] already exists", req.getAppName()))); + + appInfoDO = new AppInfoDO(); + appInfoDO.setGmtCreate(new Date()); + appInfoDO.setCreator(LoginUserHolder.getUserId()); + + } else { + appInfoDO = appInfoService.findById(id, false).orElseThrow(() -> new IllegalArgumentException("can't find appInfo by id:" + id)); + + // 不允许修改 appName + if (!appInfoDO.getAppName().equalsIgnoreCase(req.getAppName())) { + throw new IllegalArgumentException("NOT_ALLOW_CHANGE_THE_APP_NAME"); + } + } + + appInfoDO.setAppName(req.getAppName()); + appInfoDO.setTitle(req.getTitle()); + appInfoDO.setPassword(req.getPassword()); + appInfoDO.setNamespaceId(req.getNamespaceId()); + appInfoDO.setTags(req.getTags()); + appInfoDO.setExtra(req.getExtra()); + + appInfoDO.setGmtModified(new Date()); + appInfoDO.setModifier(LoginUserHolder.getUserId()); + + AppInfoDO savedAppInfo = appInfoService.save(appInfoDO); + + // 重现授权 + webAuthService.processPermissionOnSave(RoleScope.APP, savedAppInfo.getId(), req.getComponentUserRoleInfo()); + return savedAppInfo; + } + + @Override + public void delete(Long appId) { + log.warn("[AppInfoController] try to delete app: {}", appId); + + List allAliveWorkers = workerClusterQueryService.getAllAliveWorkers(appId); + if (CollectionUtils.isNotEmpty(allAliveWorkers)) { + throw new PowerJobException(ErrorCodes.OPERATION_NOT_PERMITTED, "Unable to delete apps with live workers, Please remove the worker dependency first!"); + } + + appInfoService.deleteById(appId); + log.warn("[AppInfoController] delete app[id={}] successfully!", appId); + } + + @Override + public Page list(QueryAppInfoRequest queryAppInfoRequest) { + Pageable pageable = PageRequest.of(queryAppInfoRequest.getIndex(), queryAppInfoRequest.getPageSize()); + + // 相关权限(先查处关联 ids) + Set queryAppIds; + Boolean showMyRelated = queryAppInfoRequest.getShowMyRelated(); + if (BooleanUtils.isTrue(showMyRelated)) { + Set targetIds = Sets.newHashSet(); + webAuthService.fetchMyPermissionTargets(RoleScope.APP).values().forEach(targetIds::addAll); + queryAppIds = targetIds; + + if (CollectionUtils.isEmpty(queryAppIds)) { + return Page.empty(); + } + + } else { + queryAppIds = Collections.emptySet(); + } + + Specification specification = (root, query, criteriaBuilder) -> { + List predicates = Lists.newArrayList(); + + Long appId = queryAppInfoRequest.getAppId(); + Long namespaceId = queryAppInfoRequest.getNamespaceId(); + + if (appId != null) { + predicates.add(criteriaBuilder.equal(root.get("id"), appId)); + } + + if (namespaceId != null) { + predicates.add(criteriaBuilder.equal(root.get("namespaceId"), namespaceId)); + } + + if (StringUtils.isNotEmpty(queryAppInfoRequest.getAppNameLike())) { + predicates.add(criteriaBuilder.like(root.get("appName"), QueryConvertUtils.convertLikeParams(queryAppInfoRequest.getAppNameLike()))); + } + + if (StringUtils.isNotEmpty(queryAppInfoRequest.getTagLike())) { + predicates.add(criteriaBuilder.like(root.get("tags"), QueryConvertUtils.convertLikeParams(queryAppInfoRequest.getTagLike()))); + } + + if (!queryAppIds.isEmpty()) { + predicates.add(criteriaBuilder.in(root.get("id")).value(queryAppIds)); + } + + return query.where(predicates.toArray(new Predicate[0])).getRestriction(); + }; + + return appInfoRepository.findAll(specification, pageable); + } +}