├── lite-monitor-client ├── src │ ├── main │ │ ├── resources │ │ │ ├── application.yaml │ │ │ └── logback-spring.xml │ │ └── java │ │ │ └── org │ │ │ └── example │ │ │ ├── entity │ │ │ ├── ConnectionConfig.java │ │ │ ├── Response.java │ │ │ ├── RuntimeDetail.java │ │ │ └── ClientDetail.java │ │ │ ├── MonitorClientApplication.java │ │ │ ├── utils │ │ │ ├── CronUtils.java │ │ │ └── NetUtils.java │ │ │ ├── task │ │ │ └── MonitorJobBean.java │ │ │ ├── config │ │ │ ├── QuartzConfiguration.java │ │ │ └── ServerConfiguration.java │ │ │ └── aop │ │ │ └── InitClientDetail.java │ └── test │ │ └── java │ │ └── org │ │ └── example │ │ └── LiteMonitorClientApplicationTests.java └── pom.xml ├── .gitignore ├── lite-monitor-web ├── public │ └── favicon.ico ├── src │ ├── assets │ │ ├── img │ │ │ ├── bg.jpg │ │ │ └── manage-background.jpg │ │ ├── ai-gen │ │ │ ├── c-1.png │ │ │ └── c-2.png │ │ ├── logo │ │ │ ├── lite-monitor.png │ │ │ ├── lite-monitor-logo.png │ │ │ ├── lite-monitor-nobg.png │ │ │ └── lite-monitor-logo-nobg.png │ │ └── css │ │ │ └── elemenet.less │ ├── nginx.conf │ ├── store │ │ └── index.js │ ├── App.vue │ ├── component │ │ ├── TableItem.vue │ │ ├── RegisterCard.vue │ │ ├── Terminal.vue │ │ ├── RuntimeHistory.vue │ │ ├── TerminalWindow.vue │ │ ├── ReportCard.vue │ │ ├── PreviewCard.vue │ │ ├── ReportDetails.vue │ │ └── CreateSubAccount.vue │ ├── main.js │ ├── router │ │ └── index.js │ ├── views │ │ ├── WelcomeView.vue │ │ ├── welcome │ │ │ └── LoginPage.vue │ │ ├── main │ │ │ ├── Report.vue │ │ │ └── Manage.vue │ │ └── IndexView.vue │ ├── tools │ │ └── index.js │ ├── net │ │ └── index.js │ └── echarts │ │ └── index.js ├── Dockerfile ├── lite-monitor-web.iml ├── .gitignore ├── index.html ├── vite.config.js ├── README.md └── package.json ├── lite-monitor-server ├── src │ ├── main │ │ ├── resources │ │ │ ├── application.yml │ │ │ ├── application-dev.yml │ │ │ ├── application-prod.yml │ │ │ └── logback-spring.xml │ │ └── java │ │ │ └── com │ │ │ └── example │ │ │ ├── entity │ │ │ ├── vo │ │ │ │ ├── response │ │ │ │ │ ├── SshSettingsVO.java │ │ │ │ │ ├── SubAccountVO.java │ │ │ │ │ ├── ClientSimpleVO.java │ │ │ │ │ ├── AuthorizeVO.java │ │ │ │ │ ├── RuntimeHistoryVO.java │ │ │ │ │ ├── ClientDetailsVO.java │ │ │ │ │ └── ClientPreviewVO.java │ │ │ │ └── request │ │ │ │ │ ├── ChangePasswordVO.java │ │ │ │ │ ├── ModifyEmailVO.java │ │ │ │ │ ├── RenameClientVO.java │ │ │ │ │ ├── ConfirmResetVO.java │ │ │ │ │ ├── RenameNodeVO.java │ │ │ │ │ ├── EmailResetVO.java │ │ │ │ │ ├── SshConnectionVO.java │ │ │ │ │ ├── CreateSubAccountVO.java │ │ │ │ │ ├── RuntimeDetailVO.java │ │ │ │ │ ├── ClientDetailVO.java │ │ │ │ │ └── ReportClientVO.java │ │ │ ├── dto │ │ │ │ ├── ClientSsh.java │ │ │ │ ├── Client.java │ │ │ │ ├── ClientDetail.java │ │ │ │ ├── RuntimeDetail.java │ │ │ │ └── Account.java │ │ │ ├── RestBean.java │ │ │ └── BaseData.java │ │ │ ├── mapper │ │ │ ├── AccountMapper.java │ │ │ ├── ClientMapper.java │ │ │ ├── ClientSshMapper.java │ │ │ └── ClientDetailMapper.java │ │ │ ├── config │ │ │ ├── ReportConfiguration.java │ │ │ ├── WebSocketConfiguration.java │ │ │ ├── RabbitConfiguration.java │ │ │ ├── WebConfiguration.java │ │ │ ├── SwaggerConfiguration.java │ │ │ └── SecurityConfiguration.java │ │ │ ├── service │ │ │ ├── ReportService.java │ │ │ ├── AccountService.java │ │ │ ├── ClientService.java │ │ │ └── impl │ │ │ │ └── ReportServiceImpl.java │ │ │ ├── MonitorServerApplication.java │ │ │ ├── controller │ │ │ ├── exception │ │ │ │ ├── ValidationController.java │ │ │ │ └── ErrorPageController.java │ │ │ ├── ServerController.java │ │ │ ├── UserController.java │ │ │ └── AuthorizeController.java │ │ │ ├── listener │ │ │ ├── ReportQueueListener.java │ │ │ └── MailQueueListener.java │ │ │ ├── utils │ │ │ ├── Const.java │ │ │ ├── MailUtils.java │ │ │ ├── SnowflakeIdGenerator.java │ │ │ ├── FlowUtils.java │ │ │ ├── InfluxDbUtils.java │ │ │ └── JwtUtils.java │ │ │ ├── filter │ │ │ ├── CorsFilter.java │ │ │ ├── FlowLimitingFilter.java │ │ │ ├── JwtAuthenticationFilter.java │ │ │ └── RequestLogFilter.java │ │ │ └── websocket │ │ │ └── TerminalWebSocket.java │ └── test │ │ └── java │ │ └── com │ │ └── example │ │ └── MyProjectBackendApplicationTests.java ├── Dockerfile └── pom.xml ├── lite-monitor.iml ├── docker-compose.yml ├── README.md ├── lite-monitor-db.sql └── en └── README.md /lite-monitor-client/src/main/resources/application.yaml: -------------------------------------------------------------------------------- 1 | spring.application.name=lite-monitor-client 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | log 3 | local-config 4 | lite-monitor-client/target 5 | lite-monitor-server/target 6 | target -------------------------------------------------------------------------------- /lite-monitor-web/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haueosc/lite-monitor/HEAD/lite-monitor-web/public/favicon.ico -------------------------------------------------------------------------------- /lite-monitor-web/src/assets/img/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haueosc/lite-monitor/HEAD/lite-monitor-web/src/assets/img/bg.jpg -------------------------------------------------------------------------------- /lite-monitor-web/src/assets/ai-gen/c-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haueosc/lite-monitor/HEAD/lite-monitor-web/src/assets/ai-gen/c-1.png -------------------------------------------------------------------------------- /lite-monitor-web/src/assets/ai-gen/c-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haueosc/lite-monitor/HEAD/lite-monitor-web/src/assets/ai-gen/c-2.png -------------------------------------------------------------------------------- /lite-monitor-web/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx:latest 2 | COPY dist /home/lite-monitor/frontend 3 | COPY src/nginx.conf /etc/nginx/conf.d/default.conf 4 | EXPOSE 80 -------------------------------------------------------------------------------- /lite-monitor-web/src/assets/logo/lite-monitor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haueosc/lite-monitor/HEAD/lite-monitor-web/src/assets/logo/lite-monitor.png -------------------------------------------------------------------------------- /lite-monitor-server/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | profiles: 3 | active: '@environment@' 4 | main: 5 | allow-circular-references: true -------------------------------------------------------------------------------- /lite-monitor-web/src/assets/img/manage-background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haueosc/lite-monitor/HEAD/lite-monitor-web/src/assets/img/manage-background.jpg -------------------------------------------------------------------------------- /lite-monitor-web/src/assets/logo/lite-monitor-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haueosc/lite-monitor/HEAD/lite-monitor-web/src/assets/logo/lite-monitor-logo.png -------------------------------------------------------------------------------- /lite-monitor-web/src/assets/logo/lite-monitor-nobg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haueosc/lite-monitor/HEAD/lite-monitor-web/src/assets/logo/lite-monitor-nobg.png -------------------------------------------------------------------------------- /lite-monitor-web/src/assets/logo/lite-monitor-logo-nobg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haueosc/lite-monitor/HEAD/lite-monitor-web/src/assets/logo/lite-monitor-logo-nobg.png -------------------------------------------------------------------------------- /lite-monitor-server/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:20.04 2 | RUN apt update 3 | RUN apt install -y openjdk-17-jdk 4 | COPY target/lite-monitor-server-0.0.1-SNAPSHOT.jar /home/lite-monitor/server/server.jar 5 | CMD java -jar /home/lite-monitor/server/server.jar 6 | EXPOSE 8080 -------------------------------------------------------------------------------- /lite-monitor-server/src/main/java/com/example/entity/vo/response/SshSettingsVO.java: -------------------------------------------------------------------------------- 1 | package com.example.entity.vo.response; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class SshSettingsVO { 7 | String clientAddress; 8 | Integer port = 22; 9 | String username; 10 | String password; 11 | } 12 | -------------------------------------------------------------------------------- /lite-monitor.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /lite-monitor-server/src/main/java/com/example/entity/vo/response/SubAccountVO.java: -------------------------------------------------------------------------------- 1 | package com.example.entity.vo.response; 2 | 3 | import com.alibaba.fastjson2.JSONArray; 4 | import lombok.Data; 5 | 6 | @Data 7 | public class SubAccountVO { 8 | int id; 9 | String username; 10 | String email; 11 | JSONArray clientList; 12 | } 13 | -------------------------------------------------------------------------------- /lite-monitor-server/src/main/java/com/example/mapper/AccountMapper.java: -------------------------------------------------------------------------------- 1 | package com.example.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.example.entity.dto.Account; 5 | import org.apache.ibatis.annotations.Mapper; 6 | 7 | @Mapper 8 | public interface AccountMapper extends BaseMapper { 9 | } 10 | -------------------------------------------------------------------------------- /lite-monitor-server/src/main/java/com/example/mapper/ClientMapper.java: -------------------------------------------------------------------------------- 1 | package com.example.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.example.entity.dto.Client; 5 | import org.apache.ibatis.annotations.Mapper; 6 | 7 | @Mapper 8 | public interface ClientMapper extends BaseMapper { 9 | } 10 | -------------------------------------------------------------------------------- /lite-monitor-server/src/main/java/com/example/config/ReportConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.example.config; 2 | 3 | /** 4 | *@author : Doge2077 22:05 2024/12/31 5 | */ 6 | public class ReportConfiguration { 7 | public static final class DefaultConfig { 8 | Double cpuUsage = 0.8; 9 | Double cacheUsage = 0.8; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lite-monitor-server/src/main/java/com/example/mapper/ClientSshMapper.java: -------------------------------------------------------------------------------- 1 | package com.example.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.example.entity.dto.ClientSsh; 5 | import org.apache.ibatis.annotations.Mapper; 6 | 7 | @Mapper 8 | public interface ClientSshMapper extends BaseMapper { 9 | } 10 | -------------------------------------------------------------------------------- /lite-monitor-client/src/test/java/org/example/LiteMonitorClientApplicationTests.java: -------------------------------------------------------------------------------- 1 | package org.example; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class LiteMonitorClientApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /lite-monitor-server/src/main/java/com/example/mapper/ClientDetailMapper.java: -------------------------------------------------------------------------------- 1 | package com.example.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.example.entity.dto.ClientDetail; 5 | import org.apache.ibatis.annotations.Mapper; 6 | 7 | @Mapper 8 | public interface ClientDetailMapper extends BaseMapper { 9 | } 10 | -------------------------------------------------------------------------------- /lite-monitor-server/src/main/java/com/example/entity/vo/response/ClientSimpleVO.java: -------------------------------------------------------------------------------- 1 | package com.example.entity.vo.response; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class ClientSimpleVO { 7 | int clientId; 8 | String clientName; 9 | String location; 10 | String osName; 11 | String osVersion; 12 | String clientAddress; 13 | } 14 | -------------------------------------------------------------------------------- /lite-monitor-client/src/main/java/org/example/entity/ConnectionConfig.java: -------------------------------------------------------------------------------- 1 | package org.example.entity; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | 6 | /** 7 | * @author Doge2077 8 | */ 9 | @Data 10 | @AllArgsConstructor 11 | public class ConnectionConfig { 12 | String serverAddress; 13 | String token; 14 | Integer cron; 15 | } 16 | -------------------------------------------------------------------------------- /lite-monitor-server/src/main/java/com/example/service/ReportService.java: -------------------------------------------------------------------------------- 1 | package com.example.service; 2 | 3 | import com.example.entity.dto.Client; 4 | import com.example.entity.vo.request.RuntimeDetailVO; 5 | 6 | /** 7 | * @author : Doge2077 22:18 2025/1/5 8 | */ 9 | public interface ReportService { 10 | void autoReport(Client client, RuntimeDetailVO runtime); 11 | } 12 | -------------------------------------------------------------------------------- /lite-monitor-server/src/main/java/com/example/entity/vo/request/ChangePasswordVO.java: -------------------------------------------------------------------------------- 1 | package com.example.entity.vo.request; 2 | 3 | import lombok.Data; 4 | import org.hibernate.validator.constraints.Length; 5 | 6 | @Data 7 | public class ChangePasswordVO { 8 | @Length(min = 6, max = 20) 9 | String password; 10 | @Length(min = 6, max = 20) 11 | String new_password; 12 | } 13 | -------------------------------------------------------------------------------- /lite-monitor-server/src/main/java/com/example/entity/vo/response/AuthorizeVO.java: -------------------------------------------------------------------------------- 1 | package com.example.entity.vo.response; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.Date; 6 | 7 | /** 8 | * 登录验证成功的用户信息响应 9 | */ 10 | @Data 11 | public class AuthorizeVO { 12 | String username; 13 | String email; 14 | String role; 15 | String token; 16 | Date expire; 17 | } 18 | -------------------------------------------------------------------------------- /lite-monitor-web/lite-monitor-web.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /lite-monitor-server/src/main/java/com/example/entity/vo/request/ModifyEmailVO.java: -------------------------------------------------------------------------------- 1 | package com.example.entity.vo.request; 2 | 3 | import jakarta.validation.constraints.Email; 4 | import lombok.Data; 5 | import org.hibernate.validator.constraints.Length; 6 | 7 | @Data 8 | public class ModifyEmailVO { 9 | @Email 10 | String email; 11 | @Length(max = 6, min = 6) 12 | String code; 13 | } 14 | -------------------------------------------------------------------------------- /lite-monitor-server/src/main/java/com/example/entity/vo/request/RenameClientVO.java: -------------------------------------------------------------------------------- 1 | package com.example.entity.vo.request; 2 | 3 | import jakarta.validation.constraints.NotNull; 4 | import lombok.Data; 5 | import org.hibernate.validator.constraints.Length; 6 | 7 | @Data 8 | public class RenameClientVO { 9 | @NotNull 10 | Integer clientId; 11 | @Length(min = 1, max = 20) 12 | String clientName; 13 | } 14 | -------------------------------------------------------------------------------- /lite-monitor-web/src/nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | server_name monitorweb; 4 | 5 | location / { 6 | root /home/lite-monitor/frontend; 7 | index /home/lite-monitor/frontend/index.html; 8 | try_files $uri $uri/ /index.html; 9 | } 10 | 11 | location /api { 12 | proxy_pass http://monitor-server:8010/api; 13 | } 14 | 15 | } -------------------------------------------------------------------------------- /lite-monitor-server/src/main/java/com/example/entity/vo/response/RuntimeHistoryVO.java: -------------------------------------------------------------------------------- 1 | package com.example.entity.vo.response; 2 | 3 | import com.alibaba.fastjson2.JSONObject; 4 | import lombok.Data; 5 | 6 | import java.util.LinkedList; 7 | import java.util.List; 8 | 9 | @Data 10 | public class RuntimeHistoryVO { 11 | Double diskMemory; 12 | Double memoryUsage; 13 | List list = new LinkedList<>(); 14 | } 15 | -------------------------------------------------------------------------------- /lite-monitor-client/src/main/java/org/example/MonitorClientApplication.java: -------------------------------------------------------------------------------- 1 | package org.example; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class MonitorClientApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(MonitorClientApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /lite-monitor-server/src/main/java/com/example/MonitorServerApplication.java: -------------------------------------------------------------------------------- 1 | package com.example; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class MonitorServerApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(MonitorServerApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /lite-monitor-web/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | .DS_Store 12 | dist 13 | dist-ssr 14 | coverage 15 | *.local 16 | 17 | /cypress/videos/ 18 | /cypress/screenshots/ 19 | 20 | # Editor directories and files 21 | .vscode/* 22 | !.vscode/extensions.json 23 | .idea 24 | *.suo 25 | *.ntvs* 26 | *.njsproj 27 | *.sln 28 | *.sw? 29 | -------------------------------------------------------------------------------- /lite-monitor-server/src/main/java/com/example/entity/vo/request/ConfirmResetVO.java: -------------------------------------------------------------------------------- 1 | package com.example.entity.vo.request; 2 | 3 | import jakarta.validation.constraints.Email; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | import org.hibernate.validator.constraints.Length; 7 | 8 | @Data 9 | @AllArgsConstructor 10 | public class ConfirmResetVO { 11 | @Email 12 | String email; 13 | @Length(max = 6, min = 6) 14 | String code; 15 | } 16 | -------------------------------------------------------------------------------- /lite-monitor-server/src/main/java/com/example/entity/vo/request/RenameNodeVO.java: -------------------------------------------------------------------------------- 1 | package com.example.entity.vo.request; 2 | 3 | import jakarta.validation.constraints.Pattern; 4 | import lombok.Data; 5 | import org.hibernate.validator.constraints.Length; 6 | 7 | @Data 8 | public class RenameNodeVO { 9 | Integer clientId; 10 | @Length(min = 1, max = 20) 11 | String node; 12 | @Pattern(regexp = "(cn|hk|jp|us|sg|kr|de|tw)") 13 | String location; 14 | } 15 | -------------------------------------------------------------------------------- /lite-monitor-web/src/store/index.js: -------------------------------------------------------------------------------- 1 | import {defineStore} from "pinia"; 2 | 3 | export const useStore = defineStore('general', { 4 | state: () => { 5 | return { 6 | user: { 7 | role: '', 8 | username: '', 9 | email: '' 10 | } 11 | } 12 | }, 13 | getters: { 14 | isAdmin() { 15 | return this.user.role === 'admin' 16 | } 17 | }, 18 | persist: true 19 | }) 20 | -------------------------------------------------------------------------------- /lite-monitor-server/src/main/java/com/example/entity/vo/request/EmailResetVO.java: -------------------------------------------------------------------------------- 1 | package com.example.entity.vo.request; 2 | 3 | import jakarta.validation.constraints.Email; 4 | import lombok.Data; 5 | import org.hibernate.validator.constraints.Length; 6 | 7 | /** 8 | * 密码重置表单实体 9 | */ 10 | @Data 11 | public class EmailResetVO { 12 | @Email 13 | String email; 14 | @Length(max = 6, min = 6) 15 | String code; 16 | @Length(min = 6, max = 20) 17 | String password; 18 | } 19 | -------------------------------------------------------------------------------- /lite-monitor-server/src/main/java/com/example/entity/dto/ClientSsh.java: -------------------------------------------------------------------------------- 1 | package com.example.entity.dto; 2 | 3 | import com.baomidou.mybatisplus.annotation.TableId; 4 | import com.baomidou.mybatisplus.annotation.TableName; 5 | import com.example.entity.BaseData; 6 | import lombok.Data; 7 | 8 | @Data 9 | @TableName("db_client_ssh") 10 | public class ClientSsh implements BaseData { 11 | @TableId 12 | Integer clientId; 13 | Integer port; 14 | String username; 15 | String password; 16 | } 17 | -------------------------------------------------------------------------------- /lite-monitor-server/src/main/java/com/example/config/WebSocketConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.example.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.web.socket.server.standard.ServerEndpointExporter; 6 | 7 | @Configuration 8 | public class WebSocketConfiguration { 9 | @Bean 10 | public ServerEndpointExporter serverEndpointExporter() { 11 | return new ServerEndpointExporter(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lite-monitor-web/src/App.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 24 | 25 | 30 | -------------------------------------------------------------------------------- /lite-monitor-server/src/main/java/com/example/entity/vo/request/SshConnectionVO.java: -------------------------------------------------------------------------------- 1 | package com.example.entity.vo.request; 2 | 3 | import jakarta.validation.constraints.NotNull; 4 | import lombok.Data; 5 | import lombok.NonNull; 6 | import org.hibernate.validator.constraints.Length; 7 | 8 | @Data 9 | public class SshConnectionVO { 10 | @NonNull 11 | Integer clientId; 12 | @NonNull 13 | Integer port; 14 | @NonNull 15 | @Length(min = 1) 16 | String username; 17 | @NotNull 18 | @Length(min = 1) 19 | String password; 20 | 21 | } 22 | -------------------------------------------------------------------------------- /lite-monitor-server/src/test/java/com/example/MyProjectBackendApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.example; 2 | 3 | import jakarta.annotation.Resource; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.security.crypto.password.PasswordEncoder; 7 | 8 | @SpringBootTest 9 | class MyProjectBackendApplicationTests { 10 | 11 | @Resource 12 | PasswordEncoder passwordEncoder; 13 | 14 | @Test 15 | void contextLoads() { 16 | System.out.println(this.passwordEncoder.encode("123456")); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lite-monitor-server/src/main/java/com/example/config/RabbitConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.example.config; 2 | 3 | import org.springframework.amqp.core.Queue; 4 | import org.springframework.amqp.core.QueueBuilder; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | 8 | /** 9 | * RabbitMQ消息队列配置 10 | */ 11 | @Configuration 12 | public class RabbitConfiguration { 13 | @Bean("mailQueue") 14 | public Queue queue(){ 15 | return QueueBuilder 16 | .durable("mail") 17 | .build(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lite-monitor-server/src/main/java/com/example/entity/vo/request/CreateSubAccountVO.java: -------------------------------------------------------------------------------- 1 | package com.example.entity.vo.request; 2 | 3 | import jakarta.validation.constraints.Email; 4 | import jakarta.validation.constraints.Size; 5 | import lombok.Data; 6 | import org.hibernate.validator.constraints.Length; 7 | 8 | import java.util.List; 9 | 10 | @Data 11 | public class CreateSubAccountVO { 12 | @Length(min = 1, max = 10) 13 | String username; 14 | @Email 15 | String email; 16 | @Length(min = 6, max = 20) 17 | String password; 18 | @Size(min = 1) 19 | List clients; 20 | } 21 | -------------------------------------------------------------------------------- /lite-monitor-server/src/main/java/com/example/entity/vo/response/ClientDetailsVO.java: -------------------------------------------------------------------------------- 1 | package com.example.entity.vo.response; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class ClientDetailsVO { 7 | Integer clientId; 8 | String clientName; 9 | Boolean online; 10 | String node; 11 | String location; 12 | String clientAddress; 13 | String cpuName; 14 | String osName; 15 | String osVersion; 16 | Double osMemory; 17 | Integer cpuCores; 18 | Double diskMemory; 19 | // 报警内存负载阈值 20 | Double reportMemory; 21 | // 报警Cpu负载阈值 22 | Double reportCpuUsage; 23 | } 24 | -------------------------------------------------------------------------------- /lite-monitor-client/src/main/java/org/example/entity/Response.java: -------------------------------------------------------------------------------- 1 | package org.example.entity; 2 | 3 | import com.alibaba.fastjson2.JSONObject; 4 | 5 | public record Response (int id, int code, Object data, String msg) { 6 | public Boolean success() { 7 | return code == 200; 8 | } 9 | 10 | public JSONObject asJson() { 11 | return JSONObject.from(data); 12 | } 13 | 14 | public String asString() { 15 | return data.toString(); 16 | } 17 | 18 | public static Response errorResponse(Exception e) { 19 | return new Response(0, 500, null, e.getMessage()); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lite-monitor-client/src/main/java/org/example/entity/RuntimeDetail.java: -------------------------------------------------------------------------------- 1 | package org.example.entity; 2 | 3 | import lombok.Data; 4 | import lombok.experimental.Accessors; 5 | 6 | @Data 7 | @Accessors(chain = true) 8 | public class RuntimeDetail { 9 | // 客户端运行时信息 10 | // 时间戳 11 | Long timestamp; 12 | // cpu 占用率 13 | Double cpuUsage; 14 | // 内存占用率 15 | Double memoryUsage; 16 | // 磁盘占用 17 | Double diskUsage; 18 | // 网络上行 19 | Double networkUpload; 20 | // 网络下行 21 | Double networkDownload; 22 | // 磁盘读取 23 | Double diskRead; 24 | // 磁盘写入 25 | Double diskWrite; 26 | } 27 | -------------------------------------------------------------------------------- /lite-monitor-server/src/main/java/com/example/entity/dto/Client.java: -------------------------------------------------------------------------------- 1 | package com.example.entity.dto; 2 | 3 | import com.baomidou.mybatisplus.annotation.TableId; 4 | import com.baomidou.mybatisplus.annotation.TableName; 5 | import com.example.entity.BaseData; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Data; 8 | 9 | import java.util.Date; 10 | 11 | @Data 12 | @TableName("db_client") 13 | @AllArgsConstructor 14 | public class Client implements BaseData { 15 | @TableId 16 | Integer clientId; 17 | String clientName; 18 | String clientToken; 19 | Date registerTime; 20 | String node; 21 | String location; 22 | } 23 | -------------------------------------------------------------------------------- /lite-monitor-client/src/main/java/org/example/entity/ClientDetail.java: -------------------------------------------------------------------------------- 1 | package org.example.entity; 2 | 3 | import lombok.Data; 4 | import lombok.experimental.Accessors; 5 | 6 | @Data 7 | @Accessors(chain = true) 8 | public class ClientDetail { 9 | // 客户端系统信息 10 | // 客户端 clientAddress 地址 11 | String clientAddress; 12 | // 系统架构 13 | String osArch; 14 | // 操作系统 15 | String osName; 16 | // 版本号 17 | String osVersion; 18 | // 操作系统位数 19 | Integer osBit; 20 | // cpu 名称 21 | String cpuName; 22 | // cpu 核心数 23 | Integer cpuCores; 24 | // 内存容量 25 | Double osMemory; 26 | // 磁盘容量 27 | Double diskMemory; 28 | } 29 | -------------------------------------------------------------------------------- /lite-monitor-web/src/component/TableItem.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 13 | 14 | -------------------------------------------------------------------------------- /lite-monitor-client/src/main/java/org/example/utils/CronUtils.java: -------------------------------------------------------------------------------- 1 | package org.example.utils; 2 | 3 | /** 4 | * @author Doge2077 5 | */ 6 | public class CronUtils { 7 | 8 | /** 9 | * 生成对应的 CRON 表达式。 10 | * 11 | * @param seconds 间隔的秒数 (0-59) 12 | * @return 对应的 CRON 表达式字符串 13 | * @throws IllegalArgumentException 如果秒数不在有效范围内,抛出异常 14 | */ 15 | public static String generateCron(Integer seconds) { 16 | if (seconds == null || seconds < 0 || seconds > 59) { 17 | throw new IllegalArgumentException("时间非法"); 18 | } 19 | // 返回 CRON 表达式,每隔 seconds 秒执行一次 20 | return String.format("*/%d * * * * ?", seconds); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lite-monitor-web/src/main.js: -------------------------------------------------------------------------------- 1 | import {createApp} from 'vue' 2 | import App from './App.vue' 3 | import router from './router' 4 | import axios from "axios" 5 | import '@/assets/css/elemenet.less' 6 | import 'flag-icons/css/flag-icons.min.css' 7 | import 'element-plus/theme-chalk/dark/css-vars.css' 8 | import {createPinia} from "pinia"; 9 | import piniaPluginPersistedstate from 'pinia-plugin-persistedstate' 10 | 11 | // axios.defaults.baseURL = '/api' 12 | axios.defaults.baseURL = 'http://127.0.0.1:8080/api' 13 | 14 | 15 | const app = createApp(App) 16 | const pinia = createPinia() 17 | app.use(pinia) 18 | pinia.use(piniaPluginPersistedstate) 19 | app.use(router) 20 | 21 | app.mount('#app') 22 | -------------------------------------------------------------------------------- /lite-monitor-server/src/main/java/com/example/config/WebConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.example.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 6 | import org.springframework.security.crypto.password.PasswordEncoder; 7 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 8 | 9 | /** 10 | * 一般Web服务相关配置 11 | */ 12 | @Configuration 13 | public class WebConfiguration implements WebMvcConfigurer { 14 | 15 | @Bean 16 | public PasswordEncoder passwordEncoder(){ 17 | return new BCryptPasswordEncoder(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lite-monitor-web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Vite App 11 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /lite-monitor-web/vite.config.js: -------------------------------------------------------------------------------- 1 | import {fileURLToPath, URL} from 'node:url' 2 | 3 | import {defineConfig} from 'vite' 4 | import vue from '@vitejs/plugin-vue' 5 | import AutoImport from 'unplugin-auto-import/vite' 6 | import Components from 'unplugin-vue-components/vite' 7 | import {ElementPlusResolver} from 'unplugin-vue-components/resolvers' 8 | 9 | // https://vitejs.dev/config/ 10 | export default defineConfig({ 11 | plugins: [ 12 | vue(), 13 | AutoImport({ 14 | resolvers: [ElementPlusResolver()], 15 | }), 16 | Components({ 17 | resolvers: [ElementPlusResolver()], 18 | }), 19 | ], 20 | resolve: { 21 | alias: { 22 | '@': fileURLToPath(new URL('./src', import.meta.url)) 23 | } 24 | } 25 | }) 26 | -------------------------------------------------------------------------------- /lite-monitor-server/src/main/java/com/example/entity/vo/request/RuntimeDetailVO.java: -------------------------------------------------------------------------------- 1 | package com.example.entity.vo.request; 2 | 3 | import lombok.Data; 4 | import lombok.NonNull; 5 | 6 | @Data 7 | public class RuntimeDetailVO { 8 | // 客户端运行时信息 9 | // 时间戳 10 | @NonNull 11 | Long timestamp; 12 | // cpu 占用率 13 | @NonNull 14 | Double cpuUsage; 15 | // 内存占用率 16 | @NonNull 17 | Double memoryUsage; 18 | // 磁盘占用 19 | @NonNull 20 | Double diskUsage; 21 | // 网络上行 22 | @NonNull 23 | Double networkUpload; 24 | // 网络下行 25 | @NonNull 26 | Double networkDownload; 27 | // 磁盘读取 28 | @NonNull 29 | Double diskRead; 30 | // 磁盘写入 31 | @NonNull 32 | Double diskWrite; 33 | } 34 | -------------------------------------------------------------------------------- /lite-monitor-web/README.md: -------------------------------------------------------------------------------- 1 | # my-project-frontend 2 | 3 | This template should help get you started developing with Vue 3 in Vite. 4 | 5 | ## Recommended IDE Setup 6 | 7 | [VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin). 8 | 9 | ## Customize configuration 10 | 11 | See [Vite Configuration Reference](https://vitejs.dev/config/). 12 | 13 | ## Project Setup 14 | 15 | ```sh 16 | npm install 17 | ``` 18 | 19 | ### Compile and Hot-Reload for Development 20 | 21 | ```sh 22 | npm run dev 23 | ``` 24 | 25 | ### Compile and Minify for Production 26 | 27 | ```sh 28 | npm run build 29 | ``` 30 | -------------------------------------------------------------------------------- /lite-monitor-server/src/main/java/com/example/entity/vo/request/ClientDetailVO.java: -------------------------------------------------------------------------------- 1 | package com.example.entity.vo.request; 2 | 3 | import lombok.Data; 4 | import lombok.NonNull; 5 | 6 | @Data 7 | public class ClientDetailVO { 8 | 9 | // 客户端系统信息 10 | // 客户端 clientAddress 地址 11 | @NonNull 12 | String clientAddress; 13 | // 系统架构 14 | @NonNull 15 | String osArch; 16 | // 操作系统 17 | @NonNull 18 | String osName; 19 | // 版本号 20 | @NonNull 21 | String osVersion; 22 | // 操作系统位数 23 | @NonNull 24 | Integer osBit; 25 | // cpu 名称 26 | @NonNull 27 | String cpuName; 28 | // cpu 核心数 29 | @NonNull 30 | Integer cpuCores; 31 | // 内存容量 32 | @NonNull 33 | Double osMemory; 34 | // 磁盘容量 35 | @NonNull 36 | Double diskMemory; 37 | } 38 | -------------------------------------------------------------------------------- /lite-monitor-server/src/main/java/com/example/entity/vo/response/ClientPreviewVO.java: -------------------------------------------------------------------------------- 1 | package com.example.entity.vo.response; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class ClientPreviewVO { 7 | Integer clientId; 8 | // 运行状态 9 | Boolean online; 10 | // 主机名 11 | String clientName; 12 | // 客户端 clientAddress 地址 13 | String clientAddress; 14 | // 操作系统 15 | String osName; 16 | // 版本号 17 | String osVersion; 18 | // cpu 名称 19 | String cpuName; 20 | // cpu 核心数 21 | Integer cpuCores; 22 | // cpu 占用率 23 | Double cpuUsage; 24 | // 内存占用率 25 | Double memoryUsage; 26 | // 内存容量 27 | Double osMemory; 28 | // 网络上行 29 | Double networkUpload; 30 | // 网络下行 31 | Double networkDownload; 32 | // 地区 33 | String location; 34 | } 35 | -------------------------------------------------------------------------------- /lite-monitor-server/src/main/java/com/example/entity/dto/ClientDetail.java: -------------------------------------------------------------------------------- 1 | package com.example.entity.dto; 2 | 3 | import com.baomidou.mybatisplus.annotation.TableId; 4 | import com.baomidou.mybatisplus.annotation.TableName; 5 | import lombok.Data; 6 | 7 | @Data 8 | @TableName("db_client_detail") 9 | public class ClientDetail { 10 | @TableId 11 | Integer clientId; 12 | // 客户端系统信息 13 | // 客户端 clientAddress 地址 14 | String clientAddress; 15 | // 系统架构 16 | String osArch; 17 | // 操作系统 18 | String osName; 19 | // 版本号 20 | String osVersion; 21 | // 操作系统位数 22 | Integer osBit; 23 | // cpu 名称 24 | String cpuName; 25 | // cpu 核心数 26 | Integer cpuCores; 27 | // 内存容量 28 | Double osMemory; 29 | // 磁盘容量 30 | Double diskMemory; 31 | // 报警内存负载阈值 32 | Double reportMemory; 33 | // 报警Cpu负载阈值 34 | Double reportCpuUsage; 35 | } 36 | -------------------------------------------------------------------------------- /lite-monitor-web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lite-monitor-web", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "vite --host 127.0.0.1 --port 3000", 7 | "build": "vite build", 8 | "preview": "vite preview" 9 | }, 10 | "dependencies": { 11 | "@element-plus/icons-vue": "^2.1.0", 12 | "@vueuse/core": "^10.3.0", 13 | "axios": "^1.4.0", 14 | "echarts": "^5.5.0", 15 | "element-plus": "^2.3.9", 16 | "flag-icons": "^7.2.1", 17 | "pinia": "^2.1.7", 18 | "pinia-plugin-persistedstate": "^3.2.1", 19 | "vue": "^3.3.4", 20 | "vue-router": "^4.2.4", 21 | "xterm": "^5.3.0", 22 | "xterm-addon-attach": "^0.9.0" 23 | }, 24 | "devDependencies": { 25 | "@vitejs/plugin-vue": "^4.2.3", 26 | "less": "^4.2.0", 27 | "unplugin-auto-import": "^0.15.2", 28 | "unplugin-vue-components": "^0.24.1", 29 | "vite": "^4.4.6" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lite-monitor-client/src/main/java/org/example/task/MonitorJobBean.java: -------------------------------------------------------------------------------- 1 | package org.example.task; 2 | 3 | import jakarta.annotation.Resource; 4 | import org.example.entity.RuntimeDetail; 5 | import org.example.utils.MonitorUtils; 6 | import org.example.utils.NetUtils; 7 | import org.quartz.JobExecutionContext; 8 | import org.quartz.JobExecutionException; 9 | import org.springframework.scheduling.quartz.QuartzJobBean; 10 | import org.springframework.stereotype.Component; 11 | 12 | @Component 13 | public class MonitorJobBean extends QuartzJobBean { 14 | 15 | @Resource 16 | MonitorUtils monitorUtils; 17 | 18 | @Resource 19 | NetUtils netUtils; 20 | 21 | @Override 22 | protected void executeInternal(JobExecutionContext context) throws JobExecutionException { 23 | RuntimeDetail runtimeDetail = this.monitorUtils.getRuntimeDetail(); 24 | this.netUtils.updateRuntimeDetail(runtimeDetail); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lite-monitor-server/src/main/java/com/example/entity/dto/RuntimeDetail.java: -------------------------------------------------------------------------------- 1 | package com.example.entity.dto; 2 | 3 | import com.influxdb.annotations.Column; 4 | import com.influxdb.annotations.Measurement; 5 | import lombok.Data; 6 | 7 | import java.time.Instant; 8 | 9 | @Data 10 | @Measurement(name = "runtime_detail") 11 | public class RuntimeDetail { 12 | // 客户端运行时信息 13 | @Column(tag = true) 14 | Integer id; 15 | // 时间戳 16 | @Column(timestamp = true) 17 | Instant timestamp; 18 | // cpu 占用率 19 | @Column 20 | Double cpuUsage; 21 | // 内存占用率 22 | @Column 23 | Double memoryUsage; 24 | // 磁盘占用 25 | @Column 26 | Double diskUsage; 27 | // 网络上行 28 | @Column 29 | Double networkUpload; 30 | // 网络下行 31 | @Column 32 | Double networkDownload; 33 | // 磁盘读取 34 | @Column 35 | Double diskRead; 36 | // 磁盘写入 37 | @Column 38 | Double diskWrite; 39 | } 40 | -------------------------------------------------------------------------------- /lite-monitor-server/src/main/java/com/example/entity/vo/request/ReportClientVO.java: -------------------------------------------------------------------------------- 1 | package com.example.entity.vo.request; 2 | 3 | import jakarta.validation.constraints.DecimalMax; 4 | import jakarta.validation.constraints.DecimalMin; 5 | import jakarta.validation.constraints.NotNull; 6 | import lombok.Data; 7 | import org.hibernate.validator.constraints.Length; 8 | 9 | /** 10 | * @author : Doge2077 23:52 2025/1/8 11 | */ 12 | @Data 13 | public class ReportClientVO { 14 | @NotNull 15 | Integer clientId; 16 | @DecimalMin(value = "0.1", inclusive = true, message = "内存负载报警阈值不能低于 0.1") 17 | @DecimalMax(value = "0.99", inclusive = true, message = "内存负载报警阈值不能高于 0.99") 18 | private Double reportMemory; 19 | 20 | @DecimalMin(value = "0.1", inclusive = true, message = "cpu负载报警阈值不能低于 0.1") 21 | @DecimalMax(value = "0.99", inclusive = true, message = "cpu负载报警阈值不能高于 0.99") 22 | private Double reportCpuUsage; 23 | } 24 | -------------------------------------------------------------------------------- /lite-monitor-server/src/main/java/com/example/controller/exception/ValidationController.java: -------------------------------------------------------------------------------- 1 | package com.example.controller.exception; 2 | 3 | import com.example.entity.RestBean; 4 | import jakarta.validation.ValidationException; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.web.bind.annotation.ExceptionHandler; 7 | import org.springframework.web.bind.annotation.RestControllerAdvice; 8 | 9 | /** 10 | * 用于接口参数校验处理的控制器 11 | */ 12 | @Slf4j 13 | @RestControllerAdvice 14 | public class ValidationController { 15 | 16 | /** 17 | * 与SpringBoot保持一致,校验不通过打印警告信息,而不是直接抛出异常 18 | * @param exception 验证异常 19 | * @return 校验结果 20 | */ 21 | @ExceptionHandler(ValidationException.class) 22 | public RestBean validateError(ValidationException exception) { 23 | log.warn("Resolved [{}: {}]", exception.getClass().getName(), exception.getMessage()); 24 | return RestBean.failure(400, "请求参数有误"); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lite-monitor-server/src/main/java/com/example/listener/ReportQueueListener.java: -------------------------------------------------------------------------------- 1 | package com.example.listener; 2 | 3 | import jakarta.annotation.Resource; 4 | import org.springframework.amqp.rabbit.annotation.RabbitHandler; 5 | import org.springframework.amqp.rabbit.annotation.RabbitListener; 6 | import org.springframework.beans.factory.annotation.Value; 7 | import org.springframework.mail.SimpleMailMessage; 8 | import org.springframework.mail.javamail.JavaMailSender; 9 | import org.springframework.stereotype.Component; 10 | 11 | /** 12 | * @author : Doge2077 16:37 2025/1/9 13 | */ 14 | @Component 15 | @RabbitListener(queues = "report") 16 | public class ReportQueueListener { 17 | 18 | @Resource 19 | JavaMailSender javaMailSender; 20 | 21 | @Value("${spring.mail.username}") 22 | String sander; 23 | 24 | /** 25 | * 处理邮件发送 26 | */ 27 | @RabbitHandler 28 | public void sendMailMessage(SimpleMailMessage mailMessage) { 29 | mailMessage.setFrom(sander); 30 | javaMailSender.send(mailMessage); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lite-monitor-server/src/main/java/com/example/entity/dto/Account.java: -------------------------------------------------------------------------------- 1 | package com.example.entity.dto; 2 | 3 | import com.alibaba.fastjson2.JSONArray; 4 | import com.baomidou.mybatisplus.annotation.IdType; 5 | import com.baomidou.mybatisplus.annotation.TableId; 6 | import com.baomidou.mybatisplus.annotation.TableName; 7 | import com.example.entity.BaseData; 8 | import lombok.AllArgsConstructor; 9 | import lombok.Data; 10 | 11 | import java.util.Collections; 12 | import java.util.Date; 13 | import java.util.List; 14 | 15 | /** 16 | * 数据库中的用户信息 17 | */ 18 | @Data 19 | @TableName("db_account") 20 | @AllArgsConstructor 21 | public class Account implements BaseData { 22 | @TableId(type = IdType.AUTO) 23 | Integer id; 24 | String username; 25 | String password; 26 | String email; 27 | String role; 28 | Date registerTime; 29 | String clients; 30 | 31 | public List getClientList() { 32 | if (clients == null) return Collections.emptyList(); 33 | return JSONArray.parse(clients).toList(Integer.class); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lite-monitor-web/src/assets/css/elemenet.less: -------------------------------------------------------------------------------- 1 | .is-success{ 2 | .el-progress-bar__outer { 3 | background-color: #18cb1822 !important; 4 | } 5 | 6 | .el-progress-bar__inner { 7 | background-color: #18cb18 !important; 8 | } 9 | 10 | .el-progress-circle__track { 11 | stroke: #18cb1822 !important; 12 | } 13 | 14 | .el-progress-circle__path { 15 | stroke: #18cb18 !important; 16 | } 17 | 18 | } 19 | 20 | .is-warning{ 21 | .el-progress-bar__outer { 22 | background-color: #ffa04622 !important; 23 | } 24 | 25 | .el-progress-bar__inner { 26 | background-color: #ffa046 !important; 27 | } 28 | 29 | .el-progress-circle__track { 30 | stroke: #ffa04622 !important; 31 | } 32 | 33 | .el-progress-circle__path { 34 | stroke: #ffa046 !important; 35 | } 36 | 37 | } 38 | 39 | .is-exception { 40 | .el-progress-bar__outer { 41 | background-color: #ef4e4e22 !important; 42 | } 43 | 44 | .el-progress-bar__inner { 45 | background-color: #ef4e4e !important; 46 | } 47 | 48 | .el-progress-circle__track { 49 | stroke: #ef4e4e22 !important; 50 | } 51 | 52 | .el-progress-circle__path { 53 | stroke: #ef4e4e !important; 54 | } 55 | 56 | } -------------------------------------------------------------------------------- /lite-monitor-server/src/main/resources/application-dev.yml: -------------------------------------------------------------------------------- 1 | # 开发环境配置 2 | springdoc: 3 | paths-to-match: /api/** 4 | swagger-ui: 5 | operations-sorter: alpha 6 | spring: 7 | mail: 8 | host: smtp.163.com 9 | username: chalys2021@163.com 10 | password: QLWTZHQLHAGBQQZL 11 | rabbitmq: 12 | addresses: 192.168.56.2 13 | username: admin 14 | password: admin 15 | virtual-host: / 16 | influx: 17 | url: http://192.168.56.2:8086/ 18 | user: admin 19 | password: 023017lys 20 | datasource: 21 | url: jdbc:mysql://192.168.75.217:3306/lite-monitor-db 22 | username: root 23 | password: monitormysqlroot 24 | driver-class-name: com.mysql.cj.jdbc.Driver 25 | security: 26 | jwt: 27 | key: 'abcdefghijklmn' 28 | expire: 72 29 | limit: 30 | base: 10 31 | upgrade: 300 32 | frequency: 30 33 | filter: 34 | order: -100 35 | web: 36 | verify: 37 | mail-limit: 60 38 | flow: 39 | period: 10 40 | limit: 1000 41 | block: 30 42 | cors: 43 | origin: '*' 44 | credentials: false 45 | methods: '*' 46 | data: 47 | redis: 48 | host: 192.168.56.2 49 | port: 6379 50 | database: 0 -------------------------------------------------------------------------------- /lite-monitor-client/src/main/java/org/example/config/QuartzConfiguration.java: -------------------------------------------------------------------------------- 1 | package org.example.config; 2 | 3 | import jakarta.annotation.Resource; 4 | import org.example.entity.ConnectionConfig; 5 | import org.example.task.MonitorJobBean; 6 | import org.example.utils.CronUtils; 7 | import org.quartz.*; 8 | import org.springframework.context.annotation.Configuration; 9 | 10 | @Configuration 11 | public class QuartzConfiguration { 12 | 13 | @Resource 14 | ServerConfiguration serverConfiguration; 15 | 16 | public JobDetail jobDetailFactoryBean() { 17 | return JobBuilder.newJob(MonitorJobBean.class) 18 | .withIdentity("monitor-runtime-task") 19 | .storeDurably().build(); 20 | } 21 | 22 | public Trigger cronTriggerFactoryBean() throws InterruptedException { 23 | ConnectionConfig configurationFromFile = serverConfiguration.getConfigurationFromFile(); 24 | CronScheduleBuilder cron = CronScheduleBuilder.cronSchedule(CronUtils.generateCron(configurationFromFile.getCron())); 25 | return TriggerBuilder.newTrigger() 26 | .withIdentity("monitor-runtime-trigger") 27 | .withSchedule(cron) 28 | .build(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lite-monitor-web/src/component/RegisterCard.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 24 | 25 | 47 | -------------------------------------------------------------------------------- /lite-monitor-server/src/main/resources/application-prod.yml: -------------------------------------------------------------------------------- 1 | #生产环境配置 2 | server: 3 | port: 8010 4 | springdoc: 5 | api-docs: 6 | enabled: false 7 | mybatis-plus: 8 | configuration: 9 | log-impl: org.apache.ibatis.logging.stdout.StdOutImpl 10 | spring: 11 | mail: 12 | host: smtp.163.com 13 | username: chalys2021@163.com 14 | password: QLWTZHQLHAGBQQZL 15 | rabbitmq: 16 | addresses: monitor-rabbitmq 17 | username: admin 18 | password: monitorrabbitmqadmin 19 | virtual-host: / 20 | influx: 21 | url: http://monitor-influxdb:8086/ 22 | user: admin 23 | password: monitorinfluxdbadmin 24 | datasource: 25 | url: jdbc:mysql://monitor-mysql:3306/lite-monitor-db 26 | username: root 27 | password: monitormysqlroot 28 | driver-class-name: com.mysql.cj.jdbc.Driver 29 | security: 30 | jwt: 31 | key: 'abcdefghijklmn' 32 | expire: 72 33 | limit: 34 | base: 10 35 | upgrade: 300 36 | frequency: 30 37 | filter: 38 | order: -100 39 | web: 40 | verify: 41 | mail-limit: 60 42 | flow: 43 | period: 5 44 | limit: 1000 45 | block: 30 46 | cors: 47 | origin: '*' 48 | credentials: false 49 | methods: '*' 50 | data: 51 | redis: 52 | host: monitor-redis 53 | port: 6379 54 | database: 0 -------------------------------------------------------------------------------- /lite-monitor-server/src/main/java/com/example/service/AccountService.java: -------------------------------------------------------------------------------- 1 | package com.example.service; 2 | 3 | import com.baomidou.mybatisplus.extension.service.IService; 4 | import com.example.entity.dto.Account; 5 | import com.example.entity.vo.request.ConfirmResetVO; 6 | import com.example.entity.vo.request.CreateSubAccountVO; 7 | import com.example.entity.vo.request.EmailResetVO; 8 | import com.example.entity.vo.request.ModifyEmailVO; 9 | import com.example.entity.vo.response.SubAccountVO; 10 | import org.springframework.security.core.userdetails.UserDetailsService; 11 | 12 | import java.util.List; 13 | 14 | public interface AccountService extends IService, UserDetailsService { 15 | Account findAccountByNameOrEmail(String text); 16 | String registerEmailVerifyCode(String type, String email, String address); 17 | String resetEmailAccountPassword(EmailResetVO info); 18 | String resetConfirm(ConfirmResetVO info); 19 | Boolean changePassword(int id, String oldPass, String newPass); 20 | String modifyEmail(int uid, ModifyEmailVO emailVO); 21 | void createSubAccount(CreateSubAccountVO createSubAccountVO); 22 | void deleteSubAccount(int uid); 23 | List listSubAccount(); 24 | /** 25 | * 根据 clientId 获取拥有该主机权限的管理员信息(包含admin) 26 | */ 27 | List getMailByClientId(int clientId); 28 | } 29 | -------------------------------------------------------------------------------- /lite-monitor-server/src/main/java/com/example/utils/Const.java: -------------------------------------------------------------------------------- 1 | package com.example.utils; 2 | 3 | /** 4 | * 一些常量字符串整合 5 | * @author Doge2077 6 | */ 7 | public final class Const { 8 | //JWT令牌 9 | public final static String JWT_BLACK_LIST = "jwt:blacklist:"; 10 | public final static String JWT_FREQUENCY = "jwt:frequency:"; 11 | //用户 12 | public final static String USER_BLACK_LIST = "user:blacklist:"; 13 | //请求频率限制 14 | public final static String FLOW_LIMIT_COUNTER = "flow:counter:"; 15 | public final static String FLOW_LIMIT_BLOCK = "flow:block:"; 16 | //邮件验证码 17 | public final static String VERIFY_EMAIL_LIMIT = "verify:email:limit:"; 18 | public final static String VERIFY_EMAIL_DATA = "verify:email:data:"; 19 | //过滤器优先级 20 | public final static int ORDER_FLOW_LIMIT = -101; 21 | public final static int ORDER_CORS = -102; 22 | //请求自定义属性 23 | public final static String ATTR_USER_ID = "userId"; 24 | public final static String ATTR_USER_ROLE = "userRole"; 25 | public final static String ATTR_CLIENT = "client"; 26 | //消息队列 27 | public final static String MQ_MAIL = "mail"; 28 | public final static String MQ_REPORT = "report"; 29 | //用户角色 30 | public final static String ROLE_ADMIN = "admin"; 31 | public final static String ROLE_NORMAL = "user"; 32 | 33 | // 报警频率限制 KEY 34 | public final static String REPORT_KEY = "report:client:break:"; 35 | } 36 | -------------------------------------------------------------------------------- /lite-monitor-web/src/component/Terminal.vue: -------------------------------------------------------------------------------- 1 | 54 | 55 | 58 | 59 | 62 | -------------------------------------------------------------------------------- /lite-monitor-client/src/main/java/org/example/aop/InitClientDetail.java: -------------------------------------------------------------------------------- 1 | package org.example.aop; 2 | 3 | import jakarta.annotation.Resource; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.example.config.QuartzConfiguration; 6 | import org.example.config.ServerConfiguration; 7 | import org.example.entity.ConnectionConfig; 8 | import org.example.utils.MonitorUtils; 9 | import org.example.utils.NetUtils; 10 | import org.quartz.Scheduler; 11 | import org.springframework.boot.ApplicationArguments; 12 | import org.springframework.boot.ApplicationRunner; 13 | import org.springframework.stereotype.Component; 14 | 15 | @Slf4j 16 | @Component 17 | public class InitClientDetail implements ApplicationRunner { 18 | 19 | @Resource 20 | Scheduler scheduler; 21 | 22 | @Resource 23 | QuartzConfiguration quartzConfiguration; 24 | 25 | @Resource 26 | ServerConfiguration serverConfiguration; 27 | 28 | @Resource 29 | NetUtils netUtils; 30 | 31 | @Resource 32 | MonitorUtils monitorUtils; 33 | 34 | @Override 35 | public void run(ApplicationArguments args) throws Exception { 36 | ConnectionConfig connectionConfig = this.serverConfiguration.connectionConfig(); 37 | if (connectionConfig != null) { 38 | this.netUtils.updateClientDetail(this.monitorUtils.getClientDetail()); 39 | // 创建定时任务 40 | this.scheduler.scheduleJob(this.quartzConfiguration.jobDetailFactoryBean(), 41 | this.quartzConfiguration.cronTriggerFactoryBean()); 42 | } else { 43 | log.error("初始化数据上报出错"); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /lite-monitor-server/src/main/java/com/example/service/ClientService.java: -------------------------------------------------------------------------------- 1 | package com.example.service; 2 | 3 | import com.baomidou.mybatisplus.extension.service.IService; 4 | import com.example.entity.dto.Client; 5 | import com.example.entity.vo.request.*; 6 | import com.example.entity.vo.response.*; 7 | 8 | import java.util.List; 9 | 10 | public interface ClientService extends IService { 11 | Boolean verifyAndRegister(String token); 12 | 13 | String getRegisterToken(); 14 | 15 | Client getClientById(int id); 16 | 17 | Client getClientByToken(String token); 18 | 19 | // 客户端数据上报 20 | Boolean updateClientDetail(ClientDetailVO clientDetailVO, Client client); 21 | 22 | // 客户端运行时数据上报 23 | Boolean updateRuntimeDetail(RuntimeDetailVO runtimeDetailVO, Client client); 24 | 25 | RuntimeDetailVO getRuntimeDetailByClientId(int clientId); 26 | 27 | // 列出所有主机 28 | List listClients(); 29 | 30 | List listSimpleList(); 31 | 32 | void renameClient(RenameClientVO renameClientVO); 33 | 34 | void renameNode(RenameNodeVO renameNodeVO); 35 | 36 | ClientDetailsVO clientDetails(int clientId); 37 | 38 | // 历史记录 39 | RuntimeHistoryVO clientRuntimeDetailsHistory(int clientId); 40 | 41 | // 实时数据监控 42 | RuntimeDetailVO clientRuntimeDetailsNow(int clientId); 43 | 44 | // 删除 Client 45 | void deleteClient(int clientId); 46 | 47 | // 保存 ssh 连接信息 48 | void saveClientSshConnection(SshConnectionVO sshConnectionVO); 49 | 50 | // 获取 ssh 连接信息 51 | SshSettingsVO sshSettings(int clientId); 52 | 53 | /** 54 | * 设置或更新负载阈值 55 | */ 56 | void updateReport(ReportClientVO reportClientVO); 57 | } 58 | -------------------------------------------------------------------------------- /lite-monitor-server/src/main/java/com/example/entity/RestBean.java: -------------------------------------------------------------------------------- 1 | package com.example.entity; 2 | 3 | import com.alibaba.fastjson2.JSONObject; 4 | import com.alibaba.fastjson2.JSONWriter; 5 | import org.slf4j.MDC; 6 | 7 | import java.util.Optional; 8 | 9 | /** 10 | * 响应实体类封装,Rest风格 11 | * @param code 状态码 12 | * @param data 响应数据 13 | * @param message 其他消息 14 | * @param 响应数据类型 15 | */ 16 | public record RestBean (long id, int code, T data, String message) { 17 | public static RestBean success(T data){ 18 | return new RestBean<>(requestId(), 200, data, "请求成功"); 19 | } 20 | 21 | public static RestBean success(){ 22 | return success(null); 23 | } 24 | 25 | public static RestBean forbidden(String message){ 26 | return failure(403, message); 27 | } 28 | 29 | public static RestBean unauthorized(String message){ 30 | return failure(401, message); 31 | } 32 | 33 | public static RestBean failure(int code, String message){ 34 | return new RestBean<>(requestId(), code, null, message); 35 | } 36 | 37 | public static RestBean noPermission() { 38 | return new RestBean<>(requestId(), 401, null, "权限不足,拒绝访问"); 39 | } 40 | 41 | /** 42 | * 快速将当前实体转换为JSON字符串格式 43 | * @return JSON字符串 44 | */ 45 | public String asJsonString() { 46 | return JSONObject.toJSONString(this, JSONWriter.Feature.WriteNulls); 47 | } 48 | 49 | /** 50 | * 获取当前请求ID方便快速定位错误 51 | * @return ID 52 | */ 53 | private static long requestId(){ 54 | String requestId = Optional.ofNullable(MDC.get("reqId")).orElse("0"); 55 | return Long.parseLong(requestId); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.9" 2 | services: 3 | monitor-mysql: 4 | container_name: monitor-mysql 5 | image: mysql:5 6 | environment: 7 | MYSQL_ROOT_HOST: '%' 8 | MYSQL_ROOT_PASSWORD: 'monitormysqlroot' 9 | MYSQL_DATABASE: 'lite-monitor-db' 10 | TZ: 'Asia/Shanghai' 11 | ports: 12 | - "3306:3306" 13 | volumes: 14 | - '/home/lite-monitor/server/mysql:/var/lib/mysql' 15 | - dbdata:/sdd 16 | monitor-influxdb: 17 | container_name: monitor-influxdb 18 | image: influxdb:latest 19 | environment: 20 | INFLUXDB_DB: 'lite-monitor' 21 | INFLUXDB_ADMIN_USER: 'admin' # 管理员用户名 22 | INFLUXDB_ADMIN_PASSWORD: 'monitorinfluxdbadmin' # 管理员密码 23 | ports: 24 | - "8086:8086" 25 | volumes: 26 | - '/home/lite-monitor/server/influxdb:/var/lib/influxdb' 27 | monitor-redis: 28 | container_name: monitor-redis 29 | image: redis:latest 30 | ports: 31 | - "6379:6379" 32 | monitor-rabbitmq: 33 | container_name: monitor-rabbitmq 34 | image: 'rabbitmq:3-management' 35 | hostname: 'rabbitmq' 36 | environment: 37 | RABBITMQ_DEFAULT_USER: 'admin' # 替换为你想要的用户名 38 | RABBITMQ_DEFAULT_PASS: 'monitorrabbitmqadmin' # 替换为你想要的密码 39 | RABBITMQ_DEFAULT_VHOST: '/' # 虚拟主机 40 | ports: 41 | - '5672:5672' # 应用程序连接端口 42 | - '15672:15672' # 管理界面访问端口 43 | volumes: 44 | - '/home/lite-monitor/server/rabbitmq:/var/lib/rabbitmq' 45 | monitor-server: 46 | container_name: monitor-server 47 | build: ./lite-monitor-server/ 48 | ports: 49 | - "8010:8010" 50 | restart: always 51 | monitor-frontend: 52 | container_name: monitor-frontend 53 | build: ./lite-monitor-web/ 54 | ports: 55 | - "80:80" 56 | volumes: 57 | dbdata: -------------------------------------------------------------------------------- /lite-monitor-server/src/main/resources/logback-spring.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 9 | 10 | 11 | 12 | ${CONSOLE_LOG_PATTERN} 13 | ${CONSOLE_LOG_CHARSET} 14 | 15 | 16 | 17 | 18 | 19 | ${FILE_LOG_PATTERN} 20 | ${FILE_LOG_CHARSET} 21 | 22 | 23 | log/%d{yyyy-MM-dd}-spring-%i.log 24 | true 25 | 7 26 | 10MB 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /lite-monitor-client/src/main/resources/logback-spring.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 9 | 10 | 11 | 12 | ${CONSOLE_LOG_PATTERN} 13 | ${CONSOLE_LOG_CHARSET} 14 | 15 | 16 | 17 | 18 | 19 | ${FILE_LOG_PATTERN} 20 | ${FILE_LOG_CHARSET} 21 | 22 | 23 | log/%d{yyyy-MM-dd}-lite-monitor-client-%i.log 24 | true 25 | 7 26 | 10MB 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /lite-monitor-server/src/main/java/com/example/listener/MailQueueListener.java: -------------------------------------------------------------------------------- 1 | package com.example.listener; 2 | 3 | import com.example.utils.MailUtils; 4 | import jakarta.annotation.Resource; 5 | import org.springframework.amqp.rabbit.annotation.RabbitHandler; 6 | import org.springframework.amqp.rabbit.annotation.RabbitListener; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.mail.SimpleMailMessage; 9 | import org.springframework.mail.javamail.JavaMailSender; 10 | import org.springframework.stereotype.Component; 11 | 12 | import java.util.Map; 13 | import java.util.Objects; 14 | 15 | /** 16 | * 用于处理邮件发送的消息队列监听器 17 | * @author Doge2077 18 | */ 19 | @Component 20 | @RabbitListener(queues = "mail") 21 | public class MailQueueListener { 22 | 23 | @Resource 24 | JavaMailSender sender; 25 | 26 | @Value("${spring.mail.username}") 27 | String username; 28 | 29 | /** 30 | * 处理邮件发送 31 | * 32 | * @param data 邮件信息 33 | */ 34 | @RabbitHandler 35 | public void sendMailMessage(Map data) { 36 | String email = data.get("email").toString(); 37 | Integer code = (Integer) data.get("code"); 38 | SimpleMailMessage message = switch (data.get("type").toString()) { 39 | case "reset" -> MailUtils.buildMailMessage("【Lite-Monitor】密码重置验证邮件", 40 | "尊敬的 Lite-Monitor 用户您好,您正在执行重置密码操作,验证码: " + code + ",有效时间3分钟,如非本人操作,如非本人操作,请登录平台修改密码,以防账号丢失。", 41 | email, username); 42 | case "modify" -> MailUtils.buildMailMessage("【Lite-Monitor】邮件修改验证邮件", 43 | "尊敬的 Lite-Monitor 用户您好,您正在绑定新的电子邮件地址,验证码: " + code + ",有效时间3分钟,如非本人操作,请登录平台修改密码,以防账号丢失。", 44 | email, username); 45 | default -> null; 46 | }; 47 | if (Objects.isNull(message)) { 48 | return; 49 | } 50 | sender.send(message); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lite-monitor-server/src/main/java/com/example/controller/ServerController.java: -------------------------------------------------------------------------------- 1 | package com.example.controller; 2 | 3 | import com.example.entity.RestBean; 4 | import com.example.entity.dto.Client; 5 | import com.example.entity.vo.request.ClientDetailVO; 6 | import com.example.entity.vo.request.RuntimeDetailVO; 7 | import com.example.service.ClientService; 8 | import com.example.utils.Const; 9 | import jakarta.annotation.Resource; 10 | import jakarta.validation.Valid; 11 | import lombok.extern.slf4j.Slf4j; 12 | import org.springframework.web.bind.annotation.*; 13 | 14 | 15 | @RestController 16 | @RequestMapping("/monitor") 17 | @Slf4j 18 | public class ServerController { 19 | 20 | @Resource 21 | ClientService clientService; 22 | 23 | @GetMapping("/register") 24 | public RestBean registerClient(@RequestHeader("Authorization") String token) { 25 | if (this.clientService.verifyAndRegister(token)) { 26 | return RestBean.success(); 27 | } 28 | return RestBean.failure(401, "注册失败,Token无效"); 29 | } 30 | 31 | @PostMapping("/detail") 32 | public RestBean updateClientDetails(@RequestAttribute(Const.ATTR_CLIENT) Client client, 33 | @RequestBody @Valid ClientDetailVO clientDetailVO) { 34 | Boolean success = this.clientService.updateClientDetail(clientDetailVO, client); 35 | return success ? RestBean.success() : RestBean.failure(404, "数据非法,请联系管理员"); 36 | } 37 | 38 | @PostMapping("/runtime") 39 | public RestBean updateRuntimeDetails(@RequestAttribute(Const.ATTR_CLIENT) Client client, 40 | @RequestBody @Valid RuntimeDetailVO runtimeDetailVO) { 41 | log.info(client.toString()); 42 | Boolean success = this.clientService.updateRuntimeDetail(runtimeDetailVO, client); 43 | return success ? RestBean.success() : RestBean.failure(404, "数据非法,请联系管理员"); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /lite-monitor-web/src/router/index.js: -------------------------------------------------------------------------------- 1 | import {createRouter, createWebHistory} from 'vue-router' 2 | import {unauthorized} from "@/net"; 3 | 4 | const router = createRouter({ 5 | history: createWebHistory(import.meta.env.BASE_URL), 6 | routes: [ 7 | { 8 | path: '/', 9 | name: 'welcome', 10 | component: () => import('@/views/WelcomeView.vue'), 11 | children: [ 12 | { 13 | path: '', 14 | name: 'welcome-login', 15 | component: () => import('@/views/welcome/LoginPage.vue') 16 | }, { 17 | path: 'forget', 18 | name: 'welcome-forget', 19 | component: () => import('@/views/welcome/ForgetPage.vue') 20 | } 21 | ] 22 | }, { 23 | path: '/index', 24 | name: 'index', 25 | component: () => import('@/views/IndexView.vue'), 26 | children: [ 27 | { 28 | path: '', 29 | name: 'manage', 30 | component: () => import('@/views/main/Manage.vue') 31 | }, 32 | // { 33 | // path: 'report', 34 | // name: 'report', 35 | // component: () => import('@/views/main/Report.vue') 36 | // }, 37 | { 38 | path: 'security', 39 | name: 'security', 40 | component: () => import('@/views/main/Security.vue') 41 | } 42 | ] 43 | } 44 | ] 45 | }) 46 | 47 | router.beforeEach((to, from, next) => { 48 | const isUnauthorized = unauthorized() 49 | if(to.name.startsWith('welcome') && !isUnauthorized) { 50 | next('/index') 51 | } else if(to.fullPath.startsWith('/index') && isUnauthorized) { 52 | next('/') 53 | } else { 54 | next() 55 | } 56 | }) 57 | 58 | export default router 59 | -------------------------------------------------------------------------------- /lite-monitor-web/src/views/WelcomeView.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 26 | 27 | -------------------------------------------------------------------------------- /lite-monitor-server/src/main/java/com/example/entity/BaseData.java: -------------------------------------------------------------------------------- 1 | package com.example.entity; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.lang.reflect.Constructor; 7 | import java.lang.reflect.Field; 8 | import java.util.Arrays; 9 | import java.util.function.Consumer; 10 | 11 | /** 12 | * 用于DTO快速转换VO实现,只需将DTO类继承此类即可使用 13 | */ 14 | public interface BaseData { 15 | /** 16 | * 创建指定的VO类并将当前DTO对象中的所有成员变量值直接复制到VO对象中 17 | * @param clazz 指定VO类型 18 | * @param consumer 返回VO对象之前可以使用Lambda进行额外处理 19 | * @return 指定VO对象 20 | * @param 指定VO类型 21 | */ 22 | default V asViewObject(Class clazz, Consumer consumer) { 23 | V v = this.asViewObject(clazz); 24 | consumer.accept(v); 25 | return v; 26 | } 27 | 28 | /** 29 | * 创建指定的VO类并将当前DTO对象中的所有成员变量值直接复制到VO对象中 30 | * @param clazz 指定VO类型 31 | * @return 指定VO对象 32 | * @param 指定VO类型 33 | */ 34 | default V asViewObject(Class clazz) { 35 | try { 36 | Field[] fields = clazz.getDeclaredFields(); 37 | Constructor constructor = clazz.getConstructor(); 38 | V v = constructor.newInstance(); 39 | Arrays.asList(fields).forEach(field -> convert(field, v)); 40 | return v; 41 | } catch (ReflectiveOperationException exception) { 42 | Logger logger = LoggerFactory.getLogger(BaseData.class); 43 | logger.error("在VO与DTO转换时出现了一些错误", exception); 44 | throw new RuntimeException(exception.getMessage()); 45 | } 46 | } 47 | 48 | /** 49 | * 内部使用,快速将当前类中目标对象字段同名字段的值复制到目标对象字段上 50 | * @param field 目标对象字段 51 | * @param target 目标对象 52 | */ 53 | private void convert(Field field, Object target){ 54 | try { 55 | Field source = this.getClass().getDeclaredField(field.getName()); 56 | field.setAccessible(true); 57 | source.setAccessible(true); 58 | field.set(target, source.get(this)); 59 | } catch (IllegalAccessException | NoSuchFieldException ignored) {} 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /lite-monitor-server/src/main/java/com/example/utils/MailUtils.java: -------------------------------------------------------------------------------- 1 | package com.example.utils; 2 | 3 | import com.example.entity.dto.Account; 4 | import com.example.entity.vo.response.ClientDetailsVO; 5 | import org.springframework.mail.SimpleMailMessage; 6 | 7 | import java.text.DecimalFormat; 8 | import java.text.SimpleDateFormat; 9 | import java.util.Date; 10 | 11 | /** 12 | * @author : Doge2077 16:39 2025/1/9 13 | */ 14 | public class MailUtils { 15 | 16 | /** 17 | * 构建邮件实体 18 | * 19 | * @param title 标题 20 | * @param content 内容 21 | * @param receiver 收件人 22 | * @param sender 发件人 23 | */ 24 | public static SimpleMailMessage buildMailMessage(String title, String content, String receiver, String sender) { 25 | SimpleMailMessage message = new SimpleMailMessage(); 26 | message.setSubject(title); 27 | message.setFrom(sender); 28 | message.setText(content); 29 | message.setTo(receiver); 30 | return message; 31 | } 32 | 33 | /** 34 | * 构建报警信息 35 | * 36 | * @param user 用户 37 | * @param client 主机信息 38 | * @param reportCpuUsage cpu负载 39 | * @param reportMemory 内存负载 40 | */ 41 | public static SimpleMailMessage reportMessage(Account user, ClientDetailsVO client, Double reportCpuUsage, Double reportMemory) { 42 | SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 43 | String formattedDate = dateFormat.format(new Date()); 44 | DecimalFormat numFormat = new DecimalFormat("#.00"); 45 | String cpu = numFormat.format(reportCpuUsage * 100) + "%"; 46 | String memory = numFormat.format(reportMemory * 100) + "%"; 47 | String content = String.format("尊敬的 Lite-Monitor 用户%s您好,您的主机:%s IP: %s 于 %s 检测到CPU负载:%s,内存负载: %s,超过预设报警阈值,可能正在遭受潜在的攻击行为,请您尽快处理! ", 48 | user.getUsername(), 49 | client.getClientName(), 50 | client.getClientAddress(), 51 | formattedDate, 52 | cpu, 53 | memory); 54 | return buildMailMessage("【Lite-Monitor】报警提醒邮件", content, user.getEmail(), null); 55 | } 56 | 57 | 58 | } 59 | -------------------------------------------------------------------------------- /lite-monitor-server/src/main/java/com/example/filter/CorsFilter.java: -------------------------------------------------------------------------------- 1 | package com.example.filter; 2 | 3 | import com.example.utils.Const; 4 | import jakarta.servlet.FilterChain; 5 | import jakarta.servlet.ServletException; 6 | import jakarta.servlet.http.HttpFilter; 7 | import jakarta.servlet.http.HttpServletRequest; 8 | import jakarta.servlet.http.HttpServletResponse; 9 | import org.springframework.beans.factory.annotation.Value; 10 | import org.springframework.core.annotation.Order; 11 | import org.springframework.stereotype.Component; 12 | 13 | import java.io.IOException; 14 | 15 | /** 16 | * 跨域配置过滤器,仅处理跨域,添加跨域响应头 17 | */ 18 | @Component 19 | @Order(Const.ORDER_CORS) 20 | public class CorsFilter extends HttpFilter { 21 | 22 | @Value("${spring.web.cors.origin}") 23 | String origin; 24 | 25 | @Value("${spring.web.cors.credentials}") 26 | boolean credentials; 27 | 28 | @Value("${spring.web.cors.methods}") 29 | String methods; 30 | 31 | @Override 32 | protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { 33 | this.addCorsHeader(request, response); 34 | chain.doFilter(request, response); 35 | } 36 | 37 | /** 38 | * 添加所有跨域相关响应头 39 | * @param request 请求 40 | * @param response 响应 41 | */ 42 | private void addCorsHeader(HttpServletRequest request, HttpServletResponse response) { 43 | response.addHeader("Access-Control-Allow-Origin", this.resolveOrigin(request)); 44 | response.addHeader("Access-Control-Allow-Methods", this.resolveMethod()); 45 | response.addHeader("Access-Control-Allow-Headers", "Authorization, Content-Type"); 46 | if(credentials) { 47 | response.addHeader("Access-Control-Allow-Credentials", "true"); 48 | } 49 | } 50 | 51 | /** 52 | * 解析配置文件中的请求方法 53 | * @return 解析得到的请求头值 54 | */ 55 | private String resolveMethod(){ 56 | return methods.equals("*") ? "GET, HEAD, POST, PUT, DELETE, OPTIONS, TRACE, PATCH" : methods; 57 | } 58 | 59 | /** 60 | * 解析配置文件中的请求原始站点 61 | * @param request 请求 62 | * @return 解析得到的请求头值 63 | */ 64 | private String resolveOrigin(HttpServletRequest request){ 65 | return origin.equals("*") ? request.getHeader("Origin") : origin; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /lite-monitor-server/src/main/java/com/example/controller/exception/ErrorPageController.java: -------------------------------------------------------------------------------- 1 | package com.example.controller.exception; 2 | 3 | import com.example.entity.RestBean; 4 | import jakarta.servlet.http.HttpServletRequest; 5 | import org.springframework.boot.autoconfigure.web.servlet.error.AbstractErrorController; 6 | import org.springframework.boot.web.error.ErrorAttributeOptions; 7 | import org.springframework.boot.web.servlet.error.ErrorAttributes; 8 | import org.springframework.http.HttpStatus; 9 | import org.springframework.web.bind.annotation.RequestMapping; 10 | import org.springframework.web.bind.annotation.RestController; 11 | 12 | import java.util.Map; 13 | import java.util.Optional; 14 | 15 | /** 16 | * 专用用于处理错误页面的Controller 17 | */ 18 | @RestController 19 | @RequestMapping({"${server.error.path:${error.path:/error}}"}) 20 | public class ErrorPageController extends AbstractErrorController { 21 | 22 | public ErrorPageController(ErrorAttributes errorAttributes) { 23 | super(errorAttributes); 24 | } 25 | 26 | /** 27 | * 所有错误在这里统一处理,自动解析状态码和原因 28 | * @param request 请求 29 | * @return 失败响应 30 | */ 31 | @RequestMapping 32 | public RestBean error(HttpServletRequest request) { 33 | HttpStatus status = this.getStatus(request); 34 | Map errorAttributes = this.getErrorAttributes(request, this.getAttributeOptions()); 35 | String message = this.convertErrorMessage(status) 36 | .orElse(errorAttributes.get("message").toString()); 37 | return RestBean.failure(status.value(), message); 38 | } 39 | 40 | /** 41 | * 对于一些特殊的状态码,错误信息转换 42 | * @param status 状态码 43 | * @return 错误信息 44 | */ 45 | private Optional convertErrorMessage(HttpStatus status){ 46 | String value = switch (status.value()) { 47 | case 400 -> "请求参数有误"; 48 | case 404 -> "请求的接口不存在"; 49 | case 405 -> "请求方法错误"; 50 | case 500 -> "内部错误,请联系管理员"; 51 | default -> null; 52 | }; 53 | return Optional.ofNullable(value); 54 | } 55 | 56 | /** 57 | * 错误属性获取选项,这里额外添加了错误消息和异常类型 58 | * @return 选项 59 | */ 60 | private ErrorAttributeOptions getAttributeOptions(){ 61 | return ErrorAttributeOptions 62 | .defaults() 63 | .including(ErrorAttributeOptions.Include.MESSAGE, 64 | ErrorAttributeOptions.Include.EXCEPTION); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /lite-monitor-server/src/main/java/com/example/controller/UserController.java: -------------------------------------------------------------------------------- 1 | package com.example.controller; 2 | 3 | import com.example.entity.RestBean; 4 | import com.example.entity.vo.request.ChangePasswordVO; 5 | import com.example.entity.vo.request.CreateSubAccountVO; 6 | import com.example.entity.vo.request.ModifyEmailVO; 7 | import com.example.entity.vo.response.SubAccountVO; 8 | import com.example.service.AccountService; 9 | import com.example.utils.Const; 10 | import jakarta.annotation.Resource; 11 | import jakarta.validation.Valid; 12 | import org.springframework.web.bind.annotation.*; 13 | 14 | import java.util.List; 15 | 16 | @RestController 17 | @RequestMapping("/api/user") 18 | public class UserController { 19 | 20 | @Resource 21 | AccountService accountService; 22 | 23 | @PostMapping("/change-password") 24 | public RestBean changePassword(@RequestBody @Valid ChangePasswordVO changePasswordVO, 25 | @RequestAttribute(Const.ATTR_USER_ID) int userId) { 26 | return this.accountService.changePassword(userId, changePasswordVO.getPassword(), changePasswordVO.getNew_password()) ? 27 | RestBean.success() : RestBean.failure(401, "原密码输入错误"); 28 | } 29 | 30 | @PostMapping("/modify-email") 31 | public RestBean modifyEmail(@RequestAttribute(Const.ATTR_USER_ID) int uid, 32 | @RequestBody @Valid ModifyEmailVO emailVO) { 33 | String result = accountService.modifyEmail(uid, emailVO); 34 | if(result == null) { 35 | return RestBean.success(); 36 | } else { 37 | return RestBean.failure(401, result); 38 | } 39 | } 40 | 41 | @PostMapping("/sub/create") 42 | public RestBean createSubAccount(@RequestBody @Valid CreateSubAccountVO createSubAccountVO) { 43 | this.accountService.createSubAccount(createSubAccountVO); 44 | return RestBean.success(); 45 | } 46 | 47 | @GetMapping("/sub/delete") 48 | public RestBean deleteSubAccount(int uid, 49 | @RequestAttribute(Const.ATTR_USER_ID) int userId) { 50 | if(uid == userId) { 51 | return RestBean.failure(401, "非法参数"); 52 | } 53 | this.accountService.deleteSubAccount(uid); 54 | return RestBean.success(); 55 | } 56 | 57 | @GetMapping("/sub/list") 58 | public RestBean> subAccountList() { 59 | return RestBean.success(this.accountService.listSubAccount()); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /lite-monitor-web/src/views/welcome/LoginPage.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | 75 | 76 | 79 | -------------------------------------------------------------------------------- /lite-monitor-server/src/main/java/com/example/service/impl/ReportServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.example.service.impl; 2 | 3 | import com.example.entity.dto.Account; 4 | import com.example.entity.dto.Client; 5 | import com.example.entity.vo.request.RuntimeDetailVO; 6 | import com.example.entity.vo.response.ClientDetailsVO; 7 | import com.example.service.AccountService; 8 | import com.example.service.ClientService; 9 | import com.example.service.ReportService; 10 | import com.example.utils.Const; 11 | import com.example.utils.MailUtils; 12 | import jakarta.annotation.Resource; 13 | import lombok.extern.slf4j.Slf4j; 14 | import org.apache.commons.lang3.StringUtils; 15 | import org.springframework.amqp.core.AmqpTemplate; 16 | import org.springframework.data.redis.core.StringRedisTemplate; 17 | import org.springframework.mail.SimpleMailMessage; 18 | import org.springframework.stereotype.Service; 19 | 20 | import java.util.List; 21 | import java.util.concurrent.TimeUnit; 22 | 23 | /** 24 | * @author : liyusen02 22:45 2025/1/5 25 | */ 26 | @Slf4j 27 | @Service 28 | public class ReportServiceImpl implements ReportService { 29 | 30 | @Resource 31 | AmqpTemplate rabbitTemplate; 32 | 33 | @Resource 34 | private AccountService accountService; 35 | 36 | @Resource 37 | private ClientService clientService; 38 | 39 | @Resource 40 | private StringRedisTemplate stringRedisTemplate; 41 | 42 | @Override 43 | public void autoReport(Client client, RuntimeDetailVO runtime) { 44 | Double cpuUsage = runtime.getCpuUsage(); 45 | Double memoryUsage = runtime.getMemoryUsage(); 46 | ClientDetailsVO clientDetailsVO = clientService.clientDetails(client.getClientId()); 47 | memoryUsage = memoryUsage / clientDetailsVO.getOsMemory(); 48 | Double reportMemory = clientDetailsVO.getReportMemory(); 49 | Double reportCpuUsage = clientDetailsVO.getReportCpuUsage(); 50 | List users = accountService.getMailByClientId(client.getClientId()); 51 | if (cpuUsage > reportCpuUsage || memoryUsage > reportMemory) { 52 | String reportKey = stringRedisTemplate.opsForValue().get(Const.REPORT_KEY + clientDetailsVO.getClientId()); 53 | // 如果 Key 不存在,则发送邮件报警 54 | if (StringUtils.isBlank(reportKey)) { 55 | for (Account user : users) { 56 | SimpleMailMessage simpleMailMessage = MailUtils.reportMessage(user, clientDetailsVO, cpuUsage, memoryUsage); 57 | stringRedisTemplate.opsForValue().set(Const.REPORT_KEY + clientDetailsVO.getClientId(), user.getEmail(), 1, TimeUnit.MINUTES); 58 | rabbitTemplate.convertAndSend(Const.MQ_REPORT, simpleMailMessage); 59 | } 60 | } 61 | } 62 | 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /lite-monitor-server/src/main/java/com/example/filter/FlowLimitingFilter.java: -------------------------------------------------------------------------------- 1 | package com.example.filter; 2 | 3 | import com.example.entity.RestBean; 4 | import com.example.utils.Const; 5 | import com.example.utils.FlowUtils; 6 | import jakarta.annotation.Resource; 7 | import jakarta.servlet.FilterChain; 8 | import jakarta.servlet.ServletException; 9 | import jakarta.servlet.http.HttpFilter; 10 | import jakarta.servlet.http.HttpServletRequest; 11 | import jakarta.servlet.http.HttpServletResponse; 12 | import lombok.extern.slf4j.Slf4j; 13 | import org.springframework.beans.factory.annotation.Value; 14 | import org.springframework.core.annotation.Order; 15 | import org.springframework.data.redis.core.StringRedisTemplate; 16 | import org.springframework.stereotype.Component; 17 | 18 | import java.io.IOException; 19 | import java.io.PrintWriter; 20 | 21 | /** 22 | * 限流控制过滤器 23 | * 防止用户高频请求接口,借助Redis进行限流 24 | */ 25 | @Slf4j 26 | @Component 27 | @Order(Const.ORDER_FLOW_LIMIT) 28 | public class FlowLimitingFilter extends HttpFilter { 29 | 30 | @Resource 31 | StringRedisTemplate template; 32 | //指定时间内最大请求次数限制 33 | @Value("${spring.web.flow.limit}") 34 | int limit; 35 | //计数时间周期 36 | @Value("${spring.web.flow.period}") 37 | int period; 38 | //超出请求限制封禁时间 39 | @Value("${spring.web.flow.block}") 40 | int block; 41 | 42 | @Resource 43 | FlowUtils utils; 44 | 45 | @Override 46 | protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { 47 | String address = request.getRemoteAddr(); 48 | if (!tryCount(address)) 49 | this.writeBlockMessage(response); 50 | else 51 | chain.doFilter(request, response); 52 | } 53 | 54 | /** 55 | * 尝试对指定IP地址请求计数,如果被限制则无法继续访问 56 | * @param address 请求IP地址 57 | * @return 是否操作成功 58 | */ 59 | private boolean tryCount(String address) { 60 | synchronized (address.intern()) { 61 | if(Boolean.TRUE.equals(template.hasKey(Const.FLOW_LIMIT_BLOCK + address))) 62 | return false; 63 | String counterKey = Const.FLOW_LIMIT_COUNTER + address; 64 | String blockKey = Const.FLOW_LIMIT_BLOCK + address; 65 | return utils.limitPeriodCheck(counterKey, blockKey, block, limit, period); 66 | } 67 | } 68 | 69 | /** 70 | * 为响应编写拦截内容,提示用户操作频繁 71 | * @param response 响应 72 | * @throws IOException 可能的异常 73 | */ 74 | private void writeBlockMessage(HttpServletResponse response) throws IOException { 75 | response.setStatus(HttpServletResponse.SC_FORBIDDEN); 76 | response.setContentType("application/json;charset=utf-8"); 77 | PrintWriter writer = response.getWriter(); 78 | writer.write(RestBean.forbidden("操作频繁,请稍后再试").asJsonString()); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /lite-monitor-server/src/main/java/com/example/controller/AuthorizeController.java: -------------------------------------------------------------------------------- 1 | package com.example.controller; 2 | 3 | import com.example.entity.RestBean; 4 | import com.example.entity.vo.request.ConfirmResetVO; 5 | import com.example.entity.vo.request.EmailResetVO; 6 | import com.example.service.AccountService; 7 | import io.swagger.v3.oas.annotations.Operation; 8 | import io.swagger.v3.oas.annotations.tags.Tag; 9 | import jakarta.annotation.Resource; 10 | import jakarta.servlet.http.HttpServletRequest; 11 | import jakarta.validation.Valid; 12 | import jakarta.validation.constraints.Email; 13 | import jakarta.validation.constraints.Pattern; 14 | import org.springframework.validation.annotation.Validated; 15 | import org.springframework.web.bind.annotation.*; 16 | 17 | import java.util.function.Supplier; 18 | 19 | /** 20 | * 用于验证相关Controller包含用户的注册、重置密码等操作 21 | */ 22 | @Validated 23 | @RestController 24 | @RequestMapping("/api/auth") 25 | @Tag(name = "登录校验相关", description = "包括用户登录、注册、验证码请求等操作。") 26 | public class AuthorizeController { 27 | 28 | @Resource 29 | AccountService accountService; 30 | 31 | /** 32 | * 请求邮件验证码 33 | * @param email 请求邮件 34 | * @param type 类型 35 | * @param request 请求 36 | * @return 是否请求成功 37 | */ 38 | @GetMapping("/ask-code") 39 | @Operation(summary = "请求邮件验证码") 40 | public RestBean askVerifyCode(@RequestParam @Email String email, 41 | @RequestParam @Pattern(regexp = "(reset|modify)") String type, 42 | HttpServletRequest request){ 43 | return this.messageHandle(() -> 44 | accountService.registerEmailVerifyCode(type, String.valueOf(email), request.getRemoteAddr())); 45 | } 46 | 47 | 48 | /** 49 | * 执行密码重置确认,检查验证码是否正确 50 | * @param vo 密码重置信息 51 | * @return 是否操作成功 52 | */ 53 | @PostMapping("/reset-confirm") 54 | @Operation(summary = "密码重置确认") 55 | public RestBean resetConfirm(@RequestBody @Valid ConfirmResetVO vo){ 56 | return this.messageHandle(() -> accountService.resetConfirm(vo)); 57 | } 58 | 59 | /** 60 | * 执行密码重置操作 61 | * @param vo 密码重置信息 62 | * @return 是否操作成功 63 | */ 64 | @PostMapping("/reset-password") 65 | @Operation(summary = "密码重置操作") 66 | public RestBean resetPassword(@RequestBody @Valid EmailResetVO vo){ 67 | return this.messageHandle(() -> 68 | accountService.resetEmailAccountPassword(vo)); 69 | } 70 | 71 | /** 72 | * 针对于返回值为String作为错误信息的方法进行统一处理 73 | * @param action 具体操作 74 | * @return 响应结果 75 | * @param 响应结果类型 76 | */ 77 | private RestBean messageHandle(Supplier action){ 78 | String message = action.get(); 79 | if(message == null) 80 | return RestBean.success(); 81 | else 82 | return RestBean.failure(400, message); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /lite-monitor-server/src/main/java/com/example/utils/SnowflakeIdGenerator.java: -------------------------------------------------------------------------------- 1 | package com.example.utils; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | /** 6 | * 雪花算法ID生成器 7 | */ 8 | @Component 9 | public class SnowflakeIdGenerator { 10 | private static final long START_TIMESTAMP = 1691087910202L; 11 | 12 | private static final long DATA_CENTER_ID_BITS = 5L; 13 | private static final long WORKER_ID_BITS = 5L; 14 | private static final long SEQUENCE_BITS = 12L; 15 | 16 | private static final long MAX_DATA_CENTER_ID = ~(-1L << DATA_CENTER_ID_BITS); 17 | private static final long MAX_WORKER_ID = ~(-1L << WORKER_ID_BITS); 18 | private static final long MAX_SEQUENCE = ~(-1L << SEQUENCE_BITS); 19 | 20 | private static final long WORKER_ID_SHIFT = SEQUENCE_BITS; 21 | private static final long DATA_CENTER_ID_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS; 22 | private static final long TIMESTAMP_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS + DATA_CENTER_ID_BITS; 23 | 24 | private final long dataCenterId; 25 | private final long workerId; 26 | private long lastTimestamp = -1L; 27 | private long sequence = 0L; 28 | 29 | public SnowflakeIdGenerator(){ 30 | this(1, 1); 31 | } 32 | 33 | private SnowflakeIdGenerator(long dataCenterId, long workerId) { 34 | if (dataCenterId > MAX_DATA_CENTER_ID || dataCenterId < 0) { 35 | throw new IllegalArgumentException("Data center ID can't be greater than " + MAX_DATA_CENTER_ID + " or less than 0"); 36 | } 37 | if (workerId > MAX_WORKER_ID || workerId < 0) { 38 | throw new IllegalArgumentException("Worker ID can't be greater than " + MAX_WORKER_ID + " or less than 0"); 39 | } 40 | this.dataCenterId = dataCenterId; 41 | this.workerId = workerId; 42 | } 43 | 44 | /** 45 | * 生成一个新的雪花算法ID加锁 46 | * @return 雪花ID 47 | */ 48 | public synchronized long nextId() { 49 | long timestamp = getCurrentTimestamp(); 50 | if (timestamp < lastTimestamp) { 51 | throw new IllegalStateException("Clock moved backwards. Refusing to generate ID."); 52 | } 53 | if (timestamp == lastTimestamp) { 54 | sequence = (sequence + 1) & MAX_SEQUENCE; 55 | if (sequence == 0) { 56 | timestamp = getNextTimestamp(lastTimestamp); 57 | } 58 | } else { 59 | sequence = 0L; 60 | } 61 | lastTimestamp = timestamp; 62 | return ((timestamp - START_TIMESTAMP) << TIMESTAMP_SHIFT) | 63 | (dataCenterId << DATA_CENTER_ID_SHIFT) | 64 | (workerId << WORKER_ID_SHIFT) | 65 | sequence; 66 | } 67 | 68 | private long getCurrentTimestamp() { 69 | return System.currentTimeMillis(); 70 | } 71 | 72 | private long getNextTimestamp(long lastTimestamp) { 73 | long timestamp = getCurrentTimestamp(); 74 | while (timestamp <= lastTimestamp) { 75 | timestamp = getCurrentTimestamp(); 76 | } 77 | return timestamp; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /lite-monitor-web/src/component/RuntimeHistory.vue: -------------------------------------------------------------------------------- 1 | 81 | 82 | 90 | 91 | 98 | -------------------------------------------------------------------------------- /lite-monitor-server/src/main/java/com/example/filter/JwtAuthenticationFilter.java: -------------------------------------------------------------------------------- 1 | package com.example.filter; 2 | 3 | import com.auth0.jwt.interfaces.DecodedJWT; 4 | import com.example.entity.RestBean; 5 | import com.example.entity.dto.Client; 6 | import com.example.service.ClientService; 7 | import com.example.utils.Const; 8 | import com.example.utils.JwtUtils; 9 | import jakarta.annotation.Resource; 10 | import jakarta.servlet.FilterChain; 11 | import jakarta.servlet.ServletException; 12 | import jakarta.servlet.http.HttpServletRequest; 13 | import jakarta.servlet.http.HttpServletResponse; 14 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 15 | import org.springframework.security.core.context.SecurityContextHolder; 16 | import org.springframework.security.core.userdetails.UserDetails; 17 | import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; 18 | import org.springframework.stereotype.Component; 19 | import org.springframework.web.filter.OncePerRequestFilter; 20 | 21 | import java.io.IOException; 22 | import java.util.ArrayList; 23 | 24 | /** 25 | * 用于对请求头中Jwt令牌进行校验的工具,为当前请求添加用户验证信息 26 | * 并将用户的ID存放在请求对象属性中,方便后续使用 27 | */ 28 | @Component 29 | public class JwtAuthenticationFilter extends OncePerRequestFilter { 30 | 31 | @Resource 32 | JwtUtils utils; 33 | 34 | @Resource 35 | ClientService clientService; 36 | 37 | @Override 38 | protected void doFilterInternal(HttpServletRequest request, 39 | HttpServletResponse response, 40 | FilterChain filterChain) throws ServletException, IOException { 41 | String authorization = request.getHeader("Authorization"); 42 | String uri = request.getRequestURI(); 43 | if (uri.startsWith("/monitor")) { 44 | if (!uri.endsWith("/register")) { 45 | Client client = this.clientService.getClientByToken(authorization); 46 | if (client == null) { 47 | response.setStatus(401); 48 | response.setCharacterEncoding("utf-8"); 49 | response.getWriter().write(RestBean.failure(401, "未注册的客户端").asJsonString()); 50 | return ; 51 | } else { 52 | request.setAttribute(Const.ATTR_CLIENT, client); 53 | } 54 | } 55 | } else { 56 | DecodedJWT jwt = utils.resolveJwt(authorization); 57 | if(jwt != null) { 58 | UserDetails user = utils.toUser(jwt); 59 | UsernamePasswordAuthenticationToken authentication = 60 | new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities()); 61 | authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); 62 | SecurityContextHolder.getContext().setAuthentication(authentication); 63 | request.setAttribute(Const.ATTR_USER_ID, utils.toId(jwt)); 64 | request.setAttribute(Const.ATTR_USER_ROLE, new ArrayList<>(user.getAuthorities()).get(0).getAuthority()); 65 | } 66 | } 67 | filterChain.doFilter(request, response); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /lite-monitor-server/src/main/java/com/example/utils/FlowUtils.java: -------------------------------------------------------------------------------- 1 | package com.example.utils; 2 | 3 | import jakarta.annotation.Resource; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.data.redis.core.StringRedisTemplate; 6 | import org.springframework.stereotype.Component; 7 | 8 | import java.util.Optional; 9 | import java.util.concurrent.TimeUnit; 10 | 11 | /** 12 | * 限流通用工具 13 | * 针对于不同的情况进行限流操作,支持限流升级 14 | */ 15 | @Slf4j 16 | @Component 17 | public class FlowUtils { 18 | 19 | @Resource 20 | StringRedisTemplate template; 21 | 22 | /** 23 | * 针对于单次频率限制,请求成功后,在冷却时间内不得再次进行请求,如3秒内不能再次发起请求 24 | * @param key 键 25 | * @param blockTime 限制时间 26 | * @return 是否通过限流检查 27 | */ 28 | public boolean limitOnceCheck(String key, int blockTime){ 29 | return this.internalCheck(key, 1, blockTime, (overclock) -> false); 30 | } 31 | 32 | /** 33 | * 针对于单次频率限制,请求成功后,在冷却时间内不得再次进行请求 34 | * 如3秒内不能再次发起请求,如果不听劝阻继续发起请求,将限制更长时间 35 | * @param key 键 36 | * @param frequency 请求频率 37 | * @param baseTime 基础限制时间 38 | * @param upgradeTime 升级限制时间 39 | * @return 是否通过限流检查 40 | */ 41 | public boolean limitOnceUpgradeCheck(String key, int frequency, int baseTime, int upgradeTime){ 42 | return this.internalCheck(key, frequency, baseTime, (overclock) -> { 43 | if (overclock) 44 | template.opsForValue().set(key, "1", upgradeTime, TimeUnit.SECONDS); 45 | return false; 46 | }); 47 | } 48 | 49 | /** 50 | * 针对于在时间段内多次请求限制,如3秒内限制请求20次,超出频率则封禁一段时间 51 | * @param counterKey 计数键 52 | * @param blockKey 封禁键 53 | * @param blockTime 封禁时间 54 | * @param frequency 请求频率 55 | * @param period 计数周期 56 | * @return 是否通过限流检查 57 | */ 58 | public boolean limitPeriodCheck(String counterKey, String blockKey, int blockTime, int frequency, int period){ 59 | return this.internalCheck(counterKey, frequency, period, (overclock) -> { 60 | if (overclock) 61 | template.opsForValue().set(blockKey, "", blockTime, TimeUnit.SECONDS); 62 | return !overclock; 63 | }); 64 | } 65 | 66 | /** 67 | * 内部使用请求限制主要逻辑 68 | * @param key 计数键 69 | * @param frequency 请求频率 70 | * @param period 计数周期 71 | * @param action 限制行为与策略 72 | * @return 是否通过限流检查 73 | */ 74 | private boolean internalCheck(String key, int frequency, int period, LimitAction action){ 75 | String count = template.opsForValue().get(key); 76 | if (count != null) { 77 | long value = Optional.ofNullable(template.opsForValue().increment(key)).orElse(0L); 78 | int c = Integer.parseInt(count); 79 | if(value != c + 1) 80 | template.expire(key, period, TimeUnit.SECONDS); 81 | return action.run(value > frequency); 82 | } else { 83 | template.opsForValue().set(key, "1", period, TimeUnit.SECONDS); 84 | return true; 85 | } 86 | } 87 | 88 | /** 89 | * 内部使用,限制行为与策略 90 | */ 91 | private interface LimitAction { 92 | boolean run(boolean overclock); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /lite-monitor-web/src/component/TerminalWindow.vue: -------------------------------------------------------------------------------- 1 | 54 | 55 | 86 | 87 | 100 | -------------------------------------------------------------------------------- /lite-monitor-web/src/tools/index.js: -------------------------------------------------------------------------------- 1 | import { ElMessage, ElMessageBox } from 'element-plus' 2 | import { post } from '@/net' 3 | import { useClipboard } from '@vueuse/core' 4 | 5 | function fitByUnit(value, unit) { 6 | const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'] 7 | let index = units.indexOf(unit) 8 | while (((value < 1 && value != 0) || value >= 1024) && (index >= 0 || index < units.length)) { 9 | if (value >= 1024) { 10 | value = value / 1024 11 | index = index + 1 12 | } else { 13 | value = value * 1024 14 | index = index - 1 15 | } 16 | } 17 | return `${parseInt(value)} ${units[index]}` 18 | } 19 | 20 | function percentageToStatus(percentage) { 21 | if (percentage < 50) return 'success' 22 | else if (percentage < 80) return 'warning' 23 | else return 'exception' 24 | } 25 | 26 | function rename(clientId, clientName, after) { 27 | ElMessageBox.prompt('请输入新的服务器主机名称', '修改名称', { 28 | confirmButtonText: '确认', 29 | cancelButtonText: '取消', 30 | inputValue: clientName, 31 | inputPattern: /^[a-zA-Z0-9_\u4e00-\u9fa5]{1,10}$/, 32 | inputErrorMessage: '名称只能包含中英文字符、数字和下划线', 33 | }).then(({value}) => post('/monitor/rename', { 34 | clientId: clientId, 35 | clientName: value 36 | }, () => { 37 | ElMessage.success('主机名称已更新') 38 | after() 39 | }) 40 | ) 41 | } 42 | 43 | function reportConfig(clientId, reportCpuUsage, reportMemory, after) { 44 | // 先修改CPU报警阈值 45 | ElMessageBox.prompt('请输入新的CPU报警阈值', '修改CPU报警阈值', { 46 | confirmButtonText: '下一步', 47 | cancelButtonText: '取消', 48 | inputValue: reportCpuUsage 49 | }).then(({value: cpuValue}) => { 50 | // 修改内存报警阈值 51 | return ElMessageBox.prompt('请输入新的内存报警阈值', '修改内存报警阈值', { 52 | confirmButtonText: '确认', 53 | cancelButtonText: '取消', 54 | inputValue: reportMemory 55 | }).then(({value: memoryValue}) => { 56 | return {cpuValue, memoryValue} 57 | }) 58 | }).then(({cpuValue, memoryValue}) => { 59 | // 提交更新的报警阈值 60 | post('/monitor/report', { 61 | clientId: clientId, 62 | reportMemory: memoryValue, 63 | reportCpuUsage: cpuValue 64 | }, () => { 65 | ElMessage.success('报警阈值已更新') 66 | after() 67 | }) 68 | }).catch(() => { 69 | ElMessage.info('操作已取消') 70 | }) 71 | } 72 | 73 | const {copy} = useClipboard() 74 | const copyId = (data) => copy(data.clientAddress).then(() => ElMessage.success('成功复制到剪切板')) 75 | 76 | function osNameToIcon(name) { 77 | if (name.indexOf('Ubuntu') >= 0) 78 | return {icon: 'fa-ubuntu', color: '#db4c1a'} 79 | else if (name.indexOf('CentOS') >= 0) 80 | return {icon: 'fa-centos', color: '#9dcd30'} 81 | else if (name.indexOf('macOS') >= 0) 82 | return {icon: 'fa-apple', color: 'grey'} 83 | else if (name.indexOf('Windows') >= 0) 84 | return {icon: 'fa-windows', color: '#3578b9'} 85 | else if (name.indexOf('Debian') >= 0) 86 | return {icon: 'fa-debian', color: '#a80836'} 87 | else 88 | return {icon: 'fa-linux', color: 'grey'} 89 | } 90 | 91 | 92 | export { fitByUnit, percentageToStatus, rename, copyId, osNameToIcon, reportConfig } -------------------------------------------------------------------------------- /lite-monitor-server/src/main/java/com/example/utils/InfluxDbUtils.java: -------------------------------------------------------------------------------- 1 | package com.example.utils; 2 | 3 | import com.alibaba.fastjson2.JSONObject; 4 | import com.example.entity.dto.RuntimeDetail; 5 | import com.example.entity.vo.request.RuntimeDetailVO; 6 | import com.example.entity.vo.response.RuntimeHistoryVO; 7 | import com.influxdb.client.InfluxDBClient; 8 | import com.influxdb.client.InfluxDBClientFactory; 9 | import com.influxdb.client.WriteApiBlocking; 10 | import com.influxdb.client.domain.WritePrecision; 11 | import com.influxdb.query.FluxRecord; 12 | import com.influxdb.query.FluxTable; 13 | import jakarta.annotation.PostConstruct; 14 | import lombok.extern.slf4j.Slf4j; 15 | import org.springframework.beans.BeanUtils; 16 | import org.springframework.beans.factory.annotation.Value; 17 | import org.springframework.stereotype.Component; 18 | 19 | import java.util.Date; 20 | import java.util.List; 21 | 22 | @Slf4j 23 | @Component 24 | public class InfluxDbUtils { 25 | @Value("${spring.influx.url}") 26 | String url; 27 | 28 | @Value("${spring.influx.user}") 29 | String user; 30 | 31 | @Value("${spring.influx.password}") 32 | String password; 33 | 34 | private final String BUCKET = "lite-monitor"; 35 | private final String ORG = "admin"; 36 | 37 | private InfluxDBClient client; 38 | 39 | @PostConstruct 40 | public void initInfluxDbClient() { 41 | this.client = InfluxDBClientFactory.create(url, user, password.toCharArray()); 42 | } 43 | 44 | public void updateRuntimeDetial(int clientId, RuntimeDetailVO runtimeDetailVO) { 45 | RuntimeDetail runtimeDetail = new RuntimeDetail(); 46 | BeanUtils.copyProperties(runtimeDetailVO, runtimeDetail); 47 | runtimeDetail.setId(clientId); 48 | runtimeDetail.setTimestamp(new Date(runtimeDetailVO.getTimestamp()).toInstant()); 49 | WriteApiBlocking writeApi = this.client.getWriteApiBlocking(); 50 | writeApi.writeMeasurement(BUCKET, ORG, WritePrecision.NS, runtimeDetail); 51 | } 52 | 53 | // 查询一个小时内的历史数据 54 | public RuntimeHistoryVO readRuntimeData(int clientId) { 55 | RuntimeHistoryVO runtimeHistoryVO = new RuntimeHistoryVO(); 56 | String query = """ 57 | from(bucket: "%s") 58 | |> range(start: %s) 59 | |> filter(fn: (r) => r["_measurement"] == "runtime_detail") 60 | |> filter(fn: (r) => r["id"] == "%s") 61 | """; 62 | String formatedQuery = String.format(query, BUCKET, "-1h", clientId); 63 | List fluxTableList = this.client.getQueryApi().query(formatedQuery, ORG); 64 | int size = fluxTableList.size(); 65 | if (size == 0) { 66 | log.error("查询不到数据"); 67 | return new RuntimeHistoryVO(); 68 | } 69 | List fluxRecordList = fluxTableList.get(0).getRecords(); 70 | for (int i = 0; i < fluxRecordList.size(); i ++) { 71 | JSONObject object = new JSONObject(); 72 | object.put("timestamp", fluxRecordList.get(i).getTime()); 73 | for (int j = 0; j < size; j ++) { 74 | FluxRecord fluxRecord = fluxTableList.get(j).getRecords().get(i); 75 | object.put(fluxRecord.getField(), fluxRecord.getValue()); 76 | } 77 | runtimeHistoryVO.getList().add(object); 78 | } 79 | return runtimeHistoryVO; 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /lite-monitor-web/src/net/index.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import {ElMessage} from "element-plus"; 3 | import {useStore} from "@/store"; 4 | 5 | const authItemName = "authorize" 6 | 7 | const accessHeader = () => { 8 | return { 9 | 'Authorization': `Bearer ${takeAccessToken()}` 10 | } 11 | } 12 | 13 | const defaultError = (error) => { 14 | console.error(error) 15 | ElMessage.error('发生了一些错误,请联系管理员') 16 | } 17 | 18 | const defaultFailure = (message, status, url) => { 19 | console.warn(`请求地址: ${url}, 状态码: ${status}, 错误信息: ${message}`) 20 | ElMessage.warning(message) 21 | } 22 | 23 | function takeAccessToken() { 24 | const str = localStorage.getItem(authItemName) || sessionStorage.getItem(authItemName); 25 | if(!str) return null 26 | const authObj = JSON.parse(str) 27 | if(new Date(authObj.expire) <= new Date()) { 28 | deleteAccessToken() 29 | ElMessage.warning("登录状态已过期,请重新登录!") 30 | return null 31 | } 32 | return authObj.token 33 | } 34 | 35 | function storeAccessToken(remember, token, expire){ 36 | const authObj = { 37 | token: token, 38 | expire: expire 39 | } 40 | const str = JSON.stringify(authObj) 41 | if(remember) 42 | localStorage.setItem(authItemName, str) 43 | else 44 | sessionStorage.setItem(authItemName, str) 45 | } 46 | 47 | function deleteAccessToken() { 48 | localStorage.removeItem(authItemName) 49 | sessionStorage.removeItem(authItemName) 50 | } 51 | 52 | function internalPost(url, data, headers, success, failure, error = defaultError){ 53 | axios.post(url, data, { headers: headers }).then(({data}) => { 54 | if(data.code === 200) 55 | success(data.data) 56 | else 57 | failure(data.message, data.code, url) 58 | }).catch(err => error(err)) 59 | } 60 | 61 | function internalGet(url, headers, success, failure, error = defaultError){ 62 | axios.get(url, { headers: headers }).then(({data}) => { 63 | if(data.code === 200) 64 | success(data.data) 65 | else 66 | failure(data.message, data.code, url) 67 | }).catch(err => error(err)) 68 | } 69 | 70 | function login(username, password, remember, success, failure = defaultFailure){ 71 | internalPost('/auth/login', { 72 | username: username, 73 | password: password 74 | }, { 75 | 'Content-Type': 'application/x-www-form-urlencoded' 76 | }, (data) => { 77 | storeAccessToken(remember, data.token, data.expire) 78 | const store = useStore() 79 | store.user.role = data.role 80 | store.user.username = data.username 81 | store.user.email = data.email 82 | ElMessage.success(`登录成功,欢迎 ${data.username} 来到我们的系统`) 83 | success(data) 84 | }, failure) 85 | } 86 | 87 | function post(url, data, success, failure = defaultFailure) { 88 | internalPost(url, data, accessHeader() , success, failure) 89 | } 90 | 91 | function logout(success, failure = defaultFailure){ 92 | get('/auth/logout', () => { 93 | deleteAccessToken() 94 | ElMessage.success(`退出登录成功,欢迎您再次使用`) 95 | success() 96 | }, failure) 97 | } 98 | 99 | function get(url, success, failure = defaultFailure) { 100 | internalGet(url, accessHeader(), success, failure) 101 | } 102 | 103 | function unauthorized() { 104 | return !takeAccessToken() 105 | } 106 | 107 | export { post, get, login, logout, unauthorized } 108 | -------------------------------------------------------------------------------- /lite-monitor-client/src/main/java/org/example/utils/NetUtils.java: -------------------------------------------------------------------------------- 1 | package org.example.utils; 2 | 3 | import com.alibaba.fastjson2.JSONObject; 4 | import jakarta.annotation.Resource; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.example.config.ServerConfiguration; 7 | import org.example.entity.ClientDetail; 8 | import org.example.entity.Response; 9 | import org.example.entity.RuntimeDetail; 10 | import org.springframework.context.annotation.Lazy; 11 | import org.springframework.stereotype.Component; 12 | 13 | import java.net.URI; 14 | import java.net.http.HttpClient; 15 | import java.net.http.HttpRequest; 16 | import java.net.http.HttpResponse; 17 | 18 | @Slf4j 19 | @Component 20 | public class NetUtils { 21 | 22 | @Lazy 23 | @Resource 24 | ServerConfiguration serverConfiguration; 25 | 26 | private final HttpClient httpClient = HttpClient.newHttpClient(); 27 | 28 | public Boolean registerToServer(String serverAddress, String token) { 29 | log.info("正在注册到服务器"); 30 | Response response = doGet("/register", serverAddress, token); 31 | if (response.success()) { 32 | log.info("客户端注册成功"); 33 | } else { 34 | log.error("客户端注册失败:{}", response.msg()); 35 | } 36 | return response.success(); 37 | } 38 | 39 | private Response doGet(String url, String serverAddress, String token) { 40 | try { 41 | HttpRequest httpRequest = HttpRequest.newBuilder().GET() 42 | .uri(new URI(serverAddress + "/monitor" + url)) 43 | .header("Authorization", token) 44 | .build(); 45 | HttpResponse httpResponse = this.httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString()); 46 | return JSONObject.parseObject(httpResponse.body()).to(Response.class); 47 | } catch (Exception e) { 48 | log.error("注册失败:{}", e.getMessage()); 49 | return Response.errorResponse(e); 50 | } 51 | } 52 | 53 | public void updateClientDetail(ClientDetail clientDetail) { 54 | Response response = this.doPost("/detail", clientDetail); 55 | if (response.success()) { 56 | log.info("客户端数据上报完成"); 57 | } else { 58 | log.error("客户端数据上报失败:{}", response.msg()); 59 | } 60 | } 61 | 62 | public void updateRuntimeDetail(RuntimeDetail runtimeDetail) { 63 | Response response = this.doPost("/runtime", runtimeDetail); 64 | if (!response.success()) { 65 | log.error("客户端运行时数据上报失败:{}", response); 66 | } 67 | } 68 | 69 | private Response doPost(String url, Object data) { 70 | try { 71 | String rawData = JSONObject.from(data).toJSONString(); 72 | HttpRequest httpRequest = HttpRequest.newBuilder().POST(HttpRequest.BodyPublishers.ofString(rawData)) 73 | .uri(new URI(this.serverConfiguration.getConfigurationFromFile().getServerAddress() + "/monitor" + url)) 74 | .header("Authorization", this.serverConfiguration.getConfigurationFromFile().getToken()) 75 | .header("Content-Type", "application/json") 76 | .build(); 77 | HttpResponse httpResponse = this.httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString()); 78 | return JSONObject.parseObject(httpResponse.body()).to(Response.class); 79 | } catch (Exception e) { 80 | log.error("客户端上报数据请求失败:{}", e.getMessage()); 81 | return Response.errorResponse(e); 82 | } 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /lite-monitor-web/src/views/main/Report.vue: -------------------------------------------------------------------------------- 1 | 70 | 71 | 98 | 99 | -------------------------------------------------------------------------------- /lite-monitor-client/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 3.2.4 9 | 10 | 11 | org.example 12 | lite-monitor-client 13 | 0.0.1-SNAPSHOT 14 | lite-monitor-client 15 | lite-monitor-client 16 | 17 | 17 18 | 19 | 20 | 21 | org.springframework.boot 22 | spring-boot-starter 23 | 24 | 25 | 26 | org.projectlombok 27 | lombok 28 | true 29 | 30 | 31 | org.springframework.boot 32 | spring-boot-starter-test 33 | test 34 | 35 | 36 | com.alibaba.fastjson2 37 | fastjson2 38 | 2.0.25 39 | 40 | 41 | com.github.oshi 42 | oshi-core 43 | 6.4.0 44 | 45 | 46 | org.springframework.boot 47 | spring-boot-starter-quartz 48 | 49 | 50 | 51 | 52 | client 53 | 54 | 55 | 56 | org.apache.maven.plugins 57 | maven-surefire-plugin 58 | 59 | true 60 | 61 | 62 | 63 | org.springframework.boot 64 | spring-boot-maven-plugin 65 | 66 | true 67 | 68 | 69 | org.projectlombok 70 | lombok 71 | 72 | 73 | 74 | 75 | 76 | org.springframework.boot 77 | spring-boot-maven-plugin 78 | 79 | true 80 | 81 | 82 | 83 | 84 | repackage 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /lite-monitor-client/src/main/java/org/example/config/ServerConfiguration.java: -------------------------------------------------------------------------------- 1 | package org.example.config; 2 | 3 | import com.alibaba.fastjson2.JSONObject; 4 | import jakarta.annotation.Resource; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.example.entity.ConnectionConfig; 7 | import org.example.utils.NetUtils; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | 11 | import java.io.File; 12 | import java.io.FileInputStream; 13 | import java.io.FileWriter; 14 | import java.io.IOException; 15 | import java.nio.charset.StandardCharsets; 16 | import java.util.Scanner; 17 | 18 | @Slf4j 19 | @Configuration 20 | public class ServerConfiguration { 21 | 22 | @Resource 23 | NetUtils netUtils; 24 | 25 | @Bean 26 | public ConnectionConfig connectionConfig() { 27 | log.info("正在加载服务端连接配置 ..."); 28 | ConnectionConfig connectionConfig = this.getConfigurationFromFile(); 29 | if (connectionConfig == null) { 30 | connectionConfig = this.getConfigurationByRegister(); 31 | } 32 | return connectionConfig; 33 | } 34 | 35 | private ConnectionConfig getConfigurationByRegister() { 36 | Scanner scanner = new Scanner(System.in); 37 | String serverAddress, token, cron; 38 | do { 39 | log.info("请输入服务端地址及其端口号(127.0.0.1:8080):"); 40 | serverAddress = "http://" + scanner.nextLine(); 41 | log.info("请输入服务端生成的注册 Token 密钥:"); 42 | token = scanner.nextLine(); 43 | do { 44 | log.info("请输入信息采集间隔(单位为秒,默认为 10):"); 45 | cron = scanner.nextLine(); 46 | try { 47 | int cronValue = Integer.parseInt(cron); 48 | if (cronValue < 1 || cronValue > 59) { 49 | log.warn("采集间隔必须在1到59之间,请重新输入。"); 50 | cron = null; 51 | } 52 | } catch (NumberFormatException e) { 53 | log.warn("请输入有效的数字。"); 54 | cron = null; 55 | } 56 | } while (cron == null); 57 | } while (!this.netUtils.registerToServer(serverAddress, token)); 58 | ConnectionConfig connectionConfig = new ConnectionConfig(serverAddress, token, Integer.parseInt(cron)); 59 | this.saveConnectionConfigAsFile(connectionConfig); 60 | return connectionConfig; 61 | } 62 | 63 | private void saveConnectionConfigAsFile(ConnectionConfig connectionConfig) { 64 | File configDir = new File("local-config"); 65 | if (!configDir.exists() && configDir.mkdir()) { 66 | log.info("已创建 local-config 目录"); 67 | } 68 | File configFile = new File("local-config/server.json"); 69 | try (FileWriter fileWriter = new FileWriter(configFile)){ 70 | fileWriter.write(JSONObject.from(connectionConfig).toJSONString()); 71 | log.info("服务端连接配置已保存"); 72 | } catch (Exception e) { 73 | log.error("服务端连接配置保存失败:{}", e.getMessage()); 74 | } 75 | } 76 | 77 | public ConnectionConfig getConfigurationFromFile() { 78 | File config = new File("local-config/server.json"); 79 | if (config.exists()) { 80 | try (FileInputStream fileInputStream = new FileInputStream(config)){ 81 | String rawConfig = new String(fileInputStream.readAllBytes(), StandardCharsets.UTF_8); 82 | ConnectionConfig connectionConfig = JSONObject.parseObject(rawConfig).to(ConnectionConfig.class); 83 | if (connectionConfig == null) { 84 | log.error("配置文件为空"); 85 | } else { 86 | return connectionConfig; 87 | } 88 | } catch (IOException e) { 89 | log.error("读取配置文件错误:{}", e.getMessage()); 90 | } 91 | } 92 | return null; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /lite-monitor-web/src/views/IndexView.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 79 | 80 | 133 | -------------------------------------------------------------------------------- /lite-monitor-web/src/echarts/index.js: -------------------------------------------------------------------------------- 1 | import * as echarts from "echarts"; 2 | 3 | function defaultOption(name, dataX) { 4 | return { 5 | tooltip: { 6 | trigger: 'axis', 7 | position: function (pt) { 8 | return [pt[0], pt[1]]; 9 | }, 10 | confine: true, 11 | padding: 3, 12 | backgroundColor: '#FFFFFFE0', 13 | textStyle: { 14 | fontSize: 13 15 | } 16 | }, grid:{ 17 | left: '10', 18 | right: '15', 19 | bottom: '0', 20 | top: '30', 21 | containLabel: true 22 | }, xAxis: { 23 | type: 'category', 24 | boundaryGap: false, 25 | data: dataX, 26 | animation: false, 27 | axisLabel: { 28 | formatter: function (value) { 29 | value = new Date(value) 30 | let time = value.toLocaleTimeString(); 31 | time = time.substring(0, time.length - 3) 32 | const date = [value.getDate() + 1, value.getMonth() + 1].join('/') 33 | return `${time}\n${date}`; 34 | } 35 | } 36 | }, yAxis: { 37 | type: 'value', 38 | name: name, 39 | boundaryGap: [0, '10%'] 40 | }, dataZoom: [ 41 | { 42 | type: 'inside', 43 | start: 95, 44 | end: 100, 45 | minValueSpan: 12 46 | } 47 | ] 48 | }; 49 | } 50 | 51 | function singleSeries(option, name, dataY, colors) { 52 | option.series = [ 53 | { 54 | name: name, 55 | type: 'line', 56 | sampling: 'lttb', 57 | showSymbol: false, 58 | itemStyle: { 59 | color: colors[0] 60 | }, 61 | areaStyle: { 62 | color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ 63 | { 64 | offset: 0, 65 | color: colors[1] 66 | }, { 67 | offset: 1, 68 | color: colors[2] 69 | } 70 | ]) 71 | }, 72 | data: dataY 73 | } 74 | ] 75 | } 76 | 77 | function doubleSeries(option, name, dataY, colors) { 78 | option.series = [ 79 | { 80 | name: name[0], 81 | type: 'line', 82 | sampling: 'lttb', 83 | showSymbol: false, 84 | itemStyle: { 85 | color: colors[0][0] 86 | }, 87 | areaStyle: { 88 | color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ 89 | { 90 | offset: 0, 91 | color: colors[0][1] 92 | }, { 93 | offset: 1, 94 | color: colors[0][2] 95 | } 96 | ]) 97 | }, 98 | data: dataY[0] 99 | }, { 100 | name: name[1], 101 | type: 'line', 102 | sampling: 'lttb', 103 | showSymbol: false, 104 | itemStyle: { 105 | color: colors[1][0] 106 | }, 107 | areaStyle: { 108 | color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ 109 | { 110 | offset: 0, 111 | color: colors[1][1] 112 | }, { 113 | offset: 1, 114 | color: colors[1][2] 115 | } 116 | ]) 117 | }, 118 | data: dataY[1] 119 | } 120 | ] 121 | } 122 | 123 | export { defaultOption, singleSeries, doubleSeries } 124 | -------------------------------------------------------------------------------- /lite-monitor-server/src/main/java/com/example/filter/RequestLogFilter.java: -------------------------------------------------------------------------------- 1 | package com.example.filter; 2 | 3 | import com.alibaba.fastjson2.JSONObject; 4 | import com.example.utils.Const; 5 | import com.example.utils.SnowflakeIdGenerator; 6 | import jakarta.annotation.Resource; 7 | import jakarta.servlet.FilterChain; 8 | import jakarta.servlet.ServletException; 9 | import jakarta.servlet.http.HttpServletRequest; 10 | import jakarta.servlet.http.HttpServletResponse; 11 | import lombok.extern.slf4j.Slf4j; 12 | import org.slf4j.MDC; 13 | import org.springframework.security.core.context.SecurityContextHolder; 14 | import org.springframework.security.core.userdetails.User; 15 | import org.springframework.stereotype.Component; 16 | import org.springframework.web.filter.OncePerRequestFilter; 17 | import org.springframework.web.util.ContentCachingResponseWrapper; 18 | 19 | import java.io.IOException; 20 | import java.util.Set; 21 | 22 | /** 23 | * 请求日志过滤器,用于记录所有用户请求信息 24 | */ 25 | @Slf4j 26 | @Component 27 | public class RequestLogFilter extends OncePerRequestFilter { 28 | 29 | @Resource 30 | SnowflakeIdGenerator generator; 31 | 32 | private final Set ignores = Set.of("/swagger-ui", "/v3/api-docs", 33 | "/monitor/runtime", 34 | "/api/monitor/list", 35 | "/api/monitor/runtime-now", 36 | "/api/monitor/runtime-history"); 37 | 38 | @Override 39 | protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { 40 | if(this.isIgnoreUrl(request.getServletPath())) { 41 | filterChain.doFilter(request, response); 42 | } else { 43 | long startTime = System.currentTimeMillis(); 44 | this.logRequestStart(request); 45 | ContentCachingResponseWrapper wrapper = new ContentCachingResponseWrapper(response); 46 | filterChain.doFilter(request, wrapper); 47 | this.logRequestEnd(wrapper, startTime); 48 | wrapper.copyBodyToResponse(); 49 | } 50 | } 51 | 52 | /** 53 | * 判定当前请求url是否不需要日志打印 54 | * @param url 路径 55 | * @return 是否忽略 56 | */ 57 | private boolean isIgnoreUrl(String url){ 58 | for (String ignore : ignores) { 59 | if(url.startsWith(ignore)) return true; 60 | } 61 | return false; 62 | } 63 | 64 | /** 65 | * 请求结束时的日志打印,包含处理耗时以及响应结果 66 | * @param wrapper 用于读取响应结果的包装类 67 | * @param startTime 起始时间 68 | */ 69 | public void logRequestEnd(ContentCachingResponseWrapper wrapper, long startTime){ 70 | long time = System.currentTimeMillis() - startTime; 71 | int status = wrapper.getStatus(); 72 | String content = status != 200 ? 73 | status + " 错误" : new String(wrapper.getContentAsByteArray()); 74 | log.info("请求处理耗时: {}ms | 响应结果: {}", time, content); 75 | } 76 | 77 | /** 78 | * 请求开始时的日志打印,包含请求全部信息,以及对应用户角色 79 | * @param request 请求 80 | */ 81 | public void logRequestStart(HttpServletRequest request){ 82 | long reqId = generator.nextId(); 83 | MDC.put("reqId", String.valueOf(reqId)); 84 | JSONObject object = new JSONObject(); 85 | request.getParameterMap().forEach((k, v) -> object.put(k, v.length > 0 ? v[0] : null)); 86 | Object id = request.getAttribute(Const.ATTR_USER_ID); 87 | if(id != null) { 88 | User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); 89 | log.info("请求URL: \"{}\" ({}) | 远程IP地址: {} │ 身份: {} (UID: {}) | 角色: {} | 请求参数列表: {}", 90 | request.getServletPath(), request.getMethod(), request.getRemoteAddr(), 91 | user.getUsername(), id, user.getAuthorities(), object); 92 | } else { 93 | log.info("请求URL: \"{}\" ({}) | 远程IP地址: {} │ 身份: 未验证 | 请求参数列表: {}", 94 | request.getServletPath(), request.getMethod(), request.getRemoteAddr(), object); 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /lite-monitor-web/src/component/ReportCard.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 73 | 74 | -------------------------------------------------------------------------------- /lite-monitor-web/src/component/PreviewCard.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 69 | 70 | 139 | -------------------------------------------------------------------------------- /lite-monitor-web/src/component/ReportDetails.vue: -------------------------------------------------------------------------------- 1 | 76 | 77 | 111 | 112 | -------------------------------------------------------------------------------- /lite-monitor-server/src/main/java/com/example/config/SwaggerConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.example.config; 2 | 3 | import com.example.entity.RestBean; 4 | import com.example.entity.vo.response.AuthorizeVO; 5 | import io.swagger.v3.oas.annotations.OpenAPIDefinition; 6 | import io.swagger.v3.oas.annotations.enums.SecuritySchemeIn; 7 | import io.swagger.v3.oas.annotations.enums.SecuritySchemeType; 8 | import io.swagger.v3.oas.annotations.security.SecurityRequirement; 9 | import io.swagger.v3.oas.annotations.security.SecurityScheme; 10 | import io.swagger.v3.oas.models.ExternalDocumentation; 11 | import io.swagger.v3.oas.models.OpenAPI; 12 | import io.swagger.v3.oas.models.Operation; 13 | import io.swagger.v3.oas.models.PathItem; 14 | import io.swagger.v3.oas.models.info.Info; 15 | import io.swagger.v3.oas.models.info.License; 16 | import io.swagger.v3.oas.models.media.Content; 17 | import io.swagger.v3.oas.models.media.MediaType; 18 | import io.swagger.v3.oas.models.parameters.QueryParameter; 19 | import io.swagger.v3.oas.models.responses.ApiResponse; 20 | import io.swagger.v3.oas.models.responses.ApiResponses; 21 | import org.springdoc.core.customizers.OpenApiCustomizer; 22 | import org.springframework.context.annotation.Bean; 23 | import org.springframework.context.annotation.Configuration; 24 | 25 | import java.util.HashMap; 26 | import java.util.List; 27 | import java.util.Map; 28 | 29 | /** 30 | * Swagger开发文档相关配置 31 | */ 32 | @Configuration 33 | @SecurityScheme(type = SecuritySchemeType.HTTP, scheme = "Bearer", 34 | name = "Authorization", in = SecuritySchemeIn.HEADER) 35 | @OpenAPIDefinition(security = { @SecurityRequirement(name = "Authorization") }) 36 | public class SwaggerConfiguration { 37 | 38 | /** 39 | * 配置文档介绍以及详细信息 40 | * @return OpenAPI 41 | */ 42 | @Bean 43 | public OpenAPI springShopOpenAPI() { 44 | return new OpenAPI() 45 | .info(new Info().title("示例项目 API 文档") 46 | .description("欢迎来到本示例项目API测试文档,在这里可以快速进行接口调试") 47 | .version("1.0") 48 | .license(new License() 49 | .name("项目开源地址") 50 | .url("https://github.com/Ketuer/SpringBoot-Vue-Template-Jwt") 51 | ) 52 | ) 53 | .externalDocs(new ExternalDocumentation() 54 | .description("我们的官方网站") 55 | .url("https://itbaima.net") 56 | ); 57 | } 58 | 59 | /** 60 | * 配置自定义的OpenApi相关信息 61 | * @return OpenApiCustomizer 62 | */ 63 | @Bean 64 | public OpenApiCustomizer customerGlobalHeaderOpenApiCustomizer() { 65 | return api -> this.authorizePathItems().forEach(api.getPaths()::addPathItem); 66 | } 67 | 68 | /** 69 | * 登录接口和退出登录接口手动添加一下 70 | * @return PathItems 71 | */ 72 | private Map authorizePathItems(){ 73 | Map map = new HashMap<>(); 74 | map.put("/api/auth/login", new PathItem() 75 | .post(new Operation() 76 | .tags(List.of("登录校验相关")) 77 | .summary("登录验证接口") 78 | .addParametersItem(new QueryParameter() 79 | .name("username") 80 | .required(true) 81 | ) 82 | .addParametersItem(new QueryParameter() 83 | .name("password") 84 | .required(true) 85 | ) 86 | .responses(new ApiResponses() 87 | .addApiResponse("200", new ApiResponse() 88 | .description("OK") 89 | .content(new Content().addMediaType("*/*", new MediaType() 90 | .example(RestBean.success(new AuthorizeVO()).asJsonString()) 91 | )) 92 | ) 93 | ) 94 | ) 95 | ); 96 | map.put("/api/auth/logout", new PathItem() 97 | .get(new Operation() 98 | .tags(List.of("登录校验相关")) 99 | .summary("退出登录接口") 100 | .responses(new ApiResponses() 101 | .addApiResponse("200", new ApiResponse() 102 | .description("OK") 103 | .content(new Content().addMediaType("*/*", new MediaType() 104 | .example(RestBean.success()) 105 | )) 106 | ) 107 | ) 108 | ) 109 | 110 | ); 111 | return map; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /lite-monitor-web/src/views/main/Manage.vue: -------------------------------------------------------------------------------- 1 | 75 | 76 | 125 | 126 | 157 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 |

5 |

6 | Typing SVG 7 |

8 |

9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |

18 | 19 | **** 20 | 21 |

22 | 简体中文 23 | | 24 | English 25 |

26 | 27 | ## 关于 Lite-monitor 28 | 29 | **** 30 | 31 | 这是一个快速、准确、轻量化的服务器监控系统,拥有秒级的监控粒度,支持历史数据查看便于拥有多平台服务器的用户集中管理: 32 | 33 | ![](https://image.itbaima.cn/images/40/image-20240422179520537.png) 34 | 35 | 支持一键 SSH 到目标主机,便于快速操作: 36 | 37 | ![](https://lys2021.com/wp-content/uploads/2024/04/image-20240422189124020.png) 38 | 39 | 支持多用户管理不同主机: 40 | 41 | ![](https://image.itbaima.cn/images/40/image-20240422177570347.png) 42 | 43 | **** 44 | 45 | ## 服务端部署 46 | 47 | **** 48 | 49 | ### 环境依赖 50 | 51 | **** 52 | 53 | - JDK17 54 | - SpringBoot3 55 | - Vue3 56 | - MySQL 5.7+ 57 | - Redis 58 | - InfluxDB 59 | - RabbitMQ 60 | 61 | **** 62 | 63 | ### 手动部署 64 | 65 | **** 66 | 67 | 部署 Redis: 68 | 69 | - 设置端口 `6379` 70 | - 使用 `0` 号数据库 71 | 72 | 部署 MySQL: 73 | 74 | - 设置用户 `root`,密码 `monitormysqlroot` 75 | - 创建数据库 `lite-monitor-db`,字符集设置为 `utf8mb4` 76 | - 执行导入 SQL 脚本,文件为 [`lite-monitor-db.sql`](https://github.com/Doge2077/lite-monitor/blob/main/lite-monitor-db.sql) 77 | 78 | 部署 InfluxDB: 79 | 80 | - 访问主机的 `8086` 端口进入 InfluxDB 的控制台 81 | - 设置用户为 `admin`,密码 `monitorinfluxdbadmin` 82 | - 创建新的 Bucket,名称为:`lite-monitor`,推荐设置过期时间为 7 天 83 | 84 | 部署 RabbitMQ: 85 | 86 | - 添加用户 `admin`,密码 `monitorrabbitmqadmin`,虚拟主机 `/` 87 | - 在 vhost / 下创建队列:mail 和 report 88 | - `rabbitmqadmin declare queue name=mail auto_delete=false durable=true --vhost /` 89 | - `rabbitmqadmin declare queue name=report auto_delete=false durable=true --vhost /` 90 | 91 | 92 | 部署 Redis: 93 | 94 | - 无需设置密码验证 95 | 96 | 部署后端: 97 | 98 | - 拉取本项目仓库到本地,对 `lite-monitor-server` 执行 `maven` 构建 99 | - 如果数据库等配置不与本例相同,请注意修改 `application-prod.yml` 中的配置 100 | - 使用 `maven` 打包,注意勾选 `prod` 环境配置并跳过测试 101 | - 将打包好的后端 `jar` 上传到管理监控的主机执行,主机需要 `Java17` 运行环境,默认监听 `8010` 端口 102 | 103 | 部署前端: 104 | 105 | - 配置 [`main.js`](https://github.com/Doge2077/lite-monitor/blob/main/lite-monitor-web/src/main.js) 的 `axios.defaults.baseURL` 中的主机 `ip` 106 | - 配置 [`Terminal.vue`](https://github.com/Doge2077/lite-monitor/blob/main/lite-monitor-web/src/component/Terminal.vue) 的 `ws` 地址为主机 `ip` 107 | - 本地执行 `npm` 构建,将打包后的前端文件上传到主机 108 | 109 | 部署 Nginx: 110 | 111 | - 根据实际需要进行配置 112 | 113 | 默认登录用户为 `admin`,密码默认为 `123456`,可在【安全】管理界面修改邮箱和密码 114 | 115 | **** 116 | 117 | ### Docker 部署 118 | 119 | **** 120 | 121 | 拉取本项目仓库到本地 122 | 123 | 修改前端路由: 124 | 125 | - 配置 [`main.js`](https://github.com/Doge2077/lite-monitor/blob/main/lite-monitor-web/src/main.js) 的 `axios.defaults.baseURL` 中的主机 `ip` 126 | - 配置 [`Terminal.vue`](https://github.com/Doge2077/lite-monitor/blob/main/lite-monitor-web/src/component/Terminal.vue) 的 `ws` 地址为主机 `ip` 127 | 128 | 执行 [`docker-compose.yml`](https://github.com/Doge2077/lite-monitor/blob/main/docker-compose.yml) 到目标服务器 129 | 130 | 初始化 MySQL: 131 | 132 | - 执行导入 SQL 脚本,文件为 [`lite-monitor-db.sql`](https://github.com/Doge2077/lite-monitor/blob/main/lite-monitor-db.sql) 133 | 134 | 初始化 InfluxDB: 135 | 136 | - 访问主机的 `8086` 端口进入 InfluxDB 的控制台 137 | - 设置用户为 `admin`,密码 `monitorinfluxdbadmin` 138 | - 创建新的 Bucket,名称为:`lite-monitor`,推荐设置过期时间为 7 天 139 | 140 | 默认登录用户为 `admin`,密码默认为 `123456`,可在【安全】管理界面修改邮箱和密码 141 | 142 | **** 143 | 144 | ## 客户端部署 145 | 146 | **** 147 | 148 | ### 环境依赖 149 | 150 | **** 151 | 152 | - JDK17 153 | - SpringBoot3 154 | 155 | **** 156 | 157 | ### 手动部署 158 | 159 | **** 160 | 161 | - 拉取本项目仓库到本地,对 `lite-monitor-client` 执行 `maven` 构建 162 | - 使用 `maven` 打包,注意勾选 `prod` 环境配置并跳过测试 163 | - 将打包好的后端 `jar` 上传到需要被监控的主机执行,主机需要 `Java17` 运行环境 164 | - 首次运行会在当前目录创建 `config-local` 目录,并要求注册到服务端,输入服务端主机 `ip:port` 和服务端生成的 `token` 即可 165 | - 支持将客户端以 `systemd` 方式注册为服务,具体配置可参考其他资料 166 | 167 | **** 168 | -------------------------------------------------------------------------------- /lite-monitor-web/src/component/CreateSubAccount.vue: -------------------------------------------------------------------------------- 1 | 75 | 76 | 132 | 133 | 155 | -------------------------------------------------------------------------------- /lite-monitor-db.sql: -------------------------------------------------------------------------------- 1 | -- MySQL dump 10.13 Distrib 8.0.19, for Win64 (x86_64) 2 | -- 3 | -- Host: 127.0.0.1 Database: lite-monitor-db 4 | -- ------------------------------------------------------ 5 | -- Server version 5.7.44 6 | 7 | /*!40101 SET @OLD_CHARACTER_SET_CLIENT = @@CHARACTER_SET_CLIENT */; 8 | /*!40101 SET @OLD_CHARACTER_SET_RESULTS = @@CHARACTER_SET_RESULTS */; 9 | /*!40101 SET @OLD_COLLATION_CONNECTION = @@COLLATION_CONNECTION */; 10 | /*!50503 SET NAMES utf8mb4 */; 11 | /*!40103 SET @OLD_TIME_ZONE = @@TIME_ZONE */; 12 | /*!40103 SET TIME_ZONE = '+00:00' */; 13 | /*!40014 SET @OLD_UNIQUE_CHECKS = @@UNIQUE_CHECKS, UNIQUE_CHECKS = 0 */; 14 | /*!40014 SET @OLD_FOREIGN_KEY_CHECKS = @@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS = 0 */; 15 | /*!40101 SET @OLD_SQL_MODE = @@SQL_MODE, SQL_MODE = 'NO_AUTO_VALUE_ON_ZERO' */; 16 | /*!40111 SET @OLD_SQL_NOTES = @@SQL_NOTES, SQL_NOTES = 0 */; 17 | 18 | -- 19 | -- Table structure for table `db_account` 20 | -- 21 | 22 | DROP TABLE IF EXISTS `db_account`; 23 | /*!40101 SET @saved_cs_client = @@character_set_client */; 24 | /*!50503 SET character_set_client = utf8mb4 */; 25 | CREATE TABLE `db_account` 26 | ( 27 | `id` int(11) NOT NULL AUTO_INCREMENT, 28 | `username` varchar(255) DEFAULT NULL, 29 | `email` varchar(255) DEFAULT NULL, 30 | `password` varchar(255) DEFAULT NULL, 31 | `role` varchar(255) DEFAULT NULL, 32 | `register_time` datetime DEFAULT NULL, 33 | `clients` json DEFAULT NULL, 34 | PRIMARY KEY (`id`), 35 | UNIQUE KEY `unique_email` (`email`), 36 | UNIQUE KEY `unique_username` (`username`) 37 | ) ENGINE = InnoDB 38 | AUTO_INCREMENT = 7 39 | DEFAULT CHARSET = utf8mb4; 40 | /*!40101 SET character_set_client = @saved_cs_client */; 41 | 42 | -- 43 | -- Dumping data for table `db_account` 44 | -- 45 | 46 | LOCK TABLES `db_account` WRITE; 47 | /*!40000 ALTER TABLE `db_account` 48 | DISABLE KEYS */; 49 | INSERT INTO `db_account` 50 | VALUES (1, 'admin', '1002302241@qq.com', '$2a$10$2WIHD6ZkM5AkV7iNZ0Uh4uiNgNrXMgXXq6zq8aUJRW34e5nWDcDf.', 'admin', 51 | '2024-04-04 14:45:54', NULL), 52 | (6, 'liyusen', '123456@qq.com', '$2a$10$Ry0MiC3Kvo5ebXCEi2/jjusX7CuB8shlhNicjY.PdXWNtKC8hz5oa', 'user', 53 | '2024-04-18 09:16:54', '[ 54 | 35388460, 55 | 16090204 56 | ]'); 57 | /*!40000 ALTER TABLE `db_account` 58 | ENABLE KEYS */; 59 | UNLOCK TABLES; 60 | 61 | -- 62 | -- Table structure for table `db_client` 63 | -- 64 | 65 | DROP TABLE IF EXISTS `db_client`; 66 | /*!40101 SET @saved_cs_client = @@character_set_client */; 67 | /*!50503 SET character_set_client = utf8mb4 */; 68 | CREATE TABLE `db_client` 69 | ( 70 | `client_id` int(11) NOT NULL, 71 | `client_name` varchar(255) DEFAULT NULL, 72 | `client_token` varchar(255) DEFAULT NULL, 73 | `register_time` datetime DEFAULT NULL, 74 | `node` varchar(255) DEFAULT NULL, 75 | `location` varchar(255) DEFAULT NULL, 76 | PRIMARY KEY (`client_id`) 77 | ) ENGINE = InnoDB 78 | DEFAULT CHARSET = utf8mb4; 79 | /*!40101 SET character_set_client = @saved_cs_client */; 80 | 81 | -- 82 | -- Dumping data for table `db_client` 83 | -- 84 | 85 | LOCK TABLES `db_client` WRITE; 86 | /*!40000 ALTER TABLE `db_client` 87 | DISABLE KEYS */; 88 | /*!40000 ALTER TABLE `db_client` 89 | ENABLE KEYS */; 90 | UNLOCK TABLES; 91 | 92 | -- 93 | -- Table structure for table `db_client_detail` 94 | -- 95 | 96 | DROP TABLE IF EXISTS `db_client_detail`; 97 | /*!40101 SET @saved_cs_client = @@character_set_client */; 98 | /*!50503 SET character_set_client = utf8mb4 */; 99 | CREATE TABLE `db_client_detail` 100 | ( 101 | `client_id` int(11) NOT NULL, 102 | `os_arch` varchar(255) NOT NULL, 103 | `client_address` varchar(255) NOT NULL, 104 | `os_name` varchar(255) NOT NULL, 105 | `os_version` varchar(255) NOT NULL, 106 | `os_bit` int(11) NOT NULL, 107 | `cpu_name` varchar(255) NOT NULL, 108 | `cpu_cores` int(11) NOT NULL, 109 | `os_memory` double NOT NULL, 110 | `disk_memory` varchar(255) NOT NULL, 111 | `report_memory` double DEFAULT 0.8 NOT NULL, 112 | `report_cpu_usage` double DEFAULT 0.8 NOT NULL 113 | PRIMARY KEY (`client_id`) 114 | ) ENGINE = InnoDB 115 | DEFAULT CHARSET = utf8mb4; 116 | /*!40101 SET character_set_client = @saved_cs_client */; 117 | 118 | -- 119 | -- Dumping data for table `db_client_detail` 120 | -- 121 | 122 | LOCK TABLES `db_client_detail` WRITE; 123 | /*!40000 ALTER TABLE `db_client_detail` 124 | DISABLE KEYS */; 125 | /*!40000 ALTER TABLE `db_client_detail` 126 | ENABLE KEYS */; 127 | UNLOCK TABLES; 128 | 129 | -- 130 | -- Table structure for table `db_client_ssh` 131 | -- 132 | 133 | DROP TABLE IF EXISTS `db_client_ssh`; 134 | /*!40101 SET @saved_cs_client = @@character_set_client */; 135 | /*!50503 SET character_set_client = utf8mb4 */; 136 | CREATE TABLE `db_client_ssh` 137 | ( 138 | `client_id` int(11) NOT NULL, 139 | `port` int(11) NOT NULL, 140 | `username` varchar(100) NOT NULL, 141 | `password` varchar(100) NOT NULL, 142 | PRIMARY KEY (`client_id`) 143 | ) ENGINE = InnoDB 144 | DEFAULT CHARSET = utf8mb4; 145 | /*!40101 SET character_set_client = @saved_cs_client */; 146 | 147 | -- 148 | -- Dumping data for table `db_client_ssh` 149 | -- 150 | 151 | LOCK TABLES `db_client_ssh` WRITE; 152 | /*!40000 ALTER TABLE `db_client_ssh` 153 | DISABLE KEYS */; 154 | /*!40000 ALTER TABLE `db_client_ssh` 155 | ENABLE KEYS */; 156 | UNLOCK TABLES; 157 | 158 | -- 159 | -- Dumping routines for database 'lite-monitor-db' 160 | -- 161 | /*!40103 SET TIME_ZONE = @OLD_TIME_ZONE */; 162 | 163 | /*!40101 SET SQL_MODE = @OLD_SQL_MODE */; 164 | /*!40014 SET FOREIGN_KEY_CHECKS = @OLD_FOREIGN_KEY_CHECKS */; 165 | /*!40014 SET UNIQUE_CHECKS = @OLD_UNIQUE_CHECKS */; 166 | /*!40101 SET CHARACTER_SET_CLIENT = @OLD_CHARACTER_SET_CLIENT */; 167 | /*!40101 SET CHARACTER_SET_RESULTS = @OLD_CHARACTER_SET_RESULTS */; 168 | /*!40101 SET COLLATION_CONNECTION = @OLD_COLLATION_CONNECTION */; 169 | /*!40111 SET SQL_NOTES = @OLD_SQL_NOTES */; 170 | -------------------------------------------------------------------------------- /lite-monitor-server/src/main/java/com/example/websocket/TerminalWebSocket.java: -------------------------------------------------------------------------------- 1 | package com.example.websocket; 2 | 3 | import com.example.entity.dto.ClientDetail; 4 | import com.example.entity.dto.ClientSsh; 5 | import com.example.mapper.ClientDetailMapper; 6 | import com.example.mapper.ClientSshMapper; 7 | import com.jcraft.jsch.ChannelShell; 8 | import com.jcraft.jsch.JSch; 9 | import com.jcraft.jsch.JSchException; 10 | import jakarta.annotation.Resource; 11 | import jakarta.websocket.*; 12 | import jakarta.websocket.server.PathParam; 13 | import jakarta.websocket.server.ServerEndpoint; 14 | import lombok.extern.slf4j.Slf4j; 15 | import org.springframework.stereotype.Component; 16 | 17 | import java.io.IOException; 18 | import java.io.InputStream; 19 | import java.io.OutputStream; 20 | import java.nio.charset.StandardCharsets; 21 | import java.util.Arrays; 22 | import java.util.Map; 23 | import java.util.concurrent.ConcurrentHashMap; 24 | import java.util.concurrent.ExecutorService; 25 | import java.util.concurrent.Executors; 26 | 27 | @Slf4j 28 | @Component 29 | @ServerEndpoint("/terminal/{clientId}") 30 | public class TerminalWebSocket { 31 | 32 | private static ClientDetailMapper clientDetailMapper; 33 | 34 | @Resource 35 | public void setDetailMapper(ClientDetailMapper detailMapper) { 36 | TerminalWebSocket.clientDetailMapper = detailMapper; 37 | } 38 | 39 | private static ClientSshMapper clientSshMapper; 40 | 41 | @Resource 42 | public void setSshMapper(ClientSshMapper sshMapper) { 43 | TerminalWebSocket.clientSshMapper = sshMapper; 44 | } 45 | 46 | private static final Map sessionMap = new ConcurrentHashMap<>(); 47 | private final ExecutorService service = Executors.newSingleThreadExecutor(); 48 | 49 | @OnOpen 50 | public void onOpen(Session session, 51 | @PathParam(value = "clientId") String clientId) throws Exception { 52 | ClientDetail detail = clientDetailMapper.selectById(clientId); 53 | ClientSsh ssh = clientSshMapper.selectById(clientId); 54 | if(detail == null || ssh == null) { 55 | session.close(new CloseReason(CloseReason.CloseCodes.CANNOT_ACCEPT, "无法识别此主机")); 56 | return; 57 | } 58 | if(this.createSshConnection(session, ssh, detail.getClientAddress())) { 59 | log.info("主机 {} 的SSH连接已创建", detail.getClientAddress()); 60 | } 61 | } 62 | 63 | @OnMessage 64 | public void onMessage(Session session, String message) throws IOException { 65 | Shell shell = sessionMap.get(session); 66 | OutputStream output = shell.output; 67 | output.write(message.getBytes(StandardCharsets.UTF_8)); 68 | output.flush(); 69 | } 70 | 71 | @OnClose 72 | public void onClose(Session session) throws IOException { 73 | Shell shell = sessionMap.get(session); 74 | if(shell != null) { 75 | shell.close(); 76 | sessionMap.remove(session); 77 | log.info("主机 {} 的SSH连接已断开", shell.js.getHost()); 78 | } 79 | } 80 | 81 | @OnError 82 | public void onError(Session session, Throwable error) throws IOException { 83 | log.error("用户WebSocket连接出现错误", error); 84 | session.close(); 85 | } 86 | 87 | private boolean createSshConnection(Session session, ClientSsh ssh, String clientAddress) throws IOException{ 88 | try { 89 | JSch jSch = new JSch(); 90 | com.jcraft.jsch.Session js = jSch.getSession(ssh.getUsername(), clientAddress, ssh.getPort()); 91 | js.setPassword(ssh.getPassword()); 92 | js.setConfig("StrictHostKeyChecking", "no"); 93 | js.setTimeout(3000); 94 | js.connect(); 95 | ChannelShell channel = (ChannelShell) js.openChannel("shell"); 96 | channel.setPtyType("xterm"); 97 | channel.connect(1000); 98 | sessionMap.put(session, new Shell(session, js, channel)); 99 | return true; 100 | } catch (JSchException e) { 101 | String message = e.getMessage(); 102 | if(message.equals("Auth fail")) { 103 | session.close(new CloseReason(CloseReason.CloseCodes.CANNOT_ACCEPT, 104 | "登录SSH失败,用户名或密码错误")); 105 | log.error("连接SSH失败,用户名或密码错误,登录失败"); 106 | } else if(message.contains("Connection refused")) { 107 | session.close(new CloseReason(CloseReason.CloseCodes.CANNOT_ACCEPT, 108 | "连接被拒绝,可能是没有启动SSH服务或是放开端口")); 109 | log.error("连接SSH失败,连接被拒绝,可能是没有启动SSH服务或是放开端口"); 110 | } else { 111 | session.close(new CloseReason(CloseReason.CloseCodes.CANNOT_ACCEPT, message)); 112 | log.error("连接SSH时出现错误", e); 113 | } 114 | } 115 | return false; 116 | } 117 | 118 | private class Shell { 119 | private final Session session; 120 | private final com.jcraft.jsch.Session js; 121 | private final ChannelShell channel; 122 | private final InputStream input; 123 | private final OutputStream output; 124 | 125 | public Shell(Session session, com.jcraft.jsch.Session js, ChannelShell channel) throws IOException { 126 | this.js = js; 127 | this.session = session; 128 | this.channel = channel; 129 | this.input = channel.getInputStream(); 130 | this.output = channel.getOutputStream(); 131 | service.submit(this::read); 132 | } 133 | 134 | private void read() { 135 | try { 136 | byte[] buffer = new byte[1024 * 1024]; 137 | int i; 138 | while ((i = input.read(buffer)) != -1) { 139 | String text = new String(Arrays.copyOfRange(buffer, 0, i), StandardCharsets.UTF_8); 140 | session.getBasicRemote().sendText(text); 141 | } 142 | } catch (Exception e) { 143 | log.error("读取SSH输入流时出现问题", e); 144 | } 145 | } 146 | 147 | public void close() throws IOException { 148 | input.close(); 149 | output.close(); 150 | channel.disconnect(); 151 | js.disconnect(); 152 | service.shutdown(); 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /lite-monitor-server/src/main/java/com/example/config/SecurityConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.example.config; 2 | 3 | import com.example.entity.RestBean; 4 | import com.example.entity.dto.Account; 5 | import com.example.entity.vo.response.AuthorizeVO; 6 | import com.example.filter.JwtAuthenticationFilter; 7 | import com.example.filter.RequestLogFilter; 8 | import com.example.service.AccountService; 9 | import com.example.utils.Const; 10 | import com.example.utils.JwtUtils; 11 | import jakarta.annotation.Resource; 12 | import jakarta.servlet.http.HttpServletRequest; 13 | import jakarta.servlet.http.HttpServletResponse; 14 | import org.springframework.context.annotation.Bean; 15 | import org.springframework.context.annotation.Configuration; 16 | import org.springframework.security.access.AccessDeniedException; 17 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 18 | import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; 19 | import org.springframework.security.config.http.SessionCreationPolicy; 20 | import org.springframework.security.core.Authentication; 21 | import org.springframework.security.core.userdetails.User; 22 | import org.springframework.security.web.SecurityFilterChain; 23 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; 24 | 25 | import java.io.IOException; 26 | import java.io.PrintWriter; 27 | 28 | /** 29 | * SpringSecurity相关配置 30 | */ 31 | @Configuration 32 | public class SecurityConfiguration { 33 | 34 | @Resource 35 | JwtAuthenticationFilter jwtAuthenticationFilter; 36 | 37 | @Resource 38 | RequestLogFilter requestLogFilter; 39 | 40 | @Resource 41 | JwtUtils utils; 42 | 43 | @Resource 44 | AccountService service; 45 | 46 | /** 47 | * 针对于 SpringSecurity 6 的新版配置方法 48 | * @param http 配置器 49 | * @return 自动构建的内置过滤器链 50 | * @throws Exception 可能的异常 51 | */ 52 | @Bean 53 | public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { 54 | return http 55 | .authorizeHttpRequests(conf -> conf 56 | .requestMatchers("/terminal/**").permitAll() 57 | .requestMatchers("/api/auth/**", "/error").permitAll() 58 | .requestMatchers("/monitor/**").permitAll() 59 | .requestMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll() 60 | .requestMatchers("/api/user/sub/**").hasRole(Const.ROLE_ADMIN) 61 | .anyRequest().hasAnyRole(Const.ROLE_ADMIN, Const.ROLE_NORMAL) 62 | ) 63 | .formLogin(conf -> conf 64 | .loginProcessingUrl("/api/auth/login") 65 | .failureHandler(this::handleProcess) 66 | .successHandler(this::handleProcess) 67 | .permitAll() 68 | ) 69 | .logout(conf -> conf 70 | .logoutUrl("/api/auth/logout") 71 | .logoutSuccessHandler(this::onLogoutSuccess) 72 | ) 73 | .exceptionHandling(conf -> conf 74 | .accessDeniedHandler(this::handleProcess) 75 | .authenticationEntryPoint(this::handleProcess) 76 | ) 77 | .csrf(AbstractHttpConfigurer::disable) 78 | .sessionManagement(conf -> conf 79 | .sessionCreationPolicy(SessionCreationPolicy.STATELESS)) 80 | .addFilterBefore(requestLogFilter, UsernamePasswordAuthenticationFilter.class) 81 | .addFilterBefore(jwtAuthenticationFilter, RequestLogFilter.class) 82 | .build(); 83 | } 84 | 85 | /** 86 | * 将多种类型的Handler整合到同一个方法中,包含: 87 | * - 登录成功 88 | * - 登录失败 89 | * - 未登录拦截/无权限拦截 90 | * @param request 请求 91 | * @param response 响应 92 | * @param exceptionOrAuthentication 异常或是验证实体 93 | * @throws IOException 可能的异常 94 | */ 95 | private void handleProcess(HttpServletRequest request, 96 | HttpServletResponse response, 97 | Object exceptionOrAuthentication) throws IOException { 98 | response.setContentType("application/json;charset=utf-8"); 99 | PrintWriter writer = response.getWriter(); 100 | if(exceptionOrAuthentication instanceof AccessDeniedException exception) { 101 | writer.write(RestBean 102 | .forbidden(exception.getMessage()).asJsonString()); 103 | } else if(exceptionOrAuthentication instanceof Exception exception) { 104 | writer.write(RestBean 105 | .unauthorized(exception.getMessage()).asJsonString()); 106 | } else if(exceptionOrAuthentication instanceof Authentication authentication){ 107 | User user = (User) authentication.getPrincipal(); 108 | Account account = service.findAccountByNameOrEmail(user.getUsername()); 109 | String jwt = utils.createJwt(user, account.getUsername(), account.getId()); 110 | if(jwt == null) { 111 | writer.write(RestBean.forbidden("登录验证频繁,请稍后再试").asJsonString()); 112 | } else { 113 | AuthorizeVO vo = account.asViewObject(AuthorizeVO.class, o -> o.setToken(jwt)); 114 | vo.setExpire(utils.expireTime()); 115 | writer.write(RestBean.success(vo).asJsonString()); 116 | } 117 | } 118 | } 119 | 120 | /** 121 | * 退出登录处理,将对应的Jwt令牌列入黑名单不再使用 122 | * @param request 请求 123 | * @param response 响应 124 | * @param authentication 验证实体 125 | * @throws IOException 可能的异常 126 | */ 127 | private void onLogoutSuccess(HttpServletRequest request, 128 | HttpServletResponse response, 129 | Authentication authentication) throws IOException { 130 | response.setContentType("application/json;charset=utf-8"); 131 | PrintWriter writer = response.getWriter(); 132 | String authorization = request.getHeader("Authorization"); 133 | if(utils.invalidateJwt(authorization)) { 134 | writer.write(RestBean.success("退出登录成功").asJsonString()); 135 | return; 136 | } 137 | writer.write(RestBean.failure(400, "退出登录失败").asJsonString()); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /en/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | 5 | 6 |

7 |

8 | Typing SVG 9 |

10 |

11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |

20 | 21 | **** 22 | 23 |

24 | 简体中文 25 | | 26 | English 27 |

28 | 29 | ## About Lite-monitor 30 | 31 | **** 32 | 33 | This is a fast, accurate, lightweight server monitoring system with second-level monitoring granularity, supporting historical data view for multi-platform server users: 34 | 35 | ![](https://image.itbaima.cn/images/40/image-20240422179520537.png) 36 | 37 | Supports one-click SSH to the target host, which is convenient for quick operations: 38 | 39 | ![](https://lys2021.com/wp-content/uploads/2024/04/image-20240422189124020.png) 40 | 41 | Supports multi-user management of different hosts: 42 | 43 | ![](https://image.itbaima.cn/images/40/image-20240422177570347.png) 44 | 45 | **** 46 | 47 | ## Server Deployment 48 | 49 | **** 50 | 51 | ### Environment Dependencies 52 | 53 | **** 54 | 55 | - JDK17 56 | - SpringBoot3 57 | - Vue3 58 | - MySQL 5.7+ 59 | - Redis 60 | - InfluxDB 61 | - RabbitMQ 62 | 63 | **** 64 | 65 | ### Manual Deployment 66 | 67 | **** 68 | 69 | Redis Deployment: 70 | 71 | - Set port `6379` 72 | - Use the `0` database 73 | 74 | MySQL Deployment: 75 | 76 | - Set user `root`, password `monitormysqlroot` 77 | - Create database `lite-monitor-db` with character set `utf8mb4` 78 | - Run the import SQL script, the file is [`lite-monitor-db.sql`](https://github.com/Doge2077/lite-monitor/blob/main/lite-monitor-db.sql) 79 | 80 | InfluxDB Deployment: 81 | 82 | - Access the host's console on port `8086` 83 | - Set the user to `admin` with the password `monitorinfluxdbadmin` 84 | - Create a new Bucket with the name: `lite-monitor`, we recommend setting an expiration time of 7 days 85 | 86 | RabbitMQ Deployment: 87 | 88 | - Add user `admin`, password `monitorrabbitmqadmin`, virtual host `/` 89 | - `rabbitmqadmin declare queue name=mail auto_delete=false durable=false --vhost` 90 | - `rabbitmqadmin declare queue name=report auto_delete=false durable=false --vhost` 91 | 92 | Redis Deployment: 93 | 94 | - No password validation required 95 | 96 | Backend Deployment: 97 | 98 | - Pull this project repository locally, run `maven` build for `lite-monitor-server` 99 | - If database configurations differ from this example, please modify the configurations in `application-prod.yml` 100 | - Package with `maven`, ensure `prod` environment config is selected and tests are skipped 101 | - Upload the packaged backend `jar` to the monitoring host for running. The host requires `Java17` runtime environment and listens on port `8010` by default 102 | 103 | Frontend Deployment: 104 | 105 | - Configure the host `ip` in [`main.js`](https://github.com/Doge2077/lite-monitor/blob/main/lite-monitor-web/src/main.js) under `axios.defaults.baseURL` 106 | - Configure the `ws` address in [`Terminal.vue`](https://github.com/Doge2077/lite-monitor/blob/main/lite-monitor-web/src/component/Terminal.vue) to the host `ip` 107 | - Run `npm` build locally and upload the built frontend files to the host 108 | 109 | Nginx Deployment: 110 | 111 | - Configure based on actual needs 112 | 113 | The default login user is `admin` with the default password `123456`, you can modify the email and password in the [Security] management page 114 | 115 | **** 116 | 117 | ### Docker Deployment 118 | 119 | **** 120 | 121 | Pull this project repository locally 122 | 123 | Modify frontend routing: 124 | 125 | - Configure the host `ip` in [`main.js`](https://github.com/Doge2077/lite-monitor/blob/main/lite-monitor-web/src/main.js) under `axios.defaults.baseURL` 126 | - Configure the `ws` address in [`Terminal.vue`](https://github.com/Doge2077/lite-monitor/blob/main/lite-monitor-web/src/component/Terminal.vue) to the host `ip` 127 | 128 | Run [`docker-compose.yml`](https://github.com/Doge2077/lite-monitor/blob/main/docker-compose.yml) on the target server 129 | 130 | MySQL Initialization: 131 | 132 | - Run the import SQL script, the file is [`lite-monitor-db.sql`](https://github.com/Doge2077/lite-monitor/blob/main/lite-monitor-db.sql) 133 | 134 | InfluxDB Initialization: 135 | 136 | - Access the host console on port `8086` 137 | - Set user to `admin`, password `monitorinfluxdbadmin` 138 | - Create a new Bucket named `lite-monitor`, we recommend a 7-day expiration time 139 | 140 | The default login user is `admin` with the default password `123456`, you can modify the email and password on the [Security] management page 141 | 142 | **** 143 | 144 | ## Client Deployment 145 | 146 | **** 147 | 148 | ### Environment Dependencies 149 | 150 | **** 151 | 152 | - JDK17 153 | - SpringBoot3 154 | 155 | **** 156 | 157 | ### Manual Deployment 158 | 159 | **** 160 | 161 | - Pull the repository locally, run `maven` build for `lite-monitor-client` 162 | - Package with `maven`, ensure `prod` environment config is selected and tests are skipped 163 | - Upload the packaged backend `jar` to the host that needs monitoring. The host needs the `Java17` runtime environment 164 | - The first run will create a `config-local` directory in the current directory, requiring registration with the server. Enter server host `ip:port` and the `token` generated by the server 165 | - Support for registering the client as a `systemd` service, specific configurations can refer to other documentation -------------------------------------------------------------------------------- /lite-monitor-server/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 3.1.2 9 | 10 | 11 | com.example 12 | lite-monitor-server 13 | 0.0.1-SNAPSHOT 14 | my-project-backend 15 | lite-monitor-server 16 | 17 | 17 18 | 19 | 20 | 21 | 22 | org.springframework.boot 23 | spring-boot-starter-mail 24 | 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter-validation 29 | 30 | 31 | 32 | org.springframework.boot 33 | spring-boot-starter-security 34 | 35 | 36 | 37 | org.springframework.boot 38 | spring-boot-starter-web 39 | 40 | 41 | 42 | org.springframework.boot 43 | spring-boot-starter-data-redis 44 | 45 | 46 | 47 | com.baomidou 48 | mybatis-plus-boot-starter 49 | 3.5.3.1 50 | 51 | 52 | 53 | com.mysql 54 | mysql-connector-j 55 | runtime 56 | 57 | 58 | 59 | org.projectlombok 60 | lombok 61 | true 62 | 63 | 64 | 65 | org.springframework.boot 66 | spring-boot-starter-test 67 | test 68 | 69 | 70 | org.springframework.security 71 | spring-security-test 72 | test 73 | 74 | 75 | 76 | org.springframework.boot 77 | spring-boot-starter-amqp 78 | 79 | 80 | 81 | com.alibaba.fastjson2 82 | fastjson2 83 | 2.0.25 84 | 85 | 86 | 87 | com.auth0 88 | java-jwt 89 | 4.3.0 90 | 91 | 92 | 93 | org.springdoc 94 | springdoc-openapi-starter-webmvc-ui 95 | 2.1.0 96 | 97 | 98 | com.influxdb 99 | influxdb-client-java 100 | 6.6.0 101 | 102 | 103 | org.springframework.boot 104 | spring-boot-starter-websocket 105 | 106 | 107 | com.jcraft 108 | jsch 109 | 0.1.55 110 | 111 | 112 | 113 | 114 | 115 | 116 | dev 117 | 118 | true 119 | 120 | 121 | dev 122 | 123 | 124 | 125 | 126 | prod 127 | 128 | false 129 | 130 | 131 | prod 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | org.graalvm.buildtools 140 | native-maven-plugin 141 | 142 | 143 | org.springframework.boot 144 | spring-boot-maven-plugin 145 | 146 | 147 | 148 | org.projectlombok 149 | lombok 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | src/main/resources 158 | 159 | application*.yml 160 | 161 | 162 | 163 | src/main/resources 164 | true 165 | 166 | application.yml 167 | application-${environment}.yml 168 | 169 | 170 | 171 | 172 | 173 | 174 | -------------------------------------------------------------------------------- /lite-monitor-server/src/main/java/com/example/utils/JwtUtils.java: -------------------------------------------------------------------------------- 1 | package com.example.utils; 2 | 3 | import com.auth0.jwt.JWT; 4 | import com.auth0.jwt.JWTVerifier; 5 | import com.auth0.jwt.algorithms.Algorithm; 6 | import com.auth0.jwt.exceptions.JWTVerificationException; 7 | import com.auth0.jwt.interfaces.Claim; 8 | import com.auth0.jwt.interfaces.DecodedJWT; 9 | import jakarta.annotation.Resource; 10 | import org.springframework.beans.factory.annotation.Value; 11 | import org.springframework.data.redis.core.StringRedisTemplate; 12 | import org.springframework.security.core.GrantedAuthority; 13 | import org.springframework.security.core.userdetails.User; 14 | import org.springframework.security.core.userdetails.UserDetails; 15 | import org.springframework.stereotype.Component; 16 | 17 | import java.util.Calendar; 18 | import java.util.Date; 19 | import java.util.Map; 20 | import java.util.UUID; 21 | import java.util.concurrent.TimeUnit; 22 | 23 | /** 24 | * 用于处理Jwt令牌的工具类 25 | */ 26 | @Component 27 | public class JwtUtils { 28 | 29 | //用于给Jwt令牌签名校验的秘钥 30 | @Value("${spring.security.jwt.key}") 31 | private String key; 32 | //令牌的过期时间,以小时为单位 33 | @Value("${spring.security.jwt.expire}") 34 | private int expire; 35 | //为用户生成Jwt令牌的冷却时间,防止刷接口频繁登录生成令牌,以秒为单位 36 | @Value("${spring.security.jwt.limit.base}") 37 | private int limit_base; 38 | //用户如果继续恶意刷令牌,更严厉的封禁时间 39 | @Value("${spring.security.jwt.limit.upgrade}") 40 | private int limit_upgrade; 41 | //判定用户在冷却时间内,继续恶意刷令牌的次数 42 | @Value("${spring.security.jwt.limit.frequency}") 43 | private int limit_frequency; 44 | 45 | @Resource 46 | StringRedisTemplate template; 47 | 48 | @Resource 49 | FlowUtils utils; 50 | 51 | /** 52 | * 让指定Jwt令牌失效 53 | * @param headerToken 请求头中携带的令牌 54 | * @return 是否操作成功 55 | */ 56 | public boolean invalidateJwt(String headerToken){ 57 | String token = this.convertToken(headerToken); 58 | Algorithm algorithm = Algorithm.HMAC256(key); 59 | JWTVerifier jwtVerifier = JWT.require(algorithm).build(); 60 | try { 61 | DecodedJWT verify = jwtVerifier.verify(token); 62 | return deleteToken(verify.getId(), verify.getExpiresAt()); 63 | } catch (JWTVerificationException e) { 64 | return false; 65 | } 66 | } 67 | 68 | /** 69 | * 根据配置快速计算过期时间 70 | * @return 过期时间 71 | */ 72 | public Date expireTime() { 73 | Calendar calendar = Calendar.getInstance(); 74 | calendar.add(Calendar.HOUR, expire); 75 | return calendar.getTime(); 76 | } 77 | 78 | /** 79 | * 根据UserDetails生成对应的Jwt令牌 80 | * @param user 用户信息 81 | * @return 令牌 82 | */ 83 | public String createJwt(UserDetails user, String username, int userId) { 84 | if(this.frequencyCheck(userId)) { 85 | Algorithm algorithm = Algorithm.HMAC256(key); 86 | Date expire = this.expireTime(); 87 | return JWT.create() 88 | .withJWTId(UUID.randomUUID().toString()) 89 | .withClaim("id", userId) 90 | .withClaim("name", username) 91 | .withClaim("authorities", user.getAuthorities() 92 | .stream() 93 | .map(GrantedAuthority::getAuthority).toList()) 94 | .withExpiresAt(expire) 95 | .withIssuedAt(new Date()) 96 | .sign(algorithm); 97 | } else { 98 | return null; 99 | } 100 | } 101 | 102 | /** 103 | * 解析Jwt令牌 104 | * @param headerToken 请求头中携带的令牌 105 | * @return DecodedJWT 106 | */ 107 | public DecodedJWT resolveJwt(String headerToken){ 108 | String token = this.convertToken(headerToken); 109 | if(token == null) return null; 110 | Algorithm algorithm = Algorithm.HMAC256(key); 111 | JWTVerifier jwtVerifier = JWT.require(algorithm).build(); 112 | try { 113 | DecodedJWT verify = jwtVerifier.verify(token); 114 | if(this.isInvalidToken(verify.getId())) return null; 115 | if(this.isInvalidUser(verify.getClaim("id").asInt())) return null; 116 | Map claims = verify.getClaims(); 117 | return new Date().after(claims.get("exp").asDate()) ? null : verify; 118 | } catch (JWTVerificationException e) { 119 | return null; 120 | } 121 | } 122 | 123 | /** 124 | * 将jwt对象中的内容封装为UserDetails 125 | * @param jwt 已解析的Jwt对象 126 | * @return UserDetails 127 | */ 128 | public UserDetails toUser(DecodedJWT jwt) { 129 | Map claims = jwt.getClaims(); 130 | return User 131 | .withUsername(claims.get("name").asString()) 132 | .password("******") 133 | .authorities(claims.get("authorities").asArray(String.class)) 134 | .build(); 135 | } 136 | 137 | /** 138 | * 将jwt对象中的用户ID提取出来 139 | * @param jwt 已解析的Jwt对象 140 | * @return 用户ID 141 | */ 142 | public Integer toId(DecodedJWT jwt) { 143 | Map claims = jwt.getClaims(); 144 | return claims.get("id").asInt(); 145 | } 146 | 147 | /** 148 | * 频率检测,防止用户高频申请Jwt令牌,并且采用阶段封禁机制 149 | * 如果已经提示无法登录的情况下用户还在刷,那么就封禁更长时间 150 | * @param userId 用户ID 151 | * @return 是否通过频率检测 152 | */ 153 | private boolean frequencyCheck(int userId){ 154 | String key = Const.JWT_FREQUENCY + userId; 155 | return utils.limitOnceUpgradeCheck(key, limit_frequency, limit_base, limit_upgrade); 156 | } 157 | 158 | /** 159 | * 校验并转换请求头中的Token令牌 160 | * @param headerToken 请求头中的Token 161 | * @return 转换后的令牌 162 | */ 163 | private String convertToken(String headerToken){ 164 | if(headerToken == null || !headerToken.startsWith("Bearer ")) 165 | return null; 166 | return headerToken.substring(7); 167 | } 168 | 169 | /** 170 | * 将Token列入Redis黑名单中 171 | * @param uuid 令牌ID 172 | * @param time 过期时间 173 | * @return 是否操作成功 174 | */ 175 | private boolean deleteToken(String uuid, Date time){ 176 | if(this.isInvalidToken(uuid)) 177 | return false; 178 | Date now = new Date(); 179 | long expire = Math.max(time.getTime() - now.getTime(), 0); 180 | template.opsForValue().set(Const.JWT_BLACK_LIST + uuid, "", expire, TimeUnit.MILLISECONDS); 181 | return true; 182 | } 183 | 184 | public void deleteUser(int uid) { 185 | template.opsForValue().set(Const.USER_BLACK_LIST + uid, "", expire, TimeUnit.HOURS); 186 | } 187 | 188 | private boolean isInvalidUser(int uid){ 189 | return Boolean.TRUE.equals(template.hasKey(Const.USER_BLACK_LIST + uid)); 190 | } 191 | 192 | /** 193 | * 验证Token是否被列入Redis黑名单 194 | * @param uuid 令牌ID 195 | * @return 是否操作成功 196 | */ 197 | private boolean isInvalidToken(String uuid){ 198 | return Boolean.TRUE.equals(template.hasKey(Const.JWT_BLACK_LIST + uuid)); 199 | } 200 | } 201 | --------------------------------------------------------------------------------