Merge pull request '当请求为Nacos时,Nacos返回的所有非JSON响应都会被自动转换为结构化的错误信息,同时将HTTP状态码设置为200,适合用于规范化微服务架构的响应格式。' (#831) from otto/microservices:third-party-tool-forward into third-party-tool-forward
This commit is contained in:
commit
edb5e65035
|
@ -0,0 +1,46 @@
|
|||
package com.microservices.system.api;
|
||||
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.microservices.common.core.constant.ServiceNameConstants;
|
||||
import com.microservices.system.api.factory.RemoteGatewayFallbackFactory;
|
||||
import feign.Response;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 网关
|
||||
*
|
||||
* @author microservices
|
||||
*/
|
||||
@Component
|
||||
@FeignClient(contextId = "remoteGatewayService", value = ServiceNameConstants.GATEWAY_SERVICE, fallbackFactory = RemoteGatewayFallbackFactory.class)
|
||||
public interface RemoteGatewayService {
|
||||
/**
|
||||
* 登录Sentinel
|
||||
*
|
||||
* @return 响应
|
||||
*/
|
||||
@PostMapping("/sentinel/auth/login")
|
||||
Response loginSentinel(@RequestParam("username") String username, @RequestParam("password") String password);
|
||||
|
||||
/**
|
||||
* 登录Nacos
|
||||
*
|
||||
* @return 响应
|
||||
*/
|
||||
@PostMapping(value = "/nacos/v1/auth/users/login", consumes = {"application/x-www-form-urlencoded"})
|
||||
Response loginNacos(Map<String, ?> formParams);
|
||||
|
||||
/**
|
||||
* 登录Portainer
|
||||
*
|
||||
* @return 响应
|
||||
*/
|
||||
@PostMapping("/portainer/api/auth")
|
||||
Response loginPortainer(@RequestBody JSONObject loginBody);
|
||||
}
|
|
@ -4,7 +4,7 @@ import com.microservices.common.core.constant.SecurityConstants;
|
|||
import com.microservices.common.core.constant.ServiceNameConstants;
|
||||
import com.microservices.common.core.domain.R;
|
||||
import com.microservices.system.api.domain.SysDept;
|
||||
import com.microservices.system.api.factory.RemoteCmsFallbackFactory;
|
||||
import com.microservices.system.api.factory.RemoteZoneFallbackFactory;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
|
@ -15,7 +15,7 @@ import java.util.List;
|
|||
*
|
||||
* @author microservices
|
||||
*/
|
||||
@FeignClient(contextId = "remoteZoneService", value = ServiceNameConstants.ZONE_SERVICE, fallbackFactory = RemoteCmsFallbackFactory.class)
|
||||
@FeignClient(contextId = "remoteZoneService", value = ServiceNameConstants.ZONE_SERVICE, fallbackFactory = RemoteZoneFallbackFactory.class)
|
||||
public interface RemoteZoneService {
|
||||
/**
|
||||
* 新增组织时自动创建特色专区
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
package com.microservices.system.api.factory;
|
||||
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.microservices.system.api.RemoteGatewayService;
|
||||
import feign.Response;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.cloud.openfeign.FallbackFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 用户服务降级处理
|
||||
*
|
||||
* @author microservices
|
||||
*/
|
||||
@Component
|
||||
public class RemoteGatewayFallbackFactory implements FallbackFactory<RemoteGatewayService> {
|
||||
private static final Logger log = LoggerFactory.getLogger(RemoteGatewayFallbackFactory.class);
|
||||
|
||||
@Override
|
||||
public RemoteGatewayService create(Throwable throwable) {
|
||||
log.error("网关服务调用失败:{}", throwable.getMessage());
|
||||
return new RemoteGatewayService() {
|
||||
|
||||
@Override
|
||||
public Response loginSentinel(String password, String username) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response loginNacos(Map<String, ?> formParams) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response loginPortainer(JSONObject loginBody) {
|
||||
System.out.println(throwable.getMessage());
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -5,3 +5,4 @@ com.microservices.system.api.factory.RemoteFileFallbackFactory
|
|||
com.microservices.system.api.factory.RemoteCmsFallbackFactory
|
||||
com.microservices.system.api.factory.RemoteZoneFallbackFactory
|
||||
com.microservices.system.api.factory.RemotePmsFallbackFactory
|
||||
com.microservices.system.api.factory.RemoteGatewayFallbackFactory
|
||||
|
|
|
@ -208,6 +208,22 @@ public class CacheConstants {
|
|||
return GITLINK_ORG_ID_OPEN_ENTERPRISE_KEY + gitlinkOrgId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sentinel Token缓存Key
|
||||
*/
|
||||
public final static String SENTINEL_TOKEN = "sentinel_token";
|
||||
|
||||
|
||||
/**
|
||||
* Nacos Token缓存Key
|
||||
*/
|
||||
public static final String NACOS_TOKEN = "nacos_token";
|
||||
|
||||
/**
|
||||
* Portainer Token缓存Key
|
||||
*/
|
||||
public static final String PORTAINER_TOKEN = "portainer_token";
|
||||
|
||||
/**
|
||||
* 专区项目Gitlink信息 Key前缀
|
||||
*/
|
||||
|
|
|
@ -32,4 +32,8 @@ public class ServiceNameConstants {
|
|||
* 项目管理模块的serviceid
|
||||
*/
|
||||
public static final String PMS_SERVICE = "microservices-pms";
|
||||
/**
|
||||
* 网关模块的serviceid
|
||||
*/
|
||||
public static final String GATEWAY_SERVICE = "microservices-gateway";
|
||||
}
|
||||
|
|
|
@ -18,6 +18,14 @@ public class TokenConstants {
|
|||
* GitLink令牌标识
|
||||
*/
|
||||
public static final String GitLink_Token_Key = "autologin_trustie=";
|
||||
/**
|
||||
* Sentinel令牌标识
|
||||
*/
|
||||
public static final String Sentinel_Token_Key = "sentinel_dashboard_cookie";
|
||||
/**
|
||||
* Sentinel令牌标识
|
||||
*/
|
||||
public static final String Nacos_Token_Key = "Accesstoken";
|
||||
/**
|
||||
* Cookie中令牌标识
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
package com.microservices.common.core.utils;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
public class CookieUtil {
|
||||
|
||||
|
||||
public static HashMap<String, String> getCookieMap(String cookie) {
|
||||
HashMap<String, String> map = new HashMap<>();
|
||||
if (cookie != null) {
|
||||
String[] cookies = cookie.split(";");
|
||||
for (String cookieStr : cookies) {
|
||||
String[] cookieArr = cookieStr.split("=");
|
||||
String key = cookieArr[0].trim();
|
||||
String value = cookieArr[1].trim();
|
||||
map.put(key, value);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
public static String getCookieValue(String cookie, String key) {
|
||||
HashMap<String, String> map = getCookieMap(cookie);
|
||||
return map.get(key);
|
||||
}
|
||||
|
||||
public static String removeCookieKey(String cookie, String key) {
|
||||
HashMap<String, String> map = getCookieMap(cookie);
|
||||
map.remove(key);
|
||||
return genCookieStr(map);
|
||||
}
|
||||
|
||||
private static String genCookieStr(HashMap<String, String> cookieMap) {
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
for (String key : cookieMap.keySet()) {
|
||||
stringBuilder.append(key).append("=").append(cookieMap.get(key)).append(";");
|
||||
}
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
}
|
|
@ -1,9 +1,16 @@
|
|||
package com.microservices.common.security.feign;
|
||||
|
||||
import feign.RequestInterceptor;
|
||||
import feign.codec.Encoder;
|
||||
import feign.form.FormEncoder;
|
||||
import org.springframework.beans.factory.ObjectFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
|
||||
import org.springframework.cloud.openfeign.support.SpringEncoder;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
|
||||
/**
|
||||
* Feign 配置注册
|
||||
*
|
||||
|
@ -12,9 +19,20 @@ import org.springframework.context.annotation.Configuration;
|
|||
@Configuration
|
||||
public class FeignAutoConfiguration
|
||||
{
|
||||
// 这里会由容器自动注入HttpMessageConverters的对象工厂
|
||||
@Autowired
|
||||
private ObjectFactory<HttpMessageConverters> messageConverters;
|
||||
@Bean
|
||||
public RequestInterceptor requestInterceptor()
|
||||
{
|
||||
return new FeignRequestInterceptor();
|
||||
}
|
||||
|
||||
// new一个form编码器,实现支持form表单提交
|
||||
// 注意这里方法名称,也就是bean的名称是什么不重要,
|
||||
// 重要的是返回类型要是 Encoder 并且实现类必须是 FormEncoder 或者其子类
|
||||
@Bean
|
||||
public Encoder feignFormEncoder() {
|
||||
return new FormEncoder(new SpringEncoder(messageConverters));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
package com.microservices.gateway;
|
||||
|
||||
import com.microservices.common.core.exception.ServiceException;
|
||||
import com.microservices.common.core.threadPool.ThreadPoolExecutorWrap;
|
||||
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* @author OTTO
|
||||
*/
|
||||
public class CustomExecutorFactory {
|
||||
/**
|
||||
* 创建线程池
|
||||
* 存在并发处理的情况,设置核心线程为2,设置有界队列长度为100,最大线程数为10
|
||||
* 当超出队列已满且达到最大线程数时抛出异常
|
||||
* Ncpu=CPU数量
|
||||
* Ucpu=目标CPU的使用率,0<=Ucpu<=1
|
||||
* W/C=任务等待时间与任务计算时间的比率
|
||||
* Nthreads =Ncpu*Ucpu*(1+W/C)
|
||||
*/
|
||||
public static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutorWrap(
|
||||
4
|
||||
, 10
|
||||
, 10
|
||||
, TimeUnit.SECONDS
|
||||
, new ArrayBlockingQueue<>(100)
|
||||
, Executors.defaultThreadFactory()
|
||||
, (r, executor) -> {
|
||||
throw new ServiceException("目前处理的人太多了,请稍后再试");
|
||||
}
|
||||
);
|
||||
}
|
|
@ -2,13 +2,18 @@ package com.microservices.gateway.config;
|
|||
|
||||
import feign.codec.Decoder;
|
||||
import org.springframework.beans.factory.ObjectFactory;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
|
||||
import org.springframework.cloud.openfeign.support.ResponseEntityDecoder;
|
||||
import org.springframework.cloud.openfeign.support.SpringDecoder;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author otto
|
||||
*/
|
||||
|
@ -25,4 +30,10 @@ public class FeignConfig {
|
|||
(new MappingJackson2HttpMessageConverter());
|
||||
return () -> httpMessageConverters;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public HttpMessageConverters messageConverters(ObjectProvider<HttpMessageConverter<?>> converters) {
|
||||
return new HttpMessageConverters(converters.orderedStream().collect(Collectors.toList()));
|
||||
}
|
||||
}
|
|
@ -10,7 +10,9 @@ import com.microservices.common.core.utils.JwtUtils;
|
|||
import com.microservices.common.core.utils.ServletUtils;
|
||||
import com.microservices.common.core.utils.StringUtils;
|
||||
import com.microservices.common.redis.service.RedisService;
|
||||
import com.microservices.gateway.CustomExecutorFactory;
|
||||
import com.microservices.gateway.config.properties.IgnoreWhiteProperties;
|
||||
import com.microservices.gateway.service.ThirdPartyToolService;
|
||||
import com.microservices.system.api.RemoteUserService;
|
||||
import com.microservices.system.api.domain.SysUserDeptRole;
|
||||
import com.microservices.system.api.utils.FeignUtils;
|
||||
|
@ -28,9 +30,8 @@ import org.springframework.web.server.ServerWebExchange;
|
|||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
|
||||
/**
|
||||
* 网关鉴权
|
||||
|
@ -51,7 +52,12 @@ public class AuthFilter implements GlobalFilter, Ordered {
|
|||
@Autowired
|
||||
private RemoteUserService remoteUserService;
|
||||
|
||||
ExecutorService executorService = Executors.newFixedThreadPool(1);
|
||||
@Lazy
|
||||
@Autowired
|
||||
private ThirdPartyToolService thirdPartyToolService;
|
||||
|
||||
ThreadPoolExecutor threadPoolExecutor = CustomExecutorFactory.threadPoolExecutor;
|
||||
|
||||
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
||||
|
@ -75,6 +81,9 @@ public class AuthFilter implements GlobalFilter, Ordered {
|
|||
}
|
||||
|
||||
if (StringUtils.isEmpty(token)) {
|
||||
if (StringUtils.isNotEmpty(gitLinkCookie)) {
|
||||
return unauthorizedResponse(exchange, "令牌已过期或验证不正确!");
|
||||
}
|
||||
return unauthorizedResponse(exchange, "令牌不能为空");
|
||||
}
|
||||
Claims claims;
|
||||
|
@ -105,6 +114,11 @@ public class AuthFilter implements GlobalFilter, Ordered {
|
|||
ServletUtils.addHeader(mutate, SecurityConstants.DETAILS_USERNAME, username);
|
||||
// 内部请求来源参数清除
|
||||
ServletUtils.removeHeader(mutate, SecurityConstants.FROM_SOURCE);
|
||||
|
||||
Mono<Void> thirdPartyRes = thirdPartyToolService.handleRequestForThirdPartyTools(exchange, request, mutate);
|
||||
if (thirdPartyRes != null) {
|
||||
return thirdPartyRes;
|
||||
}
|
||||
return chain.filter(exchange.mutate().request(mutate.build()).build());
|
||||
}
|
||||
|
||||
|
@ -116,7 +130,7 @@ public class AuthFilter implements GlobalFilter, Ordered {
|
|||
boolean hasUserIdentifyList = redisService.hasKey(redisKey);
|
||||
if (!hasUserIdentifyList) {
|
||||
// 网关采用异步架构,所以此处需要通过异步请求获取本系统Token
|
||||
Future<R<List<SysUserDeptRole>>> future = executorService.submit(
|
||||
Future<R<List<SysUserDeptRole>>> future = threadPoolExecutor.submit(
|
||||
() -> remoteUserService.getSysUserDeptRoleListByUserName(username, SecurityConstants.INNER));
|
||||
try {
|
||||
List<SysUserDeptRole> feignResult = FeignUtils.getReturnData(
|
||||
|
@ -130,7 +144,7 @@ public class AuthFilter implements GlobalFilter, Ordered {
|
|||
}
|
||||
|
||||
private String genCookieByToken(String token) {
|
||||
return String.format("%s%s;",TokenConstants.GitLink_Token_Key, token);
|
||||
return String.format("%s%s;", TokenConstants.GitLink_Token_Key, token);
|
||||
}
|
||||
|
||||
private String getGitLinkRequestCookie(ServerHttpRequest request) {
|
||||
|
@ -141,7 +155,7 @@ public class AuthFilter implements GlobalFilter, Ordered {
|
|||
String token = request.getHeaders().getFirst(TokenConstants.AUTHENTICATION);
|
||||
if (token != null) {
|
||||
// 网关采用异步架构,所以此处需要通过异步请求获取本系统Token
|
||||
Future<R<Boolean>> future = executorService.submit(
|
||||
Future<R<Boolean>> future = threadPoolExecutor.submit(
|
||||
() -> remoteUserService.checkGitLinkUserLogin(token, SecurityConstants.INNER));
|
||||
try {
|
||||
Boolean feignResult = FeignUtils.getReturnData(
|
||||
|
@ -194,7 +208,7 @@ public class AuthFilter implements GlobalFilter, Ordered {
|
|||
private String getGitLinkToken(String cookie) {
|
||||
if (StringUtils.isNotEmpty(cookie)) {
|
||||
// 网关采用异步架构,所以此处需要通过异步请求获取本系统Token
|
||||
Future<R<String>> future = executorService.submit(() -> remoteUserService
|
||||
Future<R<String>> future = threadPoolExecutor.submit(() -> remoteUserService
|
||||
.getSysUserTokenByGitLinkCookie(cookie, SecurityConstants.INNER));
|
||||
R<String> feignResult;
|
||||
try {
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
package com.microservices.gateway.filter;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.microservices.common.core.web.domain.AjaxResult;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.reactivestreams.Publisher;
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
||||
import org.springframework.cloud.gateway.filter.GlobalFilter;
|
||||
import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.io.buffer.DataBufferFactory;
|
||||
import org.springframework.core.io.buffer.DataBufferUtils;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class GlobalResponseFilter implements GlobalFilter, Ordered {
|
||||
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
|
||||
String url = request.getURI().getPath();
|
||||
// 当请求为Nacos时,Nacos返回的所有非JSON响应都会被自动转换为结构化的错误信息,同时将HTTP状态码设置为200,适合用于规范化微服务架构的响应格式。
|
||||
if (url.toLowerCase().startsWith("/nacos")) {
|
||||
ServerHttpResponse originalResponse = exchange.getResponse();
|
||||
DataBufferFactory bufferFactory = originalResponse.bufferFactory();
|
||||
|
||||
ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {
|
||||
@Override
|
||||
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
|
||||
MediaType contentType = getHeaders().getContentType();
|
||||
HttpStatus status = getStatusCode() != null ? getStatusCode() : HttpStatus.INTERNAL_SERVER_ERROR;
|
||||
|
||||
// 排除无内容响应和二进制类型
|
||||
if (status == HttpStatus.NO_CONTENT ||
|
||||
(contentType != null && (contentType.includes(MediaType.APPLICATION_OCTET_STREAM) ||
|
||||
contentType.includes(MediaType.MULTIPART_FORM_DATA)))) {
|
||||
return super.writeWith(body);
|
||||
}
|
||||
|
||||
// 保留原始状态码
|
||||
originalResponse.setStatusCode(HttpStatus.OK);
|
||||
getHeaders().setContentType(MediaType.APPLICATION_JSON);
|
||||
|
||||
return Flux.from(body).<String>handle((dataBuffer, synchronousSink) -> {
|
||||
byte[] bytes = new byte[dataBuffer.readableByteCount()];
|
||||
dataBuffer.read(bytes);
|
||||
//释放掉内存
|
||||
DataBufferUtils.release(dataBuffer);
|
||||
String result = new String(bytes, StandardCharsets.UTF_8);
|
||||
if (!JSON.isValid(result)) {
|
||||
synchronousSink.next(result);
|
||||
} else {
|
||||
synchronousSink.complete();
|
||||
}
|
||||
|
||||
}).flatMap(resBody -> {
|
||||
byte[] bytes = JSON.toJSONBytes(AjaxResult.error(resBody));
|
||||
|
||||
getHeaders().setContentLength(bytes.length);
|
||||
DataBuffer buffer = bufferFactory.wrap(bytes);
|
||||
return super.writeWith(Mono.just(buffer));
|
||||
}).then();
|
||||
}
|
||||
};
|
||||
|
||||
return chain.filter(exchange.mutate().response(decoratedResponse).build());
|
||||
}
|
||||
return chain.filter(exchange);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
//WRITE_RESPONSE_FILTER 之前执行
|
||||
return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 1;
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
package com.microservices.gateway.handler;
|
||||
|
||||
import com.microservices.common.core.exception.ServiceException;
|
||||
import com.microservices.common.core.utils.ServletUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -19,33 +20,27 @@ import reactor.core.publisher.Mono;
|
|||
*/
|
||||
@Order(-1)
|
||||
@Configuration
|
||||
public class GatewayExceptionHandler implements ErrorWebExceptionHandler
|
||||
{
|
||||
public class GatewayExceptionHandler implements ErrorWebExceptionHandler {
|
||||
private static final Logger log = LoggerFactory.getLogger(GatewayExceptionHandler.class);
|
||||
|
||||
@Override
|
||||
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex)
|
||||
{
|
||||
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
|
||||
ServerHttpResponse response = exchange.getResponse();
|
||||
|
||||
if (exchange.getResponse().isCommitted())
|
||||
{
|
||||
if (exchange.getResponse().isCommitted()) {
|
||||
return Mono.error(ex);
|
||||
}
|
||||
|
||||
String msg;
|
||||
|
||||
if (ex instanceof NotFoundException)
|
||||
{
|
||||
if (ex instanceof NotFoundException) {
|
||||
msg = "服务未找到";
|
||||
}
|
||||
else if (ex instanceof ResponseStatusException)
|
||||
{
|
||||
} else if (ex instanceof ServiceException) {
|
||||
msg = ex.getMessage();
|
||||
} else if (ex instanceof ResponseStatusException) {
|
||||
ResponseStatusException responseStatusException = (ResponseStatusException) ex;
|
||||
msg = responseStatusException.getMessage();
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
msg = "内部服务器错误";
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
package com.microservices.gateway.service;
|
||||
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* 第三方工具服务类
|
||||
*
|
||||
* @author OTTO
|
||||
*/
|
||||
public interface ThirdPartyToolService {
|
||||
|
||||
/**
|
||||
* 处理第三方工具请求
|
||||
*
|
||||
* @param exchange
|
||||
* @param request 请求
|
||||
* @param mutate 请求头获取器
|
||||
*/
|
||||
Mono<Void> handleRequestForThirdPartyTools(ServerWebExchange exchange, ServerHttpRequest request, ServerHttpRequest.Builder mutate);
|
||||
}
|
|
@ -0,0 +1,189 @@
|
|||
package com.microservices.gateway.service.impl;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.microservices.common.core.constant.CacheConstants;
|
||||
import com.microservices.common.core.constant.TokenConstants;
|
||||
import com.microservices.common.core.exception.ServiceException;
|
||||
import com.microservices.common.core.utils.CookieUtil;
|
||||
import com.microservices.common.core.utils.ServletUtils;
|
||||
import com.microservices.common.core.utils.StringUtils;
|
||||
import com.microservices.common.redis.service.RedisService;
|
||||
import com.microservices.gateway.CustomExecutorFactory;
|
||||
import com.microservices.gateway.service.ThirdPartyToolService;
|
||||
import com.microservices.system.api.RemoteGatewayService;
|
||||
import feign.Response;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
/**
|
||||
* @author OTTO
|
||||
*/
|
||||
@Service
|
||||
public class ThirdPartyToolServiceImpl implements ThirdPartyToolService {
|
||||
private static final Logger log = LoggerFactory.getLogger(ThirdPartyToolServiceImpl.class);
|
||||
@Value("${thirdPartyTools.nacos.auth.username:}")
|
||||
public String nacosUsername;
|
||||
@Value("${thirdPartyTools.nacos.auth.password:}")
|
||||
public String nacosPassword;
|
||||
@Value("${thirdPartyTools.sentinel.auth.username:}")
|
||||
public String sentinelUsername;
|
||||
@Value("${thirdPartyTools.sentinel.auth.password:}")
|
||||
public String sentinelPassword;
|
||||
@Value("${thirdPartyTools.portainer.auth.username:}")
|
||||
public String portainerUsername;
|
||||
@Value("${thirdPartyTools.portainer.auth.password:}")
|
||||
public String portainerPassword;
|
||||
ThreadPoolExecutor threadPoolExecutor = CustomExecutorFactory.threadPoolExecutor;
|
||||
@Lazy
|
||||
@Autowired
|
||||
private RemoteGatewayService remoteGatewayService;
|
||||
@Autowired
|
||||
private RedisService redisService;
|
||||
|
||||
/**
|
||||
* 处理第三方工具请求
|
||||
*/
|
||||
@Override
|
||||
public Mono<Void> handleRequestForThirdPartyTools(ServerWebExchange exchange, ServerHttpRequest request, ServerHttpRequest.Builder mutate) {
|
||||
String url = request.getURI().getPath();
|
||||
if (StringUtils.isEmpty(url)) {
|
||||
return null;
|
||||
}
|
||||
// 处理Sentinel请求
|
||||
if (url.toLowerCase().startsWith("/sentinel")) {
|
||||
// 检查Cookie中是否携带sentinel cookie
|
||||
String cookie = request.getHeaders().getFirst(TokenConstants.Cookie);
|
||||
if (StringUtils.isNotEmpty(CookieUtil.getCookieValue(cookie, TokenConstants.Sentinel_Token_Key))) {
|
||||
cookie = CookieUtil.removeCookieKey(cookie, TokenConstants.Sentinel_Token_Key);
|
||||
}
|
||||
String sentinelCookie = null;
|
||||
if (redisService.hasKey(CacheConstants.SENTINEL_TOKEN)) {
|
||||
sentinelCookie = redisService.getCacheObject(CacheConstants.SENTINEL_TOKEN);
|
||||
} else {
|
||||
// 网关采用异步架构,所以此处需要通过异步请求获取Sentinel Token
|
||||
Future<Response> future = threadPoolExecutor.submit(() -> remoteGatewayService.loginSentinel(sentinelUsername, sentinelPassword));
|
||||
try {
|
||||
Response response = future.get(1, TimeUnit.SECONDS);
|
||||
Map<String, Collection<String>> header = response.headers();
|
||||
if (header != null && header.containsKey("set-cookie")) {
|
||||
sentinelCookie = header.get("set-cookie").iterator().next();
|
||||
redisService.setCacheObject(CacheConstants.SENTINEL_TOKEN, sentinelCookie, 12L, TimeUnit.HOURS);
|
||||
}
|
||||
} catch (TimeoutException e) {
|
||||
log.error("获取Sentinel Token超时");
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
log.error("获取Sentinel Token失败:{}", e.getMessage());
|
||||
}
|
||||
}
|
||||
if (StringUtils.isNotEmpty(sentinelCookie)) {
|
||||
ServletUtils.removeHeader(mutate, TokenConstants.Cookie);
|
||||
mutate.header(TokenConstants.Cookie, String.format("%s;%s;", cookie, sentinelCookie));
|
||||
} else {
|
||||
throw new ServiceException("Sentinel服务获取Token失败");
|
||||
}
|
||||
}
|
||||
// 处理Nacos请求
|
||||
if (url.toLowerCase().startsWith("/nacos")) {
|
||||
String nacosToken = null;
|
||||
if (redisService.hasKey(CacheConstants.NACOS_TOKEN)) {
|
||||
nacosToken = redisService.getCacheObject(CacheConstants.NACOS_TOKEN);
|
||||
} else {
|
||||
Map<String, String> nacosMap = new HashMap<>();
|
||||
nacosMap.put("username", nacosUsername);
|
||||
nacosMap.put("password", nacosPassword);
|
||||
// 网关采用异步架构,所以此处需要通过异步请求获取Nacos Token
|
||||
Future<Response> future = threadPoolExecutor.submit(() -> remoteGatewayService.loginNacos(nacosMap));
|
||||
try {
|
||||
Response res = future.get(1, TimeUnit.SECONDS);
|
||||
StringWriter writer = new StringWriter();
|
||||
IOUtils.copy(res.body().asInputStream(), writer, StandardCharsets.UTF_8.name());
|
||||
String str = writer.toString();
|
||||
if (!JSON.isValid(str)) {
|
||||
return exceptionResponse(exchange, str, res.status());
|
||||
}
|
||||
JSONObject resJsonObject = JSONObject.parseObject(str);
|
||||
nacosToken = resJsonObject.getString("accessToken");
|
||||
Long tokenTtl = resJsonObject.getLong("tokenTtl");
|
||||
if (nacosToken != null && tokenTtl != null) {
|
||||
redisService.setCacheObject(CacheConstants.NACOS_TOKEN, nacosToken, tokenTtl - 10, TimeUnit.SECONDS);
|
||||
}
|
||||
} catch (TimeoutException e) {
|
||||
log.error("获取Nacos Token超时");
|
||||
} catch (InterruptedException | ExecutionException | IOException | NullPointerException e) {
|
||||
log.error("获取Nacos Token失败:{}", e.getMessage());
|
||||
}
|
||||
}
|
||||
if (StringUtils.isNotEmpty(nacosToken)) {
|
||||
mutate.header(TokenConstants.Nacos_Token_Key, nacosToken);
|
||||
} else {
|
||||
throw new ServiceException("Nacos服务获取Token失败");
|
||||
}
|
||||
}
|
||||
|
||||
// 处理portainer请求
|
||||
if (url.toLowerCase().startsWith("/portainer")) {
|
||||
String portainerToken = null;
|
||||
if (redisService.hasKey(CacheConstants.PORTAINER_TOKEN)) {
|
||||
portainerToken = redisService.getCacheObject(CacheConstants.PORTAINER_TOKEN);
|
||||
} else {
|
||||
// 网关采用异步架构,所以此处需要通过异步请求获取Portainer Token
|
||||
JSONObject loginPortainerBody = new JSONObject();
|
||||
loginPortainerBody.put("username", portainerUsername);
|
||||
loginPortainerBody.put("password", portainerPassword);
|
||||
Future<Response> future = threadPoolExecutor.submit(() -> remoteGatewayService.loginPortainer(loginPortainerBody));
|
||||
try {
|
||||
Response res = future.get(1, TimeUnit.SECONDS);
|
||||
StringWriter writer = new StringWriter();
|
||||
IOUtils.copy(res.body().asInputStream(), writer, StandardCharsets.UTF_8.name());
|
||||
String str = writer.toString();
|
||||
if (!JSON.isValid(str)) {
|
||||
return exceptionResponse(exchange, str, res.status());
|
||||
}
|
||||
JSONObject resJsonObject = JSONObject.parseObject(str);
|
||||
portainerToken = resJsonObject.getString("jwt");
|
||||
String[] tokenSplit = portainerToken.split("\\.");
|
||||
String tokenBodyStr = new String(Base64.getDecoder().decode(tokenSplit[1]), StandardCharsets.UTF_8);
|
||||
JSONObject tokenBody = JSONObject.parseObject(tokenBodyStr);
|
||||
Long expiration = tokenBody.getLong("exp");
|
||||
Long now = new Date().getTime() / 1000;
|
||||
|
||||
long portainerTokenTtl = expiration - now;
|
||||
if (portainerTokenTtl > 20) {
|
||||
redisService.setCacheObject(CacheConstants.PORTAINER_TOKEN, portainerToken, portainerTokenTtl - 10, TimeUnit.SECONDS);
|
||||
}
|
||||
} catch (TimeoutException e) {
|
||||
log.error("获取Portainer Token超时");
|
||||
} catch (InterruptedException | ExecutionException | IOException | NullPointerException e) {
|
||||
log.error("获取Portainer Token失败:{}", e.getMessage());
|
||||
}
|
||||
}
|
||||
if (StringUtils.isNotEmpty(portainerToken)) {
|
||||
mutate.header(TokenConstants.AUTHENTICATION, TokenConstants.PREFIX + portainerToken);
|
||||
} else {
|
||||
throw new ServiceException("Portainer服务获取Token失败");
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
private Mono<Void> exceptionResponse(ServerWebExchange exchange, String msg, int code) {
|
||||
log.error("[第三方工具请求处理异常]请求路径:{}", exchange.getRequest().getPath());
|
||||
return ServletUtils.webFluxResponseWriter(exchange.getResponse(), msg, code);
|
||||
}
|
||||
}
|
|
@ -204,6 +204,14 @@ public class PmsCiPipelinesController extends BaseController
|
|||
return success(pmsCiPipelinesService.savePipelineYamlByGraphicJson(id, pmsCiPipelineBuildInputVo));
|
||||
}
|
||||
|
||||
@PostMapping(value = "/{id}/savePipelineYamlNew")
|
||||
@ApiOperation("流水线图形构建声明式yaml并保存")
|
||||
public AjaxResult savePipelineYamlNew(@ApiParam(name = "id", value = "流水线id")@PathVariable Long id,
|
||||
@RequestBody @Validated PmsCiPipelineBuildYamlInputVo pmsCiPipelineBuildInputVo)
|
||||
{
|
||||
return success(pmsCiPipelinesService.savePipelineYamlByGraphicJsonNew(id, pmsCiPipelineBuildInputVo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动流水线执行记录任务
|
||||
*/
|
||||
|
@ -251,4 +259,16 @@ public class PmsCiPipelinesController extends BaseController
|
|||
pmsCiPipelinesService.getPipelineRunJobLogs(id, run, job, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 流水线执行测试报告结果查询
|
||||
*/
|
||||
@GetMapping(value = "/{id}/actions/runs/{run}/results")
|
||||
@ApiOperation("流水线执行测试报告结果查询")
|
||||
public AjaxResult getPipelineRunResults(@ApiParam(name = "id", value = "流水线id")@PathVariable Long id,
|
||||
@ApiParam(name = "run", value = "执行记录序号")@PathVariable String run)
|
||||
{
|
||||
return success(pmsCiPipelinesService.getPipelineRunResults(id, run));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,148 @@
|
|||
package com.microservices.pms.pipeline.domain.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public class NodeActionVo {
|
||||
|
||||
private String name;
|
||||
private final Map<String, Object> on = new LinkedHashMap<>();
|
||||
private final Map<String, Object> jobs = new LinkedHashMap<>();
|
||||
@JsonIgnore
|
||||
private Map<String, Object> params = new LinkedHashMap<>();
|
||||
|
||||
public void buildName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public void parse(List<Map<String, Object>> steps, PipelineVo.PipelineJson.Nodes node){
|
||||
String nodeName = Optional.ofNullable(node.getName()).orElse("");
|
||||
if (nodeName.startsWith("on-")) {
|
||||
buildOn(node);
|
||||
}else if (nodeName.startsWith("setup-")) {
|
||||
buildSetupStep(steps, node);
|
||||
}else if (Objects.equals(nodeName,"shell")) {
|
||||
buildShellStep(steps, node);
|
||||
}else if (StringUtils.isNotEmpty(nodeName)) {
|
||||
buildOtherStep(steps, node);
|
||||
}
|
||||
}
|
||||
|
||||
public void buildOn(PipelineVo.PipelineJson.Nodes node) {
|
||||
String nodeName = node.getName();
|
||||
Map<String, Object> step = new LinkedHashMap<>();
|
||||
List<PipelineVo.PipelineJson.Nodes.Inputs> inputs = getInputs(node);
|
||||
|
||||
if (Objects.equals(nodeName, "on-schedule")) {
|
||||
inputs.forEach(e -> {
|
||||
step.put(e.getName(), e.getValue());
|
||||
});
|
||||
this.on.put(nodeName.split("-")[1], step);
|
||||
return;
|
||||
}
|
||||
|
||||
inputs.forEach(e -> {
|
||||
step.put(e.getName(), e.getValue().replace("\'","").split(","));
|
||||
});
|
||||
|
||||
this.on.put(nodeName.split("-")[1], step);
|
||||
}
|
||||
|
||||
public void buildJobs(String jobKey, String jobName, List<Map<String, Object>> steps) {
|
||||
if (steps.isEmpty()) return;
|
||||
Map<String, Object> job = new LinkedHashMap<>();
|
||||
job.put("runs-on", "ubuntu-latest");
|
||||
job.put("name", jobName);
|
||||
job.put("steps", steps);
|
||||
this.jobs.put(jobKey+(this.jobs.size()), job);
|
||||
}
|
||||
|
||||
public void buildSetupStep(List<Map<String, Object>> steps, PipelineVo.PipelineJson.Nodes node) {
|
||||
String nodeName = node.getName();
|
||||
Map<String, Object> step = new LinkedHashMap<>();
|
||||
|
||||
List<PipelineVo.PipelineJson.Nodes.Inputs> inputs = getInputs(node);
|
||||
|
||||
if (nodeName.startsWith("setup-java")) {
|
||||
|
||||
Map<String, String> name2Value = inputs.stream()
|
||||
.collect(Collectors.toMap(e -> e.getName(), e -> e.getValue(), (o, n) -> n));
|
||||
|
||||
Map<String, Object> with = new LinkedHashMap<>();
|
||||
if (name2Value.containsKey("download_url") && StringUtils.isNotBlank(name2Value.get("download_url"))) {
|
||||
steps.add(Collections.singletonMap("run", "download_url=" + name2Value.get("download_url") + " && " + "wget -O $RUNNER_TEMP/java_package.tar.gz $download_url"));
|
||||
with.put("distribution", "jdkfile");
|
||||
with.put("jdkFile", "${{ runner.temp }}/java_package.tar.gz");
|
||||
}
|
||||
step.put("name", node.getLabel());
|
||||
step.put("uses", node.getFull_name().trim());
|
||||
if (!inputs.isEmpty()) {
|
||||
|
||||
inputs.forEach(i -> {
|
||||
with.put(i.getName(), Optional.ofNullable(i.getValue()).orElse(""));
|
||||
});
|
||||
|
||||
step.put("with", with);
|
||||
}
|
||||
steps.add(step);
|
||||
return;
|
||||
}
|
||||
|
||||
step.put("name", node.getLabel());
|
||||
step.put("uses", node.getFull_name().trim());
|
||||
if (!inputs.isEmpty()) {
|
||||
Map<String, Object> with = new LinkedHashMap<>();
|
||||
inputs.forEach(i -> {
|
||||
with.put(i.getName(), Optional.ofNullable(i.getValue()).orElse(""));
|
||||
});
|
||||
|
||||
step.put("with", with);
|
||||
}
|
||||
steps.add(step);
|
||||
|
||||
}
|
||||
|
||||
public void buildShellStep(List<Map<String, Object>> steps, PipelineVo.PipelineJson.Nodes node) {
|
||||
Map<String, Object> step = new LinkedHashMap<>();
|
||||
step.put("name", node.getLabel());
|
||||
List<PipelineVo.PipelineJson.Nodes.Inputs> inputs = Optional.ofNullable(node.getInputs()).orElse(new ArrayList<>());
|
||||
inputs.forEach(i -> {
|
||||
step.put(i.getName(), Optional.ofNullable(i.getValue()).orElse(""));
|
||||
});
|
||||
|
||||
steps.add(step);
|
||||
}
|
||||
|
||||
public void buildOtherStep(List<Map<String, Object>> steps, PipelineVo.PipelineJson.Nodes node) {
|
||||
Map<String, Object> step = new LinkedHashMap<>();
|
||||
|
||||
List<PipelineVo.PipelineJson.Nodes.Inputs> inputs = getInputs(node);
|
||||
|
||||
step.put("name", node.getLabel());
|
||||
step.put("uses", node.getFull_name().trim());
|
||||
if (!inputs.isEmpty()) {
|
||||
Map<String, Object> with = new LinkedHashMap<>();
|
||||
inputs.forEach(i -> {
|
||||
with.put(i.getName(), Optional.ofNullable(i.getValue()).orElse(""));
|
||||
});
|
||||
|
||||
step.put("with", with);
|
||||
}
|
||||
steps.add(step);
|
||||
}
|
||||
|
||||
private List<PipelineVo.PipelineJson.Nodes.Inputs> getInputs(PipelineVo.PipelineJson.Nodes node) {
|
||||
return Optional.ofNullable(node.getInputs()).orElse(new ArrayList<>())
|
||||
.stream()
|
||||
.filter(e -> StringUtils.isNotBlank(e.getValue())).collect(Collectors.toList());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,284 @@
|
|||
package com.microservices.pms.pipeline.domain.vo;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
public class PipelineVo implements Serializable {
|
||||
private String pipelineName;
|
||||
|
||||
private PipelineJson pipelineJson;
|
||||
|
||||
public String getPipelineName() {
|
||||
return this.pipelineName;
|
||||
}
|
||||
|
||||
public void setPipelineName(String pipelineName) {
|
||||
this.pipelineName = pipelineName;
|
||||
}
|
||||
|
||||
public PipelineJson getPipelineJson() {
|
||||
return this.pipelineJson;
|
||||
}
|
||||
|
||||
public void setPipelineJson(PipelineJson pipelineJson) {
|
||||
this.pipelineJson = pipelineJson;
|
||||
}
|
||||
|
||||
public static class PipelineJson implements Serializable {
|
||||
private List<Nodes> nodes;
|
||||
|
||||
private List<Edges> edges;
|
||||
|
||||
public List<Nodes> getNodes() {
|
||||
return this.nodes;
|
||||
}
|
||||
|
||||
public void setNodes(List<Nodes> nodes) {
|
||||
this.nodes = nodes;
|
||||
}
|
||||
|
||||
public List<Edges> getEdges() {
|
||||
return this.edges;
|
||||
}
|
||||
|
||||
public void setEdges(List<Edges> edges) {
|
||||
this.edges = edges;
|
||||
}
|
||||
|
||||
public static class Nodes implements Serializable {
|
||||
private Integer action_node_types_id;
|
||||
|
||||
private String img;
|
||||
|
||||
private List<Inputs> inputs;
|
||||
|
||||
private String icon;
|
||||
|
||||
private String description;
|
||||
|
||||
private String label;
|
||||
|
||||
private String type;
|
||||
|
||||
private Integer sort_no;
|
||||
|
||||
private Integer use_count;
|
||||
|
||||
private String full_name;
|
||||
|
||||
private String name;
|
||||
|
||||
private Integer x;
|
||||
|
||||
private Double y;
|
||||
|
||||
private String id;
|
||||
|
||||
private Boolean isCluster;
|
||||
|
||||
private String yaml;
|
||||
|
||||
public Integer getAction_node_types_id() {
|
||||
return this.action_node_types_id;
|
||||
}
|
||||
|
||||
public void setAction_node_types_id(Integer action_node_types_id) {
|
||||
this.action_node_types_id = action_node_types_id;
|
||||
}
|
||||
|
||||
public String getImg() {
|
||||
return this.img;
|
||||
}
|
||||
|
||||
public void setImg(String img) {
|
||||
this.img = img;
|
||||
}
|
||||
|
||||
public List<Inputs> getInputs() {
|
||||
return this.inputs;
|
||||
}
|
||||
|
||||
public void setInputs(List<Inputs> inputs) {
|
||||
this.inputs = inputs;
|
||||
}
|
||||
|
||||
public String getIcon() {
|
||||
return this.icon;
|
||||
}
|
||||
|
||||
public void setIcon(String icon) {
|
||||
this.icon = icon;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return this.description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getLabel() {
|
||||
return this.label;
|
||||
}
|
||||
|
||||
public void setLabel(String label) {
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public Integer getSort_no() {
|
||||
return this.sort_no;
|
||||
}
|
||||
|
||||
public void setSort_no(Integer sort_no) {
|
||||
this.sort_no = sort_no;
|
||||
}
|
||||
|
||||
public Integer getUse_count() {
|
||||
return this.use_count;
|
||||
}
|
||||
|
||||
public void setUse_count(Integer use_count) {
|
||||
this.use_count = use_count;
|
||||
}
|
||||
|
||||
public String getFull_name() {
|
||||
return this.full_name;
|
||||
}
|
||||
|
||||
public void setFull_name(String full_name) {
|
||||
this.full_name = full_name;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Integer getX() {
|
||||
return this.x;
|
||||
}
|
||||
|
||||
public void setX(Integer x) {
|
||||
this.x = x;
|
||||
}
|
||||
|
||||
public Double getY() {
|
||||
return this.y;
|
||||
}
|
||||
|
||||
public void setY(Double y) {
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Boolean getIsCluster() {
|
||||
return this.isCluster;
|
||||
}
|
||||
|
||||
public void setIsCluster(Boolean isCluster) {
|
||||
this.isCluster = isCluster;
|
||||
}
|
||||
|
||||
public String getYaml() {
|
||||
return this.yaml;
|
||||
}
|
||||
|
||||
public void setYaml(String yaml) {
|
||||
this.yaml = yaml;
|
||||
}
|
||||
|
||||
public static class Inputs implements Serializable {
|
||||
private Boolean is_required;
|
||||
|
||||
private String name;
|
||||
|
||||
private String value;
|
||||
|
||||
private String input_type;
|
||||
|
||||
private Integer id;
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public Boolean getIs_required() {
|
||||
return this.is_required;
|
||||
}
|
||||
|
||||
public void setIs_required(Boolean is_required) {
|
||||
this.is_required = is_required;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getInput_type() {
|
||||
return this.input_type;
|
||||
}
|
||||
|
||||
public void setInput_type(String input_type) {
|
||||
this.input_type = input_type;
|
||||
}
|
||||
|
||||
public Integer getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public void setId(Integer id) {
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class Edges implements Serializable {
|
||||
private String source;
|
||||
|
||||
private String target;
|
||||
|
||||
public String getSource() {
|
||||
return this.source;
|
||||
}
|
||||
|
||||
public void setSource(String source) {
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
public String getTarget() {
|
||||
return this.target;
|
||||
}
|
||||
|
||||
public void setTarget(String target) {
|
||||
this.target = target;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -91,6 +91,8 @@ public interface IPmsCiPipelinesService
|
|||
|
||||
JSONObject savePipelineYamlByGraphicJson(Long id, PmsCiPipelineBuildYamlInputVo pmsCiPipelineBuildInputVo);
|
||||
|
||||
JSONObject savePipelineYamlByGraphicJsonNew(Long id, PmsCiPipelineBuildYamlInputVo pmsCiPipelineBuildInputVo);
|
||||
|
||||
JSONObject reRunPipelineRunsJob(Long id, String run, String job);
|
||||
|
||||
JSONObject reRunAllJobsInPipelineRun(Long id, String run);
|
||||
|
@ -98,4 +100,6 @@ public interface IPmsCiPipelinesService
|
|||
void getPipelineRunJobLogs(Long id, String run, String job, HttpServletResponse response);
|
||||
|
||||
JSONObject runPipelineRunsJob(Long id, String workflow, String ref);
|
||||
|
||||
JSONObject getPipelineRunResults(Long id, String run);
|
||||
}
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
package com.microservices.pms.pipeline.service.impl;
|
||||
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.microservices.common.core.exception.ServiceException;
|
||||
import com.microservices.common.core.utils.DateUtils;
|
||||
import com.microservices.common.core.utils.StringUtils;
|
||||
|
@ -23,6 +28,7 @@ import com.microservices.pms.pipeline.domain.vo.*;
|
|||
import com.microservices.pms.pipeline.service.IPmsCiPipelineGraphicsService;
|
||||
import com.microservices.pms.utils.PmsConstants;
|
||||
import com.microservices.pms.utils.PmsGitLinkRequestUrl;
|
||||
import com.microservices.pms.utils.YamlUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
|
@ -363,6 +369,34 @@ public class PmsCiPipelinesServiceImpl implements IPmsCiPipelinesService {
|
|||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject savePipelineYamlByGraphicJsonNew(Long id, PmsCiPipelineBuildYamlInputVo pmsCiPipelineBuildInputVo) {
|
||||
PmsCiPipelines pmsCiPipelines = pmsCiPipelinesMapper.selectPmsCiPipelinesById(id);
|
||||
if (pmsCiPipelines == null) {
|
||||
throw new ServiceException("该项目流水线不存在(流水线ID[%s])", id);
|
||||
}
|
||||
|
||||
try {
|
||||
PipelineVo vo = JSON.parseObject(JSON.toJSONString(pmsCiPipelineBuildInputVo), PipelineVo.class);
|
||||
List<PipelineVo.PipelineJson.Nodes> nodes = vo.getPipelineJson().getNodes();
|
||||
String pipelineName = vo.getPipelineName();
|
||||
NodeActionVo actionVo = new NodeActionVo();
|
||||
actionVo.buildName(pipelineName);
|
||||
nodes.forEach(s -> {
|
||||
List<Map<String, Object>> steps = new ArrayList<>();
|
||||
actionVo.parse(steps, s);
|
||||
actionVo.buildJobs("job", s.getLabel()+String.format("[%s]",s.getId().trim()), steps);
|
||||
});
|
||||
String yaml = YamlUtil.toYaml(actionVo);
|
||||
JSONObject result = new JSONObject();
|
||||
result.put("pipeline_yaml", yaml);
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
logger.error("流水线图形Json解析失败)", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject reRunPipelineRunsJob(Long id, String run, String job) {
|
||||
PmsCiPipelines pmsCiPipelines = pmsCiPipelinesMapper.selectPmsCiPipelinesById(id);
|
||||
|
@ -414,6 +448,23 @@ public class PmsCiPipelinesServiceImpl implements IPmsCiPipelinesService {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public JSONObject getPipelineRunResults(Long id, String run) {
|
||||
PmsCiPipelines pmsCiPipelines = pmsCiPipelinesMapper.selectPmsCiPipelinesById(id);
|
||||
if (pmsCiPipelines == null) {
|
||||
throw new ServiceException("该项目流水线不存在(流水线ID[%s])", id);
|
||||
}
|
||||
try {
|
||||
return gitLinkRequestHelper.doGet(PmsGitLinkRequestUrl.GET_PIPELINE_RUN_RESULTS(pmsCiPipelines.getOwner(),
|
||||
pmsCiPipelines.getRepoIdentifier(),
|
||||
run));
|
||||
} catch (Exception e) {
|
||||
logger.error(e.getMessage());
|
||||
throw new ServiceException("获取流水线运行结果信息失败");
|
||||
}
|
||||
}
|
||||
|
||||
private void saveOrUpdateGraphicJsonToLocal(Long id, PmsCiPipelineBuildYamlInputVo pmsCiPipelineBuildInputVo) {
|
||||
PmsCiPipelineGraphics existentialPmsCiPipelineGraphics = pmsCiPipelineGraphicsService.selectPmsCiPipelineGraphicsByPmsCiPipelineId(id);
|
||||
PmsCiPipelineGraphics pmsCiPipelineGraphics = new PmsCiPipelineGraphics();
|
||||
|
|
|
@ -362,4 +362,8 @@ public class PmsGitLinkRequestUrl extends GitLinkRequestUrl {
|
|||
public static GitLinkRequestUrl SAVE_PIPELINE_YAML(String owner, String repoIdentifier) {
|
||||
return getGitLinkRequestUrl(String.format("/api/v1/%s/%s/pipelines/save_yaml", owner, repoIdentifier));
|
||||
}
|
||||
|
||||
public static GitLinkRequestUrl GET_PIPELINE_RUN_RESULTS(String owner, String repoIdentifier, String run){
|
||||
return getGitLinkRequestUrl(String.format("/api/v1/%s/%s/pipelines/run_results.json?run_id=%s", owner, repoIdentifier, run));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
package com.microservices.pms.utils;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
|
||||
import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator;
|
||||
import com.microservices.common.core.exception.ServiceException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
|
||||
@Slf4j
|
||||
public class YamlUtil {
|
||||
/**
|
||||
* 将yaml字符串转成类对象
|
||||
*
|
||||
* @param yamlStr 字符串
|
||||
* @param clazz 目标类
|
||||
* @param <T> 泛型
|
||||
* @return 目标类
|
||||
*/
|
||||
public static <T> T toObject(String yamlStr, Class<T> clazz) {
|
||||
ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
|
||||
mapper.findAndRegisterModules();
|
||||
try {
|
||||
return mapper.readValue(yamlStr, clazz);
|
||||
} catch (JsonProcessingException e) {
|
||||
log.error("yaml文本解析成java对象错误:", e);
|
||||
throw new ServiceException("yaml文本解析成java对象错误!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将类对象转yaml字符串
|
||||
*
|
||||
* @param object 对象
|
||||
* @return yaml字符串
|
||||
*/
|
||||
public static String toYaml(Object object) {
|
||||
ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
|
||||
mapper.findAndRegisterModules();
|
||||
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
|
||||
mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
|
||||
mapper = new ObjectMapper(new YAMLFactory().disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER));
|
||||
StringWriter stringWriter = new StringWriter();
|
||||
try {
|
||||
mapper.writeValue(stringWriter, object);
|
||||
return stringWriter.toString();
|
||||
} catch (IOException e) {
|
||||
log.error("Java对象解析成Yaml文本错误", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue