Compare commits
No commits in common. "main" and "dev" have entirely different histories.
6
.gitignore
vendored
6
.gitignore
vendored
@ -1,4 +1,2 @@
|
||||
.idea
|
||||
log
|
||||
.DS_Store
|
||||
config
|
||||
.idea/
|
||||
log/
|
||||
|
@ -9,9 +9,9 @@
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<groupId>com.example</groupId>
|
||||
<artifactId>itbaima-monitor-server</artifactId>
|
||||
<artifactId>itbaima-monitor-backend</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>itbaima-monitor-server</name>
|
||||
<name>my-project-backend</name>
|
||||
<description>my-project-backend</description>
|
||||
<properties>
|
||||
<java.version>17</java.version>
|
||||
@ -94,21 +94,6 @@
|
||||
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||
<version>2.1.0</version>
|
||||
</dependency>
|
||||
<!-- InfluxDB客户端框架 -->
|
||||
<dependency>
|
||||
<groupId>com.influxdb</groupId>
|
||||
<artifactId>influxdb-client-java</artifactId>
|
||||
<version>6.6.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-websocket</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.jcraft</groupId>
|
||||
<artifactId>jsch</artifactId>
|
||||
<version>0.1.55</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<profiles>
|
@ -4,10 +4,10 @@ import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class MonitorServerApplication {
|
||||
public class MyProjectBackendApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(MonitorServerApplication.class, args);
|
||||
SpringApplication.run(MyProjectBackendApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
@ -53,12 +53,9 @@ public class SecurityConfiguration {
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
return http
|
||||
.authorizeHttpRequests(conf -> conf
|
||||
.requestMatchers("/terminal/**").permitAll()
|
||||
.requestMatchers("/api/auth/**", "/error").permitAll()
|
||||
.requestMatchers("/monitor/**").permitAll()
|
||||
.requestMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll()
|
||||
.requestMatchers("/api/user/sub/**").hasRole(Const.ROLE_ADMIN)
|
||||
.anyRequest().hasAnyRole(Const.ROLE_ADMIN, Const.ROLE_NORMAL)
|
||||
.anyRequest().hasAnyRole(Const.ROLE_DEFAULT)
|
||||
)
|
||||
.formLogin(conf -> conf
|
||||
.loginProcessingUrl("/api/auth/login")
|
@ -38,7 +38,7 @@ public class AuthorizeController {
|
||||
@GetMapping("/ask-code")
|
||||
@Operation(summary = "请求邮件验证码")
|
||||
public RestBean<Void> askVerifyCode(@RequestParam @Email String email,
|
||||
@RequestParam @Pattern(regexp = "(reset|modify)") String type,
|
||||
@RequestParam @Pattern(regexp = "(reset)") String type,
|
||||
HttpServletRequest request){
|
||||
return this.messageHandle(() ->
|
||||
accountService.registerEmailVerifyCode(type, String.valueOf(email), request.getRemoteAddr()));
|
@ -0,0 +1,25 @@
|
||||
package com.example.controller;
|
||||
|
||||
import com.example.entity.RestBean;
|
||||
import com.example.entity.vo.request.RegisterServerVO;
|
||||
import com.example.service.ServerService;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/server")
|
||||
public class ServerController {
|
||||
|
||||
@Resource
|
||||
ServerService service;
|
||||
|
||||
@PostMapping("/register")
|
||||
public RestBean<Void> register(@Valid @RequestBody RegisterServerVO vo) {
|
||||
return service.registerServer(vo) ?
|
||||
RestBean.success() : RestBean.failure(401, "注册码填写错误,注册主机失败");
|
||||
}
|
||||
}
|
@ -34,10 +34,6 @@ public record RestBean<T> (long id, int code, T data, String message) {
|
||||
return new RestBean<>(requestId(), code, null, message);
|
||||
}
|
||||
|
||||
public static <T> RestBean<T> noPermission() {
|
||||
return new RestBean<>(requestId(), 401, null, "权限不足,拒绝访问");
|
||||
}
|
||||
|
||||
/**
|
||||
* 快速将当前实体转换为JSON字符串格式
|
||||
* @return JSON字符串
|
@ -1,6 +1,5 @@
|
||||
package com.example.entity.dto;
|
||||
|
||||
import com.alibaba.fastjson2.JSONArray;
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
@ -8,9 +7,7 @@ import com.example.entity.BaseData;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 数据库中的用户信息
|
||||
@ -26,10 +23,4 @@ public class Account implements BaseData {
|
||||
String email;
|
||||
String role;
|
||||
Date registerTime;
|
||||
String clients;
|
||||
|
||||
public List<Integer> getClientList() {
|
||||
if(clients == null) return Collections.emptyList();
|
||||
return JSONArray.parse(clients).toList(Integer.class);
|
||||
}
|
||||
}
|
@ -2,21 +2,17 @@ package com.example.entity.dto;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.example.entity.BaseData;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@Data
|
||||
@TableName("db_client")
|
||||
@AllArgsConstructor
|
||||
public class Client implements BaseData {
|
||||
@TableName("db_server")
|
||||
public class Server {
|
||||
@TableId
|
||||
Integer id;
|
||||
int id;
|
||||
String name;
|
||||
String token;
|
||||
String location;
|
||||
String node;
|
||||
Date registerTime;
|
||||
Date time;
|
||||
}
|
@ -5,15 +5,14 @@ import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@TableName("db_client_detail")
|
||||
public class ClientDetail {
|
||||
@TableName("db_server_info")
|
||||
public class ServerInfo {
|
||||
@TableId
|
||||
Integer id;
|
||||
int id;
|
||||
String osArch;
|
||||
String osName;
|
||||
String osVersion;
|
||||
int osBit;
|
||||
String cpuName;
|
||||
int cpuCore;
|
||||
double memory;
|
||||
double disk;
|
@ -1,7 +1,6 @@
|
||||
package com.example.entity.vo.request;
|
||||
|
||||
import jakarta.validation.constraints.Email;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import lombok.Data;
|
||||
import org.hibernate.validator.constraints.Length;
|
||||
|
@ -4,7 +4,7 @@ import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class ClientDetailVO {
|
||||
public class RegisterServerVO {
|
||||
@NotNull
|
||||
String osArch;
|
||||
@NotNull
|
||||
@ -14,8 +14,6 @@ public class ClientDetailVO {
|
||||
@NotNull
|
||||
int osBit;
|
||||
@NotNull
|
||||
String cpuName;
|
||||
@NotNull
|
||||
int cpuCore;
|
||||
@NotNull
|
||||
double memory;
|
||||
@ -23,4 +21,6 @@ public class ClientDetailVO {
|
||||
double disk;
|
||||
@NotNull
|
||||
String ip;
|
||||
@NotNull
|
||||
String accessToken;
|
||||
}
|
@ -10,7 +10,6 @@ import java.util.Date;
|
||||
@Data
|
||||
public class AuthorizeVO {
|
||||
String username;
|
||||
String email;
|
||||
String role;
|
||||
String token;
|
||||
Date expire;
|
@ -0,0 +1,46 @@
|
||||
package com.example.filter;
|
||||
|
||||
import com.auth0.jwt.interfaces.DecodedJWT;
|
||||
import com.example.utils.Const;
|
||||
import com.example.utils.JwtUtils;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 用于对请求头中Jwt令牌进行校验的工具,为当前请求添加用户验证信息
|
||||
* 并将用户的ID存放在请求对象属性中,方便后续使用
|
||||
*/
|
||||
@Component
|
||||
public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||
|
||||
@Resource
|
||||
JwtUtils utils;
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
FilterChain filterChain) throws ServletException, IOException {
|
||||
String authorization = request.getHeader("Authorization");
|
||||
DecodedJWT jwt = utils.resolveJwt(authorization);
|
||||
if(jwt != null) {
|
||||
UserDetails user = utils.toUser(jwt);
|
||||
UsernamePasswordAuthenticationToken authentication =
|
||||
new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
|
||||
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
request.setAttribute(Const.ATTR_USER_ID, utils.toId(jwt));
|
||||
}
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
}
|
@ -29,8 +29,7 @@ public class RequestLogFilter extends OncePerRequestFilter {
|
||||
@Resource
|
||||
SnowflakeIdGenerator generator;
|
||||
|
||||
private final Set<String> ignores = Set.of("/swagger-ui", "/v3/api-docs", "/monitor/runtime",
|
||||
"/api/monitor/list", "/api/monitor/runtime-now");
|
||||
private final Set<String> ignores = Set.of("/swagger-ui", "/v3/api-docs");
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
|
@ -36,10 +36,6 @@ public class MailQueueListener {
|
||||
createMessage("您的密码重置邮件",
|
||||
"你好,您正在执行重置密码操作,验证码: "+code+",有效时间3分钟,如非本人操作,请无视。",
|
||||
email);
|
||||
case "modify" ->
|
||||
createMessage("您的邮件修改验证邮件",
|
||||
"您好,您正在绑定新的电子邮件地址,验证码: "+code+",有效时间3分钟,如非本人操作,请无视",
|
||||
email);
|
||||
default -> null;
|
||||
};
|
||||
if(message == null) return;
|
@ -1,9 +1,9 @@
|
||||
package com.example.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.example.entity.dto.ClientSsh;
|
||||
import com.example.entity.dto.ServerInfo;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface ClientSshMapper extends BaseMapper<ClientSsh> {
|
||||
public interface ServerInfoMapper extends BaseMapper<ServerInfo> {
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
package com.example.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.example.entity.dto.Client;
|
||||
import com.example.entity.dto.Server;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface ClientMapper extends BaseMapper<Client> {
|
||||
public interface ServerMapper extends BaseMapper<Server> {
|
||||
}
|
@ -3,22 +3,12 @@ package com.example.service;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.example.entity.dto.Account;
|
||||
import com.example.entity.vo.request.ConfirmResetVO;
|
||||
import com.example.entity.vo.request.CreateSubAccountVO;
|
||||
import com.example.entity.vo.request.EmailResetVO;
|
||||
import com.example.entity.vo.request.ModifyEmailVO;
|
||||
import com.example.entity.vo.response.SubAccountVO;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface AccountService extends IService<Account>, UserDetailsService {
|
||||
Account findAccountByNameOrEmail(String text);
|
||||
String registerEmailVerifyCode(String type, String email, String address);
|
||||
String resetEmailAccountPassword(EmailResetVO info);
|
||||
String resetConfirm(ConfirmResetVO info);
|
||||
boolean changePassword(int id, String oldPass, String newPass);
|
||||
void createSubAccount(CreateSubAccountVO vo);
|
||||
void deleteSubAccount(int uid);
|
||||
List<SubAccountVO> listSubAccount();
|
||||
String modifyEmail(int id, ModifyEmailVO vo);
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package com.example.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.example.entity.dto.Server;
|
||||
import com.example.entity.vo.request.RegisterServerVO;
|
||||
|
||||
public interface ServerService extends IService<Server> {
|
||||
boolean registerServer(RegisterServerVO vo);
|
||||
}
|
@ -1,14 +1,10 @@
|
||||
package com.example.service.impl;
|
||||
|
||||
import com.alibaba.fastjson2.JSONArray;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.example.entity.dto.Account;
|
||||
import com.example.entity.vo.request.ConfirmResetVO;
|
||||
import com.example.entity.vo.request.CreateSubAccountVO;
|
||||
import com.example.entity.vo.request.EmailResetVO;
|
||||
import com.example.entity.vo.request.ModifyEmailVO;
|
||||
import com.example.entity.vo.response.SubAccountVO;
|
||||
import com.example.mapper.AccountMapper;
|
||||
import com.example.service.AccountService;
|
||||
import com.example.utils.Const;
|
||||
@ -23,8 +19,6 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@ -122,61 +116,6 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean changePassword(int id, String oldPass, String newPass) {
|
||||
Account account = this.getById(id);
|
||||
String password = account.getPassword();
|
||||
if(!passwordEncoder.matches(oldPass, password))
|
||||
return false;
|
||||
this.update(Wrappers.<Account>update().eq("id", id)
|
||||
.set("password", passwordEncoder.encode(newPass)));
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createSubAccount(CreateSubAccountVO vo) {
|
||||
Account account = this.findAccountByNameOrEmail(vo.getEmail());
|
||||
if(account != null)
|
||||
throw new IllegalArgumentException("该电子邮件已被注册");
|
||||
account = this.findAccountByNameOrEmail(vo.getUsername());
|
||||
if(account != null)
|
||||
throw new IllegalArgumentException("该用户名已被注册");
|
||||
account = new Account(null, vo.getUsername(), passwordEncoder.encode(vo.getPassword()),
|
||||
vo.getEmail(), Const.ROLE_NORMAL, new Date(), JSONArray.copyOf(vo.getClients()).toJSONString());
|
||||
this.save(account);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteSubAccount(int uid) {
|
||||
this.removeById(uid);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SubAccountVO> listSubAccount() {
|
||||
return this.list(Wrappers.<Account>query().eq("role", Const.ROLE_NORMAL))
|
||||
.stream().map(account -> {
|
||||
SubAccountVO vo = account.asViewObject(SubAccountVO.class);
|
||||
vo.setClientList(JSONArray.parse(account.getClients()));
|
||||
return vo;
|
||||
}).toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String modifyEmail(int id, ModifyEmailVO vo) {
|
||||
String code = getEmailVerifyCode(vo.getEmail());
|
||||
if (code == null) return "请先获取验证码";
|
||||
if(!code.equals(vo.getCode())) return "验证码错误,请重新输入";
|
||||
this.deleteEmailVerifyCode(vo.getEmail());
|
||||
Account account = this.findAccountByNameOrEmail(vo.getEmail());
|
||||
if(account != null && account.getId() != id) return "该邮箱账号已经被其他账号绑定,无法完成操作";
|
||||
this.update()
|
||||
.set("email", vo.getEmail())
|
||||
.eq("id", id)
|
||||
.update();
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除Redis中存储的邮件验证码
|
||||
* @param email 电邮
|
||||
@ -217,4 +156,22 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
|
||||
.eq("email", text)
|
||||
.one();
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询指定邮箱的用户是否已经存在
|
||||
* @param email 邮箱
|
||||
* @return 是否存在
|
||||
*/
|
||||
private boolean existsAccountByEmail(String email){
|
||||
return this.baseMapper.exists(Wrappers.<Account>query().eq("email", email));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询指定用户名的用户是否已经存在
|
||||
* @param username 用户名
|
||||
* @return 是否存在
|
||||
*/
|
||||
private boolean existsAccountByUsername(String username){
|
||||
return this.baseMapper.exists(Wrappers.<Account>query().eq("username", username));
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package com.example.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.example.entity.dto.Server;
|
||||
import com.example.entity.dto.ServerInfo;
|
||||
import com.example.entity.vo.request.RegisterServerVO;
|
||||
import com.example.mapper.ServerInfoMapper;
|
||||
import com.example.mapper.ServerMapper;
|
||||
import com.example.service.ServerService;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Random;
|
||||
|
||||
@Service
|
||||
public class ServerServiceImpl extends ServiceImpl<ServerMapper, Server> implements ServerService {
|
||||
|
||||
@Mapper
|
||||
ServerInfoMapper infoMapper;
|
||||
|
||||
@Override
|
||||
public boolean registerServer(RegisterServerVO vo) {
|
||||
if(this.verifyAccessToken(vo.getAccessToken())) {
|
||||
int id = createRandomId();
|
||||
Server server = new Server(id, "未命名服务器", new Date());
|
||||
ServerInfo info = new ServerInfo();
|
||||
BeanUtils.copyProperties(vo, info);
|
||||
info.setId(id);
|
||||
baseMapper.insert(server);
|
||||
infoMapper.insert(info);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private int createRandomId() {
|
||||
Random random = new Random();
|
||||
return random.nextInt(900000) + 100000;
|
||||
}
|
||||
|
||||
private boolean verifyAccessToken(String token) {
|
||||
return "123456".equals(token);
|
||||
}
|
||||
}
|
@ -7,8 +7,6 @@ public final class Const {
|
||||
//JWT令牌
|
||||
public final static String JWT_BLACK_LIST = "jwt:blacklist:";
|
||||
public final static String JWT_FREQUENCY = "jwt:frequency:";
|
||||
//用户
|
||||
public final static String USER_BLACK_LIST = "user:blacklist:";
|
||||
//请求频率限制
|
||||
public final static String FLOW_LIMIT_COUNTER = "flow:counter:";
|
||||
public final static String FLOW_LIMIT_BLOCK = "flow:block:";
|
||||
@ -20,11 +18,9 @@ public final class Const {
|
||||
public final static int ORDER_CORS = -102;
|
||||
//请求自定义属性
|
||||
public final static String ATTR_USER_ID = "userId";
|
||||
public final static String ATTR_USER_ROLE = "userRole";
|
||||
public final static String ATTR_CLIENT = "client";
|
||||
//消息队列
|
||||
public final static String MQ_MAIL = "mail";
|
||||
//用户角色
|
||||
public final static String ROLE_ADMIN = "admin";
|
||||
public final static String ROLE_NORMAL = "user";
|
||||
public final static String ROLE_DEFAULT = "user";
|
||||
|
||||
}
|
@ -109,7 +109,6 @@ public class JwtUtils {
|
||||
try {
|
||||
DecodedJWT verify = jwtVerifier.verify(token);
|
||||
if(this.isInvalidToken(verify.getId())) return null;
|
||||
if(this.isInvalidUser(verify.getClaim("id").asInt())) return null;
|
||||
Map<String, Claim> claims = verify.getClaims();
|
||||
return new Date().after(claims.get("exp").asDate()) ? null : verify;
|
||||
} catch (JWTVerificationException e) {
|
||||
@ -178,14 +177,6 @@ public class JwtUtils {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void deleteUser(int uid) {
|
||||
template.opsForValue().set(Const.USER_BLACK_LIST + uid, "", expire, TimeUnit.HOURS);
|
||||
}
|
||||
|
||||
private boolean isInvalidUser(int uid){
|
||||
return Boolean.TRUE.equals(template.hasKey(Const.USER_BLACK_LIST + uid));
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证Token是否被列入Redis黑名单
|
||||
* @param uuid 令牌ID
|
@ -4,10 +4,6 @@ springdoc:
|
||||
swagger-ui:
|
||||
operations-sorter: alpha
|
||||
spring:
|
||||
influx:
|
||||
url: http://localhost:8086
|
||||
user: admin
|
||||
password: 12345678
|
||||
mail:
|
||||
host: smtp.163.com
|
||||
username: javastudy111@163.com
|
||||
@ -36,8 +32,8 @@ spring:
|
||||
verify:
|
||||
mail-limit: 60
|
||||
flow:
|
||||
period: 5
|
||||
limit: 100
|
||||
period: 3
|
||||
limit: 50
|
||||
block: 30
|
||||
cors:
|
||||
origin: '*'
|
@ -8,10 +8,6 @@ mybatis-plus:
|
||||
configuration:
|
||||
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||
spring:
|
||||
influx:
|
||||
url: http://localhost:8086
|
||||
user: admin
|
||||
password: 12345678
|
||||
mail:
|
||||
host: smtp.163.com
|
||||
username: javastudy111@163.com
|
||||
@ -40,8 +36,8 @@ spring:
|
||||
verify:
|
||||
mail-limit: 60
|
||||
flow:
|
||||
period: 5
|
||||
limit: 100
|
||||
period: 3
|
||||
limit: 10
|
||||
block: 30
|
||||
cors:
|
||||
origin: '*'
|
@ -0,0 +1,13 @@
|
||||
package com.example;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
@SpringBootTest
|
||||
class MyProjectBackendApplicationTests {
|
||||
|
||||
@Test
|
||||
void contextLoads() {
|
||||
|
||||
}
|
||||
}
|
@ -1,68 +1,41 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<groupId>com.example</groupId>
|
||||
<artifactId>itbaima-monitor-client</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>itbaima-monitor-client</name>
|
||||
<description>itbaima-monitor-client</description>
|
||||
<properties>
|
||||
<java.version>17</java.version>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-quartz</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.fastjson2</groupId>
|
||||
<artifactId>fastjson2</artifactId>
|
||||
<version>2.0.37</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.oshi</groupId>
|
||||
<artifactId>oshi-core</artifactId>
|
||||
<version>6.4.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<excludes>
|
||||
<exclude>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</exclude>
|
||||
</excludes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>3.1.2</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<groupId>com.example</groupId>
|
||||
<artifactId>itbaima-monitor-client</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>my-project-backend</name>
|
||||
<description>my-project-backend</description>
|
||||
<properties>
|
||||
<java.version>17</java.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-quartz</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.oshi</groupId>
|
||||
<artifactId>oshi-core</artifactId>
|
||||
<version>6.4.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
@ -1,96 +0,0 @@
|
||||
package com.example.config;
|
||||
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.example.entity.ConnectionConfig;
|
||||
import com.example.utils.MonitorUtils;
|
||||
import com.example.utils.NetUtils;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.ApplicationArguments;
|
||||
import org.springframework.boot.ApplicationRunner;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.Scanner;
|
||||
|
||||
@Slf4j
|
||||
@Configuration
|
||||
public class ServerConfiguration implements ApplicationRunner {
|
||||
|
||||
@Resource
|
||||
NetUtils net;
|
||||
|
||||
@Resource
|
||||
MonitorUtils monitor;
|
||||
|
||||
@Bean
|
||||
ConnectionConfig connectionConfig() {
|
||||
log.info("正在加载服务端连接配置...");
|
||||
ConnectionConfig config = this.readConfigurationFromFile();
|
||||
if(config == null)
|
||||
config = this.registerToServer();
|
||||
return config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(ApplicationArguments args) {
|
||||
log.info("正在向服务端更新基本系统信息...");
|
||||
net.updateBaseDetails(monitor.monitorBaseDetail());
|
||||
}
|
||||
|
||||
private ConnectionConfig registerToServer() {
|
||||
Scanner scanner = new Scanner(System.in);
|
||||
String token, address, ifName;
|
||||
do {
|
||||
log.info("请输入需要注册的服务端访问地址,地址类似于 'http://192.168.0.22:8080' 这种写法:");
|
||||
address = scanner.nextLine();
|
||||
log.info("请输入服务端生成的用于注册客户端的Token秘钥:");
|
||||
token = scanner.nextLine();
|
||||
List<String> ifs = monitor.listNetworkInterfaceName();
|
||||
if(ifs.size() > 1) {
|
||||
log.info("检测到您的主机有多个网卡设备: {}", ifs);
|
||||
do {
|
||||
log.info("请选择需要监控的设备名称:");
|
||||
ifName = scanner.nextLine();
|
||||
} while (!ifs.contains(ifName));
|
||||
} else {
|
||||
ifName = ifs.get(0);
|
||||
}
|
||||
} while (!net.registerToServer(address, token));
|
||||
ConnectionConfig config = new ConnectionConfig(address, token, ifName);
|
||||
this.saveConfigurationToFile(config);
|
||||
return config;
|
||||
}
|
||||
|
||||
private void saveConfigurationToFile(ConnectionConfig config) {
|
||||
File dir = new File("config");
|
||||
if(!dir.exists() && dir.mkdir())
|
||||
log.info("创建用于保存服务端连接信息的目录已完成");
|
||||
File file = new File("config/server.json");
|
||||
try(FileWriter writer = new FileWriter(file)) {
|
||||
writer.write(JSONObject.from(config).toJSONString());
|
||||
} catch (IOException e) {
|
||||
log.error("保存配置文件时出现问题", e);
|
||||
}
|
||||
log.info("服务端连接信息已保存成功!");
|
||||
}
|
||||
|
||||
private ConnectionConfig readConfigurationFromFile() {
|
||||
File configurationFile = new File("config/server.json");
|
||||
if(configurationFile.exists()) {
|
||||
try (FileInputStream stream = new FileInputStream(configurationFile)){
|
||||
String raw = new String(stream.readAllBytes(), StandardCharsets.UTF_8);
|
||||
return JSONObject.parseObject(raw).to(ConnectionConfig.class);
|
||||
} catch (IOException e) {
|
||||
log.error("读取配置文件时出错", e);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
package com.example.entity;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public class ConnectionConfig {
|
||||
String address;
|
||||
String token;
|
||||
String networkInterface;
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
package com.example.entity;
|
||||
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
|
||||
public record Response(int id, int code, Object data, String message) {
|
||||
|
||||
public boolean success() {
|
||||
return code == 200;
|
||||
}
|
||||
|
||||
public JSONObject asJson() {
|
||||
return JSONObject.from(data);
|
||||
}
|
||||
|
||||
public String asString() {
|
||||
return data.toString();
|
||||
}
|
||||
|
||||
public static Response errorResponse(Exception e) {
|
||||
return new Response(0, 500, null, e.getMessage());
|
||||
}
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
package com.example.entity;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class RuntimeDetail {
|
||||
long timestamp;
|
||||
double cpuUsage;
|
||||
double memoryUsage;
|
||||
double diskUsage;
|
||||
double networkUpload;
|
||||
double networkDownload;
|
||||
double diskRead;
|
||||
double diskWrite;
|
||||
}
|
@ -1,131 +0,0 @@
|
||||
package com.example.utils;
|
||||
|
||||
import com.example.entity.BaseDetail;
|
||||
import com.example.entity.ConnectionConfig;
|
||||
import com.example.entity.RuntimeDetail;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
import oshi.SystemInfo;
|
||||
import oshi.hardware.CentralProcessor;
|
||||
import oshi.hardware.HWDiskStore;
|
||||
import oshi.hardware.HardwareAbstractionLayer;
|
||||
import oshi.hardware.NetworkIF;
|
||||
import oshi.software.os.OperatingSystem;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class MonitorUtils {
|
||||
|
||||
@Lazy
|
||||
@Resource
|
||||
ConnectionConfig config;
|
||||
|
||||
private final SystemInfo info = new SystemInfo();
|
||||
private final Properties properties = System.getProperties();
|
||||
|
||||
public BaseDetail monitorBaseDetail() {
|
||||
OperatingSystem os = info.getOperatingSystem();
|
||||
HardwareAbstractionLayer hardware = info.getHardware();
|
||||
double memory = hardware.getMemory().getTotal() / 1024.0 / 1024 /1024;
|
||||
double diskSize = Arrays.stream(File.listRoots()).mapToLong(File::getTotalSpace).sum() / 1024.0 / 1024 / 1024;
|
||||
String ip = Objects.requireNonNull(this.findNetworkInterface(hardware)).getIPv4addr()[0];
|
||||
return new BaseDetail()
|
||||
.setOsArch(properties.getProperty("os.arch"))
|
||||
.setOsName(os.getFamily())
|
||||
.setOsVersion(os.getVersionInfo().getVersion())
|
||||
.setOsBit(os.getBitness())
|
||||
.setCpuName(hardware.getProcessor().getProcessorIdentifier().getName())
|
||||
.setCpuCore(hardware.getProcessor().getLogicalProcessorCount())
|
||||
.setMemory(memory)
|
||||
.setDisk(diskSize)
|
||||
.setIp(ip);
|
||||
}
|
||||
|
||||
public RuntimeDetail monitorRuntimeDetail() {
|
||||
double statisticTime = 0.5;
|
||||
try {
|
||||
HardwareAbstractionLayer hardware = info.getHardware();
|
||||
NetworkIF networkInterface = Objects.requireNonNull(this.findNetworkInterface(hardware));
|
||||
CentralProcessor processor = hardware.getProcessor();
|
||||
double upload = networkInterface.getBytesSent(), download = networkInterface.getBytesRecv();
|
||||
double read = hardware.getDiskStores().stream().mapToLong(HWDiskStore::getReadBytes).sum();
|
||||
double write = hardware.getDiskStores().stream().mapToLong(HWDiskStore::getWriteBytes).sum();
|
||||
long[] ticks = processor.getSystemCpuLoadTicks();
|
||||
Thread.sleep((long) (statisticTime * 1000));
|
||||
networkInterface = Objects.requireNonNull(this.findNetworkInterface(hardware));
|
||||
upload = (networkInterface.getBytesSent() - upload) / statisticTime;
|
||||
download = (networkInterface.getBytesRecv() - download) / statisticTime;
|
||||
read = (hardware.getDiskStores().stream().mapToLong(HWDiskStore::getReadBytes).sum() - read) / statisticTime;
|
||||
write = (hardware.getDiskStores().stream().mapToLong(HWDiskStore::getWriteBytes).sum() - write) / statisticTime;
|
||||
double memory = (hardware.getMemory().getTotal() - hardware.getMemory().getAvailable()) / 1024.0 / 1024 / 1024;
|
||||
double disk = Arrays.stream(File.listRoots())
|
||||
.mapToLong(file -> file.getTotalSpace() - file.getFreeSpace()).sum() / 1024.0 / 1024 / 1024;
|
||||
return new RuntimeDetail()
|
||||
.setCpuUsage(this.calculateCpuUsage(processor, ticks))
|
||||
.setMemoryUsage(memory)
|
||||
.setDiskUsage(disk)
|
||||
.setNetworkUpload(upload / 1024)
|
||||
.setNetworkDownload(download / 1024)
|
||||
.setDiskRead(read / 1024/ 1024)
|
||||
.setDiskWrite(write / 1024 / 1024)
|
||||
.setTimestamp(new Date().getTime());
|
||||
} catch (Exception e) {
|
||||
log.error("读取运行时数据出现问题", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private double calculateCpuUsage(CentralProcessor processor, long[] prevTicks) {
|
||||
long[] ticks = processor.getSystemCpuLoadTicks();
|
||||
long nice = ticks[CentralProcessor.TickType.NICE.getIndex()]
|
||||
- prevTicks[CentralProcessor.TickType.NICE.getIndex()];
|
||||
long irq = ticks[CentralProcessor.TickType.IRQ.getIndex()]
|
||||
- prevTicks[CentralProcessor.TickType.IRQ.getIndex()];
|
||||
long softIrq = ticks[CentralProcessor.TickType.SOFTIRQ.getIndex()]
|
||||
- prevTicks[CentralProcessor.TickType.SOFTIRQ.getIndex()];
|
||||
long steal = ticks[CentralProcessor.TickType.STEAL.getIndex()]
|
||||
- prevTicks[CentralProcessor.TickType.STEAL.getIndex()];
|
||||
long cSys = ticks[CentralProcessor.TickType.SYSTEM.getIndex()]
|
||||
- prevTicks[CentralProcessor.TickType.SYSTEM.getIndex()];
|
||||
long cUser = ticks[CentralProcessor.TickType.USER.getIndex()]
|
||||
- prevTicks[CentralProcessor.TickType.USER.getIndex()];
|
||||
long ioWait = ticks[CentralProcessor.TickType.IOWAIT.getIndex()]
|
||||
- prevTicks[CentralProcessor.TickType.IOWAIT.getIndex()];
|
||||
long idle = ticks[CentralProcessor.TickType.IDLE.getIndex()]
|
||||
- prevTicks[CentralProcessor.TickType.IDLE.getIndex()];
|
||||
long totalCpu = cUser + nice + cSys + idle + ioWait + irq + softIrq + steal;
|
||||
return (cSys + cUser) * 1.0 / totalCpu;
|
||||
}
|
||||
|
||||
public List<String> listNetworkInterfaceName() {
|
||||
HardwareAbstractionLayer hardware = info.getHardware();
|
||||
return hardware.getNetworkIFs()
|
||||
.stream()
|
||||
.map(NetworkIF::getName)
|
||||
.toList();
|
||||
}
|
||||
|
||||
private NetworkIF findNetworkInterface(HardwareAbstractionLayer hardware) {
|
||||
try {
|
||||
String target = config.getNetworkInterface();
|
||||
List<NetworkIF> ifs = hardware.getNetworkIFs()
|
||||
.stream()
|
||||
.filter(inter -> inter.getName().equals(target))
|
||||
.toList();
|
||||
if (!ifs.isEmpty()) {
|
||||
return ifs.get(0);
|
||||
} else {
|
||||
throw new IOException("网卡信息错误,找不到网卡: " + target);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("读取网络接口信息时出错", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -1,87 +0,0 @@
|
||||
package com.example.utils;
|
||||
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.example.entity.BaseDetail;
|
||||
import com.example.entity.ConnectionConfig;
|
||||
import com.example.entity.Response;
|
||||
import com.example.entity.RuntimeDetail;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class NetUtils {
|
||||
private final HttpClient client = HttpClient.newHttpClient();
|
||||
|
||||
@Lazy
|
||||
@Resource
|
||||
ConnectionConfig config;
|
||||
|
||||
public boolean registerToServer(String address, String token) {
|
||||
log.info("正在像服务端注册,请稍后...");
|
||||
Response response = this.doGet("/register", address, token);
|
||||
if(response.success()) {
|
||||
log.info("客户端注册已完成!");
|
||||
} else {
|
||||
log.error("客户端注册失败: {}", response.message());
|
||||
}
|
||||
return response.success();
|
||||
}
|
||||
|
||||
private Response doGet(String url) {
|
||||
return this.doGet(url, config.getAddress(), config.getToken());
|
||||
}
|
||||
|
||||
private Response doGet(String url, String address, String token) {
|
||||
try {
|
||||
HttpRequest request = HttpRequest.newBuilder().GET()
|
||||
.uri(new URI(address + "/monitor" + url))
|
||||
.header("Authorization", token)
|
||||
.build();
|
||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
return JSONObject.parseObject(response.body()).to(Response.class);
|
||||
} catch (Exception e) {
|
||||
log.error("在发起服务端请求时出现问题", e);
|
||||
return Response.errorResponse(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void updateBaseDetails(BaseDetail detail) {
|
||||
Response response = this.doPost("/detail", detail);
|
||||
if(response.success()) {
|
||||
log.info("系统基本信息已更新完成");
|
||||
} else {
|
||||
log.error("系统基本信息更新失败: {}", response.message());
|
||||
}
|
||||
}
|
||||
|
||||
public void updateRuntimeDetails(RuntimeDetail detail) {
|
||||
Response response = this.doPost("/runtime", detail);
|
||||
if(!response.success()) {
|
||||
log.warn("更新运行时状态时,接收到服务端的异常响应内容: {}", response.message());
|
||||
}
|
||||
}
|
||||
|
||||
private Response doPost(String url, Object data) {
|
||||
try {
|
||||
String rawData = JSONObject.from(data).toJSONString();
|
||||
HttpRequest request = HttpRequest.newBuilder().POST(HttpRequest.BodyPublishers.ofString(rawData))
|
||||
.uri(new URI(config.getAddress() + "/monitor" + url))
|
||||
.header("Authorization", config.getToken())
|
||||
.header("Content-Type", "application/json")
|
||||
.build();
|
||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
return JSONObject.parseObject(response.body()).to(Response.class);
|
||||
} catch (Exception e) {
|
||||
log.error("在发起服务端请求时出现问题", e);
|
||||
return Response.errorResponse(e);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,13 +1,11 @@
|
||||
package com.example;
|
||||
package com.test;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class MonitorClientApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(MonitorClientApplication.class, args);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(MonitorClientApplication.class, args);
|
||||
}
|
||||
}
|
@ -1,15 +1,21 @@
|
||||
package com.example.config;
|
||||
package com.test.config;
|
||||
|
||||
import com.example.task.MonitorJobBean;
|
||||
import com.test.task.MonitorJobBean;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.quartz.*;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import oshi.SystemInfo;
|
||||
|
||||
@Slf4j
|
||||
@Configuration
|
||||
public class QuartzConfiguration {
|
||||
|
||||
@Bean
|
||||
public SystemInfo info(){
|
||||
return new SystemInfo();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public JobDetail jobDetailFactoryBean() {
|
||||
return JobBuilder.newJob(MonitorJobBean.class)
|
||||
@ -20,10 +26,10 @@ public class QuartzConfiguration {
|
||||
|
||||
@Bean
|
||||
public Trigger cronTriggerFactoryBean(JobDetail detail) {
|
||||
CronScheduleBuilder cron = CronScheduleBuilder.cronSchedule("*/10 * * * * ?");
|
||||
CronScheduleBuilder cron= CronScheduleBuilder.cronSchedule("*/1 * * * * ?");
|
||||
return TriggerBuilder.newTrigger()
|
||||
.forJob(detail)
|
||||
.withIdentity("monitor-trigger")
|
||||
.withIdentity("monitor-timer")
|
||||
.withSchedule(cron)
|
||||
.build();
|
||||
}
|
@ -1,16 +1,15 @@
|
||||
package com.example.entity;
|
||||
package com.test.entity;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class BaseDetail {
|
||||
public class SystemData {
|
||||
String osArch;
|
||||
String osName;
|
||||
String osVersion;
|
||||
int osBit;
|
||||
String cpuName;
|
||||
int cpuCore;
|
||||
double memory;
|
||||
double disk;
|
@ -1,9 +1,5 @@
|
||||
package com.example.task;
|
||||
package com.test.task;
|
||||
|
||||
import com.example.entity.RuntimeDetail;
|
||||
import com.example.utils.MonitorUtils;
|
||||
import com.example.utils.NetUtils;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.quartz.JobExecutionContext;
|
||||
import org.quartz.JobExecutionException;
|
||||
import org.springframework.scheduling.quartz.QuartzJobBean;
|
||||
@ -12,15 +8,8 @@ import org.springframework.stereotype.Component;
|
||||
@Component
|
||||
public class MonitorJobBean extends QuartzJobBean {
|
||||
|
||||
@Resource
|
||||
MonitorUtils monitor;
|
||||
|
||||
@Resource
|
||||
NetUtils net;
|
||||
|
||||
@Override
|
||||
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
|
||||
RuntimeDetail runtimeDetail = monitor.monitorRuntimeDetail();
|
||||
net.updateRuntimeDetails(runtimeDetail);
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package com.test.util;
|
||||
|
||||
public interface DataGetter<T> {
|
||||
T getData();
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
package com.test.util;
|
||||
|
||||
import com.test.entity.SystemData;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import oshi.SystemInfo;
|
||||
import oshi.hardware.HWDiskStore;
|
||||
import oshi.hardware.HardwareAbstractionLayer;
|
||||
import oshi.hardware.NetworkIF;
|
||||
import oshi.software.os.OperatingSystem;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.NetworkInterface;
|
||||
import java.util.Properties;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class SystemDataGetter implements DataGetter<SystemData>{
|
||||
|
||||
@Resource
|
||||
SystemInfo info;
|
||||
|
||||
private final Properties properties = System.getProperties();
|
||||
|
||||
public SystemData getData() {
|
||||
OperatingSystem os = info.getOperatingSystem();
|
||||
HardwareAbstractionLayer hardware = info.getHardware();
|
||||
double memory = hardware.getMemory().getTotal() / 1024.0 / 1024 / 1024;
|
||||
double diskSize = hardware.getDiskStores().stream().mapToLong(HWDiskStore::getSize).sum() / 1024.0 / 1024 / 1024;
|
||||
try {
|
||||
String ip = null;
|
||||
for (NetworkIF network : hardware.getNetworkIFs()) {
|
||||
String[] iPv4addr = network.getIPv4addr();
|
||||
NetworkInterface ni = network.queryNetworkInterface();
|
||||
if(!ni.isLoopback() && !ni.isPointToPoint() && ni.isUp() && !ni.isVirtual()
|
||||
&& (ni.getName().startsWith("eth") || ni.getName().startsWith("en"))
|
||||
&& iPv4addr.length > 0) {
|
||||
ip = network.getIPv4addr()[0];
|
||||
break;
|
||||
}
|
||||
}
|
||||
return new SystemData()
|
||||
.setOsBit(os.getBitness())
|
||||
.setOsArch(properties.getProperty("os.arch"))
|
||||
.setOsVersion(os.getVersionInfo().getVersion())
|
||||
.setOsName(os.getFamily())
|
||||
.setCpuCore(hardware.getProcessor().getLogicalProcessorCount())
|
||||
.setMemory(memory)
|
||||
.setDisk(diskSize)
|
||||
.setIp(ip);
|
||||
} catch (IOException e) {
|
||||
log.error("读取系统网络配置时出错", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -5,5 +5,6 @@
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="all" level="application" />
|
||||
</component>
|
||||
</module>
|
@ -11,15 +11,10 @@
|
||||
"@element-plus/icons-vue": "^2.1.0",
|
||||
"@vueuse/core": "^10.3.0",
|
||||
"axios": "^1.4.0",
|
||||
"echarts": "^5.4.3",
|
||||
"element-plus": "^2.3.9",
|
||||
"flag-icon-css": "^4.1.7",
|
||||
"pinia": "^2.1.7",
|
||||
"pinia-plugin-persistedstate": "^3.2.0",
|
||||
"vue": "^3.3.4",
|
||||
"vue-router": "^4.2.4",
|
||||
"xterm": "^5.3.0",
|
||||
"xterm-addon-attach": "^0.9.0"
|
||||
"vue-router": "^4.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^4.2.3",
|
||||
@ -873,20 +868,6 @@
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/echarts": {
|
||||
"version": "5.4.3",
|
||||
"resolved": "https://registry.npmmirror.com/echarts/-/echarts-5.4.3.tgz",
|
||||
"integrity": "sha512-mYKxLxhzy6zyTi/FaEbJMOZU1ULGEQHaeIeuMR5L+JnJTpz+YR03mnnpBhbR4+UYJAgiXgpyTVLffPAjOTLkZA==",
|
||||
"dependencies": {
|
||||
"tslib": "2.3.0",
|
||||
"zrender": "5.4.4"
|
||||
}
|
||||
},
|
||||
"node_modules/echarts/node_modules/tslib": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz",
|
||||
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="
|
||||
},
|
||||
"node_modules/element-plus": {
|
||||
"version": "2.3.9",
|
||||
"resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.3.9.tgz",
|
||||
@ -1444,12 +1425,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/needle": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmmirror.com/needle/-/needle-3.3.1.tgz",
|
||||
"integrity": "sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==",
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmmirror.com/needle/-/needle-3.2.0.tgz",
|
||||
"integrity": "sha512-oUvzXnyLiVyVGoianLijF9O/RecZUf7TkBfimjGrLM4eQhXyeJwM6GeAWccwfQ9aa4gMCZKqhAOuLaMIcQxajQ==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"debug": "^3.2.6",
|
||||
"iconv-lite": "^0.6.3",
|
||||
"sax": "^1.2.4"
|
||||
},
|
||||
@ -1460,6 +1442,16 @@
|
||||
"node": ">= 4.4.x"
|
||||
}
|
||||
},
|
||||
"node_modules/needle/node_modules/debug": {
|
||||
"version": "3.2.7",
|
||||
"resolved": "https://registry.npmmirror.com/debug/-/debug-3.2.7.tgz",
|
||||
"integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/normalize-path": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz",
|
||||
@ -1519,58 +1511,6 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/pinia": {
|
||||
"version": "2.1.7",
|
||||
"resolved": "https://registry.npmmirror.com/pinia/-/pinia-2.1.7.tgz",
|
||||
"integrity": "sha512-+C2AHFtcFqjPih0zpYuvof37SFxMQ7OEG2zV9jRI12i9BOy3YQVAHwdKtyyc8pDcDyIc33WCIsZaCFWU7WWxGQ==",
|
||||
"dependencies": {
|
||||
"@vue/devtools-api": "^6.5.0",
|
||||
"vue-demi": ">=0.14.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@vue/composition-api": "^1.4.0",
|
||||
"typescript": ">=4.4.4",
|
||||
"vue": "^2.6.14 || ^3.3.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@vue/composition-api": {
|
||||
"optional": true
|
||||
},
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/pinia-plugin-persistedstate": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmmirror.com/pinia-plugin-persistedstate/-/pinia-plugin-persistedstate-3.2.0.tgz",
|
||||
"integrity": "sha512-tZbNGf2vjAQcIm7alK40sE51Qu/m9oWr+rEgNm/2AWr1huFxj72CjvpQcIQzMknDBJEkQznCLAGtJTIcLKrKdw==",
|
||||
"peerDependencies": {
|
||||
"pinia": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/pinia/node_modules/vue-demi": {
|
||||
"version": "0.14.6",
|
||||
"resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.6.tgz",
|
||||
"integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==",
|
||||
"hasInstallScript": true,
|
||||
"bin": {
|
||||
"vue-demi-fix": "bin/vue-demi-fix.js",
|
||||
"vue-demi-switch": "bin/vue-demi-switch.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@vue/composition-api": "^1.0.0-rc.1",
|
||||
"vue": "^3.0.0-0 || ^2.6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@vue/composition-api": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/pkg-types": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-1.0.3.tgz",
|
||||
@ -1960,32 +1900,6 @@
|
||||
"resolved": "https://registry.npmmirror.com/webpack-virtual-modules/-/webpack-virtual-modules-0.5.0.tgz",
|
||||
"integrity": "sha512-kyDivFZ7ZM0BVOUteVbDFhlRt7Ah/CSPwJdi8hBpkK7QLumUqdLtVfm/PX/hkcnrvr0i77fO5+TjZ94Pe+C9iw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/xterm": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/xterm/-/xterm-5.3.0.tgz",
|
||||
"integrity": "sha512-8QqjlekLUFTrU6x7xck1MsPzPA571K5zNqWm0M0oroYEWVOptZ0+ubQSkQ3uxIEhcIHRujJy6emDWX4A7qyFzg=="
|
||||
},
|
||||
"node_modules/xterm-addon-attach": {
|
||||
"version": "0.9.0",
|
||||
"resolved": "https://registry.npmmirror.com/xterm-addon-attach/-/xterm-addon-attach-0.9.0.tgz",
|
||||
"integrity": "sha512-NykWWOsobVZPPK3P9eFkItrnBK9Lw0f94uey5zhqIVB1bhswdVBfl+uziEzSOhe2h0rT9wD0wOeAYsdSXeavPw==",
|
||||
"peerDependencies": {
|
||||
"xterm": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/zrender": {
|
||||
"version": "5.4.4",
|
||||
"resolved": "https://registry.npmmirror.com/zrender/-/zrender-5.4.4.tgz",
|
||||
"integrity": "sha512-0VxCNJ7AGOMCWeHVyTrGzUgrK4asT4ml9PEkeGirAkKNYXYzoPJCLvmyfdoOXcjTHPs10OZVMfD1Rwg16AZyYw==",
|
||||
"dependencies": {
|
||||
"tslib": "2.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/zrender/node_modules/tslib": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz",
|
||||
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="
|
||||
}
|
||||
}
|
||||
}
|
@ -11,15 +11,10 @@
|
||||
"@element-plus/icons-vue": "^2.1.0",
|
||||
"@vueuse/core": "^10.3.0",
|
||||
"axios": "^1.4.0",
|
||||
"echarts": "^5.4.3",
|
||||
"element-plus": "^2.3.9",
|
||||
"flag-icon-css": "^4.1.7",
|
||||
"pinia": "^2.1.7",
|
||||
"pinia-plugin-persistedstate": "^3.2.0",
|
||||
"vue": "^3.3.4",
|
||||
"vue-router": "^4.2.4",
|
||||
"xterm": "^5.3.0",
|
||||
"xterm-addon-attach": "^0.9.0"
|
||||
"vue-router": "^4.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^4.2.3",
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
109
itbaima-monitor-frontend/src/component/PreviewCard.vue
Normal file
109
itbaima-monitor-frontend/src/component/PreviewCard.vue
Normal file
@ -0,0 +1,109 @@
|
||||
<script setup>
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="instance-card">
|
||||
<div style="display: flex;justify-content: space-between">
|
||||
<div>
|
||||
<div class="title">
|
||||
<span class="flag-icon flag-icon-cn"></span>
|
||||
<span style="margin: 0 10px">柏码后端云服务器</span>
|
||||
<i class="fa-solid fa-pen-to-square"></i>
|
||||
</div>
|
||||
<div class="os">
|
||||
操作系统: Ubuntu 20.04
|
||||
</div>
|
||||
</div>
|
||||
<div class="status">
|
||||
<i style="color: #18cb18" class="fa-solid fa-circle-play"></i>
|
||||
<span style="margin-left: 5px">运行中</span>
|
||||
</div>
|
||||
</div>
|
||||
<el-divider style="margin: 10px 0"/>
|
||||
<div class="network">
|
||||
<span style="margin-right: 10px">公网IP: 192.168.0.10</span>
|
||||
<i style="color: dodgerblue" class="fa-solid fa-copy"></i>
|
||||
</div>
|
||||
<div class="hardware">
|
||||
<i class="fa-solid fa-microchip"></i>
|
||||
<span style="margin-right: 10px"> 2 CPU</span>
|
||||
<i class="fa-solid fa-memory"></i>
|
||||
<span> 4 GB</span>
|
||||
</div>
|
||||
<el-divider style="margin: 10px 0"/>
|
||||
<div class="progress">
|
||||
<span>CPU: 2.5 %</span>
|
||||
<el-progress :percentage="2.5" :stroke-width="5" :show-text="false"/>
|
||||
</div>
|
||||
<div class="progress" style="margin-top: 7px">
|
||||
<span>内存: <b>1.2</b> GB</span>
|
||||
<el-progress :percentage="1.2/4 * 100" :stroke-width="5" :show-text="false"/>
|
||||
</div>
|
||||
<div class="network-flow">
|
||||
<div>网络流量</div>
|
||||
<div>
|
||||
<i class="fa-solid fa-arrow-up"></i>
|
||||
<span> 52 KB/s</span>
|
||||
<el-divider direction="vertical"/>
|
||||
<i class="fa-solid fa-arrow-down"></i>
|
||||
<span> 272 KB/s</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
:deep(.el-progress-bar__outer) {
|
||||
background-color: #18cb1822;
|
||||
}
|
||||
|
||||
:deep(.el-progress-bar__inner) {
|
||||
background-color: #18cb18;
|
||||
}
|
||||
|
||||
.dark .instance-card { color: #d9d9d9
|
||||
}
|
||||
.instance-card {
|
||||
width: 320px;
|
||||
background-color: var(--el-bg-color);
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
box-sizing: border-box;
|
||||
color: #6b6b6b;
|
||||
|
||||
.os {
|
||||
font-size: 13px;
|
||||
color: grey;
|
||||
}
|
||||
|
||||
.status {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 15px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.network {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.hardware {
|
||||
margin-top: 5px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.progress {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.network-flow {
|
||||
margin-top: 10px;
|
||||
font-size: 12px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -3,19 +3,13 @@ import App from './App.vue'
|
||||
import router from './router'
|
||||
import axios from "axios";
|
||||
|
||||
import '@/assets/css/element.less'
|
||||
import 'flag-icon-css/css/flag-icons.min.css'
|
||||
import 'element-plus/theme-chalk/dark/css-vars.css'
|
||||
import {createPinia} from "pinia";
|
||||
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
|
||||
|
||||
axios.defaults.baseURL = 'http://localhost:8080'
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
const pinia = createPinia()
|
||||
app.use(pinia)
|
||||
pinia.use(piniaPluginPersistedstate)
|
||||
app.use(router)
|
||||
|
||||
app.mount('#app')
|
@ -1,6 +1,5 @@
|
||||
import axios from "axios";
|
||||
import {ElMessage} from "element-plus";
|
||||
import {useStore} from "@/store";
|
||||
|
||||
const authItemName = "authorize"
|
||||
|
||||
@ -75,10 +74,6 @@ function login(username, password, remember, success, failure = defaultFailure){
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
}, (data) => {
|
||||
storeAccessToken(remember, data.token, data.expire)
|
||||
const store = useStore()
|
||||
store.user.role = data.role
|
||||
store.user.username = data.username
|
||||
store.user.email = data.email
|
||||
ElMessage.success(`登录成功,欢迎 ${data.username} 来到我们的系统`)
|
||||
success(data)
|
||||
}, failure)
|
97
itbaima-monitor-frontend/src/views/IndexView.vue
Executable file
97
itbaima-monitor-frontend/src/views/IndexView.vue
Executable file
@ -0,0 +1,97 @@
|
||||
<template>
|
||||
<el-container class="main-container">
|
||||
<el-header class="main-header">
|
||||
<el-image style="height: 30px"
|
||||
src="https://element-plus.org/images/element-plus-logo.svg"/>
|
||||
<div class="tabs">
|
||||
<tab-item v-for="item in tabs"
|
||||
:name="item.name" :active="item.id === tab"
|
||||
@click="changePage(item)"/>
|
||||
</div>
|
||||
<el-switch style="margin: 0 20px"
|
||||
v-model="dark" active-color="#424242"
|
||||
:active-action-icon="Moon"
|
||||
:inactive-action-icon="Sunny"/>
|
||||
<el-dropdown>
|
||||
<el-avatar class="avatar"
|
||||
src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png"/>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click="userLogout">
|
||||
<el-icon><Back/></el-icon>
|
||||
退出登录
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</el-header>
|
||||
<el-main class="main-content">
|
||||
<router-view v-slot="{ Component }">
|
||||
<transition name="el-fade-in-linear" mode="out-in">
|
||||
<component :is="Component"/>
|
||||
</transition>
|
||||
</router-view>
|
||||
</el-main>
|
||||
</el-container>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { logout } from '@/net'
|
||||
import router from "@/router";
|
||||
import {ref} from "vue";
|
||||
import TabItem from "@/component/TabItem.vue";
|
||||
import {Back, Moon, Sunny} from "@element-plus/icons-vue";
|
||||
import {useDark} from "@vueuse/core";
|
||||
|
||||
function userLogout() {
|
||||
logout(() => router.push("/"))
|
||||
}
|
||||
|
||||
const dark = ref(useDark())
|
||||
const tab = ref(1)
|
||||
const tabs = [
|
||||
{id: 1, name: '管理', route: 'manage'},
|
||||
{id: 2, name: '安全', route: 'security'}
|
||||
]
|
||||
function changePage(item) {
|
||||
tab.value = item.id
|
||||
router.push({name: item.route})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.main-container {
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
|
||||
.main-header {
|
||||
height: 55px;
|
||||
background-color: var(--el-bg-color);
|
||||
border-bottom: solid 1px var(--el-border-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.tabs {
|
||||
height: 55px;
|
||||
gap: 10px;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: right;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
border: solid 1px var(--el-border-color);
|
||||
}
|
||||
}
|
||||
|
||||
.main-content {
|
||||
height: 100%;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
}
|
||||
|
||||
.dark .main-content {
|
||||
background-color: #232323;
|
||||
}
|
||||
</style>
|
@ -2,7 +2,7 @@
|
||||
<div style="width: 100vw;height: 100vh;overflow: hidden;display: flex">
|
||||
<div style="flex: 1">
|
||||
<el-image style="width: 100%;height: 100%" fit="cover"
|
||||
src="https://img1.baidu.com/it/u=4097856652,4033702227&fm=253&fmt=auto&app=120&f=JPEG?w=1422&h=800"/>
|
||||
src="https://img0.baidu.com/it/u=1201094067,1609021147&fm=253&fmt=auto&app=120&f=JPEG?w=1422&h=800"/>
|
||||
</div>
|
||||
<div class="welcome-title">
|
||||
<div style="font-size: 30px;font-weight: bold">欢迎来到我们的学习平台</div>
|
33
itbaima-monitor-frontend/src/views/main/Manage.vue
Normal file
33
itbaima-monitor-frontend/src/views/main/Manage.vue
Normal file
@ -0,0 +1,33 @@
|
||||
<script setup>
|
||||
|
||||
import PreviewCard from "@/component/PreviewCard.vue";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="manage-main">
|
||||
<div class="title"><i class="fa-solid fa-server"></i> 管理主机列表</div>
|
||||
<div class="description">在这里管理所有已经注册的主机实例,实时监控主机运行状态,快速进行管理和操作。</div>
|
||||
<el-divider style="margin: 10px 0"/>
|
||||
<div style="display: flex;gap: 20px">
|
||||
<preview-card/>
|
||||
<preview-card/>
|
||||
<preview-card/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.manage-main {
|
||||
margin: 0 50px;
|
||||
|
||||
.title {
|
||||
font-size: 22px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: 15px;
|
||||
color: grey;
|
||||
}
|
||||
}
|
||||
</style>
|
11
itbaima-monitor-frontend/src/views/main/Security.vue
Normal file
11
itbaima-monitor-frontend/src/views/main/Security.vue
Normal file
@ -0,0 +1,11 @@
|
||||
<script setup>
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -2,9 +2,7 @@
|
||||
<module type="GENERAL_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/log" />
|
||||
</content>
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
@ -1,13 +0,0 @@
|
||||
package com.example.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
|
||||
|
||||
@Configuration
|
||||
public class WebSocketConfiguration {
|
||||
@Bean
|
||||
public ServerEndpointExporter serverEndpointExporter() {
|
||||
return new ServerEndpointExporter();
|
||||
}
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
package com.example.controller;
|
||||
|
||||
import com.example.entity.RestBean;
|
||||
import com.example.entity.dto.Client;
|
||||
import com.example.entity.vo.request.ClientDetailVO;
|
||||
import com.example.entity.vo.request.RuntimeDetailVO;
|
||||
import com.example.service.ClientService;
|
||||
import com.example.utils.Const;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/monitor")
|
||||
public class ClientController {
|
||||
|
||||
@Resource
|
||||
ClientService service;
|
||||
|
||||
@GetMapping("/register")
|
||||
public RestBean<Void> registerClient(@RequestHeader("Authorization") String token) {
|
||||
return service.verifyAndRegister(token) ?
|
||||
RestBean.success() : RestBean.failure(401, "客户端注册失败,请检查Token是否正确");
|
||||
}
|
||||
|
||||
@PostMapping("/detail")
|
||||
public RestBean<Void> updateClientDetails(@RequestAttribute(Const.ATTR_CLIENT) Client client,
|
||||
@RequestBody @Valid ClientDetailVO vo) {
|
||||
service.updateClientDetail(vo, client);
|
||||
return RestBean.success();
|
||||
}
|
||||
|
||||
@PostMapping("/runtime")
|
||||
public RestBean<Void> updateRuntimeDetails(@RequestAttribute(Const.ATTR_CLIENT) Client client,
|
||||
@RequestBody @Valid RuntimeDetailVO vo) {
|
||||
service.updateRuntimeDetail(vo, client);
|
||||
return RestBean.success();
|
||||
}
|
||||
}
|
@ -1,166 +0,0 @@
|
||||
package com.example.controller;
|
||||
|
||||
import com.example.entity.RestBean;
|
||||
import com.example.entity.dto.Account;
|
||||
import com.example.entity.vo.request.RenameClientVO;
|
||||
import com.example.entity.vo.request.RenameNodeVO;
|
||||
import com.example.entity.vo.request.RuntimeDetailVO;
|
||||
import com.example.entity.vo.request.SshConnectionVO;
|
||||
import com.example.entity.vo.response.*;
|
||||
import com.example.service.AccountService;
|
||||
import com.example.service.ClientService;
|
||||
import com.example.utils.Const;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/monitor")
|
||||
public class MonitorController {
|
||||
|
||||
@Resource
|
||||
ClientService service;
|
||||
|
||||
@Resource
|
||||
AccountService accountService;
|
||||
|
||||
@GetMapping("/list")
|
||||
public RestBean<List<ClientPreviewVO>> listAllClient(@RequestAttribute(Const.ATTR_USER_ID) int userId,
|
||||
@RequestAttribute(Const.ATTR_USER_ROLE) String userRole) {
|
||||
List<ClientPreviewVO> clients = service.listClients();
|
||||
if(this.isAdminAccount(userRole)) {
|
||||
return RestBean.success(clients);
|
||||
} else {
|
||||
List<Integer> ids = this.accountAccessClients(userId);
|
||||
return RestBean.success(clients.stream()
|
||||
.filter(vo -> ids.contains(vo.getId()))
|
||||
.toList());
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("/simple-list")
|
||||
public RestBean<List<ClientSimpleVO>> simpleClientList(@RequestAttribute(Const.ATTR_USER_ROLE) String userRole) {
|
||||
if(this.isAdminAccount(userRole)) {
|
||||
return RestBean.success(service.listSimpleList());
|
||||
} else {
|
||||
return RestBean.noPermission();
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping("/rename")
|
||||
public RestBean<Void> renameClient(@RequestBody @Valid RenameClientVO vo,
|
||||
@RequestAttribute(Const.ATTR_USER_ID) int userId,
|
||||
@RequestAttribute(Const.ATTR_USER_ROLE) String userRole) {
|
||||
if(this.permissionCheck(userId, userRole, vo.getId())) {
|
||||
service.renameClient(vo);
|
||||
return RestBean.success();
|
||||
} else {
|
||||
return RestBean.noPermission();
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping("/node")
|
||||
public RestBean<Void> renameNode(@RequestBody @Valid RenameNodeVO vo,
|
||||
@RequestAttribute(Const.ATTR_USER_ID) int userId,
|
||||
@RequestAttribute(Const.ATTR_USER_ROLE) String userRole) {
|
||||
if(this.permissionCheck(userId, userRole, vo.getId())) {
|
||||
service.renameNode(vo);
|
||||
return RestBean.success();
|
||||
} else {
|
||||
return RestBean.noPermission();
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("/details")
|
||||
public RestBean<ClientDetailsVO> details(int clientId,
|
||||
@RequestAttribute(Const.ATTR_USER_ID) int userId,
|
||||
@RequestAttribute(Const.ATTR_USER_ROLE) String userRole) {
|
||||
if(this.permissionCheck(userId, userRole, clientId)) {
|
||||
return RestBean.success(service.clientDetails(clientId));
|
||||
} else {
|
||||
return RestBean.noPermission();
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("/runtime-history")
|
||||
public RestBean<RuntimeHistoryVO> runtimeDetailsHistory(int clientId,
|
||||
@RequestAttribute(Const.ATTR_USER_ID) int userId,
|
||||
@RequestAttribute(Const.ATTR_USER_ROLE) String userRole) {
|
||||
if(this.permissionCheck(userId, userRole, clientId)) {
|
||||
return RestBean.success(service.clientRuntimeDetailsHistory(clientId));
|
||||
} else {
|
||||
return RestBean.noPermission();
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("/runtime-now")
|
||||
public RestBean<RuntimeDetailVO> runtimeDetailsNow(int clientId,
|
||||
@RequestAttribute(Const.ATTR_USER_ID) int userId,
|
||||
@RequestAttribute(Const.ATTR_USER_ROLE) String userRole) {
|
||||
if(this.permissionCheck(userId, userRole, clientId)) {
|
||||
return RestBean.success(service.clientRuntimeDetailsNow(clientId));
|
||||
} else {
|
||||
return RestBean.noPermission();
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("/register")
|
||||
public RestBean<String> registerToken(@RequestAttribute(Const.ATTR_USER_ROLE) String userRole) {
|
||||
if (this.isAdminAccount(userRole)) {
|
||||
return RestBean.success(service.registerToken());
|
||||
} else {
|
||||
return RestBean.noPermission();
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("/delete")
|
||||
public RestBean<String> deleteClient(int clientId,
|
||||
@RequestAttribute(Const.ATTR_USER_ROLE) String userRole) {
|
||||
if (this.isAdminAccount(userRole)) {
|
||||
service.deleteClient(clientId);
|
||||
return RestBean.success();
|
||||
} else {
|
||||
return RestBean.noPermission();
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping("/ssh-save")
|
||||
public RestBean<Void> saveSshConnection(@RequestBody @Valid SshConnectionVO vo,
|
||||
@RequestAttribute(Const.ATTR_USER_ID) int userId,
|
||||
@RequestAttribute(Const.ATTR_USER_ROLE) String userRole) {
|
||||
if(this.permissionCheck(userId, userRole, vo.getId())) {
|
||||
service.saveClientSshConnection(vo);
|
||||
return RestBean.success();
|
||||
} else {
|
||||
return RestBean.noPermission();
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("/ssh")
|
||||
public RestBean<SshSettingsVO> sshSettings(int clientId,
|
||||
@RequestAttribute(Const.ATTR_USER_ID) int userId,
|
||||
@RequestAttribute(Const.ATTR_USER_ROLE) String userRole) {
|
||||
if(this.permissionCheck(userId, userRole, clientId)) {
|
||||
return RestBean.success(service.sshSettings(clientId));
|
||||
} else {
|
||||
return RestBean.noPermission();
|
||||
}
|
||||
}
|
||||
|
||||
private List<Integer> accountAccessClients(int uid) {
|
||||
Account account = accountService.getById(uid);
|
||||
return account.getClientList();
|
||||
}
|
||||
|
||||
private boolean isAdminAccount(String role) {
|
||||
role = role.substring(5);
|
||||
return Const.ROLE_ADMIN.equals(role);
|
||||
}
|
||||
|
||||
private boolean permissionCheck(int uid, String role, int clientId) {
|
||||
if(this.isAdminAccount(role)) return true;
|
||||
return this.accountAccessClients(uid).contains(clientId);
|
||||
}
|
||||
}
|
@ -1,60 +0,0 @@
|
||||
package com.example.controller;
|
||||
|
||||
import com.example.entity.RestBean;
|
||||
import com.example.entity.vo.request.ChangePasswordVO;
|
||||
import com.example.entity.vo.request.CreateSubAccountVO;
|
||||
import com.example.entity.vo.request.ModifyEmailVO;
|
||||
import com.example.entity.vo.response.SubAccountVO;
|
||||
import com.example.service.AccountService;
|
||||
import com.example.utils.Const;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/user")
|
||||
public class UserController {
|
||||
|
||||
@Resource
|
||||
AccountService service;
|
||||
|
||||
@PostMapping("/change-password")
|
||||
public RestBean<Void> changePassword(@RequestBody @Valid ChangePasswordVO vo,
|
||||
@RequestAttribute(Const.ATTR_USER_ID) int userId) {
|
||||
return service.changePassword(userId, vo.getPassword(), vo.getNew_password()) ?
|
||||
RestBean.success() : RestBean.failure(401, "原密码输入错误!");
|
||||
}
|
||||
|
||||
@PostMapping("/modify-email")
|
||||
public RestBean<Void> modifyEmail(@RequestAttribute(Const.ATTR_USER_ID) int id,
|
||||
@RequestBody @Valid ModifyEmailVO vo) {
|
||||
String result = service.modifyEmail(id, vo);
|
||||
if(result == null) {
|
||||
return RestBean.success();
|
||||
} else {
|
||||
return RestBean.failure(401, result);
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping("/sub/create")
|
||||
public RestBean<Void> createSubAccount(@RequestBody @Valid CreateSubAccountVO vo) {
|
||||
service.createSubAccount(vo);
|
||||
return RestBean.success();
|
||||
}
|
||||
|
||||
@GetMapping("/sub/delete")
|
||||
public RestBean<Void> deleteSubAccount(int uid,
|
||||
@RequestAttribute(Const.ATTR_USER_ID) int userId) {
|
||||
if(uid == userId)
|
||||
return RestBean.failure(401, "非法参数");
|
||||
service.deleteSubAccount(uid);
|
||||
return RestBean.success();
|
||||
}
|
||||
|
||||
@GetMapping("/sub/list")
|
||||
public RestBean<List<SubAccountVO>> subAccountList() {
|
||||
return RestBean.success(service.listSubAccount());
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
package com.example.entity.dto;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.example.entity.BaseData;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@TableName("db_client_ssh")
|
||||
public class ClientSsh implements BaseData {
|
||||
@TableId
|
||||
Integer id;
|
||||
Integer port;
|
||||
String username;
|
||||
String password;
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
package com.example.entity.dto;
|
||||
|
||||
import com.influxdb.annotations.Column;
|
||||
import com.influxdb.annotations.Measurement;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
@Data
|
||||
@Measurement(name = "runtime")
|
||||
public class RuntimeData {
|
||||
@Column(tag = true)
|
||||
int clientId;
|
||||
@Column(timestamp = true)
|
||||
Instant timestamp;
|
||||
@Column
|
||||
double cpuUsage;
|
||||
@Column
|
||||
double memoryUsage;
|
||||
@Column
|
||||
double diskUsage;
|
||||
@Column
|
||||
double networkUpload;
|
||||
@Column
|
||||
double networkDownload;
|
||||
@Column
|
||||
double diskRead;
|
||||
@Column
|
||||
double diskWrite;
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
package com.example.entity.vo.request;
|
||||
|
||||
import lombok.Data;
|
||||
import org.hibernate.validator.constraints.Length;
|
||||
|
||||
@Data
|
||||
public class ChangePasswordVO {
|
||||
@Length(min = 6, max = 20)
|
||||
String password;
|
||||
@Length(min = 6, max = 20)
|
||||
String new_password;
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
package com.example.entity.vo.request;
|
||||
|
||||
import jakarta.validation.constraints.Email;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.Data;
|
||||
import org.hibernate.validator.constraints.Length;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class CreateSubAccountVO {
|
||||
@Length(min = 1, max = 10)
|
||||
String username;
|
||||
@Email
|
||||
String email;
|
||||
@Length(min = 6, max = 20)
|
||||
String password;
|
||||
@Size(min = 1)
|
||||
List<Integer> clients;
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
package com.example.entity.vo.request;
|
||||
|
||||
import jakarta.validation.constraints.Email;
|
||||
import lombok.Data;
|
||||
import org.hibernate.validator.constraints.Length;
|
||||
|
||||
@Data
|
||||
public class ModifyEmailVO {
|
||||
@Email
|
||||
String email;
|
||||
@Length(max = 6, min = 6)
|
||||
String code;
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
package com.example.entity.vo.request;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
import org.hibernate.validator.constraints.Length;
|
||||
|
||||
@Data
|
||||
public class RenameClientVO {
|
||||
@NotNull
|
||||
int id;
|
||||
@Length(min = 1, max = 10)
|
||||
String name;
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
package com.example.entity.vo.request;
|
||||
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import lombok.Data;
|
||||
import org.hibernate.validator.constraints.Length;
|
||||
|
||||
@Data
|
||||
public class RenameNodeVO {
|
||||
int id;
|
||||
@Length(min = 1, max = 10)
|
||||
String node;
|
||||
@Pattern(regexp = "(cn|hk|jp|us|sg|kr|de)")
|
||||
String location;
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
package com.example.entity.vo.request;
|
||||
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class RuntimeDetailVO {
|
||||
@NotNull
|
||||
long timestamp;
|
||||
@NotNull
|
||||
double cpuUsage;
|
||||
@NotNull
|
||||
double memoryUsage;
|
||||
@NotNull
|
||||
double diskUsage;
|
||||
@NotNull
|
||||
double networkUpload;
|
||||
@NotNull
|
||||
double networkDownload;
|
||||
@NotNull
|
||||
double diskRead;
|
||||
@NotNull
|
||||
double diskWrite;
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
package com.example.entity.vo.request;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
import org.hibernate.validator.constraints.Length;
|
||||
|
||||
@Data
|
||||
public class SshConnectionVO {
|
||||
int id;
|
||||
int port;
|
||||
@NotNull
|
||||
@Length(min = 1)
|
||||
String username;
|
||||
@NotNull
|
||||
@Length(min = 1)
|
||||
String password;
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
package com.example.entity.vo.response;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class ClientDetailsVO {
|
||||
int id;
|
||||
String name;
|
||||
boolean online;
|
||||
String node;
|
||||
String location;
|
||||
String ip;
|
||||
String cpuName;
|
||||
String osName;
|
||||
String osVersion;
|
||||
double memory;
|
||||
int cpuCore;
|
||||
double disk;
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
package com.example.entity.vo.response;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class ClientPreviewVO {
|
||||
int id;
|
||||
boolean online;
|
||||
String name;
|
||||
String location;
|
||||
String osName;
|
||||
String osVersion;
|
||||
String ip;
|
||||
String cpuName;
|
||||
int cpuCore;
|
||||
double memory;
|
||||
double cpuUsage;
|
||||
double memoryUsage;
|
||||
double networkUpload;
|
||||
double networkDownload;
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
package com.example.entity.vo.response;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class ClientSimpleVO {
|
||||
int id;
|
||||
String name;
|
||||
String location;
|
||||
String osName;
|
||||
String osVersion;
|
||||
String ip;
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
package com.example.entity.vo.response;
|
||||
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class RuntimeHistoryVO {
|
||||
double disk;
|
||||
double memory;
|
||||
List<JSONObject> list = new LinkedList<>();
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
package com.example.entity.vo.response;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class SshSettingsVO {
|
||||
String ip;
|
||||
int port = 22;
|
||||
String username;
|
||||
String password;
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
package com.example.entity.vo.response;
|
||||
|
||||
import com.alibaba.fastjson2.JSONArray;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class SubAccountVO {
|
||||
int id;
|
||||
String username;
|
||||
String email;
|
||||
JSONArray clientList;
|
||||
}
|
@ -1,93 +0,0 @@
|
||||
package com.example.filter;
|
||||
|
||||
import com.auth0.jwt.interfaces.DecodedJWT;
|
||||
import com.example.entity.RestBean;
|
||||
import com.example.entity.dto.Account;
|
||||
import com.example.entity.dto.Client;
|
||||
import com.example.service.AccountService;
|
||||
import com.example.service.ClientService;
|
||||
import com.example.utils.Const;
|
||||
import com.example.utils.JwtUtils;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* 用于对请求头中Jwt令牌进行校验的工具,为当前请求添加用户验证信息
|
||||
* 并将用户的ID存放在请求对象属性中,方便后续使用
|
||||
*/
|
||||
@Component
|
||||
public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||
|
||||
@Resource
|
||||
JwtUtils utils;
|
||||
|
||||
@Resource
|
||||
ClientService service;
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
FilterChain filterChain) throws ServletException, IOException {
|
||||
String authorization = request.getHeader("Authorization");
|
||||
String uri = request.getRequestURI();
|
||||
if(uri.startsWith("/monitor")) {
|
||||
if(!uri.endsWith("/register")) {
|
||||
Client client = service.findClientByToken(authorization);
|
||||
if(client == null) {
|
||||
response.setStatus(401);
|
||||
response.setCharacterEncoding("utf-8");
|
||||
response.getWriter().write(RestBean.failure(401, "未注册").asJsonString());
|
||||
return;
|
||||
} else {
|
||||
request.setAttribute(Const.ATTR_CLIENT, client);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
DecodedJWT jwt = utils.resolveJwt(authorization);
|
||||
if(jwt != null) {
|
||||
UserDetails user = utils.toUser(jwt);
|
||||
UsernamePasswordAuthenticationToken authentication =
|
||||
new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
|
||||
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
request.setAttribute(Const.ATTR_USER_ID, utils.toId(jwt));
|
||||
request.setAttribute(Const.ATTR_USER_ROLE, new ArrayList<>(user.getAuthorities()).get(0).getAuthority());
|
||||
|
||||
if(request.getRequestURI().startsWith("/terminal/") && !accessShell(
|
||||
(int) request.getAttribute(Const.ATTR_USER_ID),
|
||||
(String) request.getAttribute(Const.ATTR_USER_ROLE),
|
||||
Integer.parseInt(request.getRequestURI().substring(10)))) {
|
||||
response.setStatus(401);
|
||||
response.setCharacterEncoding("utf-8");
|
||||
response.getWriter().write(RestBean.failure(401, "无权访问").asJsonString());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
|
||||
@Resource
|
||||
AccountService accountService;
|
||||
|
||||
private boolean accessShell(int userId, String userRole, int clientId) {
|
||||
if(Const.ROLE_ADMIN.equals(userRole.substring(5))) {
|
||||
return true;
|
||||
} else {
|
||||
Account account = accountService.getById(userId);
|
||||
return account.getClientList().contains(clientId);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
package com.example.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.example.entity.dto.ClientDetail;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface ClientDetailMapper extends BaseMapper<ClientDetail> {
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
package com.example.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.example.entity.dto.Client;
|
||||
import com.example.entity.vo.request.*;
|
||||
import com.example.entity.vo.response.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface ClientService extends IService<Client> {
|
||||
String registerToken();
|
||||
Client findClientById(int id);
|
||||
Client findClientByToken(String token);
|
||||
boolean verifyAndRegister(String token);
|
||||
void updateClientDetail(ClientDetailVO vo, Client client);
|
||||
void updateRuntimeDetail(RuntimeDetailVO vo, Client client);
|
||||
List<ClientPreviewVO> listClients();
|
||||
List<ClientSimpleVO> listSimpleList();
|
||||
void renameClient(RenameClientVO vo);
|
||||
void renameNode(RenameNodeVO vo);
|
||||
ClientDetailsVO clientDetails(int clientId);
|
||||
RuntimeHistoryVO clientRuntimeDetailsHistory(int clientId);
|
||||
RuntimeDetailVO clientRuntimeDetailsNow(int clientId);
|
||||
void deleteClient(int clientId);
|
||||
void saveClientSshConnection(SshConnectionVO vo);
|
||||
SshSettingsVO sshSettings(int clientId);
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user