Commit b034099c by 赵天增

啦啦啦

parent c2ccd6dd
...@@ -15,3 +15,32 @@ management: ...@@ -15,3 +15,32 @@ management:
endpoint: endpoint:
health: health:
show-details: ALWAYS show-details: ALWAYS
# 微服务列表,如果服务不存在会触发邮件提醒
spring:
boot:
admin:
notify:
mail:
enabled: true
to: tianzeng.zhao@medtap.cn
from: tianzeng.zhao@medtap.cn
services:
YJY-APPLICATION-USER,
YJY-APPLICATION-COMMON,
YJY-APPLICATION-WECHAT,
YJY-APPLICATION-PAYMENT,
YJY-APPLICATION-SERVICE,
YJY-APPLICATION-THIRD,
YJY-APPLICATION-DOCTOR,
YJY-APPLICATION-OPERATION,
YJY-APPLICATION-TRADE,
YJY-APPLICATION-SHOP,
YJY-APPLICATION-CRM,
OPERATION-API,
USER-API,
PROMOTION-API,
RESOURCE-API,
ITEM-API,
BABABABBABA
...@@ -21,6 +21,7 @@ import de.codecentric.boot.admin.server.domain.entities.SnapshottingInstanceRepo ...@@ -21,6 +21,7 @@ import de.codecentric.boot.admin.server.domain.entities.SnapshottingInstanceRepo
import de.codecentric.boot.admin.server.domain.events.InstanceEvent; import de.codecentric.boot.admin.server.domain.events.InstanceEvent;
import de.codecentric.boot.admin.server.eventstore.InMemoryEventStore; import de.codecentric.boot.admin.server.eventstore.InMemoryEventStore;
import de.codecentric.boot.admin.server.eventstore.InstanceEventStore; import de.codecentric.boot.admin.server.eventstore.InstanceEventStore;
import de.codecentric.boot.admin.server.notify.ServicesList;
import de.codecentric.boot.admin.server.services.EndpointDetectionTrigger; import de.codecentric.boot.admin.server.services.EndpointDetectionTrigger;
import de.codecentric.boot.admin.server.services.EndpointDetector; import de.codecentric.boot.admin.server.services.EndpointDetector;
import de.codecentric.boot.admin.server.services.HashingInstanceUrlIdGenerator; import de.codecentric.boot.admin.server.services.HashingInstanceUrlIdGenerator;
...@@ -74,6 +75,10 @@ public class AdminServerAutoConfiguration { ...@@ -74,6 +75,10 @@ public class AdminServerAutoConfiguration {
} }
@Bean @Bean
public ServicesList servicesList(){
return new ServicesList();
}
@Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
public InstanceIdGenerator instanceIdGenerator() { public InstanceIdGenerator instanceIdGenerator() {
return new HashingInstanceUrlIdGenerator(); return new HashingInstanceUrlIdGenerator();
......
package de.codecentric.boot.admin.server.notify;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import javax.validation.constraints.Max;
import java.util.List;
@Component
public class ServicesList {
@Autowired
private Environment env;
public List<String> getServices() {
return env.getProperty("spring.boot.admin.services",List.class);
}
}
...@@ -20,20 +20,11 @@ import de.codecentric.boot.admin.server.domain.entities.Instance; ...@@ -20,20 +20,11 @@ import de.codecentric.boot.admin.server.domain.entities.Instance;
import de.codecentric.boot.admin.server.domain.values.BuildVersion; import de.codecentric.boot.admin.server.domain.values.BuildVersion;
import de.codecentric.boot.admin.server.domain.values.StatusInfo; import de.codecentric.boot.admin.server.domain.values.StatusInfo;
import de.codecentric.boot.admin.server.eventstore.InstanceEventPublisher; import de.codecentric.boot.admin.server.eventstore.InstanceEventPublisher;
import de.codecentric.boot.admin.server.notify.ServicesList;
import de.codecentric.boot.admin.server.services.InstanceRegistry; import de.codecentric.boot.admin.server.services.InstanceRegistry;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.function.Tuple2;
import reactor.util.function.Tuples;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.http.codec.ServerSentEvent; import org.springframework.http.codec.ServerSentEvent;
...@@ -41,6 +32,14 @@ import org.springframework.web.bind.annotation.DeleteMapping; ...@@ -41,6 +32,14 @@ import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.function.Tuple2;
import reactor.util.function.Tuples;
import java.time.Duration;
import java.time.Instant;
import java.util.*;
import static java.util.Comparator.naturalOrder; import static java.util.Comparator.naturalOrder;
import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toList;
...@@ -55,7 +54,7 @@ public class ApplicationsController { ...@@ -55,7 +54,7 @@ public class ApplicationsController {
private static final Logger log = LoggerFactory.getLogger(ApplicationsController.class); private static final Logger log = LoggerFactory.getLogger(ApplicationsController.class);
private static final ServerSentEvent<?> PING = ServerSentEvent.builder().comment("ping").build(); private static final ServerSentEvent<?> PING = ServerSentEvent.builder().comment("ping").build();
private static final Flux<ServerSentEvent<?>> PING_FLUX = Flux.interval(Duration.ZERO, Duration.ofSeconds(10L)) private static final Flux<ServerSentEvent<?>> PING_FLUX = Flux.interval(Duration.ZERO, Duration.ofSeconds(10L))
.map(tick -> PING); .map(tick -> PING);
private final InstanceRegistry registry; private final InstanceRegistry registry;
private final InstanceEventPublisher eventPublisher; private final InstanceEventPublisher eventPublisher;
...@@ -64,6 +63,10 @@ public class ApplicationsController { ...@@ -64,6 +63,10 @@ public class ApplicationsController {
this.eventPublisher = eventPublisher; this.eventPublisher = eventPublisher;
} }
@Autowired
private ServicesList servicesList;
@GetMapping(path = "/applications", produces = MediaType.APPLICATION_JSON_VALUE) @GetMapping(path = "/applications", produces = MediaType.APPLICATION_JSON_VALUE)
public Flux<Application> applications() { public Flux<Application> applications() {
Flux<Application> applicationFlux = registry.getInstances() Flux<Application> applicationFlux = registry.getInstances()
...@@ -71,29 +74,64 @@ public class ApplicationsController { ...@@ -71,29 +74,64 @@ public class ApplicationsController {
.groupBy(instance -> instance.getRegistration().getName()) .groupBy(instance -> instance.getRegistration().getName())
.flatMap(grouped -> toApplication(grouped.key(), grouped)); .flatMap(grouped -> toApplication(grouped.key(), grouped));
List<String> appNames = new ArrayList<>();
applicationFlux.subscribe(application -> appNames.add(application.getName()));
for (String appName : minus(servicesList.getServices(),appNames)) {
Application group = new Application(appName.toUpperCase());
group.setStatus("OFFINE");
group.setInstances(Collections.EMPTY_LIST);
applicationFlux = applicationFlux.concatWithValues(group);
}
return applicationFlux; return applicationFlux;
} }
/**
* 求两数组的差集
*
* @param arr1 模板数组
* @param arr2 比较数组
* @return
*/
public static String[] minus(List<String> arr1, List<String> arr2) {
LinkedList<String> list = new LinkedList<>();
LinkedList<String> history = new LinkedList<>();
for (String str : arr1) {
if (!list.contains(str.toUpperCase())) {
list.add(str.toUpperCase());
}
}
for (String str : arr2) {
list.remove(str.toUpperCase());
}
String[] result = {};
return list.toArray(result);
}
@GetMapping(path = "/applications", produces = MediaType.TEXT_EVENT_STREAM_VALUE) @GetMapping(path = "/applications", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<ServerSentEvent<Application>> applicationsStream() { public Flux<ServerSentEvent<Application>> applicationsStream() {
return Flux.from(eventPublisher) return Flux.from(eventPublisher)
.flatMap(event -> registry.getInstance(event.getInstance())) .flatMap(event -> registry.getInstance(event.getInstance()))
.map(this::getApplicationForInstance) .map(this::getApplicationForInstance)
.flatMap(group -> toApplication(group.getT1(), group.getT2())) .flatMap(group -> toApplication(group.getT1(), group.getT2()))
.map(application -> ServerSentEvent.builder(application).build()) .map(application -> ServerSentEvent.builder(application).build())
.mergeWith(ping()); .mergeWith(ping());
} }
@DeleteMapping(path = "/applications/{name}") @DeleteMapping(path = "/applications/{name}")
public Mono<ResponseEntity<Void>> unregister(@PathVariable("name") String name) { public Mono<ResponseEntity<Void>> unregister(@PathVariable("name") String name) {
log.debug("Unregister application with name '{}'", name); log.debug("Unregister application with name '{}'", name);
return registry.getInstances(name) return registry.getInstances(name)
.flatMap(instance -> registry.deregister(instance.getId())) .flatMap(instance -> registry.deregister(instance.getId()))
.collectList() .collectList()
.map( .map(
deregistered -> !deregistered.isEmpty() ? ResponseEntity.noContent().build() : ResponseEntity deregistered -> !deregistered.isEmpty() ? ResponseEntity.noContent().build() : ResponseEntity
.notFound() .notFound()
.build()); .build());
} }
protected Tuple2<String, Flux<Instance>> getApplicationForInstance(Instance instance) { protected Tuple2<String, Flux<Instance>> getApplicationForInstance(Instance instance) {
...@@ -107,8 +145,7 @@ public class ApplicationsController { ...@@ -107,8 +145,7 @@ public class ApplicationsController {
group.setInstances(instanceList); group.setInstances(instanceList);
group.setBuildVersion(getBuildVersion(instanceList)); group.setBuildVersion(getBuildVersion(instanceList));
Tuple2<String, Instant> status = getStatus(instanceList); Tuple2<String, Instant> status = getStatus(instanceList);
// group.setStatus(status.getT1()); group.setStatus(status.getT1());
group.setStatus("UP");
group.setStatusTimestamp(status.getT2()); group.setStatusTimestamp(status.getT2());
return group; return group;
}); });
...@@ -116,11 +153,11 @@ public class ApplicationsController { ...@@ -116,11 +153,11 @@ public class ApplicationsController {
protected BuildVersion getBuildVersion(List<Instance> instances) { protected BuildVersion getBuildVersion(List<Instance> instances) {
List<BuildVersion> versions = instances.stream() List<BuildVersion> versions = instances.stream()
.map(Instance::getBuildVersion) .map(Instance::getBuildVersion)
.filter(Objects::nonNull) .filter(Objects::nonNull)
.distinct() .distinct()
.sorted() .sorted()
.collect(toList()); .collect(toList());
if (versions.isEmpty()) { if (versions.isEmpty()) {
return null; return null;
} else if (versions.size() == 1) { } else if (versions.size() == 1) {
...@@ -133,8 +170,8 @@ public class ApplicationsController { ...@@ -133,8 +170,8 @@ public class ApplicationsController {
protected Tuple2<String, Instant> getStatus(List<Instance> instances) { protected Tuple2<String, Instant> getStatus(List<Instance> instances) {
//TODO: Correct is just a second readmodel for groups //TODO: Correct is just a second readmodel for groups
Map<String, Instant> statusWithTime = instances.stream() Map<String, Instant> statusWithTime = instances.stream()
.collect(toMap(instance -> instance.getStatusInfo().getStatus(), .collect(toMap(instance -> instance.getStatusInfo().getStatus(),
Instance::getStatusTimestamp, this::getMax)); Instance::getStatusTimestamp, this::getMax));
if (statusWithTime.size() == 1) { if (statusWithTime.size() == 1) {
Map.Entry<String, Instant> e = statusWithTime.entrySet().iterator().next(); Map.Entry<String, Instant> e = statusWithTime.entrySet().iterator().next();
return Tuples.of(e.getKey(), e.getValue()); return Tuples.of(e.getKey(), e.getValue());
...@@ -142,20 +179,20 @@ public class ApplicationsController { ...@@ -142,20 +179,20 @@ public class ApplicationsController {
if (statusWithTime.containsKey(StatusInfo.STATUS_UP)) { if (statusWithTime.containsKey(StatusInfo.STATUS_UP)) {
Instant oldestNonUp = statusWithTime.entrySet() Instant oldestNonUp = statusWithTime.entrySet()
.stream() .stream()
.filter(e -> !StatusInfo.STATUS_UP.equals(e.getKey())) .filter(e -> !StatusInfo.STATUS_UP.equals(e.getKey()))
.map(Map.Entry::getValue) .map(Map.Entry::getValue)
.min(naturalOrder()) .min(naturalOrder())
.orElse(Instant.EPOCH); .orElse(Instant.EPOCH);
Instant latest = getMax(oldestNonUp, statusWithTime.getOrDefault(StatusInfo.STATUS_UP, Instant.EPOCH)); Instant latest = getMax(oldestNonUp, statusWithTime.getOrDefault(StatusInfo.STATUS_UP, Instant.EPOCH));
return Tuples.of(StatusInfo.STATUS_RESTRICTED, latest); return Tuples.of(StatusInfo.STATUS_RESTRICTED, latest);
} }
return statusWithTime.entrySet() return statusWithTime.entrySet()
.stream() .stream()
.min(Map.Entry.comparingByKey(StatusInfo.severity())) .min(Map.Entry.comparingByKey(StatusInfo.severity()))
.map(e -> Tuples.of(e.getKey(), e.getValue())) .map(e -> Tuples.of(e.getKey(), e.getValue()))
.orElse(Tuples.of(StatusInfo.STATUS_UNKNOWN, Instant.EPOCH)); .orElse(Tuples.of(StatusInfo.STATUS_UNKNOWN, Instant.EPOCH));
} }
private Instant getMax(Instant t1, Instant t2) { private Instant getMax(Instant t1, Instant t2) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment