---初始化项目
This commit is contained in:
85
powerjob-server/powerjob-server-starter/pom.xml
Normal file
85
powerjob-server/powerjob-server-starter/pom.xml
Normal file
@ -0,0 +1,85 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>powerjob-server</artifactId>
|
||||
<groupId>tech.powerjob</groupId>
|
||||
<version>5.1.2</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>powerjob-server-starter</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>8</maven.compiler.source>
|
||||
<maven.compiler.target>8</maven.compiler.target>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>tech.powerjob</groupId>
|
||||
<artifactId>powerjob-server-extension</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>tech.powerjob</groupId>
|
||||
<artifactId>powerjob-server-remote</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>tech.powerjob</groupId>
|
||||
<artifactId>powerjob-server-common</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>tech.powerjob</groupId>
|
||||
<artifactId>powerjob-server-monitor</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>tech.powerjob</groupId>
|
||||
<artifactId>powerjob-server-persistence</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>tech.powerjob</groupId>
|
||||
<artifactId>powerjob-server-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>tech.powerjob</groupId>
|
||||
<artifactId>powerjob-server-auth</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>tech.powerjob</groupId>
|
||||
<artifactId>powerjob-server-migrate</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>tech.powerjob</groupId>
|
||||
<artifactId>powerjob-client</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<!-- SpringBoot maven plugin -->
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<version>${springboot.version}</version>
|
||||
<configuration>
|
||||
<mainClass>tech.powerjob.server.PowerJobServerApplication</mainClass>
|
||||
<!-- 支持 server 作为 jar 包被外部系统集成 https://gitee.com/KFCFans/PowerJob/issues/I6H8JF ,短期内为了兼容 CI 流程 deploy 阶段关闭注释,推 master 重新打开-->
|
||||
<!-- <classifier>exec</classifier>-->
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>build-info</goal>
|
||||
<goal>repackage</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
@ -0,0 +1,45 @@
|
||||
package tech.powerjob.server;
|
||||
|
||||
import tech.powerjob.server.common.utils.PropertyUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
|
||||
/**
|
||||
* powerjob-server entry
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2020/3/29
|
||||
*/
|
||||
@Slf4j
|
||||
@EnableScheduling
|
||||
@SpringBootApplication
|
||||
public class PowerJobServerApplication {
|
||||
|
||||
private static final String TIPS = "\n\n" +
|
||||
"******************* PowerJob Tips *******************\n" +
|
||||
"如果应用无法启动,我们建议您仔细阅读以下文档来解决:\n" +
|
||||
"if server can't startup, we recommend that you read the documentation to find a solution:\n" +
|
||||
"https://www.yuque.com/powerjob/guidence/problem\n" +
|
||||
"******************* PowerJob Tips *******************\n\n";
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
pre();
|
||||
|
||||
// Start SpringBoot application.
|
||||
try {
|
||||
SpringApplication.run(PowerJobServerApplication.class, args);
|
||||
} catch (Throwable t) {
|
||||
log.error(TIPS);
|
||||
throw t;
|
||||
}
|
||||
}
|
||||
|
||||
private static void pre() {
|
||||
log.info(TIPS);
|
||||
PropertyUtils.init();
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,46 @@
|
||||
package tech.powerjob.server.auth.plugin;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.util.StreamUtils;
|
||||
import tech.powerjob.common.serialize.JsonUtils;
|
||||
import tech.powerjob.server.auth.Permission;
|
||||
import tech.powerjob.server.auth.interceptor.DynamicPermissionPlugin;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 针对 namespace 和 app 两大鉴权纬度,创建不需要任何权限,但任何修改操作都需要 WRITE 权限
|
||||
* 创建不需要权限,修改需要校验权限
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2023/9/3
|
||||
*/
|
||||
@Slf4j
|
||||
public class ModifyOrCreateDynamicPermission implements DynamicPermissionPlugin {
|
||||
@Override
|
||||
public Permission calculate(HttpServletRequest request, Object handler) {
|
||||
|
||||
try {
|
||||
//获取请求body
|
||||
byte[] bodyBytes = StreamUtils.copyToByteArray(request.getInputStream());
|
||||
String body = new String(bodyBytes, request.getCharacterEncoding());
|
||||
|
||||
Map<String, Object> inputParams = JsonUtils.parseMap(body);
|
||||
|
||||
Object id = inputParams.get("id");
|
||||
|
||||
// 创建,不需要权限
|
||||
if (id == null) {
|
||||
return Permission.NONE;
|
||||
}
|
||||
|
||||
return Permission.WRITE;
|
||||
} catch (Exception e) {
|
||||
log.error("[ModifyOrCreateDynamicPermission] check permission failed, please fix the bug!!!", e);
|
||||
}
|
||||
|
||||
// 异常情况先放行,不影响功能使用,后续修复 BUG
|
||||
return Permission.NONE;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
package tech.powerjob.server.auth.plugin;
|
||||
|
||||
import tech.powerjob.server.auth.RoleScope;
|
||||
|
||||
/**
|
||||
* desc
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2024/2/11
|
||||
*/
|
||||
public class SaveAppGrantPermissionPlugin extends SaveGrantPermissionPlugin {
|
||||
@Override
|
||||
protected RoleScope fetchRuleScope() {
|
||||
return RoleScope.APP;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,99 @@
|
||||
package tech.powerjob.server.auth.plugin;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections4.MapUtils;
|
||||
import tech.powerjob.common.response.ResultDTO;
|
||||
import tech.powerjob.common.serialize.JsonUtils;
|
||||
import tech.powerjob.server.auth.LoginUserHolder;
|
||||
import tech.powerjob.server.auth.PowerJobUser;
|
||||
import tech.powerjob.server.auth.Role;
|
||||
import tech.powerjob.server.auth.RoleScope;
|
||||
import tech.powerjob.server.auth.interceptor.GrantPermissionPlugin;
|
||||
import tech.powerjob.server.auth.service.permission.PowerJobPermissionService;
|
||||
import tech.powerjob.server.common.utils.SpringUtils;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* WEB 类保存&修改一体型请求-授权插件
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2024/2/11
|
||||
*/
|
||||
@Slf4j
|
||||
public abstract class SaveGrantPermissionPlugin implements GrantPermissionPlugin {
|
||||
|
||||
private static final String KEY_ID = "id";
|
||||
|
||||
@Override
|
||||
public void grant(Object[] args, Object result, Method method, Object originBean) {
|
||||
|
||||
List<Object> bizArgs = filterInputArgs(args);
|
||||
if (bizArgs.size() != 1) {
|
||||
throw new IllegalArgumentException("[GrantPermission] args not match(!=1), maybe there has some bug!size=" + bizArgs.size());
|
||||
}
|
||||
|
||||
// 理论上不可能,前置已完成判断
|
||||
PowerJobUser powerJobUser = LoginUserHolder.get();
|
||||
if (powerJobUser == null) {
|
||||
throw new IllegalArgumentException("[GrantPermission] user not login, can't grant permission");
|
||||
}
|
||||
|
||||
// 解析ID,非空代表更新,不授权
|
||||
Map<String, Object> saveRequest = JsonUtils.parseMap(JsonUtils.toJSONString(args[0]));
|
||||
Long id = MapUtils.getLong(saveRequest, KEY_ID);
|
||||
if (id != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(result instanceof ResultDTO)) {
|
||||
throw new IllegalArgumentException("[GrantPermission] result not instanceof ResultDTO, maybe there has some bug");
|
||||
}
|
||||
|
||||
ResultDTO<?> resultDTO = (ResultDTO<?>) result;
|
||||
|
||||
if (!resultDTO.isSuccess()) {
|
||||
log.warn("[GrantPermission] result not success, skip grant permission!");
|
||||
return;
|
||||
}
|
||||
|
||||
Map<String, Object> saveResult = JsonUtils.parseMap(JsonUtils.toJSONString(resultDTO.getData()));
|
||||
Long savedId = MapUtils.getLong(saveResult, KEY_ID);
|
||||
if (savedId == null) {
|
||||
throw new IllegalArgumentException("[GrantPermission] result success but id not exits, maybe there has some bug, please fix it!!!");
|
||||
}
|
||||
|
||||
PowerJobPermissionService powerJobPermissionService = SpringUtils.getBean(PowerJobPermissionService.class);
|
||||
|
||||
Map<String, Object> extra = Maps.newHashMap();
|
||||
extra.put("source", "SaveGrantPermissionPlugin");
|
||||
|
||||
powerJobPermissionService.grantRole(fetchRuleScope(), savedId, powerJobUser.getId(), Role.ADMIN, JsonUtils.toJSONString(extra));
|
||||
}
|
||||
|
||||
protected abstract RoleScope fetchRuleScope();
|
||||
|
||||
private static List<Object> filterInputArgs(Object[] args) {
|
||||
if (args == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<Object> ret = Lists.newArrayList();
|
||||
for (Object arg : args) {
|
||||
if (arg instanceof HttpServletRequest) {
|
||||
continue;
|
||||
}
|
||||
if (arg instanceof HttpServletResponse) {
|
||||
continue;
|
||||
}
|
||||
ret.add(arg);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
package tech.powerjob.server.auth.plugin;
|
||||
|
||||
import tech.powerjob.server.auth.RoleScope;
|
||||
|
||||
/**
|
||||
* namespace 授权插件
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2024/2/11
|
||||
*/
|
||||
public class SaveNamespaceGrantPermissionPlugin extends SaveGrantPermissionPlugin {
|
||||
@Override
|
||||
protected RoleScope fetchRuleScope() {
|
||||
return RoleScope.NAMESPACE;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,62 @@
|
||||
package tech.powerjob.server.auth.service;
|
||||
|
||||
import tech.powerjob.server.auth.Permission;
|
||||
import tech.powerjob.server.auth.Role;
|
||||
import tech.powerjob.server.auth.RoleScope;
|
||||
import tech.powerjob.server.web.request.ComponentUserRoleInfo;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Web Auth 服务
|
||||
* 写在 starter 包下,抽取 controller 的公共逻辑
|
||||
* (powerjob 的 service/core 包核心处理调度核心逻辑,admin 部分代码收口在 stater 包)
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2024/2/12
|
||||
*/
|
||||
public interface WebAuthService {
|
||||
|
||||
/**
|
||||
* 对当前登录用户授予角色
|
||||
* @param roleScope 角色范围
|
||||
* @param target 目标
|
||||
* @param role 角色
|
||||
* @param extra 其他信息
|
||||
*/
|
||||
void grantRole2LoginUser(RoleScope roleScope, Long target, Role role, String extra);
|
||||
|
||||
/**
|
||||
* 处理授权
|
||||
* @param roleScope 权限范围
|
||||
* @param target 权限目标
|
||||
* @param componentUserRoleInfo 人员角色信息
|
||||
*/
|
||||
void processPermissionOnSave(RoleScope roleScope, Long target, ComponentUserRoleInfo componentUserRoleInfo);
|
||||
|
||||
/**
|
||||
* 获取目标相关权限人员列表
|
||||
* @param roleScope 权限范围
|
||||
* @param target 权限目标
|
||||
* @return ComponentUserRoleInfo
|
||||
*/
|
||||
ComponentUserRoleInfo fetchComponentUserRoleInfo(RoleScope roleScope, Long target);
|
||||
|
||||
/**
|
||||
* 判断当前用户是否有权限
|
||||
* @param roleScope 权限范围
|
||||
* @param target 权限目标
|
||||
* @param permission 要求的权限
|
||||
* @return 是否有权限
|
||||
*/
|
||||
boolean hasPermission(RoleScope roleScope, Long target, Permission permission);
|
||||
|
||||
/**
|
||||
* 是否为全局管理员
|
||||
* @return true or false
|
||||
*/
|
||||
boolean isGlobalAdmin();
|
||||
|
||||
Map<Role, List<Long>> fetchMyPermissionTargets(RoleScope roleScope);
|
||||
}
|
||||
@ -0,0 +1,121 @@
|
||||
package tech.powerjob.server.auth.service.impl;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Sets;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import tech.powerjob.common.serialize.JsonUtils;
|
||||
import tech.powerjob.server.auth.*;
|
||||
import tech.powerjob.server.auth.common.AuthConstants;
|
||||
import tech.powerjob.common.enums.ErrorCodes;
|
||||
import tech.powerjob.server.auth.common.PowerJobAuthException;
|
||||
import tech.powerjob.server.auth.service.WebAuthService;
|
||||
import tech.powerjob.server.auth.service.permission.PowerJobPermissionService;
|
||||
import tech.powerjob.server.web.request.ComponentUserRoleInfo;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* WebAuthService
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2024/2/12
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class WebAuthServiceImpl implements WebAuthService {
|
||||
|
||||
@Resource
|
||||
private PowerJobPermissionService powerJobPermissionService;
|
||||
|
||||
|
||||
@Override
|
||||
public void grantRole2LoginUser(RoleScope roleScope, Long target, Role role, String extra) {
|
||||
Long userId = LoginUserHolder.getUserId();
|
||||
if (userId == null) {
|
||||
throw new PowerJobAuthException(ErrorCodes.USER_NOT_LOGIN);
|
||||
}
|
||||
powerJobPermissionService.grantRole(roleScope, target, userId, role, extra);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processPermissionOnSave(RoleScope roleScope, Long target, ComponentUserRoleInfo o) {
|
||||
ComponentUserRoleInfo componentUserRoleInfo = Optional.ofNullable(o).orElse(new ComponentUserRoleInfo());
|
||||
|
||||
Map<Role, Set<Long>> role2Uids = powerJobPermissionService.fetchUserWithPermissions(roleScope, target);
|
||||
diffGrant(roleScope, target, Role.OBSERVER, componentUserRoleInfo.getObserver(), role2Uids);
|
||||
diffGrant(roleScope, target, Role.QA, componentUserRoleInfo.getQa(), role2Uids);
|
||||
diffGrant(roleScope, target, Role.DEVELOPER, componentUserRoleInfo.getDeveloper(), role2Uids);
|
||||
diffGrant(roleScope, target, Role.ADMIN, componentUserRoleInfo.getAdmin(), role2Uids);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ComponentUserRoleInfo fetchComponentUserRoleInfo(RoleScope roleScope, Long target) {
|
||||
Map<Role, Set<Long>> role2Uids = powerJobPermissionService.fetchUserWithPermissions(roleScope, target);
|
||||
return new ComponentUserRoleInfo()
|
||||
.setObserver(Lists.newArrayList(role2Uids.getOrDefault(Role.OBSERVER, Collections.emptySet())))
|
||||
.setQa(Lists.newArrayList(role2Uids.getOrDefault(Role.QA, Collections.emptySet())))
|
||||
.setDeveloper(Lists.newArrayList(role2Uids.getOrDefault(Role.DEVELOPER, Collections.emptySet())))
|
||||
.setAdmin(Lists.newArrayList(role2Uids.getOrDefault(Role.ADMIN, Collections.emptySet())));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPermission(RoleScope roleScope, Long target, Permission permission) {
|
||||
|
||||
PowerJobUser powerJobUser = LoginUserHolder.get();
|
||||
if (powerJobUser == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return powerJobPermissionService.hasPermission(powerJobUser.getId(), roleScope, target, permission);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGlobalAdmin() {
|
||||
return hasPermission(RoleScope.GLOBAL, AuthConstants.GLOBAL_ADMIN_TARGET_ID, Permission.SU);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Role, List<Long>> fetchMyPermissionTargets(RoleScope roleScope) {
|
||||
|
||||
PowerJobUser powerJobUser = LoginUserHolder.get();
|
||||
if (powerJobUser == null) {
|
||||
throw new PowerJobAuthException(ErrorCodes.USER_NOT_LOGIN);
|
||||
}
|
||||
|
||||
// 展示不考虑穿透权限的问题(即拥有 namespace 权限也可以看到全部的 apps)
|
||||
return powerJobPermissionService.fetchUserHadPermissionTargets(roleScope, powerJobUser.getId());
|
||||
}
|
||||
|
||||
private void diffGrant(RoleScope roleScope, Long target, Role role, List<Long> uids, Map<Role, Set<Long>> originRole2Uids) {
|
||||
|
||||
Set<Long> originUids = Sets.newHashSet(Optional.ofNullable(originRole2Uids.get(role)).orElse(Collections.emptySet()));
|
||||
Set<Long> currentUids = Sets.newHashSet(Optional.ofNullable(uids).orElse(Collections.emptyList()));
|
||||
|
||||
Map<String, Object> extraInfo = Maps.newHashMap();
|
||||
extraInfo.put("grantor", LoginUserHolder.getUserName());
|
||||
extraInfo.put("source", "diffGrant");
|
||||
String extra = JsonUtils.toJSONString(extraInfo);
|
||||
|
||||
Set<Long> allIds = Sets.newHashSet(originUids);
|
||||
allIds.addAll(currentUids);
|
||||
|
||||
Set<Long> allIds2 = Sets.newHashSet(allIds);
|
||||
|
||||
// 在 originUids 不在 currentUids,需要取消授权
|
||||
allIds.removeAll(currentUids);
|
||||
allIds.forEach(cancelPermissionUid -> {
|
||||
powerJobPermissionService.retrieveRole(roleScope, target, cancelPermissionUid, role);
|
||||
log.info("[WebAuthService] [diffGrant] cancelPermission: roleScope={},target={},uid={},role={}", roleScope, target, cancelPermissionUid, role);
|
||||
});
|
||||
|
||||
// 在 currentUids 当不在 orignUids,需要增加授权
|
||||
allIds2.removeAll(originUids);
|
||||
allIds2.forEach(addPermissionUid -> {
|
||||
powerJobPermissionService.grantRole(roleScope, target, addPermissionUid, role, extra);
|
||||
log.info("[WebAuthService] [diffGrant] grantPermission: roleScope={},target={},uid={},role={},extra={}", roleScope, target, addPermissionUid, role, extra);
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,119 @@
|
||||
package tech.powerjob.server.config;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.servlet.*;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletRequestWrapper;
|
||||
import java.io.*;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 解决 HttpServletRequest 只能被读取一次的问题,方便全局日志 & 鉴权,切面提前读取数据
|
||||
* 在请求进入Servlet容器之前,先经过Filter的过滤器链。在请求进入Controller之前,先经过 HandlerInterceptor 的拦截器链。Filter 一定先于 HandlerInterceptor 执行
|
||||
* <a href="https://juejin.cn/post/6858037733776949262">解决HttpServletRequest 流数据不可重复读</a>
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2024/2/11
|
||||
*/
|
||||
@Component
|
||||
public class CachingRequestBodyFilter implements Filter {
|
||||
|
||||
|
||||
/**
|
||||
* 忽略部分不需要处理的类型:
|
||||
* GET 请求的数据一般是 Query String,直接在 url 的后面,不需要特殊处理
|
||||
* multipart/form-data:doDispatch() 阶段就会进行处理,此处已经空值了,强行处理会导致结果空
|
||||
* application/x-www-form-urlencoded:估计也类似,有特殊逻辑,导致 OpenAPI 部分请求参数无法传递,同样忽略
|
||||
*/
|
||||
private static final Set<String> IGNORE_CONTENT_TYPES = Sets.newHashSet("application/x-www-form-urlencoded", "multipart/form-data");
|
||||
|
||||
private static final Set<String> IGNORE_URIS = Sets.newHashSet("/container/jarUpload");
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
|
||||
throws IOException, ServletException {
|
||||
if (request instanceof HttpServletRequest) {
|
||||
String uri = ((HttpServletRequest) request).getRequestURI();
|
||||
// 忽略 jar 上传等处理路径
|
||||
if (IGNORE_URIS.contains(uri)) {
|
||||
chain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
String contentType = request.getContentType();
|
||||
if (contentType != null && !IGNORE_CONTENT_TYPES.contains(contentType)) {
|
||||
CustomHttpServletRequestWrapper wrappedRequest = new CustomHttpServletRequestWrapper((HttpServletRequest) request);
|
||||
chain.doFilter(wrappedRequest, response);
|
||||
return;
|
||||
}
|
||||
}
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
|
||||
// Implement other required methods like init() and destroy() if necessary
|
||||
|
||||
|
||||
public static class CustomHttpServletRequestWrapper extends HttpServletRequestWrapper {
|
||||
|
||||
private final String body;
|
||||
|
||||
public CustomHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
|
||||
super(request);
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
BufferedReader bufferedReader = null;
|
||||
try {
|
||||
InputStream inputStream = request.getInputStream();
|
||||
if (inputStream != null) {
|
||||
bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
|
||||
char[] charBuffer = new char[128];
|
||||
int bytesRead = -1;
|
||||
while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
|
||||
stringBuilder.append(charBuffer, 0, bytesRead);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (bufferedReader != null) {
|
||||
bufferedReader.close();
|
||||
}
|
||||
}
|
||||
body = stringBuilder.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServletInputStream getInputStream() throws IOException {
|
||||
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes());
|
||||
|
||||
return new ServletInputStream() {
|
||||
public int read() throws IOException {
|
||||
return byteArrayInputStream.read();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFinished() {
|
||||
return byteArrayInputStream.available() == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReady() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setReadListener(ReadListener readListener) {
|
||||
throw new UnsupportedOperationException("Not implemented");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public BufferedReader getReader() throws IOException {
|
||||
return new BufferedReader(new InputStreamReader(this.getInputStream()));
|
||||
}
|
||||
|
||||
public String getBody() {
|
||||
return this.body;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
package tech.powerjob.server.config;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.websocket.server.ServerEndpointConfig;
|
||||
|
||||
/**
|
||||
* WebSocket 配置
|
||||
* 解决 SpringBoot WebSocket 无法注入对象(@Resource/@Autowired)的问题
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2020/5/17
|
||||
*/
|
||||
@Component
|
||||
public class OmsEndpointConfigure extends ServerEndpointConfig.Configurator implements ApplicationContextAware {
|
||||
|
||||
private static volatile ApplicationContext context;
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
context = applicationContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T getEndpointInstance(Class<T> clazz) throws InstantiationException {
|
||||
return context.getBean(clazz);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,61 @@
|
||||
package tech.powerjob.server.config;
|
||||
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import io.swagger.v3.oas.models.info.Contact;
|
||||
import io.swagger.v3.oas.models.info.Info;
|
||||
import io.swagger.v3.oas.models.info.License;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springdoc.core.GroupedOpenApi;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import tech.powerjob.server.common.PowerJobServerConfigKey;
|
||||
import tech.powerjob.server.remote.server.self.ServerInfoService;
|
||||
|
||||
/**
|
||||
* Configuration class for Swagger UI.
|
||||
* migrate to <a href="https://springdoc.org/">springdoc</a> from v4.3.1
|
||||
* <a href="http://localhost:7700/swagger-ui/index.html#/">api address</a>
|
||||
*
|
||||
* @author tjq
|
||||
* @author Jiang Jining
|
||||
* @since 2020/3/29
|
||||
*/
|
||||
@Slf4j
|
||||
@Configuration
|
||||
@ConditionalOnProperty(name = PowerJobServerConfigKey.SWAGGER_UI_ENABLE, havingValue = "true")
|
||||
@RequiredArgsConstructor
|
||||
public class SwaggerConfig {
|
||||
|
||||
private final ServerInfoService serverInfoService;
|
||||
|
||||
@Bean
|
||||
public OpenAPI springShopOpenAPI() {
|
||||
final Contact contact = new Contact();
|
||||
contact.setName("Team PowerJob");
|
||||
contact.setUrl("http://www.powerjob.tech");
|
||||
contact.setEmail("tengjiqi@gmail.com");
|
||||
|
||||
// apiInfo()用来创建该Api的基本信息(这些基本信息会展现在文档页面中
|
||||
return new OpenAPI()
|
||||
.info(new Info().title("PowerJob")
|
||||
.description("Distributed scheduling and computing framework.")
|
||||
.version(serverInfoService.fetchCurrentServerInfo().getVersion())
|
||||
.contact(contact)
|
||||
.license(new License().name("Apache License 2.0").url("https://github.com/PowerJob/PowerJob/blob/master/LICENSE")));
|
||||
}
|
||||
|
||||
@Bean
|
||||
public GroupedOpenApi createRestApi() {
|
||||
|
||||
log.warn("[OpenAPI] openapi has been activated, make sure you want to enable it!");
|
||||
|
||||
return GroupedOpenApi.builder()
|
||||
.group("PowerJob-ALL")
|
||||
.pathsToMatch("/**")
|
||||
.build();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,77 @@
|
||||
package tech.powerjob.server.config;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.task.AsyncTaskExecutor;
|
||||
import org.springframework.core.task.TaskExecutor;
|
||||
import org.springframework.scheduling.TaskScheduler;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
|
||||
import tech.powerjob.common.utils.SysUtils;
|
||||
import tech.powerjob.server.common.RejectedExecutionHandlerFactory;
|
||||
import tech.powerjob.server.common.constants.PJThreadPool;
|
||||
import tech.powerjob.server.common.thread.NewThreadRunRejectedExecutionHandler;
|
||||
|
||||
/**
|
||||
* 公用线程池配置
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2020/4/28
|
||||
*/
|
||||
@Slf4j
|
||||
@EnableAsync
|
||||
@Configuration
|
||||
public class ThreadPoolConfig {
|
||||
|
||||
@Bean(PJThreadPool.TIMING_POOL)
|
||||
public TaskExecutor getTimingPool() {
|
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
executor.setCorePoolSize(SysUtils.availableProcessors());
|
||||
executor.setMaxPoolSize(SysUtils.availableProcessors() * 4);
|
||||
// use SynchronousQueue
|
||||
executor.setQueueCapacity(0);
|
||||
executor.setKeepAliveSeconds(60);
|
||||
executor.setThreadNamePrefix("PJ-TIMING-");
|
||||
executor.setRejectedExecutionHandler(new NewThreadRunRejectedExecutionHandler(PJThreadPool.TIMING_POOL));
|
||||
return executor;
|
||||
}
|
||||
|
||||
@Bean(PJThreadPool.BACKGROUND_POOL)
|
||||
public AsyncTaskExecutor initBackgroundPool() {
|
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
executor.setCorePoolSize(SysUtils.availableProcessors() * 8);
|
||||
executor.setMaxPoolSize(SysUtils.availableProcessors() * 16);
|
||||
executor.setQueueCapacity(8192);
|
||||
executor.setKeepAliveSeconds(60);
|
||||
executor.setThreadNamePrefix("PJ-BG-");
|
||||
executor.setRejectedExecutionHandler(RejectedExecutionHandlerFactory.newDiscard(PJThreadPool.BACKGROUND_POOL));
|
||||
return executor;
|
||||
}
|
||||
|
||||
@Bean(PJThreadPool.LOCAL_DB_POOL)
|
||||
public TaskExecutor initOmsLocalDbPool() {
|
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
int tSize = Math.max(1, SysUtils.availableProcessors() / 2);
|
||||
executor.setCorePoolSize(tSize);
|
||||
executor.setMaxPoolSize(tSize);
|
||||
executor.setQueueCapacity(2048);
|
||||
executor.setThreadNamePrefix("PJ-LOCALDB-");
|
||||
executor.setRejectedExecutionHandler(RejectedExecutionHandlerFactory.newAbort(PJThreadPool.LOCAL_DB_POOL));
|
||||
return executor;
|
||||
}
|
||||
|
||||
/**
|
||||
* 引入 WebSocket 支持后需要手动初始化调度线程池
|
||||
*/
|
||||
@Bean
|
||||
public TaskScheduler taskScheduler() {
|
||||
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
|
||||
scheduler.setPoolSize(Math.max(SysUtils.availableProcessors() * 8, 32));
|
||||
scheduler.setThreadNamePrefix("PJ-DEFAULT-");
|
||||
scheduler.setDaemon(true);
|
||||
return scheduler;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,60 @@
|
||||
package tech.powerjob.server.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
import org.springframework.web.socket.config.annotation.EnableWebSocket;
|
||||
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
|
||||
import tech.powerjob.common.OpenAPIConstant;
|
||||
import tech.powerjob.server.auth.interceptor.PowerJobAuthInterceptor;
|
||||
import tech.powerjob.server.openapi.OpenApiInterceptor;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* CORS
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2020/4/13
|
||||
*/
|
||||
@Configuration
|
||||
@EnableWebSocket
|
||||
public class WebConfig implements WebMvcConfigurer {
|
||||
|
||||
@Resource
|
||||
private OpenApiInterceptor openApiInterceptor;
|
||||
@Resource
|
||||
private PowerJobAuthInterceptor powerJobAuthInterceptor;
|
||||
|
||||
@Override
|
||||
public void addCorsMappings(CorsRegistry registry) {
|
||||
registry.addMapping("/**")
|
||||
.allowedMethods("HEAD", "GET", "PUT", "POST", "DELETE", "PATCH");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ServerEndpointExporter serverEndpointExporter() {
|
||||
return new ServerEndpointExporter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
/*
|
||||
可以添加多个拦截器
|
||||
addPathPatterns("/**") 表示对所有请求都拦截
|
||||
.excludePathPatterns("/base/index") 表示排除对/base/index请求的拦截
|
||||
多个拦截器可以设置order顺序,值越小,preHandle越先执行,postHandle和afterCompletion越后执行
|
||||
order默认的值是0,如果只添加一个拦截器,可以不显示设置order的值
|
||||
*/
|
||||
registry.addInterceptor(powerJobAuthInterceptor)
|
||||
.addPathPatterns("/**")
|
||||
.excludePathPatterns("/css/**", "/js/**", "/images/**", "/img/**", "/fonts/**", "/favicon.ico")
|
||||
.order(0);
|
||||
|
||||
registry.addInterceptor(openApiInterceptor)
|
||||
.addPathPatterns(OpenAPIConstant.WEB_PATH.concat("/**"))
|
||||
.order(1);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,97 @@
|
||||
package tech.powerjob.server.initializer;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.stereotype.Component;
|
||||
import tech.powerjob.common.utils.CommonUtils;
|
||||
import tech.powerjob.server.extension.LockService;
|
||||
import tech.powerjob.server.persistence.remote.model.SundryDO;
|
||||
import tech.powerjob.server.persistence.remote.repository.SundryRepository;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Date;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* 新系统初始化器
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2023/9/5
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class NewSystemInitializer implements CommandLineRunner {
|
||||
|
||||
|
||||
private static final String LOCK_PREFIX = "sys_init_lock_";
|
||||
|
||||
private static final int MAX_LOCK_TIME = 5000;
|
||||
|
||||
|
||||
@Resource
|
||||
private LockService lockService;
|
||||
@Resource
|
||||
private SundryRepository sundryRepository;
|
||||
@Resource
|
||||
private SystemInitializeService systemInitializeService;
|
||||
|
||||
private static final String SUNDRY_PKEY = "sys_initialize";
|
||||
|
||||
@Override
|
||||
public void run(String... args) throws Exception {
|
||||
clusterInit(SystemInitializeService.GOAL_INIT_LOCK, V -> {
|
||||
clusterInit(SystemInitializeService.GOAL_INIT_ADMIN, Void -> systemInitializeService.initAdmin());
|
||||
clusterInit(SystemInitializeService.GOAL_INIT_NAMESPACE, Void -> systemInitializeService.initNamespace());
|
||||
clusterInit(SystemInitializeService.GOAL_INIT_TEST_ENV, Void -> systemInitializeService.initTrialEnv());
|
||||
});
|
||||
}
|
||||
|
||||
private void clusterInit(String name, Consumer<Void> initFunc) {
|
||||
|
||||
Optional<SundryDO> sundryOpt = sundryRepository.findByPkeyAndSkey(SUNDRY_PKEY, name);
|
||||
if (sundryOpt.isPresent()) {
|
||||
log.info("[NewSystemInitializer] already initialized, skip: {}", name);
|
||||
return;
|
||||
}
|
||||
|
||||
String lockName = LOCK_PREFIX.concat(name);
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
|
||||
boolean lockStatus = lockService.tryLock(lockName, MAX_LOCK_TIME);
|
||||
|
||||
// 无论是否拿到锁,都重现检测一次,如果已完成初始化,则直接 return
|
||||
Optional<SundryDO> sundryOpt2 = sundryRepository.findByPkeyAndSkey(SUNDRY_PKEY, name);
|
||||
if (sundryOpt2.isPresent()) {
|
||||
log.info("[NewSystemInitializer] other server finished initialize, skip process: {}", name);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!lockStatus) {
|
||||
CommonUtils.easySleep(277);
|
||||
continue;
|
||||
}
|
||||
|
||||
log.info("[NewSystemInitializer] try to initialize: {}", name);
|
||||
initFunc.accept(null);
|
||||
log.info("[NewSystemInitializer] initialize [{}] successfully!", name);
|
||||
|
||||
// 写入初始化成功标记
|
||||
SundryDO sundryDO = new SundryDO();
|
||||
sundryDO.setPkey(SUNDRY_PKEY);
|
||||
sundryDO.setSkey(name);
|
||||
sundryDO.setContent("A");
|
||||
sundryDO.setGmtCreate(new Date());
|
||||
sundryRepository.saveAndFlush(sundryDO);
|
||||
log.info("[NewSystemInitializer] write initialized tag successfully: {}", sundryDO);
|
||||
|
||||
break;
|
||||
} finally {
|
||||
lockService.unlock(lockName);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
package tech.powerjob.server.initializer;
|
||||
|
||||
/**
|
||||
* 系统初始化服务
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2024/2/15
|
||||
*/
|
||||
public interface SystemInitializeService {
|
||||
|
||||
String GOAL_INIT_LOCK = "goal_init_system";
|
||||
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();
|
||||
|
||||
/**
|
||||
* 初始化 namespace
|
||||
*/
|
||||
void initNamespace();
|
||||
|
||||
/**
|
||||
* 初始化试用环境
|
||||
*/
|
||||
void initTrialEnv();
|
||||
}
|
||||
@ -0,0 +1,202 @@
|
||||
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.BeanUtils;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import tech.powerjob.common.serialize.JsonUtils;
|
||||
import tech.powerjob.common.utils.SysUtils;
|
||||
import tech.powerjob.server.auth.PowerJobUser;
|
||||
import tech.powerjob.server.auth.Role;
|
||||
import tech.powerjob.server.auth.RoleScope;
|
||||
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.constants.ExtensionKey;
|
||||
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.AppWebService;
|
||||
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.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.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 初始化 PowerJob 首次部署相关的内容
|
||||
* 为了可维护性足够高,统一使用 WEB 请求进行初始化,不直接操作底层,防止后续内部逻辑变更后出现问题
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2024/2/15
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
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;
|
||||
@Resource
|
||||
private PowerJobLoginService powerJobLoginService;
|
||||
@Resource
|
||||
private PowerJobPermissionService powerJobPermissionService;
|
||||
|
||||
private static final String SYSTEM_ADMIN_NAME = "ADMIN";
|
||||
|
||||
private static final String SYSTEM_DEFAULT_NAMESPACE = "default_namespace";
|
||||
|
||||
private static final String TRIAL_ENV_ACCOUNT_AND_PWD = "powerjob";
|
||||
|
||||
private static final String TEST_APP_NAME = "powerjob-worker-samples";
|
||||
private static final String TEST_APP_PWD = "powerjob123";
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackOn = Exception.class)
|
||||
public void initAdmin() {
|
||||
|
||||
String username = SYSTEM_ADMIN_NAME;
|
||||
String password = StringUtils.isEmpty(defaultAdminPassword) ? RandomStringUtils.randomAlphabetic(8) : defaultAdminPassword;
|
||||
|
||||
// 创建用户
|
||||
PowerJobUser powerJobUser = createUser(username, password, true);
|
||||
|
||||
// STEP3: 授予全局管理员权限
|
||||
powerJobPermissionService.grantRole(RoleScope.GLOBAL, AuthConstants.GLOBAL_ADMIN_TARGET_ID, powerJobUser.getId(), Role.ADMIN, null);
|
||||
log.info("[SystemInitializeService] GRANT ADMIN to user[{}] successfully!", powerJobUser);
|
||||
|
||||
// 循环10遍,强提醒用户,第一次使用必须更改 admin 密码
|
||||
for (int i = 0; i < 10; i++) {
|
||||
log.warn("[SystemInitializeService] The system has automatically created a super administrator account[username={},password={}], please log in and change the password immediately!", username, password);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackOn = Exception.class)
|
||||
public void initNamespace() {
|
||||
Optional<NamespaceDO> namespaceDOOptional = namespaceWebService.findByCode(SYSTEM_DEFAULT_NAMESPACE);
|
||||
if (namespaceDOOptional.isPresent()) {
|
||||
log.info("[SystemInitializeService] namespace[{}] already exist", SYSTEM_DEFAULT_NAMESPACE);
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
log.info("[SystemInitializeService] create default namespace successfully: {}", savedNamespaceDO);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackOn = Exception.class)
|
||||
public void initTrialEnv() {
|
||||
boolean trialEnv = SysUtils.isTrialEnv();
|
||||
if (!trialEnv) {
|
||||
return;
|
||||
}
|
||||
log.warn("[SystemInitializeService] [trialEnv] Detect trialEnv and start initializing the trial environment");
|
||||
|
||||
// 初始化测试账号
|
||||
List<String> testAccounts = Lists.newArrayList(TRIAL_ENV_ACCOUNT_AND_PWD);
|
||||
|
||||
List<PowerJobUser> testUsers = testAccounts.stream().map(un -> createUser(un, TRIAL_ENV_ACCOUNT_AND_PWD, false)).collect(Collectors.toList());
|
||||
List<Long> testUserIds = testUsers.stream().map(PowerJobUser::getId).collect(Collectors.toList());
|
||||
|
||||
log.info("[SystemInitializeService] [TestEnv] test user: {}", testUsers);
|
||||
|
||||
AppInfoDO testApp = createApp(TEST_APP_NAME, TEST_APP_PWD, SYSTEM_DEFAULT_NAMESPACE, testUserIds);
|
||||
log.info("[SystemInitializeService] [TestEnv] test app: {}", testApp);
|
||||
}
|
||||
private PowerJobUser createUser(String username, String password, boolean allowedChangePwd) {
|
||||
// STEP1: 创建 PWJB 用户
|
||||
|
||||
Optional<PwjbUserInfoDO> pwjbUserOpt = pwjbUserWebService.findByUsername(username);
|
||||
PwjbUserInfoDO savedPwjbUser = pwjbUserOpt.orElseGet(() -> {
|
||||
ModifyUserInfoRequest createUser = new ModifyUserInfoRequest();
|
||||
createUser.setUsername(username);
|
||||
createUser.setNick(username);
|
||||
createUser.setPassword(password);
|
||||
|
||||
if (!allowedChangePwd) {
|
||||
Map<String, Object> extra = Maps.newHashMap();
|
||||
extra.put(ExtensionKey.PwjbUser.allowedChangePwd, false);
|
||||
createUser.setExtra(JsonUtils.toJSONString(extra));
|
||||
}
|
||||
|
||||
log.info("[SystemInitializeService] [username:{}] create PWJB user by request: {}", username, createUser);
|
||||
PwjbUserInfoDO nPwjbUser = pwjbUserWebService.save(createUser);
|
||||
log.info("[SystemInitializeService] [username:{}] create PWJB user successfully: {}", username, nPwjbUser);
|
||||
return nPwjbUser;
|
||||
});
|
||||
log.info("[SystemInitializeService] [username:{}] => PwjbUser: {}", username, savedPwjbUser);
|
||||
|
||||
// STEP2: 创建 USER 对象
|
||||
Map<String, Object> params = Maps.newHashMap();
|
||||
params.put(AuthConstants.PARAM_KEY_USERNAME, username);
|
||||
params.put(AuthConstants.PARAM_KEY_PASSWORD, password);
|
||||
|
||||
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, String namespaceCode, List<Long> developers) {
|
||||
|
||||
ModifyAppInfoRequest modifyAppInfoRequest = new ModifyAppInfoRequest();
|
||||
|
||||
// 应用已存在则转为更新模式
|
||||
Optional<AppInfoDO> oldAppOpt = appWebService.findByAppName(appName);
|
||||
oldAppOpt.ifPresent(appInfoDO -> {
|
||||
BeanUtils.copyProperties(appInfoDO, modifyAppInfoRequest);
|
||||
modifyAppInfoRequest.setId(appInfoDO.getId());
|
||||
});
|
||||
|
||||
modifyAppInfoRequest.setAppName(appName);
|
||||
modifyAppInfoRequest.setTitle(appName);
|
||||
|
||||
modifyAppInfoRequest.setPassword(password);
|
||||
modifyAppInfoRequest.setNamespaceCode(namespaceCode);
|
||||
|
||||
modifyAppInfoRequest.setTags("test_app");
|
||||
|
||||
// 禁用靠密码成为管理员
|
||||
Map<String, Object> extra = Maps.newHashMap();
|
||||
extra.put(ExtensionKey.App.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;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,291 @@
|
||||
package tech.powerjob.server.openapi;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import tech.powerjob.client.module.AppAuthRequest;
|
||||
import tech.powerjob.client.module.AppAuthResult;
|
||||
import tech.powerjob.common.OpenAPIConstant;
|
||||
import tech.powerjob.common.enums.ErrorCodes;
|
||||
import tech.powerjob.common.enums.InstanceStatus;
|
||||
import tech.powerjob.common.exception.PowerJobException;
|
||||
import tech.powerjob.common.request.http.RunJobRequest;
|
||||
import tech.powerjob.common.request.http.SaveJobInfoRequest;
|
||||
import tech.powerjob.common.request.http.SaveWorkflowNodeRequest;
|
||||
import tech.powerjob.common.request.http.SaveWorkflowRequest;
|
||||
import tech.powerjob.common.request.query.InstancePageQuery;
|
||||
import tech.powerjob.common.request.query.JobInfoQuery;
|
||||
import tech.powerjob.common.response.*;
|
||||
import tech.powerjob.server.core.instance.InstanceService;
|
||||
import tech.powerjob.server.core.service.AppInfoService;
|
||||
import tech.powerjob.server.core.service.CacheService;
|
||||
import tech.powerjob.server.core.service.JobService;
|
||||
import tech.powerjob.server.core.workflow.WorkflowInstanceService;
|
||||
import tech.powerjob.server.core.workflow.WorkflowService;
|
||||
import tech.powerjob.server.openapi.security.OpenApiSecurityService;
|
||||
import tech.powerjob.server.persistence.remote.model.WorkflowInfoDO;
|
||||
import tech.powerjob.server.persistence.remote.model.WorkflowNodeInfoDO;
|
||||
import tech.powerjob.server.web.response.WorkflowInfoVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 开放接口(OpenAPI)控制器,对接 oms-client
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2020/4/15
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping(OpenAPIConstant.WEB_PATH)
|
||||
@RequiredArgsConstructor
|
||||
public class OpenAPIController {
|
||||
|
||||
private final AppInfoService appInfoService;
|
||||
|
||||
private final JobService jobService;
|
||||
|
||||
private final InstanceService instanceService;
|
||||
|
||||
private final WorkflowService workflowService;
|
||||
|
||||
private final WorkflowInstanceService workflowInstanceService;
|
||||
|
||||
private final OpenApiSecurityService openApiSecurityService;
|
||||
|
||||
private final CacheService cacheService;
|
||||
|
||||
|
||||
@PostMapping(OpenAPIConstant.ASSERT)
|
||||
public ResultDTO<Long> assertAppName(String appName, @RequestParam(required = false) String password) {
|
||||
return ResultDTO.success(appInfoService.assertApp(appName, password, null));
|
||||
}
|
||||
|
||||
/**
|
||||
* APP 鉴权
|
||||
* @param appAuthRequest 鉴权请求
|
||||
* @return 鉴权响应
|
||||
*/
|
||||
@PostMapping(OpenAPIConstant.AUTH_APP)
|
||||
public PowerResultDTO<AppAuthResult> auth(@RequestBody AppAuthRequest appAuthRequest) {
|
||||
try {
|
||||
return PowerResultDTO.s(openApiSecurityService.authAppByParam(appAuthRequest));
|
||||
} catch (PowerJobException pje) {
|
||||
PowerResultDTO<AppAuthResult> f = PowerResultDTO.f(pje.getMessage());
|
||||
f.setCode(pje.getCode());
|
||||
return f;
|
||||
} catch (Throwable t) {
|
||||
|
||||
log.error("[OpenAPIController] auth failed for request: {}", appAuthRequest, t);
|
||||
|
||||
PowerResultDTO<AppAuthResult> f = PowerResultDTO.f(ExceptionUtils.getMessage(t));
|
||||
f.setCode(ErrorCodes.SYSTEM_UNKNOWN_ERROR.getCode());
|
||||
return f;
|
||||
}
|
||||
}
|
||||
|
||||
/* ************* Job 区 ************* */
|
||||
|
||||
@PostMapping(OpenAPIConstant.SAVE_JOB)
|
||||
public ResultDTO<Long> saveJob(@RequestBody SaveJobInfoRequest request) {
|
||||
if (request.getId() != null) {
|
||||
checkJobIdValid(request.getId(), request.getAppId());
|
||||
}
|
||||
return ResultDTO.success(jobService.saveJob(request));
|
||||
}
|
||||
|
||||
@PostMapping(OpenAPIConstant.COPY_JOB)
|
||||
public ResultDTO<Long> copyJob(Long jobId) {
|
||||
return ResultDTO.success(jobService.copyJob(jobId).getId());
|
||||
}
|
||||
|
||||
@PostMapping(OpenAPIConstant.EXPORT_JOB)
|
||||
public ResultDTO<SaveJobInfoRequest> exportJob(Long jobId, Long appId) {
|
||||
checkJobIdValid(jobId, appId);
|
||||
return ResultDTO.success(jobService.exportJob(jobId));
|
||||
}
|
||||
|
||||
@PostMapping(OpenAPIConstant.FETCH_JOB)
|
||||
public ResultDTO<JobInfoDTO> fetchJob(Long jobId, Long appId) {
|
||||
checkJobIdValid(jobId, appId);
|
||||
return ResultDTO.success(jobService.fetchJob(jobId));
|
||||
}
|
||||
|
||||
@PostMapping(OpenAPIConstant.FETCH_ALL_JOB)
|
||||
public ResultDTO<List<JobInfoDTO>> fetchAllJob(Long appId) {
|
||||
return ResultDTO.success(jobService.fetchAllJob(appId));
|
||||
}
|
||||
|
||||
@PostMapping(OpenAPIConstant.QUERY_JOB)
|
||||
public ResultDTO<List<JobInfoDTO>> queryJob(@RequestBody JobInfoQuery powerQuery) {
|
||||
return ResultDTO.success(jobService.queryJob(powerQuery));
|
||||
}
|
||||
|
||||
@PostMapping(OpenAPIConstant.DELETE_JOB)
|
||||
public ResultDTO<Void> deleteJob(Long jobId, Long appId) {
|
||||
checkJobIdValid(jobId, appId);
|
||||
jobService.deleteJob(jobId);
|
||||
return ResultDTO.success(null);
|
||||
}
|
||||
|
||||
@PostMapping(OpenAPIConstant.DISABLE_JOB)
|
||||
public ResultDTO<Void> disableJob(Long jobId, Long appId) {
|
||||
checkJobIdValid(jobId, appId);
|
||||
jobService.disableJob(jobId);
|
||||
return ResultDTO.success(null);
|
||||
}
|
||||
|
||||
@PostMapping(OpenAPIConstant.ENABLE_JOB)
|
||||
public ResultDTO<Void> enableJob(Long jobId, Long appId) {
|
||||
checkJobIdValid(jobId, appId);
|
||||
jobService.enableJob(jobId);
|
||||
return ResultDTO.success(null);
|
||||
}
|
||||
|
||||
@PostMapping(OpenAPIConstant.RUN_JOB)
|
||||
public ResultDTO<Long> runJob(Long appId, Long jobId, @RequestParam(required = false) String instanceParams, @RequestParam(required = false) Long delay) {
|
||||
RunJobRequest request = new RunJobRequest().setAppId(appId).setJobId(jobId).setInstanceParams(instanceParams).setDelay(delay);
|
||||
return runJob2(request);
|
||||
}
|
||||
|
||||
@PostMapping(OpenAPIConstant.RUN_JOB2)
|
||||
public PowerResultDTO<Long> runJob2(@RequestBody RunJobRequest runJobRequest) {
|
||||
checkJobIdValid(runJobRequest.getJobId(), runJobRequest.getAppId());
|
||||
return PowerResultDTO.s(jobService.runJob(runJobRequest.getAppId(), runJobRequest));
|
||||
}
|
||||
|
||||
/* ************* Instance 区 ************* */
|
||||
|
||||
@PostMapping(OpenAPIConstant.STOP_INSTANCE)
|
||||
public ResultDTO<Void> stopInstance(Long instanceId, Long appId) {
|
||||
checkInstanceIdValid(instanceId, appId);
|
||||
instanceService.stopInstance(appId, instanceId);
|
||||
return ResultDTO.success(null);
|
||||
}
|
||||
|
||||
@PostMapping(OpenAPIConstant.CANCEL_INSTANCE)
|
||||
public ResultDTO<Void> cancelInstance(Long instanceId, Long appId) {
|
||||
checkInstanceIdValid(instanceId, appId);
|
||||
instanceService.cancelInstance(appId, instanceId);
|
||||
return ResultDTO.success(null);
|
||||
}
|
||||
|
||||
@PostMapping(OpenAPIConstant.RETRY_INSTANCE)
|
||||
public ResultDTO<Void> retryInstance(Long instanceId, Long appId) {
|
||||
checkInstanceIdValid(instanceId, appId);
|
||||
instanceService.retryInstance(appId, instanceId);
|
||||
return ResultDTO.success(null);
|
||||
}
|
||||
|
||||
@PostMapping(OpenAPIConstant.FETCH_INSTANCE_STATUS)
|
||||
public ResultDTO<Integer> fetchInstanceStatus(Long instanceId) {
|
||||
InstanceStatus instanceStatus = instanceService.getInstanceStatus(instanceId);
|
||||
return ResultDTO.success(instanceStatus.getV());
|
||||
}
|
||||
|
||||
@PostMapping(OpenAPIConstant.FETCH_INSTANCE_INFO)
|
||||
public ResultDTO<InstanceInfoDTO> fetchInstanceInfo(Long instanceId) {
|
||||
return ResultDTO.success(instanceService.getInstanceInfo(instanceId));
|
||||
}
|
||||
|
||||
@PostMapping(OpenAPIConstant.QUERY_INSTANCE)
|
||||
public ResultDTO<PageResult<InstanceInfoDTO>> queryInstance(@RequestBody InstancePageQuery powerQuery) {
|
||||
return ResultDTO.success(instanceService.queryInstanceInfo(powerQuery));
|
||||
}
|
||||
|
||||
/* ************* Workflow 区 ************* */
|
||||
|
||||
@PostMapping(OpenAPIConstant.SAVE_WORKFLOW)
|
||||
public ResultDTO<Long> saveWorkflow(@RequestBody SaveWorkflowRequest request) {
|
||||
return ResultDTO.success(workflowService.saveWorkflow(request));
|
||||
}
|
||||
|
||||
@PostMapping(OpenAPIConstant.COPY_WORKFLOW)
|
||||
public ResultDTO<Long> copy(Long workflowId, Long appId) {
|
||||
return ResultDTO.success(workflowService.copyWorkflow(workflowId, appId));
|
||||
}
|
||||
|
||||
|
||||
@PostMapping(OpenAPIConstant.FETCH_WORKFLOW)
|
||||
public ResultDTO<WorkflowInfoVO> fetchWorkflow(Long workflowId, Long appId) {
|
||||
WorkflowInfoDO workflowInfoDO = workflowService.fetchWorkflow(workflowId, appId);
|
||||
return ResultDTO.success(WorkflowInfoVO.from(workflowInfoDO));
|
||||
}
|
||||
|
||||
@PostMapping(OpenAPIConstant.DELETE_WORKFLOW)
|
||||
public ResultDTO<Void> deleteWorkflow(Long workflowId, Long appId) {
|
||||
workflowService.deleteWorkflow(workflowId, appId);
|
||||
return ResultDTO.success(null);
|
||||
}
|
||||
|
||||
@PostMapping(OpenAPIConstant.DISABLE_WORKFLOW)
|
||||
public ResultDTO<Void> disableWorkflow(Long workflowId, Long appId) {
|
||||
workflowService.disableWorkflow(workflowId, appId);
|
||||
return ResultDTO.success(null);
|
||||
}
|
||||
|
||||
@PostMapping(OpenAPIConstant.ENABLE_WORKFLOW)
|
||||
public ResultDTO<Void> enableWorkflow(Long workflowId, Long appId) {
|
||||
workflowService.enableWorkflow(workflowId, appId);
|
||||
return ResultDTO.success(null);
|
||||
}
|
||||
|
||||
@PostMapping(OpenAPIConstant.RUN_WORKFLOW)
|
||||
public ResultDTO<Long> runWorkflow(Long workflowId, Long appId, @RequestParam(required = false) String initParams, @RequestParam(required = false) Long delay) {
|
||||
return ResultDTO.success(workflowService.runWorkflow(workflowId, appId, initParams, delay));
|
||||
}
|
||||
|
||||
@PostMapping(OpenAPIConstant.SAVE_WORKFLOW_NODE)
|
||||
public ResultDTO<List<WorkflowNodeInfoDO>> saveWorkflowNode(@RequestBody List<SaveWorkflowNodeRequest> request) {
|
||||
return ResultDTO.success(workflowService.saveWorkflowNode(request));
|
||||
}
|
||||
|
||||
/* ************* Workflow Instance 区 ************* */
|
||||
|
||||
@PostMapping(OpenAPIConstant.STOP_WORKFLOW_INSTANCE)
|
||||
public ResultDTO<Void> stopWorkflowInstance(Long wfInstanceId, Long appId) {
|
||||
workflowInstanceService.stopWorkflowInstanceEntrance(wfInstanceId, appId);
|
||||
return ResultDTO.success(null);
|
||||
}
|
||||
|
||||
@PostMapping(OpenAPIConstant.RETRY_WORKFLOW_INSTANCE)
|
||||
public ResultDTO<Void> retryWorkflowInstance(Long wfInstanceId, Long appId) {
|
||||
workflowInstanceService.retryWorkflowInstance(wfInstanceId, appId);
|
||||
return ResultDTO.success(null);
|
||||
}
|
||||
|
||||
@PostMapping(OpenAPIConstant.MARK_WORKFLOW_NODE_AS_SUCCESS)
|
||||
public ResultDTO<Void> markWorkflowNodeAsSuccess(Long wfInstanceId, Long nodeId, Long appId) {
|
||||
workflowInstanceService.markNodeAsSuccess(appId, wfInstanceId, nodeId);
|
||||
return ResultDTO.success(null);
|
||||
}
|
||||
|
||||
@PostMapping(OpenAPIConstant.FETCH_WORKFLOW_INSTANCE_INFO)
|
||||
public ResultDTO<WorkflowInstanceInfoDTO> fetchWorkflowInstanceInfo(Long wfInstanceId, Long appId) {
|
||||
return ResultDTO.success(workflowInstanceService.fetchWorkflowInstanceInfo(wfInstanceId, appId));
|
||||
}
|
||||
|
||||
private void checkInstanceIdValid(Long instanceId, Long appId) {
|
||||
Long realAppId = cacheService.getAppIdByInstanceId(instanceId);
|
||||
if (realAppId == null) {
|
||||
throw new IllegalArgumentException("can't find instance by instanceId: " + instanceId);
|
||||
}
|
||||
if (appId.equals(realAppId)) {
|
||||
return;
|
||||
}
|
||||
throw new IllegalArgumentException("instance is not belong to the app whose appId is " + appId);
|
||||
}
|
||||
|
||||
private void checkJobIdValid(Long jobId, Long appId) {
|
||||
Long realAppId = cacheService.getAppIdByJobId(jobId);
|
||||
// 查不到,说明 jobId 不存在
|
||||
if (realAppId == null) {
|
||||
throw new IllegalArgumentException("can't find job by jobId: " + jobId);
|
||||
}
|
||||
// 不等,说明该job不属于该app,无权限操作
|
||||
if (!appId.equals(realAppId)) {
|
||||
throw new IllegalArgumentException("this job is not belong to the app whose appId is " + appId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,92 @@
|
||||
package tech.powerjob.server.openapi;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
import tech.powerjob.common.OmsConstant;
|
||||
import tech.powerjob.common.OpenAPIConstant;
|
||||
import tech.powerjob.common.exception.PowerJobException;
|
||||
import tech.powerjob.common.response.PowerResultDTO;
|
||||
import tech.powerjob.common.serialize.JsonUtils;
|
||||
import tech.powerjob.server.openapi.security.OpenApiSecurityService;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* OpenAPI 拦截器
|
||||
*
|
||||
* @author 程序帕鲁
|
||||
* @since 2024/2/19
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class OpenApiInterceptor implements HandlerInterceptor {
|
||||
|
||||
@Resource
|
||||
private OpenApiSecurityService openApiSecurityService;
|
||||
|
||||
/**
|
||||
* 4.x 及前序版本的 OpenAPI 均为携带 auth 的必要参数,直接开启鉴权功能会导致之前的服务全部报错
|
||||
* 因此提供功能开关给到使用者,若无安全影响,可展示关闭鉴权功能,等 client 升级完毕后再打开鉴权
|
||||
*/
|
||||
@Value("${oms.auth.openapi.enable:false}")
|
||||
private boolean enableOpenApiAuth;
|
||||
|
||||
private static final Set<String> IGNORE_OPEN_API_PATH = Sets.newHashSet(OpenAPIConstant.ASSERT, OpenAPIConstant.AUTH_APP);
|
||||
|
||||
@Override
|
||||
public boolean preHandle(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler) throws Exception {
|
||||
|
||||
if (!enableOpenApiAuth) {
|
||||
response.addHeader(OpenAPIConstant.RESPONSE_HEADER_AUTH_STATUS, Boolean.TRUE.toString());
|
||||
return true;
|
||||
}
|
||||
|
||||
// 鉴权类请求跳过拦截
|
||||
String requestURI = request.getRequestURI();
|
||||
for (String endPath : IGNORE_OPEN_API_PATH) {
|
||||
if (requestURI.endsWith(endPath)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
openApiSecurityService.authAppByToken(request);
|
||||
response.addHeader(OpenAPIConstant.RESPONSE_HEADER_AUTH_STATUS, Boolean.TRUE.toString());
|
||||
} catch (PowerJobException pje) {
|
||||
response.addHeader(OpenAPIConstant.RESPONSE_HEADER_AUTH_STATUS, Boolean.FALSE.toString());
|
||||
writeResponse(PowerResultDTO.f(pje), response);
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
|
||||
writeResponse(PowerResultDTO.f(e), response);
|
||||
|
||||
log.error("[OpenApiInterceptor] unknown exception when auth app by token", e);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private void writeResponse( PowerResultDTO<Object> powerResult, HttpServletResponse response) {
|
||||
|
||||
// 设置响应的 Content-Type
|
||||
response.setContentType(OmsConstant.JSON_MEDIA_TYPE);
|
||||
|
||||
// 将 JSON 写入响应
|
||||
PrintWriter writer = response.getWriter();
|
||||
writer.write(JsonUtils.toJSONString(powerResult));
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
package tech.powerjob.server.openapi.security;
|
||||
|
||||
import tech.powerjob.client.module.AppAuthRequest;
|
||||
import tech.powerjob.client.module.AppAuthResult;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
* OPENAPI 安全服务
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2024/2/19
|
||||
*/
|
||||
public interface OpenApiSecurityService {
|
||||
|
||||
/**
|
||||
* APP 纬度请求的鉴权 & 验证
|
||||
* @param appAuthRequest 请求参数
|
||||
* @return token
|
||||
*/
|
||||
AppAuthResult authAppByParam(AppAuthRequest appAuthRequest);
|
||||
|
||||
/**
|
||||
* APP 纬度请求的鉴权 & 验证
|
||||
* @param httpServletRequest http 原始请求
|
||||
*/
|
||||
void authAppByToken(HttpServletRequest httpServletRequest);
|
||||
}
|
||||
@ -0,0 +1,106 @@
|
||||
package tech.powerjob.server.openapi.security;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections4.MapUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
import tech.powerjob.client.module.AppAuthRequest;
|
||||
import tech.powerjob.client.module.AppAuthResult;
|
||||
import tech.powerjob.common.OpenAPIConstant;
|
||||
import tech.powerjob.common.enums.ErrorCodes;
|
||||
import tech.powerjob.common.exception.PowerJobException;
|
||||
import tech.powerjob.server.auth.common.utils.HttpServletUtils;
|
||||
import tech.powerjob.server.auth.jwt.JwtService;
|
||||
import tech.powerjob.server.auth.jwt.ParseResult;
|
||||
import tech.powerjob.server.core.service.AppInfoService;
|
||||
import tech.powerjob.server.persistence.remote.model.AppInfoDO;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* OpenApiSecurityService
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2024/2/19
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class OpenApiSecurityServiceImpl implements OpenApiSecurityService {
|
||||
|
||||
@Resource
|
||||
private JwtService jwtService;
|
||||
@Resource
|
||||
private AppInfoService appInfoService;
|
||||
|
||||
private static final String JWT_KEY_APP_ID = "appId";
|
||||
private static final String JWT_KEY_APP_PASSWORD = "password";
|
||||
private static final String JWT_KEY_ENCRYPT_TYPE = "encryptType";
|
||||
|
||||
@Override
|
||||
public void authAppByToken(HttpServletRequest httpServletRequest) {
|
||||
|
||||
String token = HttpServletUtils.fetchFromHeader(OpenAPIConstant.REQUEST_HEADER_ACCESS_TOKEN, httpServletRequest);
|
||||
String appIdFromHeader = HttpServletUtils.fetchFromHeader(OpenAPIConstant.REQUEST_HEADER_APP_ID, httpServletRequest);
|
||||
|
||||
if (StringUtils.isEmpty(appIdFromHeader)) {
|
||||
throw new PowerJobException(ErrorCodes.INVALID_REQUEST, "lack_of_appId_in_header");
|
||||
}
|
||||
|
||||
if (StringUtils.isEmpty(token)) {
|
||||
throw new PowerJobException(ErrorCodes.OPEN_API_AUTH_FAILED, "token_is_empty");
|
||||
}
|
||||
|
||||
ParseResult parseResult = jwtService.parse(token, null);
|
||||
switch (parseResult.getStatus()) {
|
||||
case EXPIRED:
|
||||
throw new PowerJobException(ErrorCodes.TOKEN_EXPIRED, parseResult.getMsg());
|
||||
case FAILED:
|
||||
throw new PowerJobException(ErrorCodes.INVALID_TOKEN, parseResult.getMsg());
|
||||
}
|
||||
|
||||
Map<String, Object> jwtResult = parseResult.getResult();
|
||||
|
||||
Long appIdFromJwt = MapUtils.getLong(jwtResult, JWT_KEY_APP_ID);
|
||||
String passwordFromJwt = MapUtils.getString(jwtResult, JWT_KEY_APP_PASSWORD);
|
||||
String encryptType = MapUtils.getString(jwtResult, JWT_KEY_ENCRYPT_TYPE);
|
||||
|
||||
// 校验 appId 一致性
|
||||
if (!StringUtils.equals(appIdFromHeader, String.valueOf(appIdFromJwt))) {
|
||||
throw new PowerJobException(ErrorCodes.INVALID_REQUEST, "Inconsistent_appId_from_token_and_header");
|
||||
}
|
||||
|
||||
// 此处不考虑改密码后的缓存时间,毕竟只要改了密码,一定会报错。换言之 OpenAPI 模式下,密码不可更改
|
||||
Optional<AppInfoDO> appInfoOpt = appInfoService.findById(appIdFromJwt, true);
|
||||
if (!appInfoOpt.isPresent()) {
|
||||
throw new PowerJobException(ErrorCodes.INVALID_APP, "can_not_find_app");
|
||||
}
|
||||
|
||||
appInfoService.assertApp(appInfoOpt.get(), passwordFromJwt, encryptType);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public AppAuthResult authAppByParam(AppAuthRequest appAuthRequest) {
|
||||
|
||||
String appName = appAuthRequest.getAppName();
|
||||
String encryptedPassword = appAuthRequest.getEncryptedPassword();
|
||||
|
||||
Long appId = appInfoService.assertApp(appName, encryptedPassword, appAuthRequest.getEncryptType());
|
||||
|
||||
Map<String, Object> jwtBody = Maps.newHashMap();
|
||||
jwtBody.put(JWT_KEY_APP_ID, appId);
|
||||
jwtBody.put(JWT_KEY_APP_PASSWORD, encryptedPassword);
|
||||
jwtBody.put(JWT_KEY_ENCRYPT_TYPE, appAuthRequest.getEncryptType());
|
||||
|
||||
AppAuthResult appAuthResult = new AppAuthResult();
|
||||
|
||||
appAuthResult.setAppId(appId);
|
||||
appAuthResult.setToken(jwtService.build(jwtBody, null));
|
||||
|
||||
return appAuthResult;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
package tech.powerjob.server.support;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
import tech.powerjob.server.common.aware.ServerInfoAware;
|
||||
import tech.powerjob.server.common.module.ServerInfo;
|
||||
import tech.powerjob.server.remote.server.self.ServerInfoService;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* ServerInfoAwareProcessor
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2022/9/12
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class ServerInfoAwareProcessor {
|
||||
|
||||
public ServerInfoAwareProcessor(ServerInfoService serverInfoService, List<ServerInfoAware> awareList) {
|
||||
final ServerInfo serverInfo = serverInfoService.fetchCurrentServerInfo();
|
||||
log.info("[ServerInfoAwareProcessor] current server info: {}", serverInfo);
|
||||
awareList.forEach(aware -> {
|
||||
aware.setServerInfo(serverInfo);
|
||||
log.info("[ServerInfoAwareProcessor] set ServerInfo for: {} successfully", aware);
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
package tech.powerjob.server.support;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
import tech.powerjob.server.remote.aware.TransportServiceAware;
|
||||
import tech.powerjob.server.remote.transporter.TransportService;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* TransportServiceAwareProcessor
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2023/3/4
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class TransportServiceAwareProcessor {
|
||||
|
||||
public TransportServiceAwareProcessor(TransportService transportService, List<TransportServiceAware> transportServiceAwares) {
|
||||
log.info("[TransportServiceAwareProcessor] current transportService: {}", transportService);
|
||||
transportServiceAwares.forEach(aware -> {
|
||||
aware.setTransportService(transportService);
|
||||
log.info("[TransportServiceAwareProcessor] set transportService for: {} successfully", aware);
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
package tech.powerjob.server.web;
|
||||
|
||||
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;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* AppWebService
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2024/12/8
|
||||
*/
|
||||
public interface AppWebService {
|
||||
|
||||
AppInfoDO save(ModifyAppInfoRequest request);
|
||||
|
||||
void delete(Long id);
|
||||
|
||||
Optional<AppInfoDO> findByAppName(String appName);
|
||||
|
||||
Page<AppInfoDO> list(QueryAppInfoRequest queryAppInfoRequest);
|
||||
}
|
||||
@ -0,0 +1,46 @@
|
||||
package tech.powerjob.server.web;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.messaging.handler.annotation.support.MethodArgumentTypeMismatchException;
|
||||
import org.springframework.web.HttpRequestMethodNotSupportedException;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import tech.powerjob.common.exception.PowerJobException;
|
||||
import tech.powerjob.common.response.PowerResultDTO;
|
||||
|
||||
/**
|
||||
* 统一处理 web 层异常信息
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2020/3/30
|
||||
*/
|
||||
@Slf4j
|
||||
@ControllerAdvice
|
||||
public class ControllerExceptionHandler {
|
||||
|
||||
@ResponseBody
|
||||
@ExceptionHandler(Exception.class)
|
||||
public PowerResultDTO<Void> exceptionHandler(Exception e) {
|
||||
|
||||
PowerResultDTO<Void> ret = PowerResultDTO.f(ExceptionUtils.getMessage(e));
|
||||
|
||||
// 不是所有异常都需要打印完整堆栈,后续可以定义内部的Exception,便于判断
|
||||
if (e instanceof PowerJobException) {
|
||||
ret.setCode(((PowerJobException) e).getCode());
|
||||
log.warn("[ControllerException] PowerJobException, message is {}.", e.getMessage());
|
||||
} else if (e instanceof IllegalArgumentException) {
|
||||
log.warn("[ControllerException] http request failed due to IllegalArgument, message is {}.", e.getMessage());
|
||||
} else if (e instanceof HttpMessageNotReadableException || e instanceof MethodArgumentTypeMismatchException) {
|
||||
log.warn("[ControllerException] invalid http request params, exception is {}.", e.getMessage());
|
||||
} else if (e instanceof HttpRequestMethodNotSupportedException) {
|
||||
log.warn("[ControllerException] invalid http request method, exception is {}.", e.getMessage());
|
||||
} else {
|
||||
log.error("[ControllerException] http request failed.", e);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,101 @@
|
||||
package tech.powerjob.server.web;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.google.common.collect.Lists;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Before;
|
||||
import org.aspectj.lang.annotation.Pointcut;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import tech.powerjob.server.common.utils.AOPUtils;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 使用AOP记录访问日志
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2020/6/5
|
||||
*/
|
||||
@Aspect
|
||||
@Component
|
||||
@Slf4j(topic = "WEB_LOG")
|
||||
public class WebLogAspect {
|
||||
|
||||
/**
|
||||
* 定义切入点
|
||||
* 第一个*:标识所有返回类型
|
||||
* 字母路径:包路径
|
||||
* 两个点..:当前包以及子包
|
||||
* 第二个*:所有的类
|
||||
* 第三个*:所有的方法
|
||||
* 最后的两个点:所有类型的参数
|
||||
*/
|
||||
@Pointcut("execution(public * tech.powerjob.server.web.controller..*.*(..))")
|
||||
public void include() {
|
||||
}
|
||||
|
||||
@Pointcut("execution(public * tech.powerjob.server.web.controller.ServerController.*(..))")
|
||||
public void exclude() {
|
||||
}
|
||||
|
||||
@Pointcut("include() && !exclude()")
|
||||
public void webLog() {
|
||||
}
|
||||
|
||||
@Before("webLog()")
|
||||
public void doBefore(JoinPoint joinPoint) {
|
||||
try {
|
||||
// 获取请求域
|
||||
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
||||
if (requestAttributes == null) {
|
||||
return;
|
||||
}
|
||||
HttpServletRequest request = requestAttributes.getRequest();
|
||||
|
||||
|
||||
String classNameMini = AOPUtils.parseRealClassName(joinPoint);
|
||||
String classMethod = classNameMini + "." + joinPoint.getSignature().getName();
|
||||
|
||||
// 排除特殊类
|
||||
|
||||
// 192.168.1.1|POST|com.xxx.xxx.save|请求参数
|
||||
log.info("{}|{}|{}|{}", request.getRemoteAddr(), request.getMethod(), classMethod, stringify(joinPoint.getArgs()));
|
||||
}catch (Exception e) {
|
||||
// just for safe
|
||||
log.error("[WebLogAspect] aop occur exception, please concat @KFCFans to fix the bug!", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 序列化请求对象,需要特殊处理无法序列化的对象(HttpServletRequest/HttpServletResponse)
|
||||
* @param args Web请求参数
|
||||
* @return JSON字符串
|
||||
*/
|
||||
private static String stringify(Object[] args) {
|
||||
if (ArrayUtils.isEmpty(args)) {
|
||||
return null;
|
||||
}
|
||||
List<Object> objList = Lists.newLinkedList();
|
||||
for (Object obj : args) {
|
||||
if (obj instanceof HttpServletRequest || obj instanceof HttpServletResponse) {
|
||||
break;
|
||||
}
|
||||
// FatJar
|
||||
if (obj instanceof MultipartFile || obj instanceof Resource) {
|
||||
break;
|
||||
}
|
||||
|
||||
objList.add(obj);
|
||||
}
|
||||
return JSONObject.toJSONString(objList);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,185 @@
|
||||
package tech.powerjob.server.web.controller;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.collections4.MapUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.data.domain.Page;
|
||||
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.PowerJobException;
|
||||
import tech.powerjob.common.response.ResultDTO;
|
||||
import tech.powerjob.common.serialize.JsonUtils;
|
||||
import tech.powerjob.common.utils.CommonUtils;
|
||||
import tech.powerjob.server.auth.Permission;
|
||||
import tech.powerjob.server.auth.Role;
|
||||
import tech.powerjob.server.auth.RoleScope;
|
||||
import tech.powerjob.server.auth.common.AuthConstants;
|
||||
import tech.powerjob.server.auth.common.utils.AuthHeaderUtils;
|
||||
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.constants.ExtensionKey;
|
||||
import tech.powerjob.server.core.service.AppInfoService;
|
||||
import tech.powerjob.server.persistence.PageResult;
|
||||
import tech.powerjob.server.persistence.remote.model.AppInfoDO;
|
||||
import tech.powerjob.server.persistence.remote.model.NamespaceDO;
|
||||
import tech.powerjob.server.web.AppWebService;
|
||||
import tech.powerjob.server.web.converter.NamespaceConverter;
|
||||
import tech.powerjob.server.web.request.AppAssertRequest;
|
||||
import tech.powerjob.server.web.request.ComponentUserRoleInfo;
|
||||
import tech.powerjob.server.web.request.ModifyAppInfoRequest;
|
||||
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.NamespaceWebService;
|
||||
import tech.powerjob.server.web.service.UserWebService;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* AppName Controller
|
||||
* vue axios 的POST请求必须使用 @RequestBody 接收
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2020/4/1
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/appInfo")
|
||||
@RequiredArgsConstructor
|
||||
public class AppInfoController {
|
||||
|
||||
private final AppWebService appWebService;
|
||||
|
||||
private final WebAuthService webAuthService;
|
||||
|
||||
private final UserWebService userWebService;
|
||||
|
||||
private final AppInfoService appInfoService;
|
||||
|
||||
private final NamespaceWebService namespaceWebService;
|
||||
|
||||
@PostMapping("/save")
|
||||
@ApiPermission(name = "App-Save", roleScope = RoleScope.APP, dynamicPermissionPlugin = ModifyOrCreateDynamicPermission.class, grandPermissionPlugin = SaveAppGrantPermissionPlugin.class)
|
||||
public ResultDTO<AppInfoVO> saveAppInfo(@RequestBody ModifyAppInfoRequest req, HttpServletRequest hsr) {
|
||||
|
||||
// 安全注入攻击的场景,不信任传参
|
||||
if (req.getId() != null) {
|
||||
req.setId(Long.valueOf(AuthHeaderUtils.fetchAppId(hsr)));
|
||||
}
|
||||
|
||||
AppInfoDO savedAppInfo = appWebService.save(req);
|
||||
|
||||
return ResultDTO.success(convert(Lists.newArrayList(savedAppInfo), false).get(0));
|
||||
}
|
||||
|
||||
@PostMapping("/delete")
|
||||
@ApiPermission(name = "App-Delete", roleScope = RoleScope.APP, requiredPermission = Permission.SU)
|
||||
public ResultDTO<Void> deleteApp(HttpServletRequest hsr) {
|
||||
|
||||
Long appId = Long.valueOf(AuthHeaderUtils.fetchAppId(hsr));
|
||||
log.warn("[AppInfoController] try to delete app: {}", appId);
|
||||
|
||||
appWebService.delete(appId);
|
||||
log.warn("[AppInfoController] delete app[id={}] successfully!", appId);
|
||||
return ResultDTO.success(null);
|
||||
}
|
||||
|
||||
@PostMapping("/list")
|
||||
@ApiPermission(name = "App-List", roleScope = RoleScope.APP, requiredPermission = Permission.NONE)
|
||||
public ResultDTO<PageResult<AppInfoVO>> listAppInfoByQuery(@RequestBody QueryAppInfoRequest queryAppInfoRequest) {
|
||||
|
||||
Page<AppInfoDO> pageAppInfoResult = appWebService.list(queryAppInfoRequest);
|
||||
PageResult<AppInfoVO> pageRet = new PageResult<>(pageAppInfoResult);
|
||||
|
||||
List<AppInfoDO> appInfoDos = pageAppInfoResult.get().collect(Collectors.toList());
|
||||
pageRet.setData(convert(appInfoDos, true));
|
||||
|
||||
return ResultDTO.success(pageRet);
|
||||
}
|
||||
|
||||
@PostMapping("/becomeAdmin")
|
||||
@ApiPermission(name = "App-BecomeAdmin", roleScope = RoleScope.GLOBAL, requiredPermission = Permission.NONE)
|
||||
public ResultDTO<Void> becomeAdminByAppNameAndPassword(@RequestBody AppAssertRequest appAssertRequest) {
|
||||
String appName = appAssertRequest.getAppName();
|
||||
|
||||
Optional<AppInfoDO> appOpt = appWebService.findByAppName(appName);
|
||||
if (!appOpt.isPresent()) {
|
||||
throw new PowerJobException(ErrorCodes.ILLEGAL_ARGS_ERROR, "can't find appInfo by appName: " + appName);
|
||||
}
|
||||
|
||||
String appExtra = appOpt.get().getExtra();
|
||||
if (StringUtils.isNotBlank(appExtra)) {
|
||||
Map<String, Object> appExtraMap = JsonUtils.parseMap(appExtra);
|
||||
Boolean allowedBecomeAdminByPassword = MapUtils.getBoolean(appExtraMap, ExtensionKey.App.allowedBecomeAdminByPassword, true);
|
||||
if (!allowedBecomeAdminByPassword) {
|
||||
throw new PowerJobException(ErrorCodes.OPERATION_NOT_PERMITTED, "allowedBecomeAdminByPassword=false");
|
||||
}
|
||||
}
|
||||
|
||||
Long appId = appInfoService.assertApp(appName, appAssertRequest.getPassword(), appAssertRequest.getEncryptType());
|
||||
|
||||
Map<String, Object> extra = Maps.newHashMap();
|
||||
extra.put("source", "becomeAdminByAppNameAndPassword");
|
||||
|
||||
webAuthService.grantRole2LoginUser(RoleScope.APP, appId, Role.ADMIN, JsonUtils.toJSONString(extra));
|
||||
|
||||
return ResultDTO.success(null);
|
||||
}
|
||||
|
||||
private List<AppInfoVO> convert(List<AppInfoDO> data, boolean fillDetail) {
|
||||
if (CollectionUtils.isEmpty(data)) {
|
||||
return Lists.newLinkedList();
|
||||
}
|
||||
|
||||
// app 界面使用频率不高,数据库操作 rt 也不会太长,展示不考虑性能问题,简单期间串行补全
|
||||
return data.stream().map(appInfoDO -> {
|
||||
AppInfoVO appInfoVO = new AppInfoVO();
|
||||
BeanUtils.copyProperties(appInfoDO, appInfoVO);
|
||||
|
||||
appInfoVO.setGmtCreateStr(CommonUtils.formatTime(appInfoDO.getGmtCreate()));
|
||||
appInfoVO.setGmtModifiedStr(CommonUtils.formatTime(appInfoDO.getGmtModified()));
|
||||
|
||||
if (fillDetail) {
|
||||
// 人员面板
|
||||
ComponentUserRoleInfo componentUserRoleInfo = webAuthService.fetchComponentUserRoleInfo(RoleScope.APP, appInfoDO.getId());
|
||||
appInfoVO.setComponentUserRoleInfo(componentUserRoleInfo);
|
||||
|
||||
// 密码
|
||||
boolean hasPermission = webAuthService.hasPermission(RoleScope.APP, appInfoDO.getId(), Permission.READ);
|
||||
String originPassword = appInfoService.fetchOriginAppPassword(appInfoDO);
|
||||
appInfoVO.setPassword(hasPermission ? originPassword : AuthConstants.TIPS_NO_PERMISSION_TO_SEE);
|
||||
|
||||
// namespace
|
||||
Optional<NamespaceDO> namespaceOpt = namespaceWebService.findById(appInfoDO.getNamespaceId());
|
||||
if (namespaceOpt.isPresent()) {
|
||||
NamespaceBaseVO baseNamespace = NamespaceConverter.do2BaseVo(namespaceOpt.get());
|
||||
appInfoVO.setNamespace(baseNamespace);
|
||||
appInfoVO.setNamespaceName(baseNamespace.getName());
|
||||
}
|
||||
|
||||
// user 信息
|
||||
appInfoVO.setCreatorShowName(userWebService.fetchBaseUserInfo(appInfoDO.getCreator()).map(UserBaseVO::getShowName).orElse(null));
|
||||
appInfoVO.setModifierShowName(userWebService.fetchBaseUserInfo(appInfoDO.getModifier()).map(UserBaseVO::getShowName).orElse(null));
|
||||
|
||||
}
|
||||
|
||||
return appInfoVO;
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,123 @@
|
||||
package tech.powerjob.server.web.controller;
|
||||
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import tech.powerjob.common.response.ResultDTO;
|
||||
import tech.powerjob.common.utils.CollectionUtils;
|
||||
import tech.powerjob.server.auth.Permission;
|
||||
import tech.powerjob.server.auth.PowerJobUser;
|
||||
import tech.powerjob.server.auth.RoleScope;
|
||||
import tech.powerjob.server.auth.common.AuthConstants;
|
||||
import tech.powerjob.server.auth.interceptor.ApiPermission;
|
||||
import tech.powerjob.server.auth.login.LoginTypeInfo;
|
||||
import tech.powerjob.server.auth.service.WebAuthService;
|
||||
import tech.powerjob.server.auth.service.login.LoginRequest;
|
||||
import tech.powerjob.server.auth.service.login.PowerJobLoginService;
|
||||
import tech.powerjob.server.web.request.ComponentUserRoleInfo;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 登录 & 权限相关
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2023/4/16
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/auth")
|
||||
public class AuthController {
|
||||
|
||||
@Resource
|
||||
private WebAuthService webAuthService;
|
||||
@Resource
|
||||
private PowerJobLoginService powerJobLoginService;
|
||||
|
||||
@GetMapping("/supportLoginTypes")
|
||||
public ResultDTO<List<LoginTypeInfo>> listSupportLoginTypes() {
|
||||
return ResultDTO.success(powerJobLoginService.fetchSupportLoginTypes());
|
||||
}
|
||||
|
||||
@GetMapping("/thirdPartyLoginUrl")
|
||||
public ResultDTO<String> getThirdPartyLoginUrl(String type, HttpServletRequest request) {
|
||||
String url = powerJobLoginService.fetchThirdPartyLoginUrl(type, request);
|
||||
return ResultDTO.success(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* 第三方账号体系回调登录接口,eg, 接受钉钉登录回调
|
||||
* @param httpServletRequest 请求
|
||||
* @param httpServletResponse 响应
|
||||
* @return 登录结果
|
||||
*/
|
||||
@RequestMapping(value = "/thirdPartyLoginCallback", method = {RequestMethod.GET, RequestMethod.POST})
|
||||
public ResultDTO<PowerJobUser> loginCallback(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) {
|
||||
|
||||
LoginRequest loginContext = new LoginRequest().setHttpServletRequest(httpServletRequest);
|
||||
|
||||
// 常见登录组件的标准规范(钉钉、企业微信、飞书),第三方原样透传。开发者在对接第三方登录体系时,可能需要修改此处,将 type 回填
|
||||
final String state = httpServletRequest.getParameter("state");
|
||||
loginContext.setLoginType(state);
|
||||
|
||||
final PowerJobUser powerJobUser = powerJobLoginService.doLogin(loginContext);
|
||||
fillJwt4LoginUser(powerJobUser, httpServletResponse);
|
||||
|
||||
return ResultDTO.success(powerJobUser);
|
||||
}
|
||||
|
||||
/**
|
||||
* 第三方账号体系直接登录接口,eg, 接受 PowerJob 自带账号密码体系的登录请求
|
||||
* @param loginRequest 登录请求
|
||||
* @param httpServletResponse 响应
|
||||
* @return 登录结果
|
||||
*/
|
||||
@PostMapping("/thirdPartyLoginDirect")
|
||||
public ResultDTO<PowerJobUser> selfLogin(@RequestBody LoginRequest loginRequest, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) {
|
||||
loginRequest.setHttpServletRequest(httpServletRequest);
|
||||
try {
|
||||
final PowerJobUser powerJobUser = powerJobLoginService.doLogin(loginRequest);
|
||||
if (powerJobUser == null) {
|
||||
return ResultDTO.failed("USER_NOT_FOUND");
|
||||
}
|
||||
fillJwt4LoginUser(powerJobUser, httpServletResponse);
|
||||
return ResultDTO.success(powerJobUser);
|
||||
} catch (Exception e) {
|
||||
return ResultDTO.failed(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping(value = "/ifLogin")
|
||||
public ResultDTO<PowerJobUser> ifLogin(HttpServletRequest httpServletRequest) {
|
||||
final Optional<PowerJobUser> powerJobUser = powerJobLoginService.ifLogin(httpServletRequest);
|
||||
return powerJobUser.map(ResultDTO::success).orElseGet(() -> ResultDTO.success(null));
|
||||
}
|
||||
|
||||
/* ****************** 授权相关 ****************** */
|
||||
|
||||
@GetMapping("/listGlobalAdmin")
|
||||
public ResultDTO<List<Long>> listGlobalAdmin() {
|
||||
// 全局只设置超级管理员权限
|
||||
ComponentUserRoleInfo componentUserRoleInfo = webAuthService.fetchComponentUserRoleInfo(RoleScope.GLOBAL, AuthConstants.GLOBAL_ADMIN_TARGET_ID);
|
||||
return ResultDTO.success(componentUserRoleInfo.getAdmin());
|
||||
}
|
||||
|
||||
@PostMapping("/saveGlobalAdmin")
|
||||
@ApiPermission(name = "Auth-SaveGlobalAdmin", roleScope = RoleScope.GLOBAL, requiredPermission = Permission.SU)
|
||||
public ResultDTO<Void> saveGlobalAdmin(@RequestBody ComponentUserRoleInfo componentUserRoleInfo) {
|
||||
|
||||
if (CollectionUtils.isEmpty(componentUserRoleInfo.getAdmin())) {
|
||||
throw new IllegalArgumentException("At least one super administrator is required!");
|
||||
}
|
||||
|
||||
webAuthService.processPermissionOnSave(RoleScope.GLOBAL, AuthConstants.GLOBAL_ADMIN_TARGET_ID, componentUserRoleInfo);
|
||||
|
||||
return ResultDTO.success(null);
|
||||
}
|
||||
|
||||
private void fillJwt4LoginUser(PowerJobUser powerJobUser, HttpServletResponse httpServletResponse) {
|
||||
httpServletResponse.addCookie(new Cookie(AuthConstants.JWT_NAME, powerJobUser.getJwtToken()));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,151 @@
|
||||
package tech.powerjob.server.web.controller;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.time.DateFormatUtils;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import tech.powerjob.common.OmsConstant;
|
||||
import tech.powerjob.common.enums.SwitchableStatus;
|
||||
import tech.powerjob.common.response.ResultDTO;
|
||||
import tech.powerjob.server.auth.Permission;
|
||||
import tech.powerjob.server.auth.RoleScope;
|
||||
import tech.powerjob.server.auth.common.utils.AuthHeaderUtils;
|
||||
import tech.powerjob.server.auth.interceptor.ApiPermission;
|
||||
import tech.powerjob.server.common.constants.ContainerSourceType;
|
||||
import tech.powerjob.server.common.utils.OmsFileUtils;
|
||||
import tech.powerjob.server.core.container.ContainerService;
|
||||
import tech.powerjob.server.core.container.ContainerTemplateGenerator;
|
||||
import tech.powerjob.server.persistence.remote.model.AppInfoDO;
|
||||
import tech.powerjob.server.persistence.remote.model.ContainerInfoDO;
|
||||
import tech.powerjob.server.persistence.remote.repository.AppInfoRepository;
|
||||
import tech.powerjob.server.persistence.remote.repository.ContainerInfoRepository;
|
||||
import tech.powerjob.server.web.request.GenerateContainerTemplateRequest;
|
||||
import tech.powerjob.server.web.request.SaveContainerInfoRequest;
|
||||
import tech.powerjob.server.web.response.ContainerInfoVO;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 容器信息控制层
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2020/5/15
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/container")
|
||||
public class ContainerController {
|
||||
|
||||
private final ContainerService containerService;
|
||||
|
||||
private final AppInfoRepository appInfoRepository;
|
||||
|
||||
private final ContainerInfoRepository containerInfoRepository;
|
||||
|
||||
public ContainerController(ContainerService containerService, AppInfoRepository appInfoRepository, ContainerInfoRepository containerInfoRepository) {
|
||||
this.containerService = containerService;
|
||||
this.appInfoRepository = appInfoRepository;
|
||||
this.containerInfoRepository = containerInfoRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* 暴露给 worker 的下载端口,制品本身 version 不可枚举,不单独鉴权
|
||||
* 如果对此有安全性需求,可自行实现加密鉴权逻辑,或者干脆走自己的下载通道下载制品
|
||||
* @param version 容器版本
|
||||
* @param response 响应
|
||||
* @throws IOException 异常
|
||||
*/
|
||||
@GetMapping("/downloadJar")
|
||||
public void downloadJar(String version, HttpServletResponse response) throws IOException {
|
||||
File file = containerService.fetchContainerJarFile(version);
|
||||
if (file.exists()) {
|
||||
OmsFileUtils.file2HttpResponse(file, response);
|
||||
} else {
|
||||
log.error("[Container] can't find container by version[{}], please deploy first!", version);
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping("/downloadContainerTemplate")
|
||||
public void downloadContainerTemplate(@RequestBody GenerateContainerTemplateRequest req, HttpServletResponse response) throws IOException {
|
||||
File zipFile = ContainerTemplateGenerator.generate(req.getGroup(), req.getArtifact(), req.getName(), req.getPackageName(), req.getJavaVersion());
|
||||
OmsFileUtils.file2HttpResponse(zipFile, response);
|
||||
}
|
||||
|
||||
@PostMapping("/jarUpload")
|
||||
@ApiPermission(name = "Container-JarUpload", roleScope = RoleScope.APP, requiredPermission = Permission.OPS)
|
||||
public ResultDTO<String> fileUpload(@RequestParam("file") MultipartFile file) throws Exception {
|
||||
if (file == null || file.isEmpty()) {
|
||||
return ResultDTO.failed("empty file");
|
||||
}
|
||||
return ResultDTO.success(containerService.uploadContainerJarFile(file));
|
||||
}
|
||||
|
||||
@PostMapping("/save")
|
||||
@ApiPermission(name = "Container-Save", roleScope = RoleScope.APP, requiredPermission = Permission.OPS)
|
||||
public ResultDTO<Void> saveContainer(@RequestBody SaveContainerInfoRequest request, HttpServletRequest hsr) {
|
||||
|
||||
request.setAppId(Long.valueOf(AuthHeaderUtils.fetchAppId(hsr)));
|
||||
|
||||
request.valid();
|
||||
|
||||
ContainerInfoDO container = new ContainerInfoDO();
|
||||
BeanUtils.copyProperties(request, container);
|
||||
container.setSourceType(request.getSourceType().getV());
|
||||
container.setStatus(request.getStatus().getV());
|
||||
|
||||
containerService.save(container);
|
||||
return ResultDTO.success(null);
|
||||
}
|
||||
|
||||
@GetMapping("/delete")
|
||||
@ApiPermission(name = "Container-Delete", roleScope = RoleScope.APP, requiredPermission = Permission.OPS)
|
||||
public ResultDTO<Void> deleteContainer(Long containerId, HttpServletRequest hsr) {
|
||||
containerService.delete(Long.valueOf(AuthHeaderUtils.fetchAppId(hsr)), containerId);
|
||||
return ResultDTO.success(null);
|
||||
}
|
||||
|
||||
@GetMapping("/list")
|
||||
@ApiPermission(name = "Container-List", roleScope = RoleScope.APP, requiredPermission = Permission.READ)
|
||||
public ResultDTO<List<ContainerInfoVO>> listContainers(HttpServletRequest hsr) {
|
||||
Long appId = Long.valueOf(AuthHeaderUtils.fetchAppId(hsr));
|
||||
List<ContainerInfoVO> res = containerInfoRepository.findByAppIdAndStatusNot(appId, SwitchableStatus.DELETED.getV())
|
||||
.stream().map(ContainerController::convert).collect(Collectors.toList());
|
||||
return ResultDTO.success(res);
|
||||
}
|
||||
|
||||
@GetMapping("/listDeployedWorker")
|
||||
@ApiPermission(name = "Container-ListDeployedWorker", roleScope = RoleScope.APP, requiredPermission = Permission.READ)
|
||||
public ResultDTO<String> listDeployedWorker(Long containerId, HttpServletResponse response, HttpServletRequest hsr) {
|
||||
Long appId = Long.valueOf(AuthHeaderUtils.fetchAppId(hsr));
|
||||
AppInfoDO appInfoDO = appInfoRepository.findById(appId).orElseThrow(() -> new IllegalArgumentException("can't find app by id:" + appId));
|
||||
String targetServer = appInfoDO.getCurrentServer();
|
||||
|
||||
if (StringUtils.isEmpty(targetServer)) {
|
||||
return ResultDTO.failed("No workers have even registered!");
|
||||
}
|
||||
|
||||
return ResultDTO.success(containerService.fetchDeployedInfo(appId, containerId));
|
||||
}
|
||||
|
||||
private static ContainerInfoVO convert(ContainerInfoDO containerInfoDO) {
|
||||
ContainerInfoVO vo = new ContainerInfoVO();
|
||||
BeanUtils.copyProperties(containerInfoDO, vo);
|
||||
if (containerInfoDO.getLastDeployTime() == null) {
|
||||
vo.setLastDeployTime("N/A");
|
||||
}else {
|
||||
vo.setLastDeployTime(DateFormatUtils.format(containerInfoDO.getLastDeployTime(), OmsConstant.TIME_PATTERN));
|
||||
}
|
||||
SwitchableStatus status = SwitchableStatus.of(containerInfoDO.getStatus());
|
||||
vo.setStatus(status.name());
|
||||
ContainerSourceType sourceType = ContainerSourceType.of(containerInfoDO.getSourceType());
|
||||
vo.setSourceType(sourceType.name());
|
||||
return vo;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,175 @@
|
||||
package tech.powerjob.server.web.controller;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.data.domain.Example;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import tech.powerjob.common.OmsConstant;
|
||||
import tech.powerjob.common.enums.InstanceStatus;
|
||||
import tech.powerjob.common.response.ResultDTO;
|
||||
import tech.powerjob.server.auth.Permission;
|
||||
import tech.powerjob.server.auth.RoleScope;
|
||||
import tech.powerjob.server.auth.common.utils.AuthHeaderUtils;
|
||||
import tech.powerjob.server.auth.interceptor.ApiPermission;
|
||||
import tech.powerjob.server.common.utils.OmsFileUtils;
|
||||
import tech.powerjob.server.core.instance.InstanceLogService;
|
||||
import tech.powerjob.server.core.instance.InstanceService;
|
||||
import tech.powerjob.server.core.service.CacheService;
|
||||
import tech.powerjob.server.persistence.PageResult;
|
||||
import tech.powerjob.server.persistence.StringPage;
|
||||
import tech.powerjob.server.persistence.remote.model.InstanceInfoDO;
|
||||
import tech.powerjob.server.persistence.remote.repository.InstanceInfoRepository;
|
||||
import tech.powerjob.server.web.request.QueryInstanceDetailRequest;
|
||||
import tech.powerjob.server.web.request.QueryInstanceRequest;
|
||||
import tech.powerjob.server.web.response.InstanceDetailVO;
|
||||
import tech.powerjob.server.web.response.InstanceInfoVO;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.File;
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
||||
/**
|
||||
* 任务实例 Controller
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2020/4/9
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/instance")
|
||||
public class InstanceController {
|
||||
|
||||
@Resource
|
||||
private InstanceService instanceService;
|
||||
@Resource
|
||||
private InstanceLogService instanceLogService;
|
||||
|
||||
@Resource
|
||||
private CacheService cacheService;
|
||||
@Resource
|
||||
private InstanceInfoRepository instanceInfoRepository;
|
||||
|
||||
@GetMapping("/stop")
|
||||
@ApiPermission(name = "Instance-Stop", roleScope = RoleScope.APP, requiredPermission = Permission.OPS)
|
||||
public ResultDTO<Void> stopInstance(Long instanceId, HttpServletRequest hsr) {
|
||||
instanceService.stopInstance(AuthHeaderUtils.fetchAppIdL(hsr), instanceId);
|
||||
return ResultDTO.success(null);
|
||||
}
|
||||
|
||||
@GetMapping("/retry")
|
||||
@ApiPermission(name = "Instance-Retry", roleScope = RoleScope.APP, requiredPermission = Permission.OPS)
|
||||
public ResultDTO<Void> retryInstance(Long instanceId, HttpServletRequest hsr) {
|
||||
instanceService.retryInstance(AuthHeaderUtils.fetchAppIdL(hsr), instanceId);
|
||||
return ResultDTO.success(null);
|
||||
}
|
||||
|
||||
@GetMapping("/detail")
|
||||
@ApiPermission(name = "Instance-Detail", roleScope = RoleScope.APP, requiredPermission = Permission.READ)
|
||||
public ResultDTO<InstanceDetailVO> getInstanceDetail(Long instanceId, HttpServletRequest hsr) {
|
||||
QueryInstanceDetailRequest queryInstanceDetailRequest = new QueryInstanceDetailRequest();
|
||||
queryInstanceDetailRequest.setAppId(AuthHeaderUtils.fetchAppIdL(hsr));
|
||||
queryInstanceDetailRequest.setInstanceId(instanceId);
|
||||
return getInstanceDetailPlus(queryInstanceDetailRequest, hsr);
|
||||
}
|
||||
|
||||
@PostMapping("/detailPlus")
|
||||
public ResultDTO<InstanceDetailVO> getInstanceDetailPlus(@RequestBody QueryInstanceDetailRequest req, HttpServletRequest hsr) {
|
||||
|
||||
req.setAppId(AuthHeaderUtils.fetchAppIdL(hsr));
|
||||
|
||||
// 非法请求参数校验
|
||||
String customQuery = req.getCustomQuery();
|
||||
String nonNullCustomQuery = Optional.ofNullable(customQuery).orElse(OmsConstant.NONE);
|
||||
if (StringUtils.containsAnyIgnoreCase(nonNullCustomQuery, "delete", "update", "insert", "drop", "CREATE", "ALTER", "TRUNCATE", "RENAME", "LOCK", "GRANT", "REVOKE", "PREPARE", "EXECUTE", "COMMIT", "BEGIN")) {
|
||||
throw new IllegalArgumentException("Don't get any ideas about the database, illegally query: " + customQuery);
|
||||
}
|
||||
|
||||
// 兼容老版本前端不存在 appId 的场景
|
||||
if (req.getAppId() == null) {
|
||||
req.setAppId(instanceService.getInstanceInfo(req.getInstanceId()).getAppId());
|
||||
}
|
||||
|
||||
return ResultDTO.success(InstanceDetailVO.from(instanceService.getInstanceDetail(req.getAppId(), req.getInstanceId(), customQuery)));
|
||||
}
|
||||
|
||||
@GetMapping("/log")
|
||||
@ApiPermission(name = "Instance-Log", roleScope = RoleScope.APP, requiredPermission = Permission.OPS)
|
||||
public ResultDTO<StringPage> getInstanceLog(Long instanceId, Long index, HttpServletRequest hsr) {
|
||||
return ResultDTO.success(instanceLogService.fetchInstanceLog(AuthHeaderUtils.fetchAppIdL(hsr), instanceId, index));
|
||||
}
|
||||
|
||||
@GetMapping("/downloadLogUrl")
|
||||
@ApiPermission(name = "Instance-FetchDownloadLogUrl", roleScope = RoleScope.APP, requiredPermission = Permission.READ)
|
||||
public ResultDTO<String> getDownloadUrl(Long instanceId, HttpServletRequest hsr) {
|
||||
return ResultDTO.success(instanceLogService.fetchDownloadUrl(AuthHeaderUtils.fetchAppIdL(hsr), instanceId));
|
||||
}
|
||||
|
||||
@GetMapping("/downloadLog")
|
||||
public void downloadLogFile(Long instanceId , HttpServletResponse response) throws Exception {
|
||||
File file = instanceLogService.downloadInstanceLog(instanceId);
|
||||
OmsFileUtils.file2HttpResponse(file, response);
|
||||
}
|
||||
|
||||
@GetMapping("/downloadLog4Console")
|
||||
@SneakyThrows
|
||||
public void downloadLog4Console(Long instanceId , HttpServletResponse response, HttpServletRequest hsr) {
|
||||
Long appId = AuthHeaderUtils.fetchAppIdL(hsr);
|
||||
// 获取内部下载链接
|
||||
String downloadUrl = instanceLogService.fetchDownloadUrl(appId, instanceId);
|
||||
// 先下载到本机
|
||||
String logFilePath = OmsFileUtils.genTemporaryWorkPath() + String.format("powerjob-%s-%s.log", appId, instanceId);
|
||||
File logFile = new File(logFilePath);
|
||||
|
||||
try {
|
||||
FileUtils.copyURLToFile(new URL(downloadUrl), logFile);
|
||||
|
||||
// 再推送到浏览器
|
||||
OmsFileUtils.file2HttpResponse(logFile, response);
|
||||
} finally {
|
||||
FileUtils.forceDelete(logFile);
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping("/list")
|
||||
@ApiPermission(name = "Instance-List", roleScope = RoleScope.APP, requiredPermission = Permission.READ)
|
||||
public ResultDTO<PageResult<InstanceInfoVO>> list(@RequestBody QueryInstanceRequest request, HttpServletRequest hsr) {
|
||||
|
||||
request.setAppId(AuthHeaderUtils.fetchAppIdL(hsr));
|
||||
|
||||
Sort sort = Sort.by(Sort.Direction.DESC, "gmtModified");
|
||||
PageRequest pageable = PageRequest.of(request.getIndex(), request.getPageSize(), sort);
|
||||
|
||||
InstanceInfoDO queryEntity = new InstanceInfoDO();
|
||||
BeanUtils.copyProperties(request, queryEntity);
|
||||
queryEntity.setType(request.getType().getV());
|
||||
|
||||
if (!StringUtils.isEmpty(request.getStatus())) {
|
||||
queryEntity.setStatus(InstanceStatus.valueOf(request.getStatus()).getV());
|
||||
}
|
||||
|
||||
Page<InstanceInfoDO> pageResult = instanceInfoRepository.findAll(Example.of(queryEntity), pageable);
|
||||
return ResultDTO.success(convertPage(pageResult));
|
||||
}
|
||||
|
||||
private PageResult<InstanceInfoVO> convertPage(Page<InstanceInfoDO> page) {
|
||||
List<InstanceInfoVO> content = page.getContent().stream()
|
||||
.map(x -> InstanceInfoVO.from(x, cacheService.getJobName(x.getJobId()))).collect(Collectors.toList());
|
||||
|
||||
PageResult<InstanceInfoVO> pageResult = new PageResult<>(page);
|
||||
pageResult.setData(content);
|
||||
return pageResult;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,168 @@
|
||||
package tech.powerjob.server.web.controller;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import tech.powerjob.common.enums.ErrorCodes;
|
||||
import tech.powerjob.common.enums.SwitchableStatus;
|
||||
import tech.powerjob.common.exception.PowerJobException;
|
||||
import tech.powerjob.common.request.http.RunJobRequest;
|
||||
import tech.powerjob.common.request.http.SaveJobInfoRequest;
|
||||
import tech.powerjob.common.response.ResultDTO;
|
||||
import tech.powerjob.server.auth.Permission;
|
||||
import tech.powerjob.server.auth.RoleScope;
|
||||
import tech.powerjob.server.auth.common.utils.AuthHeaderUtils;
|
||||
import tech.powerjob.server.auth.interceptor.ApiPermission;
|
||||
import tech.powerjob.server.core.service.JobService;
|
||||
import tech.powerjob.server.persistence.PageResult;
|
||||
import tech.powerjob.server.persistence.remote.model.JobInfoDO;
|
||||
import tech.powerjob.server.persistence.remote.repository.JobInfoRepository;
|
||||
import tech.powerjob.server.web.request.QueryJobInfoRequest;
|
||||
import tech.powerjob.server.web.response.JobInfoVO;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 任务信息管理 Controller
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2020/3/30
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/job")
|
||||
public class JobController {
|
||||
|
||||
@Resource
|
||||
private JobService jobService;
|
||||
@Resource
|
||||
private JobInfoRepository jobInfoRepository;
|
||||
|
||||
@PostMapping("/save")
|
||||
@ApiPermission(name = "Job-Save", roleScope = RoleScope.APP, requiredPermission = Permission.WRITE)
|
||||
public ResultDTO<Void> saveJobInfo(@RequestBody SaveJobInfoRequest request, HttpServletRequest hsr) {
|
||||
request.setAppId(Long.valueOf(AuthHeaderUtils.fetchAppId(hsr)));
|
||||
jobService.saveJob(request);
|
||||
return ResultDTO.success(null);
|
||||
}
|
||||
|
||||
@PostMapping("/copy")
|
||||
@ApiPermission(name = "Job-Copy", roleScope = RoleScope.APP, requiredPermission = Permission.WRITE)
|
||||
public ResultDTO<JobInfoVO> copyJob(String jobId, HttpServletRequest hsr) {
|
||||
preCheck(jobId, hsr);
|
||||
return ResultDTO.success(JobInfoVO.from(jobService.copyJob(Long.valueOf(jobId))));
|
||||
}
|
||||
|
||||
@GetMapping("/export")
|
||||
@ApiPermission(name = "Job-Export", roleScope = RoleScope.APP, requiredPermission = Permission.READ)
|
||||
public ResultDTO<SaveJobInfoRequest> exportJob(String jobId, HttpServletRequest hsr) {
|
||||
preCheck(jobId, hsr);
|
||||
return ResultDTO.success(jobService.exportJob(Long.valueOf(jobId)));
|
||||
}
|
||||
|
||||
@GetMapping("/disable")
|
||||
@ApiPermission(name = "Job-Disable", roleScope = RoleScope.APP, requiredPermission = Permission.WRITE)
|
||||
public ResultDTO<Void> disableJob(String jobId, HttpServletRequest hsr) {
|
||||
preCheck(jobId, hsr);
|
||||
jobService.disableJob(Long.valueOf(jobId));
|
||||
return ResultDTO.success(null);
|
||||
}
|
||||
|
||||
@GetMapping("/delete")
|
||||
@ApiPermission(name = "Job-Delete", roleScope = RoleScope.APP, requiredPermission = Permission.WRITE)
|
||||
public ResultDTO<Void> deleteJob(String jobId, HttpServletRequest hsr) {
|
||||
preCheck(jobId, hsr);
|
||||
jobService.deleteJob(Long.valueOf(jobId));
|
||||
return ResultDTO.success(null);
|
||||
}
|
||||
|
||||
@GetMapping("/run")
|
||||
@ApiPermission(name = "Job-Run", roleScope = RoleScope.APP, requiredPermission = Permission.OPS)
|
||||
public ResultDTO<Long> runImmediately(String jobId, @RequestParam(required = false) String instanceParams, HttpServletRequest hsr) {
|
||||
preCheck(jobId, hsr);
|
||||
RunJobRequest request = new RunJobRequest().setAppId(AuthHeaderUtils.fetchAppIdL(hsr)).setJobId(Long.valueOf(jobId)).setInstanceParams(instanceParams);
|
||||
return ResultDTO.success(jobService.runJob(request.getAppId(), request));
|
||||
}
|
||||
|
||||
@PostMapping("/list")
|
||||
@ApiPermission(name = "Job-List", roleScope = RoleScope.APP, requiredPermission = Permission.READ)
|
||||
public ResultDTO<PageResult<JobInfoVO>> listJobs(@RequestBody QueryJobInfoRequest request, HttpServletRequest hsr) {
|
||||
|
||||
request.setAppId(Long.valueOf(AuthHeaderUtils.fetchAppId(hsr)));
|
||||
|
||||
Sort sort = Sort.by(Sort.Direction.ASC, "id");
|
||||
PageRequest pageRequest = PageRequest.of(request.getIndex(), request.getPageSize(), sort);
|
||||
Page<JobInfoDO> jobInfoPage;
|
||||
|
||||
// 无查询条件,查询全部
|
||||
if (request.getJobId() == null && StringUtils.isEmpty(request.getKeyword())) {
|
||||
jobInfoPage = jobInfoRepository.findByAppIdAndStatusNot(request.getAppId(), SwitchableStatus.DELETED.getV(), pageRequest);
|
||||
return ResultDTO.success(convertPage(jobInfoPage));
|
||||
}
|
||||
|
||||
// 有 jobId,直接精确查询
|
||||
if (request.getJobId() != null) {
|
||||
|
||||
Optional<JobInfoDO> jobInfoOpt = jobInfoRepository.findById(request.getJobId());
|
||||
|
||||
PageResult<JobInfoVO> result = new PageResult<>();
|
||||
|
||||
if (!jobInfoOpt.isPresent()) {
|
||||
result.setTotalPages(0);
|
||||
result.setTotalItems(0);
|
||||
result.setData(Lists.newLinkedList());
|
||||
return ResultDTO.success(result);
|
||||
}
|
||||
|
||||
if (!jobInfoOpt.get().getAppId().equals(request.getAppId())){
|
||||
return ResultDTO.failed("请输入该app下的jobId");
|
||||
}
|
||||
|
||||
result.setIndex(0);
|
||||
result.setPageSize(request.getPageSize());
|
||||
|
||||
result.setTotalItems(1);
|
||||
result.setTotalPages(1);
|
||||
result.setData(Lists.newArrayList(JobInfoVO.from(jobInfoOpt.get())));
|
||||
|
||||
return ResultDTO.success(result);
|
||||
}
|
||||
|
||||
// 模糊查询
|
||||
String condition = "%" + request.getKeyword() + "%";
|
||||
jobInfoPage = jobInfoRepository.findByAppIdAndJobNameLikeAndStatusNot(request.getAppId(), condition, SwitchableStatus.DELETED.getV(), pageRequest);
|
||||
return ResultDTO.success(convertPage(jobInfoPage));
|
||||
}
|
||||
|
||||
|
||||
private static PageResult<JobInfoVO> convertPage(Page<JobInfoDO> jobInfoPage) {
|
||||
List<JobInfoVO> jobInfoVOList = jobInfoPage.getContent().stream().map(JobInfoVO::from).collect(Collectors.toList());
|
||||
|
||||
PageResult<JobInfoVO> pageResult = new PageResult<>(jobInfoPage);
|
||||
pageResult.setData(jobInfoVOList);
|
||||
return pageResult;
|
||||
}
|
||||
|
||||
private void preCheck(String jobId, HttpServletRequest hsr) {
|
||||
Long appId = Long.valueOf(AuthHeaderUtils.fetchAppId(hsr));
|
||||
Optional<JobInfoDO> jobInfoOpt = jobInfoRepository.findById(Long.valueOf(jobId));
|
||||
if (!jobInfoOpt.isPresent()) {
|
||||
throw new PowerJobException(ErrorCodes.ILLEGAL_ARGS_ERROR, "JobNotExist");
|
||||
}
|
||||
|
||||
JobInfoDO jobInfoDO = jobInfoOpt.get();
|
||||
|
||||
if (!jobInfoDO.getAppId().equals(appId)) {
|
||||
throw new PowerJobException(ErrorCodes.INVALID_REQUEST, String.format("AppIdNotMatch(%d!=%d)", jobInfoDO.getAppId(), appId));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,122 @@
|
||||
package tech.powerjob.server.web.controller;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import tech.powerjob.common.response.ResultDTO;
|
||||
import tech.powerjob.server.auth.Permission;
|
||||
import tech.powerjob.server.auth.RoleScope;
|
||||
import tech.powerjob.server.auth.common.AuthConstants;
|
||||
import tech.powerjob.server.auth.common.utils.AuthHeaderUtils;
|
||||
import tech.powerjob.server.auth.interceptor.ApiPermission;
|
||||
import tech.powerjob.server.auth.plugin.ModifyOrCreateDynamicPermission;
|
||||
import tech.powerjob.server.auth.plugin.SaveNamespaceGrantPermissionPlugin;
|
||||
import tech.powerjob.server.auth.service.WebAuthService;
|
||||
import tech.powerjob.server.persistence.PageResult;
|
||||
import tech.powerjob.server.persistence.remote.model.NamespaceDO;
|
||||
import tech.powerjob.server.web.converter.NamespaceConverter;
|
||||
import tech.powerjob.server.web.request.ComponentUserRoleInfo;
|
||||
import tech.powerjob.server.web.request.ModifyNamespaceRequest;
|
||||
import tech.powerjob.server.web.request.QueryNamespaceRequest;
|
||||
import tech.powerjob.server.web.response.NamespaceBaseVO;
|
||||
import tech.powerjob.server.web.response.NamespaceVO;
|
||||
import tech.powerjob.server.web.response.UserBaseVO;
|
||||
import tech.powerjob.server.web.service.NamespaceWebService;
|
||||
import tech.powerjob.server.web.service.UserWebService;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 命名空间 Controller
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2023/9/3
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/namespace")
|
||||
public class NamespaceController {
|
||||
|
||||
@Resource
|
||||
private WebAuthService webAuthService;
|
||||
@Resource
|
||||
private UserWebService userWebService;
|
||||
@Resource
|
||||
private NamespaceWebService namespaceWebService;
|
||||
|
||||
@ResponseBody
|
||||
@PostMapping("/save")
|
||||
@ApiPermission(name = "Namespace-Save", roleScope = RoleScope.NAMESPACE, dynamicPermissionPlugin = ModifyOrCreateDynamicPermission.class, grandPermissionPlugin = SaveNamespaceGrantPermissionPlugin.class)
|
||||
public ResultDTO<NamespaceBaseVO> save(@RequestBody ModifyNamespaceRequest req, HttpServletRequest hsr) {
|
||||
|
||||
if (req.getId() != null) {
|
||||
req.setId(AuthHeaderUtils.fetchNamespaceIdL(hsr));
|
||||
}
|
||||
NamespaceDO savedNamespace = namespaceWebService.save(req);
|
||||
return ResultDTO.success(NamespaceConverter.do2BaseVo(savedNamespace));
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@ApiPermission(name = "Namespace-Delete", roleScope = RoleScope.NAMESPACE, requiredPermission = Permission.SU)
|
||||
public ResultDTO<Void> deleteNamespace(HttpServletRequest hsr) {
|
||||
namespaceWebService.delete(AuthHeaderUtils.fetchNamespaceIdL(hsr));
|
||||
return ResultDTO.success(null);
|
||||
}
|
||||
|
||||
@PostMapping("/list")
|
||||
@ApiPermission(name = "Namespace-List", roleScope = RoleScope.NAMESPACE, requiredPermission = Permission.NONE)
|
||||
public ResultDTO<PageResult<NamespaceVO>> listNamespace(@RequestBody QueryNamespaceRequest queryNamespaceRequest) {
|
||||
|
||||
Page<NamespaceDO> namespacePageResult = namespaceWebService.list(queryNamespaceRequest);
|
||||
|
||||
PageResult<NamespaceVO> ret = new PageResult<>(namespacePageResult);
|
||||
ret.setData(namespacePageResult.get().map(x -> {
|
||||
NamespaceVO detailVo = new NamespaceVO();
|
||||
NamespaceBaseVO baseVO = NamespaceConverter.do2BaseVo(x);
|
||||
BeanUtils.copyProperties(baseVO, detailVo);
|
||||
|
||||
fillDetail(x, detailVo);
|
||||
return detailVo;
|
||||
}).collect(Collectors.toList()));
|
||||
|
||||
return ResultDTO.success(ret);
|
||||
}
|
||||
|
||||
@PostMapping("/listAll")
|
||||
@ApiPermission(name = "Namespace-ListAll", roleScope = RoleScope.NAMESPACE, requiredPermission = Permission.NONE)
|
||||
public ResultDTO<List<NamespaceBaseVO>> listAll() {
|
||||
// 数量应该不是很多,先简单处理,不查询精简对象
|
||||
List<NamespaceDO> namespaceRepositoryAll = namespaceWebService.listAll();
|
||||
List<NamespaceBaseVO> namespaceBaseVOList = namespaceRepositoryAll.stream().map(nd -> {
|
||||
NamespaceBaseVO nv = new NamespaceBaseVO();
|
||||
nv.setId(nd.getId());
|
||||
nv.setCode(nd.getCode());
|
||||
nv.setName(nd.getName());
|
||||
nv.genShowName();
|
||||
return nv;
|
||||
}).collect(Collectors.toList());
|
||||
return ResultDTO.success(namespaceBaseVOList);
|
||||
}
|
||||
|
||||
private void fillDetail(NamespaceDO namespaceDO, NamespaceVO namespaceVO) {
|
||||
|
||||
Long namespaceId = namespaceVO.getId();
|
||||
|
||||
// 权限用户关系
|
||||
ComponentUserRoleInfo componentUserRoleInfo = webAuthService.fetchComponentUserRoleInfo(RoleScope.NAMESPACE, namespaceId);
|
||||
namespaceVO.setComponentUserRoleInfo(componentUserRoleInfo);
|
||||
|
||||
// 有权限用户填充 token
|
||||
boolean hasPermission = webAuthService.hasPermission(RoleScope.NAMESPACE, namespaceId, Permission.READ);
|
||||
namespaceVO.setToken(hasPermission ? namespaceDO.getToken() : AuthConstants.TIPS_NO_PERMISSION_TO_SEE);
|
||||
|
||||
// 用户信息
|
||||
namespaceVO.setCreatorShowName(userWebService.fetchBaseUserInfo(namespaceDO.getCreator()).map(UserBaseVO::getShowName).orElse(null));
|
||||
namespaceVO.setModifierShowName(userWebService.fetchBaseUserInfo(namespaceDO.getModifier()).map(UserBaseVO::getShowName).orElse(null));
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,45 @@
|
||||
package tech.powerjob.server.web.controller;
|
||||
|
||||
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.response.ResultDTO;
|
||||
import tech.powerjob.server.web.request.ChangePasswordRequest;
|
||||
import tech.powerjob.server.web.request.ModifyUserInfoRequest;
|
||||
import tech.powerjob.server.web.service.PwjbUserWebService;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* PowerJob 自带的登录体系
|
||||
* (同样视为第三方服务,与主框架没有任何关系)
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2024/2/13
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/pwjbUser")
|
||||
public class PwjbUserInfoController {
|
||||
|
||||
@Resource
|
||||
private PwjbUserWebService pwjbUserWebService;
|
||||
|
||||
/**
|
||||
* 创建第三方登录体系(PowerJob) 的账户,不允许修改
|
||||
* @param request 请求(此处复用了主框架请求,便于用户一次性把所有参数都填入)
|
||||
* @return 创建结果
|
||||
*/
|
||||
@PostMapping("/create")
|
||||
public ResultDTO<Void> save(@RequestBody ModifyUserInfoRequest request) {
|
||||
pwjbUserWebService.save(request);
|
||||
return ResultDTO.success(null);
|
||||
}
|
||||
|
||||
@PostMapping("/changePassword")
|
||||
public ResultDTO<Void> changePassword(@RequestBody ChangePasswordRequest changePasswordRequest) {
|
||||
|
||||
pwjbUserWebService.changePassword(changePasswordRequest);
|
||||
return ResultDTO.success(null);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,105 @@
|
||||
package tech.powerjob.server.web.controller;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import tech.powerjob.common.model.WorkerAppInfo;
|
||||
import tech.powerjob.common.request.ServerDiscoveryRequest;
|
||||
import tech.powerjob.common.response.ResultDTO;
|
||||
import tech.powerjob.common.utils.CommonUtils;
|
||||
import tech.powerjob.common.utils.net.PingPongUtils;
|
||||
import tech.powerjob.server.common.aware.ServerInfoAware;
|
||||
import tech.powerjob.server.common.module.ServerInfo;
|
||||
import tech.powerjob.server.persistence.remote.model.AppInfoDO;
|
||||
import tech.powerjob.server.persistence.remote.repository.AppInfoRepository;
|
||||
import tech.powerjob.server.remote.server.election.ServerElectionService;
|
||||
import tech.powerjob.server.remote.transporter.TransportService;
|
||||
import tech.powerjob.server.remote.worker.WorkerClusterQueryService;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.TimeZone;
|
||||
|
||||
/**
|
||||
* 处理Worker请求的 Controller
|
||||
* Worker启动时,先请求assert验证appName的可用性,再根据得到的appId获取Server地址
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2020/4/4
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/server")
|
||||
@RequiredArgsConstructor
|
||||
public class ServerController implements ServerInfoAware {
|
||||
|
||||
private ServerInfo serverInfo;
|
||||
private final TransportService transportService;
|
||||
|
||||
private final ServerElectionService serverElectionService;
|
||||
|
||||
private final AppInfoRepository appInfoRepository;
|
||||
|
||||
private final WorkerClusterQueryService workerClusterQueryService;
|
||||
|
||||
@GetMapping("/assert")
|
||||
public ResultDTO<Long> assertAppName(String appName) {
|
||||
Optional<AppInfoDO> appInfoOpt = appInfoRepository.findByAppName(appName);
|
||||
return appInfoOpt.map(appInfoDO -> ResultDTO.success(appInfoDO.getId())).
|
||||
orElseGet(() -> ResultDTO.failed(String.format("app(%s) is not registered! Please register the app in oms-console first.", appName)));
|
||||
}
|
||||
|
||||
@GetMapping("/assertV2")
|
||||
public ResultDTO<WorkerAppInfo> assertAppNameV2(String appName) {
|
||||
Optional<AppInfoDO> appInfoOpt = appInfoRepository.findByAppName(appName);
|
||||
return appInfoOpt.map(appInfoDO -> {
|
||||
WorkerAppInfo workerAppInfo = new WorkerAppInfo().setAppId(appInfoDO.getId());
|
||||
return ResultDTO.success(workerAppInfo);
|
||||
}).
|
||||
orElseGet(() -> ResultDTO.failed(String.format("app(%s) is not registered! Please register the app in oms-console first.", appName)));
|
||||
}
|
||||
|
||||
@GetMapping("/acquire")
|
||||
public ResultDTO<String> acquireServer(ServerDiscoveryRequest request) {
|
||||
return ResultDTO.success(serverElectionService.elect(request));
|
||||
}
|
||||
|
||||
@GetMapping("/checkConnectivity")
|
||||
public ResultDTO<Boolean> checkConnectivity(String targetIp, Integer targetPort) {
|
||||
try {
|
||||
boolean ret = PingPongUtils.checkConnectivity(targetIp, targetPort);
|
||||
return ResultDTO.success(ret);
|
||||
} catch (Throwable t) {
|
||||
return ResultDTO.failed(t);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@GetMapping("/hello")
|
||||
public ResultDTO<JSONObject> ping(@RequestParam(required = false) boolean debug) {
|
||||
JSONObject res = new JSONObject();
|
||||
res.put("localHost", serverInfo.getIp());
|
||||
res.put("serverInfo", serverInfo);
|
||||
res.put("serverTime", CommonUtils.formatTime(System.currentTimeMillis()));
|
||||
res.put("serverTimeTs", System.currentTimeMillis());
|
||||
res.put("serverTimeZone", TimeZone.getDefault().getDisplayName());
|
||||
res.put("appIds", workerClusterQueryService.getAppId2ClusterStatus().keySet());
|
||||
if (debug) {
|
||||
res.put("appId2ClusterInfo", JSON.parseObject(JSON.toJSONString(workerClusterQueryService.getAppId2ClusterStatus())));
|
||||
}
|
||||
|
||||
try {
|
||||
res.put("defaultAddress", JSONObject.toJSON(transportService.defaultProtocol()));
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
|
||||
return ResultDTO.success(res);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setServerInfo(ServerInfo serverInfo) {
|
||||
this.serverInfo = serverInfo;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,96 @@
|
||||
package tech.powerjob.server.web.controller;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.time.DateFormatUtils;
|
||||
import org.apache.commons.lang3.time.DateUtils;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import tech.powerjob.common.OmsConstant;
|
||||
import tech.powerjob.common.enums.InstanceStatus;
|
||||
import tech.powerjob.common.response.ResultDTO;
|
||||
import tech.powerjob.server.auth.Permission;
|
||||
import tech.powerjob.server.auth.RoleScope;
|
||||
import tech.powerjob.server.auth.interceptor.ApiPermission;
|
||||
import tech.powerjob.common.enums.SwitchableStatus;
|
||||
import tech.powerjob.server.common.module.WorkerInfo;
|
||||
import tech.powerjob.server.persistence.remote.model.AppInfoDO;
|
||||
import tech.powerjob.server.persistence.remote.repository.AppInfoRepository;
|
||||
import tech.powerjob.server.persistence.remote.repository.InstanceInfoRepository;
|
||||
import tech.powerjob.server.persistence.remote.repository.JobInfoRepository;
|
||||
import tech.powerjob.server.remote.server.self.ServerInfoService;
|
||||
import tech.powerjob.server.remote.worker.WorkerClusterQueryService;
|
||||
import tech.powerjob.server.web.response.SystemOverviewVO;
|
||||
import tech.powerjob.server.web.response.WorkerStatusVO;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.TimeZone;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 系统信息控制器(服务于前端首页)
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2020/4/14
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/system")
|
||||
@RequiredArgsConstructor
|
||||
public class SystemInfoController {
|
||||
|
||||
private final AppInfoRepository appInfoRepository;
|
||||
|
||||
private final JobInfoRepository jobInfoRepository;
|
||||
|
||||
private final InstanceInfoRepository instanceInfoRepository;
|
||||
|
||||
private final ServerInfoService serverInfoService;
|
||||
|
||||
private final WorkerClusterQueryService workerClusterQueryService;
|
||||
|
||||
@GetMapping("/listWorker")
|
||||
@ApiPermission(name = "System-ListWorker", roleScope = RoleScope.APP, requiredPermission = Permission.READ)
|
||||
public ResultDTO<List<WorkerStatusVO>> listWorker(Long appId) {
|
||||
|
||||
List<WorkerInfo> workerInfos = workerClusterQueryService.getAllWorkers(appId);
|
||||
return ResultDTO.success(workerInfos.stream().map(WorkerStatusVO::new).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
@GetMapping("/overview")
|
||||
@ApiPermission(name = "System-Overview", roleScope = RoleScope.APP, requiredPermission = Permission.READ)
|
||||
public ResultDTO<SystemOverviewVO> getSystemOverview(Long appId) {
|
||||
|
||||
SystemOverviewVO overview = new SystemOverviewVO();
|
||||
|
||||
Optional<AppInfoDO> appInfoOpt = appInfoRepository.findById(appId);
|
||||
if (appInfoOpt.isPresent()) {
|
||||
AppInfoDO appInfo = appInfoOpt.get();
|
||||
|
||||
overview.setAppId(appId);
|
||||
overview.setAppName(appInfo.getAppName());
|
||||
}
|
||||
|
||||
// 总任务数量
|
||||
overview.setJobCount(jobInfoRepository.countByAppIdAndStatusNot(appId, SwitchableStatus.DELETED.getV()));
|
||||
// 运行任务数
|
||||
overview.setRunningInstanceCount(instanceInfoRepository.countByAppIdAndStatus(appId, InstanceStatus.RUNNING.getV()));
|
||||
// 近期失败任务数(24H内)
|
||||
Date date = DateUtils.addDays(new Date(), -1);
|
||||
overview.setFailedInstanceCount(instanceInfoRepository.countByAppIdAndStatusAndGmtCreateAfter(appId, InstanceStatus.FAILED.getV(), date));
|
||||
|
||||
// 服务器时区
|
||||
overview.setTimezone(TimeZone.getDefault().getDisplayName());
|
||||
// 服务器时间
|
||||
overview.setServerTime(DateFormatUtils.format(new Date(), OmsConstant.TIME_PATTERN));
|
||||
|
||||
overview.setWebServerInfo(serverInfoService.fetchCurrentServerInfo());
|
||||
overview.setScheduleServerInfo(serverInfoService.fetchAppServerInfo(appId));
|
||||
|
||||
return ResultDTO.success(overview);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,96 @@
|
||||
package tech.powerjob.server.web.controller;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections4.MapUtils;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import tech.powerjob.common.PowerSerializable;
|
||||
import tech.powerjob.common.response.AskResponse;
|
||||
import tech.powerjob.common.serialize.JsonUtils;
|
||||
import tech.powerjob.common.utils.CollectionUtils;
|
||||
import tech.powerjob.remote.framework.base.URL;
|
||||
import tech.powerjob.server.common.utils.TestUtils;
|
||||
import tech.powerjob.server.core.alarm.AlarmCenter;
|
||||
import tech.powerjob.server.core.alarm.module.JobInstanceAlarm;
|
||||
import tech.powerjob.server.extension.alarm.AlarmTarget;
|
||||
import tech.powerjob.server.remote.transporter.TransportService;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 开发团队专用(或者 PRO 用户用来做自检也可以 lol)
|
||||
* 测试某些强依赖运行时环境的组件,如 Mail 告警等
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2023/7/31
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/test")
|
||||
public class TestController {
|
||||
|
||||
@Value("${server.port}")
|
||||
private int port;
|
||||
|
||||
@Resource
|
||||
private AlarmCenter alarmCenter;
|
||||
@Resource
|
||||
private TransportService transportService;
|
||||
|
||||
@RequestMapping("/io")
|
||||
public Map<String, Object> io(@RequestBody Map<String, Object> input) {
|
||||
log.info("[TestController] input: {}", JsonUtils.toJSONString(input));
|
||||
return input;
|
||||
}
|
||||
|
||||
@GetMapping("/check")
|
||||
public void check() {
|
||||
Map<String, Object> testConfig = TestUtils.fetchTestConfig();
|
||||
if (CollectionUtils.isEmpty(testConfig)) {
|
||||
log.info("[TestController] testConfig not exist, skip check!");
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("[TestController] testConfig: {}", JsonUtils.toJSONString(testConfig));
|
||||
|
||||
testAlarmCenter();
|
||||
}
|
||||
|
||||
@PostMapping("/transport")
|
||||
public Object testTransportService(@RequestBody Map<String, Object> params) throws ClassNotFoundException {
|
||||
String method = MapUtils.getString(params, "method");
|
||||
|
||||
String protocol = MapUtils.getString(params, "protocol");
|
||||
Object url = MapUtils.getObject(params, "url");
|
||||
Object request = MapUtils.getObject(params, "request");
|
||||
String requestClassName = MapUtils.getString(params, "requestClassName");
|
||||
Class<? extends PowerSerializable> requestClz = (Class<? extends PowerSerializable>) Class.forName(requestClassName);
|
||||
|
||||
if ("ask".equalsIgnoreCase(method)) {
|
||||
return transportService.ask(protocol, JsonUtils.toJavaObject(url, URL.class), JsonUtils.toJavaObject(request, requestClz) , AskResponse.class);
|
||||
}
|
||||
|
||||
transportService.tell(protocol, JsonUtils.toJavaObject(url, URL.class), (PowerSerializable) request);
|
||||
return "TELL_SUCCESS";
|
||||
}
|
||||
|
||||
void testAlarmCenter() {
|
||||
JobInstanceAlarm jobInstanceAlarm = new JobInstanceAlarm().setAppId(277).setJobId(1).setInstanceId(2)
|
||||
.setJobName("test-alarm").setJobParams("jobParams").setInstanceParams("instanceParams")
|
||||
.setExecuteType(1).setFinishedTime(System.currentTimeMillis());
|
||||
|
||||
AlarmTarget target = new AlarmTarget().setName("ald").setPhone("208140").setExtra("extra")
|
||||
.setPhone(MapUtils.getString(TestUtils.fetchTestConfig(), TestUtils.KEY_PHONE_NUMBER))
|
||||
.setEmail("tjq@zju.edu.cn")
|
||||
.setWebHook(localUrlPath().concat("/test/io"));
|
||||
|
||||
log.info("[TestController] start to testAlarmCenter, target: {}", target);
|
||||
alarmCenter.alarmFailed(jobInstanceAlarm, Lists.newArrayList(target));
|
||||
}
|
||||
|
||||
private String localUrlPath() {
|
||||
return String.format("http://127.0.0.1:%d", port);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,255 @@
|
||||
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.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import tech.powerjob.common.exception.PowerJobException;
|
||||
import tech.powerjob.common.response.ResultDTO;
|
||||
import tech.powerjob.server.auth.Permission;
|
||||
import tech.powerjob.server.auth.PowerJobUser;
|
||||
import tech.powerjob.server.auth.Role;
|
||||
import tech.powerjob.server.auth.RoleScope;
|
||||
import tech.powerjob.common.enums.ErrorCodes;
|
||||
import tech.powerjob.server.auth.common.PowerJobAuthException;
|
||||
import tech.powerjob.server.auth.interceptor.ApiPermission;
|
||||
import tech.powerjob.server.auth.service.WebAuthService;
|
||||
import tech.powerjob.server.auth.service.login.PowerJobLoginService;
|
||||
import tech.powerjob.common.enums.SwitchableStatus;
|
||||
import tech.powerjob.server.persistence.remote.model.AppInfoDO;
|
||||
import tech.powerjob.server.persistence.remote.model.NamespaceDO;
|
||||
import tech.powerjob.server.persistence.remote.model.UserInfoDO;
|
||||
import tech.powerjob.server.persistence.remote.repository.AppInfoRepository;
|
||||
import tech.powerjob.server.persistence.remote.repository.NamespaceRepository;
|
||||
import tech.powerjob.server.persistence.remote.repository.UserInfoRepository;
|
||||
import tech.powerjob.server.web.converter.NamespaceConverter;
|
||||
import tech.powerjob.server.web.converter.UserConverter;
|
||||
import tech.powerjob.server.web.request.ModifyUserInfoRequest;
|
||||
import tech.powerjob.server.web.request.QueryUserRequest;
|
||||
import tech.powerjob.server.web.response.AppBaseVO;
|
||||
import tech.powerjob.server.web.response.NamespaceBaseVO;
|
||||
import tech.powerjob.server.web.response.UserBaseVO;
|
||||
import tech.powerjob.server.web.response.UserDetailVO;
|
||||
import tech.powerjob.server.web.service.UserWebService;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 用户信息控制层
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2020/4/12
|
||||
*/
|
||||
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/user")
|
||||
public class UserInfoController {
|
||||
|
||||
@Resource
|
||||
private UserWebService userWebService;
|
||||
@Resource
|
||||
private UserInfoRepository userInfoRepository;
|
||||
@Resource
|
||||
private PowerJobLoginService powerJobLoginService;
|
||||
@Resource
|
||||
private WebAuthService webAuthService;
|
||||
@Resource
|
||||
private NamespaceRepository namespaceRepository;
|
||||
@Resource
|
||||
private AppInfoRepository appInfoRepository;
|
||||
|
||||
@SneakyThrows
|
||||
@PostMapping("/modify")
|
||||
public ResultDTO<Void> modifyUser(@RequestBody ModifyUserInfoRequest modifyUserInfoRequest, HttpServletRequest httpServletRequest) {
|
||||
|
||||
Long userId = modifyUserInfoRequest.getId();
|
||||
checkModifyUserPermission(userId, httpServletRequest);
|
||||
|
||||
Optional<UserInfoDO> userOpt = userInfoRepository.findById(userId);
|
||||
if (!userOpt.isPresent()) {
|
||||
throw new IllegalArgumentException("can't find user by userId:" + userId);
|
||||
}
|
||||
|
||||
UserInfoDO dbUser = userOpt.get();
|
||||
|
||||
// 拷入允许修改的内容
|
||||
if (StringUtils.isNotEmpty(modifyUserInfoRequest.getNick())) {
|
||||
dbUser.setNick(modifyUserInfoRequest.getNick());
|
||||
}
|
||||
if (StringUtils.isNotEmpty(modifyUserInfoRequest.getPhone())) {
|
||||
dbUser.setPhone(modifyUserInfoRequest.getPhone());
|
||||
}
|
||||
if (StringUtils.isNotEmpty(modifyUserInfoRequest.getEmail())) {
|
||||
dbUser.setEmail(modifyUserInfoRequest.getEmail());
|
||||
}
|
||||
if (StringUtils.isNotEmpty(modifyUserInfoRequest.getWebHook())) {
|
||||
dbUser.setWebHook(modifyUserInfoRequest.getWebHook());
|
||||
}
|
||||
if (StringUtils.isNotEmpty(modifyUserInfoRequest.getExtra())) {
|
||||
dbUser.setExtra(modifyUserInfoRequest.getExtra());
|
||||
}
|
||||
|
||||
dbUser.setGmtModified(new Date());
|
||||
userInfoRepository.saveAndFlush(dbUser);
|
||||
|
||||
return ResultDTO.success(null);
|
||||
}
|
||||
|
||||
@GetMapping("/list")
|
||||
public ResultDTO<List<UserBaseVO>> list(@RequestParam(required = false) String name) {
|
||||
|
||||
List<UserInfoDO> result;
|
||||
if (StringUtils.isEmpty(name)) {
|
||||
result = userInfoRepository.findAll();
|
||||
}else {
|
||||
result = userInfoRepository.findByUsernameLike("%" + name + "%");
|
||||
}
|
||||
return ResultDTO.success(convert(result));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询用户信息(用于管理员操作,会返回敏感信息)
|
||||
* @param queryUserRequest 查询请求
|
||||
* @return 响应
|
||||
*/
|
||||
@PostMapping("/query")
|
||||
@ApiPermission(name = "User-Query", roleScope = RoleScope.GLOBAL, requiredPermission = Permission.SU)
|
||||
public ResultDTO<List<UserBaseVO>> query(@RequestBody QueryUserRequest queryUserRequest) {
|
||||
List<UserInfoDO> userInfoDos = userWebService.list(queryUserRequest);
|
||||
List<UserBaseVO> userBaseVOS = userInfoDos.stream().map(x -> UserConverter.do2BaseVo(x, true)).collect(Collectors.toList());
|
||||
return ResultDTO.success(userBaseVOS);
|
||||
}
|
||||
|
||||
@GetMapping("/detail")
|
||||
public ResultDTO<UserDetailVO> getUserDetail(HttpServletRequest httpServletRequest) {
|
||||
Optional<PowerJobUser> powerJobUserOpt = powerJobLoginService.ifLogin(httpServletRequest);
|
||||
if (!powerJobUserOpt.isPresent()) {
|
||||
throw new PowerJobAuthException(ErrorCodes.USER_NOT_LOGIN);
|
||||
}
|
||||
Optional<UserInfoDO> userinfoDoOpt = userInfoRepository.findById(powerJobUserOpt.get().getId());
|
||||
if (!userinfoDoOpt.isPresent()) {
|
||||
throw new IllegalArgumentException("can't find user by id: " + powerJobUserOpt.get().getId());
|
||||
}
|
||||
UserDetailVO userDetailVO = new UserDetailVO();
|
||||
BeanUtils.copyProperties(userinfoDoOpt.get(), userDetailVO);
|
||||
userDetailVO.genShowName();
|
||||
|
||||
// 权限信息
|
||||
Map<Role, List<Long>> globalPermissions = webAuthService.fetchMyPermissionTargets(RoleScope.GLOBAL);
|
||||
userDetailVO.setGlobalRoles(globalPermissions.keySet().stream().map(Enum::name).collect(Collectors.toList()));
|
||||
|
||||
Map<Role, List<Long>> namespacePermissions = webAuthService.fetchMyPermissionTargets(RoleScope.NAMESPACE);
|
||||
List<NamespaceDO> nsList = namespaceRepository.findAllByIdIn(mergeIds(namespacePermissions));
|
||||
Map<Long, NamespaceDO> id2NamespaceDo = Maps.newHashMap();
|
||||
nsList.forEach(x -> id2NamespaceDo.put(x.getId(), x));
|
||||
Map<String, List<NamespaceBaseVO>> role2NamespaceBaseVo = Maps.newHashMap();
|
||||
namespacePermissions.forEach((k, v) -> {
|
||||
List<NamespaceBaseVO> namespaceBaseVOS = Lists.newArrayList();
|
||||
role2NamespaceBaseVo.put(k.name(), namespaceBaseVOS);
|
||||
v.forEach(nId -> {
|
||||
NamespaceDO namespaceDO = id2NamespaceDo.get(nId);
|
||||
if (namespaceDO == null) {
|
||||
return;
|
||||
}
|
||||
NamespaceBaseVO namespaceBaseVO = NamespaceConverter.do2BaseVo(namespaceDO);
|
||||
namespaceBaseVOS.add(namespaceBaseVO);
|
||||
});
|
||||
});
|
||||
userDetailVO.setRole2NamespaceList(role2NamespaceBaseVo);
|
||||
|
||||
Map<Role, List<Long>> appPermissions = webAuthService.fetchMyPermissionTargets(RoleScope.APP);
|
||||
List<AppInfoDO> appList = appInfoRepository.findAllByIdIn(mergeIds(appPermissions));
|
||||
Map<Long, AppInfoDO> id2AppInfo = Maps.newHashMap();
|
||||
appList.forEach(x -> id2AppInfo.put(x.getId(), x));
|
||||
Map<String, List<AppBaseVO>> role2AppBaseVo = Maps.newHashMap();
|
||||
appPermissions.forEach((k, v) -> {
|
||||
List<AppBaseVO> appBaseVOS = Lists.newArrayList();
|
||||
role2AppBaseVo.put(k.name(), appBaseVOS);
|
||||
v.forEach(nId -> {
|
||||
AppInfoDO appInfoDO = id2AppInfo.get(nId);
|
||||
if (appInfoDO == null) {
|
||||
return;
|
||||
}
|
||||
AppBaseVO appBaseVO = new AppBaseVO();
|
||||
BeanUtils.copyProperties(appInfoDO, appBaseVO);
|
||||
appBaseVOS.add(appBaseVO);
|
||||
});
|
||||
});
|
||||
userDetailVO.setRole2AppList(role2AppBaseVo);
|
||||
|
||||
return ResultDTO.success(userDetailVO);
|
||||
}
|
||||
|
||||
@PostMapping("/disable")
|
||||
public ResultDTO<Void> disableUser(Long uid, HttpServletRequest httpServletRequest) {
|
||||
changeAccountStatus(uid, SwitchableStatus.DISABLE, httpServletRequest);
|
||||
return ResultDTO.success(null);
|
||||
}
|
||||
|
||||
@PostMapping("/enable")
|
||||
public ResultDTO<Void> enableUser(Long uid, HttpServletRequest httpServletRequest) {
|
||||
changeAccountStatus(uid, SwitchableStatus.ENABLE, httpServletRequest);
|
||||
return ResultDTO.success(null);
|
||||
}
|
||||
|
||||
private void changeAccountStatus(Long uid, SwitchableStatus targetStatus, HttpServletRequest httpServletRequest) {
|
||||
checkModifyUserPermission(uid, httpServletRequest);
|
||||
|
||||
Optional<UserInfoDO> userOpt = userInfoRepository.findById(uid);
|
||||
if (!userOpt.isPresent()) {
|
||||
throw new IllegalArgumentException("can't find user by userId:" + uid);
|
||||
}
|
||||
|
||||
UserInfoDO dbUser = userOpt.get();
|
||||
|
||||
dbUser.setStatus(targetStatus.getV());
|
||||
dbUser.setGmtModified(new Date());
|
||||
|
||||
userInfoRepository.saveAndFlush(dbUser);
|
||||
log.info("[UserInfoController] changeAccountStatus, userId={},targetStatus={}", uid, targetStatus);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查针对 user 处理的权限
|
||||
* @param uid 目标 userId
|
||||
* @param httpServletRequest http 上下文请求
|
||||
*/
|
||||
private void checkModifyUserPermission(Long uid, HttpServletRequest httpServletRequest) {
|
||||
Optional<PowerJobUser> powerJobUserOpt = powerJobLoginService.ifLogin(httpServletRequest);
|
||||
if (!powerJobUserOpt.isPresent()) {
|
||||
throw new PowerJobAuthException(ErrorCodes.USER_NOT_LOGIN);
|
||||
}
|
||||
PowerJobUser currentLoginUser = powerJobUserOpt.get();
|
||||
|
||||
boolean myself = uid.equals(currentLoginUser.getId());
|
||||
boolean globalAdmin = webAuthService.isGlobalAdmin();
|
||||
|
||||
if (myself || globalAdmin) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new PowerJobException("Only the administrator and account owner can modify the account");
|
||||
}
|
||||
|
||||
private static List<UserBaseVO> convert(List<UserInfoDO> data) {
|
||||
if (CollectionUtils.isEmpty(data)) {
|
||||
return Lists.newLinkedList();
|
||||
}
|
||||
return data.stream().map(x -> UserConverter.do2BaseVo(x, false)).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private static Set<Long> mergeIds(Map<?, List<Long>> map) {
|
||||
Set<Long> ids = Sets.newHashSet();
|
||||
map.values().forEach(ids::addAll);
|
||||
return ids;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,43 @@
|
||||
package tech.powerjob.server.web.controller;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import tech.powerjob.common.enums.TimeExpressionType;
|
||||
import tech.powerjob.common.response.ResultDTO;
|
||||
import tech.powerjob.server.core.scheduler.TimingStrategyService;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 校验控制器
|
||||
*
|
||||
* @author tjq
|
||||
* @author Echo009
|
||||
* @since 2020/11/28
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/validate")
|
||||
@RequiredArgsConstructor
|
||||
public class ValidateController {
|
||||
|
||||
private final TimingStrategyService timingStrategyService;
|
||||
|
||||
@GetMapping("/timeExpression")
|
||||
public ResultDTO<List<String>> checkTimeExpression(TimeExpressionType timeExpressionType,
|
||||
String timeExpression,
|
||||
@RequestParam(required = false) Long startTime,
|
||||
@RequestParam(required = false) Long endTime
|
||||
) {
|
||||
try {
|
||||
timingStrategyService.validate(timeExpressionType, timeExpression, startTime, endTime);
|
||||
return ResultDTO.success(timingStrategyService.calculateNextTriggerTimes(timeExpressionType, timeExpression, startTime, endTime));
|
||||
} catch (Exception e) {
|
||||
return ResultDTO.success(Lists.newArrayList(ExceptionUtils.getMessage(e)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,136 @@
|
||||
package tech.powerjob.server.web.controller;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import tech.powerjob.common.enums.SwitchableStatus;
|
||||
import tech.powerjob.common.request.http.SaveWorkflowNodeRequest;
|
||||
import tech.powerjob.common.request.http.SaveWorkflowRequest;
|
||||
import tech.powerjob.common.response.ResultDTO;
|
||||
import tech.powerjob.server.auth.Permission;
|
||||
import tech.powerjob.server.auth.RoleScope;
|
||||
import tech.powerjob.server.auth.common.utils.AuthHeaderUtils;
|
||||
import tech.powerjob.server.auth.interceptor.ApiPermission;
|
||||
import tech.powerjob.server.core.workflow.WorkflowService;
|
||||
import tech.powerjob.server.persistence.PageResult;
|
||||
import tech.powerjob.server.persistence.remote.model.WorkflowInfoDO;
|
||||
import tech.powerjob.server.persistence.remote.model.WorkflowNodeInfoDO;
|
||||
import tech.powerjob.server.persistence.remote.repository.WorkflowInfoRepository;
|
||||
import tech.powerjob.server.web.request.QueryWorkflowInfoRequest;
|
||||
import tech.powerjob.server.web.response.WorkflowInfoVO;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.text.ParseException;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 工作流控制器
|
||||
*
|
||||
* @author tjq
|
||||
* @author zenggonggu
|
||||
* @since 2020/5/26
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/workflow")
|
||||
public class WorkflowController {
|
||||
|
||||
@Resource
|
||||
private WorkflowService workflowService;
|
||||
@Resource
|
||||
private WorkflowInfoRepository workflowInfoRepository;
|
||||
|
||||
@PostMapping("/save")
|
||||
@ApiPermission(name = "Workflow-Save", roleScope = RoleScope.APP, requiredPermission = Permission.WRITE)
|
||||
public ResultDTO<Long> save(@RequestBody SaveWorkflowRequest req, HttpServletRequest hsr) throws ParseException {
|
||||
req.setAppId(AuthHeaderUtils.fetchAppIdL(hsr));
|
||||
return ResultDTO.success(workflowService.saveWorkflow(req));
|
||||
}
|
||||
|
||||
@PostMapping("/copy")
|
||||
@ApiPermission(name = "Workflow-Copy", roleScope = RoleScope.APP, requiredPermission = Permission.WRITE)
|
||||
public ResultDTO<Long> copy(Long workflowId, HttpServletRequest hsr) {
|
||||
return ResultDTO.success(workflowService.copyWorkflow(workflowId, AuthHeaderUtils.fetchAppIdL(hsr)));
|
||||
}
|
||||
|
||||
@GetMapping("/disable")
|
||||
@ApiPermission(name = "Workflow-Disable", roleScope = RoleScope.APP, requiredPermission = Permission.WRITE)
|
||||
public ResultDTO<Void> disableWorkflow(Long workflowId, HttpServletRequest hsr) {
|
||||
workflowService.disableWorkflow(workflowId, AuthHeaderUtils.fetchAppIdL(hsr));
|
||||
return ResultDTO.success(null);
|
||||
}
|
||||
|
||||
@GetMapping("/enable")
|
||||
@ApiPermission(name = "Workflow-Enable", roleScope = RoleScope.APP, requiredPermission = Permission.WRITE)
|
||||
public ResultDTO<Void> enableWorkflow(Long workflowId, HttpServletRequest hsr) {
|
||||
workflowService.enableWorkflow(workflowId, AuthHeaderUtils.fetchAppIdL(hsr));
|
||||
return ResultDTO.success(null);
|
||||
}
|
||||
|
||||
@GetMapping("/delete")
|
||||
@ApiPermission(name = "Workflow-Delete", roleScope = RoleScope.APP, requiredPermission = Permission.WRITE)
|
||||
public ResultDTO<Void> deleteWorkflow(Long workflowId, HttpServletRequest hsr) {
|
||||
workflowService.deleteWorkflow(workflowId, AuthHeaderUtils.fetchAppIdL(hsr));
|
||||
return ResultDTO.success(null);
|
||||
}
|
||||
|
||||
@PostMapping("/list")
|
||||
@ApiPermission(name = "Workflow-List", roleScope = RoleScope.APP, requiredPermission = Permission.READ)
|
||||
public ResultDTO<PageResult<WorkflowInfoVO>> list(@RequestBody QueryWorkflowInfoRequest req, HttpServletRequest hsr) {
|
||||
|
||||
req.setAppId(AuthHeaderUtils.fetchAppIdL(hsr));
|
||||
|
||||
Sort sort = Sort.by(Sort.Direction.DESC, "gmtCreate");
|
||||
PageRequest pageRequest = PageRequest.of(req.getIndex(), req.getPageSize(), sort);
|
||||
Page<WorkflowInfoDO> wfPage;
|
||||
|
||||
// 排除已删除数据
|
||||
int nStatus = SwitchableStatus.DELETED.getV();
|
||||
// 无查询条件,查询全部
|
||||
if (req.getWorkflowId() == null && StringUtils.isEmpty(req.getKeyword())) {
|
||||
wfPage = workflowInfoRepository.findByAppIdAndStatusNot(req.getAppId(), nStatus, pageRequest);
|
||||
}else if (req.getWorkflowId() != null) {
|
||||
wfPage = workflowInfoRepository.findByIdAndStatusNot(req.getWorkflowId(), nStatus, pageRequest);
|
||||
}else {
|
||||
String condition = "%" + req.getKeyword() + "%";
|
||||
wfPage = workflowInfoRepository.findByAppIdAndStatusNotAndWfNameLike(req.getAppId(), nStatus, condition, pageRequest);
|
||||
}
|
||||
return ResultDTO.success(convertPage(wfPage));
|
||||
}
|
||||
|
||||
@GetMapping("/run")
|
||||
@ApiPermission(name = "Workflow-Run", roleScope = RoleScope.APP, requiredPermission = Permission.OPS)
|
||||
public ResultDTO<Long> runWorkflow(Long workflowId, HttpServletRequest hsr,
|
||||
@RequestParam(required = false,defaultValue = "0") Long delay,
|
||||
@RequestParam(required = false) String initParams
|
||||
) {
|
||||
return ResultDTO.success(workflowService.runWorkflow(workflowId, AuthHeaderUtils.fetchAppIdL(hsr), initParams, delay));
|
||||
}
|
||||
|
||||
@GetMapping("/fetch")
|
||||
@ApiPermission(name = "Workflow-Fetch", roleScope = RoleScope.APP, requiredPermission = Permission.READ)
|
||||
public ResultDTO<WorkflowInfoVO> fetchWorkflow(Long workflowId, HttpServletRequest hsr) {
|
||||
WorkflowInfoDO workflowInfoDO = workflowService.fetchWorkflow(workflowId, AuthHeaderUtils.fetchAppIdL(hsr));
|
||||
return ResultDTO.success(WorkflowInfoVO.from(workflowInfoDO));
|
||||
}
|
||||
|
||||
@PostMapping("/saveNode")
|
||||
@ApiPermission(name = "Workflow-SaveNode", roleScope = RoleScope.APP, requiredPermission = Permission.WRITE)
|
||||
public ResultDTO<List<WorkflowNodeInfoDO>> addWorkflowNode(@RequestBody List<SaveWorkflowNodeRequest> request, HttpServletRequest hsr) {
|
||||
Long appId = AuthHeaderUtils.fetchAppIdL(hsr);
|
||||
request.forEach(r -> r.setAppId(appId));
|
||||
return ResultDTO.success(workflowService.saveWorkflowNode(request));
|
||||
}
|
||||
|
||||
|
||||
private static PageResult<WorkflowInfoVO> convertPage(Page<WorkflowInfoDO> originPage) {
|
||||
|
||||
PageResult<WorkflowInfoVO> newPage = new PageResult<>(originPage);
|
||||
newPage.setData(originPage.getContent().stream().map(WorkflowInfoVO::from).collect(Collectors.toList()));
|
||||
return newPage;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,100 @@
|
||||
package tech.powerjob.server.web.controller;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.data.domain.Example;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import tech.powerjob.common.enums.WorkflowInstanceStatus;
|
||||
import tech.powerjob.common.response.ResultDTO;
|
||||
import tech.powerjob.server.auth.Permission;
|
||||
import tech.powerjob.server.auth.RoleScope;
|
||||
import tech.powerjob.server.auth.common.utils.AuthHeaderUtils;
|
||||
import tech.powerjob.server.auth.interceptor.ApiPermission;
|
||||
import tech.powerjob.server.core.service.CacheService;
|
||||
import tech.powerjob.server.core.workflow.WorkflowInstanceService;
|
||||
import tech.powerjob.server.persistence.PageResult;
|
||||
import tech.powerjob.server.persistence.remote.model.WorkflowInstanceInfoDO;
|
||||
import tech.powerjob.server.persistence.remote.repository.WorkflowInstanceInfoRepository;
|
||||
import tech.powerjob.server.web.request.QueryWorkflowInstanceRequest;
|
||||
import tech.powerjob.server.web.response.WorkflowInstanceInfoVO;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 工作流实例控制器
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2020/5/31
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/wfInstance")
|
||||
public class WorkflowInstanceController {
|
||||
|
||||
@Resource
|
||||
private CacheService cacheService;
|
||||
@Resource
|
||||
private WorkflowInstanceService workflowInstanceService;
|
||||
@Resource
|
||||
private WorkflowInstanceInfoRepository workflowInstanceInfoRepository;
|
||||
|
||||
@GetMapping("/stop")
|
||||
@ApiPermission(name = "WorkflowInstance-Stop", roleScope = RoleScope.APP, requiredPermission = Permission.OPS)
|
||||
public ResultDTO<Void> stopWfInstance(Long wfInstanceId, HttpServletRequest hsr) {
|
||||
workflowInstanceService.stopWorkflowInstanceEntrance(wfInstanceId, AuthHeaderUtils.fetchAppIdL(hsr));
|
||||
return ResultDTO.success(null);
|
||||
}
|
||||
|
||||
@RequestMapping("/retry")
|
||||
@ApiPermission(name = "WorkflowInstance-Retry", roleScope = RoleScope.APP, requiredPermission = Permission.OPS)
|
||||
public ResultDTO<Void> retryWfInstance(Long wfInstanceId, HttpServletRequest hsr) {
|
||||
workflowInstanceService.retryWorkflowInstance(wfInstanceId, AuthHeaderUtils.fetchAppIdL(hsr));
|
||||
return ResultDTO.success(null);
|
||||
}
|
||||
|
||||
@RequestMapping("/markNodeAsSuccess")
|
||||
@ApiPermission(name = "WorkflowInstance-MarkNodeAsSuccess", roleScope = RoleScope.APP, requiredPermission = Permission.OPS)
|
||||
public ResultDTO<Void> markNodeAsSuccess(Long wfInstanceId, Long nodeId, HttpServletRequest hsr) {
|
||||
workflowInstanceService.markNodeAsSuccess(AuthHeaderUtils.fetchAppIdL(hsr), wfInstanceId, nodeId);
|
||||
return ResultDTO.success(null);
|
||||
}
|
||||
|
||||
|
||||
@GetMapping("/info")
|
||||
@ApiPermission(name = "WorkflowInstance-Info", roleScope = RoleScope.APP, requiredPermission = Permission.READ)
|
||||
public ResultDTO<WorkflowInstanceInfoVO> getInfo(Long wfInstanceId, HttpServletRequest hsr) {
|
||||
WorkflowInstanceInfoDO wfInstanceDO = workflowInstanceService.fetchWfInstance(wfInstanceId, AuthHeaderUtils.fetchAppIdL(hsr));
|
||||
return ResultDTO.success(WorkflowInstanceInfoVO.from(wfInstanceDO, cacheService.getWorkflowName(wfInstanceDO.getWorkflowId())));
|
||||
}
|
||||
|
||||
@PostMapping("/list")
|
||||
@ApiPermission(name = "WorkflowInstance-List", roleScope = RoleScope.APP, requiredPermission = Permission.READ)
|
||||
public ResultDTO<PageResult<WorkflowInstanceInfoVO>> listWfInstance(@RequestBody QueryWorkflowInstanceRequest req, HttpServletRequest hsr) {
|
||||
|
||||
req.setAppId(AuthHeaderUtils.fetchAppIdL(hsr));
|
||||
|
||||
Sort sort = Sort.by(Sort.Direction.DESC, "gmtModified");
|
||||
PageRequest pageable = PageRequest.of(req.getIndex(), req.getPageSize(), sort);
|
||||
|
||||
WorkflowInstanceInfoDO queryEntity = new WorkflowInstanceInfoDO();
|
||||
BeanUtils.copyProperties(req, queryEntity);
|
||||
|
||||
if (!StringUtils.isEmpty(req.getStatus())) {
|
||||
queryEntity.setStatus(WorkflowInstanceStatus.valueOf(req.getStatus()).getV());
|
||||
}
|
||||
|
||||
Page<WorkflowInstanceInfoDO> ps = workflowInstanceInfoRepository.findAll(Example.of(queryEntity), pageable);
|
||||
|
||||
return ResultDTO.success(convertPage(ps));
|
||||
}
|
||||
|
||||
private PageResult<WorkflowInstanceInfoVO> convertPage(Page<WorkflowInstanceInfoDO> ps) {
|
||||
PageResult<WorkflowInstanceInfoVO> pr = new PageResult<>(ps);
|
||||
pr.setData(ps.getContent().stream().map(x -> WorkflowInstanceInfoVO.from(x, cacheService.getWorkflowName(x.getWorkflowId()))).collect(Collectors.toList()));
|
||||
return pr;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
package tech.powerjob.server.web.converter;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import tech.powerjob.common.utils.CommonUtils;
|
||||
import tech.powerjob.common.enums.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());
|
||||
|
||||
v.genShowName();
|
||||
return v;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
package tech.powerjob.server.web.converter;
|
||||
|
||||
import tech.powerjob.common.enums.SwitchableStatus;
|
||||
import tech.powerjob.server.persistence.remote.model.UserInfoDO;
|
||||
import tech.powerjob.server.web.response.UserBaseVO;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* UserConverter
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2023/9/4
|
||||
*/
|
||||
public class UserConverter {
|
||||
|
||||
public static UserBaseVO do2BaseVo(UserInfoDO x, boolean includeSensitiveInfo) {
|
||||
|
||||
UserBaseVO userBaseVO = new UserBaseVO();
|
||||
|
||||
userBaseVO.setId(x.getId());
|
||||
userBaseVO.setAccountType(x.getAccountType());
|
||||
userBaseVO.setUsername(x.getUsername());
|
||||
userBaseVO.setNick(x.getNick());
|
||||
userBaseVO.setStatus(Optional.ofNullable(x.getStatus()).orElse(SwitchableStatus.ENABLE.getV()));
|
||||
userBaseVO.setEnable(userBaseVO.getStatus() == SwitchableStatus.ENABLE.getV());
|
||||
|
||||
if (includeSensitiveInfo) {
|
||||
userBaseVO.setPhone(x.getPhone());
|
||||
userBaseVO.setEmail(x.getEmail());
|
||||
}
|
||||
|
||||
userBaseVO.genShowName();
|
||||
return userBaseVO;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
package tech.powerjob.server.web.request;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 验证应用(应用登陆)
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2020/6/20
|
||||
*/
|
||||
@Data
|
||||
public class AppAssertRequest {
|
||||
private String appName;
|
||||
private String password;
|
||||
|
||||
private String encryptType;
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
package tech.powerjob.server.web.request;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 修改密码
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2024/2/13
|
||||
*/
|
||||
@Data
|
||||
public class ChangePasswordRequest implements Serializable {
|
||||
|
||||
private String username;
|
||||
|
||||
private String oldPassword;
|
||||
|
||||
private String newPassword;
|
||||
|
||||
private String newPassword2;
|
||||
}
|
||||
@ -0,0 +1,34 @@
|
||||
package tech.powerjob.server.web.request;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 组件上的用户角色信息
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2024/2/12
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class ComponentUserRoleInfo {
|
||||
/**
|
||||
* 观察者
|
||||
*/
|
||||
private List<Long> observer;
|
||||
/**
|
||||
* 测试
|
||||
*/
|
||||
private List<Long> qa;
|
||||
/**
|
||||
* 开发者
|
||||
*/
|
||||
private List<Long> developer;
|
||||
/**
|
||||
* 管理员
|
||||
*/
|
||||
private List<Long> admin;
|
||||
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
package tech.powerjob.server.web.request;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 创建容器模版请求
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2020/5/15
|
||||
*/
|
||||
@Data
|
||||
public class GenerateContainerTemplateRequest {
|
||||
|
||||
/**
|
||||
* Maven Group
|
||||
*/
|
||||
private String group;
|
||||
/**
|
||||
* Maven artifact
|
||||
*/
|
||||
private String artifact;
|
||||
/**
|
||||
* Maven name
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 包名(com.xx.xx.xx)
|
||||
*/
|
||||
private String packageName;
|
||||
/**
|
||||
* Java版本号,8或者11
|
||||
*/
|
||||
private Integer javaVersion;
|
||||
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
package tech.powerjob.server.web.request;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 授权请求
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2024/2/12
|
||||
*/
|
||||
@Data
|
||||
public class GrantPermissionRequest implements Serializable {
|
||||
|
||||
/**
|
||||
* 目标ID
|
||||
*/
|
||||
private Long targetId;
|
||||
|
||||
/**
|
||||
* 授予的角色
|
||||
*/
|
||||
private Integer role;
|
||||
|
||||
/**
|
||||
* 授予的用户IDS
|
||||
*/
|
||||
private List<Long> userIds;
|
||||
}
|
||||
@ -0,0 +1,55 @@
|
||||
package tech.powerjob.server.web.request;
|
||||
|
||||
import tech.powerjob.common.exception.PowerJobException;
|
||||
import tech.powerjob.common.utils.CommonUtils;
|
||||
import lombok.Data;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/**
|
||||
* 修改应用信息请求
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2020/4/1
|
||||
*/
|
||||
@Data
|
||||
public class ModifyAppInfoRequest {
|
||||
|
||||
private Long id;
|
||||
private String appName;
|
||||
|
||||
/**
|
||||
* namespace 唯一标识,任选其一传递即可
|
||||
*/
|
||||
private Long namespaceId;
|
||||
private String namespaceCode;
|
||||
|
||||
private String oldPassword;
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
private String title;
|
||||
|
||||
/**
|
||||
* 管理标签
|
||||
*/
|
||||
private String tags;
|
||||
/**
|
||||
* 扩展字段
|
||||
*/
|
||||
private String extra;
|
||||
|
||||
private ComponentUserRoleInfo componentUserRoleInfo;
|
||||
|
||||
public void valid() {
|
||||
CommonUtils.requireNonNull(appName, "appName can't be empty");
|
||||
if (StringUtils.containsWhitespace(appName)) {
|
||||
throw new PowerJobException("appName can't contains white space!");
|
||||
}
|
||||
CommonUtils.requireNonNull(password, "password can't be empty");
|
||||
|
||||
// 后续版本强制要求设置 namespace,方便统一管理
|
||||
CommonUtils.requireNonNull(namespaceId, "namespace can't be empty");
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,53 @@
|
||||
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 String dept;
|
||||
|
||||
/**
|
||||
* 标签,扩展性之王,多值逗号分割
|
||||
*/
|
||||
private String tags;
|
||||
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 扩展字段
|
||||
*/
|
||||
private String extra;
|
||||
/**
|
||||
* 权限表单
|
||||
*/
|
||||
private ComponentUserRoleInfo componentUserRoleInfo;
|
||||
|
||||
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!");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
package tech.powerjob.server.web.request;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 创建/修改 UserInfo 请求
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2020/4/12
|
||||
*/
|
||||
@Data
|
||||
public class ModifyUserInfoRequest {
|
||||
|
||||
private Long id;
|
||||
|
||||
private String username;
|
||||
private String nick;
|
||||
private String password;
|
||||
private String webHook;
|
||||
|
||||
/**
|
||||
* 手机号
|
||||
*/
|
||||
private String phone;
|
||||
/**
|
||||
* 邮箱地址
|
||||
*/
|
||||
private String email;
|
||||
|
||||
private String extra;
|
||||
}
|
||||
@ -0,0 +1,44 @@
|
||||
package tech.powerjob.server.web.request;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 查询应用信息
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2024/2/11
|
||||
*/
|
||||
@Data
|
||||
public class QueryAppInfoRequest {
|
||||
|
||||
/**
|
||||
* appId 精确查旋
|
||||
*/
|
||||
private Long appId;
|
||||
/**
|
||||
* namespaceId
|
||||
*/
|
||||
private Long namespaceId;
|
||||
/**
|
||||
* 任务名称
|
||||
*/
|
||||
private String appNameLike;
|
||||
|
||||
private String tagLike;
|
||||
|
||||
/**
|
||||
* 查询与我相关的任务(我有直接权限的)
|
||||
*/
|
||||
private Boolean showMyRelated;
|
||||
|
||||
/* ****************** 分页参数 ****************** */
|
||||
/**
|
||||
* 当前页码
|
||||
*/
|
||||
private Integer index;
|
||||
/**
|
||||
* 页大小
|
||||
*/
|
||||
private Integer pageSize;
|
||||
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
package tech.powerjob.server.web.request;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 查询任务详情的请求
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2024/2/25
|
||||
*/
|
||||
@Data
|
||||
public class QueryInstanceDetailRequest implements Serializable {
|
||||
|
||||
private Long appId;
|
||||
|
||||
private Long instanceId;
|
||||
|
||||
/**
|
||||
* 自定义查询条件(SQL)
|
||||
*/
|
||||
private String customQuery;
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
package tech.powerjob.server.web.request;
|
||||
|
||||
import lombok.Data;
|
||||
import tech.powerjob.server.common.constants.InstanceType;
|
||||
|
||||
/**
|
||||
* 任务实例查询对象
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2020/4/14
|
||||
*/
|
||||
@Data
|
||||
public class QueryInstanceRequest {
|
||||
|
||||
/**
|
||||
* 任务所属应用ID
|
||||
*/
|
||||
private Long appId;
|
||||
/**
|
||||
* 当前页码
|
||||
*/
|
||||
private Integer index;
|
||||
/**
|
||||
* 页大小
|
||||
*/
|
||||
private Integer pageSize;
|
||||
/**
|
||||
* 查询条件(NORMAL/WORKFLOW)
|
||||
*/
|
||||
private InstanceType type;
|
||||
private Long instanceId;
|
||||
private Long jobId;
|
||||
private Long wfInstanceId;
|
||||
|
||||
private String status;
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
package tech.powerjob.server.web.request;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 查询任务列表
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2020/4/13
|
||||
*/
|
||||
@Data
|
||||
public class QueryJobInfoRequest {
|
||||
|
||||
/**
|
||||
* 任务所属应用ID
|
||||
*/
|
||||
private Long appId;
|
||||
/**
|
||||
* 当前页码
|
||||
*/
|
||||
private Integer index;
|
||||
/**
|
||||
* 页大小
|
||||
*/
|
||||
private Integer pageSize;
|
||||
/**
|
||||
* 任务ID
|
||||
*/
|
||||
private Long jobId;
|
||||
private String keyword;
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
package tech.powerjob.server.web.request;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 查询 namespace 请求
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2024/2/11
|
||||
*/
|
||||
@Data
|
||||
public class QueryNamespaceRequest {
|
||||
|
||||
/**
|
||||
* code 模糊查询
|
||||
*/
|
||||
private String codeLike;
|
||||
|
||||
/**
|
||||
* 名称模糊查询
|
||||
*/
|
||||
private String nameLike;
|
||||
|
||||
private String tagLike;
|
||||
|
||||
/* ****************** 分页参数 ****************** */
|
||||
/**
|
||||
* 当前页码
|
||||
*/
|
||||
private Integer index = 0;
|
||||
/**
|
||||
* 页大小
|
||||
*/
|
||||
private Integer pageSize = 10;
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
package tech.powerjob.server.web.request;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 用户查询请求
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2024/3/16
|
||||
*/
|
||||
@Data
|
||||
public class QueryUserRequest implements Serializable {
|
||||
|
||||
/**
|
||||
* 通过 userId 精确查询
|
||||
*/
|
||||
private Long userIdEq;
|
||||
|
||||
private String accountTypeEq;
|
||||
|
||||
/**
|
||||
* nick 模糊查询
|
||||
*/
|
||||
private String nickLike;
|
||||
|
||||
/**
|
||||
* 手机号模糊查询
|
||||
*/
|
||||
private String phoneLike;
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
package tech.powerjob.server.web.request;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 查询工作流
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2020/5/27
|
||||
*/
|
||||
@Data
|
||||
public class QueryWorkflowInfoRequest {
|
||||
|
||||
/**
|
||||
* 任务所属应用ID
|
||||
*/
|
||||
private Long appId;
|
||||
/**
|
||||
* 当前页码
|
||||
*/
|
||||
private Integer index;
|
||||
/**
|
||||
* 页大小
|
||||
*/
|
||||
private Integer pageSize;
|
||||
/**
|
||||
* 查询条件
|
||||
*/
|
||||
private Long workflowId;
|
||||
private String keyword;
|
||||
|
||||
}
|
||||
@ -0,0 +1,34 @@
|
||||
package tech.powerjob.server.web.request;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 查询工作流实例请求
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2020/5/31
|
||||
*/
|
||||
@Data
|
||||
public class QueryWorkflowInstanceRequest {
|
||||
|
||||
/**
|
||||
* 任务所属应用ID
|
||||
*/
|
||||
private Long appId;
|
||||
/**
|
||||
* 当前页码
|
||||
*/
|
||||
private Integer index;
|
||||
/**
|
||||
* 页大小
|
||||
*/
|
||||
private Integer pageSize;
|
||||
/**
|
||||
* 查询条件(NORMAL/WORKFLOW)
|
||||
*/
|
||||
private Long wfInstanceId;
|
||||
|
||||
private Long workflowId;
|
||||
|
||||
private String status;
|
||||
}
|
||||
@ -0,0 +1,51 @@
|
||||
package tech.powerjob.server.web.request;
|
||||
|
||||
import tech.powerjob.common.utils.CommonUtils;
|
||||
import tech.powerjob.server.common.constants.ContainerSourceType;
|
||||
import tech.powerjob.common.enums.SwitchableStatus;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 保存/修改 容器 请求
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2020/5/15
|
||||
*/
|
||||
@Data
|
||||
public class SaveContainerInfoRequest {
|
||||
|
||||
/**
|
||||
* 容器ID,null -> 创建;否则代表修改
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 所属的应用ID
|
||||
*/
|
||||
private Long appId;
|
||||
|
||||
/**
|
||||
* 容器名称
|
||||
*/
|
||||
private String containerName;
|
||||
|
||||
/**
|
||||
* 容器类型,枚举值为 ContainerSourceType(JarFile/Git)
|
||||
*/
|
||||
private ContainerSourceType sourceType;
|
||||
/**
|
||||
* 由 sourceType 决定,JarFile -> String,存储文件名称;Git -> JSON,包括 URL,branch,username,password
|
||||
*/
|
||||
private String sourceInfo;
|
||||
|
||||
/**
|
||||
* 状态,枚举值为 ContainerStatus(ENABLE/DISABLE)
|
||||
*/
|
||||
private SwitchableStatus status;
|
||||
|
||||
public void valid() {
|
||||
CommonUtils.requireNonNull(containerName, "containerName can't be empty");
|
||||
CommonUtils.requireNonNull(appId, "appId can't be empty");
|
||||
CommonUtils.requireNonNull(sourceInfo, "sourceInfo can't be empty");
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
package tech.powerjob.server.web.response;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* AppBaseVO
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2024/2/13
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class AppBaseVO implements Serializable {
|
||||
|
||||
protected Long id;
|
||||
|
||||
protected String appName;
|
||||
|
||||
protected Long namespaceId;
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
protected String title;
|
||||
}
|
||||
@ -0,0 +1,47 @@
|
||||
package tech.powerjob.server.web.response;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import tech.powerjob.server.web.request.ComponentUserRoleInfo;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* AppInfoVO
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2024/2/12
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
public class AppInfoVO extends AppBaseVO {
|
||||
|
||||
private String password;
|
||||
|
||||
private String tags;
|
||||
|
||||
private String extra;
|
||||
|
||||
private ComponentUserRoleInfo componentUserRoleInfo;
|
||||
|
||||
private Date gmtCreate;
|
||||
|
||||
private String gmtCreateStr;
|
||||
|
||||
private Date gmtModified;
|
||||
|
||||
private String gmtModifiedStr;
|
||||
|
||||
private String creatorShowName;
|
||||
|
||||
private String modifierShowName;
|
||||
|
||||
/**
|
||||
* Namespace Info
|
||||
*/
|
||||
private NamespaceBaseVO namespace;
|
||||
|
||||
private String namespaceName;
|
||||
}
|
||||
@ -0,0 +1,43 @@
|
||||
package tech.powerjob.server.web.response;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 容器信息 视图层展示对象
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2020/5/15
|
||||
*/
|
||||
@Data
|
||||
public class ContainerInfoVO {
|
||||
|
||||
private Long id;
|
||||
|
||||
private String containerName;
|
||||
|
||||
/**
|
||||
* 容器类型,枚举值为 ContainerSourceType
|
||||
*/
|
||||
private String sourceType;
|
||||
/**
|
||||
* 由 sourceType 决定,JarFile -> String,存储文件名称;Git -> JSON,包括 URL,branch,username,password
|
||||
*/
|
||||
private String sourceInfo;
|
||||
/**
|
||||
* 版本 (Jar包使用md5,Git使用commitId,前者32位,后者40位,不会产生碰撞)
|
||||
*/
|
||||
private String version;
|
||||
/**
|
||||
* 状态,枚举值为 ContainerStatus
|
||||
*/
|
||||
private String status;
|
||||
/**
|
||||
* 上一次部署时间
|
||||
*/
|
||||
private String lastDeployTime;
|
||||
|
||||
private Date gmtCreate;
|
||||
private Date gmtModified;
|
||||
}
|
||||
@ -0,0 +1,161 @@
|
||||
package tech.powerjob.server.web.response;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import tech.powerjob.common.PowerSerializable;
|
||||
import tech.powerjob.common.model.InstanceDetail;
|
||||
import tech.powerjob.common.utils.CommonUtils;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 任务实例的运行详细信息(对外展示对象)
|
||||
* 注意:日期的格式化全部需要在 server 完成,不能在浏览器完成,否则会有时区问题(当 server 与 browser 时区不一致时显示会有问题)
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2020/7/18
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public class InstanceDetailVO {
|
||||
|
||||
/**
|
||||
* 任务预计执行时间
|
||||
*/
|
||||
private String expectedTriggerTime;
|
||||
/**
|
||||
* 任务整体开始时间
|
||||
*/
|
||||
private String actualTriggerTime;
|
||||
/**
|
||||
* 任务整体结束时间(可能不存在)
|
||||
*/
|
||||
private String finishedTime;
|
||||
/**
|
||||
* 任务状态
|
||||
*/
|
||||
private Integer status;
|
||||
/**
|
||||
* 任务执行结果(可能不存在)
|
||||
*/
|
||||
private String result;
|
||||
/**
|
||||
* TaskTracker地址
|
||||
*/
|
||||
private String taskTrackerAddress;
|
||||
/**
|
||||
* 任务参数
|
||||
*/
|
||||
private String jobParams;
|
||||
/**
|
||||
* 启动参数
|
||||
*/
|
||||
private String instanceParams;
|
||||
|
||||
/**
|
||||
* “外键”,用于 OPENAPI 场景业务场景与 PowerJob 实例的绑定
|
||||
*/
|
||||
private String outerKey;
|
||||
/**
|
||||
* 扩展属性,用于 OPENAPI 场景上下文参数的透传
|
||||
*/
|
||||
private String extendValue;
|
||||
|
||||
/**
|
||||
* MR或BD任务专用
|
||||
* 事实上为 instance 的 task 统计信息,命名为 instanceTaskStats 更合理,不过出于兼容性暂时不改名称了
|
||||
*/
|
||||
private InstanceDetailVO.InstanceTaskStats taskDetail;
|
||||
/**
|
||||
* 查询出来的 Task 详细结果
|
||||
*/
|
||||
private List<TaskDetailInfoVO> queriedTaskDetailInfoList;
|
||||
/**
|
||||
* 秒级任务专用
|
||||
*/
|
||||
private List<InstanceDetailVO.SubInstanceDetail> subInstanceDetails;
|
||||
|
||||
/**
|
||||
* 重试次数
|
||||
*/
|
||||
private Long runningTimes;
|
||||
|
||||
/**
|
||||
* 秒级任务的 extra -> List<SubInstanceDetail>
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public static class SubInstanceDetail implements PowerSerializable {
|
||||
private long subInstanceId;
|
||||
private String startTime;
|
||||
private String finishedTime;
|
||||
private String result;
|
||||
private int status;
|
||||
}
|
||||
|
||||
/**
|
||||
* MapReduce 和 Broadcast 任务的 extra ->
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public static class InstanceTaskStats implements PowerSerializable {
|
||||
private long totalTaskNum;
|
||||
private long succeedTaskNum;
|
||||
private long failedTaskNum;
|
||||
|
||||
// 等待派发状态(仅存在 TaskTracker 数据库中)
|
||||
protected Long waitingDispatchTaskNum;
|
||||
// 已派发,但 ProcessorTracker 未确认,可能由于网络错误请求未送达,也有可能 ProcessorTracker 线程池满,拒绝执行
|
||||
protected Long workerUnreceivedTaskNum;
|
||||
// ProcessorTracker确认接收,存在与线程池队列中,排队执行
|
||||
protected Long receivedTaskNum;
|
||||
// ProcessorTracker正在执行
|
||||
protected Long runningTaskNum;
|
||||
}
|
||||
|
||||
public static InstanceDetailVO from(InstanceDetail origin) {
|
||||
InstanceDetailVO vo = new InstanceDetailVO();
|
||||
BeanUtils.copyProperties(origin, vo);
|
||||
|
||||
// 格式化时间
|
||||
vo.setFinishedTime(CommonUtils.formatTime(origin.getFinishedTime()));
|
||||
vo.setActualTriggerTime(CommonUtils.formatTime(origin.getActualTriggerTime()));
|
||||
vo.setExpectedTriggerTime(CommonUtils.formatTime(origin.getExpectedTriggerTime()));
|
||||
|
||||
// 拷贝 TaskDetail
|
||||
if (origin.getTaskDetail() != null) {
|
||||
InstanceTaskStats voDetail = new InstanceTaskStats();
|
||||
BeanUtils.copyProperties(origin.getTaskDetail(), voDetail);
|
||||
vo.setTaskDetail(voDetail);
|
||||
}
|
||||
|
||||
// 拷贝秒级任务数据
|
||||
if (!CollectionUtils.isEmpty(origin.getSubInstanceDetails())) {
|
||||
vo.subInstanceDetails = Lists.newLinkedList();
|
||||
|
||||
origin.getSubInstanceDetails().forEach(subDetail -> {
|
||||
|
||||
SubInstanceDetail voSubDetail = new SubInstanceDetail();
|
||||
BeanUtils.copyProperties(subDetail, voSubDetail);
|
||||
|
||||
// 格式化时间
|
||||
voSubDetail.setStartTime(CommonUtils.formatTime(subDetail.getStartTime()));
|
||||
voSubDetail.setFinishedTime(CommonUtils.formatTime(subDetail.getFinishedTime()));
|
||||
|
||||
vo.subInstanceDetails.add(voSubDetail);
|
||||
});
|
||||
}
|
||||
|
||||
// 拷贝 MR Task 结果
|
||||
List<TaskDetailInfoVO> taskDetailInfoVOList = Optional.ofNullable(origin.getQueriedTaskDetailInfoList()).orElse(Collections.emptyList()).stream().map(TaskDetailInfoVO::from).collect(Collectors.toList());
|
||||
vo.setQueriedTaskDetailInfoList(taskDetailInfoVOList);
|
||||
|
||||
return vo;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,88 @@
|
||||
package tech.powerjob.server.web.response;
|
||||
|
||||
import tech.powerjob.common.OmsConstant;
|
||||
import tech.powerjob.server.persistence.remote.model.InstanceInfoDO;
|
||||
import lombok.Data;
|
||||
import org.apache.commons.lang3.time.DateFormatUtils;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
|
||||
/**
|
||||
* InstanceInfo 对外展示对象
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2020/4/12
|
||||
*/
|
||||
@Data
|
||||
public class InstanceInfoVO {
|
||||
|
||||
/**
|
||||
* 任务ID(JS精度丢失)
|
||||
*/
|
||||
private String jobId;
|
||||
/**
|
||||
* 任务名称
|
||||
*/
|
||||
private String jobName;
|
||||
/**
|
||||
* 任务实例ID(JS精度丢失)
|
||||
*/
|
||||
private String instanceId;
|
||||
/**
|
||||
* 该任务实例所属的 workflow ID,仅 workflow 任务存在
|
||||
*/
|
||||
private String wfInstanceId;
|
||||
/**
|
||||
* 执行结果
|
||||
*/
|
||||
private String result;
|
||||
/**
|
||||
* TaskTracker地址
|
||||
*/
|
||||
private String taskTrackerAddress;
|
||||
/**
|
||||
* 总共执行的次数(用于重试判断)
|
||||
*/
|
||||
private Long runningTimes;
|
||||
private int status;
|
||||
|
||||
/* ********** 不一致区域 ********** */
|
||||
/**
|
||||
* 实际触发时间(需要格式化为人看得懂的时间)
|
||||
*/
|
||||
private String actualTriggerTime;
|
||||
/**
|
||||
* 结束时间(同理,需要格式化)
|
||||
*/
|
||||
private String finishedTime;
|
||||
|
||||
public static InstanceInfoVO from(InstanceInfoDO instanceInfoDo, String jobName) {
|
||||
InstanceInfoVO instanceInfoVO = new InstanceInfoVO();
|
||||
BeanUtils.copyProperties(instanceInfoDo, instanceInfoVO);
|
||||
|
||||
// 额外设置任务名称,提高可读性
|
||||
instanceInfoVO.setJobName(jobName);
|
||||
|
||||
// ID 转化为 String(JS精度丢失)
|
||||
instanceInfoVO.setJobId(instanceInfoDo.getJobId().toString());
|
||||
instanceInfoVO.setInstanceId(instanceInfoDo.getInstanceId().toString());
|
||||
if (instanceInfoDo.getWfInstanceId() == null) {
|
||||
instanceInfoVO.setWfInstanceId(OmsConstant.NONE);
|
||||
}else {
|
||||
instanceInfoVO.setWfInstanceId(String.valueOf(instanceInfoDo.getWfInstanceId()));
|
||||
}
|
||||
|
||||
// 格式化时间
|
||||
if (instanceInfoDo.getActualTriggerTime() == null) {
|
||||
instanceInfoVO.setActualTriggerTime(OmsConstant.NONE);
|
||||
}else {
|
||||
instanceInfoVO.setActualTriggerTime(DateFormatUtils.format(instanceInfoDo.getActualTriggerTime(), OmsConstant.TIME_PATTERN));
|
||||
}
|
||||
if (instanceInfoDo.getFinishedTime() == null) {
|
||||
instanceInfoVO.setFinishedTime(OmsConstant.NONE);
|
||||
}else {
|
||||
instanceInfoVO.setFinishedTime(DateFormatUtils.format(instanceInfoDo.getFinishedTime(), OmsConstant.TIME_PATTERN));
|
||||
}
|
||||
|
||||
return instanceInfoVO;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,213 @@
|
||||
package tech.powerjob.server.web.response;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import tech.powerjob.common.enums.ExecuteType;
|
||||
import tech.powerjob.common.enums.ProcessorType;
|
||||
import tech.powerjob.common.enums.TimeExpressionType;
|
||||
import tech.powerjob.common.model.AlarmConfig;
|
||||
import tech.powerjob.common.model.JobAdvancedRuntimeConfig;
|
||||
import tech.powerjob.common.model.LogConfig;
|
||||
import tech.powerjob.common.model.LifeCycle;
|
||||
import tech.powerjob.common.utils.CommonUtils;
|
||||
import tech.powerjob.server.common.SJ;
|
||||
import tech.powerjob.common.enums.DispatchStrategy;
|
||||
import tech.powerjob.common.enums.SwitchableStatus;
|
||||
import tech.powerjob.server.persistence.remote.model.JobInfoDO;
|
||||
import com.google.common.collect.Lists;
|
||||
import lombok.Data;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* JobInfo 对外展示对象
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2020/4/12
|
||||
*/
|
||||
@Data
|
||||
public class JobInfoVO {
|
||||
|
||||
private Long id;
|
||||
|
||||
/* ************************** 任务基本信息 ************************** */
|
||||
/**
|
||||
* 任务名称
|
||||
*/
|
||||
private String jobName;
|
||||
/**
|
||||
* 任务描述
|
||||
*/
|
||||
private String jobDescription;
|
||||
/**
|
||||
* 任务所属的应用ID
|
||||
*/
|
||||
private Long appId;
|
||||
/**
|
||||
* 任务自带的参数
|
||||
*/
|
||||
private String jobParams;
|
||||
|
||||
/* ************************** 定时参数 ************************** */
|
||||
/**
|
||||
* 时间表达式类型(CRON/API/FIX_RATE/FIX_DELAY)
|
||||
*/
|
||||
private String timeExpressionType;
|
||||
/**
|
||||
* 时间表达式,CRON/NULL/LONG/LONG
|
||||
*/
|
||||
private String timeExpression;
|
||||
|
||||
/* ************************** 执行方式 ************************** */
|
||||
/**
|
||||
* 执行类型,单机/广播/MR
|
||||
*/
|
||||
private String executeType;
|
||||
/**
|
||||
* 执行器类型,Java/Shell
|
||||
*/
|
||||
private String processorType;
|
||||
/**
|
||||
* 执行器信息
|
||||
*/
|
||||
private String processorInfo;
|
||||
|
||||
/* ************************** 运行时配置 ************************** */
|
||||
/**
|
||||
* 最大同时运行任务数,默认 1
|
||||
*/
|
||||
private Integer maxInstanceNum;
|
||||
/**
|
||||
* 并发度,同时执行某个任务的最大线程数量
|
||||
*/
|
||||
private Integer concurrency;
|
||||
/**
|
||||
* 任务整体超时时间
|
||||
*/
|
||||
private Long instanceTimeLimit;
|
||||
|
||||
/* ************************** 重试配置 ************************** */
|
||||
|
||||
private Integer instanceRetryNum;
|
||||
private Integer taskRetryNum;
|
||||
|
||||
/**
|
||||
* 1 正常运行,2 停止(不再调度)
|
||||
*/
|
||||
private boolean enable;
|
||||
/**
|
||||
* 下一次调度时间
|
||||
*/
|
||||
private Long nextTriggerTime;
|
||||
/**
|
||||
* 下一次调度时间(文字版)
|
||||
*/
|
||||
private String nextTriggerTimeStr;
|
||||
|
||||
/* ************************** 繁忙机器配置 ************************** */
|
||||
/**
|
||||
* 最低CPU核心数量,0代表不限
|
||||
*/
|
||||
private double minCpuCores;
|
||||
/**
|
||||
* 最低内存空间,单位 GB,0代表不限
|
||||
*/
|
||||
private double minMemorySpace;
|
||||
/**
|
||||
* 最低磁盘空间,单位 GB,0代表不限
|
||||
*/
|
||||
private double minDiskSpace;
|
||||
|
||||
private Date gmtCreate;
|
||||
private Date gmtModified;
|
||||
|
||||
/* ************************** 集群配置 ************************** */
|
||||
/**
|
||||
* 指定机器运行,空代表不限,非空则只会使用其中的机器运行(多值逗号分割)
|
||||
*/
|
||||
private String designatedWorkers;
|
||||
/**
|
||||
* 最大机器数量
|
||||
*/
|
||||
private Integer maxWorkerCount;
|
||||
|
||||
/**
|
||||
* 报警用户ID列表
|
||||
*/
|
||||
private List<String> notifyUserIds;
|
||||
|
||||
private String extra;
|
||||
|
||||
private String dispatchStrategy;
|
||||
|
||||
/**
|
||||
* 某种派发策略背后的具体配置,值取决于 dispatchStrategy
|
||||
*/
|
||||
private String dispatchStrategyConfig;
|
||||
|
||||
private LifeCycle lifeCycle;
|
||||
|
||||
private AlarmConfig alarmConfig;
|
||||
|
||||
/**
|
||||
* 任务归类,开放给接入方自由定制
|
||||
*/
|
||||
private String tag;
|
||||
|
||||
/**
|
||||
* 日志配置,包括日志级别、日志方式等配置信息
|
||||
*/
|
||||
private LogConfig logConfig;
|
||||
|
||||
private JobAdvancedRuntimeConfig advancedRuntimeConfig;
|
||||
|
||||
public static JobInfoVO from(JobInfoDO jobInfoDO) {
|
||||
JobInfoVO jobInfoVO = new JobInfoVO();
|
||||
BeanUtils.copyProperties(jobInfoDO, jobInfoVO);
|
||||
|
||||
TimeExpressionType timeExpressionType = TimeExpressionType.of(jobInfoDO.getTimeExpressionType());
|
||||
ExecuteType executeType = ExecuteType.of(jobInfoDO.getExecuteType());
|
||||
ProcessorType processorType = ProcessorType.of(jobInfoDO.getProcessorType());
|
||||
DispatchStrategy dispatchStrategy = DispatchStrategy.of(jobInfoDO.getDispatchStrategy());
|
||||
|
||||
jobInfoVO.setTimeExpressionType(timeExpressionType.name());
|
||||
jobInfoVO.setExecuteType(executeType.name());
|
||||
jobInfoVO.setProcessorType(processorType.name());
|
||||
jobInfoVO.setEnable(jobInfoDO.getStatus() == SwitchableStatus.ENABLE.getV());
|
||||
jobInfoVO.setDispatchStrategy(dispatchStrategy.name());
|
||||
|
||||
if (!StringUtils.isEmpty(jobInfoDO.getNotifyUserIds())) {
|
||||
jobInfoVO.setNotifyUserIds(SJ.COMMA_SPLITTER.splitToList(jobInfoDO.getNotifyUserIds()));
|
||||
}else {
|
||||
jobInfoVO.setNotifyUserIds(Lists.newLinkedList());
|
||||
}
|
||||
jobInfoVO.setNextTriggerTimeStr(CommonUtils.formatTime(jobInfoDO.getNextTriggerTime()));
|
||||
|
||||
if (!StringUtils.isEmpty(jobInfoDO.getAlarmConfig())){
|
||||
jobInfoVO.setAlarmConfig(JSON.parseObject(jobInfoDO.getAlarmConfig(),AlarmConfig.class));
|
||||
} else {
|
||||
jobInfoVO.setAlarmConfig(new AlarmConfig());
|
||||
}
|
||||
if (!StringUtils.isEmpty(jobInfoDO.getLifecycle())){
|
||||
jobInfoVO.setLifeCycle(LifeCycle.parse(jobInfoDO.getLifecycle()));
|
||||
}
|
||||
|
||||
if (!StringUtils.isEmpty(jobInfoDO.getLogConfig())) {
|
||||
jobInfoVO.setLogConfig(JSONObject.parseObject(jobInfoDO.getLogConfig(), LogConfig.class));
|
||||
} else {
|
||||
// 不存在 job 配置时防止前端报错
|
||||
jobInfoVO.setLogConfig(new LogConfig());
|
||||
}
|
||||
|
||||
if (StringUtils.isEmpty(jobInfoDO.getAdvancedRuntimeConfig())) {
|
||||
jobInfoVO.setAdvancedRuntimeConfig(new JobAdvancedRuntimeConfig());
|
||||
} else {
|
||||
jobInfoVO.setAdvancedRuntimeConfig(JSONObject.parseObject(jobInfoDO.getAdvancedRuntimeConfig(), JobAdvancedRuntimeConfig.class));
|
||||
}
|
||||
|
||||
return jobInfoVO;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
package tech.powerjob.server.web.response;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* namespace 基本 VO 对象,用于列表渲染
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2024/2/12
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class NamespaceBaseVO implements Serializable {
|
||||
|
||||
protected Long id;
|
||||
|
||||
/**
|
||||
* 空间唯一标识
|
||||
*/
|
||||
protected String code;
|
||||
|
||||
/**
|
||||
* 空间名称,比如中文描述(XX部门XX空间)
|
||||
*/
|
||||
protected String name;
|
||||
|
||||
private String dept;
|
||||
private String tags;
|
||||
|
||||
/**
|
||||
* 扩展字段
|
||||
*/
|
||||
private String extra;
|
||||
|
||||
private Integer status;
|
||||
private String statusStr;
|
||||
|
||||
private Date gmtCreate;
|
||||
|
||||
private String gmtCreateStr;
|
||||
|
||||
private Date gmtModified;
|
||||
|
||||
private String gmtModifiedStr;
|
||||
|
||||
/**
|
||||
* 前端名称(拼接 code + name,更容易辨认)
|
||||
*/
|
||||
protected String showName;
|
||||
|
||||
public void genShowName() {
|
||||
showName = String.format("%s(%s)", name, code);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
package tech.powerjob.server.web.response;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import tech.powerjob.server.web.request.ComponentUserRoleInfo;
|
||||
|
||||
/**
|
||||
* 基础版本的命名空间
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2023/9/3
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
public class NamespaceVO extends NamespaceBaseVO {
|
||||
|
||||
/**
|
||||
* 访问 token
|
||||
* 仅拥有当前 namespace 权限的访问者可见
|
||||
*/
|
||||
private String token;
|
||||
|
||||
private ComponentUserRoleInfo componentUserRoleInfo;
|
||||
|
||||
private String creatorShowName;
|
||||
|
||||
private String modifierShowName;
|
||||
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
package tech.powerjob.server.web.response;
|
||||
|
||||
import lombok.Data;
|
||||
import tech.powerjob.server.common.module.ServerInfo;
|
||||
|
||||
/**
|
||||
* 系统概览
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2020/4/14
|
||||
*/
|
||||
@Data
|
||||
public class SystemOverviewVO {
|
||||
|
||||
private Long appId;
|
||||
|
||||
private String appName;
|
||||
|
||||
private long jobCount;
|
||||
private long runningInstanceCount;
|
||||
private long failedInstanceCount;
|
||||
/**
|
||||
* 服务器时区
|
||||
*/
|
||||
private String timezone;
|
||||
/**
|
||||
* 服务器时间
|
||||
*/
|
||||
private String serverTime;
|
||||
|
||||
/**
|
||||
* 处理当前 WEB 服务的 server 信息
|
||||
*/
|
||||
private ServerInfo webServerInfo;
|
||||
/**
|
||||
* 调度服务器信息
|
||||
*/
|
||||
private ServerInfo scheduleServerInfo;
|
||||
}
|
||||
@ -0,0 +1,62 @@
|
||||
package tech.powerjob.server.web.response;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import tech.powerjob.common.model.TaskDetailInfo;
|
||||
import tech.powerjob.common.utils.CommonUtils;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 任务详情
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2024/2/25
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class TaskDetailInfoVO implements Serializable {
|
||||
|
||||
private String taskId;
|
||||
private String taskName;
|
||||
/**
|
||||
* 任务对象(map 的 subTask)
|
||||
*/
|
||||
private String taskContent;
|
||||
/**
|
||||
* 处理器地址
|
||||
*/
|
||||
private String processorAddress;
|
||||
private Integer status;
|
||||
private String statusStr;
|
||||
|
||||
private String result;
|
||||
private Integer failedCnt;
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private Long createdTime;
|
||||
private String createdTimeStr;
|
||||
/**
|
||||
* 最后修改时间
|
||||
*/
|
||||
private Long lastModifiedTime;
|
||||
private String lastModifiedTimeStr;
|
||||
/**
|
||||
* ProcessorTracker 最后上报时间
|
||||
*/
|
||||
private Long lastReportTime;
|
||||
private String lastReportTimeStr;
|
||||
|
||||
public static TaskDetailInfoVO from(TaskDetailInfo taskDetailInfo) {
|
||||
TaskDetailInfoVO vo = new TaskDetailInfoVO();
|
||||
BeanUtils.copyProperties(taskDetailInfo, vo);
|
||||
|
||||
vo.setCreatedTimeStr(CommonUtils.formatTime(vo.getCreatedTime()));
|
||||
vo.setLastModifiedTimeStr(CommonUtils.formatTime(vo.getLastModifiedTime()));
|
||||
vo.setLastReportTimeStr(CommonUtils.formatTime(vo.getLastReportTime()));
|
||||
|
||||
return vo;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
package tech.powerjob.server.web.response;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/**
|
||||
* 用户基础信息
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2023/9/3
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public class UserBaseVO {
|
||||
|
||||
protected Long id;
|
||||
protected String username;
|
||||
protected String nick;
|
||||
|
||||
/**
|
||||
* 账户类型
|
||||
*/
|
||||
private String accountType;
|
||||
|
||||
/**
|
||||
* 手机号
|
||||
*/
|
||||
private String phone;
|
||||
/**
|
||||
* 邮箱地址
|
||||
*/
|
||||
private String email;
|
||||
|
||||
/**
|
||||
* 账号当前状态
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
private boolean enable;
|
||||
|
||||
/**
|
||||
* 前端展示名称,更容易辨认
|
||||
*/
|
||||
protected String showName;
|
||||
|
||||
public void genShowName() {
|
||||
|
||||
if (StringUtils.isEmpty(nick)) {
|
||||
showName = username;
|
||||
} else {
|
||||
showName = String.format("%s (%s)", nick, username);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,51 @@
|
||||
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 2024/2/13
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
public class UserDetailVO extends UserBaseVO {
|
||||
|
||||
|
||||
/**
|
||||
* 密码
|
||||
*/
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* webHook
|
||||
*/
|
||||
private String webHook;
|
||||
|
||||
private String originUsername;
|
||||
/**
|
||||
* 扩展字段
|
||||
*/
|
||||
private String extra;
|
||||
|
||||
/**
|
||||
* 拥有的全局权限
|
||||
*/
|
||||
private List<String> globalRoles;
|
||||
/**
|
||||
* 拥有的 namespace 权限
|
||||
*/
|
||||
private Map<String, List<NamespaceBaseVO>> role2NamespaceList;
|
||||
/**
|
||||
* 拥有的 app 权限
|
||||
*/
|
||||
private Map<String, List<AppBaseVO>> role2AppList;
|
||||
|
||||
}
|
||||
@ -0,0 +1,112 @@
|
||||
package tech.powerjob.server.web.response;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import tech.powerjob.common.model.SystemMetrics;
|
||||
import tech.powerjob.common.utils.CommonUtils;
|
||||
import tech.powerjob.server.common.module.WorkerInfo;
|
||||
|
||||
import java.text.NumberFormat;
|
||||
|
||||
/**
|
||||
* Worker机器状态
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2020/4/14
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public class WorkerStatusVO {
|
||||
|
||||
private String address;
|
||||
private String cpuLoad;
|
||||
private String memoryLoad;
|
||||
private String diskLoad;
|
||||
|
||||
private String protocol;
|
||||
private String tag;
|
||||
private String lastActiveTime;
|
||||
/**
|
||||
* 上一次worker在线时间(取 worker 端时间)
|
||||
*/
|
||||
private String lastActiveWorkerTime;
|
||||
|
||||
private Integer lightTaskTrackerNum;
|
||||
|
||||
private Integer heavyTaskTrackerNum;
|
||||
|
||||
private long lastOverloadTime;
|
||||
|
||||
private boolean overloading;
|
||||
|
||||
/**
|
||||
* 1 -> 健康,绿色,2 -> 一般,橙色,3 -> 糟糕,红色,9999 -> 非在线机器
|
||||
*/
|
||||
private int status;
|
||||
|
||||
/**
|
||||
* 12.3%(4 cores)
|
||||
*/
|
||||
private static final String CPU_FORMAT = "%s / %s cores";
|
||||
/**
|
||||
* 27.7%(2.9/8.0 GB)
|
||||
*/
|
||||
private static final String OTHER_FORMAT = "%s%%(%s / %s GB)";
|
||||
|
||||
private static final double THRESHOLD = 0.8;
|
||||
|
||||
// 静态 NumberFormat 实例,线程安全
|
||||
private static final NumberFormat NUMBER_FORMAT;
|
||||
// 静态初始化块,配置 NumberFormat 的格式
|
||||
static {
|
||||
NUMBER_FORMAT = NumberFormat.getInstance();
|
||||
// 设置最小小数位数为 0
|
||||
NUMBER_FORMAT.setMinimumFractionDigits(0);
|
||||
// 设置最大小数位数为 1
|
||||
NUMBER_FORMAT.setMaximumFractionDigits(1);
|
||||
}
|
||||
public WorkerStatusVO(WorkerInfo workerInfo) {
|
||||
SystemMetrics systemMetrics = workerInfo.getSystemMetrics();
|
||||
|
||||
this.status = 1;
|
||||
this.address = workerInfo.getAddress();
|
||||
this.cpuLoad = String.format(CPU_FORMAT, NUMBER_FORMAT.format(systemMetrics.getCpuLoad()), systemMetrics.getCpuProcessors());
|
||||
if (systemMetrics.getCpuLoad() > systemMetrics.getCpuProcessors() * THRESHOLD) {
|
||||
this.status ++;
|
||||
}
|
||||
|
||||
String menL = NUMBER_FORMAT.format(systemMetrics.getJvmMemoryUsage() * 100);
|
||||
String menUsed = NUMBER_FORMAT.format(systemMetrics.getJvmUsedMemory());
|
||||
String menMax = NUMBER_FORMAT.format(systemMetrics.getJvmMaxMemory());
|
||||
this.memoryLoad = String.format(OTHER_FORMAT, menL, menUsed, menMax);
|
||||
if (systemMetrics.getJvmMemoryUsage() > THRESHOLD) {
|
||||
this.status ++;
|
||||
}
|
||||
|
||||
String diskL = NUMBER_FORMAT.format(systemMetrics.getDiskUsage() * 100);
|
||||
String diskUsed = NUMBER_FORMAT.format(systemMetrics.getDiskUsed());
|
||||
String diskMax = NUMBER_FORMAT.format(systemMetrics.getDiskTotal());
|
||||
this.diskLoad = String.format(OTHER_FORMAT, diskL, diskUsed, diskMax);
|
||||
if (systemMetrics.getDiskUsage() > THRESHOLD) {
|
||||
this.status ++;
|
||||
}
|
||||
|
||||
if (workerInfo.overload()){
|
||||
// 超载的情况直接置为 3
|
||||
this.status = 3;
|
||||
}
|
||||
|
||||
if (workerInfo.timeout()) {
|
||||
this.status = 9999;
|
||||
}
|
||||
|
||||
this.protocol = workerInfo.getProtocol();
|
||||
this.tag = CommonUtils.formatString(workerInfo.getTag());
|
||||
this.lastActiveTime = CommonUtils.formatTime(workerInfo.getLastActiveTime());
|
||||
this.lastActiveWorkerTime = CommonUtils.formatTime(workerInfo.getLastActiveWorkerTime());
|
||||
this.lightTaskTrackerNum = workerInfo.getLightTaskTrackerNum();
|
||||
this.heavyTaskTrackerNum = workerInfo.getHeavyTaskTrackerNum();
|
||||
this.lastOverloadTime = workerInfo.getLastOverloadTime();
|
||||
this.overloading = workerInfo.isOverloading();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,89 @@
|
||||
package tech.powerjob.server.web.response;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import tech.powerjob.common.enums.TimeExpressionType;
|
||||
import tech.powerjob.common.model.LifeCycle;
|
||||
import tech.powerjob.common.model.PEWorkflowDAG;
|
||||
import tech.powerjob.server.common.SJ;
|
||||
import tech.powerjob.common.enums.SwitchableStatus;
|
||||
import tech.powerjob.server.persistence.remote.model.WorkflowInfoDO;
|
||||
import lombok.Data;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 工作流对外展示对象
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2020/5/27
|
||||
*/
|
||||
@Data
|
||||
public class WorkflowInfoVO {
|
||||
|
||||
private Long id;
|
||||
|
||||
private String wfName;
|
||||
|
||||
private String wfDescription;
|
||||
|
||||
/**
|
||||
* 所属应用ID
|
||||
*/
|
||||
private Long appId;
|
||||
|
||||
/**
|
||||
* 点线表示法
|
||||
*/
|
||||
private PEWorkflowDAG pEWorkflowDAG;
|
||||
|
||||
/* ************************** 定时参数 ************************** */
|
||||
|
||||
/**
|
||||
* 时间表达式类型(CRON/API/FIX_RATE/FIX_DELAY)
|
||||
*/
|
||||
private String timeExpressionType;
|
||||
/**
|
||||
* 时间表达式,CRON/NULL/LONG/LONG
|
||||
*/
|
||||
private String timeExpression;
|
||||
|
||||
/**
|
||||
* 最大同时运行的工作流个数,默认 1
|
||||
*/
|
||||
private Integer maxWfInstanceNum;
|
||||
|
||||
/**
|
||||
* ENABLE / DISABLE
|
||||
*/
|
||||
private Boolean enable;
|
||||
/**
|
||||
* 工作流整体失败的报警
|
||||
*/
|
||||
private List<Long> notifyUserIds;
|
||||
|
||||
private LifeCycle lifeCycle;
|
||||
|
||||
private Date gmtCreate;
|
||||
|
||||
private Date gmtModified;
|
||||
|
||||
public static WorkflowInfoVO from(WorkflowInfoDO wfDO) {
|
||||
WorkflowInfoVO vo = new WorkflowInfoVO();
|
||||
BeanUtils.copyProperties(wfDO, vo);
|
||||
|
||||
vo.enable = SwitchableStatus.of(wfDO.getStatus()) == SwitchableStatus.ENABLE;
|
||||
vo.setTimeExpressionType(TimeExpressionType.of(wfDO.getTimeExpressionType()).name());
|
||||
vo.setPEWorkflowDAG(JSON.parseObject(wfDO.getPeDAG(), PEWorkflowDAG.class));
|
||||
if (!StringUtils.isEmpty(wfDO.getNotifyUserIds())) {
|
||||
vo.setNotifyUserIds(SJ.COMMA_SPLITTER.splitToList(wfDO.getNotifyUserIds()).stream().map(Long::valueOf).collect(Collectors.toList()));
|
||||
}
|
||||
if (!StringUtils.isEmpty(wfDO.getLifecycle())) {
|
||||
vo.lifeCycle = LifeCycle.parse(wfDO.getLifecycle());
|
||||
}
|
||||
return vo;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,85 @@
|
||||
package tech.powerjob.server.web.response;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import tech.powerjob.common.OmsConstant;
|
||||
import tech.powerjob.common.model.PEWorkflowDAG;
|
||||
import tech.powerjob.server.persistence.remote.model.WorkflowInstanceInfoDO;
|
||||
import lombok.Data;
|
||||
import org.apache.commons.lang3.time.DateFormatUtils;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
|
||||
/**
|
||||
* 工作流实例视图层展示对象
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2020/5/31
|
||||
*/
|
||||
@Data
|
||||
public class WorkflowInstanceInfoVO {
|
||||
|
||||
/**
|
||||
* workflowInstanceId(任务实例表都使用单独的ID作为主键以支持潜在的分表需求)
|
||||
*/
|
||||
private String wfInstanceId;
|
||||
|
||||
private String workflowId;
|
||||
/**
|
||||
* 工作流名称,通过 workflowId 查询获取
|
||||
*/
|
||||
private String workflowName;
|
||||
|
||||
/**
|
||||
* workflow 状态(WorkflowInstanceStatus)
|
||||
*/
|
||||
private Integer status;
|
||||
/**
|
||||
* 工作流启动参数
|
||||
*/
|
||||
private String wfInitParams;
|
||||
|
||||
/**
|
||||
* 工作流上下文
|
||||
*/
|
||||
private String wfContext;
|
||||
|
||||
private PEWorkflowDAG pEWorkflowDAG;
|
||||
private String result;
|
||||
|
||||
/**
|
||||
* 预计触发时间
|
||||
*/
|
||||
private String expectedTriggerTime;
|
||||
/**
|
||||
* 实际触发时间(需要格式化为人看得懂的时间)
|
||||
*/
|
||||
private String actualTriggerTime;
|
||||
/**
|
||||
* 结束时间(同理,需要格式化)
|
||||
*/
|
||||
private String finishedTime;
|
||||
|
||||
public static WorkflowInstanceInfoVO from(WorkflowInstanceInfoDO wfInstanceDO, String workflowName) {
|
||||
WorkflowInstanceInfoVO vo = new WorkflowInstanceInfoVO();
|
||||
BeanUtils.copyProperties(wfInstanceDO, vo);
|
||||
|
||||
vo.setWorkflowName(workflowName);
|
||||
vo.setPEWorkflowDAG(JSONObject.parseObject(wfInstanceDO.getDag(), PEWorkflowDAG.class));
|
||||
|
||||
// JS精度丢失问题
|
||||
vo.setWfInstanceId(String.valueOf(wfInstanceDO.getWfInstanceId()));
|
||||
vo.setWorkflowId(String.valueOf(wfInstanceDO.getWorkflowId()));
|
||||
|
||||
// 格式化时间
|
||||
if (wfInstanceDO.getExpectedTriggerTime() != null) {
|
||||
vo.setExpectedTriggerTime(DateFormatUtils.format(wfInstanceDO.getExpectedTriggerTime(), OmsConstant.TIME_PATTERN));
|
||||
}
|
||||
vo.setActualTriggerTime(DateFormatUtils.format(wfInstanceDO.getActualTriggerTime(), OmsConstant.TIME_PATTERN));
|
||||
if (wfInstanceDO.getFinishedTime() == null) {
|
||||
vo.setFinishedTime(OmsConstant.NONE);
|
||||
}else {
|
||||
vo.setFinishedTime(DateFormatUtils.format(wfInstanceDO.getFinishedTime(), OmsConstant.TIME_PATTERN));
|
||||
}
|
||||
|
||||
return vo;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
package tech.powerjob.server.web.service;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import tech.powerjob.server.persistence.remote.model.NamespaceDO;
|
||||
import tech.powerjob.server.web.request.ModifyNamespaceRequest;
|
||||
import tech.powerjob.server.web.request.QueryNamespaceRequest;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* namespace web 服务
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2024/2/15
|
||||
*/
|
||||
public interface NamespaceWebService {
|
||||
|
||||
NamespaceDO save(ModifyNamespaceRequest req);
|
||||
|
||||
void delete(Long id);
|
||||
|
||||
Optional<NamespaceDO> findById(Long id);
|
||||
|
||||
Optional<NamespaceDO> findByCode(String code);
|
||||
|
||||
Page<NamespaceDO> list(QueryNamespaceRequest queryNamespaceRequest);
|
||||
|
||||
List<NamespaceDO> listAll();
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
package tech.powerjob.server.web.service;
|
||||
|
||||
import tech.powerjob.server.persistence.remote.model.PwjbUserInfoDO;
|
||||
import tech.powerjob.server.web.request.ChangePasswordRequest;
|
||||
import tech.powerjob.server.web.request.ModifyUserInfoRequest;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* PwjbUserWebService
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2024/2/15
|
||||
*/
|
||||
public interface PwjbUserWebService {
|
||||
|
||||
PwjbUserInfoDO save(ModifyUserInfoRequest request);
|
||||
|
||||
void changePassword(ChangePasswordRequest changePasswordRequest);
|
||||
|
||||
Optional<PwjbUserInfoDO> findByUsername(String username);
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
package tech.powerjob.server.web.service;
|
||||
|
||||
import tech.powerjob.server.persistence.remote.model.UserInfoDO;
|
||||
import tech.powerjob.server.web.request.QueryUserRequest;
|
||||
import tech.powerjob.server.web.response.UserBaseVO;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 用户 WEB 服务
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2024/2/17
|
||||
*/
|
||||
public interface UserWebService {
|
||||
|
||||
Optional<UserBaseVO> fetchBaseUserInfo(Long userId);
|
||||
|
||||
List<UserInfoDO> list(QueryUserRequest queryUserRequest);
|
||||
}
|
||||
@ -0,0 +1,170 @@
|
||||
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.AppWebService;
|
||||
import tech.powerjob.server.web.request.ModifyAppInfoRequest;
|
||||
import tech.powerjob.server.web.request.QueryAppInfoRequest;
|
||||
import tech.powerjob.server.web.service.NamespaceWebService;
|
||||
|
||||
import javax.persistence.criteria.Predicate;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 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<WorkerInfo> 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 Optional<AppInfoDO> findByAppName(String appName) {
|
||||
return appInfoRepository.findByAppName(appName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<AppInfoDO> list(QueryAppInfoRequest queryAppInfoRequest) {
|
||||
Pageable pageable = PageRequest.of(queryAppInfoRequest.getIndex(), queryAppInfoRequest.getPageSize());
|
||||
|
||||
// 相关权限(先查处关联 ids)
|
||||
Set<Long> queryAppIds;
|
||||
Boolean showMyRelated = queryAppInfoRequest.getShowMyRelated();
|
||||
if (BooleanUtils.isTrue(showMyRelated)) {
|
||||
Set<Long> 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<AppInfoDO> specification = (root, query, criteriaBuilder) -> {
|
||||
List<Predicate> 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);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,168 @@
|
||||
package tech.powerjob.server.web.service.impl;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
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.enums.SwitchableStatus;
|
||||
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.SJ;
|
||||
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.persistence.remote.repository.NamespaceRepository;
|
||||
import tech.powerjob.server.web.request.ModifyNamespaceRequest;
|
||||
import tech.powerjob.server.web.request.QueryNamespaceRequest;
|
||||
import tech.powerjob.server.web.service.NamespaceWebService;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.persistence.criteria.Predicate;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* NamespaceWebService
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2024/2/15
|
||||
*/
|
||||
@Service
|
||||
public class NamespaceWebServiceImpl implements NamespaceWebService {
|
||||
|
||||
@Resource
|
||||
private WebAuthService webAuthService;
|
||||
@Resource
|
||||
private AppInfoRepository appInfoRepository;
|
||||
@Resource
|
||||
private NamespaceRepository namespaceRepository;
|
||||
|
||||
@Override
|
||||
public NamespaceDO save(ModifyNamespaceRequest req) {
|
||||
req.valid();
|
||||
|
||||
Long id = req.getId();
|
||||
NamespaceDO namespaceDO;
|
||||
|
||||
boolean isCreate = id == null;
|
||||
|
||||
if (isCreate) {
|
||||
|
||||
namespaceRepository.findByCode(req.getCode()).ifPresent(x -> new PowerJobExceptionLauncher(ErrorCodes.ILLEGAL_ARGS_ERROR, String.format("namespace[%s] already exists", req.getCode())));
|
||||
|
||||
namespaceDO = new NamespaceDO();
|
||||
namespaceDO.setGmtCreate(new Date());
|
||||
|
||||
// code 单独拷贝
|
||||
namespaceDO.setCode(req.getCode());
|
||||
// 创建时生成 token
|
||||
namespaceDO.setToken(UUID.randomUUID().toString());
|
||||
namespaceDO.setCreator(LoginUserHolder.getUserId());
|
||||
|
||||
} else {
|
||||
namespaceDO = fetchById(id);
|
||||
namespaceDO.setModifier(LoginUserHolder.getUserId());
|
||||
|
||||
if (!namespaceDO.getCode().equalsIgnoreCase(req.getCode())) {
|
||||
throw new IllegalArgumentException("NOT_ALLOW_CHANGE_THE_NAMESPACE_CODE");
|
||||
}
|
||||
}
|
||||
|
||||
// 拷贝通用变更属性(code 不允许更改)
|
||||
namespaceDO.setTags(req.getTags());
|
||||
namespaceDO.setName(req.getName());
|
||||
namespaceDO.setExtra(req.getExtra());
|
||||
namespaceDO.setStatus(Optional.ofNullable(req.getStatus()).orElse(SwitchableStatus.ENABLE.getV()));
|
||||
|
||||
namespaceDO.setGmtModified(new Date());
|
||||
NamespaceDO savedNamespace = namespaceRepository.save(namespaceDO);
|
||||
|
||||
// 授权
|
||||
webAuthService.processPermissionOnSave(RoleScope.NAMESPACE, savedNamespace.getId(), req.getComponentUserRoleInfo());
|
||||
|
||||
return savedNamespace;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(Long id) {
|
||||
List<AppInfoDO> appInfosInNamespace = appInfoRepository.findAllByNamespaceId(id);
|
||||
if (CollectionUtils.isNotEmpty(appInfosInNamespace)) {
|
||||
List<String> relatedApps = appInfosInNamespace.stream().map(AppInfoDO::getAppName).collect(Collectors.toList());
|
||||
throw new PowerJobException("Unable to delete due to associated apps: " + SJ.COMMA_JOINER.join(relatedApps));
|
||||
}
|
||||
|
||||
namespaceRepository.deleteById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<NamespaceDO> findById(Long id) {
|
||||
if (id == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
return namespaceRepository.findById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<NamespaceDO> findByCode(String code) {
|
||||
if (StringUtils.isEmpty(code)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
return namespaceRepository.findByCode(code);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<NamespaceDO> list(QueryNamespaceRequest queryNamespaceRequest) {
|
||||
String codeLike = queryNamespaceRequest.getCodeLike();
|
||||
String nameLike = queryNamespaceRequest.getNameLike();
|
||||
String tagLike = queryNamespaceRequest.getTagLike();
|
||||
|
||||
Pageable pageable = PageRequest.of(queryNamespaceRequest.getIndex(), queryNamespaceRequest.getPageSize());
|
||||
Specification<NamespaceDO> specification = (root, query, cb) -> {
|
||||
|
||||
List<Predicate> predicates = Lists.newArrayList();
|
||||
|
||||
if (StringUtils.isNotEmpty(codeLike)) {
|
||||
predicates.add(cb.like(root.get("code"), QueryConvertUtils.convertLikeParams(codeLike)));
|
||||
}
|
||||
|
||||
if (StringUtils.isNotEmpty(nameLike)) {
|
||||
predicates.add(cb.like(root.get("name"), QueryConvertUtils.convertLikeParams(nameLike)));
|
||||
}
|
||||
if (StringUtils.isNotEmpty(tagLike)) {
|
||||
predicates.add(cb.like(root.get("tags"), QueryConvertUtils.convertLikeParams(tagLike)));
|
||||
}
|
||||
|
||||
if (predicates.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return query.where(predicates.toArray(new Predicate[0])).getRestriction();
|
||||
};
|
||||
|
||||
return namespaceRepository.findAll(specification, pageable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<NamespaceDO> listAll() {
|
||||
return namespaceRepository.findAll();
|
||||
}
|
||||
|
||||
private NamespaceDO fetchById(Long id) {
|
||||
Optional<NamespaceDO> namespaceDoOpt = namespaceRepository.findById(id);
|
||||
if (!namespaceDoOpt.isPresent()) {
|
||||
throw new IllegalArgumentException("can't find namespace by id: " + id);
|
||||
}
|
||||
return namespaceDoOpt.get();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,129 @@
|
||||
package tech.powerjob.server.web.service.impl;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Sets;
|
||||
import lombok.SneakyThrows;
|
||||
import org.apache.commons.collections4.MapUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
import tech.powerjob.common.PowerJobDKey;
|
||||
import tech.powerjob.common.enums.ErrorCodes;
|
||||
import tech.powerjob.common.exception.PowerJobException;
|
||||
import tech.powerjob.common.serialize.JsonUtils;
|
||||
import tech.powerjob.common.utils.CommonUtils;
|
||||
import tech.powerjob.common.utils.DigestUtils;
|
||||
import tech.powerjob.server.auth.common.PowerJobAuthException;
|
||||
import tech.powerjob.server.common.SJ;
|
||||
import tech.powerjob.server.common.constants.ExtensionKey;
|
||||
import tech.powerjob.server.persistence.remote.model.PwjbUserInfoDO;
|
||||
import tech.powerjob.server.persistence.remote.repository.PwjbUserInfoRepository;
|
||||
import tech.powerjob.server.web.request.ChangePasswordRequest;
|
||||
import tech.powerjob.server.web.request.ModifyUserInfoRequest;
|
||||
import tech.powerjob.server.web.service.PwjbUserWebService;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* PwjbUserWebService
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2024/2/15
|
||||
*/
|
||||
@Service
|
||||
public class PwjbUserWebServiceImplImpl implements PwjbUserWebService {
|
||||
|
||||
@Resource
|
||||
private PwjbUserInfoRepository pwjbUserInfoRepository;
|
||||
|
||||
private static final Set<String> NOT_ALLOWED_CHANGE_PASSWORD_ACCOUNTS = Sets.newHashSet("powerjob_trial_account");
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public PwjbUserInfoDO save(ModifyUserInfoRequest request) {
|
||||
String username = request.getUsername();
|
||||
CommonUtils.requireNonNull(username, "userName can't be null or empty!");
|
||||
CommonUtils.requireNonNull(request.getPassword(), "password can't be null or empty!");
|
||||
|
||||
Optional<PwjbUserInfoDO> oldUserOpt = pwjbUserInfoRepository.findByUsername(username);
|
||||
if (oldUserOpt.isPresent()) {
|
||||
throw new IllegalArgumentException("username already exist, please change one!");
|
||||
}
|
||||
|
||||
PwjbUserInfoDO pwjbUserInfoDO = new PwjbUserInfoDO();
|
||||
|
||||
pwjbUserInfoDO.setUsername(username);
|
||||
pwjbUserInfoDO.setGmtCreate(new Date());
|
||||
pwjbUserInfoDO.setGmtModified(new Date());
|
||||
|
||||
// 二次加密密码
|
||||
final String password = request.getPassword();
|
||||
if (StringUtils.isNotEmpty(password)) {
|
||||
pwjbUserInfoDO.setPassword(DigestUtils.rePassword(password, pwjbUserInfoDO.getUsername()));
|
||||
}
|
||||
|
||||
// 其他参数存入 extra,在回调创建真正的内部 USER 时回填
|
||||
ModifyUserInfoRequest cpRequest = JsonUtils.parseObject(JsonUtils.toJSONString(request), ModifyUserInfoRequest.class);
|
||||
cpRequest.setPassword(null);
|
||||
cpRequest.setUsername(null);
|
||||
cpRequest.setNick(null);
|
||||
pwjbUserInfoDO.setExtra(JsonUtils.toJSONString(cpRequest));
|
||||
|
||||
return pwjbUserInfoRepository.save(pwjbUserInfoDO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changePassword(ChangePasswordRequest changePasswordRequest) {
|
||||
if (!StringUtils.equals(changePasswordRequest.getNewPassword(), changePasswordRequest.getNewPassword2())) {
|
||||
throw new IllegalArgumentException("Inconsistent passwords");
|
||||
}
|
||||
|
||||
String username = changePasswordRequest.getUsername();
|
||||
Optional<PwjbUserInfoDO> userOpt = pwjbUserInfoRepository.findByUsername(username);
|
||||
if (!userOpt.isPresent()) {
|
||||
throw new IllegalArgumentException("can't find user by username: " + username);
|
||||
}
|
||||
|
||||
PwjbUserInfoDO dbUser = userOpt.get();
|
||||
String oldPasswordInDb = dbUser.getPassword();
|
||||
String oldPasswordInReq = DigestUtils.rePassword(changePasswordRequest.getOldPassword(), dbUser.getUsername());
|
||||
if (!StringUtils.equals(oldPasswordInDb, oldPasswordInReq)) {
|
||||
throw new PowerJobAuthException(ErrorCodes.INCORRECT_PASSWORD);
|
||||
}
|
||||
|
||||
// 不允许修改密码判定
|
||||
String extra = dbUser.getExtra();
|
||||
if (StringUtils.isNotEmpty(extra)) {
|
||||
ModifyUserInfoRequest originRequest = JsonUtils.parseObjectUnsafe(extra, ModifyUserInfoRequest.class);
|
||||
if (StringUtils.isNotEmpty(originRequest.getExtra())) {
|
||||
Map<String, Object> extraMap = JsonUtils.parseMap(originRequest.getExtra());
|
||||
boolean allowedChangePwd = MapUtils.getBoolean(extraMap, ExtensionKey.PwjbUser.allowedChangePwd, true);
|
||||
if (!allowedChangePwd) {
|
||||
throw new PowerJobException(ErrorCodes.OPERATION_NOT_PERMITTED, "notAllowedChangePassword");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 测试账号特殊处理
|
||||
Set<String> testAccounts = Sets.newHashSet(NOT_ALLOWED_CHANGE_PASSWORD_ACCOUNTS);
|
||||
String testAccountsStr = System.getProperty(PowerJobDKey.SERVER_TEST_ACCOUNT_USERNAME);
|
||||
if (StringUtils.isNotEmpty(testAccountsStr)) {
|
||||
testAccounts.addAll(Lists.newArrayList(SJ.COMMA_SPLITTER.split(testAccountsStr)));
|
||||
}
|
||||
if (testAccounts.contains(username)) {
|
||||
throw new IllegalArgumentException("this account not allowed change the password");
|
||||
}
|
||||
|
||||
dbUser.setPassword(DigestUtils.rePassword(changePasswordRequest.getNewPassword(), dbUser.getUsername()));
|
||||
dbUser.setGmtModified(new Date());
|
||||
pwjbUserInfoRepository.saveAndFlush(dbUser);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<PwjbUserInfoDO> findByUsername(String username) {
|
||||
return pwjbUserInfoRepository.findByUsername(username);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,103 @@
|
||||
package tech.powerjob.server.web.service.impl;
|
||||
|
||||
import com.google.common.cache.Cache;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.collect.Lists;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
import org.springframework.stereotype.Service;
|
||||
import tech.powerjob.server.persistence.QueryConvertUtils;
|
||||
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.QueryUserRequest;
|
||||
import tech.powerjob.server.web.response.UserBaseVO;
|
||||
import tech.powerjob.server.web.service.UserWebService;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.persistence.criteria.Predicate;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* UserWebService
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2024/2/17
|
||||
*/
|
||||
@Service
|
||||
public class UserWebServiceImpl implements UserWebService {
|
||||
|
||||
/**
|
||||
* 展示用的 user 查询缓存,对延迟不敏感
|
||||
*/
|
||||
private final Cache<Long, UserInfoDO> userCache4Show = CacheBuilder.newBuilder()
|
||||
.softValues()
|
||||
.maximumSize(256)
|
||||
.expireAfterWrite(3, TimeUnit.MINUTES)
|
||||
.build();
|
||||
|
||||
@Resource
|
||||
private UserInfoRepository userInfoRepository;
|
||||
|
||||
@Override
|
||||
public Optional<UserBaseVO> fetchBaseUserInfo(Long userId) {
|
||||
|
||||
if (userId == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
try {
|
||||
UserInfoDO userInfoDO = userCache4Show.get(userId, () -> {
|
||||
Optional<UserInfoDO> userInfoOpt = userInfoRepository.findById(userId);
|
||||
if (userInfoOpt.isPresent()) {
|
||||
return userInfoOpt.get();
|
||||
}
|
||||
throw new IllegalArgumentException("can't find user by userId: " + userId);
|
||||
});
|
||||
|
||||
return Optional.of(UserConverter.do2BaseVo(userInfoDO, false));
|
||||
} catch (Exception e) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserInfoDO> list(QueryUserRequest q) {
|
||||
|
||||
Long userIdEq = q.getUserIdEq();
|
||||
String accountTypeEq = q.getAccountTypeEq();
|
||||
String nickLike = q.getNickLike();
|
||||
String phoneLike = q.getPhoneLike();
|
||||
|
||||
|
||||
Specification<UserInfoDO> specification = (root, query, cb) -> {
|
||||
|
||||
List<Predicate> predicates = Lists.newArrayList();
|
||||
|
||||
if (userIdEq != null) {
|
||||
predicates.add(cb.equal(root.get("id"), userIdEq));
|
||||
}
|
||||
|
||||
if (StringUtils.isNotEmpty(accountTypeEq)) {
|
||||
predicates.add(cb.equal(root.get("accountType"), accountTypeEq));
|
||||
}
|
||||
|
||||
if (StringUtils.isNotEmpty(nickLike)) {
|
||||
predicates.add(cb.like(root.get("nick"), QueryConvertUtils.convertLikeParams(nickLike)));
|
||||
}
|
||||
|
||||
if (StringUtils.isNotEmpty(phoneLike)) {
|
||||
predicates.add(cb.like(root.get("phone"), QueryConvertUtils.convertLikeParams(phoneLike)));
|
||||
}
|
||||
|
||||
if (predicates.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return query.where(predicates.toArray(new Predicate[0])).getRestriction();
|
||||
};
|
||||
|
||||
return userInfoRepository.findAll(specification);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* 处理 WEB 服务的 service 层
|
||||
* 如果有共用逻辑可以单独抽成 service,否则直接写在 controller 即可。PowerJob 的 WEB 领域模型不复杂,没必要过度封装。
|
||||
* LESS IS MORE
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2024/2/15
|
||||
*/
|
||||
package tech.powerjob.server.web.service;
|
||||
@ -0,0 +1,59 @@
|
||||
package tech.powerjob.server.web.websocket;
|
||||
|
||||
import tech.powerjob.server.config.OmsEndpointConfigure;
|
||||
import tech.powerjob.server.core.container.ContainerService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.websocket.*;
|
||||
import javax.websocket.server.PathParam;
|
||||
import javax.websocket.server.ServerEndpoint;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 容器部署 WebSocket 服务
|
||||
* 记录一个不错的 WebSocket 测试网站:<a>http://www.easyswoole.com/wstool.html</a>
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2020/5/17
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@ServerEndpoint(value = "/container/deploy/{id}", configurator = OmsEndpointConfigure.class)
|
||||
public class ContainerDeployServerEndpoint {
|
||||
|
||||
@Resource
|
||||
private ContainerService containerService;
|
||||
|
||||
@OnOpen
|
||||
public void onOpen(@PathParam("id") Long id, Session session) {
|
||||
|
||||
RemoteEndpoint.Async remote = session.getAsyncRemote();
|
||||
remote.sendText("SYSTEM: connected successfully, start to deploy container: " + id);
|
||||
try {
|
||||
containerService.deploy(id, session);
|
||||
}catch (Exception e) {
|
||||
log.error("[ContainerDeployServerEndpoint] deploy container {} failed.", id, e);
|
||||
|
||||
remote.sendText("SYSTEM: deploy failed because of the exception");
|
||||
remote.sendText(ExceptionUtils.getStackTrace(e));
|
||||
}
|
||||
try {
|
||||
session.close();
|
||||
}catch (Exception e) {
|
||||
log.error("[ContainerDeployServerEndpoint] close session for {} failed.", id, e);
|
||||
}
|
||||
}
|
||||
|
||||
@OnError
|
||||
public void onError(Session session, Throwable throwable) {
|
||||
try {
|
||||
session.close();
|
||||
} catch (IOException e) {
|
||||
log.error("[ContainerDeployServerEndpoint] close session failed.", e);
|
||||
}
|
||||
log.warn("[ContainerDeployServerEndpoint] session onError!", throwable);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,45 @@
|
||||
oms.env=DAILY
|
||||
logging.config=classpath:logback-dev.xml
|
||||
|
||||
####### Database properties(Configure according to the the environment) #######
|
||||
spring.datasource.core.driver-class-name=com.mysql.cj.jdbc.Driver
|
||||
spring.datasource.core.jdbc-url=jdbc:mysql://powerjob-mysql:3306/powerjob-daily?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
|
||||
spring.datasource.core.username=root
|
||||
spring.datasource.core.password=No1Bug2Please3!
|
||||
spring.datasource.core.maximum-pool-size=20
|
||||
spring.datasource.core.minimum-idle=5
|
||||
|
||||
####### Storage properties(Delete if not needed) #######
|
||||
#oms.storage.dfs.mongodb.uri=mongodb+srv://zqq:No1Bug2Please3!@cluster0.wie54.gcp.mongodb.net/powerjob_daily?retryWrites=true&w=majority
|
||||
oms.storage.dfs.mysql_series.driver=com.mysql.cj.jdbc.Driver
|
||||
oms.storage.dfs.mysql_series.url=jdbc:mysql://powerjob-mysql:3306/powerjob-daily?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
|
||||
oms.storage.dfs.mysql_series.username=root
|
||||
oms.storage.dfs.mysql_series.password=No1Bug2Please3!
|
||||
oms.storage.dfs.mysql_series.auto_create_table=true
|
||||
|
||||
####### Email properties(Non-core configuration properties) #######
|
||||
####### Delete the following code to disable the mail #######
|
||||
spring.mail.host=smtp.163.com
|
||||
spring.mail.username=zqq@163.com
|
||||
spring.mail.password=GOFZPNARMVKCGONV
|
||||
spring.mail.properties.mail.smtp.auth=true
|
||||
spring.mail.properties.mail.smtp.starttls.enable=true
|
||||
spring.mail.properties.mail.smtp.starttls.required=true
|
||||
|
||||
####### DingTalk properties(Non-core configuration properties) #######
|
||||
####### Delete the following code to disable the DingTalk #######
|
||||
oms.alarm.ding.app-key=dingauqwkvxxnqskknfv
|
||||
oms.alarm.ding.app-secret=XWrEPdAZMPgJeFtHuL0LH73LRj-74umF2_0BFcoXMfvnX0pCQvt0rpb1JOJU_HLl
|
||||
oms.alarm.ding.agent-id=847044348
|
||||
|
||||
####### Resource cleaning properties #######
|
||||
oms.instanceinfo.retention=1
|
||||
oms.container.retention.local=1
|
||||
oms.container.retention.remote=-1
|
||||
|
||||
####### Cache properties #######
|
||||
oms.instance.metadata.cache.size=1024
|
||||
|
||||
####### Threshold in precise fetching server(0~100). 100 means full detection of server, in which #######
|
||||
####### split-brain could be avoided while performance overhead would increase. #######
|
||||
oms.accurate.select.server.percentage = 50
|
||||
@ -0,0 +1,40 @@
|
||||
oms.env=PRE
|
||||
logging.config=classpath:logback-product.xml
|
||||
|
||||
####### Database properties(Configure according to the the environment) #######
|
||||
spring.datasource.core.driver-class-name=com.mysql.cj.jdbc.Driver
|
||||
spring.datasource.core.jdbc-url=jdbc:mysql://remotehost:3306/powerjob-pre?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
|
||||
spring.datasource.core.username=root
|
||||
spring.datasource.core.password=No1Bug2Please3!
|
||||
spring.datasource.core.maximum-pool-size=20
|
||||
spring.datasource.core.minimum-idle=5
|
||||
|
||||
####### Storage properties(Delete if not needed) #######
|
||||
oms.storage.dfs.mongodb.uri=mongodb://remotehost:27017/powerjob-pre
|
||||
|
||||
####### Email properties(Non-core configuration properties) #######
|
||||
####### Delete the following code to disable the mail #######
|
||||
spring.mail.host=smtp.qq.com
|
||||
spring.mail.username=zqq
|
||||
spring.mail.password=qqz
|
||||
spring.mail.properties.mail.smtp.auth=true
|
||||
spring.mail.properties.mail.smtp.starttls.enable=true
|
||||
spring.mail.properties.mail.smtp.starttls.required=true
|
||||
|
||||
####### DingTalk properties(Non-core configuration properties) #######
|
||||
####### Delete the following code to disable the DingTalk #######
|
||||
oms.alarm.ding.app-key=dingauqwkvxxnqskknfv
|
||||
oms.alarm.ding.app-secret=XWrEPdAZMPgJeFtHuL0LH73LRj-74umF2_0BFcoXMfvnX0pCQvt0rpb1JOJU_HLl
|
||||
oms.alarm.ding.agent-id=847044348
|
||||
|
||||
####### Resource cleaning properties #######
|
||||
oms.instanceinfo.retention=3
|
||||
oms.container.retention.local=3
|
||||
oms.container.retention.remote=-1
|
||||
|
||||
####### Cache properties #######
|
||||
oms.instance.metadata.cache.size=1024
|
||||
|
||||
####### Threshold in precise fetching server(0~100). 100 means full detection of server, in which #######
|
||||
####### split-brain could be avoided while performance overhead would increase. #######
|
||||
oms.accurate.select.server.percentage = 50
|
||||
@ -0,0 +1,40 @@
|
||||
oms.env=PRODUCT
|
||||
logging.config=classpath:logback-product.xml
|
||||
|
||||
####### Database properties(Configure according to the the environment) #######
|
||||
spring.datasource.core.driver-class-name=com.mysql.cj.jdbc.Driver
|
||||
spring.datasource.core.jdbc-url=jdbc:mysql://localhost:3306/powerjob-product?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
|
||||
spring.datasource.core.username=root
|
||||
spring.datasource.core.password=No1Bug2Please3!
|
||||
spring.datasource.core.maximum-pool-size=20
|
||||
spring.datasource.core.minimum-idle=5
|
||||
|
||||
####### Storage properties(Delete if not needed) #######
|
||||
#oms.storage.dfs.mongodb.uri=mongodb://localhost:27017/powerjob-product
|
||||
|
||||
####### Email properties(Non-core configuration properties) #######
|
||||
####### Delete the following code to disable the mail #######
|
||||
spring.mail.host=smtp.qq.com
|
||||
spring.mail.username=zqq
|
||||
spring.mail.password=qqz
|
||||
spring.mail.properties.mail.smtp.auth=true
|
||||
spring.mail.properties.mail.smtp.starttls.enable=true
|
||||
spring.mail.properties.mail.smtp.starttls.required=true
|
||||
|
||||
####### DingTalk properties(Non-core configuration properties) #######
|
||||
####### Delete the following code to disable the DingTalk #######
|
||||
oms.alarm.ding.app-key=
|
||||
oms.alarm.ding.app-secret=
|
||||
oms.alarm.ding.agent-id=
|
||||
|
||||
####### Resource cleaning properties #######
|
||||
oms.instanceinfo.retention=7
|
||||
oms.container.retention.local=7
|
||||
oms.container.retention.remote=-1
|
||||
|
||||
####### Cache properties #######
|
||||
oms.instance.metadata.cache.size=2048
|
||||
|
||||
####### Threshold in precise fetching server(0~100). 100 means full detection of server, in which #######
|
||||
####### split-brain could be avoided while performance overhead would increase. #######
|
||||
oms.accurate.select.server.percentage = 50
|
||||
@ -0,0 +1,27 @@
|
||||
# Http server port
|
||||
server.port=7700
|
||||
|
||||
spring.profiles.active=product
|
||||
spring.main.banner-mode=log
|
||||
spring.jpa.open-in-view=false
|
||||
spring.data.mongodb.repositories.type=none
|
||||
logging.level.org.mongodb=warn
|
||||
|
||||
# Configuration for uploading files.
|
||||
spring.servlet.multipart.enabled=true
|
||||
spring.servlet.multipart.file-size-threshold=0
|
||||
spring.servlet.multipart.max-file-size=209715200
|
||||
spring.servlet.multipart.max-request-size=209715200
|
||||
|
||||
###### PowerJob transporter configuration ######
|
||||
oms.transporter.active.protocols=AKKA,HTTP,MU
|
||||
oms.transporter.main.protocol=HTTP
|
||||
oms.akka.port=10086
|
||||
oms.http.port=10010
|
||||
oms.mu.port=10077
|
||||
# Prefix for all tables. Default empty string. Config if you have needs, i.e. pj_
|
||||
oms.table-prefix=
|
||||
|
||||
###### PowerJob User and Permission Configuration Configuration ######
|
||||
oms.auth.initiliaze.admin.password=powerjob_admin
|
||||
oms.auth.openapi.enable=false
|
||||
@ -0,0 +1,15 @@
|
||||
${AnsiColor.GREEN}
|
||||
███████ ██ ██
|
||||
░██░░░░██ ░██ ░██
|
||||
░██ ░██ ██████ ███ ██ █████ ██████ ░██ ██████ ░██
|
||||
░███████ ██░░░░██░░██ █ ░██ ██░░░██░░██░░█ ░██ ██░░░░██░██████
|
||||
░██░░░░ ░██ ░██ ░██ ███░██░███████ ░██ ░ ░██░██ ░██░██░░░██
|
||||
░██ ░██ ░██ ░████░████░██░░░░ ░██ ██ ░██░██ ░██░██ ░██
|
||||
░██ ░░██████ ███░ ░░░██░░██████░███ ░░█████ ░░██████ ░██████
|
||||
░░ ░░░░░░ ░░░ ░░░ ░░░░░░ ░░░ ░░░░░ ░░░░░░ ░░░░░
|
||||
${AnsiColor.BRIGHT_RED}
|
||||
* Maintainer: tengjiqi@gmail.com & Team PowerJob
|
||||
* OfficialWebsite: http://www.powerjob.tech/
|
||||
* SourceCode: https://github.com/PowerJob/PowerJob
|
||||
* PoweredBy: SpringBoot${spring-boot.formatted-version}
|
||||
${AnsiColor.DEFAULT}
|
||||
@ -0,0 +1,132 @@
|
||||
<?xml version="1.0"?>
|
||||
<included>
|
||||
|
||||
<property name="MONITOR_LOG_PATH" value="${LOG_PATH}/monitors"/>
|
||||
<property name="ROTATE_PATTERN" value="%d{yyyy-MM-dd}.%i"/>
|
||||
|
||||
<property name="MONITOR_LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS}|%thread|%X{serverId}|%msg%n"/>
|
||||
|
||||
<!-- database -->
|
||||
<appender name="MONITOR_LOGGER_DB_OPERATION_APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${MONITOR_LOG_PATH}/database.log</file>
|
||||
<encoder>
|
||||
<pattern>${MONITOR_LOG_PATTERN}</pattern>
|
||||
<charset>UTF-8</charset>
|
||||
</encoder>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||
<fileNamePattern>${MONITOR_LOG_PATH}/database.log.${ROTATE_PATTERN}</fileNamePattern>
|
||||
<maxHistory>3</maxHistory>
|
||||
<maxFileSize>200MB</maxFileSize>
|
||||
<totalSizeCap>1000MB</totalSizeCap>
|
||||
</rollingPolicy>
|
||||
</appender>
|
||||
<appender name="ASYNC_MONITOR_LOGGER_DB_OPERATION_APPENDER" class="ch.qos.logback.classic.AsyncAppender">
|
||||
<queueSize>512</queueSize>
|
||||
<discardingThreshold>0</discardingThreshold>
|
||||
<neverBlock>true</neverBlock>
|
||||
<appender-ref ref="MONITOR_LOGGER_DB_OPERATION_APPENDER"/>
|
||||
</appender>
|
||||
<logger name="MONITOR_LOGGER_DB_OPERATION" level="INFO" additivity="false">
|
||||
<appender-ref ref="ASYNC_MONITOR_LOGGER_DB_OPERATION_APPENDER"/>
|
||||
</logger>
|
||||
|
||||
|
||||
<!-- TtReportInstanceStatusEvent -->
|
||||
<appender name="MONITOR_LOGGER_TT_REPORT_STATUS_APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${MONITOR_LOG_PATH}/tt_status_report.log</file>
|
||||
<encoder>
|
||||
<pattern>${MONITOR_LOG_PATTERN}</pattern>
|
||||
<charset>UTF-8</charset>
|
||||
</encoder>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||
<fileNamePattern>${MONITOR_LOG_PATH}/tt_status_report.log.${ROTATE_PATTERN}</fileNamePattern>
|
||||
<maxHistory>3</maxHistory>
|
||||
<maxFileSize>200MB</maxFileSize>
|
||||
<totalSizeCap>1000MB</totalSizeCap>
|
||||
</rollingPolicy>
|
||||
</appender>
|
||||
<appender name="ASYNC_MONITOR_LOGGER_TT_REPORT_STATUS_APPENDER" class="ch.qos.logback.classic.AsyncAppender">
|
||||
<queueSize>512</queueSize>
|
||||
<discardingThreshold>0</discardingThreshold>
|
||||
<neverBlock>true</neverBlock>
|
||||
<appender-ref ref="MONITOR_LOGGER_TT_REPORT_STATUS_APPENDER"/>
|
||||
</appender>
|
||||
<logger name="MONITOR_LOGGER_TT_REPORT_STATUS" level="INFO" additivity="false">
|
||||
<appender-ref ref="ASYNC_MONITOR_LOGGER_TT_REPORT_STATUS_APPENDER"/>
|
||||
</logger>
|
||||
|
||||
<!-- WorkerHeartbeatEvent -->
|
||||
<appender name="MONITOR_LOGGER_WORKER_HEART_BEAT_APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${MONITOR_LOG_PATH}/worker_heartbeat.log</file>
|
||||
<encoder>
|
||||
<pattern>${MONITOR_LOG_PATTERN}</pattern>
|
||||
<charset>UTF-8</charset>
|
||||
</encoder>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||
<fileNamePattern>${MONITOR_LOG_PATH}/worker_heartbeat.log.${ROTATE_PATTERN}</fileNamePattern>
|
||||
<maxHistory>3</maxHistory>
|
||||
<maxFileSize>200MB</maxFileSize>
|
||||
<totalSizeCap>1000MB</totalSizeCap>
|
||||
</rollingPolicy>
|
||||
</appender>
|
||||
<appender name="ASYNC_MONITOR_LOGGER_WORKER_HEART_BEAT_APPENDER" class="ch.qos.logback.classic.AsyncAppender">
|
||||
<queueSize>512</queueSize>
|
||||
<discardingThreshold>0</discardingThreshold>
|
||||
<neverBlock>true</neverBlock>
|
||||
<appender-ref ref="MONITOR_LOGGER_WORKER_HEART_BEAT_APPENDER"/>
|
||||
</appender>
|
||||
<logger name="MONITOR_LOGGER_WORKER_HEART_BEAT" level="INFO" additivity="false">
|
||||
<appender-ref ref="ASYNC_MONITOR_LOGGER_WORKER_HEART_BEAT_APPENDER"/>
|
||||
</logger>
|
||||
|
||||
|
||||
<!-- WorkerLogReportEvent -->
|
||||
<appender name="MONITOR_LOGGER_WORKER_LOG_REPORT_APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${MONITOR_LOG_PATH}/worker_log_report.log</file>
|
||||
<encoder>
|
||||
<pattern>${MONITOR_LOG_PATTERN}</pattern>
|
||||
<charset>UTF-8</charset>
|
||||
</encoder>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||
<fileNamePattern>${MONITOR_LOG_PATH}/worker_log_report.log.${ROTATE_PATTERN}</fileNamePattern>
|
||||
<maxHistory>3</maxHistory>
|
||||
<maxFileSize>200MB</maxFileSize>
|
||||
<totalSizeCap>1000MB</totalSizeCap>
|
||||
</rollingPolicy>
|
||||
</appender>
|
||||
<appender name="ASYNC_MONITOR_LOGGER_WORKER_LOG_REPORT_APPENDER" class="ch.qos.logback.classic.AsyncAppender">
|
||||
<queueSize>512</queueSize>
|
||||
<discardingThreshold>0</discardingThreshold>
|
||||
<neverBlock>true</neverBlock>
|
||||
<appender-ref ref="MONITOR_LOGGER_WORKER_LOG_REPORT_APPENDER"/>
|
||||
</appender>
|
||||
<logger name="MONITOR_LOGGER_WORKER_LOG_REPORT" level="INFO" additivity="false">
|
||||
<appender-ref ref="ASYNC_MONITOR_LOGGER_WORKER_LOG_REPORT_APPENDER"/>
|
||||
</logger>
|
||||
|
||||
|
||||
<!-- SlowLockEvent -->
|
||||
<appender name="MONITOR_LOGGER_SLOW_LOCK_APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${MONITOR_LOG_PATH}/lock.log</file>
|
||||
<encoder>
|
||||
<pattern>${MONITOR_LOG_PATTERN}</pattern>
|
||||
<charset>UTF-8</charset>
|
||||
</encoder>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||
<fileNamePattern>${MONITOR_LOG_PATH}/lock.log.${ROTATE_PATTERN}</fileNamePattern>
|
||||
<maxHistory>3</maxHistory>
|
||||
<maxFileSize>200MB</maxFileSize>
|
||||
<totalSizeCap>1000MB</totalSizeCap>
|
||||
</rollingPolicy>
|
||||
</appender>
|
||||
<appender name="ASYNC_MONITOR_LOGGER_SLOW_LOCK_APPENDER" class="ch.qos.logback.classic.AsyncAppender">
|
||||
<queueSize>512</queueSize>
|
||||
<discardingThreshold>0</discardingThreshold>
|
||||
<neverBlock>true</neverBlock>
|
||||
<appender-ref ref="MONITOR_LOGGER_SLOW_LOCK_APPENDER"/>
|
||||
</appender>
|
||||
<logger name="MONITOR_LOGGER_SLOW_LOCK" level="INFO" additivity="false">
|
||||
<appender-ref ref="ASYNC_MONITOR_LOGGER_SLOW_LOCK_APPENDER"/>
|
||||
</logger>
|
||||
|
||||
</included>
|
||||
@ -0,0 +1,38 @@
|
||||
<?xml version="1.0"?>
|
||||
<!-- Configuration for local environment, all logs would print in console. -->
|
||||
<configuration>
|
||||
<!-- Configure color for logs. -->
|
||||
<!-- Classes for rendering color. -->
|
||||
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/>
|
||||
<conversionRule conversionWord="wex"
|
||||
converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
|
||||
<conversionRule conversionWord="wEx"
|
||||
converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
|
||||
<!-- Color log pattern. -->
|
||||
<property name="CONSOLE_LOG_PATTERN"
|
||||
value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{20}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
|
||||
|
||||
<logger name="MONITOR_LOGGER_DB_OPERATION" level="OFF"/>
|
||||
|
||||
<!-- Configuration for console output. -->
|
||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
|
||||
<charset>utf8</charset>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- Quit timing logs, which seems disordered. -->
|
||||
<logger name="tech.powerjob.server.service.timing" level="WARN" additivity="false">
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
</logger>
|
||||
|
||||
<logger name="tech.powerjob" level="DEBUG" additivity="false">
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
</logger>
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
</root>
|
||||
|
||||
</configuration>
|
||||
@ -0,0 +1,78 @@
|
||||
<?xml version="1.0"?>
|
||||
<!-- Configuration for production environment. -->
|
||||
<configuration>
|
||||
|
||||
<!-- Default configuration. -->
|
||||
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
|
||||
<!-- Configuration for console. -->
|
||||
<include resource="org/springframework/boot/logging/logback/console-appender.xml"/>
|
||||
|
||||
<!--
|
||||
Log path, pay attention to permission, logs may be unable to generate.
|
||||
Bug recording: Setting `~/logs`, is unable to create folder in user home directory,
|
||||
a folder with the name ~ is created in project folder.
|
||||
-->
|
||||
<property name="LOG_PATH" value="${user.home}/powerjob/server/logs"/>
|
||||
|
||||
<!-- include other logback configs -->
|
||||
<include resource="logback-config/powerjob_monitor.xml"/>
|
||||
|
||||
<!-- Configuration for ERROR logs. All error logs will write twice. -->
|
||||
<appender name="ERROR_APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${LOG_PATH}/powerjob-server-error.log</file>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<FileNamePattern>${LOG_PATH}/powerjob-server-error.%d{yyyy-MM-dd}.log</FileNamePattern>
|
||||
<MaxHistory>7</MaxHistory>
|
||||
</rollingPolicy>
|
||||
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{20} - %msg%n</pattern>
|
||||
<charset>UTF-8</charset>
|
||||
</encoder>
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<level>ERROR</level>
|
||||
<onMatch>ACCEPT</onMatch>
|
||||
<onMismatch>DENY</onMismatch>
|
||||
</filter>
|
||||
</appender>
|
||||
<!-- End of configuration for ERROR logs. -->
|
||||
|
||||
<!-- Configuration for Web services. -->
|
||||
<appender name="WEB_LOG_APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${LOG_PATH}/powerjob-server-web.log</file>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<FileNamePattern>${LOG_PATH}/powerjob-server-web.%d{yyyy-MM-dd}.log</FileNamePattern>
|
||||
<MaxHistory>7</MaxHistory>
|
||||
</rollingPolicy>
|
||||
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level - %msg%n</pattern>
|
||||
<charset>UTF-8</charset>
|
||||
</encoder>
|
||||
<append>true</append>
|
||||
</appender>
|
||||
<logger name="WEB_LOG" level="INFO" additivity="false">
|
||||
<appender-ref ref="WEB_LOG_APPENDER"/>
|
||||
</logger>
|
||||
<!-- End of configuration for Web services. -->
|
||||
|
||||
<!-- Configuration for system logs. -->
|
||||
<appender name="DEFAULT_APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${LOG_PATH}/powerjob-server-application.log</file>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<FileNamePattern>${LOG_PATH}/powerjob-server-application.%d{yyyy-MM-dd}.log</FileNamePattern>
|
||||
<MaxHistory>7</MaxHistory>
|
||||
</rollingPolicy>
|
||||
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{20} - %msg%n</pattern>
|
||||
<charset>UTF-8</charset>
|
||||
</encoder>
|
||||
<append>true</append>
|
||||
</appender>
|
||||
<!-- End of configuration for system logs. -->
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
<appender-ref ref="DEFAULT_APPENDER"/>
|
||||
<appender-ref ref="ERROR_APPENDER"/>
|
||||
</root>
|
||||
|
||||
</configuration>
|
||||
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 114 KiB |
@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg class="icon" width="81px" height="81.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M571.392 87.074133l-0.631467 0.8704-356.9152 465.92c-10.222933 14.4384-12.544 32.512-3.669333 48.503467l2.013867 3.310933c8.618667 14.677333 23.808 22.766933 40.891733 22.766934l190.122667-0.017067-45.841067 270.08-0.631467 2.679467c-4.437333 21.128533 6.144 39.5776 24.832 48.401066l2.372267 1.024 0.529067 0.324267c5.12 2.696533 10.7008 4.215467 16.4352 4.642133l3.464533 0.136534c13.755733 0 26.368-5.410133 36.078933-15.121067a34.133333 34.133333 0 0 0 3.7376-4.437333L812.032 472.576a34.133333 34.133333 0 0 0 5.632-13.090133c2.048-10.376533 2.048-20.48-0.375467-30.173867a34.133333 34.133333 0 0 0-2.645333-7.133867l-1.6384-3.003733c-8.6528-14.557867-23.773867-22.596267-40.789333-22.596267l-162.833067-0.017066 46.165333-272.093867a34.133333 34.133333 0 0 0 0.477867-5.700267l-0.068267-3.072c-0.9216-18.312533-9.984-34.2016-26.760533-41.2672l-1.143467-0.426666-1.006933-0.597334c-19.694933-10.581333-41.984-4.539733-55.671467 13.6704z m2.833067 108.5952l-38.894934 229.3248-0.341333 2.491734a34.133333 34.133333 0 0 0 33.9968 37.358933l164.898133-0.017067-254.856533 360.379734 38.229333-225.1776 0.341334-2.491734a34.133333 34.133333 0 0 0-33.979734-37.376H294.980267l279.2448-364.4928z" fill="#444444" /><path d="M828.672 559.616a34.133333 34.133333 0 0 1 30.122667 54.698667l-99.874134 135.0656-55.7568-39.389867 100.608-136.055467 0.256-0.3584a34.048 34.048 0 0 1 24.644267-13.943466z" fill="#00B386" /></svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg class="icon" width="81px" height="81.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M484.522667 77.192533l-307.2 134.997334A68.266667 68.266667 0 0 0 136.533333 274.653867V486.229333c0 129.5872 57.9584 245.384533 160.085334 325.188267l176.0256 124.2112a68.266667 68.266667 0 0 0 78.711466 0l174.6944-123.221333C829.508267 731.5968 887.466667 615.799467 887.466667 486.229333V274.670933a68.266667 68.266667 0 0 0-40.789334-62.498133l-307.2-134.997333a68.266667 68.266667 0 0 0-54.954666 0zM204.8 274.670933l307.2-134.997333 307.2 134.997333V486.229333c0 108.305067-48.0768 204.373333-133.853867 271.394134L512 879.837867l-174.6944-123.221334C252.8768 690.551467 204.8 594.500267 204.8 486.1952V274.670933z" fill="#444444" /><path d="M682.666667 358.4l48.264533 48.264533-221.866667 221.866667a34.133333 34.133333 0 0 1-48.264533 0l-136.533333-136.533333L372.5312 443.733333l112.401067 112.384L682.666667 358.4z" fill="#00B386" /></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="icon" href="favicon.ico">
|
||||
<title>PowerJob</title>
|
||||
<link href="js/0.js" rel="prefetch"><link href="js/1.js" rel="prefetch"><link href="js/10.js" rel="prefetch"><link href="js/11.js" rel="prefetch"><link href="js/12.js" rel="prefetch"><link href="js/13.js" rel="prefetch"><link href="js/14.js" rel="prefetch"><link href="js/15.js" rel="prefetch"><link href="js/16.js" rel="prefetch"><link href="js/17.js" rel="prefetch"><link href="js/2.js" rel="prefetch"><link href="js/3.js" rel="prefetch"><link href="js/4.js" rel="prefetch"><link href="js/5.js" rel="prefetch"><link href="js/6.js" rel="prefetch"><link href="js/7.js" rel="prefetch"><link href="js/8.js" rel="prefetch"><link href="js/9.js" rel="prefetch"><link href="js/app.js" rel="preload" as="script"><link href="js/chunk-vendors.js" rel="preload" as="script"></head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but oms-console doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
<script type="text/javascript" src="js/chunk-vendors.js"></script><script type="text/javascript" src="js/app.js"></script></body>
|
||||
</html>
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user