======== 创建SpringBoot Admin服务端
1、POM依赖
<?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>
<!-- 服务监控管理 -->
<packaging>jar</packaging>
<groupId>com.za.edu</groupId>
<version>0.0.1-SNAPSHOT</version>
<name>zaedu-server-manager</name>
<artifactId>zaedu-server-manager</artifactId>
<!-- SpringBoot版本 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.3.RELEASE</version>
<relativePath/>
</parent>
<dependencies>
<!-- =========================== Nacos =========================== -->
<!-- Nacos服务发现检测 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2.2.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>2.2.3.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-spring-context</artifactId>
<version>0.3.4</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-openfeign-core</artifactId>
<version>2.2.3.RELEASE</version>
</dependency>
<!-- =========================== Admin服务监控管理 =========================== -->
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-server</artifactId>
<version>2.2.4</version>
</dependency>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-server-ui</artifactId>
<version>2.2.4</version>·
</dependency>
<!-- 安全认证 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--Web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- SSH运维工具 -->
<dependency>
<groupId>ch.ethz.ganymed</groupId>
<artifactId>ganymed-ssh2</artifactId>
<version>262</version>
</dependency>
<!-- 集群环境 跨服务请求工具 -->
<dependency>
<groupId>com.netflix.feign</groupId>
<artifactId>feign-jackson</artifactId>
<version>8.18.0</version>
</dependency>
<dependency>
<groupId>com.jcraft</groupId>
<artifactId>jsch</artifactId>
<version>0.1.55</version>
</dependency>
<!-- 日志 引入log4j2依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<!-- 去除log4j2依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions><!-- 去掉springboot默认配置 -->
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--工具类 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.4</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.4</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.aliyun.openservices</groupId>
<artifactId>ons-client</artifactId>
<version>1.8.7.1.Final</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>9.0.39</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.11.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.11.RELEASE</version>
</dependency>
</dependencies>
<profiles>
<!-- 开发环境 -->
<profile>
<id>dev</id>
<properties>
<group>DEV_GROUP</group> <!-- Nacos配置环境分组 -->
<namespace>public</namespace> <!-- 命名空间 -->
<server-addr>XX.XXX.XX.XXX:8848</server-addr> <!-- Nacos IP地址 -->
<activatedProperties>dev</activatedProperties> <!-- 项目环境 -->
</properties>
<activation>
<activeByDefault>false</activeByDefault>
</activation>
</profile>
<!-- 测试环境 -->
<profile>
<id>test</id>
<properties>
<group>TEST_GROUP_DEBUG</group> <!-- Nacos 环境分组 -->
<namespace>public</namespace> <!-- Nacos 命名空间 -->
<server-addr>XX.XXX.XX.XXX:8848</server-addr> <!-- Nacos IP地址 -->
<activatedProperties>test</activatedProperties> <!-- 项目环境 -->
</properties>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
</profile>
<!-- 生产环境 -->
<profile>
<id>prod</id>
<properties>
<group>PROD_GROUP</group> <!-- Nacos 环境分组 -->
<namespace>public</namespace> <!-- Nacos 命名空间 -->
<server-addr>XX.XXX.XX.XXX:8848</server-addr> <!-- Nacos IP地址 -->
<activatedProperties>prod</activatedProperties> <!-- 项目环境 -->
</properties>
<activation>
<activeByDefault>false</activeByDefault>
</activation>
</profile>
</profiles>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<includeSystemScope>true</includeSystemScope>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
2、启动类加上@EnableAdminServer
import de.codecentric.boot.admin.server.config.EnableAdminServer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@EnableAdminServer
@EnableFeignClients
@SpringBootApplication
@EnableDiscoveryClient
public class ServerManagerApplication {
public static void main(String[] args) {
SpringApplication.run(ServerManagerApplication.class, args);
}
}
3、SecuritySecureConfig安全认证
package com.za.edu.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;
@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()
.formLogin().loginPage(adminContextPath + "/login").successHandler(successHandler).and()
.logout().logoutUrl(adminContextPath + "/logout").and()
.httpBasic().and()
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.ignoringAntMatchers(
"/instances",
"/actuator/**");
}
}
4、CorsConfig跨域配置
package com.za.edu.config;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
@Configuration
public class CorsConfig {
@Bean
public FilterRegistrationBean corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("OPTIONS");
config.addAllowedMethod("HEAD");
config.addAllowedMethod("GET");
config.addAllowedMethod("PUT");
config.addAllowedMethod("POST");
config.addAllowedMethod("DELETE");
config.addAllowedMethod("PATCH");
source.registerCorsConfiguration("/**", config);
final FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
bean.setOrder(0);
return bean;
}
}
5、application.yml配置文件
server:
port: 9088
# SpringBoot Admin页面URL前缀
servlet:
context-path: /admin
spring:
application:
name: zaedu-server-manager
#当前服务配置环境
profiles:
active: @activatedProperties@
# SpringBoot Admin security安全认证账号
security:
user:
name: admin
password: admin
#================== 服务注册与发现 ==================
cloud:
nacos:
discovery:
group: @group@
namespace: @namespace@
server-addr: @server-addr@
# Nacos (开启\关闭) 服务发现
# enabled: false
# Nacos (开启\关闭) 自动注册服务
register-enabled: true
#================== SpringBoot Admin配置 ==================
metadata:
management:
context-path: ${server.servlet.context-path}/actuator
# SpringBoot Admin用户账号
user:
name: admin
password: admin
#================== SpringBoot Admin ==================
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: always
#SpringBoot Admin 日志文件目录
logfile:
#例:F:/文件夹/项目目录/trunk/zaedu-cloud/logs/:
external-file: ${user.dir}/logs/${spring.application.name}/info.log
enabled: true
logging:
config: classpath:log4j2.xml
#打印项目业务相关的sql日志
level:
com:
#禁止打印c.a.n.client.config.impl.ClientWorker : get changedGroupKeys:[] 日志
alibaba:
nacos:
client:
config:
impl: WARN
6、log4j2.xml日志配置
<?xml version="1.0" encoding="UTF-8"?>
<!--Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出-->
<!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数-->
<configuration monitorInterval="5">
<!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
<!--从小到大依次是:trace、debug、info、warn、error
由于我们使用的是 slf4j 接口包,该接口包中只提供了未标有删除线的日志级别的输出。(官方注释) -->
<!--变量配置-->
<Properties>
<!-- 格式化输出:%date表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度 %msg:日志消息,%n是换行符-->
<!-- %logger{36} 表示 Logger 名字最长36个字符 -->
<property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %highlight{%-5level}{FATAL=Bright red, ERROR=Bright red, WARN=Bright yellow, INFO=Bright Cyan , DEBUG=Bright blue, TRACE=Bright Magenta } %highlight{[%t]}{FATAL=Bright red, ERROR=Bright yellow, WARN=Bright white, INFO=Bright Magenta, DEBUG=Bright blue, TRACE=Bright Magenta} %logger: %highlight{%msg%n}{FATAL=Bright red, ERROR=Bright red, WARN=Bright yellow, INFO=Bright Cyan , DEBUG=Bright blue, TRACE=Bright Magenta}"/>
<!-- 定义日志存储的路径 -->
<property name="FILE_PATH" value="logs/zaedu-server-manager"/>
<property name="FILE_NAME" value="zaedu-server-manager"/>
</Properties>
<appenders>
<console name="Console" target="${SYSTEM_OUT}">
<!--输出日志的格式-->
<PatternLayout pattern="${LOG_PATTERN}" disableAnsi="false" noConsoleNoAnsi="false"/>
<!--控制台只输出level及其以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
<!--onMatch="ACCEPT" 表示匹配该级别及以上
onMatch="DENY" 表示不匹配该级别及以上
onMatch="NEUTRAL" 表示该级别及以上的,由下一个filter处理,如果当前是最后一个,则表示匹配该级别及以上
onMismatch="ACCEPT" 表示匹配该级别以下
onMismatch="NEUTRAL" 表示该级别及以下的,由下一个filter处理,如果当前是最后一个,则不匹配该级别以下的
onMismatch="DENY" 表示不匹配该级别以下的-->
<ThresholdFilter level="debug" onMatch="ACCEPT"/>
</console>
<!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append(是否追加)属性决定,适合临时测试用-->
<!-- <File name="Filelog" fileName="${FILE_PATH}/test.log" append="false">-->
<!-- <PatternLayout pattern="${LOG_PATTERN}" disableAnsi="false" noConsoleNoAnsi="false"/>-->
<!-- </File>-->
<!-- 这个会打印出所有的info及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
<RollingFile name="RollingFileInfo" fileName="${FILE_PATH}/info.log"
filePattern="${FILE_PATH}/${FILE_NAME}-INFO-%d{yyyy-MM-dd}_%i.log.gz">
<!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="${LOG_PATTERN}" disableAnsi="false" noConsoleNoAnsi="false"/>
<Policies>
<!--
此参数需要与filePattern结合使用,规定了触发rollover的频率,默认值为1。
假设interval为4,若filePattern的date/time pattern的最小时间粒度为小时(如yyyy-MM-dd HH),
则每4小时触发一次rollover;若filePattern的date/time pattern的最小时间粒度为分钟(如yyyy-MM-dd HH-mm),
则每4分钟触发一次rollover。
-->
<TimeBasedTriggeringPolicy interval="1"/>
<!-- 按文件大小触发-->
<SizeBasedTriggeringPolicy size="500MB"/>
</Policies>
<!-- 通过DeleteAction可以删除任何文件,而不仅仅像DefaultRolloverStrategy那样,删除最旧的文件,所以使用的时候需要谨慎!-->
<DefaultRolloverStrategy>
<Delete basePath="${FILE_PATH}" maxDepth="2">
<IfFileName glob="zaedu-server-manager-*.log.gz" />
<IfLastModified age="30d" />
</Delete>
</DefaultRolloverStrategy>
</RollingFile>
<!-- 这个会打印出所有的warn及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
<RollingFile name="RollingFileWarn" fileName="${FILE_PATH}/warn.log"
filePattern="${FILE_PATH}/${FILE_NAME}-WARN-%d{yyyy-MM-dd}_%i.log.gz">
<!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
<ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="${LOG_PATTERN}" disableAnsi="false" noConsoleNoAnsi="false"/>
<Policies>
<!--
此参数需要与filePattern结合使用,规定了触发rollover的频率,默认值为1。
假设interval为4,若filePattern的date/time pattern的最小时间粒度为小时(如yyyy-MM-dd HH),
则每4小时触发一次rollover;若filePattern的date/time pattern的最小时间粒度为分钟(如yyyy-MM-dd HH-mm),
则每4分钟触发一次rollover。
-->
<TimeBasedTriggeringPolicy interval="1"/>
<!-- 按文件大小触发-->
<SizeBasedTriggeringPolicy size="500MB"/>
</Policies>
<!-- 通过DeleteAction可以删除任何文件,而不仅仅像DefaultRolloverStrategy那样,删除最旧的文件,所以使用的时候需要谨慎!-->
<DefaultRolloverStrategy>
<Delete basePath="${FILE_PATH}" maxDepth="2">
<IfFileName glob="zaedu-server-manager-*.log.gz" />
<IfLastModified age="30d" />
</Delete>
</DefaultRolloverStrategy>
</RollingFile>
<!-- 这个会打印出所有的error及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
<RollingFile name="RollingFileError" fileName="${FILE_PATH}/error.log"
filePattern="${FILE_PATH}/${FILE_NAME}-ERROR-%d{yyyy-MM-dd}_%i.log.gz">
<!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
<ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="${LOG_PATTERN}" disableAnsi="false" noConsoleNoAnsi="false"/>
<Policies>
<!--
此参数需要与filePattern结合使用,规定了触发rollover的频率,默认值为1。
假设interval为4,若filePattern的date/time pattern的最小时间粒度为小时(如yyyy-MM-dd HH),
则每4小时触发一次rollover;若filePattern的date/time pattern的最小时间粒度为分钟(如yyyy-MM-dd HH-mm),
则每4分钟触发一次rollover。
-->
<TimeBasedTriggeringPolicy interval="1"/>
<!-- 按文件大小触发-->
<SizeBasedTriggeringPolicy size="500MB"/>
</Policies>
<!-- 通过DeleteAction可以删除任何文件,而不仅仅像DefaultRolloverStrategy那样,删除最旧的文件,所以使用的时候需要谨慎!-->
<DefaultRolloverStrategy>
<Delete basePath="${FILE_PATH}" maxDepth="2">
<IfFileName glob="zaedu-server-manager-*.log.gz" />
<IfLastModified age="30d" />
</Delete>
</DefaultRolloverStrategy>
</RollingFile>
</appenders>
<!--Logger节点用来单独指定日志的形式,比如要为指定包下的class指定不同的日志级别等。-->
<!--然后定义loggers,只有定义了logger并引入的appender,appender才会生效-->
<loggers>
<!--若是additivity设为false,则 子Logger 只会在自己的appender里输出,而不会在 父Logger 的appender里输出。-->
<Logger name="org.springframework" level="info" additivity="false">
<AppenderRef ref="Console"/>
</Logger>
<logger name="corg.apache.ibatis" level="debug">
<appender-ref ref="Console"/>
<appender-ref ref="RollingFileInfo"/>
</logger>
<root level="info">
<appender-ref ref="Console"/>
<appender-ref ref="RollingFileInfo"/>
<appender-ref ref="RollingFileWarn"/>
<appender-ref ref="RollingFileError"/>
</root>
</loggers>
</configuration>
7、登录Admin页面
查看当前服务 查看详情 查看日志
======== 创建 (zaedu-inform)客户端 并整合钉钉消息通知
1、POM依赖
<!-- 钉钉消息推送 -->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>alibaba-dingtalk-service-sdk</artifactId>
<version>1.0.1</version>
<exclusions>
<exclusion>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- ============ Admin客户端 必须引入以下依赖 ============ -->
<!--SpringBoot Admin 客户端-->
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId>
</dependency>
<!--SpringBoot Admin 健康检查监测 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
2、配置文件application.yml
server:
port: 9029
spring:
application:
#消息通知服务
name: zaedu-inform
#配置的环境
profiles:
active: test
#================== SpringBoot Admin 配置参数 ==================
management:
health:
redis:
enabled: false #关闭redis健康检查
sentinel:
enabled: false #关闭sentinel健康检查
ldap:
enabled: false #关闭ldap健康检查
#暴露端点
endpoints:
web:
exposure:
include: '*'
#关闭过滤敏感信息
health:
sensitive: false
endpoint:
health:
show-details: always #显示详细信息
#SpringBoot Admin 日志文件目录
logfile:
#例:F:/文件夹/项目目录/trunk/zaedu-cloud/logs/:
external-file: ${user.dir}/logs/${spring.application.name}/info.log
enabled: true
#================== 日志配置 ==================
logging:
config: classpath:log4j2.xml
#打印项目业务相关的sql日志
level:
com:
za:
edu:
mapper: debug
dao: debug
#禁止打印c.a.n.client.config.impl.ClientWorker : get changedGroupKeys:[] 日志
alibaba:
nacos:
client:
config:
impl: WARN
#================== 钉钉消息通知 ==================
# 对接详情 查看钉钉官方文档:https://open.dingtalk.com/document/group/custom-robot-access
dingTalk:
#签名
sign: XXXXXXX
#消息服务地址
serverUrl: https://XXXXXXX?access_token=XXXXXXX
对接详情 查看钉钉官方文档: https://open.dingtalk.com/document/group/custom-robot-access
3、定义消息体DingTalkMessage
package com.za.edu.bean;
import com.dingtalk.api.request.OapiRobotSendRequest;
import lombok.Data;
import java.util.List;
@Data
public class DingTalkMessage {
private String title;
private String secret;
private String content;
private String msgType;
private String webhook;
private Boolean isAtAll;
private List<String> mobileList;
private String picUrl;
private String messageUrl;
private List<String> atMobiles;
private List<String> atUserIds;
private List<OapiRobotSendRequest.Links> links;
private String btnOrientation;
private List<OapiRobotSendRequest.Btns> btns;
public DingTalkMessage(){
}
}
4、定义MessageModeEnums消息模板
package com.za.edu.enums;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public enum MessageModeEnums {
TITLE_SERVER_ALARM("服务进程异常通知","消息关键字(服务进程异常警报)"),
TITLE_SERVER_STATUS("服务状态变更通知","消息关键字(服务状态变更通知)"),
MODE_TEXT("text","文本消息"),
MODE_LINK("link","链接消息"),
MODE_MARKDOWN ("markdown","图文链接消息"),
MODE_FEED_CARD ("feedCard","卡片图文样式消息"),
MODE_ACTION_CARD ( "actionCard","事件选择框样式消息");
MessageModeEnums(String type, String explain) {
this.type = type;
this.explain = explain;
}
private String type;
private String explain;
public String getType() {
return type;
}
public String getExplain() {
return explain;
}
public static String getValue(String type) {
String result = null;
try {
for (MessageModeEnums enums : MessageModeEnums.values()) {
if (enums.getType().equals(type)) {
result = enums.getExplain();
}
}
} catch (Exception e) {
log.error("Message event get explain faild: " + e.getMessage());
}
return result;
}
}
5、钉钉消息推送工具类DingTalkUtils
package com.za.edu.utils;
import com.dingtalk.api.DefaultDingTalkClient;
import com.dingtalk.api.DingTalkClient;
import com.dingtalk.api.request.OapiRobotSendRequest;
import com.dingtalk.api.response.OapiRobotSendResponse;
import com.za.edu.bean.DingTalkMessage;
import com.za.edu.enums.MessageModeEnums;
import com.za.edu.exception.BusinessException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.net.URLEncoder;
@Slf4j
public class DingTalkUtils {
public static OapiRobotSendResponse sendMessage(DingTalkMessage message) {
OapiRobotSendRequest request = null;
OapiRobotSendResponse response = null;
try {
ExceptionUtil.isBlank(message.getMsgType(), "Message type not null!");
ExceptionUtil.isBlank(message.getContent(), "Message content not null!");
DingTalkClient client = getClient();
ExceptionUtil.isNull(client, "Message client not null!");
if (MessageModeEnums.MODE_TEXT.getType().equals(message.getMsgType())) {
request = buildTextMessage(message.getContent(), message.getIsAtAll());
} else if (MessageModeEnums.MODE_LINK.getType().equals(message.getMsgType())) {
request = buildLinkMessage(message);
} else if (MessageModeEnums.MODE_MARKDOWN.getType().equals(message.getMsgType())) {
request = buildMarkdownMessage(message);
} else if (MessageModeEnums.MODE_FEED_CARD.getType().equals(message.getMsgType())) {
request = buildFeedCardMessage(message);
}
else if (MessageModeEnums.MODE_ACTION_CARD.getType().equals(message.getMsgType())) {
request = buildActionCardMessage(message);
}
else {
throw new BusinessException("Unknown message type:[" + message.getMsgType() + "]!");
}
ExceptionUtil.isNull(request,"build request faild !");
response = client.execute(request);
log.info("DingTalk send message response:" + response);
} catch (Exception e) {
e.printStackTrace();
log.error("DingTalk send message faild:" + e.getMessage());
}
return response;
}
public static OapiRobotSendRequest buildTextMessage(String content, Boolean isAtAll) {
try {
ExceptionUtil.isNull(isAtAll, "params:[isAtAll] not null !");
ExceptionUtil.isBlank(content, "params:[content] not null !");
OapiRobotSendRequest request = new OapiRobotSendRequest();
OapiRobotSendRequest.At at = new OapiRobotSendRequest.At();
OapiRobotSendRequest.Text text = new OapiRobotSendRequest.Text();
text.setContent(content);
at.setIsAtAll(isAtAll);
request.setAt(at);
request.setMsgtype(MessageModeEnums.MODE_TEXT.getType());
request.setText(text);
return request;
} catch (Exception e) {
log.error("Build text message faild: " + e.getMessage());
return null;
}
}
public static OapiRobotSendRequest buildLinkMessage(DingTalkMessage message) {
try {
ExceptionUtil.isBlank(message.getTitle(), "params:[title] not null !");
ExceptionUtil.isBlank(message.getPicUrl(), "params:[picUrl] not null !");
ExceptionUtil.isNull(message.getIsAtAll(), "params:[isAtAll] not null !");
ExceptionUtil.isBlank(message.getContent(), "params:[content] not null !");
ExceptionUtil.isBlank(message.getMessageUrl(), "params:[messageUrl] not null !");
OapiRobotSendRequest request = new OapiRobotSendRequest();
OapiRobotSendRequest.At at = new OapiRobotSendRequest.At();
OapiRobotSendRequest.Link link = new OapiRobotSendRequest.Link();
link.setTitle(message.getTitle());
link.setText(message.getContent());
link.setPicUrl(message.getPicUrl());
link.setMessageUrl(message.getMessageUrl());
at.setIsAtAll(message.getIsAtAll());
request.setAt(at);
request.setMsgtype(MessageModeEnums.MODE_LINK.getType());
request.setLink(link);
return request;
} catch (Exception e) {
log.error("Build link message faild: " + e.getMessage());
return null;
}
}
public static OapiRobotSendRequest buildMarkdownMessage(DingTalkMessage message) {
try {
ExceptionUtil.isBlank(message.getTitle(), "params:[title] not null !");
ExceptionUtil.isNull(message.getIsAtAll(), "params:[isAtAll] not null !");
ExceptionUtil.isBlank(message.getContent(), "params:[content] not null !");
OapiRobotSendRequest request = new OapiRobotSendRequest();
OapiRobotSendRequest.At at = new OapiRobotSendRequest.At();
OapiRobotSendRequest.Markdown markdown = new OapiRobotSendRequest.Markdown();
markdown.setTitle(message.getTitle());
markdown.setText(message.getContent());
at.setIsAtAll(message.getIsAtAll());
request.setAt(at);
request.setMsgtype(MessageModeEnums.MODE_MARKDOWN.getType());
request.setMarkdown(markdown);
return request;
} catch (Exception e) {
log.error("Build link message faild: " + e.getMessage());
return null;
}
}
public static OapiRobotSendRequest buildFeedCardMessage(DingTalkMessage message) {
try {
ExceptionUtil.isEmpty(message.getLinks(), "params:[links] not null !");
ExceptionUtil.isNull(message.getIsAtAll(), "params:[isAtAll] not null !");
OapiRobotSendRequest request = new OapiRobotSendRequest();
OapiRobotSendRequest.At at = new OapiRobotSendRequest.At();
OapiRobotSendRequest.Feedcard feedcard = new OapiRobotSendRequest.Feedcard();
feedcard.setLinks(message.getLinks());
at.setIsAtAll(message.getIsAtAll());
request.setAt(at);
request.setMsgtype(MessageModeEnums.MODE_FEED_CARD.getType());
request.setFeedCard(feedcard);
return request;
} catch (Exception e) {
log.error("Build link message faild: " + e.getMessage());
return null;
}
}
public static OapiRobotSendRequest buildActionCardMessage(DingTalkMessage message) {
try {
ExceptionUtil.isBlank(message.getTitle(), "params:[title] not null !");
ExceptionUtil.isNull(message.getIsAtAll(), "params:[isAtAll] not null !");
ExceptionUtil.isBlank(message.getContent(), "params:[content] not null !");
OapiRobotSendRequest request = new OapiRobotSendRequest();
OapiRobotSendRequest.At at = new OapiRobotSendRequest.At();
OapiRobotSendRequest.Actioncard actioncard = new OapiRobotSendRequest.Actioncard();
actioncard.setTitle(message.getTitle());
actioncard.setText(message.getContent());
actioncard.setBtns(message.getBtns());
at.setIsAtAll(message.getIsAtAll());
request.setAt(at);
request.setMsgtype(MessageModeEnums.MODE_ACTION_CARD.getType());
request.setActionCard(actioncard);
return request;
} catch (Exception e) {
log.error("Build link message faild: " + e.getMessage());
return null;
}
}
private static DingTalkClient getClient() {
String sign;
String serverUrl;
DingTalkClient client;
try {
sign = ApplicationContextHolder.getApplicationProperty("dingTalk.sign");
ExceptionUtil.isBlank(sign, "Params: [sign] not null!");
serverUrl = ApplicationContextHolder.getApplicationProperty("dingTalk.serverUrl");
ExceptionUtil.isBlank(serverUrl, "Params: [serverUrl] not null!");
client = new DefaultDingTalkClient(serverUrl);
ExceptionUtil.isNull(client, "Create dingTalk client faild!");
} catch (Exception e) {
throw new BusinessException("Init instance faild: " + e.getMessage());
}
try {
Long timestamp = System.currentTimeMillis();
String stringToSign = timestamp + "\n" + sign;
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(sign.getBytes("UTF-8"), "HmacSHA256"));
byte[] signData = mac.doFinal(stringToSign.getBytes("UTF-8"));
String signEncoder = URLEncoder.encode(new String(Base64.encodeBase64(signData)), "UTF-8");
client = new DefaultDingTalkClient(serverUrl + "×tamp=" + timestamp + "&sign=" + signEncoder);
} catch (Exception e) {
log.error("Create dingTalk client faild: " + e.getMessage());
return null;
}
return client;
}
}
6、消息推送接口DingTalkMessageController
package com.za.edu.controller;
import com.dingtalk.api.response.OapiRobotSendResponse;
import com.za.edu.bean.DingTalkMessage;
import com.za.edu.returns.ApiResult;
import com.za.edu.utils.DingTalkUtils;
import com.za.edu.utils.ExceptionUtil;
import org.springframework.web.bind.annotation.*;
import java.util.Objects;
@RestController
@RequestMapping("/dingTalk/message")
public class DingTalkMessageController {
@PostMapping(value = "/pushInform")
public ApiResult pushInform(@RequestBody DingTalkMessage message) {
ExceptionUtil.isNull(message, "Message params not null!");
OapiRobotSendResponse result = DingTalkUtils.sendMessage(message);
return ApiResult.successWithObject(Objects.isNull(result) ? null : result.getCode());
}
}
7、启动第一个客户端(通知项目zaedu-inform)
======== Admin服务端 整合项目服务 监听事件
1、TaluohuiNotifier 服务监听通知类
package com.za.edu.event;
import com.za.edu.bean.DingTalkMessage;
import com.za.edu.bean.ServerInfo;
import com.za.edu.client.InformServiceClient;
import com.za.edu.utils.ApplicationContextHolder;
import com.za.edu.utils.NacosUtils;
import de.codecentric.boot.admin.server.domain.entities.Instance;
import de.codecentric.boot.admin.server.domain.entities.InstanceRepository;
import de.codecentric.boot.admin.server.domain.events.InstanceEvent;
import de.codecentric.boot.admin.server.domain.events.InstanceStatusChangedEvent;
import de.codecentric.boot.admin.server.notify.AbstractEventNotifier;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Slf4j
@Component
@SuppressWarnings("all")
public class TaluohuiNotifier extends AbstractEventNotifier {
@Resource
private NacosUtils nacosUtils;
@Resource
private InformServiceClient informServiceClient;
protected TaluohuiNotifier(InstanceRepository repository) {
super(repository);
}
@Override
protected Mono<Void> doNotify(InstanceEvent event, Instance instance) {
return Mono.fromRunnable(() -> {
try {
if (event instanceof InstanceStatusChangedEvent) {
log.info("\nServer status event: "
+ "Name: [" + instance.getRegistration().getName()
+ "], Status: [" + ((InstanceStatusChangedEvent) event).getStatusInfo().getStatus()
+ "], Instance: [" + event.getInstance() + "]");
if ("UP".contains(((InstanceStatusChangedEvent) event).getStatusInfo().getStatus())) {
DingTalkMessage message = new DingTalkMessage();
message.setIsAtAll(false);
message.setMsgType("text");
ApplicationContext context = ApplicationContextHolder.getApplicationContext();
String active = context.getEnvironment().getActiveProfiles()[0];
StringBuffer body = new StringBuffer
(" 《服务状态通知》 ");
body.append("\n服务环境: " + active);
body.append("\n服务状态: 上线! ");
body.append("\n服务名称: " + instance.getRegistration().getName());
body.append("\n服务注册IP: " + instance.getRegistration().getServiceUrl());
body.append("\nNacos分组: " + nacosUtils.getGroupName());
message.setContent(body.toString());
informServiceClient.pushInform(message);
}
else if ("OFFLINE".contains(((InstanceStatusChangedEvent) event).getStatusInfo().getStatus())) {
DingTalkMessage message = new DingTalkMessage();
message.setIsAtAll(false);
message.setMsgType("text");
ApplicationContext context = ApplicationContextHolder.getApplicationContext();
String active = context.getEnvironment().getActiveProfiles()[0];
StringBuffer body = new StringBuffer
(" 《服务状态通知》 ");
body.append("\n服务环境: " + active);
body.append("\n服务状态: 离线!");
body.append("\n服务名称: " + instance.getRegistration().getName());
body.append("\n服务注册IP: " + instance.getRegistration().getServiceUrl());
body.append("\nNacos分组: " + nacosUtils.getGroupName());
message.setContent(body.toString());
informServiceClient.pushInform(message);
}
}
else {
log.info("\nServer info: "
+ "Name: [" + instance.getRegistration().getName()
+ "], Type: [" + event.getType()
+ "], Instance: [" + event.getInstance() + "]");
}
} catch (Exception e) {
log.error("Runnable server exception: " + e.getMessage());
}
});
}
}
2、跨服务调用钉钉通知
package com.za.edu.client;
import com.za.edu.bean.DingTalkMessage;
import com.za.edu.returns.ApiResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@FeignClient("zaedu-inform")
public interface InformServiceClient {
@RequestMapping(value = "/dingTalk/message/pushInform", method = RequestMethod.POST, produces = {"application/json"}, consumes = {"application/excel+json"})
ApiResult pushInform(@RequestBody DingTalkMessage message);
}
3、启动第二个客户端(zaedu-algorithm)
参考第一个客户端 引入 客户端pom依赖 以及 配置参数即可
4、获取服务列表 接口详情
通过访问 http://192.168.0.27:9088/admin/applications可以 获取当前服务详情列表
5、服务端触发 客户端服务 状态变更事件**
6、推送 钉钉自定义机器人 消息通知**
7、推送 钉钉自定义机器人 下线事件通知**
服务下线则触发下线通知
|