集成SpringBoot-Admin监控框架

This commit is contained in:
dataprince 2023-08-17 11:18:22 +08:00
parent 53a07d2329
commit db25e10b83
21 changed files with 418 additions and 32 deletions

View File

@ -13,7 +13,7 @@
<description>Ruoyi-Flex管理系统</description>
<properties>
<revision>4.1.4</revision>
<revision>4.1.5-SNAPSHOT</revision>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>17</java.version>

View File

@ -67,6 +67,12 @@
<artifactId>ruoyi-demo</artifactId>
</dependency>
<!-- spring-boot-admin 监控客户端 -->
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId>
</dependency>
</dependencies>
<build>

View File

@ -105,4 +105,24 @@ redisson:
# 发布和订阅连接池大小
subscriptionConnectionPoolSize: 50
--- # Actuator 监控端点的配置项
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: ALWAYS
logfile:
external-file: ./logs/ruoyi-monitor.log
--- # 监控中心配置
spring.boot.admin.client:
# 增加客户端开关
enabled: true
url: http://localhost:9090/admin
instance:
service-host-type: IP
username: ruoyi
password: 123456

View File

@ -107,4 +107,25 @@ redisson:
# 发布和订阅连接池大小
subscriptionConnectionPoolSize: 50
--- # Actuator 监控端点的配置项
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: ALWAYS
logfile:
external-file: ./logs/ruoyi-monitor.log
--- # 监控中心配置
spring.boot.admin.client:
# 增加客户端开关
enabled: true
url: http://localhost:9090/admin
instance:
service-host-type: IP
username: ruoyi
password: 123456

View File

@ -14,7 +14,7 @@
</description>
<properties>
<revision>4.1.4</revision>
<revision>4.1.5-SNAPSHOT</revision>
</properties>
<dependencyManagement>

View File

@ -98,7 +98,7 @@ public class ExcelUtil<T> {
/**
* 用于dictType属性数据存储避免重复查缓存
*/
public Map<String, String> sysDictMap = new HashMap<String, String>();
public Map<String, String> sysDictMap = new HashMap<>();
/**
* Excel sheet最大行数默认65536
@ -178,7 +178,7 @@ public class ExcelUtil<T> {
/**
* 统计列表
*/
private Map<Integer, Double> statistics = new HashMap<Integer, Double>();
private Map<Integer, Double> statistics = new HashMap<>();
/**
* 数字格式
@ -203,7 +203,6 @@ public class ExcelUtil<T> {
* 隐藏Excel中列属性
*
* @param fields 列属性名 示例[单个"name"/多个"id","name"]
* @throws Exception
*/
public void hideColumn(String... fields) {
this.excludeFields = fields;
@ -211,7 +210,7 @@ public class ExcelUtil<T> {
public void init(List<T> list, String sheetName, String title, Excel.Type type) {
if (list == null) {
list = new ArrayList<T>();
list = new ArrayList<>();
}
this.list = list;
this.sheetName = sheetName;
@ -300,7 +299,7 @@ public class ExcelUtil<T> {
public List<T> importExcel(String sheetName, InputStream is, int titleNum) throws Exception {
this.type = Excel.Type.IMPORT;
this.wb = WorkbookFactory.create(is);
List<T> list = new ArrayList<T>();
List<T> list = new ArrayList<>();
// 如果指定sheet名,则取指定sheet中的内容 否则默认指向第1个sheet
Sheet sheet = StringUtils.isNotEmpty(sheetName) ? wb.getSheet(sheetName) : wb.getSheetAt(0);
if (sheet == null) {
@ -318,7 +317,7 @@ public class ExcelUtil<T> {
if (rows > 0) {
// 定义一个map用于存放excel列的序号和field.
Map<String, Integer> cellMap = new HashMap<String, Integer>();
Map<String, Integer> cellMap = new HashMap<>();
// 获取表头
Row heard = sheet.getRow(titleNum);
for (int i = 0; i < heard.getPhysicalNumberOfCells(); i++) {
@ -332,7 +331,7 @@ public class ExcelUtil<T> {
}
// 有数据时才处理 得到类的所有field.
List<Object[]> fields = this.getFields();
Map<Integer, Object[]> fieldsMap = new HashMap<Integer, Object[]>();
Map<Integer, Object[]> fieldsMap = new HashMap<>();
for (Object[] objects : fields) {
Excel attr = (Excel) objects[1];
Integer column = cellMap.get(attr.name());
@ -447,7 +446,6 @@ public class ExcelUtil<T> {
* @param response 返回数据
* @param list 导出数据集合
* @param sheetName 工作表的名称
* @return 结果
*/
public void exportExcel(HttpServletResponse response, List<T> list, String sheetName) {
exportExcel(response, list, sheetName, StringUtils.EMPTY);
@ -460,7 +458,6 @@ public class ExcelUtil<T> {
* @param list 导出数据集合
* @param sheetName 工作表的名称
* @param title 标题
* @return 结果
*/
public void exportExcel(HttpServletResponse response, List<T> list, String sheetName, String title) {
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
@ -495,7 +492,6 @@ public class ExcelUtil<T> {
* 对list数据源将其里面的数据导入到excel表单
*
* @param sheetName 工作表的名称
* @return 结果
*/
public void importTemplateExcel(HttpServletResponse response, String sheetName) {
importTemplateExcel(response, sheetName, StringUtils.EMPTY);
@ -506,7 +502,6 @@ public class ExcelUtil<T> {
*
* @param sheetName 工作表的名称
* @param title 标题
* @return 结果
*/
public void importTemplateExcel(HttpServletResponse response, String sheetName, String title) {
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
@ -518,7 +513,6 @@ public class ExcelUtil<T> {
/**
* 对list数据源将其里面的数据导入到excel表单
*
* @return 结果
*/
public void exportExcel(HttpServletResponse response) {
try {
@ -650,7 +644,7 @@ public class ExcelUtil<T> {
*/
private Map<String, CellStyle> createStyles(Workbook wb) {
// 写入各条记录,每条记录对应excel表中的一行
Map<String, CellStyle> styles = new HashMap<String, CellStyle>();
Map<String, CellStyle> styles = new HashMap<>();
CellStyle style = wb.createCellStyle();
style.setAlignment(HorizontalAlignment.CENTER);
style.setVerticalAlignment(VerticalAlignment.CENTER);
@ -701,7 +695,7 @@ public class ExcelUtil<T> {
* @return 自定义样式列表
*/
private Map<String, CellStyle> annotationHeaderStyles(Workbook wb, Map<String, CellStyle> styles) {
Map<String, CellStyle> headerStyles = new HashMap<String, CellStyle>();
Map<String, CellStyle> headerStyles = new HashMap<>();
for (Object[] os : fields) {
Excel excel = (Excel) os[1];
String key = StringUtils.format("header_{}_{}", excel.headerColor(), excel.headerBackgroundColor());
@ -731,7 +725,7 @@ public class ExcelUtil<T> {
* @return 自定义样式列表
*/
private Map<String, CellStyle> annotationDataStyles(Workbook wb) {
Map<String, CellStyle> styles = new HashMap<String, CellStyle>();
Map<String, CellStyle> styles = new HashMap<>();
for (Object[] os : fields) {
Excel excel = (Excel) os[1];
String key = StringUtils.format("data_{}_{}_{}", excel.align(), excel.color(), excel.backgroundColor());
@ -840,7 +834,7 @@ public class ExcelUtil<T> {
* 创建表格样式
*/
public void setDataValidation(Excel attr, Row row, int column) {
if (attr.name().indexOf("注:") >= 0) {
if (attr.name().contains("注:")) {
sheet.setColumnWidth(column, 6000);
} else {
// 设置列宽
@ -1153,7 +1147,6 @@ public class ExcelUtil<T> {
* @param field 字段
* @param excel 注解
* @return 最终的属性值
* @throws Exception
*/
private Object getTargetValue(T vo, Field field, Excel excel) throws Exception {
Object o = field.get(vo);
@ -1202,7 +1195,7 @@ public class ExcelUtil<T> {
* 获取字段注解信息
*/
public List<Object[]> getFields() {
List<Object[]> fields = new ArrayList<Object[]>();
List<Object[]> fields = new ArrayList<>();
List<Field> tempFields = new ArrayList<>();
tempFields.addAll(Arrays.asList(clazz.getSuperclass().getDeclaredFields()));
tempFields.addAll(Arrays.asList(clazz.getDeclaredFields()));
@ -1345,7 +1338,7 @@ public class ExcelUtil<T> {
* @return Map key:图片单元格索引1_1Stringvalue:图片流PictureData
*/
public static Map<String, PictureData> getSheetPictures03(HSSFSheet sheet, HSSFWorkbook workbook) {
Map<String, PictureData> sheetIndexPicMap = new HashMap<String, PictureData>();
Map<String, PictureData> sheetIndexPicMap = new HashMap<>();
List<HSSFPictureData> pictures = workbook.getAllPictures();
if (!pictures.isEmpty()) {
for (HSSFShape shape : sheet.getDrawingPatriarch().getChildren()) {
@ -1372,7 +1365,7 @@ public class ExcelUtil<T> {
* @return Map key:图片单元格索引1_1Stringvalue:图片流PictureData
*/
public static Map<String, PictureData> getSheetPictures07(XSSFSheet sheet, XSSFWorkbook workbook) {
Map<String, PictureData> sheetIndexPicMap = new HashMap<String, PictureData>();
Map<String, PictureData> sheetIndexPicMap = new HashMap<>();
for (POIXMLDocumentPart dr : sheet.getRelations()) {
if (dr instanceof XSSFDrawing) {
XSSFDrawing drawing = (XSSFDrawing) dr;

View File

@ -3,8 +3,8 @@
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>ruoyi-flex</artifactId>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-flex</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@ -13,7 +13,7 @@
<artifactId>ruoyi-extra</artifactId>
<modules>
<module>ruoyi-monitor</module>
</modules>
</project>

View File

@ -0,0 +1,13 @@
FROM findepi/graalvm:java17-native
MAINTAINER Lion Li
RUN mkdir -p /ruoyi/monitor/logs
WORKDIR /ruoyi/monitor
EXPOSE 9090
ADD ./target/ruoyi-monitor-admin.jar ./app.jar
ENTRYPOINT ["java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "app.jar"]

View File

@ -0,0 +1,67 @@
<?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>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-extra</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging>
<artifactId>ruoyi-monitor</artifactId>
<dependencies>
<!-- SpringWeb模块 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- spring security 安全认证 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- spring-boot-admin 监控服务端 -->
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-server</artifactId>
</dependency>
<!-- spring-boot-admin 监控客户端 -->
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<configuration>
<!-- <fork>true</fork> &lt;!&ndash; 如果没有该配置devtools不会生效 &ndash;&gt;-->
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,21 @@
package com.ruoyi.monitor.admin;
import de.codecentric.boot.admin.server.config.EnableAdminServer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* SpringBoot Admin 监控启动程序
*
* @author dataprince数据小王子
*/
@EnableAdminServer
@SpringBootApplication
public class RuoYiMonitorAdminApplication {
public static void main(String[] args) {
SpringApplication.run(RuoYiMonitorAdminApplication.class, args);
System.out.println("SpringBoot Admin 监控程序启动成功!");
}
}

View File

@ -0,0 +1,31 @@
package com.ruoyi.monitor.admin.config;
import de.codecentric.boot.admin.server.config.EnableAdminServer;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration;
import org.springframework.boot.task.TaskExecutorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
/**
* springboot-admin server配置类
*
* @author Lion Li
*/
@Configuration
@EnableAdminServer
public class AdminServerConfig {
@Lazy
@Bean(name = TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME)
@ConditionalOnMissingBean(Executor.class)
public ThreadPoolTaskExecutor applicationTaskExecutor(TaskExecutorBuilder builder) {
return builder.build();
}
}

View File

@ -0,0 +1,56 @@
package com.ruoyi.monitor.admin.config;
import de.codecentric.boot.admin.server.config.AdminServerProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
/**
* SpringBootAdmin 监控安全配置
*
* @author Lion Li
*/
@EnableWebSecurity
@Configuration
public class SpringBootAdminSecurityConfig {
private final String adminContextPath;
public SpringBootAdminSecurityConfig(AdminServerProperties adminServerProperties) {
this.adminContextPath = adminServerProperties.getContextPath();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
SavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
successHandler.setTargetUrlParameter("redirectTo");
successHandler.setDefaultTargetUrl(adminContextPath + "/");
return httpSecurity
.headers((header) ->
header.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable))
.authorizeHttpRequests((authorize) ->
authorize.requestMatchers(
new AntPathRequestMatcher(adminContextPath + "/assets/**"),
new AntPathRequestMatcher(adminContextPath + "/login"),
new AntPathRequestMatcher("/actuator"),
new AntPathRequestMatcher("/actuator/**")
).permitAll()
.anyRequest().authenticated())
.formLogin((formLogin) ->
formLogin.loginPage(adminContextPath + "/login").successHandler(successHandler))
.logout((logout) ->
logout.logoutUrl(adminContextPath + "/logout"))
.httpBasic(Customizer.withDefaults())
.csrf(AbstractHttpConfigurer::disable)
.build();
}
}

View File

@ -0,0 +1,40 @@
package com.ruoyi.monitor.admin.notifier;
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.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
/**
* 自定义事件通知处理
*
* @author Lion Li
*/
@Slf4j
@Component
public class CustomNotifier extends AbstractEventNotifier {
protected CustomNotifier(InstanceRepository repository) {
super(repository);
}
@Override
@SuppressWarnings("all")
protected Mono<Void> doNotify(InstanceEvent event, Instance instance) {
return Mono.fromRunnable(() -> {
// 实例状态改变事件
if (event instanceof InstanceStatusChangedEvent) {
String registName = instance.getRegistration().getName();
String instanceId = event.getInstance().getValue();
String status = ((InstanceStatusChangedEvent) event).getStatusInfo().getStatus();
log.info("Instance Status Change: [{}],[{}],[{}]", registName, instanceId, status);
}
});
}
}

View File

@ -0,0 +1,45 @@
server:
port: 9090
spring:
application:
name: Ruoyi-Monitor-AdminServer
profiles:
active: @profiles.active@
logging:
config: classpath:logback-ruoyi-monitor.xml
--- # 监控中心服务端配置
spring:
security:
user:
name: ruoyi
password: 123456
boot:
admin:
ui:
title: RuoYi-Flex服务监控中心
context-path: /admin
--- # Actuator 监控端点的配置项
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: ALWAYS
logfile:
external-file: ./logs/ruoyi-monitor.log
--- # 监控配置
spring.boot.admin.client:
# 增加客户端开关
enabled: true
# 设置 Spring Boot Admin Server 地址
url: http://localhost:9090/admin
instance:
service-host-type: IP
username: ruoyi
password: 123456

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false" scan="true" scanPeriod="1 seconds">
<contextName>logback</contextName>
<property name="log.path" value="./logs/ruoyi-monitor"/>
<property name="console.log.pattern"
value="%red(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %boldMagenta(%logger{36}%n) - %msg%n"/>
<property name="log.pattern" value="%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"/>
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${console.log.pattern}</pattern>
<charset>utf-8</charset>
</encoder>
</appender>
<appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log.path}.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 日志最大的历史 60天 -->
<maxHistory>60</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
</appender>
<root level="info">
<appender-ref ref="console"/>
<appender-ref ref="file"/>
</root>
</configuration>

View File

@ -7,5 +7,8 @@ ENV = 'development'
# 若依管理系统/开发环境
VUE_APP_BASE_API = '/dev-api'
# 监控地址
VUE_APP_MONITRO_ADMIN = 'http://localhost:9090/admin/applications'
# 路由懒加载
VUE_CLI_BABEL_TRANSPILE_MODULES = true

View File

@ -6,3 +6,6 @@ ENV = 'production'
# 若依管理系统/生产环境
VUE_APP_BASE_API = '/prod-api'
# 监控地址
VUE_APP_MONITRO_ADMIN = 'http://localhost:9090/admin/applications'

View File

@ -1,7 +1,7 @@
<template>
<div v-loading="loading" :style="'height:' + height">
<iframe
:src="src"
:src="url"
frameborder="no"
style="width: 100%; height: 100%"
scrolling="auto"
@ -13,14 +13,14 @@ export default {
props: {
src: {
type: String,
required: true
},
default:"/"
}
},
data() {
return {
height: document.documentElement.clientHeight - 94.5 + "px;",
loading: true,
url: this.src
url:this.src
};
},
mounted: function () {

View File

@ -41,7 +41,7 @@
<el-col :sm="24" :lg="12" style="padding-left: 20px">
<h2>Ruoyi-Flex后台管理框架</h2>
<p>
Ruoyi-Flex是基于RuoYi-Vue v3.8.6进行的扩展集成MyBatis-FlexJDK17SpringBootV3lombokSa-TokenPowerJobHutoolOSSureport-keepFlowablevue3TypeScript等优秀开源软件准备作为未来5年软件开发的底座本系统可以用于所有的Web应用程序如网站管理后台网站会员中心CMSCRMOAERP等等当然您也可以对她进行深度定制以做出更强系统所有前端后台代码封装过后十分精简易上手出错概率低同时支持移动客户端访问系统会陆续更新一些实用功能
Ruoyi-Flex是基于RuoYi-Vue v3.8.6RuoYi-Vue-Plus进行的扩展集成MyBatis-FlexJDK17SpringBootV3LombokSa-TokenHutool等优秀开源软件准备作为未来5年软件开发的底座本系统可以用于所有的Web应用程序如网站管理后台网站会员中心CMSCRMOAERP等等当然您也可以对她进行深度定制以做出更强系统所有前端后台代码封装过后十分精简易上手出错概率低同时支持移动客户端访问系统会陆续更新一些实用功能
</p>
<p>
<b>当前版本:</b> <span>v{{ version }}</span>
@ -73,10 +73,10 @@
<h4>后端技术</h4>
<ul>
<li>SpringBoot V3</li>
<li>Spring Security</li>
<li>Sa-Token</li>
<li>JWT</li>
<li>MyBatis-FlexMyBatis</li>
<li>Fastjson</li>
<li>Lombok</li>
<li>...</li>
</ul>
</el-col>
@ -136,6 +136,14 @@
</div>
<el-collapse accordion>
<el-collapse-item title="v4.1.4 - 2023-08-16">
<ol>
<li>优化登录提示信息</li>
<li>调整项目结构</li>
<li>集成Sa-TokenLombokHutool等软件</li>
<li>SpringDoc模块与Sa-Token集成</li>
</ol>
</el-collapse-item>
<el-collapse-item title="v4.1.3 - 2023-07-29">
<ol>
<li>集成SpringDoc代替springfox</li>
@ -208,7 +216,7 @@ export default {
data() {
return {
//
version: "4.1.3"
version: "4.1.4"
};
},
methods: {

View File

@ -0,0 +1,22 @@
<template>
<div>
<i-frame :src="url"></i-frame>
</div>
</template>
<script>
import IFrame from "@/components/iFrame/index.vue";
export default {
name: "admin",
components: {IFrame},
data() {
return {
url: String
};
},
created() {
this.url = process.env.VUE_APP_MONITRO_ADMIN;
}
};
</script>

View File

@ -272,5 +272,8 @@ UPDATE sys_user SET user_type="sys_user";
delete FROM sys_menu WHERE menu_name = "缓存列表"
-- “服务监控”菜单使用SpringBoot-Admin监控框架
UPDATE `sys_menu` SET `path`='admin', `component`='monitor/admin/index', `perms`='monitor:admin:list' WHERE `menu_id`=112;