add 初始化
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
version: '2'
|
||||
services:
|
||||
jero-boot-mysql:
|
||||
build:
|
||||
context: ../db
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: root
|
||||
MYSQL_ROOT_HOST: '%'
|
||||
TZ: Asia/Shanghai
|
||||
restart: always
|
||||
container_name: jero-boot-mysql
|
||||
command:
|
||||
--character-set-server=utf8mb4
|
||||
--collation-server=utf8mb4_general_ci
|
||||
--explicit_defaults_for_timestamp=true
|
||||
--lower_case_table_names=1
|
||||
--max_allowed_packet=128M
|
||||
--default-authentication-plugin=caching_sha2_password
|
||||
ports:
|
||||
- 3306:3306
|
||||
|
||||
jero-boot-redis:
|
||||
image: redis:5.0
|
||||
ports:
|
||||
- 6379:6379
|
||||
restart: always
|
||||
container_name: jero-boot-redis
|
||||
hostname: jero-boot-redis
|
||||
|
||||
jero-boot-nacos:
|
||||
restart: always
|
||||
image: nacos/nacos-server:1.4.0
|
||||
container_name: jero-boot-nacos
|
||||
hostname: jero-boot-nacos
|
||||
ports:
|
||||
- 8848:8848
|
||||
depends_on:
|
||||
- jero-boot-mysql
|
||||
# volumes:
|
||||
# - ./init/docker-startup.sh:/home/nacos/bin/docker-startup.sh
|
||||
# - ./init/application.properties:/home/nacos/conf/application.properties
|
||||
environment:
|
||||
MODE: standalone
|
||||
PREFER_HOST_MODE: hostname
|
||||
SPRING_DATASOURCE_PLATFORM: mysql
|
||||
MYSQL_SERVICE_HOST: jero-boot-mysql
|
||||
MYSQL_SERVICE_PORT: 3306
|
||||
MYSQL_SERVICE_USER: root
|
||||
MYSQL_SERVICE_PASSWORD: root
|
||||
MYSQL_SERVICE_DB_NAME: nacos
|
||||
|
||||
jero-boot-gateway:
|
||||
build:
|
||||
context: ./jero-cloud-gateway
|
||||
ports:
|
||||
- 9999:9999
|
||||
depends_on:
|
||||
- jero-boot-nacos
|
||||
- jero-boot-redis
|
||||
container_name: jero-boot-gateway
|
||||
hostname: jero-boot-gateway
|
||||
|
||||
jero-boot-system:
|
||||
depends_on:
|
||||
- jero-boot-mysql
|
||||
- jero-boot-redis
|
||||
- jero-boot-nacos
|
||||
build:
|
||||
context: ./jero-cloud-system-start
|
||||
container_name: jero-boot-system
|
||||
hostname: jero-boot-system
|
||||
restart: on-failure
|
||||
environment:
|
||||
- TZ=Asia/Shanghai
|
||||
|
||||
jero-boot-xxljob:
|
||||
build:
|
||||
context: ./jero-cloud-xxljob
|
||||
ports:
|
||||
- 9080:9080
|
||||
container_name: jero-boot-xxljob
|
||||
hostname: jero-boot-xxljob
|
||||
|
||||
# jero-boot-rabbitmq:
|
||||
# # image: rabbitmq:3-management
|
||||
# image: rabbitmq:3
|
||||
# ports:
|
||||
# - 5672:5672
|
||||
# # - 15672:15672
|
||||
# restart: always
|
||||
# container_name: jero-boot-rabbitmq
|
||||
# hostname: jero-boot-rabbitmq
|
||||
@@ -0,0 +1,15 @@
|
||||
FROM anapsix/alpine-java:8_server-jre_unlimited
|
||||
|
||||
MAINTAINER test@163.com
|
||||
|
||||
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
|
||||
|
||||
RUN mkdir -p /jero-cloud-gateway
|
||||
|
||||
WORKDIR /jero-cloud-gateway
|
||||
|
||||
EXPOSE 9999
|
||||
|
||||
ADD ./target/jero-cloud-gateway-2.5.1.jar ./
|
||||
|
||||
CMD java -Dfile.encoding=utf-8 -Djava.security.egd=file:/dev/./urandom -jar jero-cloud-gateway-2.5.1.jar
|
||||
@@ -0,0 +1,105 @@
|
||||
<?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>jero-cloud-module</artifactId>
|
||||
<groupId>com.jero.boot</groupId>
|
||||
<version>2.5.1</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>jero-cloud-gateway</artifactId>
|
||||
<dependencies>
|
||||
<!-- jero 微服务基础依赖-->
|
||||
<dependency>
|
||||
<groupId>com.jero.boot</groupId>
|
||||
<artifactId>jero-boot-starter-cloud</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>com.jero.boot</groupId>
|
||||
<artifactId>jero-system-cloud-api</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<!-- spring-cloud网关-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-gateway</artifactId>
|
||||
</dependency>
|
||||
<!--sentinel断路器依赖-->
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.csp</groupId>
|
||||
<artifactId>sentinel-web-servlet</artifactId>
|
||||
</dependency>
|
||||
<!--Spring Webflux-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||
</dependency>
|
||||
<!-- 熔断、降级 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
|
||||
</dependency>
|
||||
<!--健康监控-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 限流Redis实现 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
|
||||
</dependency>
|
||||
<!--springboot2.X默认使用lettuce连接池,需要引入commons-pool2-->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-pool2</artifactId>
|
||||
</dependency>
|
||||
<!--server-api-->
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Swagger API文档 -->
|
||||
<dependency>
|
||||
<groupId>com.github.xiaoymin</groupId>
|
||||
<artifactId>knife4j-spring-ui</artifactId>
|
||||
<version>${knife4j-spring-ui.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.xiaoymin</groupId>
|
||||
<artifactId>knife4j-spring-boot-starter</artifactId>
|
||||
<version>${knife4j-spring-boot-starter.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
</resources>
|
||||
</build>
|
||||
</project>
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package com.jero;
|
||||
|
||||
import com.jero.loader.DynamicRouteLoader;
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
|
||||
import org.springframework.cloud.openfeign.EnableFeignClients;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
@EnableFeignClients
|
||||
@EnableDiscoveryClient
|
||||
@SpringBootApplication
|
||||
public class JeroGatewayApplication implements CommandLineRunner {
|
||||
|
||||
@Resource
|
||||
private DynamicRouteLoader dynamicRouteLoader;
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(JeroGatewayApplication.class, args);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 容器初始化后加载路由
|
||||
* @param strings
|
||||
*/
|
||||
@Override
|
||||
public void run(String... strings) {
|
||||
dynamicRouteLoader.refresh();
|
||||
}
|
||||
|
||||
}
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
package com.jero.config;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import com.jero.handler.HystrixFallbackHandler;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.reactive.function.server.RequestPredicates;
|
||||
import org.springframework.web.reactive.function.server.RouterFunction;
|
||||
import org.springframework.web.reactive.function.server.RouterFunctions;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
|
||||
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
|
||||
import static org.springframework.web.reactive.function.server.ServerResponse.ok;
|
||||
|
||||
/**
|
||||
* @author scott
|
||||
* @date 2020/05/26
|
||||
* 路由配置信息
|
||||
*/
|
||||
@Slf4j
|
||||
@Configuration
|
||||
public class GatewayRoutersConfiguration {
|
||||
|
||||
public static final long DEFAULT_TIMEOUT = 30000;
|
||||
|
||||
public static String SERVER_ADDR;
|
||||
|
||||
public static String NAMESPACE;
|
||||
|
||||
public static String DATA_ID;
|
||||
|
||||
public static String ROUTE_GROUP;
|
||||
|
||||
/**
|
||||
* 路由配置文件数据获取方式yml,nacos,database
|
||||
*/
|
||||
public static String DATA_TYPE;
|
||||
|
||||
@Value("${spring.cloud.nacos.discovery.server-addr}")
|
||||
public void setServerAddr(String serverAddr) {
|
||||
synchronized (GatewayRoutersConfiguration.class){
|
||||
if(SERVER_ADDR == null){
|
||||
SERVER_ADDR = serverAddr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Value("${spring.cloud.nacos.discovery.namespace}")
|
||||
public void setNamespace(String namespace) {
|
||||
synchronized (GatewayRoutersConfiguration.class){
|
||||
if(NAMESPACE == null){
|
||||
NAMESPACE = namespace;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Value("${jero.route.config.data-id:#{null}}")
|
||||
public void setRouteDataId(String dataId) {
|
||||
synchronized (GatewayRoutersConfiguration.class){
|
||||
if(DATA_ID == null){
|
||||
DATA_ID = dataId + ".json";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Value("${jero.route.config.group:DEFAULT_GROUP:#{null}}")
|
||||
public void setRouteGroup(String routeGroup) {
|
||||
synchronized (GatewayRoutersConfiguration.class){
|
||||
if(ROUTE_GROUP == null){
|
||||
ROUTE_GROUP = routeGroup;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Value("${jero.route.config.data-type}")
|
||||
public void setDataType(String dataType) {
|
||||
synchronized (GatewayRoutersConfiguration.class){
|
||||
if(DATA_TYPE == null){
|
||||
DATA_TYPE = dataType;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 路由断言
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> routerFunction() {
|
||||
return RouterFunctions.route(
|
||||
RequestPredicates.path("/globalFallback").and(RequestPredicates.accept(MediaType.TEXT_PLAIN)), hystrixFallbackHandler);
|
||||
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> indexRouter(@Value("classpath:/META-INF/resources/doc.html") final org.springframework.core.io.Resource indexHtml) {
|
||||
return route(GET("/"), request -> ok().contentType(MediaType.TEXT_HTML).syncBody(indexHtml));
|
||||
}
|
||||
|
||||
@Resource
|
||||
private HystrixFallbackHandler hystrixFallbackHandler;
|
||||
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
package com.jero.config;
|
||||
|
||||
import com.jero.filter.GlobalAccessTokenFilter;
|
||||
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* @author scott
|
||||
* @date 2020/5/26
|
||||
* 路由限流配置
|
||||
*/
|
||||
@Configuration
|
||||
public class RateLimiterConfiguration {
|
||||
/**
|
||||
* IP限流 (通过exchange对象可以获取到请求信息,这边用了HostName)
|
||||
*/
|
||||
@Bean
|
||||
@Primary
|
||||
public KeyResolver ipKeyResolver() {
|
||||
return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户限流 (通过exchange对象可以获取到请求信息,获取当前请求的用户 TOKEN)
|
||||
*/
|
||||
@Bean
|
||||
public KeyResolver userKeyResolver() {
|
||||
//使用这种方式限流,请求Header中必须携带X-Access-Token参数
|
||||
return exchange -> Mono.just(exchange.getRequest().getHeaders().getFirst(GlobalAccessTokenFilter.X_ACCESS_TOKEN));
|
||||
}
|
||||
|
||||
/**
|
||||
* 接口限流 (获取请求地址的uri作为限流key)
|
||||
*/
|
||||
@Bean
|
||||
public KeyResolver apiKeyResolver() {
|
||||
return exchange -> Mono.just(exchange.getRequest().getPath().value());
|
||||
}
|
||||
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package com.jero.config;
|
||||
|
||||
/**
|
||||
* nocos配置方式枚举
|
||||
*/
|
||||
public class RouterDataType {
|
||||
private RouterDataType(){
|
||||
|
||||
}
|
||||
/**
|
||||
* 数据库加载路由配置
|
||||
*/
|
||||
public static final String DATABASE = "database";
|
||||
/**
|
||||
* 本地yml加载路由配置
|
||||
*/
|
||||
public static final String YML = "yml";
|
||||
/**
|
||||
* nacos加载路由配置
|
||||
*/
|
||||
public static final String NACOS = "nacos";
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package com.jero.fallback;
|
||||
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* 响应超时熔断处理器
|
||||
*
|
||||
* @author zyf
|
||||
*/
|
||||
@RestController
|
||||
public class FallbackController {
|
||||
|
||||
/**
|
||||
* 全局熔断处理
|
||||
* @return
|
||||
*/
|
||||
@RequestMapping("/fallback")
|
||||
public Mono<String> fallback() {
|
||||
return Mono.just("访问超时,请稍后再试!");
|
||||
}
|
||||
|
||||
/**
|
||||
* demo熔断处理
|
||||
* @return
|
||||
*/
|
||||
@RequestMapping("/demo/fallback")
|
||||
public Mono<String> fallback2() {
|
||||
return Mono.just("访问超时,请稍后再试!");
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
package com.jero.filter;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
||||
import org.springframework.cloud.gateway.filter.GlobalFilter;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.Collectors;
|
||||
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR;
|
||||
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.addOriginalRequestUrl;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class GlobalAccessTokenFilter implements GlobalFilter, Ordered {
|
||||
public static final String X_ACCESS_TOKEN = "X-Access-Token";
|
||||
public static final String X_GATEWAY_BASE_PATH = "X_GATEWAY_BASE_PATH";
|
||||
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
||||
|
||||
String scheme = exchange.getRequest().getURI().getScheme();
|
||||
String host = exchange.getRequest().getURI().getHost();
|
||||
int port = exchange.getRequest().getURI().getPort();
|
||||
String basePath = scheme + "://" + host + ":" + port;
|
||||
|
||||
// 1. 重写StripPrefix(获取真实的URL)
|
||||
addOriginalRequestUrl(exchange, exchange.getRequest().getURI());
|
||||
String rawPath = exchange.getRequest().getURI().getRawPath();
|
||||
String newPath = File.separator + Arrays.stream(StringUtils.tokenizeToStringArray(rawPath, File.separator)).skip(1L).collect(Collectors.joining("/"));
|
||||
ServerHttpRequest newRequest = exchange.getRequest().mutate().path(newPath).build();
|
||||
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, newRequest.getURI());
|
||||
|
||||
//将现在的request,添加当前身份
|
||||
ServerHttpRequest mutableReq = exchange.getRequest().mutate().header("Authorization-UserName", "").header(X_GATEWAY_BASE_PATH,basePath).build();
|
||||
ServerWebExchange mutableExchange = exchange.mutate().request(mutableReq).build();
|
||||
return chain.filter(mutableExchange);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package com.jero.filter;
|
||||
|
||||
import com.alibaba.csp.sentinel.adapter.servlet.CommonFilter;
|
||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
|
||||
/**
|
||||
* @author Administrator
|
||||
*/
|
||||
@Configuration
|
||||
public class SentinelFilterContextConfig {
|
||||
@Bean
|
||||
public FilterRegistrationBean<Filter> sentinelFilterRegistration() {
|
||||
FilterRegistrationBean<Filter> registration = new FilterRegistrationBean<>();
|
||||
registration.setFilter(new CommonFilter());
|
||||
registration.addUrlPatterns("/*");
|
||||
// 入口资源关闭聚合
|
||||
registration.addInitParameter(CommonFilter.WEB_CONTEXT_UNIFY, "false");
|
||||
registration.setName("sentinelFilter");
|
||||
registration.setOrder(1);
|
||||
return registration;
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package com.jero.handler;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.reactive.function.BodyInserters;
|
||||
import org.springframework.web.reactive.function.server.HandlerFunction;
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR;
|
||||
|
||||
/**
|
||||
* @author scott
|
||||
* @date 2020/05/26
|
||||
* Hystrix 降级处理
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class HystrixFallbackHandler implements HandlerFunction<ServerResponse> {
|
||||
@Override
|
||||
public Mono<ServerResponse> handle(ServerRequest serverRequest) {
|
||||
Optional<Object> originalUris = serverRequest.attribute(GATEWAY_ORIGINAL_REQUEST_URL_ATTR);
|
||||
|
||||
originalUris.ifPresent(originalUri -> log.error("网关执行请求:{}失败,hystrix服务降级处理", originalUri));
|
||||
|
||||
return ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR.value())
|
||||
.header("Content-Type","text/plain; charset=utf-8").body(BodyInserters.fromObject("访问超时,请稍后再试"));
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package com.jero.handler;
|
||||
|
||||
import com.jero.common.base.BaseMap;
|
||||
import com.jero.common.modules.redis.listener.JeroRedisListerer;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import com.jero.loader.DynamicRouteLoader;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* 路由刷新监听
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class LoderRouderHandler implements JeroRedisListerer {
|
||||
|
||||
@Resource
|
||||
private DynamicRouteLoader dynamicRouteLoader;
|
||||
|
||||
|
||||
@Override
|
||||
public void onMessage(BaseMap message) {
|
||||
dynamicRouteLoader.refresh();
|
||||
}
|
||||
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
package com.jero.handler;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.cloud.gateway.route.RouteLocator;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.stereotype.Component;
|
||||
import springfox.documentation.swagger.web.SwaggerResource;
|
||||
import springfox.documentation.swagger.web.SwaggerResourcesProvider;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 聚合各个服务的swagger接口
|
||||
*/
|
||||
@Component
|
||||
@Primary
|
||||
public class MySwaggerResourceProvider implements SwaggerResourcesProvider {
|
||||
/**
|
||||
* swagger2默认的url后缀
|
||||
*/
|
||||
private static final String SWAGGER2URL = "/v2/api-docs";
|
||||
|
||||
/**
|
||||
* 网关路由
|
||||
*/
|
||||
private final RouteLocator routeLocator;
|
||||
|
||||
/**
|
||||
* 网关应用名称
|
||||
*/
|
||||
@Value("${spring.application.name}")
|
||||
private String self;
|
||||
|
||||
@Autowired
|
||||
public MySwaggerResourceProvider(RouteLocator routeLocator) {
|
||||
this.routeLocator = routeLocator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SwaggerResource> get() {
|
||||
List<SwaggerResource> resources = new ArrayList<>();
|
||||
List<String> routeHosts = new ArrayList<>();
|
||||
// 获取所有可用的host:serviceId
|
||||
routeLocator.getRoutes().filter(route -> route.getUri().getHost() != null)
|
||||
.filter(route -> !self.equals(route.getUri().getHost()))
|
||||
.subscribe(route -> routeHosts.add(route.getUri().getHost()));
|
||||
|
||||
// 记录已经添加过的server,存在同一个应用注册了多个服务在nacos上
|
||||
Set<String> dealed = new HashSet<>();
|
||||
routeHosts.forEach(instance -> {
|
||||
// 拼接url
|
||||
String url = "/" + instance.toLowerCase() + SWAGGER2URL;
|
||||
if (!dealed.contains(url)) {
|
||||
dealed.add(url);
|
||||
SwaggerResource swaggerResource = new SwaggerResource();
|
||||
swaggerResource.setUrl(url);
|
||||
swaggerResource.setName(instance);
|
||||
//Swagger排除监控
|
||||
if(instance.indexOf("jero-cloud-monitor")==-1){
|
||||
resources.add(swaggerResource);
|
||||
}
|
||||
}
|
||||
});
|
||||
return resources;
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package com.jero.handler;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import springfox.documentation.swagger.web.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* swagger聚合接口,三个接口都是 doc.html需要访问的接口
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/swagger-resources")
|
||||
public class SwaggerResourceController {
|
||||
private MySwaggerResourceProvider swaggerResourceProvider;
|
||||
|
||||
@Autowired
|
||||
public SwaggerResourceController(MySwaggerResourceProvider swaggerResourceProvider) {
|
||||
this.swaggerResourceProvider = swaggerResourceProvider;
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/configuration/security")
|
||||
public ResponseEntity<SecurityConfiguration> securityConfiguration() {
|
||||
return new ResponseEntity<>(SecurityConfigurationBuilder.builder().build(), HttpStatus.OK);
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/configuration/ui")
|
||||
public ResponseEntity<UiConfiguration> uiConfiguration() {
|
||||
return new ResponseEntity<>(UiConfigurationBuilder.builder().build(), HttpStatus.OK);
|
||||
}
|
||||
|
||||
@RequestMapping
|
||||
public ResponseEntity<List<SwaggerResource>> swaggerResources() {
|
||||
return new ResponseEntity<>(swaggerResourceProvider.get(), HttpStatus.OK);
|
||||
}
|
||||
}
|
||||
+290
@@ -0,0 +1,290 @@
|
||||
package com.jero.loader;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.alibaba.nacos.api.NacosFactory;
|
||||
import com.alibaba.nacos.api.config.ConfigService;
|
||||
import com.alibaba.nacos.api.config.listener.Listener;
|
||||
import com.alibaba.nacos.api.exception.NacosException;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.jero.common.constant.CacheConstant;
|
||||
import com.jero.common.util.RedisUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import com.jero.config.GatewayRoutersConfiguration;
|
||||
import com.jero.config.RouterDataType;
|
||||
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
|
||||
import org.springframework.cloud.gateway.filter.FilterDefinition;
|
||||
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
|
||||
import org.springframework.cloud.gateway.route.InMemoryRouteDefinitionRepository;
|
||||
import org.springframework.cloud.gateway.route.RouteDefinition;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.ApplicationEventPublisherAware;
|
||||
import org.springframework.context.annotation.DependsOn;
|
||||
import org.springframework.stereotype.Component;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
* 动态路由加载器
|
||||
*
|
||||
* @author : zyf
|
||||
* @date :2020-11-10
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@DependsOn({"gatewayRoutersConfiguration"})
|
||||
public class DynamicRouteLoader implements ApplicationEventPublisherAware {
|
||||
|
||||
|
||||
private ApplicationEventPublisher publisher;
|
||||
|
||||
private InMemoryRouteDefinitionRepository repository;
|
||||
|
||||
private DynamicRouteService dynamicRouteService;
|
||||
|
||||
private ConfigService configService;
|
||||
|
||||
private RedisUtil redisUtil;
|
||||
|
||||
private static String updateRouteText = "update route : {}";
|
||||
|
||||
|
||||
public DynamicRouteLoader(InMemoryRouteDefinitionRepository repository, DynamicRouteService dynamicRouteService, RedisUtil redisUtil) {
|
||||
if(this.repository == null){
|
||||
this.repository = repository;
|
||||
}
|
||||
this.dynamicRouteService = dynamicRouteService;
|
||||
this.redisUtil = redisUtil;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
String dataType = GatewayRoutersConfiguration.DATA_TYPE;
|
||||
log.info("初始化路由,dataType:"+ dataType);
|
||||
if (RouterDataType.NACOS.endsWith(dataType)) {
|
||||
loadRoutesByNacos();
|
||||
}
|
||||
//从数据库加载路由
|
||||
if (RouterDataType.DATABASE.endsWith(dataType)) {
|
||||
loadRoutesByRedis();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 刷新路由
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Mono<Void> refresh() {
|
||||
String dataType = GatewayRoutersConfiguration.DATA_TYPE;
|
||||
if (!RouterDataType.YML.endsWith(dataType)) {
|
||||
this.init();
|
||||
}
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 从nacos中读取路由配置
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private void loadRoutesByNacos() {
|
||||
List<RouteDefinition> routes = Lists.newArrayList();
|
||||
configService = createConfigService();
|
||||
try {
|
||||
String configInfo = "";
|
||||
if (configService == null) {
|
||||
log.warn("initConfigService fail");
|
||||
}else {
|
||||
configInfo = configService.getConfig(GatewayRoutersConfiguration.DATA_ID, GatewayRoutersConfiguration.ROUTE_GROUP, GatewayRoutersConfiguration.DEFAULT_TIMEOUT);
|
||||
}
|
||||
if (StringUtils.isNotBlank(configInfo)) {
|
||||
log.info("获取网关当前配置:\r\n{}", configInfo);
|
||||
routes = JSON.parseArray(configInfo, RouteDefinition.class);
|
||||
}
|
||||
} catch (NacosException e) {
|
||||
log.error("初始化网关路由时发生错误", e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
for (RouteDefinition definition : routes) {
|
||||
log.info(updateRouteText, definition.toString());
|
||||
dynamicRouteService.add(definition);
|
||||
}
|
||||
this.publisher.publishEvent(new RefreshRoutesEvent(this));
|
||||
dynamicRouteByNacosListener(GatewayRoutersConfiguration.DATA_ID, GatewayRoutersConfiguration.ROUTE_GROUP);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 从redis中读取路由配置
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private void loadRoutesByRedis() {
|
||||
List<MyRouteDefinition> routes = Lists.newArrayList();
|
||||
configService = createConfigService();
|
||||
if (configService == null) {
|
||||
log.warn("initConfigService fail");
|
||||
}
|
||||
Object configInfo = redisUtil.get(CacheConstant.GATEWAY_ROUTES);
|
||||
if (ObjectUtil.isNotEmpty(configInfo)) {
|
||||
log.info("获取网关当前配置:\r\n{}", configInfo);
|
||||
JSONArray array = JSON.parseArray(configInfo.toString());
|
||||
try {
|
||||
routes = getRoutesByJson(array);
|
||||
} catch (URISyntaxException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
for (MyRouteDefinition definition : routes) {
|
||||
log.info(updateRouteText, definition.toString());
|
||||
Integer status=definition.getStatus();
|
||||
if(status.equals(0)){
|
||||
dynamicRouteService.delete(definition.getId());
|
||||
}else{
|
||||
dynamicRouteService.add(definition);
|
||||
}
|
||||
}
|
||||
this.publisher.publishEvent(new RefreshRoutesEvent(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* redis中的信息需要处理下 转成RouteDefinition对象
|
||||
* - id: login
|
||||
* uri: lb://cloud-jero-system
|
||||
* predicates:
|
||||
* - Path=/jero-boot/sys/**,
|
||||
*
|
||||
* @param array
|
||||
* @return
|
||||
*/
|
||||
public static List<MyRouteDefinition> getRoutesByJson(JSONArray array) throws URISyntaxException {
|
||||
List<MyRouteDefinition> ls = new ArrayList<>();
|
||||
for (int i = 0; i < array.size(); i++) {
|
||||
JSONObject obj = array.getJSONObject(i);
|
||||
MyRouteDefinition route = new MyRouteDefinition();
|
||||
route.setId(obj.getString("routerId"));
|
||||
route.setStatus(obj.getInteger("status"));
|
||||
Object uri = obj.get("uri");
|
||||
if (uri == null) {
|
||||
route.setUri(new URI("lb://" + obj.getString("name")));
|
||||
} else {
|
||||
route.setUri(new URI(obj.getString("uri")));
|
||||
}
|
||||
Object predicates = obj.get("predicates");
|
||||
if (predicates != null) {
|
||||
JSONArray list = JSON.parseArray(predicates.toString());
|
||||
List<PredicateDefinition> predicateDefinitionList = getPredicateDefinitions(list);
|
||||
route.setPredicates(predicateDefinitionList);
|
||||
}
|
||||
|
||||
Object filters = obj.get("filters");
|
||||
if (filters != null) {
|
||||
JSONArray list = JSON.parseArray(filters.toString());
|
||||
List<FilterDefinition> filterDefinitionList = new ArrayList<>();
|
||||
if (ObjectUtil.isNotEmpty(list)) {
|
||||
addFilterDefinition(list, filterDefinitionList);
|
||||
route.setFilters(filterDefinitionList);
|
||||
}
|
||||
}
|
||||
ls.add(route);
|
||||
}
|
||||
return ls;
|
||||
}
|
||||
|
||||
private static void addFilterDefinition(JSONArray list, List<FilterDefinition> filterDefinitionList) {
|
||||
for (Object map : list) {
|
||||
JSONObject json = (JSONObject) map;
|
||||
JSONArray jsonArray = json.getJSONArray("args");
|
||||
String name = json.getString("name");
|
||||
FilterDefinition filterDefinition = new FilterDefinition();
|
||||
for (Object o : jsonArray) {
|
||||
JSONObject params = (JSONObject) o;
|
||||
filterDefinition.addArg(params.getString("key"), params.get("value").toString());
|
||||
}
|
||||
filterDefinition.setName(name);
|
||||
filterDefinitionList.add(filterDefinition);
|
||||
}
|
||||
}
|
||||
|
||||
private static List<PredicateDefinition> getPredicateDefinitions(JSONArray list) {
|
||||
List<PredicateDefinition> predicateDefinitionList = new ArrayList<>();
|
||||
for (Object map : list) {
|
||||
JSONObject json = (JSONObject) map;
|
||||
PredicateDefinition predicateDefinition = new PredicateDefinition();
|
||||
predicateDefinition.setName(json.getString("name"));
|
||||
JSONArray jsonArray = json.getJSONArray("args");
|
||||
for (int j = 0; j < jsonArray.size(); j++) {
|
||||
predicateDefinition.addArg("_genkey" + j, jsonArray.get(j).toString());
|
||||
}
|
||||
predicateDefinitionList.add(predicateDefinition);
|
||||
}
|
||||
return predicateDefinitionList;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 监听Nacos下发的动态路由配置
|
||||
*
|
||||
* @param dataId
|
||||
* @param group
|
||||
*/
|
||||
public void dynamicRouteByNacosListener(String dataId, String group) {
|
||||
try {
|
||||
configService.addListener(dataId, group, new Listener() {
|
||||
@Override
|
||||
public void receiveConfigInfo(String configInfo) {
|
||||
log.info("进行网关更新:\n\r{}", configInfo);
|
||||
List<RouteDefinition> definitionList = JSON.parseArray(configInfo, RouteDefinition.class);
|
||||
for (RouteDefinition definition : definitionList) {
|
||||
log.info(updateRouteText, definition.toString());
|
||||
dynamicRouteService.update(definition);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Executor getExecutor() {
|
||||
log.info("getExecutor\n\r");
|
||||
return null;
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
log.error("从nacos接收动态路由配置出错!!!", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建ConfigService
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private ConfigService createConfigService() {
|
||||
try {
|
||||
Properties properties = new Properties();
|
||||
properties.setProperty("serverAddr", GatewayRoutersConfiguration.SERVER_ADDR);
|
||||
properties.setProperty("namespace", GatewayRoutersConfiguration.NAMESPACE);
|
||||
configService = NacosFactory.createConfigService(properties);
|
||||
return configService;
|
||||
} catch (Exception e) {
|
||||
log.error("创建ConfigService异常", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
|
||||
this.publisher = applicationEventPublisher;
|
||||
}
|
||||
}
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
package com.jero.loader;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
|
||||
import org.springframework.cloud.gateway.route.InMemoryRouteDefinitionRepository;
|
||||
import org.springframework.cloud.gateway.route.RouteDefinition;
|
||||
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
|
||||
import org.springframework.cloud.gateway.support.NotFoundException;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.ApplicationEventPublisherAware;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Service;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* 动态更新路由网关service
|
||||
* 1)实现一个Spring提供的事件推送接口ApplicationEventPublisherAware
|
||||
* 2)提供动态路由的基础方法,可通过获取bean操作该类的方法。该类提供新增路由、更新路由、删除路由,然后实现发布的功能。
|
||||
*
|
||||
* @author zyf
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class DynamicRouteService implements ApplicationEventPublisherAware {
|
||||
|
||||
@Autowired
|
||||
private RouteDefinitionWriter routeDefinitionWriter;
|
||||
|
||||
@Autowired
|
||||
private InMemoryRouteDefinitionRepository repository;
|
||||
|
||||
/**
|
||||
* 发布事件
|
||||
*/
|
||||
|
||||
private ApplicationEventPublisher publisher;
|
||||
|
||||
@Override
|
||||
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
|
||||
this.publisher = applicationEventPublisher;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除路由
|
||||
*
|
||||
* @param id
|
||||
* @return
|
||||
*/
|
||||
public synchronized void delete(String id) {
|
||||
try {
|
||||
repository.delete(Mono.just(id)).subscribe();
|
||||
this.publisher.publishEvent(new RefreshRoutesEvent(this));
|
||||
}catch (Exception e){
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新路由
|
||||
*
|
||||
* @param definition
|
||||
* @return
|
||||
*/
|
||||
public synchronized String update(RouteDefinition definition) {
|
||||
try {
|
||||
log.info("gateway update route {}", definition);
|
||||
delete(definition.getId());
|
||||
} catch (Exception e) {
|
||||
return "update fail,not find route routeId: " + definition.getId();
|
||||
}
|
||||
try {
|
||||
repository.save(Mono.just(definition)).subscribe();
|
||||
this.publisher.publishEvent(new RefreshRoutesEvent(this));
|
||||
return "success";
|
||||
} catch (Exception e) {
|
||||
return "update route fail";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加路由
|
||||
*
|
||||
* @param definition
|
||||
* @return
|
||||
*/
|
||||
public synchronized String add(RouteDefinition definition) {
|
||||
log.info("gateway add route {}", definition);
|
||||
try {
|
||||
repository.save(Mono.just(definition)).subscribe();
|
||||
} catch (Exception e) {
|
||||
log.error(e.toString());
|
||||
}
|
||||
return "success";
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package com.jero.loader;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class GatewayRouteVo {
|
||||
private String id;
|
||||
private String name;
|
||||
private String uri;
|
||||
private String predicates;
|
||||
private String filters;
|
||||
private Integer stripPrefix;
|
||||
private Integer retryable;
|
||||
private Integer persist;
|
||||
private Integer status;
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package com.jero.loader;
|
||||
|
||||
import org.springframework.cloud.gateway.route.RouteDefinition;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 自定义RouteDefinition
|
||||
* @author zyf
|
||||
*/
|
||||
public class MyRouteDefinition extends RouteDefinition {
|
||||
/**
|
||||
* 路由状态
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
public Integer getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(Integer status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
if (!super.equals(o)) return false;
|
||||
MyRouteDefinition that = (MyRouteDefinition) o;
|
||||
return Objects.equals(status, that.status);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(super.hashCode(), status);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
server:
|
||||
port: 8080
|
||||
spring:
|
||||
application:
|
||||
name: jero-gateway
|
||||
cloud:
|
||||
gateway:
|
||||
discovery:
|
||||
locator:
|
||||
enabled: true
|
||||
globalcors:
|
||||
cors-configurations:
|
||||
'[/**]':
|
||||
allowCredentials: true
|
||||
allowedOrigins: "*"
|
||||
allowedMethods: "*"
|
||||
allowedHeaders: "*"
|
||||
#如果启用nacos或者数据库配置请删除一下配置
|
||||
routes:
|
||||
- id: jero-demo
|
||||
uri: lb://jero-demo
|
||||
predicates:
|
||||
- Path=/mock/**,/test/**,/bigscreen/template1/**,/bigscreen/template2/**
|
||||
- id: jero-system
|
||||
uri: lb://jero-system
|
||||
predicates:
|
||||
- Path=/sys/**,/eoa/**,/v1/**,/joa/**,/online/**,/bigscreen/**,/jmreport/**,/desform/**,/act/**,/plug-in/**,/generic/**
|
||||
- id: jero-system-websocket
|
||||
uri: lb:ws://jero-system
|
||||
predicates:
|
||||
- Path=/websocket/**,/eoaSocket/**,/newsWebsocket/**
|
||||
- id: jero-demo-websocket
|
||||
uri: lb:ws://jero-demo
|
||||
predicates:
|
||||
- Path=/vxeSocket/**
|
||||
# 全局熔断降级配置
|
||||
default-filters:
|
||||
- name: Hystrix
|
||||
args:
|
||||
name: default
|
||||
#转发地址
|
||||
fallbackUri: 'forward:/fallback'
|
||||
- name: Retry
|
||||
args:
|
||||
#重试次数,默认值是 3 次
|
||||
retries: 3
|
||||
#HTTP 的状态返回码
|
||||
statuses: BAD_GATEWAY,BAD_REQUEST
|
||||
#指定哪些方法的请求需要进行重试逻辑,默认值是 GET 方法
|
||||
methods: GET,POST
|
||||
# hystrix 信号量隔离,10秒后自动超时
|
||||
hystrix:
|
||||
enabled: true
|
||||
shareSecurityContext: true
|
||||
command:
|
||||
default:
|
||||
execution:
|
||||
isolation:
|
||||
strategy: SEMAPHORE
|
||||
thread:
|
||||
timeoutInMilliseconds: 10000
|
||||
@@ -0,0 +1,47 @@
|
||||
# 以下@符号包裹的配置,均在父POM中进行定义.
|
||||
# 在微服务环境下,该配置文件会先于application.yml加载
|
||||
# 对应到nacos配置的data-id是 application.name(应用名称) + profiles.active(开发环境是dev)
|
||||
spring:
|
||||
profiles:
|
||||
# 当前激活环境
|
||||
active: @profile.name@
|
||||
cloud:
|
||||
#配置Bus id(远程推送事件)
|
||||
bus:
|
||||
id: ${spring.application.name}:${server.port}
|
||||
nacos:
|
||||
config:
|
||||
# Nacos 认证用户
|
||||
username: nacos
|
||||
# Nacos 认证密码
|
||||
password: nacos
|
||||
# 命名空间 常用场景之一是不同环境的配置的区分隔离,例如开发测试环境和生产环境的资源(如配置、服务)隔离等
|
||||
namespace: @config.namespace@
|
||||
# 配置中心地址
|
||||
server-addr: @config.server-addr@
|
||||
# 配置对应的分组
|
||||
group: @config.group@
|
||||
# 配置nacos中的配置文件名称
|
||||
prefix: jero-gateway
|
||||
# 配置文件后缀
|
||||
file-extension: yaml
|
||||
# 支持多个共享 Data Id 的配置,优先级小于extension-configs,自定义 Data Id 配置 属性是个集合,内部由 Config POJO 组成。Config 有 3 个属性,分别是 dataId, group 以及 refresh
|
||||
#shared-configs[0]:
|
||||
#data-id: @prefix.name@-common.yaml # 配置文件名-Data Id
|
||||
#group: @config.group@ # 默认为DEFAULT_GROUP
|
||||
#refresh: false # 是否动态刷新,默认为false
|
||||
discovery:
|
||||
namespace: @config.namespace@
|
||||
server-addr: @config.server-addr@
|
||||
watch:
|
||||
enabled: false
|
||||
jero:
|
||||
# 签名密钥串(前后端要一致)
|
||||
signatureSecret: dd05f1c54d63749eda95f9fa6d49v442a
|
||||
# 文件限制后缀黑名单
|
||||
fileSuffixLimits: 0x00,%00,\\00,.jsp,.exe,.php,.asp,.aspx,.jspx,.xml,.html,.js,.sh,.bin
|
||||
# 跨站白名单
|
||||
whiteUrls:
|
||||
route:
|
||||
config:
|
||||
data-type: yml
|
||||
@@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration debug="false">
|
||||
<!--定义日志文件的存储地址 -->
|
||||
<property name="LOG_HOME" value="../logs" />
|
||||
|
||||
<!--<property name="COLOR_PATTERN" value="%black(%contextName-) %red(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %boldMagenta( %replace(%caller{1}){'\t|Caller.{1}0|\r\n', ''})- %gray(%msg%xEx%n)" />-->
|
||||
<!-- 控制台输出 -->
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
||||
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}:%L - %msg%n</pattern>-->
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %highlight(%-5level) %cyan(%logger{50}:%L) - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- 按照每天生成日志文件 -->
|
||||
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||
<!--日志文件输出的文件名 -->
|
||||
<FileNamePattern>${LOG_HOME}/jeroboot-%d{yyyy-MM-dd}.%i.log</FileNamePattern>
|
||||
<!--日志文件保留天数 -->
|
||||
<MaxHistory>30</MaxHistory>
|
||||
<maxFileSize>10MB</maxFileSize>
|
||||
</rollingPolicy>
|
||||
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
||||
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符 -->
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}:%L - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
|
||||
<!--myibatis log configure -->
|
||||
<logger name="com.apache.ibatis" level="TRACE" />
|
||||
<logger name="java.sql.Connection" level="DEBUG" />
|
||||
<logger name="java.sql.Statement" level="DEBUG" />
|
||||
<logger name="java.sql.PreparedStatement" level="DEBUG" />
|
||||
|
||||
<!-- 日志输出级别 -->
|
||||
<root level="INFO">
|
||||
<appender-ref ref="STDOUT" />
|
||||
<appender-ref ref="FILE" />
|
||||
</root>
|
||||
|
||||
</configuration>
|
||||
@@ -0,0 +1,65 @@
|
||||
<?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>jero-cloud-module</artifactId>
|
||||
<groupId>com.jero.boot</groupId>
|
||||
<version>2.5.1</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>jero-cloud-monitor</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<!--Spring Boot Admin Server监控服务端-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>de.codecentric</groupId>
|
||||
<artifactId>spring-boot-admin-starter-server</artifactId>
|
||||
<version>2.3.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!--安全模块-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-tomcat</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<!--undertow容器-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-undertow</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
</resources>
|
||||
</build>
|
||||
</project>
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package com.jero.monitor;
|
||||
|
||||
import de.codecentric.boot.admin.server.config.EnableAdminServer;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
/**
|
||||
* 监控服务
|
||||
*/
|
||||
@SpringBootApplication
|
||||
@EnableAdminServer
|
||||
public class JeroMonitorApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(JeroMonitorApplication.class);
|
||||
}
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
package com.jero.monitor.config;
|
||||
|
||||
import de.codecentric.boot.admin.server.config.AdminServerProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
|
||||
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
|
||||
|
||||
/**
|
||||
* @author scott
|
||||
*/
|
||||
@Configuration
|
||||
public class SecuritySecureConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
private final String adminContextPath;
|
||||
|
||||
public SecuritySecureConfig(AdminServerProperties adminServerProperties) {
|
||||
this.adminContextPath = adminServerProperties.getContextPath();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
// 登录成功处理类
|
||||
SavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
|
||||
successHandler.setTargetUrlParameter("redirectTo");
|
||||
successHandler.setDefaultTargetUrl(adminContextPath + "/");
|
||||
|
||||
http.authorizeRequests()
|
||||
//静态文件允许访问
|
||||
.antMatchers(adminContextPath + "/assets/**").permitAll()
|
||||
//登录页面允许访问
|
||||
.antMatchers(adminContextPath + "/login", "/css/**", "/js/**", "/image/*").permitAll()
|
||||
//其他所有请求需要登录
|
||||
.anyRequest().authenticated()
|
||||
.and()
|
||||
//登录页面配置,用于替换security默认页面
|
||||
.formLogin().loginPage(adminContextPath + "/login").successHandler(successHandler).and()
|
||||
//登出页面配置,用于替换security默认页面
|
||||
.logout().logoutUrl(adminContextPath + "/logout").and()
|
||||
.httpBasic().and()
|
||||
.csrf()
|
||||
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
|
||||
.ignoringAntMatchers(
|
||||
"/instances",
|
||||
"/actuator/**"
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
server:
|
||||
port: 9111
|
||||
spring:
|
||||
boot:
|
||||
admin:
|
||||
ui:
|
||||
title: JeroCloud监控中心
|
||||
client:
|
||||
instance:
|
||||
metadata:
|
||||
tags:
|
||||
environment: local
|
||||
security:
|
||||
user:
|
||||
|
||||
|
||||
|
||||
|
||||
name: "admin"
|
||||
password: "admin"
|
||||
application:
|
||||
name: jero-monitor
|
||||
cloud:
|
||||
nacos:
|
||||
discovery:
|
||||
server-addr: @config.server-addr@
|
||||
metadata:
|
||||
user.name: ${spring.security.user.name}
|
||||
user.password: ${spring.security.user.password}
|
||||
# 服务端点检查
|
||||
management:
|
||||
trace:
|
||||
http:
|
||||
enabled: true
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
include: "*"
|
||||
endpoint:
|
||||
health:
|
||||
show-details: always
|
||||
@@ -0,0 +1,15 @@
|
||||
FROM anapsix/alpine-java:8_server-jre_unlimited
|
||||
|
||||
MAINTAINER jeroos@163.com
|
||||
|
||||
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
|
||||
|
||||
RUN mkdir -p /jero-cloud-nacos
|
||||
|
||||
WORKDIR /jero-cloud-nacos
|
||||
|
||||
EXPOSE 8848
|
||||
|
||||
ADD ./target/jero-cloud-nacos-2.5.1.jar ./
|
||||
|
||||
CMD sleep 5;java -Dfile.encoding=utf-8 -Djava.security.egd=file:/dev/./urandom -jar jero-cloud-nacos-2.5.1.jar
|
||||
@@ -0,0 +1,75 @@
|
||||
<?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">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<artifactId>jero-cloud-module</artifactId>
|
||||
<groupId>com.jero.boot</groupId>
|
||||
<version>2.5.1</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>jero-cloud-nacos</artifactId>
|
||||
<name>jero-cloud-nacos</name>
|
||||
<description>nacos启动模块</description>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>aliyun</id>
|
||||
<name>aliyun Repository</name>
|
||||
<url>https://maven.aliyun.com/repository/public</url>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>jeecg</id>
|
||||
<name>jeecg Repository</name>
|
||||
<url>https://maven.jeecg.org/nexus/content/repositories/jeecg</url>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.apache.tomcat.embed</groupId>
|
||||
<artifactId>tomcat-embed-jasper</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.nacos</groupId>
|
||||
<artifactId>nacos-naming</artifactId>
|
||||
<version>2.0.4</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.nacos</groupId>
|
||||
<artifactId>nacos-istio</artifactId>
|
||||
<version>2.0.4</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.nacos</groupId>
|
||||
<artifactId>nacos-config</artifactId>
|
||||
<version>2.0.4</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.nacos</groupId>
|
||||
<artifactId>nacos-console</artifactId>
|
||||
<version>2.0.4</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package com.alibaba.nacos;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.web.servlet.ServletComponentScan;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
|
||||
/**
|
||||
* Nacos 启动类
|
||||
* 引用的nacos console 源码运行,简化开发
|
||||
* 生产建议从官网下载最新版配置运行
|
||||
* @author zyf
|
||||
*/
|
||||
@SpringBootApplication(scanBasePackages = "com.alibaba.nacos")
|
||||
@ServletComponentScan
|
||||
@EnableScheduling
|
||||
public class JeroNacosApplication {
|
||||
|
||||
/**
|
||||
* 是否单机模式启动
|
||||
*/
|
||||
private static String standalone = "true";
|
||||
|
||||
/**
|
||||
* 是否开启鉴权
|
||||
*/
|
||||
private static String enabled = "false";
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.setProperty("nacos.standalone", standalone);
|
||||
System.setProperty("nacos.core.auth.enabled", enabled);
|
||||
System.setProperty("server.tomcat.basedir","logs");
|
||||
//自定义启动端口号
|
||||
System.setProperty("server.port","8848");
|
||||
SpringApplication.run(JeroNacosApplication.class, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
server:
|
||||
servlet:
|
||||
contextPath: /nacos
|
||||
tomcat:
|
||||
accesslog:
|
||||
enabled: true
|
||||
pattern: '%h %l %u %t "%r" %s %b %D %{User-Agent}i %{Request-Source}i'
|
||||
basedir: ''
|
||||
spring:
|
||||
datasource:
|
||||
platform: mysql
|
||||
db:
|
||||
num: 1
|
||||
password:
|
||||
'0': 123456
|
||||
url:
|
||||
'0': jdbc:mysql://127.0.0.1:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
|
||||
user:
|
||||
'0': root
|
||||
management:
|
||||
metrics:
|
||||
export:
|
||||
elastic:
|
||||
enabled: false
|
||||
influx:
|
||||
enabled: false
|
||||
nacos:
|
||||
core:
|
||||
auth:
|
||||
caching:
|
||||
enabled: true
|
||||
default:
|
||||
token:
|
||||
expire:
|
||||
seconds: 18000
|
||||
secret:
|
||||
key: SecretKey012345678901234567890123456789012345678901234567890123456789
|
||||
enabled: false
|
||||
system:
|
||||
type: nacos
|
||||
istio:
|
||||
mcp:
|
||||
server:
|
||||
enabled: false
|
||||
naming:
|
||||
empty-service:
|
||||
auto-clean: true
|
||||
clean:
|
||||
initial-delay-ms: 50000
|
||||
period-time-ms: 30000
|
||||
security:
|
||||
ignore:
|
||||
urls: /,/error,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-ui/public/**,/v1/auth/**,/v1/console/health/**,/actuator/**,/v1/console/server/**
|
||||
standalone: true
|
||||
@@ -0,0 +1,111 @@
|
||||
<?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">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<artifactId>jero-cloud-module</artifactId>
|
||||
<groupId>com.jero.boot</groupId>
|
||||
<version>2.5.1</version>
|
||||
</parent>
|
||||
<artifactId>jero-cloud-sentinel</artifactId>
|
||||
<name>jero-cloud-sentinel</name>
|
||||
<description>sentinel启动模块</description>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>aliyun</id>
|
||||
<name>aliyun Repository</name>
|
||||
<url>https://maven.aliyun.com/repository/public</url>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>jeecg</id>
|
||||
<name>jeecg Repository</name>
|
||||
<url>https://maven.jeecg.org/nexus/content/repositories/jeecg</url>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jeecgframework.cloud</groupId>
|
||||
<artifactId>sentinel-dashboard</artifactId>
|
||||
<version>1.8.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.csp</groupId>
|
||||
<artifactId>sentinel-datasource-nacos</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.csp</groupId>
|
||||
<artifactId>sentinel-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.csp</groupId>
|
||||
<artifactId>sentinel-web-servlet</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.csp</groupId>
|
||||
<artifactId>sentinel-transport-simple-http</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.csp</groupId>
|
||||
<artifactId>sentinel-parameter-flow-control</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.csp</groupId>
|
||||
<artifactId>sentinel-api-gateway-adapter-common</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<!--undertow容器-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-undertow</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>commons-lang</groupId>
|
||||
<artifactId>commons-lang</artifactId>
|
||||
<version>2.6</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
<version>4.5.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpcore</artifactId>
|
||||
<version>4.4.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpasyncclient</artifactId>
|
||||
<version>4.1.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpcore-nio</artifactId>
|
||||
<version>4.4.6</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.alibaba.csp.sentinel.dashboard;
|
||||
|
||||
import com.alibaba.csp.sentinel.init.InitExecutor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.core.env.Environment;
|
||||
|
||||
/**
|
||||
* Sentinel dashboard application.
|
||||
*
|
||||
* @author Carpenter Lee
|
||||
*/
|
||||
@SpringBootApplication
|
||||
@Slf4j
|
||||
public class JeroSentinelDashboardApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.setProperty("csp.sentinel.app.type", "1");
|
||||
triggerSentinelInit();
|
||||
ConfigurableApplicationContext application = SpringApplication.run(JeroSentinelDashboardApplication.class, args);
|
||||
Environment env = application.getEnvironment();
|
||||
String port = env.getProperty("server.port");
|
||||
log.info("\n----------------------------------------------------------\n\t" +
|
||||
"Application SentinelDashboard is running! Access URLs:\n\t" +
|
||||
"Local: \t\thttp://localhost:" + port + "/\n\t" +
|
||||
"----------------------------------------------------------");
|
||||
}
|
||||
|
||||
private static void triggerSentinelInit() {
|
||||
new Thread(InitExecutor::doInit).start();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#spring settings
|
||||
spring.http.encoding.force=true
|
||||
spring.http.encoding.charset=UTF-8
|
||||
spring.http.encoding.enabled=true
|
||||
|
||||
#cookie name setting
|
||||
server.servlet.session.cookie.name=sentinel_dashboard_cookie
|
||||
#spring.cloud.nacos.config.server-addr=127.0.0.1:8848
|
||||
#spring.cloud.nacos.config.namespace=
|
||||
#spring.cloud.nacos.config.group-id=DEFAULT_GROUP
|
||||
server.port=8087
|
||||
|
||||
#logging settings
|
||||
logging.level.org.springframework.web=INFO
|
||||
logging.file=${user.home}/logs/csp/sentinel-dashboard.log
|
||||
logging.pattern.file= %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n
|
||||
#logging.pattern.console= %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n
|
||||
|
||||
#auth settings
|
||||
auth.filter.exclude-urls=/,/auth/login,/auth/logout,/registry/machine,/version
|
||||
auth.filter.exclude-url-suffixes=htm,html,js,css,map,ico,ttf,woff,png
|
||||
# If auth.enabled=false, Sentinel console disable login
|
||||
auth.username=sentinel
|
||||
auth.password=sentinel
|
||||
|
||||
# Inject the dashboard version. It's required to enable
|
||||
# filtering in pom.xml for this resource file.
|
||||
sentinel.dashboard.version=1.8.2
|
||||
@@ -0,0 +1,15 @@
|
||||
FROM anapsix/alpine-java:8_server-jre_unlimited
|
||||
|
||||
MAINTAINER test@163.com
|
||||
|
||||
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
|
||||
|
||||
RUN mkdir -p /jero-cloud-system
|
||||
|
||||
WORKDIR /jero-cloud-system
|
||||
|
||||
EXPOSE 7001
|
||||
|
||||
ADD ./target/jero-cloud-system-start-2.5.1.jar ./
|
||||
|
||||
CMD sleep 10;java -Dfile.encoding=utf-8 -Djava.security.egd=file:/dev/./urandom -jar jero-cloud-system-start-2.5.1.jar
|
||||
@@ -0,0 +1,64 @@
|
||||
<?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>jero-cloud-module</artifactId>
|
||||
<groupId>com.jero.boot</groupId>
|
||||
<version>2.5.1</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>jero-cloud-system-start</artifactId>
|
||||
<description>System项目微服务启动</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- jero-boot-starter-cloud feign 依赖 -->
|
||||
<dependency>
|
||||
<groupId>com.jero.boot</groupId>
|
||||
<artifactId>jero-boot-starter-cloud</artifactId>
|
||||
<!--system模块需要排除jero-system-cloud-api-->
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>com.jero.boot</groupId>
|
||||
<artifactId>jero-system-cloud-api</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<!-- 引入jero-boot-module-system依赖 启动 -->
|
||||
<dependency>
|
||||
<groupId>com.jero.boot</groupId>
|
||||
<artifactId>jero-boot-module-system</artifactId>
|
||||
</dependency>
|
||||
<!-- 引入jero-boot-base-generater依赖 启动 -->
|
||||
<dependency>
|
||||
<groupId>com.jero.boot</groupId>
|
||||
<artifactId>jero-boot-base-generater</artifactId>
|
||||
<version>${jero.version}</version>
|
||||
</dependency>
|
||||
<!--rabbitmq消息队列-->
|
||||
<dependency>
|
||||
<groupId>com.jero.boot</groupId>
|
||||
<artifactId>jero-boot-starter-rabbitmq</artifactId>
|
||||
</dependency>
|
||||
<!--xxl-job定时任务-->
|
||||
<dependency>
|
||||
<groupId>com.jero.boot</groupId>
|
||||
<artifactId>jero-boot-starter-job</artifactId>
|
||||
</dependency>
|
||||
<!-- 分布式锁依赖 -->
|
||||
<dependency>
|
||||
<groupId>com.jero.boot</groupId>
|
||||
<artifactId>jero-boot-starter-lock</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
package com.jero;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import com.jero.common.util.oConvertUtils;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
|
||||
import org.springframework.cloud.openfeign.EnableFeignClients;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
/**
|
||||
* 微服务启动类(采用此类启动项目为微服务模式)
|
||||
* 注意: 需要先在naocs里面创建配置文件,参考文档 xxx
|
||||
*/
|
||||
@Slf4j
|
||||
@SpringBootApplication
|
||||
@EnableFeignClients(basePackages = {"com.jero"})
|
||||
@EnableScheduling
|
||||
public class JeroSystemCloudApplication extends SpringBootServletInitializer {
|
||||
|
||||
@Override
|
||||
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
|
||||
return application.sources(JeroSystemCloudApplication.class);
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws UnknownHostException {
|
||||
String subString = "/doc.html\n";
|
||||
ConfigurableApplicationContext application = SpringApplication.run(JeroSystemCloudApplication.class, args);
|
||||
Environment env = application.getEnvironment();
|
||||
String ip = InetAddress.getLocalHost().getHostAddress();
|
||||
String port = env.getProperty("server.port");
|
||||
String path = oConvertUtils.getString(env.getProperty("server.servlet.context-path"));
|
||||
log.info("\n----------------------------------------------------------\n\t" +
|
||||
"Application jero-boot is running! Access URLs:\n\t" +
|
||||
"Local: \t\thttp://localhost:" + port + path + subString +
|
||||
"External: \thttp://" + ip + ":" + port + path + subString +
|
||||
"Swagger文档: \thttp://" + ip + ":" + port + path + subString +
|
||||
"----------------------------------------------------------");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package com.jero.modules.cloud.constant;
|
||||
|
||||
/**
|
||||
* 微服务单元测试常量定义
|
||||
*/
|
||||
public class CloudConstant {
|
||||
private CloudConstant(){
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 微服务名【对应模块jero-boot-module-demo】
|
||||
*/
|
||||
public static final String SERVER_NAME_JERO_DEMO = "jero-demo";
|
||||
|
||||
/**
|
||||
* MQ测试队列名字
|
||||
*/
|
||||
public static final String MQ_JERO_PLACE_ORDER = "jero_place_order";
|
||||
public static final String MQ_JERO_PLACE_ORDER_TIME = "jero_place_order_time";
|
||||
|
||||
/**
|
||||
* MQ测试消息总线
|
||||
*/
|
||||
public static final String MQ_DEMO_BUS_EVENT = "demoBusEvent";
|
||||
|
||||
/**
|
||||
* 分布式锁lock key
|
||||
*/
|
||||
public static final String REDISSON_DEMO_LOCK_KEY1 = "demoLockKey1";
|
||||
public static final String REDISSON_DEMO_LOCK_KEY2 = "demoLockKey2";
|
||||
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package com.jero.modules.cloud.ebus;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import com.jero.boot.starter.rabbitmq.event.EventObj;
|
||||
import com.jero.boot.starter.rabbitmq.event.JeroBusEventHandler;
|
||||
import com.jero.common.base.BaseMap;
|
||||
import com.jero.modules.cloud.constant.CloudConstant;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 消息处理器【发布订阅】
|
||||
*/
|
||||
@Slf4j
|
||||
@Component(CloudConstant.MQ_DEMO_BUS_EVENT)
|
||||
public class DemoBusEvent implements JeroBusEventHandler {
|
||||
|
||||
|
||||
@Override
|
||||
public void onMessage(EventObj obj) {
|
||||
if (ObjectUtil.isNotEmpty(obj)) {
|
||||
BaseMap baseMap = obj.getBaseMap();
|
||||
String orderId = baseMap.get("orderId");
|
||||
log.info("业务处理----订单ID:" + orderId);
|
||||
}
|
||||
}
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
package com.jero.modules.cloud.feign.controller;
|
||||
|
||||
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import com.jero.common.api.vo.Results;
|
||||
import com.jero.starter.cloud.feign.impl.JeroFeignService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import com.jero.boot.starter.rabbitmq.client.RabbitMqClient;
|
||||
import com.jero.common.base.BaseMap;
|
||||
import com.jero.modules.cloud.constant.CloudConstant;
|
||||
import com.jero.modules.cloud.feign.feign.JeroTestClient;
|
||||
import com.jero.modules.cloud.feign.feign.JeroTestClientDyn;
|
||||
import org.apache.poi.ss.formula.functions.T;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/sys/test")
|
||||
@Api(tags = "【微服务】单元测试")
|
||||
public class JeroTestFeignController {
|
||||
|
||||
@Autowired
|
||||
private JeroFeignService jeroFeignService;
|
||||
@Autowired
|
||||
private JeroTestClient jeroTestClient;
|
||||
@Autowired
|
||||
private RabbitMqClient rabbitMqClient;
|
||||
|
||||
@GetMapping("getMessage")
|
||||
@ApiOperation(value = "测试feign", notes = "测试feign")
|
||||
public Results<String> getMessage() {
|
||||
return jeroTestClient.getMessage("jero-boot");
|
||||
}
|
||||
|
||||
@GetMapping("getMessage2")
|
||||
@ApiOperation(value = "测试动态feign", notes = "测试动态feign")
|
||||
public Results<String> getMessage2() {
|
||||
JeroTestClientDyn myClientDyn = jeroFeignService.newInstance(JeroTestClientDyn.class, CloudConstant.SERVER_NAME_JERO_DEMO);
|
||||
return myClientDyn.getMessage("动态fegin——jero-boot2");
|
||||
}
|
||||
|
||||
@GetMapping(value = "/rabbitmq")
|
||||
@ApiOperation(value = "测试rabbitmq", notes = "测试rabbitmq")
|
||||
public Results<T> rabbitMqClientTest(HttpServletRequest req) {
|
||||
//rabbitmq消息队列测试
|
||||
BaseMap map = new BaseMap();
|
||||
map.put("orderId", RandomUtil.randomNumbers(10));
|
||||
rabbitMqClient.sendMessage(CloudConstant.MQ_JERO_PLACE_ORDER, map);
|
||||
rabbitMqClient.sendMessage(CloudConstant.MQ_JERO_PLACE_ORDER_TIME, map,10);
|
||||
|
||||
//rabbitmq消息总线测试
|
||||
BaseMap params = new BaseMap();
|
||||
params.put("orderId", "123456");
|
||||
rabbitMqClient.publishEvent(CloudConstant.MQ_DEMO_BUS_EVENT, params);
|
||||
return Results.OK("MQ发送消息成功");
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package com.jero.modules.cloud.feign.feign;
|
||||
|
||||
import com.jero.common.api.vo.Results;
|
||||
import com.jero.modules.cloud.constant.CloudConstant;
|
||||
import com.jero.modules.cloud.feign.feign.fallback.JeroTestClientFallback;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
/**
|
||||
* 常规feign接口定义
|
||||
*/
|
||||
@FeignClient(value = CloudConstant.SERVER_NAME_JERO_DEMO, fallbackFactory = JeroTestClientFallback.class)
|
||||
@Component
|
||||
public interface JeroTestClient {
|
||||
|
||||
@GetMapping(value = "/test/getMessage")
|
||||
Results<String> getMessage(@RequestParam("name") String name);
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package com.jero.modules.cloud.feign.feign;
|
||||
|
||||
import com.jero.common.api.vo.Results;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
/**
|
||||
* 动态feign接口定义
|
||||
*/
|
||||
public interface JeroTestClientDyn {
|
||||
|
||||
@GetMapping(value = "/test/getMessage")
|
||||
Results<String> getMessage(@RequestParam("name") String name);
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package com.jero.modules.cloud.feign.feign.fallback;
|
||||
|
||||
import feign.hystrix.FallbackFactory;
|
||||
import com.jero.modules.cloud.feign.feign.JeroTestClient;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* @author qinfeng
|
||||
*/
|
||||
@Component
|
||||
public class JeroTestClientFallback implements FallbackFactory<JeroTestClient> {
|
||||
|
||||
@Override
|
||||
public JeroTestClient create(Throwable throwable) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
package com.jero.modules.cloud.lock;
|
||||
|
||||
import com.jero.boot.starter.lock.annotation.JLock;
|
||||
import com.jero.boot.starter.lock.client.RedissonLockClient;
|
||||
import com.jero.boot.starter.rabbitmq.client.RabbitMqClient;
|
||||
import com.jero.common.base.BaseMap;
|
||||
import com.jero.modules.cloud.constant.CloudConstant;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 分布式锁测试demo
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class DemoLockTest {
|
||||
@Autowired
|
||||
RedissonLockClient redissonLock;
|
||||
@Autowired
|
||||
RabbitMqClient rabbitMqClient;
|
||||
|
||||
/**
|
||||
* 测试分布式锁【注解方式】
|
||||
* 注释掉测试环境
|
||||
*/
|
||||
// @Scheduled(cron = "0/5 * * * * ?")
|
||||
@JLock(lockKey = CloudConstant.REDISSON_DEMO_LOCK_KEY1)
|
||||
public void execute() throws InterruptedException {
|
||||
log.info("执行execute任务开始,休眠三秒");
|
||||
Thread.sleep(3000);
|
||||
log.info("=======================业务逻辑1=============================");
|
||||
Map<String,Object> map = new BaseMap();
|
||||
map.put("orderId", "BJ0001");
|
||||
rabbitMqClient.sendMessage(CloudConstant.MQ_JERO_PLACE_ORDER, map);
|
||||
//延迟10秒发送
|
||||
map.put("orderId", "NJ0002");
|
||||
rabbitMqClient.sendMessage(CloudConstant.MQ_JERO_PLACE_ORDER, map, 10000);
|
||||
log.info("execute任务结束,休眠三秒");
|
||||
}
|
||||
|
||||
public DemoLockTest() {
|
||||
// do other
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试分布式锁【编码方式】
|
||||
*/
|
||||
//@Scheduled(cron = "0/5 * * * * ?")
|
||||
public void execute2() throws InterruptedException {
|
||||
if (redissonLock.tryLock(CloudConstant.REDISSON_DEMO_LOCK_KEY2, -1, 6000)) {
|
||||
log.info("执行任务execute2开始,休眠十秒");
|
||||
Thread.sleep(10000);
|
||||
log.info("=======================业务逻辑2=============================");
|
||||
log.info("定时execute2结束,休眠十秒");
|
||||
|
||||
redissonLock.unlock(CloudConstant.REDISSON_DEMO_LOCK_KEY2);
|
||||
} else {
|
||||
log.info("execute2获取锁失败");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package com.jero.modules.cloud.rabbitmq;
|
||||
|
||||
import com.rabbitmq.client.Channel;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import com.jero.boot.starter.rabbitmq.core.BaseRabbiMqHandler;
|
||||
import com.jero.boot.starter.rabbitmq.listenter.MqListener;
|
||||
import com.jero.common.annotation.RabbitComponent;
|
||||
import com.jero.common.base.BaseMap;
|
||||
import com.jero.modules.cloud.constant.CloudConstant;
|
||||
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
|
||||
import org.springframework.amqp.rabbit.annotation.RabbitListener;
|
||||
import org.springframework.amqp.support.AmqpHeaders;
|
||||
import org.springframework.messaging.handler.annotation.Header;
|
||||
|
||||
/**
|
||||
* RabbitMq接受者1
|
||||
* (@RabbitListener声明类上,一个类只能监听一个队列)
|
||||
*/
|
||||
@Slf4j
|
||||
@RabbitListener(queues = CloudConstant.MQ_JERO_PLACE_ORDER)
|
||||
@RabbitComponent(value = "helloReceiver1")
|
||||
public class HelloReceiver1 extends BaseRabbiMqHandler<BaseMap> {
|
||||
|
||||
@RabbitHandler
|
||||
public void onMessage(BaseMap baseMap, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag) {
|
||||
super.onMessage(baseMap, deliveryTag, channel, new MqListener<BaseMap>() {
|
||||
@Override
|
||||
public void handler(BaseMap map, Channel channel) {
|
||||
//业务处理
|
||||
String orderId = map.get("orderId").toString();
|
||||
log.info("MQ Receiver1,orderId : " + orderId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package com.jero.modules.cloud.rabbitmq;
|
||||
|
||||
import com.rabbitmq.client.Channel;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import com.jero.boot.starter.rabbitmq.core.BaseRabbiMqHandler;
|
||||
import com.jero.boot.starter.rabbitmq.listenter.MqListener;
|
||||
import com.jero.common.annotation.RabbitComponent;
|
||||
import com.jero.common.base.BaseMap;
|
||||
import com.jero.modules.cloud.constant.CloudConstant;
|
||||
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
|
||||
import org.springframework.amqp.rabbit.annotation.RabbitListener;
|
||||
import org.springframework.amqp.support.AmqpHeaders;
|
||||
import org.springframework.messaging.handler.annotation.Header;
|
||||
|
||||
/**
|
||||
* RabbitMq接受者2
|
||||
* (@RabbitListener声明类上,一个类只能监听一个队列)
|
||||
*/
|
||||
@Slf4j
|
||||
@RabbitListener(queues = CloudConstant.MQ_JERO_PLACE_ORDER)
|
||||
@RabbitComponent(value = "helloReceiver2")
|
||||
public class HelloReceiver2 extends BaseRabbiMqHandler<BaseMap> {
|
||||
|
||||
@RabbitHandler
|
||||
public void onMessage(BaseMap baseMap, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag) {
|
||||
super.onMessage(baseMap, deliveryTag, channel, new MqListener<BaseMap>() {
|
||||
@Override
|
||||
public void handler(BaseMap map, Channel channel) {
|
||||
//业务处理
|
||||
String orderId = map.get("orderId").toString();
|
||||
log.info("MQ Receiver2,orderId : " + orderId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
package com.jero.modules.cloud.rabbitmq;
|
||||
|
||||
import com.rabbitmq.client.Channel;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import com.jero.boot.starter.rabbitmq.core.BaseRabbiMqHandler;
|
||||
import com.jero.boot.starter.rabbitmq.listenter.MqListener;
|
||||
import com.jero.common.annotation.RabbitComponent;
|
||||
import com.jero.common.base.BaseMap;
|
||||
import com.jero.modules.cloud.constant.CloudConstant;
|
||||
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
|
||||
import org.springframework.amqp.rabbit.annotation.RabbitListener;
|
||||
import org.springframework.amqp.support.AmqpHeaders;
|
||||
import org.springframework.messaging.handler.annotation.Header;
|
||||
|
||||
/**
|
||||
* RabbitMq接受者3
|
||||
* (@RabbitListener声明类方法上,一个类可以多监听多个队列)
|
||||
*/
|
||||
@Slf4j
|
||||
@RabbitComponent(value = "helloReceiver3")
|
||||
public class HelloReceiver3 extends BaseRabbiMqHandler<BaseMap> {
|
||||
|
||||
@RabbitListener(queues = CloudConstant.MQ_JERO_PLACE_ORDER)
|
||||
public void onMessage(BaseMap baseMap, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag) {
|
||||
super.onMessage(baseMap, deliveryTag, channel, new MqListener<BaseMap>() {
|
||||
@Override
|
||||
public void handler(BaseMap map, Channel channel) {
|
||||
//业务处理
|
||||
String orderId = map.get("orderId").toString();
|
||||
log.info("MQ Receiver3,orderId : " + orderId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package com.jero.modules.cloud.rabbitmq;
|
||||
|
||||
import com.rabbitmq.client.Channel;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import com.jero.boot.starter.rabbitmq.core.BaseRabbiMqHandler;
|
||||
import com.jero.boot.starter.rabbitmq.listenter.MqListener;
|
||||
import com.jero.common.annotation.RabbitComponent;
|
||||
import com.jero.common.base.BaseMap;
|
||||
import com.jero.modules.cloud.constant.CloudConstant;
|
||||
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
|
||||
import org.springframework.amqp.rabbit.annotation.RabbitListener;
|
||||
import org.springframework.amqp.support.AmqpHeaders;
|
||||
import org.springframework.messaging.handler.annotation.Header;
|
||||
|
||||
@Slf4j
|
||||
@RabbitListener(queues = CloudConstant.MQ_JERO_PLACE_ORDER_TIME)
|
||||
@RabbitComponent(value = "helloTimeReceiver")
|
||||
public class HelloTimeReceiver extends BaseRabbiMqHandler<BaseMap> {
|
||||
|
||||
@RabbitHandler
|
||||
public void onMessage(BaseMap baseMap, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag) {
|
||||
super.onMessage(baseMap, deliveryTag, channel, new MqListener<BaseMap>() {
|
||||
@Override
|
||||
public void handler(BaseMap map, Channel channel) {
|
||||
//业务处理
|
||||
String orderId = map.get("orderId").toString();
|
||||
log.info("Time Receiver1,orderId : " + orderId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
+235
@@ -0,0 +1,235 @@
|
||||
|
||||
package com.jero.modules.cloud.xxljob;;
|
||||
|
||||
|
||||
import com.xxl.job.core.biz.model.ReturnT;
|
||||
import com.xxl.job.core.handler.IJobHandler;
|
||||
import com.xxl.job.core.handler.annotation.XxlJob;
|
||||
import com.xxl.job.core.log.XxlJobLogger;
|
||||
import com.xxl.job.core.util.ShardingUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.util.Arrays;
|
||||
|
||||
|
||||
/**
|
||||
* xxl-job定时任务测试
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class DemoJobHandler {
|
||||
|
||||
|
||||
/**
|
||||
* 简单任务
|
||||
*
|
||||
* @param params
|
||||
* @return
|
||||
*/
|
||||
|
||||
@XxlJob(value = "demoJob")
|
||||
public ReturnT<String> demoJobHandler(String params) {
|
||||
log.info("我是定时任务,我执行了...............................");
|
||||
return ReturnT.SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* 2、分片广播任务
|
||||
*/
|
||||
|
||||
@XxlJob("shardingJobHandler")
|
||||
public ReturnT<String> shardingJobHandler(String param) throws Exception {
|
||||
|
||||
// 分片参数
|
||||
ShardingUtil.ShardingVO shardingVO = ShardingUtil.getShardingVo();
|
||||
XxlJobLogger.log("分片参数:当前分片序号 = {}, 总分片数 = {}", shardingVO.getIndex(), shardingVO.getTotal());
|
||||
|
||||
// 业务逻辑
|
||||
for (int i = 0; i < shardingVO.getTotal(); i++) {
|
||||
if (i == shardingVO.getIndex()) {
|
||||
XxlJobLogger.log("第 {} 片, 命中分片开始处理", i);
|
||||
} else {
|
||||
XxlJobLogger.log("第 {} 片, 忽略", i);
|
||||
}
|
||||
}
|
||||
|
||||
return ReturnT.SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 3、命令行任务
|
||||
*/
|
||||
|
||||
@XxlJob("commandJobHandler")
|
||||
public ReturnT<String> commandJobHandler(String param) throws Exception {
|
||||
String command = param;
|
||||
int exitValue = -1;
|
||||
|
||||
BufferedReader bufferedReader = null;
|
||||
try {
|
||||
// command process
|
||||
Process process = Runtime.getRuntime().exec(command);
|
||||
BufferedInputStream bufferedInputStream = new BufferedInputStream(process.getInputStream());
|
||||
bufferedReader = new BufferedReader(new InputStreamReader(bufferedInputStream));
|
||||
|
||||
// command log
|
||||
String line;
|
||||
while ((line = bufferedReader.readLine()) != null) {
|
||||
XxlJobLogger.log(line);
|
||||
}
|
||||
|
||||
// command exit
|
||||
process.waitFor();
|
||||
exitValue = process.exitValue();
|
||||
} catch (Exception e) {
|
||||
XxlJobLogger.log(e);
|
||||
} finally {
|
||||
if (bufferedReader != null) {
|
||||
bufferedReader.close();
|
||||
}
|
||||
}
|
||||
|
||||
if (exitValue == 0) {
|
||||
return IJobHandler.SUCCESS;
|
||||
} else {
|
||||
return new ReturnT<String>(IJobHandler.FAIL.getCode(), "command exit value(" + exitValue + ") is failed");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 4、跨平台Http任务
|
||||
* 参数示例:
|
||||
* "url: http://www.baidu.com\n" +
|
||||
* "method: get\n" +
|
||||
* "data: content\n";
|
||||
*/
|
||||
|
||||
@XxlJob("httpJobHandler")
|
||||
public ReturnT<String> httpJobHandler(String param) throws Exception {
|
||||
|
||||
// param parse
|
||||
if (param == null || param.trim().length() == 0) {
|
||||
XxlJobLogger.log("param[" + param + "] invalid.");
|
||||
return ReturnT.FAIL;
|
||||
}
|
||||
String[] httpParams = param.split("\n");
|
||||
String url = null;
|
||||
String method = null;
|
||||
String data = null;
|
||||
for (String httpParam : httpParams) {
|
||||
if (httpParam.startsWith("url:")) {
|
||||
url = httpParam.substring(httpParam.indexOf("url:") + 4).trim();
|
||||
}
|
||||
if (httpParam.startsWith("method:")) {
|
||||
method = httpParam.substring(httpParam.indexOf("method:") + 7).trim().toUpperCase();
|
||||
}
|
||||
if (httpParam.startsWith("data:")) {
|
||||
data = httpParam.substring(httpParam.indexOf("data:") + 5).trim();
|
||||
}
|
||||
}
|
||||
|
||||
// param valid
|
||||
if (url == null || url.trim().length() == 0) {
|
||||
XxlJobLogger.log("url[" + url + "] invalid.");
|
||||
return ReturnT.FAIL;
|
||||
}
|
||||
if (method == null || !Arrays.asList("GET", "POST").contains(method)) {
|
||||
XxlJobLogger.log("method[" + method + "] invalid.");
|
||||
return ReturnT.FAIL;
|
||||
}
|
||||
|
||||
// request
|
||||
HttpURLConnection connection = null;
|
||||
BufferedReader bufferedReader = null;
|
||||
try {
|
||||
// connection
|
||||
URL realUrl = new URL(url);
|
||||
connection = (HttpURLConnection) realUrl.openConnection();
|
||||
|
||||
// connection setting
|
||||
connection.setRequestMethod(method);
|
||||
connection.setDoOutput(true);
|
||||
connection.setDoInput(true);
|
||||
connection.setUseCaches(false);
|
||||
connection.setReadTimeout(5 * 1000);
|
||||
connection.setConnectTimeout(3 * 1000);
|
||||
connection.setRequestProperty("connection", "Keep-Alive");
|
||||
connection.setRequestProperty("Content-Type", "application/json;charset=UTF-8");
|
||||
connection.setRequestProperty("Accept-Charset", "application/json;charset=UTF-8");
|
||||
|
||||
// do connection
|
||||
connection.connect();
|
||||
|
||||
// data
|
||||
if (data != null && data.trim().length() > 0) {
|
||||
DataOutputStream dataOutputStream = new DataOutputStream(connection.getOutputStream());
|
||||
dataOutputStream.write(data.getBytes("UTF-8"));
|
||||
dataOutputStream.flush();
|
||||
dataOutputStream.close();
|
||||
}
|
||||
|
||||
// valid StatusCode
|
||||
int statusCode = connection.getResponseCode();
|
||||
if (statusCode != 200) {
|
||||
throw new RuntimeException("Http Request StatusCode(" + statusCode + ") Invalid.");
|
||||
}
|
||||
|
||||
// result
|
||||
bufferedReader = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8"));
|
||||
StringBuilder result = new StringBuilder();
|
||||
String line;
|
||||
while ((line = bufferedReader.readLine()) != null) {
|
||||
result.append(line);
|
||||
}
|
||||
String responseMsg = result.toString();
|
||||
|
||||
XxlJobLogger.log(responseMsg);
|
||||
return ReturnT.SUCCESS;
|
||||
} catch (Exception e) {
|
||||
XxlJobLogger.log(e);
|
||||
return ReturnT.FAIL;
|
||||
} finally {
|
||||
try {
|
||||
if (bufferedReader != null) {
|
||||
bufferedReader.close();
|
||||
}
|
||||
if (connection != null) {
|
||||
connection.disconnect();
|
||||
}
|
||||
} catch (Exception e2) {
|
||||
XxlJobLogger.log(e2);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 5、生命周期任务示例:任务初始化与销毁时,支持自定义相关逻辑;
|
||||
*/
|
||||
|
||||
@XxlJob(value = "demoJobHandler2", init = "init", destroy = "destroy")
|
||||
public ReturnT<String> demoJobHandler2(String param) throws Exception {
|
||||
XxlJobLogger.log("XXL-JOB, Hello World.");
|
||||
return ReturnT.SUCCESS;
|
||||
}
|
||||
|
||||
public void init() {
|
||||
log.info("init");
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
log.info("destory");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
server:
|
||||
#微服务端口
|
||||
port: 7001
|
||||
spring:
|
||||
application:
|
||||
name: jero-system
|
||||
@@ -0,0 +1,44 @@
|
||||
# 以下@符号包裹的配置,均在父POM中进行定义.
|
||||
# 在微服务环境下,该配置文件会先于application.yml加载
|
||||
# 对应到nacos配置的data-id是 application.name(应用名称) + profiles.active(开发环境是dev)
|
||||
spring:
|
||||
profiles:
|
||||
# 当前激活环境
|
||||
active: @profile.name@
|
||||
cloud:
|
||||
#配置Bus id(远程推送事件)
|
||||
bus:
|
||||
id: ${spring.application.name}:${server.port}
|
||||
nacos:
|
||||
config:
|
||||
# Nacos 认证用户
|
||||
username: nacos
|
||||
# Nacos 认证密码
|
||||
password: nacos
|
||||
# 命名空间 常用场景之一是不同环境的配置的区分隔离,例如开发测试环境和生产环境的资源(如配置、服务)隔离等
|
||||
namespace: @config.namespace@
|
||||
# 配置中心地址
|
||||
server-addr: @config.server-addr@
|
||||
# 配置对应的分组
|
||||
group: @config.group@
|
||||
# 配置nacos中的配置文件名称
|
||||
prefix: jero-system
|
||||
# 配置文件后缀
|
||||
file-extension: yaml
|
||||
# 支持多个共享 Data Id 的配置,优先级小于extension-configs,自定义 Data Id 配置 属性是个集合,内部由 Config POJO 组成。Config 有 3 个属性,分别是 dataId, group 以及 refresh
|
||||
#shared-configs[0]:
|
||||
#data-id: @prefix.name@-common.yaml # 配置文件名-Data Id
|
||||
#group: @config.group@ # 默认为DEFAULT_GROUP
|
||||
#refresh: false # 是否动态刷新,默认为false
|
||||
discovery:
|
||||
namespace: @config.namespace@
|
||||
server-addr: @config.server-addr@
|
||||
watch:
|
||||
enabled: false
|
||||
jero:
|
||||
# 签名密钥串(前后端要一致)
|
||||
signatureSecret: dd05f1c54d63749eda95f9fa6d49v442a
|
||||
# 文件限制后缀黑名单
|
||||
fileSuffixLimits: 0x00,%00,\\00,.jsp,.exe,.php,.asp,.aspx,.jspx,.xml,.html,.js,.sh,.bin
|
||||
# 跨站白名单
|
||||
whiteUrls:
|
||||
@@ -0,0 +1,149 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration debug="false">
|
||||
<!--定义日志文件的存储地址 -->
|
||||
<property name="LOG_HOME" value="../logs" />
|
||||
<!-- 项目名称 每个项目依据实际情况修改 -->
|
||||
<property name="PROJECT_NAME" value="jero-boot" />
|
||||
<!-- 定义系统日志文件的存储地址,勿在 LogBacDk的配置中使用相对路径 -->
|
||||
<property name="LOG_HOME_SYSTEM" value="system" />
|
||||
<!-- 定义Druid日志文件的存储地址,勿在 LogBack的配置中使用相对路径 -->
|
||||
<property name="LOG_HOME_DRUID" value="druid" />
|
||||
|
||||
<!--<property name="COLOR_PATTERN" value="%black(%contextName-) %red(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %boldMagenta( %replace(%caller{1}){'\t|Caller.{1}0|\r\n', ''})- %gray(%msg%xEx%n)" />-->
|
||||
<!-- 控制台输出 -->
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
||||
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %highlight(%-5level) %cyan(%logger{50}:%L) - %msg%n</pattern>
|
||||
<charset>UTF-8</charset>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- 按照每天生成日志文件 -->
|
||||
<appender name="SYSTEM_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||
<!--日志文件输出的文件名 -->
|
||||
<FileNamePattern>${LOG_HOME}/${PROJECT_NAME}/${LOG_HOME_SYSTEM}/${PROJECT_NAME}-%d{yyyy-MM-dd}.%i.log</FileNamePattern>
|
||||
<!--日志文件保留天数 -->
|
||||
<MaxHistory>30</MaxHistory>
|
||||
<maxFileSize>100MB</maxFileSize>
|
||||
</rollingPolicy>
|
||||
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
||||
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符 -->
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}:%L - %msg%n</pattern>
|
||||
<charset>UTF-8</charset>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- 生成 error日志开始 -->
|
||||
<appender name="SYSTEM_ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<!-- 过滤器,只打印ERROR级别的日志 -->
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<level>ERROR</level>
|
||||
<onMatch>ACCEPT</onMatch>
|
||||
<onMismatch>DENY</onMismatch>
|
||||
</filter>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||
<!--日志文件输出的文件名-->
|
||||
<FileNamePattern>${LOG_HOME}/${PROJECT_NAME}/${LOG_HOME_SYSTEM}/${PROJECT_NAME}-error.%d{yyyy-MM-dd}.%i.log</FileNamePattern>
|
||||
<!--日志文件保留天数-->
|
||||
<MaxHistory>30</MaxHistory>
|
||||
<!--日志文件最大的大小-->
|
||||
<MaxFileSize>100MB</MaxFileSize>
|
||||
</rollingPolicy>
|
||||
|
||||
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
||||
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
|
||||
<pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%thread] [%-5level] %logger{50} - %msg%n</pattern>
|
||||
<charset>UTF-8</charset>
|
||||
</encoder>
|
||||
</appender>
|
||||
<!-- 生成 error 日志结束 -->
|
||||
|
||||
<!-- 每天生成一个html格式的日志开始 -->
|
||||
<appender name="SYSTEM_FILE_HTML" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||
<!--日志文件输出的文件名 -->
|
||||
<FileNamePattern>/${PROJECT_NAME}/${LOG_HOME_SYSTEM}/${PROJECT_NAME}-%d{yyyy-MM-dd}.%i.html</FileNamePattern>
|
||||
<!--日志文件保留天数 -->
|
||||
<MaxHistory>30</MaxHistory>
|
||||
<MaxFileSize>100MB</MaxFileSize>
|
||||
</rollingPolicy>
|
||||
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
|
||||
<layout class="ch.qos.logback.classic.html.HTMLLayout">
|
||||
<pattern>%p%d%msg%M%F{32}%L</pattern>
|
||||
</layout>
|
||||
</encoder>
|
||||
</appender>
|
||||
<!-- 每天生成一个html格式的日志结束 -->
|
||||
|
||||
<!-- Druid日志输出,用于记录执行INFO级别的慢SQL -->
|
||||
<appender name="DRUID_SLOWSQL_INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<!-- LevelFilter: 级别过滤器,根据日志级别进行过滤 -->
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<level>INFO</level>
|
||||
<onMatch>ACCEPT</onMatch>
|
||||
<onMismatch>DENY</onMismatch>
|
||||
</filter>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||
<!--日志文件输出的文件名-->
|
||||
<FileNamePattern>${LOG_HOME}/${PROJECT_NAME}/${LOG_HOME_DRUID}/${PROJECT_NAME}.druid_info.%d{yyyy-MM-dd}.%i.log</FileNamePattern>
|
||||
<!--日志文件保留天数-->
|
||||
<MaxHistory>15</MaxHistory>
|
||||
<!--日志文件最大的大小-->
|
||||
<MaxFileSize>100MB</MaxFileSize>
|
||||
</rollingPolicy>
|
||||
|
||||
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
||||
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
|
||||
<pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%thread] [%-5level] %logger{50} - %msg%n</pattern>
|
||||
<charset>UTF-8</charset>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- Druid打印的日志文件,用于记录执行WARN级别的SQL -->
|
||||
<appender name="DRUID_SLOWSQL_WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||
<level>WARN</level>
|
||||
</filter>
|
||||
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<!--日志文件输出的文件名-->
|
||||
<FileNamePattern>${LOG_HOME}/${PROJECT_NAME}/${LOG_HOME_DRUID}/${PROJECT_NAME}.druid_warn.%d{yyyy-MM-dd}.%i.log</FileNamePattern>
|
||||
<!--日志文件保留天数-->
|
||||
<MaxHistory>30</MaxHistory>
|
||||
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
|
||||
<maxFileSize>100MB</maxFileSize>
|
||||
</timeBasedFileNamingAndTriggeringPolicy>
|
||||
</rollingPolicy>
|
||||
|
||||
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
||||
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
|
||||
<pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%thread] [%-5level] %logger{50} - %msg%n</pattern>
|
||||
<charset>UTF-8</charset>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!--myibatis log configure -->
|
||||
<logger name="com.apache.ibatis" level="TRACE" />
|
||||
<logger name="java.sql.Connection" level="DEBUG" />
|
||||
<logger name="java.sql.Statement" level="DEBUG" />
|
||||
<logger name="java.sql.PreparedStatement" level="DEBUG" />
|
||||
|
||||
<logger name="com.alibaba.druid.filter.stat.StatFilter" level="INFO" additivity="true">
|
||||
<appender-ref ref="DRUID_SLOWSQL_INFO_FILE"/>
|
||||
</logger>
|
||||
|
||||
<logger name="com.alibaba.druid.filter.stat.StatFilter" level="WARN">
|
||||
<appender-ref ref="DRUID_SLOWSQL_WARN_FILE"/>
|
||||
</logger>
|
||||
|
||||
<!-- 日志输出级别 -->
|
||||
<root level="INFO">
|
||||
<appender-ref ref="STDOUT" />
|
||||
<appender-ref ref="SYSTEM_FILE" />
|
||||
<appender-ref ref="SYSTEM_ERROR_FILE" />
|
||||
<appender-ref ref="SYSTEM_FILE_HTML" />
|
||||
</root>
|
||||
|
||||
</configuration>
|
||||
@@ -0,0 +1,16 @@
|
||||
FROM anapsix/alpine-java:8_server-jre_unlimited
|
||||
|
||||
MAINTAINER test@163.com
|
||||
|
||||
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
|
||||
|
||||
RUN mkdir -p /jero-cloud-xxljob
|
||||
|
||||
WORKDIR /jero-cloud-xxljob
|
||||
|
||||
EXPOSE 9080
|
||||
|
||||
ADD ./target/jero-cloud-xxljob-2.5.1.jar ./
|
||||
|
||||
CMD java -Dfile.encoding=utf-8 -Djava.security.egd=file:/dev/./urandom -jar jero-cloud-xxljob-2.5.1.jar
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
- 初始化数据库(mysql)
|
||||
|
||||
jero-cloud-xxljob\doc\db\tables_xxl_job.sql
|
||||
|
||||
- 修改数据库连接
|
||||
|
||||
jero-cloud-xxljob\src\main\resources\application.yml
|
||||
|
||||
- 启动项目
|
||||
|
||||
jero-cloud-xxljob\src\main\java\com\xxl\job\admin\XxlJobAdminApplication.java
|
||||
|
||||
- 访问项目
|
||||
http://127.0.0.1:9080/xxl-job-admin/toLogin
|
||||
admin/123456
|
||||
|
||||
- docker方式安装
|
||||
|
||||
https://my.oschina.net/xxxx/blog/4729020
|
||||
|
||||
|
||||
|
||||
概念说明
|
||||
1、手工创建执行器,AppName对应服务名字 比如: jero-demo
|
||||
2、手工创建定时任务,选择执行器(服务)、JobHandler对应XxlJob的值
|
||||
@@ -0,0 +1,119 @@
|
||||
#
|
||||
# XXL-JOB v2.2.0
|
||||
# Copyright (c) 2015-present, xuxueli.
|
||||
|
||||
CREATE database if NOT EXISTS `xxl_job` default character set utf8mb4 collate utf8mb4_unicode_ci;
|
||||
use `xxl_job`;
|
||||
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
CREATE TABLE `xxl_job_info` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`job_group` int(11) NOT NULL COMMENT '执行器主键ID',
|
||||
`job_cron` varchar(128) NOT NULL COMMENT '任务执行CRON',
|
||||
`job_desc` varchar(255) NOT NULL,
|
||||
`add_time` datetime DEFAULT NULL,
|
||||
`update_time` datetime DEFAULT NULL,
|
||||
`author` varchar(64) DEFAULT NULL COMMENT '作者',
|
||||
`alarm_email` varchar(255) DEFAULT NULL COMMENT '报警邮件',
|
||||
`executor_route_strategy` varchar(50) DEFAULT NULL COMMENT '执行器路由策略',
|
||||
`executor_handler` varchar(255) DEFAULT NULL COMMENT '执行器任务handler',
|
||||
`executor_param` varchar(512) DEFAULT NULL COMMENT '执行器任务参数',
|
||||
`executor_block_strategy` varchar(50) DEFAULT NULL COMMENT '阻塞处理策略',
|
||||
`executor_timeout` int(11) NOT NULL DEFAULT '0' COMMENT '任务执行超时时间,单位秒',
|
||||
`executor_fail_retry_count` int(11) NOT NULL DEFAULT '0' COMMENT '失败重试次数',
|
||||
`glue_type` varchar(50) NOT NULL COMMENT 'GLUE类型',
|
||||
`glue_source` mediumtext COMMENT 'GLUE源代码',
|
||||
`glue_remark` varchar(128) DEFAULT NULL COMMENT 'GLUE备注',
|
||||
`glue_updatetime` datetime DEFAULT NULL COMMENT 'GLUE更新时间',
|
||||
`child_jobid` varchar(255) DEFAULT NULL COMMENT '子任务ID,多个逗号分隔',
|
||||
`trigger_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '调度状态:0-停止,1-运行',
|
||||
`trigger_last_time` bigint(13) NOT NULL DEFAULT '0' COMMENT '上次调度时间',
|
||||
`trigger_next_time` bigint(13) NOT NULL DEFAULT '0' COMMENT '下次调度时间',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
CREATE TABLE `xxl_job_log` (
|
||||
`id` bigint(20) NOT NULL AUTO_INCREMENT,
|
||||
`job_group` int(11) NOT NULL COMMENT '执行器主键ID',
|
||||
`job_id` int(11) NOT NULL COMMENT '任务,主键ID',
|
||||
`executor_address` varchar(255) DEFAULT NULL COMMENT '执行器地址,本次执行的地址',
|
||||
`executor_handler` varchar(255) DEFAULT NULL COMMENT '执行器任务handler',
|
||||
`executor_param` varchar(512) DEFAULT NULL COMMENT '执行器任务参数',
|
||||
`executor_sharding_param` varchar(20) DEFAULT NULL COMMENT '执行器任务分片参数,格式如 1/2',
|
||||
`executor_fail_retry_count` int(11) NOT NULL DEFAULT '0' COMMENT '失败重试次数',
|
||||
`trigger_time` datetime DEFAULT NULL COMMENT '调度-时间',
|
||||
`trigger_code` int(11) NOT NULL COMMENT '调度-结果',
|
||||
`trigger_msg` text COMMENT '调度-日志',
|
||||
`handle_time` datetime DEFAULT NULL COMMENT '执行-时间',
|
||||
`handle_code` int(11) NOT NULL COMMENT '执行-状态',
|
||||
`handle_msg` text COMMENT '执行-日志',
|
||||
`alarm_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '告警状态:0-默认、1-无需告警、2-告警成功、3-告警失败',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `I_trigger_time` (`trigger_time`),
|
||||
KEY `I_handle_code` (`handle_code`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
CREATE TABLE `xxl_job_log_report` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`trigger_day` datetime DEFAULT NULL COMMENT '调度-时间',
|
||||
`running_count` int(11) NOT NULL DEFAULT '0' COMMENT '运行中-日志数量',
|
||||
`suc_count` int(11) NOT NULL DEFAULT '0' COMMENT '执行成功-日志数量',
|
||||
`fail_count` int(11) NOT NULL DEFAULT '0' COMMENT '执行失败-日志数量',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `i_trigger_day` (`trigger_day`) USING BTREE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
CREATE TABLE `xxl_job_logglue` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`job_id` int(11) NOT NULL COMMENT '任务,主键ID',
|
||||
`glue_type` varchar(50) DEFAULT NULL COMMENT 'GLUE类型',
|
||||
`glue_source` mediumtext COMMENT 'GLUE源代码',
|
||||
`glue_remark` varchar(128) NOT NULL COMMENT 'GLUE备注',
|
||||
`add_time` datetime DEFAULT NULL,
|
||||
`update_time` datetime DEFAULT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
CREATE TABLE `xxl_job_registry` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`registry_group` varchar(50) NOT NULL,
|
||||
`registry_key` varchar(255) NOT NULL,
|
||||
`registry_value` varchar(255) NOT NULL,
|
||||
`update_time` datetime DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `i_g_k_v` (`registry_group`,`registry_key`,`registry_value`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
CREATE TABLE `xxl_job_group` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`app_name` varchar(64) NOT NULL COMMENT '执行器AppName',
|
||||
`title` varchar(12) NOT NULL COMMENT '执行器名称',
|
||||
`address_type` tinyint(4) NOT NULL DEFAULT '0' COMMENT '执行器地址类型:0=自动注册、1=手动录入',
|
||||
`address_list` varchar(512) DEFAULT NULL COMMENT '执行器地址列表,多地址逗号分隔',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
CREATE TABLE `xxl_job_user` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`username` varchar(50) NOT NULL COMMENT '账号',
|
||||
`password` varchar(50) NOT NULL COMMENT '密码',
|
||||
`role` tinyint(4) NOT NULL COMMENT '角色:0-普通用户、1-管理员',
|
||||
`permission` varchar(255) DEFAULT NULL COMMENT '权限:执行器ID列表,多个逗号分割',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `i_username` (`username`) USING BTREE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
CREATE TABLE `xxl_job_lock` (
|
||||
`lock_name` varchar(50) NOT NULL COMMENT '锁名称',
|
||||
PRIMARY KEY (`lock_name`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
|
||||
INSERT INTO `xxl_job_group`(`id`, `app_name`, `title`, `address_type`, `address_list`) VALUES (1, 'xxl-job-executor-sample', '示例执行器', 0, NULL);
|
||||
INSERT INTO `xxl_job_info`(`id`, `job_group`, `job_cron`, `job_desc`, `add_time`, `update_time`, `author`, `alarm_email`, `executor_route_strategy`, `executor_handler`, `executor_param`, `executor_block_strategy`, `executor_timeout`, `executor_fail_retry_count`, `glue_type`, `glue_source`, `glue_remark`, `glue_updatetime`, `child_jobid`) VALUES (1, 1, '0 0 0 * * ? *', '测试任务1', '2018-11-03 22:21:31', '2018-11-03 22:21:31', 'XXL', '', 'FIRST', 'demoJobHandler', '', 'SERIAL_EXECUTION', 0, 0, 'BEAN', '', 'GLUE代码初始化', '2018-11-03 22:21:31', '');
|
||||
INSERT INTO `xxl_job_user`(`id`, `username`, `password`, `role`, `permission`) VALUES (1, 'admin', 'e10adc3949ba59abbe56e057f20f883e', 1, NULL);
|
||||
INSERT INTO `xxl_job_lock` ( `lock_name`) VALUES ( 'schedule_lock');
|
||||
|
||||
commit;
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
<?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>jero-cloud-module</artifactId>
|
||||
<groupId>com.jero.boot</groupId>
|
||||
<version>2.5.1</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>jero-cloud-xxljob</artifactId>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<!-- starter-test:junit + spring-test + mockito -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!-- freemarker-starter -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-freemarker</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- starter-actuator -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- mybatis-starter:mybatis + mybatis-spring + hikari(default) -->
|
||||
<dependency>
|
||||
<groupId>org.mybatis.spring.boot</groupId>
|
||||
<artifactId>mybatis-spring-boot-starter</artifactId>
|
||||
<version>2.1.3</version>
|
||||
</dependency>
|
||||
<!-- mysql -->
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- mail-starter -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-mail</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.xuxueli</groupId>
|
||||
<artifactId>xxl-job-core</artifactId>
|
||||
<version>${xxl-job-core.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<!-- update-begin-author:taoyan date:20210226 for:docker部署报错:no main manifest attribute -->
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>repackage</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<includeSystemScope>true</includeSystemScope>
|
||||
<mainClass>com.xxl.job.admin.XxlJobAdminApplication</mainClass>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<!-- update-end-author:taoyan date:20210226 for:docker部署报错:no main manifest attribute -->
|
||||
</project>
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package com.xxl.job.admin;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
/**
|
||||
* @author xuxueli 2018-10-28 00:38:13
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class XxlJobAdminApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(XxlJobAdminApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
package com.xxl.job.admin.controller;
|
||||
|
||||
import com.xxl.job.admin.controller.annotation.PermissionLimit;
|
||||
import com.xxl.job.admin.service.LoginService;
|
||||
import com.xxl.job.admin.service.XxlJobService;
|
||||
import com.xxl.job.core.biz.model.ReturnT;
|
||||
import org.springframework.beans.propertyeditors.CustomDateEditor;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.WebDataBinder;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* index controller
|
||||
* @author xuxueli 2015-12-19 16:13:16
|
||||
*/
|
||||
@Controller
|
||||
public class IndexController {
|
||||
|
||||
@Resource
|
||||
private XxlJobService xxlJobService;
|
||||
@Resource
|
||||
private LoginService loginService;
|
||||
|
||||
|
||||
@RequestMapping("/")
|
||||
public String index(Model model) {
|
||||
|
||||
Map<String, Object> dashboardMap = xxlJobService.dashboardInfo();
|
||||
model.addAllAttributes(dashboardMap);
|
||||
|
||||
return "index";
|
||||
}
|
||||
|
||||
@RequestMapping("/chartInfo")
|
||||
@ResponseBody
|
||||
public ReturnT<Map<String, Object>> chartInfo(Date startDate, Date endDate) {
|
||||
return xxlJobService.chartInfo(startDate, endDate);
|
||||
}
|
||||
|
||||
@RequestMapping("/toLogin")
|
||||
@PermissionLimit(limit=false)
|
||||
public String toLogin(HttpServletRequest request, HttpServletResponse response) {
|
||||
if (loginService.ifLogin(request, response) != null) {
|
||||
return "redirect:/";
|
||||
}
|
||||
return "login";
|
||||
}
|
||||
|
||||
@PostMapping("login")
|
||||
@ResponseBody
|
||||
@PermissionLimit(limit=false)
|
||||
public ReturnT<String> loginDo(HttpServletRequest request, HttpServletResponse response, String userName, String password, String ifRemember){
|
||||
boolean ifRem = ifRemember!=null && ifRemember.trim().length()>0 && "on".equals(ifRemember);
|
||||
return loginService.login(request, response, userName, password, ifRem);
|
||||
}
|
||||
|
||||
@PostMapping("logout")
|
||||
@ResponseBody
|
||||
@PermissionLimit(limit=false)
|
||||
public ReturnT<String> logout(HttpServletRequest request, HttpServletResponse response){
|
||||
return loginService.logout(request, response);
|
||||
}
|
||||
|
||||
@RequestMapping("/help")
|
||||
public String help() {
|
||||
return "help";
|
||||
}
|
||||
|
||||
@InitBinder
|
||||
public void initBinder(WebDataBinder binder) {
|
||||
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
dateFormat.setLenient(false);
|
||||
binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, true));
|
||||
}
|
||||
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
package com.xxl.job.admin.controller;
|
||||
|
||||
import com.xxl.job.admin.controller.annotation.PermissionLimit;
|
||||
import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
|
||||
import com.xxl.job.core.biz.AdminBiz;
|
||||
import com.xxl.job.core.biz.model.HandleCallbackParam;
|
||||
import com.xxl.job.core.biz.model.RegistryParam;
|
||||
import com.xxl.job.core.biz.model.ReturnT;
|
||||
import com.xxl.job.core.util.GsonTool;
|
||||
import com.xxl.job.core.util.XxlJobRemotingUtil;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Created by xuxueli on 17/5/10.
|
||||
*/
|
||||
@Controller
|
||||
@RequestMapping("/api")
|
||||
public class JobApiController {
|
||||
|
||||
@Resource
|
||||
private AdminBiz adminBiz;
|
||||
|
||||
/**
|
||||
* api
|
||||
*
|
||||
* @param uri
|
||||
* @param data
|
||||
* @return
|
||||
*/
|
||||
@RequestMapping("/{uri}")
|
||||
@ResponseBody
|
||||
@PermissionLimit(limit=false)
|
||||
public ReturnT<String> api(HttpServletRequest request, @PathVariable("uri") String uri, @RequestBody(required = false) String data) {
|
||||
|
||||
// valid
|
||||
if (!"POST".equalsIgnoreCase(request.getMethod())) {
|
||||
return new ReturnT<>(ReturnT.FAIL_CODE, "invalid request, HttpMethod not support.");
|
||||
}
|
||||
if (uri==null || uri.trim().length()==0) {
|
||||
return new ReturnT<>(ReturnT.FAIL_CODE, "invalid request, uri-mapping empty.");
|
||||
}
|
||||
if (XxlJobAdminConfig.getAdminConfig().getAccessToken()!=null
|
||||
&& XxlJobAdminConfig.getAdminConfig().getAccessToken().trim().length()>0
|
||||
&& !XxlJobAdminConfig.getAdminConfig().getAccessToken().equals(request.getHeader(XxlJobRemotingUtil.XXL_JOB_ACCESS_TOKEN))) {
|
||||
return new ReturnT<>(ReturnT.FAIL_CODE, "The access token is wrong.");
|
||||
}
|
||||
|
||||
// services mapping
|
||||
if ("callback".equals(uri)) {
|
||||
List<HandleCallbackParam> callbackParamList = GsonTool.fromJson(data, List.class, HandleCallbackParam.class);
|
||||
return adminBiz.callback(callbackParamList);
|
||||
} else if ("registry".equals(uri)) {
|
||||
RegistryParam registryParam = GsonTool.fromJson(data, RegistryParam.class);
|
||||
return adminBiz.registry(registryParam);
|
||||
} else if ("registryRemove".equals(uri)) {
|
||||
RegistryParam registryParam = GsonTool.fromJson(data, RegistryParam.class);
|
||||
return adminBiz.registryRemove(registryParam);
|
||||
} else {
|
||||
return new ReturnT<>(ReturnT.FAIL_CODE, "invalid request, uri-mapping("+ uri +") not found.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
package com.xxl.job.admin.controller;
|
||||
|
||||
import com.xxl.job.admin.core.model.XxlJobInfo;
|
||||
import com.xxl.job.admin.core.model.XxlJobLogGlue;
|
||||
import com.xxl.job.admin.core.util.I18nUtil;
|
||||
import com.xxl.job.admin.dao.XxlJobInfoDao;
|
||||
import com.xxl.job.admin.dao.XxlJobLogGlueDao;
|
||||
import com.xxl.job.core.biz.model.ReturnT;
|
||||
import com.xxl.job.core.glue.GlueTypeEnum;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* job code controller
|
||||
* @author xuxueli 2015-12-19 16:13:16
|
||||
*/
|
||||
@Controller
|
||||
@RequestMapping("/jobcode")
|
||||
public class JobCodeController {
|
||||
|
||||
@Resource
|
||||
private XxlJobInfoDao xxlJobInfoDao;
|
||||
@Resource
|
||||
private XxlJobLogGlueDao xxlJobLogGlueDao;
|
||||
|
||||
@RequestMapping
|
||||
public String index(HttpServletRequest request, Model model, int jobId) {
|
||||
XxlJobInfo jobInfo = xxlJobInfoDao.loadById(jobId);
|
||||
List<XxlJobLogGlue> jobLogGlues = xxlJobLogGlueDao.findByJobId(jobId);
|
||||
|
||||
if (jobInfo == null) {
|
||||
throw new IllegalArgumentException(I18nUtil.getString("jobinfo_glue_jobid_unvalid"));
|
||||
}
|
||||
if (GlueTypeEnum.BEAN == GlueTypeEnum.match(jobInfo.getGlueType())) {
|
||||
throw new IllegalArgumentException(I18nUtil.getString("jobinfo_glue_gluetype_unvalid"));
|
||||
}
|
||||
|
||||
// valid permission
|
||||
JobInfoController.validPermission(request, jobInfo.getJobGroup());
|
||||
|
||||
// Glue类型-字典
|
||||
model.addAttribute("GlueTypeEnum", GlueTypeEnum.values());
|
||||
|
||||
model.addAttribute("jobInfo", jobInfo);
|
||||
model.addAttribute("jobLogGlues", jobLogGlues);
|
||||
return "jobcode/jobcode.index";
|
||||
}
|
||||
|
||||
@RequestMapping("/save")
|
||||
@ResponseBody
|
||||
public ReturnT<String> save(Model model, int id, String glueSource, String glueRemark) {
|
||||
// valid
|
||||
if (glueRemark==null) {
|
||||
return new ReturnT<>(500, (I18nUtil.getString("system_please_input") + I18nUtil.getString("jobinfo_glue_remark")) );
|
||||
}
|
||||
if (glueRemark.length()<4 || glueRemark.length()>100) {
|
||||
return new ReturnT<>(500, I18nUtil.getString("jobinfo_glue_remark_limit"));
|
||||
}
|
||||
XxlJobInfo existsJobInfo = xxlJobInfoDao.loadById(id);
|
||||
if (existsJobInfo == null) {
|
||||
return new ReturnT<>(500, I18nUtil.getString("jobinfo_glue_jobid_unvalid"));
|
||||
}
|
||||
|
||||
// update new code
|
||||
existsJobInfo.setGlueSource(glueSource);
|
||||
existsJobInfo.setGlueRemark(glueRemark);
|
||||
existsJobInfo.setGlueUpdatetime(new Date());
|
||||
|
||||
existsJobInfo.setUpdateTime(new Date());
|
||||
xxlJobInfoDao.update(existsJobInfo);
|
||||
|
||||
// log old code
|
||||
XxlJobLogGlue xxlJobLogGlue = new XxlJobLogGlue();
|
||||
xxlJobLogGlue.setJobId(existsJobInfo.getId());
|
||||
xxlJobLogGlue.setGlueType(existsJobInfo.getGlueType());
|
||||
xxlJobLogGlue.setGlueSource(glueSource);
|
||||
xxlJobLogGlue.setGlueRemark(glueRemark);
|
||||
|
||||
xxlJobLogGlue.setAddTime(new Date());
|
||||
xxlJobLogGlue.setUpdateTime(new Date());
|
||||
xxlJobLogGlueDao.save(xxlJobLogGlue);
|
||||
|
||||
// remove code backup more than 30
|
||||
xxlJobLogGlueDao.removeOld(existsJobInfo.getId(), 30);
|
||||
|
||||
return ReturnT.SUCCESS;
|
||||
}
|
||||
|
||||
}
|
||||
+202
@@ -0,0 +1,202 @@
|
||||
package com.xxl.job.admin.controller;
|
||||
|
||||
import com.xxl.job.admin.core.model.XxlJobGroup;
|
||||
import com.xxl.job.admin.core.model.XxlJobRegistry;
|
||||
import com.xxl.job.admin.core.util.I18nUtil;
|
||||
import com.xxl.job.admin.dao.XxlJobGroupDao;
|
||||
import com.xxl.job.admin.dao.XxlJobInfoDao;
|
||||
import com.xxl.job.admin.dao.XxlJobRegistryDao;
|
||||
import com.xxl.job.core.biz.model.ReturnT;
|
||||
import com.xxl.job.core.enums.RegistryConfig;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* job group controller
|
||||
* @author xuxueli 2016-10-02 20:52:56
|
||||
*/
|
||||
@Controller
|
||||
@RequestMapping("/jobgroup")
|
||||
public class JobGroupController {
|
||||
|
||||
@Resource
|
||||
public XxlJobInfoDao xxlJobInfoDao;
|
||||
@Resource
|
||||
public XxlJobGroupDao xxlJobGroupDao;
|
||||
@Resource
|
||||
private XxlJobRegistryDao xxlJobRegistryDao;
|
||||
|
||||
private String systemPleaseInput= "system_please_input";
|
||||
|
||||
@RequestMapping
|
||||
public String index(Model model) {
|
||||
return "jobgroup/jobgroup.index";
|
||||
}
|
||||
|
||||
@RequestMapping("/pageList")
|
||||
@ResponseBody
|
||||
public Map<String, Object> pageList(HttpServletRequest request,
|
||||
@RequestParam(required = false, defaultValue = "0") int start,
|
||||
@RequestParam(required = false, defaultValue = "10") int length,
|
||||
String appname, String title) {
|
||||
|
||||
// page query
|
||||
List<XxlJobGroup> list = xxlJobGroupDao.pageList(start, length, appname, title);
|
||||
int listCount = xxlJobGroupDao.pageListCount(start, length, appname, title);
|
||||
|
||||
// package result
|
||||
Map<String, Object> maps = new HashMap<>();
|
||||
maps.put("recordsTotal", listCount); // 总记录数
|
||||
maps.put("recordsFiltered", listCount); // 过滤后的总记录数
|
||||
maps.put("data", list); // 分页列表
|
||||
return maps;
|
||||
}
|
||||
|
||||
@RequestMapping("/save")
|
||||
@ResponseBody
|
||||
public ReturnT<String> save(XxlJobGroup xxlJobGroup){
|
||||
|
||||
// valid
|
||||
if (xxlJobGroup.getAppname()==null || xxlJobGroup.getAppname().trim().length()==0) {
|
||||
return new ReturnT<>(500, (I18nUtil.getString(systemPleaseInput)+"AppName") );
|
||||
}
|
||||
if (xxlJobGroup.getAppname().length()<4 || xxlJobGroup.getAppname().length()>64) {
|
||||
return new ReturnT<>(500, I18nUtil.getString("jobgroup_field_appname_length") );
|
||||
}
|
||||
if (xxlJobGroup.getTitle()==null || xxlJobGroup.getTitle().trim().length()==0) {
|
||||
return new ReturnT<>(500, (I18nUtil.getString(systemPleaseInput) + I18nUtil.getString("jobgroup_field_title")) );
|
||||
}
|
||||
if (xxlJobGroup.getAddressType()!=0) {
|
||||
ReturnT<String> x = getStringReturnT(xxlJobGroup);
|
||||
if (x != null) {
|
||||
return x;
|
||||
}
|
||||
}
|
||||
|
||||
int ret = xxlJobGroupDao.save(xxlJobGroup);
|
||||
return (ret>0)?ReturnT.SUCCESS:ReturnT.FAIL;
|
||||
}
|
||||
|
||||
private ReturnT<String> getStringReturnT(XxlJobGroup xxlJobGroup) {
|
||||
if (xxlJobGroup.getAddressList()==null || xxlJobGroup.getAddressList().trim().length()==0) {
|
||||
return new ReturnT<>(500, I18nUtil.getString("jobgroup_field_addressType_limit") );
|
||||
}
|
||||
String[] addresss = xxlJobGroup.getAddressList().split(",");
|
||||
for (String item: addresss) {
|
||||
if (item==null || item.trim().length()==0) {
|
||||
return new ReturnT<>(500, I18nUtil.getString("jobgroup_field_registryList_unvalid") );
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@RequestMapping("/update")
|
||||
@ResponseBody
|
||||
public ReturnT<String> update(XxlJobGroup xxlJobGroup){
|
||||
// valid
|
||||
if (xxlJobGroup.getAppname()==null || xxlJobGroup.getAppname().trim().length()==0) {
|
||||
return new ReturnT<>(500, (I18nUtil.getString(systemPleaseInput)+"AppName") );
|
||||
}
|
||||
if (xxlJobGroup.getAppname().length()<4 || xxlJobGroup.getAppname().length()>64) {
|
||||
return new ReturnT<>(500, I18nUtil.getString("jobgroup_field_appname_length") );
|
||||
}
|
||||
if (xxlJobGroup.getTitle()==null || xxlJobGroup.getTitle().trim().length()==0) {
|
||||
return new ReturnT<>(500, (I18nUtil.getString(systemPleaseInput) + I18nUtil.getString("jobgroup_field_title")) );
|
||||
}
|
||||
if (xxlJobGroup.getAddressType() == 0) {
|
||||
// 0=自动注册
|
||||
List<String> registryList = findRegistryByAppName(xxlJobGroup.getAppname());
|
||||
StringBuilder addressListStr = getStringBuilder(registryList);
|
||||
xxlJobGroup.setAddressList(addressListStr.toString());
|
||||
} else {
|
||||
// 1=手动录入
|
||||
if (xxlJobGroup.getAddressList()==null || xxlJobGroup.getAddressList().trim().length()==0) {
|
||||
return new ReturnT<>(500, I18nUtil.getString("jobgroup_field_addressType_limit") );
|
||||
}
|
||||
if (getAddresss(xxlJobGroup))
|
||||
return new ReturnT<>(500, I18nUtil.getString("jobgroup_field_registryList_unvalid"));
|
||||
}
|
||||
|
||||
int ret = xxlJobGroupDao.update(xxlJobGroup);
|
||||
return (ret>0)?ReturnT.SUCCESS:ReturnT.FAIL;
|
||||
}
|
||||
|
||||
private boolean getAddresss(XxlJobGroup xxlJobGroup) {
|
||||
String[] addresss = xxlJobGroup.getAddressList().split(",");
|
||||
for (String item : addresss) {
|
||||
if (item == null || item.trim().length() == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private StringBuilder getStringBuilder(List<String> registryList) {
|
||||
StringBuilder addressListStr = new StringBuilder();
|
||||
if (registryList!=null && !registryList.isEmpty()) {
|
||||
Collections.sort(registryList);
|
||||
addressListStr = new StringBuilder();
|
||||
for (String item:registryList) {
|
||||
addressListStr.append(item).append(",");
|
||||
}
|
||||
addressListStr = new StringBuilder(addressListStr.substring(0, addressListStr.length() - 1));
|
||||
}
|
||||
return addressListStr;
|
||||
}
|
||||
|
||||
private List<String> findRegistryByAppName(String appnameParam){
|
||||
HashMap<String, List<String>> appAddressMap = new HashMap<>();
|
||||
List<XxlJobRegistry> list = xxlJobRegistryDao.findAll(RegistryConfig.DEAD_TIMEOUT, new Date());
|
||||
if (list != null) {
|
||||
for (XxlJobRegistry item: list) {
|
||||
if (RegistryConfig.RegistType.EXECUTOR.name().equals(item.getRegistryGroup())) {
|
||||
String appname = item.getRegistryKey();
|
||||
List<String> registryList = appAddressMap.get(appname);
|
||||
if (registryList == null) {
|
||||
registryList = new ArrayList<>();
|
||||
}
|
||||
|
||||
if (!registryList.contains(item.getRegistryValue())) {
|
||||
registryList.add(item.getRegistryValue());
|
||||
}
|
||||
appAddressMap.put(appname, registryList);
|
||||
}
|
||||
}
|
||||
}
|
||||
return appAddressMap.get(appnameParam);
|
||||
}
|
||||
|
||||
@RequestMapping("/remove")
|
||||
@ResponseBody
|
||||
public ReturnT<String> remove(int id){
|
||||
|
||||
// valid
|
||||
int count = xxlJobInfoDao.pageListCount(0, 10, id, -1, null, null, null);
|
||||
if (count > 0) {
|
||||
return new ReturnT<>(500, I18nUtil.getString("jobgroup_del_limit_0") );
|
||||
}
|
||||
|
||||
List<XxlJobGroup> allList = xxlJobGroupDao.findAll();
|
||||
if (allList.size() == 1) {
|
||||
return new ReturnT<>(500, I18nUtil.getString("jobgroup_del_limit_1") );
|
||||
}
|
||||
|
||||
int ret = xxlJobGroupDao.remove(id);
|
||||
return (ret>0)?ReturnT.SUCCESS:ReturnT.FAIL;
|
||||
}
|
||||
|
||||
@RequestMapping("/loadById")
|
||||
@ResponseBody
|
||||
public ReturnT<XxlJobGroup> loadById(int id){
|
||||
XxlJobGroup jobGroup = xxlJobGroupDao.load(id);
|
||||
return jobGroup!=null?new ReturnT<>(jobGroup):new ReturnT<>(ReturnT.FAIL_CODE, null);
|
||||
}
|
||||
|
||||
}
|
||||
+171
@@ -0,0 +1,171 @@
|
||||
package com.xxl.job.admin.controller;
|
||||
|
||||
import com.xxl.job.admin.core.cron.CronExpression;
|
||||
import com.xxl.job.admin.core.exception.XxlJobException;
|
||||
import com.xxl.job.admin.core.model.XxlJobGroup;
|
||||
import com.xxl.job.admin.core.model.XxlJobInfo;
|
||||
import com.xxl.job.admin.core.model.XxlJobUser;
|
||||
import com.xxl.job.admin.core.route.ExecutorRouteStrategyEnum;
|
||||
import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
|
||||
import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
|
||||
import com.xxl.job.admin.core.util.I18nUtil;
|
||||
import com.xxl.job.admin.dao.XxlJobGroupDao;
|
||||
import com.xxl.job.admin.service.LoginService;
|
||||
import com.xxl.job.admin.service.XxlJobService;
|
||||
import com.xxl.job.core.biz.model.ReturnT;
|
||||
import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
|
||||
import com.xxl.job.core.glue.GlueTypeEnum;
|
||||
import com.xxl.job.core.util.DateUtil;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.text.ParseException;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* index controller
|
||||
* @author xuxueli 2015-12-19 16:13:16
|
||||
*/
|
||||
@Controller
|
||||
@RequestMapping("/jobinfo")
|
||||
public class JobInfoController {
|
||||
|
||||
@Resource
|
||||
private XxlJobGroupDao xxlJobGroupDao;
|
||||
@Resource
|
||||
private XxlJobService xxlJobService;
|
||||
|
||||
@RequestMapping
|
||||
public String index(HttpServletRequest request, Model model, @RequestParam(required = false, defaultValue = "-1") int jobGroup) {
|
||||
|
||||
// 枚举-字典
|
||||
model.addAttribute("ExecutorRouteStrategyEnum", ExecutorRouteStrategyEnum.values()); // 路由策略-列表
|
||||
model.addAttribute("GlueTypeEnum", GlueTypeEnum.values()); // Glue类型-字典
|
||||
model.addAttribute("ExecutorBlockStrategyEnum", ExecutorBlockStrategyEnum.values()); // 阻塞处理策略-字典
|
||||
|
||||
// 执行器列表
|
||||
List<XxlJobGroup> jobGroupListAll = xxlJobGroupDao.findAll();
|
||||
|
||||
// filter group
|
||||
List<XxlJobGroup> jobGroupList = filterJobGroupByRole(request, jobGroupListAll);
|
||||
if (!jobGroupList.isEmpty()) {
|
||||
throw new XxlJobException(I18nUtil.getString("jobgroup_empty"));
|
||||
}
|
||||
|
||||
model.addAttribute("JobGroupList", jobGroupList);
|
||||
model.addAttribute("jobGroup", jobGroup);
|
||||
|
||||
return "jobinfo/jobinfo.index";
|
||||
}
|
||||
|
||||
public static List<XxlJobGroup> filterJobGroupByRole(HttpServletRequest request, List<XxlJobGroup> jobGroupListAll){
|
||||
List<XxlJobGroup> jobGroupList = new ArrayList<>();
|
||||
if (jobGroupListAll!=null && !jobGroupListAll.isEmpty()) {
|
||||
XxlJobUser loginUser = (XxlJobUser) request.getAttribute(LoginService.LOGIN_IDENTITY_KEY);
|
||||
if (loginUser.getRole() == 1) {
|
||||
jobGroupList = jobGroupListAll;
|
||||
} else {
|
||||
List<String> groupIdStrs = new ArrayList<>();
|
||||
if (loginUser.getPermission()!=null && loginUser.getPermission().trim().length()>0) {
|
||||
groupIdStrs = Arrays.asList(loginUser.getPermission().trim().split(","));
|
||||
}
|
||||
for (XxlJobGroup groupItem:jobGroupListAll) {
|
||||
getAdd(jobGroupList, groupIdStrs, groupItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
return jobGroupList;
|
||||
}
|
||||
|
||||
private static void getAdd(List<XxlJobGroup> jobGroupList, List<String> groupIdStrs, XxlJobGroup groupItem) {
|
||||
if (groupIdStrs.contains(String.valueOf(groupItem.getId()))) {
|
||||
jobGroupList.add(groupItem);
|
||||
}
|
||||
}
|
||||
|
||||
public static void validPermission(HttpServletRequest request, int jobGroup) {
|
||||
XxlJobUser loginUser = (XxlJobUser) request.getAttribute(LoginService.LOGIN_IDENTITY_KEY);
|
||||
if (!loginUser.validPermission(jobGroup)) {
|
||||
throw new IllegalArgumentException(I18nUtil.getString("system_permission_limit") + "[username="+ loginUser.getUsername() +"]");
|
||||
}
|
||||
}
|
||||
|
||||
@RequestMapping("/pageList")
|
||||
@ResponseBody
|
||||
public Map<String, Object> pageList(@RequestParam(required = false, defaultValue = "0") int start,
|
||||
@RequestParam(required = false, defaultValue = "10") int length,
|
||||
int jobGroup, int triggerStatus, String jobDesc, String executorHandler, String author) {
|
||||
|
||||
return xxlJobService.pageList(start, length, jobGroup, triggerStatus, jobDesc, executorHandler, author);
|
||||
}
|
||||
|
||||
@RequestMapping("/add")
|
||||
@ResponseBody
|
||||
public ReturnT<String> add(XxlJobInfo jobInfo) {
|
||||
return xxlJobService.add(jobInfo);
|
||||
}
|
||||
|
||||
@RequestMapping("/update")
|
||||
@ResponseBody
|
||||
public ReturnT<String> update(XxlJobInfo jobInfo) {
|
||||
return xxlJobService.update(jobInfo);
|
||||
}
|
||||
|
||||
@RequestMapping("/remove")
|
||||
@ResponseBody
|
||||
public ReturnT<String> remove(int id) {
|
||||
return xxlJobService.remove(id);
|
||||
}
|
||||
|
||||
@RequestMapping("/stop")
|
||||
@ResponseBody
|
||||
public ReturnT<String> pause(int id) {
|
||||
return xxlJobService.stop(id);
|
||||
}
|
||||
|
||||
@RequestMapping("/start")
|
||||
@ResponseBody
|
||||
public ReturnT<String> start(int id) {
|
||||
return xxlJobService.start(id);
|
||||
}
|
||||
|
||||
@RequestMapping("/trigger")
|
||||
@ResponseBody
|
||||
//@PermissionLimit(limit = false)
|
||||
public ReturnT<String> triggerJob(int id, String executorParam, String addressList) {
|
||||
// force cover job param
|
||||
if (executorParam == null) {
|
||||
executorParam = "";
|
||||
}
|
||||
|
||||
JobTriggerPoolHelper.trigger(id, TriggerTypeEnum.MANUAL, -1, null, executorParam, addressList);
|
||||
return ReturnT.SUCCESS;
|
||||
}
|
||||
|
||||
@RequestMapping("/nextTriggerTime")
|
||||
@ResponseBody
|
||||
public ReturnT<List<String>> nextTriggerTime(String cron) {
|
||||
List<String> result = new ArrayList<>();
|
||||
try {
|
||||
CronExpression cronExpression = new CronExpression(cron);
|
||||
Date lastTime = new Date();
|
||||
for (int i = 0; i < 5; i++) {
|
||||
lastTime = cronExpression.getNextValidTimeAfter(lastTime);
|
||||
if (lastTime != null) {
|
||||
result.add(DateUtil.formatDateTime(lastTime));
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (ParseException e) {
|
||||
return new ReturnT<>(ReturnT.FAIL_CODE, I18nUtil.getString("jobinfo_field_cron_unvalid"));
|
||||
}
|
||||
return new ReturnT<>(result);
|
||||
}
|
||||
|
||||
}
|
||||
+231
@@ -0,0 +1,231 @@
|
||||
package com.xxl.job.admin.controller;
|
||||
|
||||
import com.xxl.job.admin.core.exception.XxlJobException;
|
||||
import com.xxl.job.admin.core.model.XxlJobGroup;
|
||||
import com.xxl.job.admin.core.model.XxlJobInfo;
|
||||
import com.xxl.job.admin.core.model.XxlJobLog;
|
||||
import com.xxl.job.admin.core.scheduler.XxlJobScheduler;
|
||||
import com.xxl.job.admin.core.util.I18nUtil;
|
||||
import com.xxl.job.admin.dao.XxlJobGroupDao;
|
||||
import com.xxl.job.admin.dao.XxlJobInfoDao;
|
||||
import com.xxl.job.admin.dao.XxlJobLogDao;
|
||||
import com.xxl.job.core.biz.ExecutorBiz;
|
||||
import com.xxl.job.core.biz.model.KillParam;
|
||||
import com.xxl.job.core.biz.model.LogParam;
|
||||
import com.xxl.job.core.biz.model.LogResult;
|
||||
import com.xxl.job.core.biz.model.ReturnT;
|
||||
import com.xxl.job.core.util.DateUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* index controller
|
||||
* @author xuxueli 2015-12-19 16:13:16
|
||||
*/
|
||||
@Controller
|
||||
@RequestMapping("/joblog")
|
||||
public class JobLogController {
|
||||
private static Logger logger = LoggerFactory.getLogger(JobLogController.class);
|
||||
|
||||
@Resource
|
||||
private XxlJobGroupDao xxlJobGroupDao;
|
||||
@Resource
|
||||
public XxlJobInfoDao xxlJobInfoDao;
|
||||
@Resource
|
||||
public XxlJobLogDao xxlJobLogDao;
|
||||
|
||||
@RequestMapping
|
||||
public String index(HttpServletRequest request, Model model, @RequestParam(required = false, defaultValue = "0") Integer jobId) {
|
||||
|
||||
// 执行器列表
|
||||
List<XxlJobGroup> jobGroupListAll = xxlJobGroupDao.findAll();
|
||||
|
||||
// filter group
|
||||
List<XxlJobGroup> jobGroupList = JobInfoController.filterJobGroupByRole(request, jobGroupListAll);
|
||||
if (jobGroupList==null || jobGroupList.isEmpty()) {
|
||||
throw new XxlJobException(I18nUtil.getString("jobgroup_empty"));
|
||||
}
|
||||
|
||||
model.addAttribute("JobGroupList", jobGroupList);
|
||||
|
||||
// 任务
|
||||
if (jobId > 0) {
|
||||
XxlJobInfo jobInfo = xxlJobInfoDao.loadById(jobId);
|
||||
if (jobInfo == null) {
|
||||
throw new IllegalArgumentException(I18nUtil.getString("jobinfo_field_id") + I18nUtil.getString("system_unvalid"));
|
||||
}
|
||||
|
||||
model.addAttribute("jobInfo", jobInfo);
|
||||
|
||||
// valid permission
|
||||
JobInfoController.validPermission(request, jobInfo.getJobGroup());
|
||||
}
|
||||
|
||||
return "joblog/joblog.index";
|
||||
}
|
||||
|
||||
@RequestMapping("/getJobsByGroup")
|
||||
@ResponseBody
|
||||
public ReturnT<List<XxlJobInfo>> getJobsByGroup(int jobGroup){
|
||||
List<XxlJobInfo> list = xxlJobInfoDao.getJobsByGroup(jobGroup);
|
||||
return new ReturnT<>(list);
|
||||
}
|
||||
|
||||
@RequestMapping("/pageList")
|
||||
@ResponseBody
|
||||
public Map<String, Object> pageList(HttpServletRequest request,
|
||||
@RequestParam(required = false, defaultValue = "0") int start,
|
||||
@RequestParam(required = false, defaultValue = "10") int length,
|
||||
int jobGroup, int jobId, int logStatus, String filterTime) {
|
||||
|
||||
// valid permission
|
||||
JobInfoController.validPermission(request, jobGroup); // 仅管理员支持查询全部;普通用户仅支持查询有权限的 jobGroup
|
||||
|
||||
// parse param
|
||||
Date triggerTimeStart = null;
|
||||
Date triggerTimeEnd = null;
|
||||
if (filterTime!=null && filterTime.trim().length()>0) {
|
||||
String[] temp = filterTime.split(" - ");
|
||||
if (temp.length == 2) {
|
||||
triggerTimeStart = DateUtil.parseDateTime(temp[0]);
|
||||
triggerTimeEnd = DateUtil.parseDateTime(temp[1]);
|
||||
}
|
||||
}
|
||||
|
||||
// page query
|
||||
List<XxlJobLog> list = xxlJobLogDao.pageList(start, length, jobGroup, jobId, triggerTimeStart, triggerTimeEnd, logStatus);
|
||||
int listCount = xxlJobLogDao.pageListCount(start, length, jobGroup, jobId, triggerTimeStart, triggerTimeEnd, logStatus);
|
||||
|
||||
// package result
|
||||
Map<String, Object> maps = new HashMap<>();
|
||||
maps.put("recordsTotal", listCount); // 总记录数
|
||||
maps.put("recordsFiltered", listCount); // 过滤后的总记录数
|
||||
maps.put("data", list); // 分页列表
|
||||
return maps;
|
||||
}
|
||||
|
||||
@RequestMapping("/logDetailPage")
|
||||
public String logDetailPage(int id, Model model){
|
||||
|
||||
// base check
|
||||
XxlJobLog jobLog = xxlJobLogDao.load(id);
|
||||
if (jobLog == null) {
|
||||
throw new IllegalArgumentException(I18nUtil.getString("joblog_logid_unvalid"));
|
||||
}
|
||||
|
||||
model.addAttribute("triggerCode", jobLog.getTriggerCode());
|
||||
model.addAttribute("handleCode", jobLog.getHandleCode());
|
||||
model.addAttribute("executorAddress", jobLog.getExecutorAddress());
|
||||
model.addAttribute("triggerTime", jobLog.getTriggerTime().getTime());
|
||||
model.addAttribute("logId", jobLog.getId());
|
||||
return "joblog/joblog.detail";
|
||||
}
|
||||
|
||||
@RequestMapping("/logDetailCat")
|
||||
@ResponseBody
|
||||
public ReturnT<LogResult> logDetailCat(String executorAddress, long triggerTime, long logId, int fromLineNum){
|
||||
try {
|
||||
ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(executorAddress);
|
||||
ReturnT<LogResult> logResult = executorBiz.log(new LogParam(triggerTime, logId, fromLineNum));
|
||||
|
||||
// is end
|
||||
if (logResult.getContent()!=null && logResult.getContent().getFromLineNum() > logResult.getContent().getToLineNum()) {
|
||||
XxlJobLog jobLog = xxlJobLogDao.load(logId);
|
||||
if (jobLog.getHandleCode() > 0) {
|
||||
logResult.getContent().setEnd(true);
|
||||
}
|
||||
}
|
||||
|
||||
return logResult;
|
||||
} catch (Exception e) {
|
||||
logger.error(e.getMessage(), e);
|
||||
return new ReturnT<>(ReturnT.FAIL_CODE, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@RequestMapping("/logKill")
|
||||
@ResponseBody
|
||||
public ReturnT<String> logKill(int id){
|
||||
// base check
|
||||
XxlJobLog log = xxlJobLogDao.load(id);
|
||||
XxlJobInfo jobInfo = xxlJobInfoDao.loadById(log.getJobId());
|
||||
if (jobInfo==null) {
|
||||
return new ReturnT<>(500, I18nUtil.getString("jobinfo_glue_jobid_unvalid"));
|
||||
}
|
||||
if (ReturnT.SUCCESS_CODE != log.getTriggerCode()) {
|
||||
return new ReturnT<>(500, I18nUtil.getString("joblog_kill_log_limit"));
|
||||
}
|
||||
|
||||
// request of kill
|
||||
ReturnT<String> runResult = null;
|
||||
try {
|
||||
ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(log.getExecutorAddress());
|
||||
runResult = executorBiz.kill(new KillParam(jobInfo.getId()));
|
||||
} catch (Exception e) {
|
||||
logger.error(e.getMessage(), e);
|
||||
runResult = new ReturnT<>(500, e.getMessage());
|
||||
}
|
||||
|
||||
if (ReturnT.SUCCESS_CODE == runResult.getCode()) {
|
||||
log.setHandleCode(ReturnT.FAIL_CODE);
|
||||
log.setHandleMsg( I18nUtil.getString("joblog_kill_log_byman")+":" + (runResult.getMsg()!=null?runResult.getMsg():""));
|
||||
log.setHandleTime(new Date());
|
||||
xxlJobLogDao.updateHandleInfo(log);
|
||||
return new ReturnT<>(runResult.getMsg());
|
||||
} else {
|
||||
return new ReturnT<>(500, runResult.getMsg());
|
||||
}
|
||||
}
|
||||
|
||||
@RequestMapping("/clearLog")
|
||||
@ResponseBody
|
||||
public ReturnT<String> clearLog(int jobGroup, int jobId, int type){
|
||||
|
||||
Date clearBeforeTime = null;
|
||||
int clearBeforeNum = 0;
|
||||
if (type == 1) {
|
||||
clearBeforeTime = DateUtil.addMonths(new Date(), -1); // 清理一个月之前日志数据
|
||||
} else if (type == 2) {
|
||||
clearBeforeTime = DateUtil.addMonths(new Date(), -3); // 清理三个月之前日志数据
|
||||
} else if (type == 3) {
|
||||
clearBeforeTime = DateUtil.addMonths(new Date(), -6); // 清理六个月之前日志数据
|
||||
} else if (type == 4) {
|
||||
clearBeforeTime = DateUtil.addYears(new Date(), -1); // 清理一年之前日志数据
|
||||
} else if (type == 5) {
|
||||
clearBeforeNum = 1000; // 清理一千条以前日志数据
|
||||
} else if (type == 6) {
|
||||
clearBeforeNum = 10000; // 清理一万条以前日志数据
|
||||
} else if (type == 7) {
|
||||
clearBeforeNum = 30000; // 清理三万条以前日志数据
|
||||
} else if (type == 8) {
|
||||
clearBeforeNum = 100000; // 清理十万条以前日志数据
|
||||
} else if (type == 9) {
|
||||
clearBeforeNum = 0; // 清理所有日志数据
|
||||
} else {
|
||||
return new ReturnT<>(ReturnT.FAIL_CODE, I18nUtil.getString("joblog_clean_type_unvalid"));
|
||||
}
|
||||
|
||||
List<Long> logIds = null;
|
||||
do {
|
||||
logIds = xxlJobLogDao.findClearLogIds(jobGroup, jobId, clearBeforeTime, clearBeforeNum, 1000);
|
||||
if (logIds!=null && !logIds.isEmpty()) {
|
||||
xxlJobLogDao.clearLog(logIds);
|
||||
}
|
||||
} while (logIds!=null && !logIds.isEmpty());
|
||||
|
||||
return ReturnT.SUCCESS;
|
||||
}
|
||||
|
||||
}
|
||||
+174
@@ -0,0 +1,174 @@
|
||||
package com.xxl.job.admin.controller;
|
||||
|
||||
import com.xxl.job.admin.controller.annotation.PermissionLimit;
|
||||
import com.xxl.job.admin.core.model.XxlJobGroup;
|
||||
import com.xxl.job.admin.core.model.XxlJobUser;
|
||||
import com.xxl.job.admin.core.util.I18nUtil;
|
||||
import com.xxl.job.admin.dao.XxlJobGroupDao;
|
||||
import com.xxl.job.admin.dao.XxlJobUserDao;
|
||||
import com.xxl.job.admin.service.LoginService;
|
||||
import com.xxl.job.core.biz.model.ReturnT;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.util.DigestUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author xuxueli 2019-05-04 16:39:50
|
||||
*/
|
||||
@Controller
|
||||
@RequestMapping("/user")
|
||||
public class UserController {
|
||||
|
||||
@Resource
|
||||
private XxlJobUserDao xxlJobUserDao;
|
||||
@Resource
|
||||
private XxlJobGroupDao xxlJobGroupDao;
|
||||
private String systemLenghLimit ="system_lengh_limit";
|
||||
private String fourTwenty ="[4-20]";
|
||||
|
||||
@RequestMapping
|
||||
@PermissionLimit(adminuser = true)
|
||||
public String index(Model model) {
|
||||
|
||||
// 执行器列表
|
||||
List<XxlJobGroup> groupList = xxlJobGroupDao.findAll();
|
||||
model.addAttribute("groupList", groupList);
|
||||
|
||||
return "user/user.index";
|
||||
}
|
||||
|
||||
@RequestMapping("/pageList")
|
||||
@ResponseBody
|
||||
@PermissionLimit(adminuser = true)
|
||||
public Map<String, Object> pageList(@RequestParam(required = false, defaultValue = "0") int start,
|
||||
@RequestParam(required = false, defaultValue = "10") int length,
|
||||
String username, int role) {
|
||||
|
||||
// page list
|
||||
List<XxlJobUser> list = xxlJobUserDao.pageList(start, length, username, role);
|
||||
int listCount = xxlJobUserDao.pageListCount(start, length, username, role);
|
||||
|
||||
// package result
|
||||
Map<String, Object> maps = new HashMap<>();
|
||||
maps.put("recordsTotal", listCount); // 总记录数
|
||||
maps.put("recordsFiltered", listCount); // 过滤后的总记录数
|
||||
maps.put("data", list); // 分页列表
|
||||
return maps;
|
||||
}
|
||||
|
||||
@RequestMapping("/add")
|
||||
@ResponseBody
|
||||
@PermissionLimit(adminuser = true)
|
||||
public ReturnT<String> add(XxlJobUser xxlJobUser) {
|
||||
|
||||
// valid username
|
||||
if (!StringUtils.hasText(xxlJobUser.getUsername())) {
|
||||
return new ReturnT<>(ReturnT.FAIL_CODE, I18nUtil.getString("system_please_input")+I18nUtil.getString("user_username") );
|
||||
}
|
||||
xxlJobUser.setUsername(xxlJobUser.getUsername().trim());
|
||||
if (!(xxlJobUser.getUsername().length()>=4 && xxlJobUser.getUsername().length()<=20)) {
|
||||
return new ReturnT<>(ReturnT.FAIL_CODE, I18nUtil.getString(systemLenghLimit)+fourTwenty );
|
||||
}
|
||||
// valid password
|
||||
if (!StringUtils.hasText(xxlJobUser.getPassword())) {
|
||||
return new ReturnT<>(ReturnT.FAIL_CODE, I18nUtil.getString("system_please_input")+I18nUtil.getString("user_password") );
|
||||
}
|
||||
xxlJobUser.setPassword(xxlJobUser.getPassword().trim());
|
||||
if (!(xxlJobUser.getPassword().length()>=4 && xxlJobUser.getPassword().length()<=20)) {
|
||||
return new ReturnT<>(ReturnT.FAIL_CODE, I18nUtil.getString(systemLenghLimit)+fourTwenty );
|
||||
}
|
||||
// md5 password
|
||||
xxlJobUser.setPassword(DigestUtils.md5DigestAsHex(xxlJobUser.getPassword().getBytes()));
|
||||
|
||||
// check repeat
|
||||
XxlJobUser existUser = xxlJobUserDao.loadByUserName(xxlJobUser.getUsername());
|
||||
if (existUser != null) {
|
||||
return new ReturnT<>(ReturnT.FAIL_CODE, I18nUtil.getString("user_username_repeat") );
|
||||
}
|
||||
|
||||
// write
|
||||
xxlJobUserDao.save(xxlJobUser);
|
||||
return ReturnT.SUCCESS;
|
||||
}
|
||||
|
||||
@RequestMapping("/update")
|
||||
@ResponseBody
|
||||
@PermissionLimit(adminuser = true)
|
||||
public ReturnT<String> update(HttpServletRequest request, XxlJobUser xxlJobUser) {
|
||||
|
||||
// avoid opt login seft
|
||||
XxlJobUser loginUser = (XxlJobUser) request.getAttribute(LoginService.LOGIN_IDENTITY_KEY);
|
||||
if (loginUser.getUsername().equals(xxlJobUser.getUsername())) {
|
||||
return new ReturnT<>(ReturnT.FAIL.getCode(), I18nUtil.getString("user_update_loginuser_limit"));
|
||||
}
|
||||
|
||||
// valid password
|
||||
if (StringUtils.hasText(xxlJobUser.getPassword())) {
|
||||
xxlJobUser.setPassword(xxlJobUser.getPassword().trim());
|
||||
if (!(xxlJobUser.getPassword().length()>=4 && xxlJobUser.getPassword().length()<=20)) {
|
||||
return new ReturnT<>(ReturnT.FAIL_CODE, I18nUtil.getString(systemLenghLimit)+fourTwenty );
|
||||
}
|
||||
// md5 password
|
||||
xxlJobUser.setPassword(DigestUtils.md5DigestAsHex(xxlJobUser.getPassword().getBytes()));
|
||||
} else {
|
||||
xxlJobUser.setPassword(null);
|
||||
}
|
||||
|
||||
// write
|
||||
xxlJobUserDao.update(xxlJobUser);
|
||||
return ReturnT.SUCCESS;
|
||||
}
|
||||
|
||||
@RequestMapping("/remove")
|
||||
@ResponseBody
|
||||
@PermissionLimit(adminuser = true)
|
||||
public ReturnT<String> remove(HttpServletRequest request, int id) {
|
||||
|
||||
// avoid opt login seft
|
||||
XxlJobUser loginUser = (XxlJobUser) request.getAttribute(LoginService.LOGIN_IDENTITY_KEY);
|
||||
if (loginUser.getId() == id) {
|
||||
return new ReturnT<>(ReturnT.FAIL.getCode(), I18nUtil.getString("user_update_loginuser_limit"));
|
||||
}
|
||||
|
||||
xxlJobUserDao.delete(id);
|
||||
return ReturnT.SUCCESS;
|
||||
}
|
||||
|
||||
@RequestMapping("/updatePwd")
|
||||
@ResponseBody
|
||||
public ReturnT<String> updatePwd(HttpServletRequest request, String password){
|
||||
|
||||
// valid password
|
||||
if (password==null || password.trim().length()==0){
|
||||
return new ReturnT<>(ReturnT.FAIL.getCode(), "密码不可为空");
|
||||
}
|
||||
password = password.trim();
|
||||
if (!(password.length()>=4 && password.length()<=20)) {
|
||||
return new ReturnT<>(ReturnT.FAIL_CODE, I18nUtil.getString(systemLenghLimit)+fourTwenty );
|
||||
}
|
||||
|
||||
// md5 password
|
||||
String md5Password = DigestUtils.md5DigestAsHex(password.getBytes());
|
||||
|
||||
// update pwd
|
||||
XxlJobUser loginUser = (XxlJobUser) request.getAttribute(LoginService.LOGIN_IDENTITY_KEY);
|
||||
|
||||
// do write
|
||||
XxlJobUser existUser = xxlJobUserDao.loadByUserName(loginUser.getUsername());
|
||||
existUser.setPassword(md5Password);
|
||||
xxlJobUserDao.update(existUser);
|
||||
|
||||
return ReturnT.SUCCESS;
|
||||
}
|
||||
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package com.xxl.job.admin.controller.annotation;
|
||||
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* 权限限制
|
||||
* @author xuxueli 2015-12-12 18:29:02
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface PermissionLimit {
|
||||
|
||||
/**
|
||||
* 登录拦截 (默认拦截)
|
||||
*/
|
||||
boolean limit() default true;
|
||||
|
||||
/**
|
||||
* 要求管理员权限
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
boolean adminuser() default false;
|
||||
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
package com.xxl.job.admin.controller.interceptor;
|
||||
|
||||
import com.xxl.job.admin.core.util.FtlUtil;
|
||||
import com.xxl.job.admin.core.util.I18nUtil;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* push cookies to model as cookieMap
|
||||
*
|
||||
* @author xuxueli 2015-12-12 18:09:04
|
||||
*/
|
||||
@Component
|
||||
public class CookieInterceptor extends HandlerInterceptorAdapter {
|
||||
|
||||
@Override
|
||||
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
|
||||
ModelAndView modelAndView) throws Exception {
|
||||
|
||||
// cookie
|
||||
if (modelAndView!=null && request.getCookies()!=null && request.getCookies().length>0) {
|
||||
HashMap<String, Cookie> cookieMap = new HashMap<>();
|
||||
for (Cookie ck : request.getCookies()) {
|
||||
cookieMap.put(ck.getName(), ck);
|
||||
}
|
||||
modelAndView.addObject("cookieMap", cookieMap);
|
||||
}
|
||||
|
||||
// static method
|
||||
if (modelAndView != null) {
|
||||
modelAndView.addObject("I18nUtil", FtlUtil.generateStaticModel(I18nUtil.class.getName()));
|
||||
}
|
||||
|
||||
super.postHandle(request, response, handler, modelAndView);
|
||||
}
|
||||
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
package com.xxl.job.admin.controller.interceptor;
|
||||
|
||||
import com.xxl.job.admin.controller.annotation.PermissionLimit;
|
||||
import com.xxl.job.admin.core.model.XxlJobUser;
|
||||
import com.xxl.job.admin.core.util.I18nUtil;
|
||||
import com.xxl.job.admin.service.LoginService;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* 权限拦截
|
||||
*
|
||||
* @author xuxueli 2015-12-12 18:09:04
|
||||
*/
|
||||
@Component
|
||||
public class PermissionInterceptor extends HandlerInterceptorAdapter {
|
||||
|
||||
@Resource
|
||||
private LoginService loginService;
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||||
|
||||
if (!(handler instanceof HandlerMethod)) {
|
||||
return super.preHandle(request, response, handler);
|
||||
}
|
||||
|
||||
// if need login
|
||||
boolean needLogin = true;
|
||||
boolean needAdminuser = false;
|
||||
HandlerMethod method = (HandlerMethod)handler;
|
||||
PermissionLimit permission = method.getMethodAnnotation(PermissionLimit.class);
|
||||
if (permission!=null) {
|
||||
needLogin = permission.limit();
|
||||
needAdminuser = permission.adminuser();
|
||||
}
|
||||
|
||||
if (needLogin) {
|
||||
XxlJobUser loginUser = loginService.ifLogin(request, response);
|
||||
if (loginUser == null) {
|
||||
response.sendRedirect(request.getContextPath() + "/toLogin");
|
||||
return false;
|
||||
}
|
||||
if (needAdminuser && loginUser.getRole()!=1) {
|
||||
throw new IllegalArgumentException(I18nUtil.getString("system_permission_limit"));
|
||||
}
|
||||
request.setAttribute(LoginService.LOGIN_IDENTITY_KEY, loginUser);
|
||||
}
|
||||
|
||||
return super.preHandle(request, response, handler);
|
||||
}
|
||||
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package com.xxl.job.admin.controller.interceptor;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* web mvc config
|
||||
*
|
||||
* @author xuxueli 2018-04-02 20:48:20
|
||||
*/
|
||||
@Configuration
|
||||
public class WebMvcConfig implements WebMvcConfigurer {
|
||||
|
||||
@Resource
|
||||
private PermissionInterceptor permissionInterceptor;
|
||||
@Resource
|
||||
private CookieInterceptor cookieInterceptor;
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
registry.addInterceptor(permissionInterceptor).addPathPatterns("/**");
|
||||
registry.addInterceptor(cookieInterceptor).addPathPatterns("/**");
|
||||
}
|
||||
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
package com.xxl.job.admin.controller.resolver;
|
||||
|
||||
import com.xxl.job.admin.core.exception.XxlJobException;
|
||||
import com.xxl.job.admin.core.util.JacksonUtil;
|
||||
import com.xxl.job.core.biz.model.ReturnT;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.servlet.HandlerExceptionResolver;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* common exception resolver
|
||||
*
|
||||
* @author xuxueli 2016-1-6 19:22:18
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class WebExceptionResolver implements HandlerExceptionResolver {
|
||||
|
||||
@Override
|
||||
public ModelAndView resolveException(HttpServletRequest request,
|
||||
HttpServletResponse response, Object handler, Exception ex) {
|
||||
|
||||
if (!(ex instanceof XxlJobException)) {
|
||||
log.error("WebExceptionResolver:{}", ex);
|
||||
}
|
||||
|
||||
// if json
|
||||
boolean isJson = false;
|
||||
HandlerMethod method = (HandlerMethod)handler;
|
||||
ResponseBody responseBody = method.getMethodAnnotation(ResponseBody.class);
|
||||
if (responseBody != null) {
|
||||
isJson = true;
|
||||
}
|
||||
|
||||
// error result
|
||||
ReturnT<String> errorResult = new ReturnT<>(ReturnT.FAIL_CODE, ex.toString().replace("\n", "<br/>"));
|
||||
|
||||
// response
|
||||
ModelAndView mv = new ModelAndView();
|
||||
if (isJson) {
|
||||
try {
|
||||
response.setContentType("application/json;charset=utf-8");
|
||||
response.getWriter().print(JacksonUtil.writeValueAsString(errorResult));
|
||||
} catch (IOException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
}
|
||||
return mv;
|
||||
} else {
|
||||
|
||||
mv.addObject("exceptionMsg", errorResult.getMsg());
|
||||
mv.setViewName("/common/common.exception");
|
||||
return mv;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package com.xxl.job.admin.core.alarm;
|
||||
|
||||
import com.xxl.job.admin.core.model.XxlJobInfo;
|
||||
import com.xxl.job.admin.core.model.XxlJobLog;
|
||||
|
||||
/**
|
||||
* @author xuxueli 2020-01-19
|
||||
*/
|
||||
public interface JobAlarm {
|
||||
|
||||
/**
|
||||
* job alarm
|
||||
*
|
||||
* @param info
|
||||
* @param jobLog
|
||||
* @return
|
||||
*/
|
||||
public boolean doAlarm(XxlJobInfo info, XxlJobLog jobLog);
|
||||
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
package com.xxl.job.admin.core.alarm;
|
||||
|
||||
import com.xxl.job.admin.core.model.XxlJobInfo;
|
||||
import com.xxl.job.admin.core.model.XxlJobLog;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Component
|
||||
public class JobAlarmer implements ApplicationContextAware, InitializingBean {
|
||||
private static Logger logger = LoggerFactory.getLogger(JobAlarmer.class);
|
||||
|
||||
private ApplicationContext applicationContext;
|
||||
private List<JobAlarm> jobAlarmList;
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
Map<String, JobAlarm> serviceBeanMap = applicationContext.getBeansOfType(JobAlarm.class);
|
||||
if (!serviceBeanMap.isEmpty()) {
|
||||
jobAlarmList = new ArrayList<>(serviceBeanMap.values());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* job alarm
|
||||
*
|
||||
* @param info
|
||||
* @param jobLog
|
||||
* @return
|
||||
*/
|
||||
public boolean alarm(XxlJobInfo info, XxlJobLog jobLog) {
|
||||
|
||||
boolean result = false;
|
||||
if (jobAlarmList!=null && !jobAlarmList.isEmpty()) {
|
||||
result = true; // success means all-success
|
||||
for (JobAlarm alarm: jobAlarmList) {
|
||||
boolean resultItem = false;
|
||||
try {
|
||||
resultItem = alarm.doAlarm(info, jobLog);
|
||||
} catch (Exception e) {
|
||||
logger.error(e.getMessage(), e);
|
||||
}
|
||||
if (!resultItem) {
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
package com.xxl.job.admin.core.alarm.impl;
|
||||
|
||||
import com.xxl.job.admin.core.alarm.JobAlarm;
|
||||
import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
|
||||
import com.xxl.job.admin.core.model.XxlJobGroup;
|
||||
import com.xxl.job.admin.core.model.XxlJobInfo;
|
||||
import com.xxl.job.admin.core.model.XxlJobLog;
|
||||
import com.xxl.job.admin.core.util.I18nUtil;
|
||||
import com.xxl.job.core.biz.model.ReturnT;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.mail.javamail.MimeMessageHelper;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.mail.internet.MimeMessage;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* job alarm by email
|
||||
*
|
||||
* @author xuxueli 2020-01-19
|
||||
*/
|
||||
@Component
|
||||
public class EmailJobAlarm implements JobAlarm {
|
||||
private static Logger logger = LoggerFactory.getLogger(EmailJobAlarm.class);
|
||||
|
||||
private static final String TD = "</td>\n";
|
||||
|
||||
/**
|
||||
* fail alarm
|
||||
*
|
||||
* @param jobLog
|
||||
*/
|
||||
public boolean doAlarm(XxlJobInfo info, XxlJobLog jobLog){
|
||||
boolean alarmResult = true;
|
||||
|
||||
// send monitor email
|
||||
if (info!=null && info.getAlarmEmail()!=null && info.getAlarmEmail().trim().length()>0) {
|
||||
|
||||
// alarmContent
|
||||
String alarmContent = "Alarm Job LogId=" + jobLog.getId();
|
||||
if (jobLog.getTriggerCode() != ReturnT.SUCCESS_CODE) {
|
||||
alarmContent += "<br>TriggerMsg=<br>" + jobLog.getTriggerMsg();
|
||||
}
|
||||
if (jobLog.getHandleCode()>0 && jobLog.getHandleCode() != ReturnT.SUCCESS_CODE) {
|
||||
alarmContent += "<br>HandleCode=" + jobLog.getHandleMsg();
|
||||
}
|
||||
|
||||
// email info
|
||||
XxlJobGroup group = XxlJobAdminConfig.getAdminConfig().getXxlJobGroupDao().load(info.getJobGroup());
|
||||
String personal = I18nUtil.getString("admin_name_full");
|
||||
String title = I18nUtil.getString("jobconf_monitor");
|
||||
String content = MessageFormat.format(loadEmailJobAlarmTemplate(),
|
||||
group!=null?group.getTitle():"null",
|
||||
info.getId(),
|
||||
info.getJobDesc(),
|
||||
alarmContent);
|
||||
|
||||
Set<String> emailSet = new HashSet<>(Arrays.asList(info.getAlarmEmail().split(",")));
|
||||
for (String email: emailSet) {
|
||||
|
||||
// make mail
|
||||
try {
|
||||
MimeMessage mimeMessage = XxlJobAdminConfig.getAdminConfig().getMailSender().createMimeMessage();
|
||||
|
||||
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
|
||||
helper.setFrom(XxlJobAdminConfig.getAdminConfig().getEmailUserName(), personal);
|
||||
helper.setTo(email);
|
||||
helper.setSubject(title);
|
||||
helper.setText(content, true);
|
||||
|
||||
XxlJobAdminConfig.getAdminConfig().getMailSender().send(mimeMessage);
|
||||
} catch (Exception e) {
|
||||
logger.error(">>>>>>>>>>> xxl-job, job fail alarm email send error, JobLogId:{}", jobLog.getId(), e);
|
||||
|
||||
alarmResult = false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return alarmResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* load email job alarm template
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private static final String loadEmailJobAlarmTemplate(){
|
||||
|
||||
return "<h5>" + I18nUtil.getString("jobconf_monitor_detail") + ":</span>" +
|
||||
"<table border=\"1\" cellpadding=\"3\" style=\"border-collapse:collapse; width:80%;\" >\n" +
|
||||
" <thead style=\"font-weight: bold;color: #ffffff;background-color: #ff8c00;\" >" +
|
||||
" <tr>\n" +
|
||||
" <td width=\"20%\" >"+ I18nUtil.getString("jobinfo_field_jobgroup") +TD +
|
||||
" <td width=\"10%\" >"+ I18nUtil.getString("jobinfo_field_id") +TD +
|
||||
" <td width=\"20%\" >"+ I18nUtil.getString("jobinfo_field_jobdesc") +TD +
|
||||
" <td width=\"10%\" >"+ I18nUtil.getString("jobconf_monitor_alarm_title") +TD +
|
||||
" <td width=\"40%\" >"+ I18nUtil.getString("jobconf_monitor_alarm_content") +TD +
|
||||
" </tr>\n" +
|
||||
" </thead>\n" +
|
||||
" <tbody>\n" +
|
||||
" <tr>\n" +
|
||||
" <td>{0}</td>\n" +
|
||||
" <td>{1}</td>\n" +
|
||||
" <td>{2}</td>\n" +
|
||||
" <td>"+ I18nUtil.getString("jobconf_monitor_alarm_type") +TD +
|
||||
" <td>{3}</td>\n" +
|
||||
" </tr>\n" +
|
||||
" </tbody>\n" +
|
||||
"</table>";
|
||||
}
|
||||
|
||||
}
|
||||
+156
@@ -0,0 +1,156 @@
|
||||
package com.xxl.job.admin.core.conf;
|
||||
|
||||
import com.xxl.job.admin.core.alarm.JobAlarmer;
|
||||
import com.xxl.job.admin.core.scheduler.XxlJobScheduler;
|
||||
import com.xxl.job.admin.dao.*;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.mail.javamail.JavaMailSender;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.sql.DataSource;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* xxl-job config
|
||||
*
|
||||
* @author xuxueli 2017-04-28
|
||||
*/
|
||||
|
||||
@Component
|
||||
public class XxlJobAdminConfig implements InitializingBean, DisposableBean {
|
||||
|
||||
private static XxlJobAdminConfig adminConfig = null;
|
||||
public static XxlJobAdminConfig getAdminConfig() {
|
||||
return adminConfig;
|
||||
}
|
||||
|
||||
|
||||
// ---------------------- XxlJobScheduler ----------------------
|
||||
|
||||
private XxlJobScheduler xxlJobScheduler;
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
xxlJobScheduler = new XxlJobScheduler();
|
||||
xxlJobScheduler.init();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() throws Exception {
|
||||
xxlJobScheduler.destroy();
|
||||
}
|
||||
|
||||
|
||||
// ---------------------- XxlJobScheduler ----------------------
|
||||
|
||||
// conf
|
||||
@Value("${xxl.job.i18n}")
|
||||
private String i18n;
|
||||
|
||||
@Value("${xxl.job.accessToken}")
|
||||
private String accessToken;
|
||||
|
||||
@Value("${spring.mail.username}")
|
||||
private String emailUserName;
|
||||
|
||||
@Value("${xxl.job.triggerpool.fast.max}")
|
||||
private int triggerPoolFastMax;
|
||||
|
||||
@Value("${xxl.job.triggerpool.slow.max}")
|
||||
private int triggerPoolSlowMax;
|
||||
|
||||
@Value("${xxl.job.logretentiondays}")
|
||||
private int logretentiondays;
|
||||
|
||||
// dao, service
|
||||
|
||||
@Resource
|
||||
private XxlJobLogDao xxlJobLogDao;
|
||||
@Resource
|
||||
private XxlJobInfoDao xxlJobInfoDao;
|
||||
@Resource
|
||||
private XxlJobRegistryDao xxlJobRegistryDao;
|
||||
@Resource
|
||||
private XxlJobGroupDao xxlJobGroupDao;
|
||||
@Resource
|
||||
private XxlJobLogReportDao xxlJobLogReportDao;
|
||||
@Resource
|
||||
private JavaMailSender mailSender;
|
||||
@Resource
|
||||
private DataSource dataSource;
|
||||
@Resource
|
||||
private JobAlarmer jobAlarmer;
|
||||
|
||||
|
||||
public String getI18n() {
|
||||
if (!Arrays.asList("zh_CN", "zh_TC", "en").contains(i18n)) {
|
||||
return "zh_CN";
|
||||
}
|
||||
return i18n;
|
||||
}
|
||||
|
||||
public String getAccessToken() {
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
public String getEmailUserName() {
|
||||
return emailUserName;
|
||||
}
|
||||
|
||||
public int getTriggerPoolFastMax() {
|
||||
if (triggerPoolFastMax < 200) {
|
||||
return 200;
|
||||
}
|
||||
return triggerPoolFastMax;
|
||||
}
|
||||
|
||||
public int getTriggerPoolSlowMax() {
|
||||
if (triggerPoolSlowMax < 100) {
|
||||
return 100;
|
||||
}
|
||||
return triggerPoolSlowMax;
|
||||
}
|
||||
|
||||
public int getLogretentiondays() {
|
||||
if (logretentiondays < 7) {
|
||||
return -1; // Limit greater than or equal to 7, otherwise close
|
||||
}
|
||||
return logretentiondays;
|
||||
}
|
||||
|
||||
public XxlJobLogDao getXxlJobLogDao() {
|
||||
return xxlJobLogDao;
|
||||
}
|
||||
|
||||
public XxlJobInfoDao getXxlJobInfoDao() {
|
||||
return xxlJobInfoDao;
|
||||
}
|
||||
|
||||
public XxlJobRegistryDao getXxlJobRegistryDao() {
|
||||
return xxlJobRegistryDao;
|
||||
}
|
||||
|
||||
public XxlJobGroupDao getXxlJobGroupDao() {
|
||||
return xxlJobGroupDao;
|
||||
}
|
||||
|
||||
public XxlJobLogReportDao getXxlJobLogReportDao() {
|
||||
return xxlJobLogReportDao;
|
||||
}
|
||||
|
||||
public JavaMailSender getMailSender() {
|
||||
return mailSender;
|
||||
}
|
||||
|
||||
public DataSource getDataSource() {
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
public JobAlarmer getJobAlarmer() {
|
||||
return jobAlarmer;
|
||||
}
|
||||
|
||||
}
|
||||
+1809
File diff suppressed because it is too large
Load Diff
+14
@@ -0,0 +1,14 @@
|
||||
package com.xxl.job.admin.core.exception;
|
||||
|
||||
/**
|
||||
* @author xuxueli 2019-05-04 23:19:29
|
||||
*/
|
||||
public class XxlJobException extends RuntimeException {
|
||||
|
||||
public XxlJobException() {
|
||||
}
|
||||
public XxlJobException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
package com.xxl.job.admin.core.model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Created by xuxueli on 16/9/30.
|
||||
*/
|
||||
public class XxlJobGroup {
|
||||
|
||||
private int id;
|
||||
private String appname;
|
||||
private String title;
|
||||
private int addressType; // 执行器地址类型:0=自动注册、1=手动录入
|
||||
private String addressList; // 执行器地址列表,多地址逗号分隔(手动录入)
|
||||
|
||||
// registry list
|
||||
private List<String> registryList; // 执行器地址列表(系统注册)
|
||||
public List<String> getRegistryList() {
|
||||
if (addressList!=null && addressList.trim().length()>0) {
|
||||
registryList = new ArrayList<>(Arrays.asList(addressList.split(",")));
|
||||
}
|
||||
return registryList;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getAppname() {
|
||||
return appname;
|
||||
}
|
||||
|
||||
public void setAppname(String appname) {
|
||||
this.appname = appname;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public int getAddressType() {
|
||||
return addressType;
|
||||
}
|
||||
|
||||
public void setAddressType(int addressType) {
|
||||
this.addressType = addressType;
|
||||
}
|
||||
|
||||
public String getAddressList() {
|
||||
return addressList;
|
||||
}
|
||||
|
||||
public void setAddressList(String addressList) {
|
||||
this.addressList = addressList;
|
||||
}
|
||||
|
||||
}
|
||||
+218
@@ -0,0 +1,218 @@
|
||||
package com.xxl.job.admin.core.model;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* xxl-job info
|
||||
*
|
||||
* @author xuxueli 2016-1-12 18:25:49
|
||||
*/
|
||||
public class XxlJobInfo {
|
||||
|
||||
private int id; // 主键ID
|
||||
|
||||
private int jobGroup; // 执行器主键ID
|
||||
private String jobCron; // 任务执行CRON表达式
|
||||
private String jobDesc;
|
||||
|
||||
private Date addTime;
|
||||
private Date updateTime;
|
||||
|
||||
private String author; // 负责人
|
||||
private String alarmEmail; // 报警邮件
|
||||
|
||||
private String executorRouteStrategy; // 执行器路由策略
|
||||
private String executorHandler; // 执行器,任务Handler名称
|
||||
private String executorParam; // 执行器,任务参数
|
||||
private String executorBlockStrategy; // 阻塞处理策略
|
||||
private int executorTimeout; // 任务执行超时时间,单位秒
|
||||
private int executorFailRetryCount; // 失败重试次数
|
||||
|
||||
private String glueType; // GLUE类型 #com.xxl.job.core.glue.GlueTypeEnum
|
||||
private String glueSource; // GLUE源代码
|
||||
private String glueRemark; // GLUE备注
|
||||
private Date glueUpdatetime; // GLUE更新时间
|
||||
|
||||
private String childJobId; // 子任务ID,多个逗号分隔
|
||||
|
||||
private int triggerStatus; // 调度状态:0-停止,1-运行
|
||||
private long triggerLastTime; // 上次调度时间
|
||||
private long triggerNextTime; // 下次调度时间
|
||||
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public int getJobGroup() {
|
||||
return jobGroup;
|
||||
}
|
||||
|
||||
public void setJobGroup(int jobGroup) {
|
||||
this.jobGroup = jobGroup;
|
||||
}
|
||||
|
||||
public String getJobCron() {
|
||||
return jobCron;
|
||||
}
|
||||
|
||||
public void setJobCron(String jobCron) {
|
||||
this.jobCron = jobCron;
|
||||
}
|
||||
|
||||
public String getJobDesc() {
|
||||
return jobDesc;
|
||||
}
|
||||
|
||||
public void setJobDesc(String jobDesc) {
|
||||
this.jobDesc = jobDesc;
|
||||
}
|
||||
|
||||
public Date getAddTime() {
|
||||
return addTime;
|
||||
}
|
||||
|
||||
public void setAddTime(Date addTime) {
|
||||
this.addTime = addTime;
|
||||
}
|
||||
|
||||
public Date getUpdateTime() {
|
||||
return updateTime;
|
||||
}
|
||||
|
||||
public void setUpdateTime(Date updateTime) {
|
||||
this.updateTime = updateTime;
|
||||
}
|
||||
|
||||
public String getAuthor() {
|
||||
return author;
|
||||
}
|
||||
|
||||
public void setAuthor(String author) {
|
||||
this.author = author;
|
||||
}
|
||||
|
||||
public String getAlarmEmail() {
|
||||
return alarmEmail;
|
||||
}
|
||||
|
||||
public void setAlarmEmail(String alarmEmail) {
|
||||
this.alarmEmail = alarmEmail;
|
||||
}
|
||||
|
||||
public String getExecutorRouteStrategy() {
|
||||
return executorRouteStrategy;
|
||||
}
|
||||
|
||||
public void setExecutorRouteStrategy(String executorRouteStrategy) {
|
||||
this.executorRouteStrategy = executorRouteStrategy;
|
||||
}
|
||||
|
||||
public String getExecutorHandler() {
|
||||
return executorHandler;
|
||||
}
|
||||
|
||||
public void setExecutorHandler(String executorHandler) {
|
||||
this.executorHandler = executorHandler;
|
||||
}
|
||||
|
||||
public String getExecutorParam() {
|
||||
return executorParam;
|
||||
}
|
||||
|
||||
public void setExecutorParam(String executorParam) {
|
||||
this.executorParam = executorParam;
|
||||
}
|
||||
|
||||
public String getExecutorBlockStrategy() {
|
||||
return executorBlockStrategy;
|
||||
}
|
||||
|
||||
public void setExecutorBlockStrategy(String executorBlockStrategy) {
|
||||
this.executorBlockStrategy = executorBlockStrategy;
|
||||
}
|
||||
|
||||
public int getExecutorTimeout() {
|
||||
return executorTimeout;
|
||||
}
|
||||
|
||||
public void setExecutorTimeout(int executorTimeout) {
|
||||
this.executorTimeout = executorTimeout;
|
||||
}
|
||||
|
||||
public int getExecutorFailRetryCount() {
|
||||
return executorFailRetryCount;
|
||||
}
|
||||
|
||||
public void setExecutorFailRetryCount(int executorFailRetryCount) {
|
||||
this.executorFailRetryCount = executorFailRetryCount;
|
||||
}
|
||||
|
||||
public String getGlueType() {
|
||||
return glueType;
|
||||
}
|
||||
|
||||
public void setGlueType(String glueType) {
|
||||
this.glueType = glueType;
|
||||
}
|
||||
|
||||
public String getGlueSource() {
|
||||
return glueSource;
|
||||
}
|
||||
|
||||
public void setGlueSource(String glueSource) {
|
||||
this.glueSource = glueSource;
|
||||
}
|
||||
|
||||
public String getGlueRemark() {
|
||||
return glueRemark;
|
||||
}
|
||||
|
||||
public void setGlueRemark(String glueRemark) {
|
||||
this.glueRemark = glueRemark;
|
||||
}
|
||||
|
||||
public Date getGlueUpdatetime() {
|
||||
return glueUpdatetime;
|
||||
}
|
||||
|
||||
public void setGlueUpdatetime(Date glueUpdatetime) {
|
||||
this.glueUpdatetime = glueUpdatetime;
|
||||
}
|
||||
|
||||
public String getChildJobId() {
|
||||
return childJobId;
|
||||
}
|
||||
|
||||
public void setChildJobId(String childJobId) {
|
||||
this.childJobId = childJobId;
|
||||
}
|
||||
|
||||
public int getTriggerStatus() {
|
||||
return triggerStatus;
|
||||
}
|
||||
|
||||
public void setTriggerStatus(int triggerStatus) {
|
||||
this.triggerStatus = triggerStatus;
|
||||
}
|
||||
|
||||
public long getTriggerLastTime() {
|
||||
return triggerLastTime;
|
||||
}
|
||||
|
||||
public void setTriggerLastTime(long triggerLastTime) {
|
||||
this.triggerLastTime = triggerLastTime;
|
||||
}
|
||||
|
||||
public long getTriggerNextTime() {
|
||||
return triggerNextTime;
|
||||
}
|
||||
|
||||
public void setTriggerNextTime(long triggerNextTime) {
|
||||
this.triggerNextTime = triggerNextTime;
|
||||
}
|
||||
}
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
package com.xxl.job.admin.core.model;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* xxl-job log, used to track trigger process
|
||||
* @author xuxueli 2015-12-19 23:19:09
|
||||
*/
|
||||
public class XxlJobLog {
|
||||
|
||||
private long id;
|
||||
|
||||
// job info
|
||||
private int jobGroup;
|
||||
private int jobId;
|
||||
|
||||
// execute info
|
||||
private String executorAddress;
|
||||
private String executorHandler;
|
||||
private String executorParam;
|
||||
private String executorShardingParam;
|
||||
private int executorFailRetryCount;
|
||||
|
||||
// trigger info
|
||||
private Date triggerTime;
|
||||
private int triggerCode;
|
||||
private String triggerMsg;
|
||||
|
||||
// handle info
|
||||
private Date handleTime;
|
||||
private int handleCode;
|
||||
private String handleMsg;
|
||||
|
||||
// alarm info
|
||||
private int alarmStatus;
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public int getJobGroup() {
|
||||
return jobGroup;
|
||||
}
|
||||
|
||||
public void setJobGroup(int jobGroup) {
|
||||
this.jobGroup = jobGroup;
|
||||
}
|
||||
|
||||
public int getJobId() {
|
||||
return jobId;
|
||||
}
|
||||
|
||||
public void setJobId(int jobId) {
|
||||
this.jobId = jobId;
|
||||
}
|
||||
|
||||
public String getExecutorAddress() {
|
||||
return executorAddress;
|
||||
}
|
||||
|
||||
public void setExecutorAddress(String executorAddress) {
|
||||
this.executorAddress = executorAddress;
|
||||
}
|
||||
|
||||
public String getExecutorHandler() {
|
||||
return executorHandler;
|
||||
}
|
||||
|
||||
public void setExecutorHandler(String executorHandler) {
|
||||
this.executorHandler = executorHandler;
|
||||
}
|
||||
|
||||
public String getExecutorParam() {
|
||||
return executorParam;
|
||||
}
|
||||
|
||||
public void setExecutorParam(String executorParam) {
|
||||
this.executorParam = executorParam;
|
||||
}
|
||||
|
||||
public String getExecutorShardingParam() {
|
||||
return executorShardingParam;
|
||||
}
|
||||
|
||||
public void setExecutorShardingParam(String executorShardingParam) {
|
||||
this.executorShardingParam = executorShardingParam;
|
||||
}
|
||||
|
||||
public int getExecutorFailRetryCount() {
|
||||
return executorFailRetryCount;
|
||||
}
|
||||
|
||||
public void setExecutorFailRetryCount(int executorFailRetryCount) {
|
||||
this.executorFailRetryCount = executorFailRetryCount;
|
||||
}
|
||||
|
||||
public Date getTriggerTime() {
|
||||
return triggerTime;
|
||||
}
|
||||
|
||||
public void setTriggerTime(Date triggerTime) {
|
||||
this.triggerTime = triggerTime;
|
||||
}
|
||||
|
||||
public int getTriggerCode() {
|
||||
return triggerCode;
|
||||
}
|
||||
|
||||
public void setTriggerCode(int triggerCode) {
|
||||
this.triggerCode = triggerCode;
|
||||
}
|
||||
|
||||
public String getTriggerMsg() {
|
||||
return triggerMsg;
|
||||
}
|
||||
|
||||
public void setTriggerMsg(String triggerMsg) {
|
||||
this.triggerMsg = triggerMsg;
|
||||
}
|
||||
|
||||
public Date getHandleTime() {
|
||||
return handleTime;
|
||||
}
|
||||
|
||||
public void setHandleTime(Date handleTime) {
|
||||
this.handleTime = handleTime;
|
||||
}
|
||||
|
||||
public int getHandleCode() {
|
||||
return handleCode;
|
||||
}
|
||||
|
||||
public void setHandleCode(int handleCode) {
|
||||
this.handleCode = handleCode;
|
||||
}
|
||||
|
||||
public String getHandleMsg() {
|
||||
return handleMsg;
|
||||
}
|
||||
|
||||
public void setHandleMsg(String handleMsg) {
|
||||
this.handleMsg = handleMsg;
|
||||
}
|
||||
|
||||
public int getAlarmStatus() {
|
||||
return alarmStatus;
|
||||
}
|
||||
|
||||
public void setAlarmStatus(int alarmStatus) {
|
||||
this.alarmStatus = alarmStatus;
|
||||
}
|
||||
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
package com.xxl.job.admin.core.model;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* xxl-job log for glue, used to track job code process
|
||||
* @author xuxueli 2016-5-19 17:57:46
|
||||
*/
|
||||
public class XxlJobLogGlue {
|
||||
|
||||
private int id;
|
||||
private int jobId; // 任务主键ID
|
||||
private String glueType; // GLUE类型 #com.xxl.job.core.glue.GlueTypeEnum
|
||||
private String glueSource;
|
||||
private String glueRemark;
|
||||
private Date addTime;
|
||||
private Date updateTime;
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public int getJobId() {
|
||||
return jobId;
|
||||
}
|
||||
|
||||
public void setJobId(int jobId) {
|
||||
this.jobId = jobId;
|
||||
}
|
||||
|
||||
public String getGlueType() {
|
||||
return glueType;
|
||||
}
|
||||
|
||||
public void setGlueType(String glueType) {
|
||||
this.glueType = glueType;
|
||||
}
|
||||
|
||||
public String getGlueSource() {
|
||||
return glueSource;
|
||||
}
|
||||
|
||||
public void setGlueSource(String glueSource) {
|
||||
this.glueSource = glueSource;
|
||||
}
|
||||
|
||||
public String getGlueRemark() {
|
||||
return glueRemark;
|
||||
}
|
||||
|
||||
public void setGlueRemark(String glueRemark) {
|
||||
this.glueRemark = glueRemark;
|
||||
}
|
||||
|
||||
public Date getAddTime() {
|
||||
return addTime;
|
||||
}
|
||||
|
||||
public void setAddTime(Date addTime) {
|
||||
this.addTime = addTime;
|
||||
}
|
||||
|
||||
public Date getUpdateTime() {
|
||||
return updateTime;
|
||||
}
|
||||
|
||||
public void setUpdateTime(Date updateTime) {
|
||||
this.updateTime = updateTime;
|
||||
}
|
||||
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
package com.xxl.job.admin.core.model;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public class XxlJobLogReport {
|
||||
|
||||
private int id;
|
||||
|
||||
private Date triggerDay;
|
||||
|
||||
private int runningCount;
|
||||
private int sucCount;
|
||||
private int failCount;
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Date getTriggerDay() {
|
||||
return triggerDay;
|
||||
}
|
||||
|
||||
public void setTriggerDay(Date triggerDay) {
|
||||
this.triggerDay = triggerDay;
|
||||
}
|
||||
|
||||
public int getRunningCount() {
|
||||
return runningCount;
|
||||
}
|
||||
|
||||
public void setRunningCount(int runningCount) {
|
||||
this.runningCount = runningCount;
|
||||
}
|
||||
|
||||
public int getSucCount() {
|
||||
return sucCount;
|
||||
}
|
||||
|
||||
public void setSucCount(int sucCount) {
|
||||
this.sucCount = sucCount;
|
||||
}
|
||||
|
||||
public int getFailCount() {
|
||||
return failCount;
|
||||
}
|
||||
|
||||
public void setFailCount(int failCount) {
|
||||
this.failCount = failCount;
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
package com.xxl.job.admin.core.model;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Created by xuxueli on 16/9/30.
|
||||
*/
|
||||
public class XxlJobRegistry {
|
||||
|
||||
private int id;
|
||||
private String registryGroup;
|
||||
private String registryKey;
|
||||
private String registryValue;
|
||||
private Date updateTime;
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getRegistryGroup() {
|
||||
return registryGroup;
|
||||
}
|
||||
|
||||
public void setRegistryGroup(String registryGroup) {
|
||||
this.registryGroup = registryGroup;
|
||||
}
|
||||
|
||||
public String getRegistryKey() {
|
||||
return registryKey;
|
||||
}
|
||||
|
||||
public void setRegistryKey(String registryKey) {
|
||||
this.registryKey = registryKey;
|
||||
}
|
||||
|
||||
public String getRegistryValue() {
|
||||
return registryValue;
|
||||
}
|
||||
|
||||
public void setRegistryValue(String registryValue) {
|
||||
this.registryValue = registryValue;
|
||||
}
|
||||
|
||||
public Date getUpdateTime() {
|
||||
return updateTime;
|
||||
}
|
||||
|
||||
public void setUpdateTime(Date updateTime) {
|
||||
this.updateTime = updateTime;
|
||||
}
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
package com.xxl.job.admin.core.model;
|
||||
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* @author xuxueli 2019-05-04 16:43:12
|
||||
*/
|
||||
public class XxlJobUser {
|
||||
|
||||
private int id;
|
||||
private String username; // 账号
|
||||
private String password; // 密码
|
||||
private int role; // 角色:0-普通用户、1-管理员
|
||||
private String permission; // 权限:执行器ID列表,多个逗号分割
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public int getRole() {
|
||||
return role;
|
||||
}
|
||||
|
||||
public void setRole(int role) {
|
||||
this.role = role;
|
||||
}
|
||||
|
||||
public String getPermission() {
|
||||
return permission;
|
||||
}
|
||||
|
||||
public void setPermission(String permission) {
|
||||
this.permission = permission;
|
||||
}
|
||||
|
||||
// plugin
|
||||
public boolean validPermission(int jobGroup){
|
||||
if (this.role == 1) {
|
||||
return true;
|
||||
} else {
|
||||
if (StringUtils.hasText(this.permission)) {
|
||||
for (String permissionItem : this.permission.split(",")) {
|
||||
if (String.valueOf(jobGroup).equals(permissionItem)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
package com.xxl.job.admin.core.route;
|
||||
|
||||
import com.xxl.job.admin.core.route.strategy.*;
|
||||
import com.xxl.job.admin.core.util.I18nUtil;
|
||||
|
||||
/**
|
||||
* Created by xuxueli on 17/3/10.
|
||||
*/
|
||||
public enum ExecutorRouteStrategyEnum {
|
||||
|
||||
FIRST(I18nUtil.getString("jobconf_route_first"), new ExecutorRouteFirst()),
|
||||
LAST(I18nUtil.getString("jobconf_route_last"), new ExecutorRouteLast()),
|
||||
ROUND(I18nUtil.getString("jobconf_route_round"), new ExecutorRouteRound()),
|
||||
RANDOM(I18nUtil.getString("jobconf_route_random"), new ExecutorRouteRandom()),
|
||||
CONSISTENT_HASH(I18nUtil.getString("jobconf_route_consistenthash"), new ExecutorRouteConsistentHash()),
|
||||
LEAST_FREQUENTLY_USED(I18nUtil.getString("jobconf_route_lfu"), new ExecutorRouteLFU()),
|
||||
LEAST_RECENTLY_USED(I18nUtil.getString("jobconf_route_lru"), new ExecutorRouteLRU()),
|
||||
FAILOVER(I18nUtil.getString("jobconf_route_failover"), new ExecutorRouteFailover()),
|
||||
BUSYOVER(I18nUtil.getString("jobconf_route_busyover"), new ExecutorRouteBusyover()),
|
||||
SHARDING_BROADCAST(I18nUtil.getString("jobconf_route_shard"), null);
|
||||
|
||||
ExecutorRouteStrategyEnum(String title, ExecutorRouter router) {
|
||||
this.title = title;
|
||||
this.router = router;
|
||||
}
|
||||
|
||||
private String title;
|
||||
private ExecutorRouter router;
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
public ExecutorRouter getRouter() {
|
||||
return router;
|
||||
}
|
||||
|
||||
public static ExecutorRouteStrategyEnum match(String name, ExecutorRouteStrategyEnum defaultItem){
|
||||
if (name != null) {
|
||||
for (ExecutorRouteStrategyEnum item: ExecutorRouteStrategyEnum.values()) {
|
||||
if (item.name().equals(name)) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
return defaultItem;
|
||||
}
|
||||
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package com.xxl.job.admin.core.route;
|
||||
|
||||
import com.xxl.job.core.biz.model.ReturnT;
|
||||
import com.xxl.job.core.biz.model.TriggerParam;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Created by xuxueli on 17/3/10.
|
||||
*/
|
||||
public abstract class ExecutorRouter {
|
||||
protected static Logger logger = LoggerFactory.getLogger(ExecutorRouter.class);
|
||||
|
||||
/**
|
||||
* route address
|
||||
*
|
||||
* @param addressList
|
||||
* @return ReturnT.content=address
|
||||
*/
|
||||
public abstract ReturnT<String> route(TriggerParam triggerParam, List<String> addressList);
|
||||
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
package com.xxl.job.admin.core.route.strategy;
|
||||
|
||||
import com.xxl.job.admin.core.scheduler.XxlJobScheduler;
|
||||
import com.xxl.job.admin.core.route.ExecutorRouter;
|
||||
import com.xxl.job.admin.core.util.I18nUtil;
|
||||
import com.xxl.job.core.biz.ExecutorBiz;
|
||||
import com.xxl.job.core.biz.model.IdleBeatParam;
|
||||
import com.xxl.job.core.biz.model.ReturnT;
|
||||
import com.xxl.job.core.biz.model.TriggerParam;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Created by xuxueli on 17/3/10.
|
||||
*/
|
||||
public class ExecutorRouteBusyover extends ExecutorRouter {
|
||||
|
||||
@Override
|
||||
public ReturnT<String> route(TriggerParam triggerParam, List<String> addressList) {
|
||||
StringBuilder idleBeatResultSB = new StringBuilder();
|
||||
for (String address : addressList) {
|
||||
// beat
|
||||
ReturnT<String> idleBeatResult = null;
|
||||
try {
|
||||
ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(address);
|
||||
idleBeatResult = executorBiz.idleBeat(new IdleBeatParam(triggerParam.getJobId()));
|
||||
} catch (Exception e) {
|
||||
logger.error(e.getMessage(), e);
|
||||
idleBeatResult = new ReturnT<>(ReturnT.FAIL_CODE, ""+e );
|
||||
}
|
||||
idleBeatResultSB.append( (idleBeatResultSB.length()>0)?"<br><br>":"")
|
||||
.append(I18nUtil.getString("jobconf_idleBeat") + ":")
|
||||
.append("<br>address:").append(address)
|
||||
.append("<br>code:").append(idleBeatResult.getCode())
|
||||
.append("<br>msg:").append(idleBeatResult.getMsg());
|
||||
|
||||
// beat success
|
||||
if (idleBeatResult.getCode() == ReturnT.SUCCESS_CODE) {
|
||||
idleBeatResult.setMsg(idleBeatResultSB.toString());
|
||||
idleBeatResult.setContent(address);
|
||||
return idleBeatResult;
|
||||
}
|
||||
}
|
||||
|
||||
return new ReturnT<>(ReturnT.FAIL_CODE, idleBeatResultSB.toString());
|
||||
}
|
||||
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
package com.xxl.job.admin.core.route.strategy;
|
||||
|
||||
import com.xxl.job.admin.core.route.ExecutorRouter;
|
||||
import com.xxl.job.core.biz.model.ReturnT;
|
||||
import com.xxl.job.core.biz.model.TriggerParam;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.List;
|
||||
import java.util.SortedMap;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/**
|
||||
* 分组下机器地址相同,不同JOB均匀散列在不同机器上,保证分组下机器分配JOB平均;且每个JOB固定调度其中一台机器;
|
||||
* a、virtual node:解决不均衡问题
|
||||
* b、hash method replace hashCode:String的hashCode可能重复,需要进一步扩大hashCode的取值范围
|
||||
* Created by xuxueli on 17/3/10.
|
||||
*/
|
||||
public class ExecutorRouteConsistentHash extends ExecutorRouter {
|
||||
|
||||
private static final int VIRTUAL_NODE_NUM = 100;
|
||||
|
||||
/**
|
||||
* get hash code on 2^32 ring (md5散列的方式计算hash值)
|
||||
* @param key
|
||||
* @return
|
||||
*/
|
||||
private static long hash(String key) {
|
||||
|
||||
// md5 byte
|
||||
MessageDigest md5;
|
||||
try {
|
||||
md5 = MessageDigest.getInstance("MD5");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new IllegalArgumentException("MD5 not supported", e);
|
||||
}
|
||||
md5.reset();
|
||||
byte[] keyBytes = null;
|
||||
keyBytes = key.getBytes(StandardCharsets.UTF_8);
|
||||
md5.update(keyBytes);
|
||||
byte[] digest = md5.digest();
|
||||
|
||||
// hash code, Truncate to 32-bits
|
||||
long hashCode = ((long) (digest[3] & 0xFF) << 24)
|
||||
| ((long) (digest[2] & 0xFF) << 16)
|
||||
| ((long) (digest[1] & 0xFF) << 8)
|
||||
| (digest[0] & 0xFF);
|
||||
|
||||
return hashCode & 0xffffffffL;
|
||||
}
|
||||
|
||||
public String hashJob(int jobId, List<String> addressList) {
|
||||
|
||||
// ------A1------A2-------A3------
|
||||
// -----------J1------------------
|
||||
TreeMap<Long, String> addressRing = new TreeMap<>();
|
||||
for (String address: addressList) {
|
||||
for (int i = 0; i < VIRTUAL_NODE_NUM; i++) {
|
||||
long addressHash = hash("SHARD-" + address + "-NODE-" + i);
|
||||
addressRing.put(addressHash, address);
|
||||
}
|
||||
}
|
||||
|
||||
long jobHash = hash(String.valueOf(jobId));
|
||||
SortedMap<Long, String> lastRing = addressRing.tailMap(jobHash);
|
||||
if (!lastRing.isEmpty()) {
|
||||
return lastRing.get(lastRing.firstKey());
|
||||
}
|
||||
return addressRing.firstEntry().getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReturnT<String> route(TriggerParam triggerParam, List<String> addressList) {
|
||||
String address = hashJob(triggerParam.getJobId(), addressList);
|
||||
return new ReturnT<>(address);
|
||||
}
|
||||
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
package com.xxl.job.admin.core.route.strategy;
|
||||
|
||||
import com.xxl.job.admin.core.scheduler.XxlJobScheduler;
|
||||
import com.xxl.job.admin.core.route.ExecutorRouter;
|
||||
import com.xxl.job.admin.core.util.I18nUtil;
|
||||
import com.xxl.job.core.biz.ExecutorBiz;
|
||||
import com.xxl.job.core.biz.model.ReturnT;
|
||||
import com.xxl.job.core.biz.model.TriggerParam;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Created by xuxueli on 17/3/10.
|
||||
*/
|
||||
public class ExecutorRouteFailover extends ExecutorRouter {
|
||||
|
||||
@Override
|
||||
public ReturnT<String> route(TriggerParam triggerParam, List<String> addressList) {
|
||||
|
||||
StringBuilder beatResultSB = new StringBuilder();
|
||||
for (String address : addressList) {
|
||||
// beat
|
||||
ReturnT<String> beatResult = null;
|
||||
try {
|
||||
ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(address);
|
||||
beatResult = executorBiz.beat();
|
||||
} catch (Exception e) {
|
||||
logger.error(e.getMessage(), e);
|
||||
beatResult = new ReturnT<>(ReturnT.FAIL_CODE, ""+e );
|
||||
}
|
||||
beatResultSB.append( (beatResultSB.length()>0)?"<br><br>":"")
|
||||
.append(I18nUtil.getString("jobconf_beat") + ":")
|
||||
.append("<br>address:").append(address)
|
||||
.append("<br>code:").append(beatResult.getCode())
|
||||
.append("<br>msg:").append(beatResult.getMsg());
|
||||
|
||||
// beat success
|
||||
if (beatResult.getCode() == ReturnT.SUCCESS_CODE) {
|
||||
|
||||
beatResult.setMsg(beatResultSB.toString());
|
||||
beatResult.setContent(address);
|
||||
return beatResult;
|
||||
}
|
||||
}
|
||||
return new ReturnT<>(ReturnT.FAIL_CODE, beatResultSB.toString());
|
||||
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package com.xxl.job.admin.core.route.strategy;
|
||||
|
||||
import com.xxl.job.admin.core.route.ExecutorRouter;
|
||||
import com.xxl.job.core.biz.model.ReturnT;
|
||||
import com.xxl.job.core.biz.model.TriggerParam;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Created by xuxueli on 17/3/10.
|
||||
*/
|
||||
public class ExecutorRouteFirst extends ExecutorRouter {
|
||||
|
||||
@Override
|
||||
public ReturnT<String> route(TriggerParam triggerParam, List<String> addressList){
|
||||
return new ReturnT<>(addressList.get(0));
|
||||
}
|
||||
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
package com.xxl.job.admin.core.route.strategy;
|
||||
|
||||
import com.xxl.job.admin.core.route.ExecutorRouter;
|
||||
import com.xxl.job.core.biz.model.ReturnT;
|
||||
import com.xxl.job.core.biz.model.TriggerParam;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
/**
|
||||
* 单个JOB对应的每个执行器,使用频率最低的优先被选举
|
||||
* a(*)、LFU(Least Frequently Used):最不经常使用,频率/次数
|
||||
* b、LRU(Least Recently Used):最近最久未使用,时间
|
||||
*
|
||||
* Created by xuxueli on 17/3/10.
|
||||
*/
|
||||
@Slf4j
|
||||
public class ExecutorRouteLFU extends ExecutorRouter {
|
||||
|
||||
private static ConcurrentMap<Integer, HashMap<String, Integer>> jobLfuMap = new ConcurrentHashMap<>();
|
||||
private long cacheValidTime = 0;
|
||||
|
||||
private static Random rand;
|
||||
static {
|
||||
try {
|
||||
rand = SecureRandom.getInstanceStrong();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
log.info("Exception:{}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public String route(int jobId, List<String> addressList) {
|
||||
|
||||
// cache clear
|
||||
if (System.currentTimeMillis() > cacheValidTime) {
|
||||
jobLfuMap.clear();
|
||||
cacheValidTime = System.currentTimeMillis() + 1000*60*60*24;
|
||||
}
|
||||
|
||||
// lfu item init
|
||||
HashMap<String, Integer> lfuItemMap = jobLfuMap.get(jobId); // Key排序可以用TreeMap+构造入参Compare;Value排序暂时只能通过ArrayList;
|
||||
if (lfuItemMap == null) {
|
||||
lfuItemMap = new HashMap<>();
|
||||
jobLfuMap.putIfAbsent(jobId, lfuItemMap); // 避免重复覆盖
|
||||
}
|
||||
|
||||
// put new
|
||||
for (String address: addressList) {
|
||||
if (!lfuItemMap.containsKey(address) || lfuItemMap.get(address) >1000000 ) {
|
||||
// 初始化时主动Random一次,缓解首次压力
|
||||
lfuItemMap.put(address, rand.nextInt(addressList.size()));
|
||||
}
|
||||
}
|
||||
// remove old
|
||||
List<String> delKeys = new ArrayList<>();
|
||||
for (String existKey: lfuItemMap.keySet()) {
|
||||
if (!addressList.contains(existKey)) {
|
||||
delKeys.add(existKey);
|
||||
}
|
||||
}
|
||||
if (!delKeys.isEmpty()) {
|
||||
for (String delKey: delKeys) {
|
||||
lfuItemMap.remove(delKey);
|
||||
}
|
||||
}
|
||||
|
||||
// load least userd count address
|
||||
List<Map.Entry<String, Integer>> lfuItemList = new ArrayList<>(lfuItemMap.entrySet());
|
||||
Collections.sort(lfuItemList, Comparator.comparing(Map.Entry::getValue));
|
||||
|
||||
Map.Entry<String, Integer> addressItem = lfuItemList.get(0);
|
||||
addressItem.setValue(addressItem.getValue() + 1);
|
||||
|
||||
return addressItem.getKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReturnT<String> route(TriggerParam triggerParam, List<String> addressList) {
|
||||
String address = route(triggerParam.getJobId(), addressList);
|
||||
return new ReturnT<>(address);
|
||||
}
|
||||
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
package com.xxl.job.admin.core.route.strategy;
|
||||
|
||||
import com.xxl.job.admin.core.route.ExecutorRouter;
|
||||
import com.xxl.job.core.biz.model.ReturnT;
|
||||
import com.xxl.job.core.biz.model.TriggerParam;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
/**
|
||||
* 单个JOB对应的每个执行器,最久为使用的优先被选举
|
||||
* a、LFU(Least Frequently Used):最不经常使用,频率/次数
|
||||
* b(*)、LRU(Least Recently Used):最近最久未使用,时间
|
||||
*
|
||||
* Created by xuxueli on 17/3/10.
|
||||
*/
|
||||
public class ExecutorRouteLRU extends ExecutorRouter {
|
||||
|
||||
private static ConcurrentMap<Integer, LinkedHashMap<String, String>> jobLRUMap = new ConcurrentHashMap<>();
|
||||
private long cacheValidTime = 0;
|
||||
|
||||
public String route(int jobId, List<String> addressList) {
|
||||
|
||||
// cache clear
|
||||
if (System.currentTimeMillis() > cacheValidTime) {
|
||||
jobLRUMap.clear();
|
||||
cacheValidTime = System.currentTimeMillis() + 1000*60*60*24;
|
||||
}
|
||||
|
||||
// init lru
|
||||
LinkedHashMap<String, String> lruItem = jobLRUMap.get(jobId);
|
||||
if (lruItem == null) {
|
||||
/**
|
||||
* LinkedHashMap
|
||||
* a、accessOrder:true=访问顺序排序(get/put时排序);false=插入顺序排期;
|
||||
* b、removeEldestEntry:新增元素时将会调用,返回true时会删除最老元素;可封装LinkedHashMap并重写该方法,比如定义最大容量,超出是返回true即可实现固定长度的LRU算法;
|
||||
*/
|
||||
lruItem = new LinkedHashMap<>(16, 0.75f, true);
|
||||
jobLRUMap.putIfAbsent(jobId, lruItem);
|
||||
}
|
||||
|
||||
// put new
|
||||
for (String address: addressList) {
|
||||
lruItem.computeIfAbsent(address,key->address);
|
||||
}
|
||||
// remove old
|
||||
List<String> delKeys = new ArrayList<>();
|
||||
for (String existKey: lruItem.keySet()) {
|
||||
if (!addressList.contains(existKey)) {
|
||||
delKeys.add(existKey);
|
||||
}
|
||||
}
|
||||
if (!delKeys.isEmpty()) {
|
||||
for (String delKey: delKeys) {
|
||||
lruItem.remove(delKey);
|
||||
}
|
||||
}
|
||||
|
||||
// load
|
||||
String eldestKey = lruItem.entrySet().iterator().next().getKey();
|
||||
return lruItem.get(eldestKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReturnT<String> route(TriggerParam triggerParam, List<String> addressList) {
|
||||
String address = route(triggerParam.getJobId(), addressList);
|
||||
return new ReturnT<>(address);
|
||||
}
|
||||
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package com.xxl.job.admin.core.route.strategy;
|
||||
|
||||
import com.xxl.job.admin.core.route.ExecutorRouter;
|
||||
import com.xxl.job.core.biz.model.ReturnT;
|
||||
import com.xxl.job.core.biz.model.TriggerParam;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Created by xuxueli on 17/3/10.
|
||||
*/
|
||||
public class ExecutorRouteLast extends ExecutorRouter {
|
||||
|
||||
@Override
|
||||
public ReturnT<String> route(TriggerParam triggerParam, List<String> addressList) {
|
||||
return new ReturnT<>(addressList.get(addressList.size()-1));
|
||||
}
|
||||
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package com.xxl.job.admin.core.route.strategy;
|
||||
|
||||
import com.xxl.job.admin.core.route.ExecutorRouter;
|
||||
import com.xxl.job.core.biz.model.ReturnT;
|
||||
import com.xxl.job.core.biz.model.TriggerParam;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* Created by xuxueli on 17/3/10.
|
||||
*/
|
||||
public class ExecutorRouteRandom extends ExecutorRouter {
|
||||
|
||||
private static Random localRandom = new Random();
|
||||
|
||||
@Override
|
||||
public ReturnT<String> route(TriggerParam triggerParam, List<String> addressList) {
|
||||
String address = addressList.get(localRandom.nextInt(addressList.size()));
|
||||
return new ReturnT<>(address);
|
||||
}
|
||||
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
package com.xxl.job.admin.core.route.strategy;
|
||||
|
||||
import com.xxl.job.admin.core.route.ExecutorRouter;
|
||||
import com.xxl.job.core.biz.model.ReturnT;
|
||||
import com.xxl.job.core.biz.model.TriggerParam;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
/**
|
||||
* Created by xuxueli on 17/3/10.
|
||||
*/
|
||||
@Slf4j
|
||||
public class ExecutorRouteRound extends ExecutorRouter {
|
||||
|
||||
private static ConcurrentMap<Integer, Integer> routeCountEachJob = new ConcurrentHashMap<>();
|
||||
private static long cacheValidTime = 0;
|
||||
|
||||
private static Random rand;
|
||||
static {
|
||||
try {
|
||||
rand = SecureRandom.getInstanceStrong();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
log.info("Exception:{}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static int count(int jobId) {
|
||||
// cache clear
|
||||
if (System.currentTimeMillis() > cacheValidTime) {
|
||||
routeCountEachJob.clear();
|
||||
cacheValidTime = System.currentTimeMillis() + 1000*60*60*24;
|
||||
}
|
||||
|
||||
// count++
|
||||
Integer count = routeCountEachJob.get(jobId);
|
||||
count = (count==null || count>1000000)?(rand.nextInt(100)):++count; // 初始化时主动Random一次,缓解首次压力
|
||||
routeCountEachJob.put(jobId, count);
|
||||
return count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReturnT<String> route(TriggerParam triggerParam, List<String> addressList) {
|
||||
String address = addressList.get(count(triggerParam.getJobId())%addressList.size());
|
||||
return new ReturnT<>(address);
|
||||
}
|
||||
|
||||
}
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
package com.xxl.job.admin.core.scheduler;
|
||||
|
||||
import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
|
||||
import com.xxl.job.admin.core.thread.*;
|
||||
import com.xxl.job.admin.core.util.I18nUtil;
|
||||
import com.xxl.job.core.biz.ExecutorBiz;
|
||||
import com.xxl.job.core.biz.client.ExecutorBizClient;
|
||||
import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
/**
|
||||
* @author xuxueli 2018-10-28 00:18:17
|
||||
*/
|
||||
|
||||
public class XxlJobScheduler {
|
||||
private static final Logger logger = LoggerFactory.getLogger(XxlJobScheduler.class);
|
||||
|
||||
|
||||
public void init() {
|
||||
// init i18n
|
||||
initI18n();
|
||||
|
||||
// admin registry monitor run
|
||||
JobRegistryMonitorHelper.getInstance().start();
|
||||
|
||||
// admin fail-monitor run
|
||||
JobFailMonitorHelper.getInstance().start();
|
||||
|
||||
// admin lose-monitor run
|
||||
JobLosedMonitorHelper.getInstance().start();
|
||||
|
||||
// admin trigger pool start
|
||||
JobTriggerPoolHelper.toStart();
|
||||
|
||||
// admin log report start
|
||||
JobLogReportHelper.getInstance().start();
|
||||
|
||||
// start-schedule
|
||||
JobScheduleHelper.getInstance().start();
|
||||
|
||||
logger.info(">>>>>>>>> init xxl-job admin success.");
|
||||
}
|
||||
|
||||
|
||||
public void destroy() {
|
||||
|
||||
// stop-schedule
|
||||
JobScheduleHelper.getInstance().toStop();
|
||||
|
||||
// admin log report stop
|
||||
JobLogReportHelper.getInstance().toStop();
|
||||
|
||||
// admin trigger pool stop
|
||||
JobTriggerPoolHelper.toStop();
|
||||
|
||||
// admin lose-monitor stop
|
||||
JobLosedMonitorHelper.getInstance().toStop();
|
||||
|
||||
// admin fail-monitor stop
|
||||
JobFailMonitorHelper.getInstance().toStop();
|
||||
|
||||
// admin registry stop
|
||||
JobRegistryMonitorHelper.getInstance().toStop();
|
||||
|
||||
}
|
||||
|
||||
// ---------------------- I18n ----------------------
|
||||
|
||||
private void initI18n(){
|
||||
for (ExecutorBlockStrategyEnum item:ExecutorBlockStrategyEnum.values()) {
|
||||
item.setTitle(I18nUtil.getString("jobconf_block_".concat(item.name())));
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------- executor-client ----------------------
|
||||
private static ConcurrentMap<String, ExecutorBiz> executorBizRepository = new ConcurrentHashMap<>();
|
||||
public static ExecutorBiz getExecutorBiz(String address) {
|
||||
// valid
|
||||
if (address==null || address.trim().length()==0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// load-cache
|
||||
address = address.trim();
|
||||
ExecutorBiz executorBiz = executorBizRepository.get(address);
|
||||
if (executorBiz != null) {
|
||||
return executorBiz;
|
||||
}
|
||||
|
||||
// set-cache
|
||||
executorBiz = new ExecutorBizClient(address, XxlJobAdminConfig.getAdminConfig().getAccessToken());
|
||||
|
||||
executorBizRepository.put(address, executorBiz);
|
||||
return executorBiz;
|
||||
}
|
||||
|
||||
}
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
package com.xxl.job.admin.core.thread;
|
||||
|
||||
import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
|
||||
import com.xxl.job.admin.core.model.XxlJobInfo;
|
||||
import com.xxl.job.admin.core.model.XxlJobLog;
|
||||
import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
|
||||
import com.xxl.job.admin.core.util.I18nUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* job monitor instance
|
||||
*
|
||||
* @author xuxueli 2015-9-1 18:05:56
|
||||
*/
|
||||
@Slf4j
|
||||
public class JobFailMonitorHelper {
|
||||
|
||||
private static JobFailMonitorHelper instance = new JobFailMonitorHelper();
|
||||
|
||||
public static JobFailMonitorHelper getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
// ---------------------- monitor ----------------------
|
||||
|
||||
private Thread monitorThread;
|
||||
private volatile boolean toStop = false;
|
||||
|
||||
public void start() {
|
||||
monitorThread = new Thread(() -> {
|
||||
// monitor
|
||||
getWhile();
|
||||
|
||||
log.info(">>>>>>>>>>> xxl-job, job fail monitor thread stop");
|
||||
|
||||
|
||||
});
|
||||
monitorThread.setDaemon(true);
|
||||
monitorThread.setName("xxl-job, admin JobFailMonitorHelper");
|
||||
monitorThread.start();
|
||||
}
|
||||
|
||||
private void getWhile() {
|
||||
while (!toStop) {
|
||||
try {
|
||||
List<Long> failLogIds = XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().findFailJobLogIds(1000);
|
||||
if (failLogIds != null && !failLogIds.isEmpty()) {
|
||||
getFor(failLogIds);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
if (!toStop) {
|
||||
log.error(">>>>>>>>>>> xxl-job, job fail monitor thread error:{}", e);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
TimeUnit.SECONDS.sleep(10);
|
||||
} catch (Exception e) {
|
||||
if (!toStop) {
|
||||
log.error(e.getMessage(), e);
|
||||
}
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void getFor(List<Long> failLogIds) {
|
||||
for (long failLogId : failLogIds) {
|
||||
|
||||
// lock log
|
||||
int lockRet = XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().updateAlarmStatus(failLogId, 0, -1);
|
||||
if (lockRet < 1) {
|
||||
continue;
|
||||
}
|
||||
XxlJobLog log = XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().load(failLogId);
|
||||
XxlJobInfo info = XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().loadById(log.getJobId());
|
||||
|
||||
// 1、fail retry monitor
|
||||
if (log.getExecutorFailRetryCount() > 0) {
|
||||
JobTriggerPoolHelper.trigger(log.getJobId(), TriggerTypeEnum.RETRY, (log.getExecutorFailRetryCount() - 1), log.getExecutorShardingParam(), log.getExecutorParam(), null);
|
||||
String retryMsg = "<br><br><span style=\"color:#F39C12;\" > >>>>>>>>>>>" + I18nUtil.getString("jobconf_trigger_type_retry") + "<<<<<<<<<<< </span><br>";
|
||||
log.setTriggerMsg(log.getTriggerMsg() + retryMsg);
|
||||
XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().updateTriggerInfo(log);
|
||||
}
|
||||
|
||||
// 2、fail alarm monitor
|
||||
int newAlarmStatus = 0; // 告警状态:0-默认、-1=锁定状态、1-无需告警、2-告警成功、3-告警失败
|
||||
if (info != null && info.getAlarmEmail() != null && info.getAlarmEmail().trim().length() > 0) {
|
||||
boolean alarmResult = XxlJobAdminConfig.getAdminConfig().getJobAlarmer().alarm(info, log);
|
||||
newAlarmStatus = alarmResult ? 2 : 3;
|
||||
} else {
|
||||
newAlarmStatus = 1;
|
||||
}
|
||||
|
||||
XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().updateAlarmStatus(failLogId, -1, newAlarmStatus);
|
||||
}
|
||||
}
|
||||
|
||||
public void toStop() {
|
||||
toStop = true;
|
||||
// interrupt and wait
|
||||
monitorThread.interrupt();
|
||||
try {
|
||||
monitorThread.join();
|
||||
} catch (InterruptedException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
package com.xxl.job.admin.core.thread;
|
||||
|
||||
import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
|
||||
import com.xxl.job.admin.core.model.XxlJobLogReport;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* job log report helper
|
||||
*
|
||||
* @author xuxueli 2019-11-22
|
||||
*/
|
||||
@Slf4j
|
||||
public class JobLogReportHelper {
|
||||
|
||||
private static JobLogReportHelper instance = new JobLogReportHelper();
|
||||
|
||||
public static JobLogReportHelper getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
|
||||
private Thread logrThread;
|
||||
private volatile boolean toStop = false;
|
||||
|
||||
public void start() {
|
||||
logrThread = new Thread(() -> {
|
||||
// last clean log time
|
||||
long lastCleanLogTime = 0;
|
||||
getWhile(lastCleanLogTime);
|
||||
|
||||
log.info(">>>>>>>>>>> xxl-job, job log report thread stop");
|
||||
});
|
||||
logrThread.setDaemon(true);
|
||||
logrThread.setName("xxl-job, admin JobLogReportHelper");
|
||||
logrThread.start();
|
||||
}
|
||||
|
||||
private void getWhile(long lastCleanLogTime) {
|
||||
while (!toStop) {
|
||||
// 1、log-report refresh: refresh log report in 3 days
|
||||
try {
|
||||
getFor();
|
||||
|
||||
} catch (Exception e) {
|
||||
if (!toStop) {
|
||||
log.error(">>>>>>>>>>> xxl-job, job log report thread error:{}", e);
|
||||
}
|
||||
}
|
||||
|
||||
// 2、log-clean: switch open & once each day
|
||||
lastCleanLogTime = getLastCleanLogTime(lastCleanLogTime);
|
||||
|
||||
try {
|
||||
TimeUnit.MINUTES.sleep(1);
|
||||
} catch (Exception e) {
|
||||
if (!toStop) {
|
||||
log.error(e.getMessage(), e);
|
||||
}
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private long getLastCleanLogTime(long lastCleanLogTime) {
|
||||
if (XxlJobAdminConfig.getAdminConfig().getLogretentiondays() > 0
|
||||
&& System.currentTimeMillis() - lastCleanLogTime > 24 * 60 * 60 * 1000) {
|
||||
|
||||
// expire-time
|
||||
Calendar expiredDay = Calendar.getInstance();
|
||||
expiredDay.add(Calendar.DAY_OF_MONTH, -1 * XxlJobAdminConfig.getAdminConfig().getLogretentiondays());
|
||||
expiredDay.set(Calendar.HOUR_OF_DAY, 0);
|
||||
expiredDay.set(Calendar.MINUTE, 0);
|
||||
expiredDay.set(Calendar.SECOND, 0);
|
||||
expiredDay.set(Calendar.MILLISECOND, 0);
|
||||
Date clearBeforeTime = expiredDay.getTime();
|
||||
|
||||
// clean expired log
|
||||
List<Long> logIds = null;
|
||||
do {
|
||||
logIds = XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().findClearLogIds(0, 0, clearBeforeTime, 0, 1000);
|
||||
if (logIds != null && !logIds.isEmpty()) {
|
||||
XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().clearLog(logIds);
|
||||
}
|
||||
} while (logIds != null && !logIds.isEmpty());
|
||||
|
||||
// update clean time
|
||||
lastCleanLogTime = System.currentTimeMillis();
|
||||
}
|
||||
return lastCleanLogTime;
|
||||
}
|
||||
|
||||
private void getFor() {
|
||||
for (int i = 0; i < 3; i++) {
|
||||
// today
|
||||
Calendar itemDay = Calendar.getInstance();
|
||||
itemDay.add(Calendar.DAY_OF_MONTH, -i);
|
||||
itemDay.set(Calendar.HOUR_OF_DAY, 0);
|
||||
itemDay.set(Calendar.MINUTE, 0);
|
||||
itemDay.set(Calendar.SECOND, 0);
|
||||
itemDay.set(Calendar.MILLISECOND, 0);
|
||||
|
||||
Date todayFrom = itemDay.getTime();
|
||||
|
||||
itemDay.set(Calendar.HOUR_OF_DAY, 23);
|
||||
itemDay.set(Calendar.MINUTE, 59);
|
||||
itemDay.set(Calendar.SECOND, 59);
|
||||
itemDay.set(Calendar.MILLISECOND, 999);
|
||||
|
||||
Date todayTo = itemDay.getTime();
|
||||
|
||||
// refresh log-report every minute
|
||||
XxlJobLogReport xxlJobLogReport = new XxlJobLogReport();
|
||||
xxlJobLogReport.setTriggerDay(todayFrom);
|
||||
xxlJobLogReport.setRunningCount(0);
|
||||
xxlJobLogReport.setSucCount(0);
|
||||
xxlJobLogReport.setFailCount(0);
|
||||
|
||||
Map<String, Object> triggerCountMap = XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().findLogReport(todayFrom, todayTo);
|
||||
if (triggerCountMap != null && triggerCountMap.size() > 0) {
|
||||
int triggerDayCount = triggerCountMap.containsKey("triggerDayCount") ? Integer.valueOf(String.valueOf(triggerCountMap.get("triggerDayCount"))) : 0;
|
||||
int triggerDayCountRunning = triggerCountMap.containsKey("triggerDayCountRunning") ? Integer.valueOf(String.valueOf(triggerCountMap.get("triggerDayCountRunning"))) : 0;
|
||||
int triggerDayCountSuc = triggerCountMap.containsKey("triggerDayCountSuc") ? Integer.valueOf(String.valueOf(triggerCountMap.get("triggerDayCountSuc"))) : 0;
|
||||
int triggerDayCountFail = triggerDayCount - triggerDayCountRunning - triggerDayCountSuc;
|
||||
|
||||
xxlJobLogReport.setRunningCount(triggerDayCountRunning);
|
||||
xxlJobLogReport.setSucCount(triggerDayCountSuc);
|
||||
xxlJobLogReport.setFailCount(triggerDayCountFail);
|
||||
}
|
||||
|
||||
// do refresh
|
||||
int ret = XxlJobAdminConfig.getAdminConfig().getXxlJobLogReportDao().update(xxlJobLogReport);
|
||||
if (ret < 1) {
|
||||
XxlJobAdminConfig.getAdminConfig().getXxlJobLogReportDao().save(xxlJobLogReport);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void toStop() {
|
||||
toStop = true;
|
||||
// interrupt and wait
|
||||
logrThread.interrupt();
|
||||
try {
|
||||
logrThread.join();
|
||||
} catch (InterruptedException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
package com.xxl.job.admin.core.thread;
|
||||
|
||||
import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
|
||||
import com.xxl.job.admin.core.model.XxlJobLog;
|
||||
import com.xxl.job.admin.core.util.I18nUtil;
|
||||
import com.xxl.job.core.biz.model.ReturnT;
|
||||
import com.xxl.job.core.util.DateUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* job lose-monitor instance
|
||||
*
|
||||
* @author xuxueli 2015-9-1 18:05:56
|
||||
*/
|
||||
@Slf4j
|
||||
public class JobLosedMonitorHelper {
|
||||
|
||||
private static JobLosedMonitorHelper instance = new JobLosedMonitorHelper();
|
||||
|
||||
public static JobLosedMonitorHelper getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
// ---------------------- monitor ----------------------
|
||||
|
||||
private Thread monitorThread;
|
||||
private volatile boolean toStop = false;
|
||||
|
||||
public void start() {
|
||||
monitorThread = new Thread(() -> {
|
||||
// monitor
|
||||
getWhile();
|
||||
log.info(">>>>>>>>>>> xxl-job, JobLosedMonitorHelper stop");
|
||||
});
|
||||
monitorThread.setDaemon(true);
|
||||
monitorThread.setName("xxl-job, admin JobLosedMonitorHelper");
|
||||
monitorThread.start();
|
||||
}
|
||||
|
||||
private void getWhile() {
|
||||
while (!toStop) {
|
||||
try {
|
||||
// 任务结果丢失处理:调度记录停留在 "运行中" 状态超过10min,且对应执行器心跳注册失败不在线,则将本地调度主动标记失败;
|
||||
Date losedTime = DateUtil.addMinutes(new Date(), -10);
|
||||
List<Long> losedJobIds = XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().findLostJobIds(losedTime);
|
||||
|
||||
if (losedJobIds != null && !losedJobIds.isEmpty()) {
|
||||
getFor(losedJobIds);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (!toStop) {
|
||||
log.error(">>>>>>>>>>> xxl-job, job fail monitor thread error:{}", e);
|
||||
}
|
||||
}
|
||||
try {
|
||||
TimeUnit.SECONDS.sleep(60);
|
||||
} catch (Exception e) {
|
||||
if (!toStop) {
|
||||
log.error(e.getMessage(), e);
|
||||
}
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void getFor(List<Long> losedJobIds) {
|
||||
for (Long logId : losedJobIds) {
|
||||
|
||||
XxlJobLog jobLog = new XxlJobLog();
|
||||
jobLog.setId(logId);
|
||||
|
||||
jobLog.setHandleTime(new Date());
|
||||
jobLog.setHandleCode(ReturnT.FAIL_CODE);
|
||||
jobLog.setHandleMsg(I18nUtil.getString("joblog_lost_fail"));
|
||||
|
||||
XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().updateHandleInfo(jobLog);
|
||||
}
|
||||
}
|
||||
|
||||
public void toStop() {
|
||||
toStop = true;
|
||||
// interrupt and wait
|
||||
monitorThread.interrupt();
|
||||
try {
|
||||
monitorThread.join();
|
||||
} catch (InterruptedException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
package com.xxl.job.admin.core.thread;
|
||||
|
||||
import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
|
||||
import com.xxl.job.admin.core.model.XxlJobGroup;
|
||||
import com.xxl.job.admin.core.model.XxlJobRegistry;
|
||||
import com.xxl.job.core.enums.RegistryConfig;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* job registry instance
|
||||
*
|
||||
* @author xuxueli 2016-10-02 19:10:24
|
||||
*/
|
||||
@Slf4j
|
||||
public class JobRegistryMonitorHelper {
|
||||
|
||||
private static JobRegistryMonitorHelper instance = new JobRegistryMonitorHelper();
|
||||
|
||||
public static JobRegistryMonitorHelper getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
private Thread registryThread;
|
||||
private volatile boolean toStop = false;
|
||||
|
||||
public void start() {
|
||||
registryThread = new Thread(() -> {
|
||||
getWhile();
|
||||
log.info(">>>>>>>>>>> xxl-job, job registry monitor thread stop");
|
||||
});
|
||||
registryThread.setDaemon(true);
|
||||
registryThread.setName("xxl-job, admin JobRegistryMonitorHelper");
|
||||
registryThread.start();
|
||||
}
|
||||
|
||||
private void getWhile() {
|
||||
while (!toStop) {
|
||||
try {
|
||||
// auto registry group
|
||||
List<XxlJobGroup> groupList = XxlJobAdminConfig.getAdminConfig().getXxlJobGroupDao().findByAddressType(0);
|
||||
getGroupList(groupList);
|
||||
} catch (Exception e) {
|
||||
if (!toStop) {
|
||||
log.error(">>>>>>>>>>> xxl-job, job registry monitor thread error:{}", e);
|
||||
}
|
||||
}
|
||||
try {
|
||||
TimeUnit.SECONDS.sleep(RegistryConfig.BEAT_TIMEOUT);
|
||||
} catch (InterruptedException e) {
|
||||
if (!toStop) {
|
||||
log.error(">>>>>>>>>>> xxl-job, job registry monitor thread error:{}", e);
|
||||
}
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void getGroupList(List<XxlJobGroup> groupList) {
|
||||
if (groupList != null && !groupList.isEmpty()) {
|
||||
|
||||
// remove dead address (admin/executor)
|
||||
List<Integer> ids = XxlJobAdminConfig.getAdminConfig().getXxlJobRegistryDao().findDead(RegistryConfig.DEAD_TIMEOUT, new Date());
|
||||
if (ids != null && !ids.isEmpty()) {
|
||||
XxlJobAdminConfig.getAdminConfig().getXxlJobRegistryDao().removeDead(ids);
|
||||
}
|
||||
|
||||
// fresh online address (admin/executor)
|
||||
HashMap<String, List<String>> appAddressMap = new HashMap<>();
|
||||
List<XxlJobRegistry> list = XxlJobAdminConfig.getAdminConfig().getXxlJobRegistryDao().findAll(RegistryConfig.DEAD_TIMEOUT, new Date());
|
||||
if (list != null) {
|
||||
getFor(appAddressMap, list);
|
||||
}
|
||||
|
||||
// fresh group address
|
||||
getFreshGroupAddress(groupList, appAddressMap);
|
||||
}
|
||||
}
|
||||
|
||||
private void getFreshGroupAddress(List<XxlJobGroup> groupList, HashMap<String, List<String>> appAddressMap) {
|
||||
for (XxlJobGroup group : groupList) {
|
||||
List<String> registryList = appAddressMap.get(group.getAppname());
|
||||
StringBuilder addressListStr = new StringBuilder();
|
||||
if (registryList != null && !registryList.isEmpty()) {
|
||||
Collections.sort(registryList);
|
||||
for (String item : registryList) {
|
||||
addressListStr.append(item).append(",");
|
||||
}
|
||||
addressListStr = new StringBuilder(addressListStr.substring(0, addressListStr.length() - 1));
|
||||
}
|
||||
group.setAddressList(addressListStr.toString());
|
||||
XxlJobAdminConfig.getAdminConfig().getXxlJobGroupDao().update(group);
|
||||
}
|
||||
}
|
||||
|
||||
private void getFor(HashMap<String, List<String>> appAddressMap, List<XxlJobRegistry> list) {
|
||||
for (XxlJobRegistry item : list) {
|
||||
if (RegistryConfig.RegistType.EXECUTOR.name().equals(item.getRegistryGroup())) {
|
||||
String appname = item.getRegistryKey();
|
||||
List<String> registryList = appAddressMap.get(appname);
|
||||
if (registryList == null) {
|
||||
registryList = new ArrayList<>();
|
||||
}
|
||||
|
||||
if (!registryList.contains(item.getRegistryValue())) {
|
||||
registryList.add(item.getRegistryValue());
|
||||
}
|
||||
appAddressMap.put(appname, registryList);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void toStop() {
|
||||
toStop = true;
|
||||
// interrupt and wait
|
||||
registryThread.interrupt();
|
||||
try {
|
||||
registryThread.join();
|
||||
} catch (InterruptedException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+378
@@ -0,0 +1,378 @@
|
||||
package com.xxl.job.admin.core.thread;
|
||||
|
||||
import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
|
||||
import com.xxl.job.admin.core.cron.CronExpression;
|
||||
import com.xxl.job.admin.core.model.XxlJobInfo;
|
||||
import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
import java.text.ParseException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* @author xuxueli 2019-05-21
|
||||
*/
|
||||
@Slf4j
|
||||
public class JobScheduleHelper {
|
||||
|
||||
private static JobScheduleHelper instance = new JobScheduleHelper();
|
||||
|
||||
public static JobScheduleHelper getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
public static final long PRE_READ_MS = 5000; // pre read
|
||||
|
||||
private Thread scheduleThread;
|
||||
private Thread ringThread;
|
||||
private volatile boolean scheduleThreadToStop = false;
|
||||
private volatile boolean ringThreadToStop = false;
|
||||
private static Map<Integer, List<Integer>> ringData = new ConcurrentHashMap<>();
|
||||
|
||||
public void start() {
|
||||
|
||||
// schedule thread
|
||||
scheduleThread = new Thread(() -> {
|
||||
getThread();
|
||||
log.info(">>>>>>>>>>> xxl-job, JobScheduleHelper#scheduleThread stop");
|
||||
});
|
||||
scheduleThread.setDaemon(true);
|
||||
scheduleThread.setName("xxl-job, admin JobScheduleHelper#scheduleThread");
|
||||
scheduleThread.start();
|
||||
|
||||
|
||||
// ring thread
|
||||
ringThread = new Thread(() -> {
|
||||
// align second
|
||||
getThread1();
|
||||
log.info(">>>>>>>>>>> xxl-job, JobScheduleHelper#ringThread stop");
|
||||
});
|
||||
ringThread.setDaemon(true);
|
||||
ringThread.setName("xxl-job, admin JobScheduleHelper#ringThread");
|
||||
ringThread.start();
|
||||
}
|
||||
|
||||
private void getThread1() {
|
||||
try {
|
||||
TimeUnit.MILLISECONDS.sleep(1000 - System.currentTimeMillis() % 1000);
|
||||
} catch (InterruptedException e) {
|
||||
if (!ringThreadToStop) {
|
||||
log.error(e.getMessage(), e);
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
while (!ringThreadToStop) {
|
||||
|
||||
try {
|
||||
// second data
|
||||
getSecondData();
|
||||
} catch (Exception e) {
|
||||
if (!ringThreadToStop) {
|
||||
log.error(">>>>>>>>>>> xxl-job, JobScheduleHelper#ringThread error:{}", e);
|
||||
}
|
||||
}
|
||||
|
||||
// next second, align second
|
||||
try {
|
||||
TimeUnit.MILLISECONDS.sleep(1000 - System.currentTimeMillis() % 1000);
|
||||
} catch (InterruptedException e) {
|
||||
if (!ringThreadToStop) {
|
||||
log.error(e.getMessage(), e);
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void getSecondData() {
|
||||
List<Integer> ringItemData = new ArrayList<>();
|
||||
int nowSecond = Calendar.getInstance().get(Calendar.SECOND); // 避免处理耗时太长,跨过刻度,向前校验一个刻度;
|
||||
for (int i = 0; i < 2; i++) {
|
||||
List<Integer> tmpData = ringData.remove((nowSecond + 60 - i) % 60);
|
||||
if (tmpData != null) {
|
||||
ringItemData.addAll(tmpData);
|
||||
}
|
||||
}
|
||||
|
||||
// ring trigger
|
||||
log.debug(">>>>>>>>>>> xxl-job, time-ring beat : " + nowSecond + " = " + Arrays.asList(ringItemData));
|
||||
if (!ringItemData.isEmpty()) {
|
||||
// do trigger
|
||||
for (int jobId : ringItemData) {
|
||||
// do trigger
|
||||
JobTriggerPoolHelper.trigger(jobId, TriggerTypeEnum.CRON, -1, null, null, null);
|
||||
}
|
||||
// clear
|
||||
ringItemData.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private void getThread() {
|
||||
try {
|
||||
TimeUnit.MILLISECONDS.sleep(5000 - System.currentTimeMillis() % 1000);
|
||||
} catch (InterruptedException e) {
|
||||
if (!scheduleThreadToStop) {
|
||||
log.error(e.getMessage(), e);
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
log.info(">>>>>>>>> init xxl-job admin scheduler success.");
|
||||
// pre-read count: treadpool-size * trigger-qps (each trigger cost 50ms, qps = 1000/50 = 20)
|
||||
int preReadCount = (XxlJobAdminConfig.getAdminConfig().getTriggerPoolFastMax() + XxlJobAdminConfig.getAdminConfig().getTriggerPoolSlowMax()) * 20;
|
||||
|
||||
while (!scheduleThreadToStop) {
|
||||
// Scan Job
|
||||
getWhile(preReadCount);
|
||||
}
|
||||
}
|
||||
|
||||
private void getWhile(int preReadCount) {
|
||||
long start = System.currentTimeMillis();
|
||||
Connection conn = null;
|
||||
Boolean connAutoCommit = null;
|
||||
PreparedStatement preparedStatement = null;
|
||||
|
||||
boolean preReadSuc = true;
|
||||
try {
|
||||
|
||||
conn = XxlJobAdminConfig.getAdminConfig().getDataSource().getConnection();
|
||||
connAutoCommit = conn.getAutoCommit();
|
||||
conn.setAutoCommit(false);
|
||||
|
||||
preparedStatement = conn.prepareStatement("select * from xxl_job_lock where lock_name = 'schedule_lock' for update");
|
||||
preparedStatement.execute();
|
||||
|
||||
// tx start
|
||||
|
||||
// 1、pre read
|
||||
long nowTime = System.currentTimeMillis();
|
||||
List<XxlJobInfo> scheduleList = XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().scheduleJobQuery(nowTime + PRE_READ_MS, preReadCount);
|
||||
if (scheduleList != null && !scheduleList.isEmpty()) {
|
||||
// 2、push time-ring
|
||||
getFor(nowTime, scheduleList);
|
||||
|
||||
// 3、update trigger info
|
||||
for (XxlJobInfo jobInfo : scheduleList) {
|
||||
XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().scheduleUpdate(jobInfo);
|
||||
}
|
||||
|
||||
} else {
|
||||
preReadSuc = false;
|
||||
}
|
||||
|
||||
// tx stop
|
||||
|
||||
|
||||
} catch (Exception e) {
|
||||
if (!scheduleThreadToStop) {
|
||||
log.error(">>>>>>>>>>> xxl-job, JobScheduleHelper#scheduleThread error:{}", e);
|
||||
}
|
||||
} finally {
|
||||
|
||||
// commit
|
||||
getFinally(conn, connAutoCommit, preparedStatement);
|
||||
}
|
||||
long cost = System.currentTimeMillis() - start;
|
||||
|
||||
|
||||
// Wait seconds, align second
|
||||
getCost(preReadSuc, cost);
|
||||
}
|
||||
|
||||
private void getCost(boolean preReadSuc, long cost) {
|
||||
if (cost < 1000) { // scan-overtime, not wait
|
||||
try {
|
||||
TimeUnit.MILLISECONDS.sleep((preReadSuc ? 1000 : PRE_READ_MS) - System.currentTimeMillis() % 1000);
|
||||
} catch (InterruptedException e) {
|
||||
if (!scheduleThreadToStop) {
|
||||
log.error(e.getMessage(), e);
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void getFinally(Connection conn, Boolean connAutoCommit, PreparedStatement preparedStatement) {
|
||||
if (conn != null) {
|
||||
try {
|
||||
conn.commit();
|
||||
} catch (SQLException e) {
|
||||
getScheduleThreadToStop(e);
|
||||
}
|
||||
try {
|
||||
conn.setAutoCommit(connAutoCommit);
|
||||
} catch (SQLException e) {
|
||||
getScheduleThreadToStop(e);
|
||||
}
|
||||
try {
|
||||
conn.close();
|
||||
} catch (SQLException e) {
|
||||
getScheduleThreadToStop(e);
|
||||
}
|
||||
}
|
||||
|
||||
// close PreparedStatement
|
||||
if (null != preparedStatement) {
|
||||
try {
|
||||
preparedStatement.close();
|
||||
} catch (SQLException e) {
|
||||
getScheduleThreadToStop(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void getScheduleThreadToStop(SQLException e) {
|
||||
if (!scheduleThreadToStop) {
|
||||
log.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private void getFor(long nowTime, List<XxlJobInfo> scheduleList) throws ParseException {
|
||||
for (XxlJobInfo jobInfo : scheduleList) {
|
||||
|
||||
// time-ring jump
|
||||
if (nowTime > jobInfo.getTriggerNextTime() + PRE_READ_MS) {
|
||||
// 2.1、trigger-expire > 5s:pass && make next-trigger-time
|
||||
log.warn(">>>>>>>>>>> xxl-job, schedule misfire, jobId = " + jobInfo.getId());
|
||||
|
||||
// fresh next
|
||||
refreshNextValidTime(jobInfo, new Date());
|
||||
|
||||
} else if (nowTime > jobInfo.getTriggerNextTime()) {
|
||||
// 2.2、trigger-expire < 5s:direct-trigger && make next-trigger-time
|
||||
|
||||
// 1、trigger
|
||||
JobTriggerPoolHelper.trigger(jobInfo.getId(), TriggerTypeEnum.CRON, -1, null, null, null);
|
||||
log.debug(">>>>>>>>>>> xxl-job, schedule push trigger : jobId = {}", jobInfo.getId());
|
||||
|
||||
// 2、fresh next
|
||||
refreshNextValidTime(jobInfo, new Date());
|
||||
|
||||
// next-trigger-time in 5s, pre-read again
|
||||
if (jobInfo.getTriggerStatus() == 1 && nowTime + PRE_READ_MS > jobInfo.getTriggerNextTime()) {
|
||||
|
||||
// 1、make ring second
|
||||
int ringSecond = (int) ((jobInfo.getTriggerNextTime() / 1000) % 60);
|
||||
|
||||
// 2、push time ring
|
||||
pushTimeRing(ringSecond, jobInfo.getId());
|
||||
|
||||
// 3、fresh next
|
||||
refreshNextValidTime(jobInfo, new Date(jobInfo.getTriggerNextTime()));
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
// 2.3、trigger-pre-read:time-ring trigger && make next-trigger-time
|
||||
|
||||
// 1、make ring second
|
||||
int ringSecond = (int) ((jobInfo.getTriggerNextTime() / 1000) % 60);
|
||||
|
||||
// 2、push time ring
|
||||
pushTimeRing(ringSecond, jobInfo.getId());
|
||||
|
||||
// 3、fresh next
|
||||
refreshNextValidTime(jobInfo, new Date(jobInfo.getTriggerNextTime()));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void refreshNextValidTime(XxlJobInfo jobInfo, Date fromTime) throws ParseException {
|
||||
Date nextValidTime = new CronExpression(jobInfo.getJobCron()).getNextValidTimeAfter(fromTime);
|
||||
if (nextValidTime != null) {
|
||||
jobInfo.setTriggerLastTime(jobInfo.getTriggerNextTime());
|
||||
jobInfo.setTriggerNextTime(nextValidTime.getTime());
|
||||
} else {
|
||||
jobInfo.setTriggerStatus(0);
|
||||
jobInfo.setTriggerLastTime(0);
|
||||
jobInfo.setTriggerNextTime(0);
|
||||
}
|
||||
}
|
||||
|
||||
private void pushTimeRing(int ringSecond, int jobId) {
|
||||
// push async ring
|
||||
List<Integer> ringItemData = ringData.computeIfAbsent(ringSecond, k -> new ArrayList<>());
|
||||
ringItemData.add(jobId);
|
||||
|
||||
log.debug(">>>>>>>>>>> xxl-job, schedule push time-ring : " + ringSecond + " = " + Arrays.asList(ringItemData));
|
||||
}
|
||||
|
||||
public void toStop() {
|
||||
|
||||
// 1、stop schedule
|
||||
scheduleThreadToStop = true;
|
||||
try {
|
||||
TimeUnit.SECONDS.sleep(1); // wait
|
||||
} catch (InterruptedException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
if (scheduleThread.getState() != Thread.State.TERMINATED) {
|
||||
// interrupt and wait
|
||||
scheduleThread.interrupt();
|
||||
try {
|
||||
scheduleThread.join();
|
||||
} catch (InterruptedException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
// if has ring data
|
||||
boolean hasRingData = isHasRingData();
|
||||
if (hasRingData) {
|
||||
try {
|
||||
TimeUnit.SECONDS.sleep(8);
|
||||
} catch (InterruptedException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
// stop ring (wait job-in-memory stop)
|
||||
ringThreadToStop = true;
|
||||
try {
|
||||
TimeUnit.SECONDS.sleep(1);
|
||||
} catch (InterruptedException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
if (ringThread.getState() != Thread.State.TERMINATED) {
|
||||
// interrupt and wait
|
||||
ringThread.interrupt();
|
||||
try {
|
||||
ringThread.join();
|
||||
} catch (InterruptedException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
log.info(">>>>>>>>>>> xxl-job, JobScheduleHelper stop");
|
||||
}
|
||||
|
||||
private boolean isHasRingData() {
|
||||
boolean hasRingData = false;
|
||||
if (!ringData.isEmpty()) {
|
||||
Set<Map.Entry<Integer, List<Integer>>> entries = ringData.entrySet();
|
||||
for (Map.Entry<Integer, List<Integer>> e : entries) {
|
||||
List<Integer> tmpData = e.getValue();
|
||||
if (tmpData != null && !tmpData.isEmpty()) {
|
||||
hasRingData = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return hasRingData;
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user