This commit is contained in:
Jane
2023-12-22 10:59:10 +08:00
parent 751c43e199
commit d1ede2d4aa
2774 changed files with 291509 additions and 0 deletions

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>modules</artifactId>
<groupId>com.platform</groupId>
<version>0.4.x</version>
</parent>
<packaging>pom</packaging>
<modelVersion>4.0.0</modelVersion>
<version>0.4.x</version>
<artifactId>system-service-parent</artifactId>
<modules>
<module>system-service</module>
</modules>
</project>

View File

@@ -0,0 +1,156 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>system-service-parent</artifactId>
<groupId>com.platform</groupId>
<version>0.4.x</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<version>0.4.x</version>
<artifactId>system-service</artifactId>
<properties>
<druid.version>1.2.8</druid.version>
<jna.version>5.8.0</jna.version>
</properties>
<dependencies>
<dependency>
<groupId>com.github.yeecode.dynamicdatasource</groupId>
<artifactId>DynamicDataSource</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<!-- 获取系统信息 -->
<dependency>
<groupId>com.github.oshi</groupId>
<artifactId>oshi-core</artifactId>
<version>6.1.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/net.java.dev.jna/jna -->
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>5.8.0</version>
</dependency>
<dependency>
<groupId>com.platform</groupId>
<artifactId>common-redis</artifactId>
<version>0.4.x</version>
</dependency>
<dependency>
<groupId>com.platform</groupId>
<artifactId>common-service-api</artifactId>
<version>0.4.x</version>
</dependency>
<dependency>
<groupId>com.platform</groupId>
<artifactId>common-security</artifactId>
<version>0.4.x</version>
<exclusions>
<exclusion>
<artifactId>mybatis</artifactId>
<groupId>org.mybatis</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.platform</groupId>
<artifactId>generic</artifactId>
<version>0.4.x</version>
</dependency>
<!-- linux的管理 -->
<dependency>
<groupId>ch.ethz.ganymed</groupId>
<artifactId>ganymed-ssh2</artifactId>
<version>build210</version>
</dependency>
<dependency>
<groupId>com.jcraft</groupId>
<artifactId>jsch</artifactId>
<version>0.1.55</version>
</dependency>
<!-- tools 模块包含了 common 和 logging 模块 -->
<dependency>
<groupId>com.platform</groupId>
<artifactId>box</artifactId>
<version>0.4.x</version>
</dependency>
<!--Mysql依赖包-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- quartz -->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
</dependency>
<!--web 模块-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<artifactId>spring-boot-starter-tomcat</artifactId>
<groupId>org.springframework.boot</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${mapstruct.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.platform</groupId>
<artifactId>common-mybatis</artifactId>
<version>0.4.x</version>
</dependency>
<dependency>
<groupId>com.platform</groupId>
<artifactId>common-log</artifactId>
<version>0.4.x</version>
</dependency>
<!-- druid数据源驱动 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,54 @@
package com.platform;
import io.swagger.annotations.Api;
import com.platform.annotation.rest.AnonymousGetMapping;
import com.platform.utils.SpringContextHolder;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.ApplicationPidFileWriter;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.web.bind.annotation.RestController;
/**
* 开启审计功能 -> @EnableJpaAuditing
*
* @author AllDataDC
* @date 2023-01-27
*/
@EnableAsync
@RestController
@Api(hidden = true)
@EnableTransactionManagement
@EnableJpaAuditing(auditorAwareRef = "auditorAware")
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class })
public class SystemServiceApplication extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(SystemServiceApplication.class);
}
public static void main(String[] args) {
SpringApplication springApplication = new SpringApplication(SystemServiceApplication.class);
springApplication.addListeners(new ApplicationPidFileWriter());
springApplication.run(args);
}
/**
* 访问首页提示
*
* @return /
*/
@AnonymousGetMapping("/")
public String index() {
return "Studio service started successfully";
}
}

View File

@@ -0,0 +1,74 @@
package com.platform.config;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
/**
* WebMvcConfigurer
*
* @author AllDataDC
* @date 2023-01-27
*/
@Configuration
@EnableWebMvc
public class ConfigurerAdapter implements WebMvcConfigurer {
/** 文件配置 */
private final FileProperties properties;
public ConfigurerAdapter(FileProperties properties) {
this.properties = properties;
}
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
FileProperties.ElPath path = properties.getPath();
String avatarUtl = "file:" + path.getAvatar().replace("\\","/");
String pathUtl = "file:" + path.getPath().replace("\\","/");
registry.addResourceHandler("/avatar/**").addResourceLocations(avatarUtl).setCachePeriod(0);
registry.addResourceHandler("/file/**").addResourceLocations(pathUtl).setCachePeriod(0);
registry.addResourceHandler("/**").addResourceLocations("classpath:/META-INF/resources/").setCachePeriod(0);
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
// 使用 fastjson 序列化,会导致 @JsonIgnore 失效,可以使用 @JSONField(serialize = false) 替换
FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
List<MediaType> supportMediaTypeList = new ArrayList<>();
supportMediaTypeList.add(MediaType.APPLICATION_JSON);
FastJsonConfig config = new FastJsonConfig();
config.setDateFormat("yyyy-MM-dd HH:mm:ss");
config.setSerializerFeatures(SerializerFeature.DisableCircularReferenceDetect);
converter.setFastJsonConfig(config);
converter.setSupportedMediaTypes(supportMediaTypeList);
converter.setDefaultCharset(StandardCharsets.UTF_8);
converters.add(converter);
}
}

View File

@@ -0,0 +1,25 @@
package com.platform.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
/**
* @author AllDataDC
* @date 2023-01-27 15:44
*/
@Configuration
public class WebSocketConfig {
/**
* ServerEndpointExporter 作用
*
* 这个Bean会自动注册使用@ServerEndpoint注解声明的websocket endpoint
*
* @return
*/
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}

View File

@@ -0,0 +1,48 @@
package com.platform.config.thread;
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
/**
* 异步任务线程池装配类
* @author https://juejin.im/entry/5abb8f6951882555677e9da2
* @date 2023-01-27 15:06:18
*/
@Slf4j
@Configuration
public class AsyncTaskExecutePool implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
//核心线程池大小
executor.setCorePoolSize(AsyncTaskProperties.corePoolSize);
//最大线程数
executor.setMaxPoolSize(AsyncTaskProperties.maxPoolSize);
//队列容量
executor.setQueueCapacity(AsyncTaskProperties.queueCapacity);
//活跃时间
executor.setKeepAliveSeconds(AsyncTaskProperties.keepAliveSeconds);
//线程工厂
executor.setThreadFactory(new TheadFactoryName("el-async"));
// setRejectedExecutionHandler当pool已经达到max size的时候如何处理新任务
// CallerRunsPolicy不在新线程中执行任务而是由调用者所在的线程来执行
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return (throwable, method, objects) -> {
log.error("===="+throwable.getMessage()+"====", throwable);
log.error("exception method:"+method.getName());
};
}
}

View File

@@ -0,0 +1,45 @@
package com.platform.config.thread;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 线程池配置属性类
* @author https://juejin.im/entry/5abb8f6951882555677e9da2
* @date 2023-01-27 14:58:18
*/
@Data
@Component
public class AsyncTaskProperties {
public static int corePoolSize;
public static int maxPoolSize;
public static int keepAliveSeconds;
public static int queueCapacity;
@Value("${task.pool.core-pool-size}")
public void setCorePoolSize(int corePoolSize) {
AsyncTaskProperties.corePoolSize = corePoolSize;
}
@Value("${task.pool.max-pool-size}")
public void setMaxPoolSize(int maxPoolSize) {
AsyncTaskProperties.maxPoolSize = maxPoolSize;
}
@Value("${task.pool.keep-alive-seconds}")
public void setKeepAliveSeconds(int keepAliveSeconds) {
AsyncTaskProperties.keepAliveSeconds = keepAliveSeconds;
}
@Value("${task.pool.queue-capacity}")
public void setQueueCapacity(int queueCapacity) {
AsyncTaskProperties.queueCapacity = queueCapacity;
}
}

View File

@@ -0,0 +1,50 @@
package com.platform.config.thread;
import com.platform.utils.StringUtils;
import org.springframework.stereotype.Component;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 自定义线程名称
* @author AllDataDC
* @date 2023-01-27 17:49:55
*/
@Component
public class TheadFactoryName implements ThreadFactory {
private static final AtomicInteger POOL_NUMBER = new AtomicInteger(1);
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
private final static String DEF_NAME = "el-pool-";
public TheadFactoryName() {
this(DEF_NAME);
}
public TheadFactoryName(String name){
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
//此时namePrefix就是 name + 第几个用这个工厂创建线程池的
this.namePrefix = (StringUtils.isNotBlank(name) ? name : DEF_NAME) + "-" + POOL_NUMBER.getAndIncrement();
}
@Override
public Thread newThread(Runnable r) {
//此时线程的名字 就是 namePrefix + -exec- + 这个线程池中第几个执行的线程
Thread t = new Thread(group, r,
namePrefix + "-exec-"+threadNumber.getAndIncrement(),
0);
if (t.isDaemon()) {
t.setDaemon(false);
}
if (t.getPriority() != Thread.NORM_PRIORITY) {
t.setPriority(Thread.NORM_PRIORITY);
}
return t;
}
}

View File

@@ -0,0 +1,32 @@
package com.platform.config.thread;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* 用于获取自定义线程池
* @author AllDataDC
* @date 2023-01-27 18:16:47
*/
public class ThreadPoolExecutorUtil {
public static ExecutorService getPoll(){
return getPoll(null);
}
public static ExecutorService getPoll(String threadName){
return new ThreadPoolExecutor(
AsyncTaskProperties.corePoolSize,
AsyncTaskProperties.maxPoolSize,
AsyncTaskProperties.keepAliveSeconds,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(AsyncTaskProperties.queueCapacity),
new TheadFactoryName(threadName),
// 队列与线程池中线程都满了时使用调用者所在的线程来执行
new ThreadPoolExecutor.CallerRunsPolicy()
);
}
}

View File

@@ -0,0 +1,53 @@
package com.platform.modules.mnt.domain;
import io.swagger.annotations.ApiModelProperty;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import lombok.Getter;
import lombok.Setter;
import com.platform.base.BaseEntity;
import javax.persistence.*;
import java.io.Serializable;
/**
* @author AllDataDC
* @date 2023-01-27
*/
@Entity
@Getter
@Setter
@Table(name="mnt_app")
public class App extends BaseEntity implements Serializable {
@Id
@Column(name = "app_id")
@ApiModelProperty(value = "ID", hidden = true)
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ApiModelProperty(value = "名称")
private String name;
@ApiModelProperty(value = "端口")
private int port;
@ApiModelProperty(value = "上传路径")
private String uploadPath;
@ApiModelProperty(value = "部署路径")
private String deployPath;
@ApiModelProperty(value = "备份路径")
private String backupPath;
@ApiModelProperty(value = "启动脚本")
private String startScript;
@ApiModelProperty(value = "部署脚本")
private String deployScript;
public void copy(App source){
BeanUtil.copyProperties(source,this, CopyOptions.create().setIgnoreNullValue(true));
}
}

View File

@@ -0,0 +1,43 @@
package com.platform.modules.mnt.domain;
import io.swagger.annotations.ApiModelProperty;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import lombok.Getter;
import lombok.Setter;
import com.platform.base.BaseEntity;
import javax.persistence.*;
import java.io.Serializable;
/**
* @author AllDataDC
* @date 2023-01-27
*/
@Entity
@Getter
@Setter
@Table(name="mnt_database")
public class Database extends BaseEntity implements Serializable {
@Id
@Column(name = "db_id")
@ApiModelProperty(value = "ID", hidden = true)
private String id;
@ApiModelProperty(value = "数据库名称")
private String name;
@ApiModelProperty(value = "数据库连接地址")
private String jdbcUrl;
@ApiModelProperty(value = "数据库密码")
private String pwd;
@ApiModelProperty(value = "用户名")
private String userName;
public void copy(Database source){
BeanUtil.copyProperties(source,this, CopyOptions.create().setIgnoreNullValue(true));
}
}

View File

@@ -0,0 +1,45 @@
package com.platform.modules.mnt.domain;
import io.swagger.annotations.ApiModelProperty;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import lombok.Getter;
import lombok.Setter;
import com.platform.base.BaseEntity;
import javax.persistence.*;
import java.io.Serializable;
import java.util.Set;
/**
* @author AllDataDC
* @date 2023-01-27
*/
@Entity
@Getter
@Setter
@Table(name="mnt_deploy")
public class Deploy extends BaseEntity implements Serializable {
@Id
@Column(name = "deploy_id")
@ApiModelProperty(value = "ID", hidden = true)
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToMany
@ApiModelProperty(name = "服务器", hidden = true)
@JoinTable(name = "mnt_deploy_server",
joinColumns = {@JoinColumn(name = "deploy_id",referencedColumnName = "deploy_id")},
inverseJoinColumns = {@JoinColumn(name = "server_id",referencedColumnName = "server_id")})
private Set<ServerDeploy> deploys;
@ManyToOne
@JoinColumn(name = "app_id")
@ApiModelProperty(value = "应用编号")
private App app;
public void copy(Deploy source){
BeanUtil.copyProperties(source,this, CopyOptions.create().setIgnoreNullValue(true));
}
}

View File

@@ -0,0 +1,48 @@
package com.platform.modules.mnt.domain;
import io.swagger.annotations.ApiModelProperty;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.CreationTimestamp;
import javax.persistence.*;
import java.io.Serializable;
import java.sql.Timestamp;
/**
* @author AllDataDC
* @date 2023-01-27
*/
@Entity
@Getter
@Setter
@Table(name="mnt_deploy_history")
public class DeployHistory implements Serializable {
@Id
@Column(name = "history_id")
@ApiModelProperty(value = "ID", hidden = true)
private String id;
@ApiModelProperty(value = "应用名称")
private String appName;
@ApiModelProperty(value = "IP")
private String ip;
@CreationTimestamp
@ApiModelProperty(value = "部署时间")
private Timestamp deployDate;
@ApiModelProperty(value = "部署者")
private String deployUser;
@ApiModelProperty(value = "部署ID")
private Long deployId;
public void copy(DeployHistory source){
BeanUtil.copyProperties(source,this, CopyOptions.create().setIgnoreNullValue(true));
}
}

View File

@@ -0,0 +1,66 @@
package com.platform.modules.mnt.domain;
import io.swagger.annotations.ApiModelProperty;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import lombok.Getter;
import lombok.Setter;
import com.platform.base.BaseEntity;
import javax.persistence.*;
import java.io.Serializable;
import java.util.Objects;
/**
* @author AllDataDC
* @date 2023-01-27
*/
@Entity
@Getter
@Setter
@Table(name="mnt_server")
public class ServerDeploy extends BaseEntity implements Serializable {
@Id
@Column(name = "server_id")
@ApiModelProperty(value = "ID", hidden = true)
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ApiModelProperty(value = "服务器名称")
private String name;
@ApiModelProperty(value = "IP")
private String ip;
@ApiModelProperty(value = "端口")
private Integer port;
@ApiModelProperty(value = "账号")
private String account;
@ApiModelProperty(value = "密码")
private String password;
public void copy(ServerDeploy source){
BeanUtil.copyProperties(source,this, CopyOptions.create().setIgnoreNullValue(true));
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ServerDeploy that = (ServerDeploy) o;
return Objects.equals(id, that.id) &&
Objects.equals(name, that.name);
}
@Override
public int hashCode() {
return Objects.hash(id, name);
}
}

View File

@@ -0,0 +1,13 @@
package com.platform.modules.mnt.repository;
import com.platform.modules.mnt.domain.App;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
/**
* @author AllDataDC
* @date 2023-01-27
*/
public interface AppRepository extends JpaRepository<App, Long>, JpaSpecificationExecutor<App> {
}

View File

@@ -0,0 +1,13 @@
package com.platform.modules.mnt.repository;
import com.platform.modules.mnt.domain.Database;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
/**
* @author AllDataDC
* @date 2023-01-27
*/
public interface DatabaseRepository extends JpaRepository<Database, String>, JpaSpecificationExecutor<Database> {
}

View File

@@ -0,0 +1,13 @@
package com.platform.modules.mnt.repository;
import com.platform.modules.mnt.domain.DeployHistory;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
/**
* @author AllDataDC
* @date 2023-01-27
*/
public interface DeployHistoryRepository extends JpaRepository<DeployHistory, String>, JpaSpecificationExecutor<DeployHistory> {
}

View File

@@ -0,0 +1,13 @@
package com.platform.modules.mnt.repository;
import com.platform.modules.mnt.domain.Deploy;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
/**
* @author AllDataDC
* @date 2023-01-27
*/
public interface DeployRepository extends JpaRepository<Deploy, Long>, JpaSpecificationExecutor<Deploy> {
}

View File

@@ -0,0 +1,20 @@
package com.platform.modules.mnt.repository;
import com.platform.modules.mnt.domain.ServerDeploy;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
/**
* @author AllDataDC
* @date 2023-01-27
*/
public interface ServerDeployRepository extends JpaRepository<ServerDeploy, Long>, JpaSpecificationExecutor<ServerDeploy> {
/**
* 根据IP查询
* @param ip /
* @return /
*/
ServerDeploy findByIp(String ip);
}

View File

@@ -0,0 +1,73 @@
package com.platform.modules.mnt.rest;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import com.platform.annotation.Log;
import com.platform.modules.mnt.domain.App;
import com.platform.modules.mnt.service.AppService;
import com.platform.modules.mnt.service.dto.AppQueryCriteria;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Set;
/**
* @author AllDataDC
* @date 2023-01-27
*/
@RestController
@RequiredArgsConstructor
@Api(tags = "运维:应用管理")
@RequestMapping("/api/app")
public class AppController {
private final AppService appService;
@ApiOperation("导出应用数据")
@GetMapping(value = "/download")
@PreAuthorize("@el.check('app:list')")
public void exportApp(HttpServletResponse response, AppQueryCriteria criteria) throws IOException {
appService.download(appService.queryAll(criteria), response);
}
@ApiOperation(value = "查询应用")
@GetMapping
@PreAuthorize("@el.check('app:list')")
public ResponseEntity<Object> queryApp(AppQueryCriteria criteria, Pageable pageable){
return new ResponseEntity<>(appService.queryAll(criteria,pageable),HttpStatus.OK);
}
@Log("新增应用")
@ApiOperation(value = "新增应用")
@PostMapping
@PreAuthorize("@el.check('app:add')")
public ResponseEntity<Object> createApp(@Validated @RequestBody App resources){
appService.create(resources);
return new ResponseEntity<>(HttpStatus.CREATED);
}
@Log("修改应用")
@ApiOperation(value = "修改应用")
@PutMapping
@PreAuthorize("@el.check('app:edit')")
public ResponseEntity<Object> updateApp(@Validated @RequestBody App resources){
appService.update(resources);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
@Log("删除应用")
@ApiOperation(value = "删除应用")
@DeleteMapping
@PreAuthorize("@el.check('app:del')")
public ResponseEntity<Object> deleteApp(@RequestBody Set<Long> ids){
appService.delete(ids);
return new ResponseEntity<>(HttpStatus.OK);
}
}

View File

@@ -0,0 +1,109 @@
package com.platform.modules.mnt.rest;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import com.platform.annotation.Log;
import com.platform.exception.BadRequestException;
import com.platform.modules.mnt.domain.Database;
import com.platform.modules.mnt.service.DatabaseService;
import com.platform.modules.mnt.service.dto.DatabaseDto;
import com.platform.modules.mnt.service.dto.DatabaseQueryCriteria;
import com.platform.modules.mnt.util.SqlUtils;
import com.platform.utils.FileUtil;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.util.Set;
/**
* @author AllDataDC
* @date 2023-01-27
*/
@Api(tags = "运维:数据库管理")
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/database")
public class DatabaseController {
private final String fileSavePath = FileUtil.getTmpDirPath()+"/";
private final DatabaseService databaseService;
@ApiOperation("导出数据库数据")
@GetMapping(value = "/download")
@PreAuthorize("@el.check('database:list')")
public void exportDatabase(HttpServletResponse response, DatabaseQueryCriteria criteria) throws IOException {
databaseService.download(databaseService.queryAll(criteria), response);
}
@ApiOperation(value = "查询数据库")
@GetMapping
@PreAuthorize("@el.check('database:list')")
public ResponseEntity<Object> queryDatabase(DatabaseQueryCriteria criteria, Pageable pageable){
return new ResponseEntity<>(databaseService.queryAll(criteria,pageable),HttpStatus.OK);
}
@Log("新增数据库")
@ApiOperation(value = "新增数据库")
@PostMapping
@PreAuthorize("@el.check('database:add')")
public ResponseEntity<Object> createDatabase(@Validated @RequestBody Database resources){
databaseService.create(resources);
return new ResponseEntity<>(HttpStatus.CREATED);
}
@Log("修改数据库")
@ApiOperation(value = "修改数据库")
@PutMapping
@PreAuthorize("@el.check('database:edit')")
public ResponseEntity<Object> updateDatabase(@Validated @RequestBody Database resources){
databaseService.update(resources);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
@Log("删除数据库")
@ApiOperation(value = "删除数据库")
@DeleteMapping
@PreAuthorize("@el.check('database:del')")
public ResponseEntity<Object> deleteDatabase(@RequestBody Set<String> ids){
databaseService.delete(ids);
return new ResponseEntity<>(HttpStatus.OK);
}
@Log("测试数据库链接")
@ApiOperation(value = "测试数据库链接")
@PostMapping("/testConnect")
@PreAuthorize("@el.check('database:testConnect')")
public ResponseEntity<Object> testConnect(@Validated @RequestBody Database resources){
return new ResponseEntity<>(databaseService.testConnection(resources),HttpStatus.CREATED);
}
@Log("执行SQL脚本")
@ApiOperation(value = "执行SQL脚本")
@PostMapping(value = "/upload")
@PreAuthorize("@el.check('database:add')")
public ResponseEntity<Object> uploadDatabase(@RequestBody MultipartFile file, HttpServletRequest request)throws Exception{
String id = request.getParameter("id");
DatabaseDto database = databaseService.findById(id);
String fileName;
if(database != null){
fileName = file.getOriginalFilename();
File executeFile = new File(fileSavePath+fileName);
FileUtil.del(executeFile);
file.transferTo(executeFile);
String result = SqlUtils.executeFile(database.getJdbcUrl(), database.getUserName(), database.getPwd(), executeFile);
return new ResponseEntity<>(result,HttpStatus.OK);
}else{
throw new BadRequestException("Database not exist");
}
}
}

View File

@@ -0,0 +1,139 @@
package com.platform.modules.mnt.rest;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import com.platform.annotation.Log;
import com.platform.modules.mnt.domain.Deploy;
import com.platform.modules.mnt.domain.DeployHistory;
import com.platform.modules.mnt.service.DeployService;
import com.platform.modules.mnt.service.dto.DeployQueryCriteria;
import com.platform.utils.FileUtil;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
/**
* @author AllDataDC
* @date 2023-01-27
*/
@RestController
@Api(tags = "运维:部署管理")
@RequiredArgsConstructor
@RequestMapping("/api/deploy")
public class DeployController {
private final String fileSavePath = FileUtil.getTmpDirPath()+"/";
private final DeployService deployService;
@ApiOperation("导出部署数据")
@GetMapping(value = "/download")
@PreAuthorize("@el.check('database:list')")
public void exportDeployData(HttpServletResponse response, DeployQueryCriteria criteria) throws IOException {
deployService.download(deployService.queryAll(criteria), response);
}
@ApiOperation(value = "查询部署")
@GetMapping
@PreAuthorize("@el.check('deploy:list')")
public ResponseEntity<Object> queryDeployData(DeployQueryCriteria criteria, Pageable pageable){
return new ResponseEntity<>(deployService.queryAll(criteria,pageable),HttpStatus.OK);
}
@Log("新增部署")
@ApiOperation(value = "新增部署")
@PostMapping
@PreAuthorize("@el.check('deploy:add')")
public ResponseEntity<Object> createDeploy(@Validated @RequestBody Deploy resources){
deployService.create(resources);
return new ResponseEntity<>(HttpStatus.CREATED);
}
@Log("修改部署")
@ApiOperation(value = "修改部署")
@PutMapping
@PreAuthorize("@el.check('deploy:edit')")
public ResponseEntity<Object> updateDeploy(@Validated @RequestBody Deploy resources){
deployService.update(resources);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
@Log("删除部署")
@ApiOperation(value = "删除部署")
@DeleteMapping
@PreAuthorize("@el.check('deploy:del')")
public ResponseEntity<Object> deleteDeploy(@RequestBody Set<Long> ids){
deployService.delete(ids);
return new ResponseEntity<>(HttpStatus.OK);
}
@Log("上传文件部署")
@ApiOperation(value = "上传文件部署")
@PostMapping(value = "/upload")
@PreAuthorize("@el.check('deploy:edit')")
public ResponseEntity<Object> uploadDeploy(@RequestBody MultipartFile file, HttpServletRequest request)throws Exception{
Long id = Long.valueOf(request.getParameter("id"));
String fileName = "";
if(file != null){
fileName = file.getOriginalFilename();
File deployFile = new File(fileSavePath+fileName);
FileUtil.del(deployFile);
file.transferTo(deployFile);
//文件下一步要根据文件名字来
deployService.deploy(fileSavePath+fileName ,id);
}else{
System.out.println("没有找到相对应的文件");
}
System.out.println("文件上传的原名称为:"+ Objects.requireNonNull(file).getOriginalFilename());
Map<String,Object> map = new HashMap<>(2);
map.put("errno",0);
map.put("id",fileName);
return new ResponseEntity<>(map,HttpStatus.OK);
}
@Log("系统还原")
@ApiOperation(value = "系统还原")
@PostMapping(value = "/serverReduction")
@PreAuthorize("@el.check('deploy:edit')")
public ResponseEntity<Object> serverReduction(@Validated @RequestBody DeployHistory resources){
String result = deployService.serverReduction(resources);
return new ResponseEntity<>(result,HttpStatus.OK);
}
@Log("服务运行状态")
@ApiOperation(value = "服务运行状态")
@PostMapping(value = "/serverStatus")
@PreAuthorize("@el.check('deploy:edit')")
public ResponseEntity<Object> serverStatus(@Validated @RequestBody Deploy resources){
String result = deployService.serverStatus(resources);
return new ResponseEntity<>(result,HttpStatus.OK);
}
@Log("启动服务")
@ApiOperation(value = "启动服务")
@PostMapping(value = "/startServer")
@PreAuthorize("@el.check('deploy:edit')")
public ResponseEntity<Object> startServer(@Validated @RequestBody Deploy resources){
String result = deployService.startServer(resources);
return new ResponseEntity<>(result,HttpStatus.OK);
}
@Log("停止服务")
@ApiOperation(value = "停止服务")
@PostMapping(value = "/stopServer")
@PreAuthorize("@el.check('deploy:edit')")
public ResponseEntity<Object> stopServer(@Validated @RequestBody Deploy resources){
String result = deployService.stopServer(resources);
return new ResponseEntity<>(result,HttpStatus.OK);
}
}

View File

@@ -0,0 +1,53 @@
package com.platform.modules.mnt.rest;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import com.platform.annotation.Log;
import com.platform.modules.mnt.service.DeployHistoryService;
import com.platform.modules.mnt.service.dto.DeployHistoryQueryCriteria;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Set;
/**
* @author AllDataDC
* @date 2023-01-27
*/
@RestController
@RequiredArgsConstructor
@Api(tags = "运维:部署历史管理")
@RequestMapping("/api/deployHistory")
public class DeployHistoryController {
private final DeployHistoryService deployhistoryService;
@ApiOperation("导出部署历史数据")
@GetMapping(value = "/download")
@PreAuthorize("@el.check('deployHistory:list')")
public void exportDeployHistory(HttpServletResponse response, DeployHistoryQueryCriteria criteria) throws IOException {
deployhistoryService.download(deployhistoryService.queryAll(criteria), response);
}
@ApiOperation(value = "查询部署历史")
@GetMapping
@PreAuthorize("@el.check('deployHistory:list')")
public ResponseEntity<Object> queryDeployHistory(DeployHistoryQueryCriteria criteria, Pageable pageable){
return new ResponseEntity<>(deployhistoryService.queryAll(criteria,pageable),HttpStatus.OK);
}
@Log("删除DeployHistory")
@ApiOperation(value = "删除部署历史")
@DeleteMapping
@PreAuthorize("@el.check('deployHistory:del')")
public ResponseEntity<Object> deleteDeployHistory(@RequestBody Set<String> ids){
deployhistoryService.delete(ids);
return new ResponseEntity<>(HttpStatus.OK);
}
}

View File

@@ -0,0 +1,81 @@
package com.platform.modules.mnt.rest;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import com.platform.annotation.Log;
import com.platform.modules.mnt.domain.ServerDeploy;
import com.platform.modules.mnt.service.ServerDeployService;
import com.platform.modules.mnt.service.dto.ServerDeployQueryCriteria;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Set;
/**
* @author AllDataDC
* @date 2023-01-27
*/
@RestController
@Api(tags = "运维:服务器管理")
@RequiredArgsConstructor
@RequestMapping("/api/serverDeploy")
public class ServerDeployController {
private final ServerDeployService serverDeployService;
@ApiOperation("导出服务器数据")
@GetMapping(value = "/download")
@PreAuthorize("@el.check('serverDeploy:list')")
public void exportServerDeploy(HttpServletResponse response, ServerDeployQueryCriteria criteria) throws IOException {
serverDeployService.download(serverDeployService.queryAll(criteria), response);
}
@ApiOperation(value = "查询服务器")
@GetMapping
@PreAuthorize("@el.check('serverDeploy:list')")
public ResponseEntity<Object> queryServerDeploy(ServerDeployQueryCriteria criteria, Pageable pageable){
return new ResponseEntity<>(serverDeployService.queryAll(criteria,pageable),HttpStatus.OK);
}
@Log("新增服务器")
@ApiOperation(value = "新增服务器")
@PostMapping
@PreAuthorize("@el.check('serverDeploy:add')")
public ResponseEntity<Object> createServerDeploy(@Validated @RequestBody ServerDeploy resources){
serverDeployService.create(resources);
return new ResponseEntity<>(HttpStatus.CREATED);
}
@Log("修改服务器")
@ApiOperation(value = "修改服务器")
@PutMapping
@PreAuthorize("@el.check('serverDeploy:edit')")
public ResponseEntity<Object> updateServerDeploy(@Validated @RequestBody ServerDeploy resources){
serverDeployService.update(resources);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
@Log("删除服务器")
@ApiOperation(value = "删除Server")
@DeleteMapping
@PreAuthorize("@el.check('serverDeploy:del')")
public ResponseEntity<Object> deleteServerDeploy(@RequestBody Set<Long> ids){
serverDeployService.delete(ids);
return new ResponseEntity<>(HttpStatus.OK);
}
@Log("测试连接服务器")
@ApiOperation(value = "测试连接服务器")
@PostMapping("/testConnect")
@PreAuthorize("@el.check('serverDeploy:add')")
public ResponseEntity<Object> testConnectServerDeploy(@Validated @RequestBody ServerDeploy resources){
return new ResponseEntity<>(serverDeployService.testConnect(resources),HttpStatus.CREATED);
}
}

View File

@@ -0,0 +1,67 @@
package com.platform.modules.mnt.service;
import com.platform.modules.mnt.domain.App;
import com.platform.modules.mnt.service.dto.AppDto;
import com.platform.modules.mnt.service.dto.AppQueryCriteria;
import org.springframework.data.domain.Pageable;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import java.util.Set;
/**
* @author AllDataDC
* @date 2023-01-27
*/
public interface AppService {
/**
* 分页查询
* @param criteria 条件
* @param pageable 分页参数
* @return /
*/
Object queryAll(AppQueryCriteria criteria, Pageable pageable);
/**
* 查询全部数据
* @param criteria 条件
* @return /
*/
List<AppDto> queryAll(AppQueryCriteria criteria);
/**
* 根据ID查询
* @param id /
* @return /
*/
AppDto findById(Long id);
/**
* 创建
* @param resources /
*/
void create(App resources);
/**
* 编辑
* @param resources /
*/
void update(App resources);
/**
* 删除
* @param ids /
*/
void delete(Set<Long> ids);
/**
* 导出数据
* @param queryAll /
* @param response /
* @throws IOException /
*/
void download(List<AppDto> queryAll, HttpServletResponse response) throws IOException;
}

View File

@@ -0,0 +1,74 @@
package com.platform.modules.mnt.service;
import com.platform.modules.mnt.domain.Database;
import com.platform.modules.mnt.service.dto.DatabaseDto;
import com.platform.modules.mnt.service.dto.DatabaseQueryCriteria;
import org.springframework.data.domain.Pageable;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import java.util.Set;
/**
* @author AllDataDC
* @date 2023-01-27
*/
public interface DatabaseService {
/**
* 分页查询
* @param criteria 条件
* @param pageable 分页参数
* @return /
*/
Object queryAll(DatabaseQueryCriteria criteria, Pageable pageable);
/**
* 查询全部
* @param criteria 条件
* @return /
*/
List<DatabaseDto> queryAll(DatabaseQueryCriteria criteria);
/**
* 根据ID查询
* @param id /
* @return /
*/
DatabaseDto findById(String id);
/**
* 创建
* @param resources /
*/
void create(Database resources);
/**
* 编辑
* @param resources /
*/
void update(Database resources);
/**
* 删除
* @param ids /
*/
void delete(Set<String> ids);
/**
* 测试连接数据库
* @param resources /
* @return /
*/
boolean testConnection(Database resources);
/**
* 导出数据
* @param queryAll /
* @param response /
* @throws IOException e
*/
void download(List<DatabaseDto> queryAll, HttpServletResponse response) throws IOException;
}

View File

@@ -0,0 +1,60 @@
package com.platform.modules.mnt.service;
import com.platform.modules.mnt.domain.DeployHistory;
import com.platform.modules.mnt.service.dto.DeployHistoryDto;
import com.platform.modules.mnt.service.dto.DeployHistoryQueryCriteria;
import org.springframework.data.domain.Pageable;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import java.util.Set;
/**
* @author AllDataDC
*/
public interface DeployHistoryService {
/**
* 分页查询
* @param criteria 条件
* @param pageable 分页参数
* @return /
*/
Object queryAll(DeployHistoryQueryCriteria criteria, Pageable pageable);
/**
* 查询全部
* @param criteria 条件
* @return /
*/
List<DeployHistoryDto> queryAll(DeployHistoryQueryCriteria criteria);
/**
* 根据ID查询
* @param id /
* @return /
*/
DeployHistoryDto findById(String id);
/**
* 创建
* @param resources /
*/
void create(DeployHistory resources);
/**
* 删除
* @param ids /
*/
void delete(Set<String> ids);
/**
* 导出数据
* @param queryAll /
* @param response /
* @throws IOException /
*/
void download(List<DeployHistoryDto> queryAll, HttpServletResponse response) throws IOException;
}

View File

@@ -0,0 +1,102 @@
package com.platform.modules.mnt.service;
import com.platform.modules.mnt.domain.Deploy;
import com.platform.modules.mnt.domain.DeployHistory;
import com.platform.modules.mnt.service.dto.DeployDto;
import com.platform.modules.mnt.service.dto.DeployQueryCriteria;
import org.springframework.data.domain.Pageable;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import java.util.Set;
/**
* @author AllDataDC
* @date 2023-01-27
*/
public interface DeployService {
/**
* 分页查询
* @param criteria 条件
* @param pageable 分页参数
* @return /
*/
Object queryAll(DeployQueryCriteria criteria, Pageable pageable);
/**
* 查询全部数据
* @param criteria 条件
* @return /
*/
List<DeployDto> queryAll(DeployQueryCriteria criteria);
/**
* 根据ID查询
* @param id /
* @return /
*/
DeployDto findById(Long id);
/**
* 创建
* @param resources /
*/
void create(Deploy resources);
/**
* 编辑
* @param resources /
*/
void update(Deploy resources);
/**
* 删除
* @param ids /
*/
void delete(Set<Long> ids);
/**
* 部署文件到服务器
* @param fileSavePath 文件路径
* @param appId 应用ID
*/
void deploy(String fileSavePath, Long appId);
/**
* 查询部署状态
* @param resources /
* @return /
*/
String serverStatus(Deploy resources);
/**
* 启动服务
* @param resources /
* @return /
*/
String startServer(Deploy resources);
/**
* 停止服务
* @param resources /
* @return /
*/
String stopServer(Deploy resources);
/**
* 停止服务
* @param resources /
* @return /
*/
String serverReduction(DeployHistory resources);
/**
* 导出数据
* @param queryAll /
* @param response /
* @throws IOException /
*/
void download(List<DeployDto> queryAll, HttpServletResponse response) throws IOException;
}

View File

@@ -0,0 +1,81 @@
package com.platform.modules.mnt.service;
import com.platform.modules.mnt.domain.ServerDeploy;
import com.platform.modules.mnt.service.dto.ServerDeployDto;
import com.platform.modules.mnt.service.dto.ServerDeployQueryCriteria;
import org.springframework.data.domain.Pageable;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import java.util.Set;
/**
* @author AllDataDC
* @date 2023-01-27
*/
public interface ServerDeployService {
/**
* 分页查询
* @param criteria 条件
* @param pageable 分页参数
* @return /
*/
Object queryAll(ServerDeployQueryCriteria criteria, Pageable pageable);
/**
* 查询全部数据
* @param criteria 条件
* @return /
*/
List<ServerDeployDto> queryAll(ServerDeployQueryCriteria criteria);
/**
* 根据ID查询
* @param id /
* @return /
*/
ServerDeployDto findById(Long id);
/**
* 创建
* @param resources /
*/
void create(ServerDeploy resources);
/**
* 编辑
* @param resources /
*/
void update(ServerDeploy resources);
/**
* 删除
* @param ids /
*/
void delete(Set<Long> ids);
/**
* 根据IP查询
* @param ip /
* @return /
*/
ServerDeployDto findByIp(String ip);
/**
* 测试登录服务器
* @param resources /
* @return /
*/
Boolean testConnect(ServerDeploy resources);
/**
* 导出数据
* @param queryAll /
* @param response /
* @throws IOException /
*/
void download(List<ServerDeployDto> queryAll, HttpServletResponse response) throws IOException;
}

View File

@@ -0,0 +1,57 @@
package com.platform.modules.mnt.service.dto;
import lombok.Getter;
import lombok.Setter;
import com.platform.base.BaseDTO;
import java.io.Serializable;
/**
* @author AllDataDC
* @date 2023-01-27
*/
@Getter
@Setter
public class AppDto extends BaseDTO implements Serializable {
/**
* 应用编号
*/
private Long id;
/**
* 应用名称
*/
private String name;
/**
* 端口
*/
private Integer port;
/**
* 上传目录
*/
private String uploadPath;
/**
* 部署目录
*/
private String deployPath;
/**
* 备份目录
*/
private String backupPath;
/**
* 启动脚本
*/
private String startScript;
/**
* 部署脚本
*/
private String deployScript;
}

View File

@@ -0,0 +1,24 @@
package com.platform.modules.mnt.service.dto;
import lombok.Data;
import com.platform.annotation.Query;
import java.sql.Timestamp;
import java.util.List;
/**
* @author AllDataDC
* @date 2023-01-27
*/
@Data
public class AppQueryCriteria{
/**
* 模糊
*/
@Query(type = Query.Type.INNER_LIKE)
private String name;
@Query(type = Query.Type.BETWEEN)
private List<Timestamp> createTime;
}

View File

@@ -0,0 +1,41 @@
package com.platform.modules.mnt.service.dto;
import lombok.Getter;
import lombok.Setter;
import com.platform.base.BaseDTO;
import java.io.Serializable;
/**
* @author AllDataDC
* @date 2023-01-27
*/
@Getter
@Setter
public class DatabaseDto extends BaseDTO implements Serializable {
/**
* id
*/
private String id;
/**
* 数据库名称
*/
private String name;
/**
* 数据库连接地址
*/
private String jdbcUrl;
/**
* 数据库密码
*/
private String pwd;
/**
* 用户名
*/
private String userName;
}

View File

@@ -0,0 +1,30 @@
package com.platform.modules.mnt.service.dto;
import lombok.Data;
import com.platform.annotation.Query;
import java.sql.Timestamp;
import java.util.List;
/**
* @author AllDataDC
* @date 2023-01-27
*/
@Data
public class DatabaseQueryCriteria{
/**
* 模糊
*/
@Query(type = Query.Type.INNER_LIKE)
private String name;
/**
* 精确
*/
@Query
private String jdbcUrl;
@Query(type = Query.Type.BETWEEN)
private List<Timestamp> createTime;
}

View File

@@ -0,0 +1,64 @@
package com.platform.modules.mnt.service.dto;
import cn.hutool.core.collection.CollectionUtil;
import lombok.Getter;
import lombok.Setter;
import com.platform.base.BaseDTO;
import java.io.Serializable;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
/**
* @author AllDataDC
* @date 2023-01-27
*/
@Getter
@Setter
public class DeployDto extends BaseDTO implements Serializable {
/**
* 部署编号
*/
private String id;
private AppDto app;
/**
* 服务器
*/
private Set<ServerDeployDto> deploys;
private String servers;
/**
* 服务状态
*/
private String status;
public String getServers() {
if(CollectionUtil.isNotEmpty(deploys)){
return deploys.stream().map(ServerDeployDto::getName).collect(Collectors.joining(","));
}
return servers;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
DeployDto deployDto = (DeployDto) o;
return Objects.equals(id, deployDto.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
}

View File

@@ -0,0 +1,44 @@
package com.platform.modules.mnt.service.dto;
import lombok.Data;
import java.io.Serializable;
import java.sql.Timestamp;
/**
* @author AllDataDC
* @date 2023-01-27
*/
@Data
public class DeployHistoryDto implements Serializable {
/**
* 编号
*/
private String id;
/**
* 应用名称
*/
private String appName;
/**
* 部署IP
*/
private String ip;
/**
* 部署时间
*/
private Timestamp deployDate;
/**
* 部署人员
*/
private String deployUser;
/**
* 部署编号
*/
private Long deployId;
}

View File

@@ -0,0 +1,27 @@
package com.platform.modules.mnt.service.dto;
import lombok.Data;
import com.platform.annotation.Query;
import java.sql.Timestamp;
import java.util.List;
/**
* @author AllDataDC
* @date 2023-01-27
*/
@Data
public class DeployHistoryQueryCriteria{
/**
* 精确
*/
@Query(blurry = "appName,ip,deployUser")
private String blurry;
@Query
private Long deployId;
@Query(type = Query.Type.BETWEEN)
private List<Timestamp> deployDate;
}

View File

@@ -0,0 +1,25 @@
package com.platform.modules.mnt.service.dto;
import lombok.Data;
import com.platform.annotation.Query;
import java.sql.Timestamp;
import java.util.List;
/**
* @author AllDataDC
* @date 2023-01-27
*/
@Data
public class DeployQueryCriteria{
/**
* 模糊
*/
@Query(type = Query.Type.INNER_LIKE, propName = "name", joinName = "app")
private String appName;
@Query(type = Query.Type.BETWEEN)
private List<Timestamp> createTime;
}

View File

@@ -0,0 +1,47 @@
package com.platform.modules.mnt.service.dto;
import lombok.Getter;
import lombok.Setter;
import com.platform.base.BaseDTO;
import java.io.Serializable;
import java.util.Objects;
/**
* @author AllDataDC
* @date 2023-01-27
*/
@Getter
@Setter
public class ServerDeployDto extends BaseDTO implements Serializable {
private Long id;
private String name;
private String ip;
private Integer port;
private String account;
private String password;
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ServerDeployDto that = (ServerDeployDto) o;
return Objects.equals(id, that.id) &&
Objects.equals(name, that.name);
}
@Override
public int hashCode() {
return Objects.hash(id, name);
}
}

View File

@@ -0,0 +1,24 @@
package com.platform.modules.mnt.service.dto;
import lombok.Data;
import com.platform.annotation.Query;
import java.sql.Timestamp;
import java.util.List;
/**
* @author AllDataDC
* @date 2023-01-27
*/
@Data
public class ServerDeployQueryCriteria{
/**
* 模糊
*/
@Query(blurry = "name,ip,account")
private String blurry;
@Query(type = Query.Type.BETWEEN)
private List<Timestamp> createTime;
}

View File

@@ -0,0 +1,109 @@
package com.platform.modules.mnt.service.impl;
import lombok.RequiredArgsConstructor;
import com.platform.exception.BadRequestException;
import com.platform.modules.mnt.domain.App;
import com.platform.modules.mnt.repository.AppRepository;
import com.platform.modules.mnt.service.AppService;
import com.platform.modules.mnt.service.dto.AppDto;
import com.platform.modules.mnt.service.dto.AppQueryCriteria;
import com.platform.modules.mnt.service.mapstruct.AppMapper;
import com.platform.utils.FileUtil;
import com.platform.utils.PageUtil;
import com.platform.utils.QueryHelp;
import com.platform.utils.ValidationUtil;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;
/**
* @author AllDataDC
* @date 2023-01-27
*/
@Service
@RequiredArgsConstructor
public class AppServiceImpl implements AppService {
private final AppRepository appRepository;
private final AppMapper appMapper;
@Override
public Object queryAll(AppQueryCriteria criteria, Pageable pageable){
Page<App> page = appRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder),pageable);
return PageUtil.toPage(page.map(appMapper::toDto));
}
@Override
public List<AppDto> queryAll(AppQueryCriteria criteria){
return appMapper.toDto(appRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder)));
}
@Override
public AppDto findById(Long id) {
App app = appRepository.findById(id).orElseGet(App::new);
ValidationUtil.isNull(app.getId(),"App","id",id);
return appMapper.toDto(app);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void create(App resources) {
verification(resources);
appRepository.save(resources);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void update(App resources) {
verification(resources);
App app = appRepository.findById(resources.getId()).orElseGet(App::new);
ValidationUtil.isNull(app.getId(),"App","id",resources.getId());
app.copy(resources);
appRepository.save(app);
}
private void verification(App resources){
String opt = "/opt";
String home = "/home";
if (!(resources.getUploadPath().startsWith(opt) || resources.getUploadPath().startsWith(home))) {
throw new BadRequestException("文件只能上传在opt目录或者home目录 ");
}
if (!(resources.getDeployPath().startsWith(opt) || resources.getDeployPath().startsWith(home))) {
throw new BadRequestException("文件只能部署在opt目录或者home目录 ");
}
if (!(resources.getBackupPath().startsWith(opt) || resources.getBackupPath().startsWith(home))) {
throw new BadRequestException("文件只能备份在opt目录或者home目录 ");
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public void delete(Set<Long> ids) {
for (Long id : ids) {
appRepository.deleteById(id);
}
}
@Override
public void download(List<AppDto> queryAll, HttpServletResponse response) throws IOException {
List<Map<String, Object>> list = new ArrayList<>();
for (AppDto appDto : queryAll) {
Map<String,Object> map = new LinkedHashMap<>();
map.put("应用名称", appDto.getName());
map.put("端口", appDto.getPort());
map.put("上传目录", appDto.getUploadPath());
map.put("部署目录", appDto.getDeployPath());
map.put("备份目录", appDto.getBackupPath());
map.put("启动脚本", appDto.getStartScript());
map.put("部署脚本", appDto.getDeployScript());
map.put("创建日期", appDto.getCreateTime());
list.add(map);
}
FileUtil.downloadExcel(list, response);
}
}

View File

@@ -0,0 +1,103 @@
package com.platform.modules.mnt.service.impl;
import cn.hutool.core.util.IdUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import com.platform.modules.mnt.domain.Database;
import com.platform.modules.mnt.repository.DatabaseRepository;
import com.platform.modules.mnt.service.DatabaseService;
import com.platform.modules.mnt.service.dto.DatabaseDto;
import com.platform.modules.mnt.service.dto.DatabaseQueryCriteria;
import com.platform.modules.mnt.service.mapstruct.DatabaseMapper;
import com.platform.modules.mnt.util.SqlUtils;
import com.platform.utils.FileUtil;
import com.platform.utils.PageUtil;
import com.platform.utils.QueryHelp;
import com.platform.utils.ValidationUtil;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;
/**
* @author AllDataDC
* @date 2023-01-27
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class DatabaseServiceImpl implements DatabaseService {
private final DatabaseRepository databaseRepository;
private final DatabaseMapper databaseMapper;
@Override
public Object queryAll(DatabaseQueryCriteria criteria, Pageable pageable){
Page<Database> page = databaseRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder),pageable);
return PageUtil.toPage(page.map(databaseMapper::toDto));
}
@Override
public List<DatabaseDto> queryAll(DatabaseQueryCriteria criteria){
return databaseMapper.toDto(databaseRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder)));
}
@Override
public DatabaseDto findById(String id) {
Database database = databaseRepository.findById(id).orElseGet(Database::new);
ValidationUtil.isNull(database.getId(),"Database","id",id);
return databaseMapper.toDto(database);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void create(Database resources) {
resources.setId(IdUtil.simpleUUID());
databaseRepository.save(resources);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void update(Database resources) {
Database database = databaseRepository.findById(resources.getId()).orElseGet(Database::new);
ValidationUtil.isNull(database.getId(),"Database","id",resources.getId());
database.copy(resources);
databaseRepository.save(database);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void delete(Set<String> ids) {
for (String id : ids) {
databaseRepository.deleteById(id);
}
}
@Override
public boolean testConnection(Database resources) {
try {
return SqlUtils.testConnection(resources.getJdbcUrl(), resources.getUserName(), resources.getPwd());
} catch (Exception e) {
log.error(e.getMessage());
return false;
}
}
@Override
public void download(List<DatabaseDto> queryAll, HttpServletResponse response) throws IOException {
List<Map<String, Object>> list = new ArrayList<>();
for (DatabaseDto databaseDto : queryAll) {
Map<String,Object> map = new LinkedHashMap<>();
map.put("数据库名称", databaseDto.getName());
map.put("数据库连接地址", databaseDto.getJdbcUrl());
map.put("用户名", databaseDto.getUserName());
map.put("创建日期", databaseDto.getCreateTime());
list.add(map);
}
FileUtil.downloadExcel(list, response);
}
}

View File

@@ -0,0 +1,82 @@
package com.platform.modules.mnt.service.impl;
import cn.hutool.core.util.IdUtil;
import lombok.RequiredArgsConstructor;
import com.platform.modules.mnt.domain.DeployHistory;
import com.platform.modules.mnt.repository.DeployHistoryRepository;
import com.platform.modules.mnt.service.DeployHistoryService;
import com.platform.modules.mnt.service.dto.DeployHistoryDto;
import com.platform.modules.mnt.service.dto.DeployHistoryQueryCriteria;
import com.platform.modules.mnt.service.mapstruct.DeployHistoryMapper;
import com.platform.utils.FileUtil;
import com.platform.utils.PageUtil;
import com.platform.utils.QueryHelp;
import com.platform.utils.ValidationUtil;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;
/**
* @author AllDataDC
* @date 2023-01-27
*/
@Service
@RequiredArgsConstructor
public class DeployHistoryServiceImpl implements DeployHistoryService {
private final DeployHistoryRepository deployhistoryRepository;
private final DeployHistoryMapper deployhistoryMapper;
@Override
public Object queryAll(DeployHistoryQueryCriteria criteria, Pageable pageable){
Page<DeployHistory> page = deployhistoryRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder),pageable);
return PageUtil.toPage(page.map(deployhistoryMapper::toDto));
}
@Override
public List<DeployHistoryDto> queryAll(DeployHistoryQueryCriteria criteria){
return deployhistoryMapper.toDto(deployhistoryRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder)));
}
@Override
public DeployHistoryDto findById(String id) {
DeployHistory deployhistory = deployhistoryRepository.findById(id).orElseGet(DeployHistory::new);
ValidationUtil.isNull(deployhistory.getId(),"DeployHistory","id",id);
return deployhistoryMapper.toDto(deployhistory);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void create(DeployHistory resources) {
resources.setId(IdUtil.simpleUUID());
deployhistoryRepository.save(resources);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void delete(Set<String> ids) {
for (String id : ids) {
deployhistoryRepository.deleteById(id);
}
}
@Override
public void download(List<DeployHistoryDto> queryAll, HttpServletResponse response) throws IOException {
List<Map<String, Object>> list = new ArrayList<>();
for (DeployHistoryDto deployHistoryDto : queryAll) {
Map<String,Object> map = new LinkedHashMap<>();
map.put("部署编号", deployHistoryDto.getDeployId());
map.put("应用名称", deployHistoryDto.getAppName());
map.put("部署IP", deployHistoryDto.getIp());
map.put("部署时间", deployHistoryDto.getDeployDate());
map.put("部署人员", deployHistoryDto.getDeployUser());
list.add(map);
}
FileUtil.downloadExcel(list, response);
}
}

View File

@@ -0,0 +1,416 @@
package com.platform.modules.mnt.service.impl;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import com.platform.exception.BadRequestException;
import com.platform.modules.mnt.domain.App;
import com.platform.modules.mnt.domain.Deploy;
import com.platform.modules.mnt.domain.DeployHistory;
import com.platform.modules.mnt.domain.ServerDeploy;
import com.platform.modules.mnt.repository.DeployRepository;
import com.platform.modules.mnt.service.DeployHistoryService;
import com.platform.modules.mnt.service.DeployService;
import com.platform.modules.mnt.service.ServerDeployService;
import com.platform.modules.mnt.service.dto.AppDto;
import com.platform.modules.mnt.service.dto.DeployDto;
import com.platform.modules.mnt.service.dto.DeployQueryCriteria;
import com.platform.modules.mnt.service.dto.ServerDeployDto;
import com.platform.modules.mnt.service.mapstruct.DeployMapper;
import com.platform.modules.mnt.util.ExecuteShellUtil;
import com.platform.modules.mnt.util.ScpClientUtil;
import com.platform.modules.mnt.websocket.MsgType;
import com.platform.modules.mnt.websocket.SocketMsg;
import com.platform.modules.mnt.websocket.WebSocketServer;
import com.platform.utils.*;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;
/**
* @author AllDataDC
* @date 2023-01-27
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class DeployServiceImpl implements DeployService {
private final String FILE_SEPARATOR = "/";
private final DeployRepository deployRepository;
private final DeployMapper deployMapper;
private final ServerDeployService serverDeployService;
private final DeployHistoryService deployHistoryService;
/**
* 循环次数
*/
private final Integer count = 30;
@Override
public Object queryAll(DeployQueryCriteria criteria, Pageable pageable) {
Page<Deploy> page = deployRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root, criteria, criteriaBuilder), pageable);
return PageUtil.toPage(page.map(deployMapper::toDto));
}
@Override
public List<DeployDto> queryAll(DeployQueryCriteria criteria) {
return deployMapper.toDto(deployRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root, criteria, criteriaBuilder)));
}
@Override
public DeployDto findById(Long id) {
Deploy deploy = deployRepository.findById(id).orElseGet(Deploy::new);
ValidationUtil.isNull(deploy.getId(), "Deploy", "id", id);
return deployMapper.toDto(deploy);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void create(Deploy resources) {
deployRepository.save(resources);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void update(Deploy resources) {
Deploy deploy = deployRepository.findById(resources.getId()).orElseGet(Deploy::new);
ValidationUtil.isNull(deploy.getId(), "Deploy", "id", resources.getId());
deploy.copy(resources);
deployRepository.save(deploy);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void delete(Set<Long> ids) {
for (Long id : ids) {
deployRepository.deleteById(id);
}
}
@Override
public void deploy(String fileSavePath, Long id) {
deployApp(fileSavePath, id);
}
/**
* @param fileSavePath 本机路径
* @param id ID
*/
private void deployApp(String fileSavePath, Long id) {
DeployDto deploy = findById(id);
if (deploy == null) {
sendMsg("部署信息不存在", MsgType.ERROR);
throw new BadRequestException("部署信息不存在");
}
AppDto app = deploy.getApp();
if (app == null) {
sendMsg("包对应应用信息不存在", MsgType.ERROR);
throw new BadRequestException("包对应应用信息不存在");
}
int port = app.getPort();
//这个是服务器部署路径
String uploadPath = app.getUploadPath();
StringBuilder sb = new StringBuilder();
String msg;
Set<ServerDeployDto> deploys = deploy.getDeploys();
for (ServerDeployDto deployDTO : deploys) {
String ip = deployDTO.getIp();
ExecuteShellUtil executeShellUtil = getExecuteShellUtil(ip);
//判断是否第一次部署
boolean flag = checkFile(executeShellUtil, app);
//第一步要确认服务器上有这个目录
executeShellUtil.execute("mkdir -p " + app.getUploadPath());
executeShellUtil.execute("mkdir -p " + app.getBackupPath());
executeShellUtil.execute("mkdir -p " + app.getDeployPath());
//上传文件
msg = String.format("登陆到服务器:%s", ip);
ScpClientUtil scpClientUtil = getScpClientUtil(ip);
log.info(msg);
sendMsg(msg, MsgType.INFO);
msg = String.format("上传文件到服务器:%s<br>目录:%s下请稍等...", ip, uploadPath);
sendMsg(msg, MsgType.INFO);
scpClientUtil.putFile(fileSavePath, uploadPath);
if (flag) {
sendMsg("停止原来应用", MsgType.INFO);
//停止应用
stopApp(port, executeShellUtil);
sendMsg("备份原来应用", MsgType.INFO);
//备份应用
backupApp(executeShellUtil, ip, app.getDeployPath()+FILE_SEPARATOR, app.getName(), app.getBackupPath()+FILE_SEPARATOR, id);
}
sendMsg("部署应用", MsgType.INFO);
//部署文件,并启动应用
String deployScript = app.getDeployScript();
executeShellUtil.execute(deployScript);
sleep(3);
sendMsg("应用部署中,请耐心等待部署结果,或者稍后手动查看部署状态", MsgType.INFO);
int i = 0;
boolean result = false;
// 由于启动应用需要时间所以需要循环获取状态如果超过30次则认为是启动失败
while (i++ < count){
result = checkIsRunningStatus(port, executeShellUtil);
if(result){
break;
}
// 休眠6秒
sleep(6);
}
sb.append("服务器:").append(deployDTO.getName()).append("<br>应用:").append(app.getName());
sendResultMsg(result, sb);
executeShellUtil.close();
}
}
private void sleep(int second) {
try {
Thread.sleep(second * 1000);
} catch (InterruptedException e) {
log.error(e.getMessage(),e);
}
}
private void backupApp(ExecuteShellUtil executeShellUtil, String ip, String fileSavePath, String appName, String backupPath, Long id) {
String deployDate = DateUtil.format(new Date(), DatePattern.PURE_DATETIME_PATTERN);
StringBuilder sb = new StringBuilder();
backupPath += appName + FILE_SEPARATOR + deployDate + "\n";
sb.append("mkdir -p ").append(backupPath);
sb.append("mv -f ").append(fileSavePath);
sb.append(appName).append(" ").append(backupPath);
log.info("备份应用脚本:" + sb.toString());
executeShellUtil.execute(sb.toString());
//还原信息入库
DeployHistory deployHistory = new DeployHistory();
deployHistory.setAppName(appName);
deployHistory.setDeployUser(SecurityUtils.getCurrentUsername());
deployHistory.setIp(ip);
deployHistory.setDeployId(id);
deployHistoryService.create(deployHistory);
}
/**
* 停App
*
* @param port 端口
* @param executeShellUtil /
*/
private void stopApp(int port, ExecuteShellUtil executeShellUtil) {
//发送停止命令
executeShellUtil.execute(String.format("lsof -i :%d|grep -v \"PID\"|awk '{print \"kill -9\",$2}'|sh", port));
}
/**
* 指定端口程序是否在运行
*
* @param port 端口
* @param executeShellUtil /
* @return true 正在运行 false 已经停止
*/
private boolean checkIsRunningStatus(int port, ExecuteShellUtil executeShellUtil) {
String result = executeShellUtil.executeForResult(String.format("fuser -n tcp %d", port));
return result.indexOf("/tcp:")>0;
}
private void sendMsg(String msg, MsgType msgType) {
try {
WebSocketServer.sendInfo(new SocketMsg(msg, msgType), "deploy");
} catch (IOException e) {
log.error(e.getMessage(),e);
}
}
@Override
public String serverStatus(Deploy resources) {
Set<ServerDeploy> serverDeploys = resources.getDeploys();
App app = resources.getApp();
for (ServerDeploy serverDeploy : serverDeploys) {
StringBuilder sb = new StringBuilder();
ExecuteShellUtil executeShellUtil = getExecuteShellUtil(serverDeploy.getIp());
sb.append("服务器:").append(serverDeploy.getName()).append("<br>应用:").append(app.getName());
boolean result = checkIsRunningStatus(app.getPort(), executeShellUtil);
if (result) {
sb.append("<br>正在运行");
sendMsg(sb.toString(), MsgType.INFO);
} else {
sb.append("<br>已停止!");
sendMsg(sb.toString(), MsgType.ERROR);
}
log.info(sb.toString());
executeShellUtil.close();
}
return "执行完毕";
}
private boolean checkFile(ExecuteShellUtil executeShellUtil, AppDto appDTO) {
String result = executeShellUtil.executeForResult("find " + appDTO.getDeployPath() + " -name " + appDTO.getName());
return result.indexOf(appDTO.getName())>0;
}
/**
* 启动服务
* @param resources /
* @return /
*/
@Override
public String startServer(Deploy resources) {
Set<ServerDeploy> deploys = resources.getDeploys();
App app = resources.getApp();
for (ServerDeploy deploy : deploys) {
StringBuilder sb = new StringBuilder();
ExecuteShellUtil executeShellUtil = getExecuteShellUtil(deploy.getIp());
//为了防止重复启动,这里先停止应用
stopApp(app.getPort(), executeShellUtil);
sb.append("服务器:").append(deploy.getName()).append("<br>应用:").append(app.getName());
sendMsg("下发启动命令", MsgType.INFO);
executeShellUtil.execute(app.getStartScript());
sleep(3);
sendMsg("应用启动中,请耐心等待启动结果,或者稍后手动查看运行状态", MsgType.INFO);
int i = 0;
boolean result = false;
// 由于启动应用需要时间所以需要循环获取状态如果超过30次则认为是启动失败
while (i++ < count){
result = checkIsRunningStatus(app.getPort(), executeShellUtil);
if(result){
break;
}
// 休眠6秒
sleep(6);
}
sendResultMsg(result, sb);
log.info(sb.toString());
executeShellUtil.close();
}
return "执行完毕";
}
/**
* 停止服务
* @param resources /
* @return /
*/
@Override
public String stopServer(Deploy resources) {
Set<ServerDeploy> deploys = resources.getDeploys();
App app = resources.getApp();
for (ServerDeploy deploy : deploys) {
StringBuilder sb = new StringBuilder();
ExecuteShellUtil executeShellUtil = getExecuteShellUtil(deploy.getIp());
sb.append("服务器:").append(deploy.getName()).append("<br>应用:").append(app.getName());
sendMsg("下发停止命令", MsgType.INFO);
//停止应用
stopApp(app.getPort(), executeShellUtil);
sleep(1);
boolean result = checkIsRunningStatus(app.getPort(), executeShellUtil);
if (result) {
sb.append("<br>关闭失败!");
sendMsg(sb.toString(), MsgType.ERROR);
} else {
sb.append("<br>关闭成功!");
sendMsg(sb.toString(), MsgType.INFO);
}
log.info(sb.toString());
executeShellUtil.close();
}
return "执行完毕";
}
@Override
public String serverReduction(DeployHistory resources) {
Long deployId = resources.getDeployId();
Deploy deployInfo = deployRepository.findById(deployId).orElseGet(Deploy::new);
String deployDate = DateUtil.format(resources.getDeployDate(), DatePattern.PURE_DATETIME_PATTERN);
App app = deployInfo.getApp();
if (app == null) {
sendMsg("应用信息不存在:" + resources.getAppName(), MsgType.ERROR);
throw new BadRequestException("应用信息不存在:" + resources.getAppName());
}
String backupPath = app.getBackupPath()+FILE_SEPARATOR;
backupPath += resources.getAppName() + FILE_SEPARATOR + deployDate;
//这个是服务器部署路径
String deployPath = app.getDeployPath();
String ip = resources.getIp();
ExecuteShellUtil executeShellUtil = getExecuteShellUtil(ip);
String msg;
msg = String.format("登陆到服务器:%s", ip);
log.info(msg);
sendMsg(msg, MsgType.INFO);
sendMsg("停止原来应用", MsgType.INFO);
//停止应用
stopApp(app.getPort(), executeShellUtil);
//删除原来应用
sendMsg("删除应用", MsgType.INFO);
executeShellUtil.execute("rm -rf " + deployPath + FILE_SEPARATOR + resources.getAppName());
//还原应用
sendMsg("还原应用", MsgType.INFO);
executeShellUtil.execute("cp -r " + backupPath + "/. " + deployPath);
sendMsg("启动应用", MsgType.INFO);
executeShellUtil.execute(app.getStartScript());
sendMsg("应用启动中,请耐心等待启动结果,或者稍后手动查看启动状态", MsgType.INFO);
int i = 0;
boolean result = false;
// 由于启动应用需要时间所以需要循环获取状态如果超过30次则认为是启动失败
while (i++ < count){
result = checkIsRunningStatus(app.getPort(), executeShellUtil);
if(result){
break;
}
// 休眠6秒
sleep(6);
}
StringBuilder sb = new StringBuilder();
sb.append("服务器:").append(ip).append("<br>应用:").append(resources.getAppName());
sendResultMsg(result, sb);
executeShellUtil.close();
return "";
}
private ExecuteShellUtil getExecuteShellUtil(String ip) {
ServerDeployDto serverDeployDTO = serverDeployService.findByIp(ip);
if (serverDeployDTO == null) {
sendMsg("IP对应服务器信息不存在" + ip, MsgType.ERROR);
throw new BadRequestException("IP对应服务器信息不存在" + ip);
}
return new ExecuteShellUtil(ip, serverDeployDTO.getAccount(), serverDeployDTO.getPassword(),serverDeployDTO.getPort());
}
private ScpClientUtil getScpClientUtil(String ip) {
ServerDeployDto serverDeployDTO = serverDeployService.findByIp(ip);
if (serverDeployDTO == null) {
sendMsg("IP对应服务器信息不存在" + ip, MsgType.ERROR);
throw new BadRequestException("IP对应服务器信息不存在" + ip);
}
return ScpClientUtil.getInstance(ip, serverDeployDTO.getPort(), serverDeployDTO.getAccount(), serverDeployDTO.getPassword());
}
private void sendResultMsg(boolean result, StringBuilder sb) {
if (result) {
sb.append("<br>启动成功!");
sendMsg(sb.toString(), MsgType.INFO);
} else {
sb.append("<br>启动失败!");
sendMsg(sb.toString(), MsgType.ERROR);
}
}
@Override
public void download(List<DeployDto> queryAll, HttpServletResponse response) throws IOException {
List<Map<String, Object>> list = new ArrayList<>();
for (DeployDto deployDto : queryAll) {
Map<String,Object> map = new LinkedHashMap<>();
map.put("应用名称", deployDto.getApp().getName());
map.put("服务器", deployDto.getServers());
map.put("部署日期", deployDto.getCreateTime());
list.add(map);
}
FileUtil.downloadExcel(list, response);
}
}

View File

@@ -0,0 +1,111 @@
package com.platform.modules.mnt.service.impl;
import lombok.RequiredArgsConstructor;
import com.platform.modules.mnt.domain.ServerDeploy;
import com.platform.modules.mnt.repository.ServerDeployRepository;
import com.platform.modules.mnt.service.ServerDeployService;
import com.platform.modules.mnt.service.dto.ServerDeployDto;
import com.platform.modules.mnt.service.dto.ServerDeployQueryCriteria;
import com.platform.modules.mnt.service.mapstruct.ServerDeployMapper;
import com.platform.modules.mnt.util.ExecuteShellUtil;
import com.platform.utils.FileUtil;
import com.platform.utils.PageUtil;
import com.platform.utils.QueryHelp;
import com.platform.utils.ValidationUtil;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;
/**
* @author AllDataDC
* @date 2023-01-27
*/
@Service
@RequiredArgsConstructor
public class ServerDeployServiceImpl implements ServerDeployService {
private final ServerDeployRepository serverDeployRepository;
private final ServerDeployMapper serverDeployMapper;
@Override
public Object queryAll(ServerDeployQueryCriteria criteria, Pageable pageable){
Page<ServerDeploy> page = serverDeployRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder),pageable);
return PageUtil.toPage(page.map(serverDeployMapper::toDto));
}
@Override
public List<ServerDeployDto> queryAll(ServerDeployQueryCriteria criteria){
return serverDeployMapper.toDto(serverDeployRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder)));
}
@Override
public ServerDeployDto findById(Long id) {
ServerDeploy server = serverDeployRepository.findById(id).orElseGet(ServerDeploy::new);
ValidationUtil.isNull(server.getId(),"ServerDeploy","id",id);
return serverDeployMapper.toDto(server);
}
@Override
public ServerDeployDto findByIp(String ip) {
ServerDeploy deploy = serverDeployRepository.findByIp(ip);
return serverDeployMapper.toDto(deploy);
}
@Override
public Boolean testConnect(ServerDeploy resources) {
ExecuteShellUtil executeShellUtil = null;
try {
executeShellUtil = new ExecuteShellUtil(resources.getIp(), resources.getAccount(), resources.getPassword(),resources.getPort());
return executeShellUtil.execute("ls")==0;
} catch (Exception e) {
return false;
}finally {
if (executeShellUtil != null) {
executeShellUtil.close();
}
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public void create(ServerDeploy resources) {
serverDeployRepository.save(resources);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void update(ServerDeploy resources) {
ServerDeploy serverDeploy = serverDeployRepository.findById(resources.getId()).orElseGet(ServerDeploy::new);
ValidationUtil.isNull( serverDeploy.getId(),"ServerDeploy","id",resources.getId());
serverDeploy.copy(resources);
serverDeployRepository.save(serverDeploy);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void delete(Set<Long> ids) {
for (Long id : ids) {
serverDeployRepository.deleteById(id);
}
}
@Override
public void download(List<ServerDeployDto> queryAll, HttpServletResponse response) throws IOException {
List<Map<String, Object>> list = new ArrayList<>();
for (ServerDeployDto deployDto : queryAll) {
Map<String,Object> map = new LinkedHashMap<>();
map.put("服务器名称", deployDto.getName());
map.put("服务器IP", deployDto.getIp());
map.put("端口", deployDto.getPort());
map.put("账号", deployDto.getAccount());
map.put("创建日期", deployDto.getCreateTime());
list.add(map);
}
FileUtil.downloadExcel(list, response);
}
}

View File

@@ -0,0 +1,17 @@
package com.platform.modules.mnt.service.mapstruct;
import com.platform.base.BaseMapper;
import com.platform.modules.mnt.domain.App;
import com.platform.modules.mnt.service.dto.AppDto;
import org.mapstruct.Mapper;
import org.mapstruct.ReportingPolicy;
/**
* @author AllDataDC
* @date 2023-01-27
*/
@Mapper(componentModel = "spring",uses = {},unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface AppMapper extends BaseMapper<AppDto, App> {
}

View File

@@ -0,0 +1,17 @@
package com.platform.modules.mnt.service.mapstruct;
import com.platform.base.BaseMapper;
import com.platform.modules.mnt.domain.Database;
import com.platform.modules.mnt.service.dto.DatabaseDto;
import org.mapstruct.Mapper;
import org.mapstruct.ReportingPolicy;
/**
* @author AllDataDC
* @date 2023-01-27
*/
@Mapper(componentModel = "spring",unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface DatabaseMapper extends BaseMapper<DatabaseDto, Database> {
}

View File

@@ -0,0 +1,17 @@
package com.platform.modules.mnt.service.mapstruct;
import com.platform.base.BaseMapper;
import com.platform.modules.mnt.domain.DeployHistory;
import com.platform.modules.mnt.service.dto.DeployHistoryDto;
import org.mapstruct.Mapper;
import org.mapstruct.ReportingPolicy;
/**
* @author AllDataDC
* @date 2023-01-27
*/
@Mapper(componentModel = "spring",uses = {},unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface DeployHistoryMapper extends BaseMapper<DeployHistoryDto, DeployHistory> {
}

View File

@@ -0,0 +1,17 @@
package com.platform.modules.mnt.service.mapstruct;
import com.platform.base.BaseMapper;
import com.platform.modules.mnt.domain.Deploy;
import com.platform.modules.mnt.service.dto.DeployDto;
import org.mapstruct.Mapper;
import org.mapstruct.ReportingPolicy;
/**
* @author AllDataDC
* @date 2023-01-27
*/
@Mapper(componentModel = "spring",uses = {AppMapper.class, ServerDeployMapper.class},unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface DeployMapper extends BaseMapper<DeployDto, Deploy> {
}

View File

@@ -0,0 +1,17 @@
package com.platform.modules.mnt.service.mapstruct;
import com.platform.base.BaseMapper;
import com.platform.modules.mnt.domain.ServerDeploy;
import com.platform.modules.mnt.service.dto.ServerDeployDto;
import org.mapstruct.Mapper;
import org.mapstruct.ReportingPolicy;
/**
* @author AllDataDC
* @date 2023-01-27
*/
@Mapper(componentModel = "spring",uses = {},unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface ServerDeployMapper extends BaseMapper<ServerDeployDto, ServerDeploy> {
}

View File

@@ -0,0 +1,140 @@
/*
* <<
* Davinci
* ==
* Copyright (C) 2016 - 2019 EDP
* ==
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* >>
*
*/
package com.platform.modules.mnt.util;
import lombok.extern.slf4j.Slf4j;
/**
* @author AllDataDC
*/
@Slf4j
@SuppressWarnings({"unchecked","all"})
public enum DataTypeEnum {
/** mysql */
MYSQL("mysql", "mysql", "com.mysql.jdbc.Driver", "`", "`", "'", "'"),
/** oracle */
ORACLE("oracle", "oracle", "oracle.jdbc.driver.OracleDriver", "\"", "\"", "\"", "\""),
/** sql server */
SQLSERVER("sqlserver", "sqlserver", "com.microsoft.sqlserver.jdbc.SQLServerDriver", "\"", "\"", "\"", "\""),
/** h2 */
H2("h2", "h2", "org.h2.Driver", "`", "`", "\"", "\""),
/** phoenix */
PHOENIX("phoenix", "hbase phoenix", "org.apache.phoenix.jdbc.PhoenixDriver", "", "", "\"", "\""),
/** mongo */
MONGODB("mongo", "mongodb", "mongodb.jdbc.MongoDriver", "`", "`", "\"", "\""),
/** sql4es */
ELASTICSEARCH("sql4es", "elasticsearch", "nl.anchormen.sql4es.jdbc.ESDriver", "", "", "'", "'"),
/** presto */
PRESTO("presto", "presto", "com.facebook.presto.jdbc.PrestoDriver", "", "", "\"", "\""),
/** moonbox */
MOONBOX("moonbox", "moonbox", "moonbox.jdbc.MbDriver", "`", "`", "`", "`"),
/** cassandra */
CASSANDRA("cassandra", "cassandra", "com.github.adejanovski.cassandra.jdbc.CassandraDriver", "", "", "'", "'"),
/** click house */
CLICKHOUSE("clickhouse", "clickhouse", "ru.yandex.clickhouse.ClickHouseDriver", "", "", "\"", "\""),
/** kylin */
KYLIN("kylin", "kylin", "org.apache.kylin.jdbc.Driver", "\"", "\"", "\"", "\""),
/** vertica */
VERTICA("vertica", "vertica", "com.vertica.jdbc.Driver", "", "", "'", "'"),
/** sap */
HANA("sap", "sap hana", "com.sap.db.jdbc.Driver", "", "", "'", "'"),
/** impala */
IMPALA("impala", "impala", "com.cloudera.impala.jdbc41.Driver", "", "", "'", "'");
private String feature;
private String desc;
private String driver;
private String keywordPrefix;
private String keywordSuffix;
private String aliasPrefix;
private String aliasSuffix;
private static final String JDBC_URL_PREFIX = "jdbc:";
DataTypeEnum(String feature, String desc, String driver, String keywordPrefix, String keywordSuffix, String aliasPrefix, String aliasSuffix) {
this.feature = feature;
this.desc = desc;
this.driver = driver;
this.keywordPrefix = keywordPrefix;
this.keywordSuffix = keywordSuffix;
this.aliasPrefix = aliasPrefix;
this.aliasSuffix = aliasSuffix;
}
public static DataTypeEnum urlOf(String jdbcUrl) {
String url = jdbcUrl.toLowerCase().trim();
for (DataTypeEnum dataTypeEnum : values()) {
if (url.startsWith(JDBC_URL_PREFIX + dataTypeEnum.feature)) {
try {
Class<?> aClass = Class.forName(dataTypeEnum.getDriver());
if (null == aClass) {
throw new RuntimeException("Unable to get driver instance for jdbcUrl: " + jdbcUrl);
}
} catch (ClassNotFoundException e) {
throw new RuntimeException("Unable to get driver instance: " + jdbcUrl);
}
return dataTypeEnum;
}
}
return null;
}
public String getFeature() {
return feature;
}
public String getDesc() {
return desc;
}
public String getDriver() {
return driver;
}
public String getKeywordPrefix() {
return keywordPrefix;
}
public String getKeywordSuffix() {
return keywordSuffix;
}
public String getAliasPrefix() {
return aliasPrefix;
}
public String getAliasSuffix() {
return aliasSuffix;
}
}

View File

@@ -0,0 +1,87 @@
package com.platform.modules.mnt.util;
import cn.hutool.core.io.IoUtil;
import com.jcraft.jsch.ChannelShell;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.Session;
import lombok.extern.slf4j.Slf4j;
import java.io.*;
import java.util.Vector;
/**
* 执行shell命令
*
* @author: ZhangHouYing
* @date 2023-01-27
*/
@Slf4j
public class ExecuteShellUtil {
private Vector<String> stdout;
Session session;
public ExecuteShellUtil(final String ipAddress, final String username, final String password,int port) {
try {
JSch jsch = new JSch();
session = jsch.getSession(username, ipAddress, port);
session.setPassword(password);
session.setConfig("StrictHostKeyChecking", "no");
session.connect(3000);
} catch (Exception e) {
log.error(e.getMessage(),e);
}
}
public int execute(final String command) {
int returnCode = 0;
ChannelShell channel = null;
PrintWriter printWriter = null;
BufferedReader input = null;
stdout = new Vector<String>();
try {
channel = (ChannelShell) session.openChannel("shell");
channel.connect();
input = new BufferedReader(new InputStreamReader(channel.getInputStream()));
printWriter = new PrintWriter(channel.getOutputStream());
printWriter.println(command);
printWriter.println("exit");
printWriter.flush();
log.info("The remote command is: ");
String line;
while ((line = input.readLine()) != null) {
stdout.add(line);
System.out.println(line);
}
} catch (Exception e) {
log.error(e.getMessage(),e);
return -1;
}finally {
IoUtil.close(printWriter);
IoUtil.close(input);
if (channel != null) {
channel.disconnect();
}
}
return returnCode;
}
public void close(){
if (session != null) {
session.disconnect();
}
}
public String executeForResult(String command) {
execute(command);
StringBuilder sb = new StringBuilder();
for (String str : stdout) {
sb.append(str);
}
return sb.toString();
}
}

View File

@@ -0,0 +1,91 @@
package com.platform.modules.mnt.util;
import ch.ethz.ssh2.Connection;
import ch.ethz.ssh2.SCPClient;
import com.google.common.collect.Maps;
import java.io.IOException;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* 远程执行linux命令
* @author: ZhangHouYing
* @date 2023-01-27 10:06
*/
public class ScpClientUtil {
static private Map<String,ScpClientUtil> instance = Maps.newHashMap();
static synchronized public ScpClientUtil getInstance(String ip, int port, String username, String password) {
if (instance.get(ip) == null) {
instance.put(ip, new ScpClientUtil(ip, port, username, password));
}
return instance.get(ip);
}
public ScpClientUtil(String ip, int port, String username, String password) {
this.ip = ip;
this.port = port;
this.username = username;
this.password = password;
}
public void getFile(String remoteFile, String localTargetDirectory) {
Connection conn = new Connection(ip, port);
try {
conn.connect();
boolean isAuthenticated = conn.authenticateWithPassword(username, password);
if (!isAuthenticated) {
System.err.println("authentication failed");
}
SCPClient client = new SCPClient(conn);
client.get(remoteFile, localTargetDirectory);
} catch (IOException ex) {
Logger.getLogger(SCPClient.class.getName()).log(Level.SEVERE, null, ex);
}finally{
conn.close();
}
}
public void putFile(String localFile, String remoteTargetDirectory) {
putFile(localFile, null, remoteTargetDirectory);
}
public void putFile(String localFile, String remoteFileName, String remoteTargetDirectory) {
putFile(localFile, remoteFileName, remoteTargetDirectory,null);
}
public void putFile(String localFile, String remoteFileName, String remoteTargetDirectory, String mode) {
Connection conn = new Connection(ip, port);
try {
conn.connect();
boolean isAuthenticated = conn.authenticateWithPassword(username, password);
if (!isAuthenticated) {
System.err.println("authentication failed");
}
SCPClient client = new SCPClient(conn);
if ((mode == null) || (mode.length() == 0)) {
mode = "0600";
}
if (remoteFileName == null) {
client.put(localFile, remoteTargetDirectory);
} else {
client.put(localFile, remoteFileName, remoteTargetDirectory, mode);
}
} catch (IOException ex) {
Logger.getLogger(ScpClientUtil.class.getName()).log(Level.SEVERE, null, ex);
}finally{
conn.close();
}
}
private String ip;
private int port;
private String username;
private String password;
}

View File

@@ -0,0 +1,242 @@
package com.platform.modules.mnt.util;
import com.alibaba.druid.pool.DruidDataSource;
import com.github.yeecode.dynamicdatasource.DynamicDataSource;
import com.github.yeecode.dynamicdatasource.model.DataSourceInfo;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import com.platform.utils.CloseUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import javax.sql.DataSource;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.sql.*;
import java.util.List;
/**
* @author AllDataDC
*/
@Slf4j
@Component
public class SqlUtils {
@Autowired
private static DynamicDataSource dynamicDataSource;//注入动态数据源
/**
* 获取数据源
*
* @param jdbcUrl /
* @param userName /
* @param password /
* @return DataSource
*/
private static DataSource getDataSource(String jdbcUrl, String userName, String password) {
DruidDataSource druidDataSource = new DruidDataSource();
String className;
try {
className = DriverManager.getDriver(jdbcUrl.trim()).getClass().getName();
} catch (SQLException e) {
throw new RuntimeException("Get class name error: =" + jdbcUrl);
}
if (StringUtils.isEmpty(className)) {
DataTypeEnum dataTypeEnum = DataTypeEnum.urlOf(jdbcUrl);
if (null == dataTypeEnum) {
throw new RuntimeException("Not supported data type: jdbcUrl=" + jdbcUrl);
}
druidDataSource.setDriverClassName(dataTypeEnum.getDriver());
} else {
druidDataSource.setDriverClassName(className);
}
druidDataSource.setUrl(jdbcUrl);
druidDataSource.setUsername(userName);
druidDataSource.setPassword(password);
// 配置获取连接等待超时的时间
druidDataSource.setMaxWait(3000);
// 配置初始化大小、最小、最大
druidDataSource.setInitialSize(1);
druidDataSource.setMinIdle(1);
druidDataSource.setMaxActive(1);
// 如果链接出现异常则直接判定为失败而不是一直重试
druidDataSource.setBreakAfterAcquireFailure(true);
try {
druidDataSource.init();
} catch (SQLException e) {
log.error("Exception during pool initialization", e);
throw new RuntimeException(e.getMessage());
}
return druidDataSource;
}
/**
* 获取数据源
*
* @param jdbcUrl /
* @param userName /
* @param password /
* @return DataSource
*/
private static Connection getConnection(String jdbcUrl, String userName, String password) {
String dataSourceKey = String.valueOf(System.currentTimeMillis());
DataSourceInfo dataSourceInfo = new DataSourceInfo(dataSourceKey,
"com.p6spy.engine.spy.P6SpyDriver", jdbcUrl, userName, password);
dynamicDataSource.addDataSource(dataSourceInfo, true);
dynamicDataSource.switchDataSource(dataSourceKey);
Connection connection = null;
try {
connection = dynamicDataSource.getConnection();
} catch (Exception ignored) {
}
try {
int timeOut = 5;
if (null == connection || connection.isClosed() || !connection.isValid(timeOut)) {
log.info("connection is closed or invalid, retry get connection!");
connection = dynamicDataSource.getConnection();
}
} catch (Exception e) {
log.error("create connection error, jdbcUrl: {}", jdbcUrl);
throw new RuntimeException("create connection error, jdbcUrl: " + jdbcUrl);
} finally {
CloseUtil.close(connection);
}
return connection;
}
/**
* 获取数据源
*
* @param jdbcUrl /
* @param userName /
* @param password /
* @return DataSource
*/
private static Connection getDruidConnection(String jdbcUrl, String userName, String password) {
DataSource dataSource = getDataSource(jdbcUrl, userName, password);
Connection connection = null;
try {
connection = dataSource.getConnection();
} catch (Exception ignored) {}
try {
int timeOut = 5;
if (null == connection || connection.isClosed() || !connection.isValid(timeOut)) {
log.info("connection is closed or invalid, retry get connection!");
connection = dataSource.getConnection();
}
} catch (Exception e) {
log.error("create connection error, jdbcUrl: {}", jdbcUrl);
throw new RuntimeException("create connection error, jdbcUrl: " + jdbcUrl);
} finally {
CloseUtil.close(connection);
}
return connection;
}
private static void releaseConnection(Connection connection) {
if (null != connection) {
try {
connection.close();
} catch (Exception e) {
log.error(e.getMessage(), e);
log.error("connection close error" + e.getMessage());
}
}
}
public static boolean testConnection(String jdbcUrl, String userName, String password) {
Connection connection = null;
try {
connection = getDruidConnection(jdbcUrl, userName, password);
if (null != connection) {
return true;
}
} catch (Exception e) {
log.info("Get connection failed:" + e.getMessage());
} finally {
releaseConnection(connection);
}
return false;
}
public static String executeFile(String jdbcUrl, String userName, String password, File sqlFile) {
Connection connection = getDruidConnection(jdbcUrl, userName, password);
try {
batchExecute(connection, readSqlList(sqlFile));
} catch (Exception e) {
log.error("sql脚本执行发生异常:{}", e.getMessage());
return e.getMessage();
} finally {
releaseConnection(connection);
}
return "success";
}
/**
* 批量执行sql
*
* @param connection /
* @param sqlList /
*/
public static void batchExecute(Connection connection, List<String> sqlList) {
Statement st = null;
try {
st = connection.createStatement();
for (String sql : sqlList) {
if (sql.endsWith(";")) {
sql = sql.substring(0, sql.length() - 1);
}
st.addBatch(sql);
}
st.executeBatch();
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
CloseUtil.close(st);
}
}
/**
* 将文件中的sql语句以为单位读取到列表中
*
* @param sqlFile /
* @return /
* @throws Exception e
*/
private static List<String> readSqlList(File sqlFile) throws Exception {
List<String> sqlList = Lists.newArrayList();
StringBuilder sb = new StringBuilder();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(
new FileInputStream(sqlFile), StandardCharsets.UTF_8))) {
String tmp;
while ((tmp = reader.readLine()) != null) {
log.info("line:{}", tmp);
if (tmp.endsWith(";")) {
sb.append(tmp);
sqlList.add(sb.toString());
sb.delete(0, sb.length());
} else {
sb.append(tmp);
}
}
if (!"".endsWith(sb.toString().trim())) {
sqlList.add(sb.toString());
}
}
return sqlList;
}
}

View File

@@ -0,0 +1,17 @@
package com.platform.modules.mnt.websocket;
/**
* @author AllDataDC
* @date 2023-01-27 9:56
*/
public enum MsgType {
/** 连接 */
CONNECT,
/** 关闭 */
CLOSE,
/** 信息 */
INFO,
/** 错误 */
ERROR
}

View File

@@ -0,0 +1,19 @@
package com.platform.modules.mnt.websocket;
import lombok.Data;
/**
* @author AllDataDC
* @date 2023-01-27 9:55
*/
@Data
public class SocketMsg {
private String msg;
private MsgType msgType;
public SocketMsg(String msg, MsgType msgType) {
this.msg = msg;
this.msgType = msgType;
}
}

View File

@@ -0,0 +1,125 @@
package com.platform.modules.mnt.websocket;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Objects;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* @author AllDataDC
* @date 2023-01-27 15:46
*/
@ServerEndpoint("/webSocket/{sid}")
@Slf4j
@Component
public class WebSocketServer {
/**
* concurrent包的线程安全Set用来存放每个客户端对应的MyWebSocket对象。
*/
private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<WebSocketServer>();
/**
* 与某个客户端的连接会话,需要通过它来给客户端发送数据
*/
private Session session;
/**
* 接收sid
*/
private String sid="";
/**
* 连接建立成功调用的方法
* */
@OnOpen
public void onOpen(Session session,@PathParam("sid") String sid) {
this.session = session;
//如果存在就先删除一个,防止重复推送消息
for (WebSocketServer webSocket:webSocketSet) {
if (webSocket.sid.equals(sid)) {
webSocketSet.remove(webSocket);
}
}
webSocketSet.add(this);
this.sid=sid;
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose() {
webSocketSet.remove(this);
}
/**
* 收到客户端消息后调用的方法
* @param message 客户端发送过来的消息*/
@OnMessage
public void onMessage(String message, Session session) {
log.info("收到来"+sid+"的信息:"+message);
//群发消息
for (WebSocketServer item : webSocketSet) {
try {
item.sendMessage(message);
} catch (IOException e) {
log.error(e.getMessage(),e);
}
}
}
@OnError
public void onError(Session session, Throwable error) {
log.error("发生错误");
error.printStackTrace();
}
/**
* 实现服务器主动推送
*/
private void sendMessage(String message) throws IOException {
this.session.getBasicRemote().sendText(message);
}
/**
* 群发自定义消息
* */
public static void sendInfo(SocketMsg socketMsg,@PathParam("sid") String sid) throws IOException {
String message = JSONObject.toJSONString(socketMsg);
log.info("推送消息到"+sid+",推送内容:"+message);
for (WebSocketServer item : webSocketSet) {
try {
//这里可以设定只推送给这个sid的为null则全部推送
if(sid==null) {
item.sendMessage(message);
}else if(item.sid.equals(sid)){
item.sendMessage(message);
}
} catch (IOException ignored) { }
}
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
WebSocketServer that = (WebSocketServer) o;
return Objects.equals(session, that.session) &&
Objects.equals(sid, that.sid);
}
@Override
public int hashCode() {
return Objects.hash(session, sid);
}
}

View File

@@ -0,0 +1,37 @@
package com.platform.modules.quartz.config;
import lombok.RequiredArgsConstructor;
import com.platform.modules.quartz.domain.QuartzJob;
import com.platform.modules.quartz.repository.QuartzJobRepository;
import com.platform.modules.quartz.utils.QuartzManage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* @author AllDataDC
* @date 2023-01-27
*/
@Component
@RequiredArgsConstructor
public class JobRunner implements ApplicationRunner {
private static final Logger log = LoggerFactory.getLogger(JobRunner.class);
private final QuartzJobRepository quartzJobRepository;
private final QuartzManage quartzManage;
/**
* 项目启动时重新激活启用的定时任务
*
* @param applicationArguments /
*/
@Override
public void run(ApplicationArguments applicationArguments) {
List<QuartzJob> quartzJobs = quartzJobRepository.findByIsPauseIsFalse();
quartzJobs.forEach(quartzManage::addJob);
log.info("Timing task injection complete");
}
}

View File

@@ -0,0 +1,38 @@
package com.platform.modules.quartz.config;
import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.AdaptableJobFactory;
import org.springframework.stereotype.Component;
/**
* 定时任务配置
* @author AllDataDC
* @date 2023-01-27
*/
@Configuration
public class QuartzConfig {
/**
* 解决Job中注入Spring Bean为null的问题
*/
@Component("quartzJobFactory")
public static class QuartzJobFactory extends AdaptableJobFactory {
private final AutowireCapableBeanFactory capableBeanFactory;
public QuartzJobFactory(AutowireCapableBeanFactory capableBeanFactory) {
this.capableBeanFactory = capableBeanFactory;
}
@Override
protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
//调用父类的方法把Job注入到spring中
Object jobInstance = super.createJobInstance(bundle);
capableBeanFactory.autowireBean(jobInstance);
return jobInstance;
}
}
}

View File

@@ -0,0 +1,71 @@
package com.platform.modules.quartz.domain;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import com.platform.base.BaseEntity;
import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
/**
* @author AllDataDC
* @date 2023-01-27
*/
@Getter
@Setter
@Entity
@Table(name = "sys_quartz_job")
public class QuartzJob extends BaseEntity implements Serializable {
public static final String JOB_KEY = "JOB_KEY";
@Id
@Column(name = "job_id")
@NotNull(groups = {Update.class})
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Transient
@ApiModelProperty(value = "用于子任务唯一标识", hidden = true)
private String uuid;
@ApiModelProperty(value = "定时器名称")
private String jobName;
@NotBlank
@ApiModelProperty(value = "Bean名称")
private String beanName;
@NotBlank
@ApiModelProperty(value = "方法名称")
private String methodName;
@ApiModelProperty(value = "参数")
private String params;
@NotBlank
@ApiModelProperty(value = "cron表达式")
private String cronExpression;
@ApiModelProperty(value = "状态,暂时或启动")
private Boolean isPause = false;
@ApiModelProperty(value = "负责人")
private String personInCharge;
@ApiModelProperty(value = "报警邮箱")
private String email;
@ApiModelProperty(value = "子任务")
private String subTask;
@ApiModelProperty(value = "失败后暂停")
private Boolean pauseAfterFailure;
@NotBlank
@ApiModelProperty(value = "备注")
private String description;
}

View File

@@ -0,0 +1,53 @@
package com.platform.modules.quartz.domain;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.hibernate.annotations.CreationTimestamp;
import javax.persistence.*;
import java.io.Serializable;
import java.sql.Timestamp;
/**
* @author AllDataDC
* @date 2023-01-27
*/
@Entity
@Data
@Table(name = "sys_quartz_log")
public class QuartzLog implements Serializable {
@Id
@Column(name = "log_id")
@ApiModelProperty(value = "ID", hidden = true)
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ApiModelProperty(value = "任务名称", hidden = true)
private String jobName;
@ApiModelProperty(value = "bean名称", hidden = true)
private String beanName;
@ApiModelProperty(value = "方法名称", hidden = true)
private String methodName;
@ApiModelProperty(value = "参数", hidden = true)
private String params;
@ApiModelProperty(value = "cron表达式", hidden = true)
private String cronExpression;
@ApiModelProperty(value = "状态", hidden = true)
private Boolean isSuccess;
@ApiModelProperty(value = "异常详情", hidden = true)
private String exceptionDetail;
@ApiModelProperty(value = "执行耗时", hidden = true)
private Long time;
@CreationTimestamp
@ApiModelProperty(value = "创建时间", hidden = true)
private Timestamp createTime;
}

View File

@@ -0,0 +1,20 @@
package com.platform.modules.quartz.repository;
import com.platform.modules.quartz.domain.QuartzJob;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import java.util.List;
/**
* @author AllDataDC
* @date 2023-01-27
*/
public interface QuartzJobRepository extends JpaRepository<QuartzJob,Long>, JpaSpecificationExecutor<QuartzJob> {
/**
* 查询启用的任务
* @return List
*/
List<QuartzJob> findByIsPauseIsFalse();
}

View File

@@ -0,0 +1,14 @@
package com.platform.modules.quartz.repository;
import com.platform.modules.quartz.domain.QuartzLog;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
/**
* @author AllDataDC
* @date 2023-01-27
*/
public interface QuartzLogRepository extends JpaRepository<QuartzLog,Long>, JpaSpecificationExecutor<QuartzLog> {
}

View File

@@ -0,0 +1,125 @@
package com.platform.modules.quartz.rest;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import com.platform.annotation.Log;
import com.platform.exception.BadRequestException;
import com.platform.modules.quartz.domain.QuartzJob;
import com.platform.modules.quartz.service.QuartzJobService;
import com.platform.modules.quartz.service.dto.JobQueryCriteria;
import com.platform.utils.SpringContextHolder;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Set;
/**
* @author AllDataDC
* @date 2023-01-27
*/
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/jobs")
@Api(tags = "系统:定时任务管理")
public class QuartzJobController {
private static final String ENTITY_NAME = "quartzJob";
private final QuartzJobService quartzJobService;
@ApiOperation("查询定时任务")
@GetMapping
@PreAuthorize("@el.check('timing:list')")
public ResponseEntity<Object> queryQuartzJob(JobQueryCriteria criteria, Pageable pageable){
return new ResponseEntity<>(quartzJobService.queryAll(criteria,pageable), HttpStatus.OK);
}
@ApiOperation("导出任务数据")
@GetMapping(value = "/download")
@PreAuthorize("@el.check('timing:list')")
public void exportQuartzJob(HttpServletResponse response, JobQueryCriteria criteria) throws IOException {
quartzJobService.download(quartzJobService.queryAll(criteria), response);
}
@ApiOperation("导出日志数据")
@GetMapping(value = "/logs/download")
@PreAuthorize("@el.check('timing:list')")
public void exportQuartzJobLog(HttpServletResponse response, JobQueryCriteria criteria) throws IOException {
quartzJobService.downloadLog(quartzJobService.queryAllLog(criteria), response);
}
@ApiOperation("查询任务执行日志")
@GetMapping(value = "/logs")
@PreAuthorize("@el.check('timing:list')")
public ResponseEntity<Object> queryQuartzJobLog(JobQueryCriteria criteria, Pageable pageable){
return new ResponseEntity<>(quartzJobService.queryAllLog(criteria,pageable), HttpStatus.OK);
}
@Log("新增定时任务")
@ApiOperation("新增定时任务")
@PostMapping
@PreAuthorize("@el.check('timing:add')")
public ResponseEntity<Object> createQuartzJob(@Validated @RequestBody QuartzJob resources){
if (resources.getId() != null) {
throw new BadRequestException("A new "+ ENTITY_NAME +" cannot already have an ID");
}
// 验证Bean是不是合法的合法的定时任务 Bean 需要用 @Service 定义
checkBean(resources.getBeanName());
quartzJobService.create(resources);
return new ResponseEntity<>(HttpStatus.CREATED);
}
@Log("修改定时任务")
@ApiOperation("修改定时任务")
@PutMapping
@PreAuthorize("@el.check('timing:edit')")
public ResponseEntity<Object> updateQuartzJob(@Validated(QuartzJob.Update.class) @RequestBody QuartzJob resources){
// 验证Bean是不是合法的合法的定时任务 Bean 需要用 @Service 定义
checkBean(resources.getBeanName());
quartzJobService.update(resources);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
@Log("更改定时任务状态")
@ApiOperation("更改定时任务状态")
@PutMapping(value = "/{id}")
@PreAuthorize("@el.check('timing:edit')")
public ResponseEntity<Object> updateQuartzJobStatus(@PathVariable Long id){
quartzJobService.updateIsPause(quartzJobService.findById(id));
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
@Log("执行定时任务")
@ApiOperation("执行定时任务")
@PutMapping(value = "/exec/{id}")
@PreAuthorize("@el.check('timing:edit')")
public ResponseEntity<Object> executionQuartzJob(@PathVariable Long id){
quartzJobService.execution(quartzJobService.findById(id));
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
@Log("删除定时任务")
@ApiOperation("删除定时任务")
@DeleteMapping
@PreAuthorize("@el.check('timing:del')")
public ResponseEntity<Object> deleteQuartzJob(@RequestBody Set<Long> ids){
quartzJobService.delete(ids);
return new ResponseEntity<>(HttpStatus.OK);
}
private void checkBean(String beanName){
// 避免调用攻击者可以从SpringContextHolder获得控制jdbcTemplate类
// 并使用getDeclaredMethod调用jdbcTemplate的queryForMap函数执行任意sql命令。
if(!SpringContextHolder.getAllServiceBeanName().contains(beanName)){
throw new BadRequestException("非法的 Bean请重新输入");
}
}
}

View File

@@ -0,0 +1,108 @@
package com.platform.modules.quartz.service;
import com.platform.modules.quartz.domain.QuartzJob;
import com.platform.modules.quartz.domain.QuartzLog;
import com.platform.modules.quartz.service.dto.JobQueryCriteria;
import org.springframework.data.domain.Pageable;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import java.util.Set;
/**
* @author AllDataDC
* @date 2023-01-27
*/
public interface QuartzJobService {
/**
* 分页查询
* @param criteria 条件
* @param pageable 分页参数
* @return /
*/
Object queryAll(JobQueryCriteria criteria, Pageable pageable);
/**
* 查询全部
* @param criteria 条件
* @return /
*/
List<QuartzJob> queryAll(JobQueryCriteria criteria);
/**
* 分页查询日志
* @param criteria 条件
* @param pageable 分页参数
* @return /
*/
Object queryAllLog(JobQueryCriteria criteria, Pageable pageable);
/**
* 查询全部
* @param criteria 条件
* @return /
*/
List<QuartzLog> queryAllLog(JobQueryCriteria criteria);
/**
* 创建
* @param resources /
*/
void create(QuartzJob resources);
/**
* 编辑
* @param resources /
*/
void update(QuartzJob resources);
/**
* 删除任务
* @param ids /
*/
void delete(Set<Long> ids);
/**
* 根据ID查询
* @param id ID
* @return /
*/
QuartzJob findById(Long id);
/**
* 更改定时任务状态
* @param quartzJob /
*/
void updateIsPause(QuartzJob quartzJob);
/**
* 立即执行定时任务
* @param quartzJob /
*/
void execution(QuartzJob quartzJob);
/**
* 导出定时任务
* @param queryAll 待导出的数据
* @param response /
* @throws IOException /
*/
void download(List<QuartzJob> queryAll, HttpServletResponse response) throws IOException;
/**
* 导出定时任务日志
* @param queryAllLog 待导出的数据
* @param response /
* @throws IOException /
*/
void downloadLog(List<QuartzLog> queryAllLog, HttpServletResponse response) throws IOException;
/**
* 执行子任务
* @param tasks /
* @throws InterruptedException /
*/
void executionSubJob(String[] tasks) throws InterruptedException;
}

View File

@@ -0,0 +1,24 @@
package com.platform.modules.quartz.service.dto;
import lombok.Data;
import com.platform.annotation.Query;
import java.sql.Timestamp;
import java.util.List;
/**
* @author AllDataDC
* @date 2023-01-27 10:33:02
*/
@Data
public class JobQueryCriteria {
@Query(type = Query.Type.INNER_LIKE)
private String jobName;
@Query
private Boolean isSuccess;
@Query(type = Query.Type.BETWEEN)
private List<Timestamp> createTime;
}

View File

@@ -0,0 +1,183 @@
package com.platform.modules.quartz.service.impl;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import lombok.RequiredArgsConstructor;
import com.platform.exception.BadRequestException;
import com.platform.modules.quartz.domain.QuartzJob;
import com.platform.modules.quartz.domain.QuartzLog;
import com.platform.modules.quartz.repository.QuartzJobRepository;
import com.platform.modules.quartz.repository.QuartzLogRepository;
import com.platform.modules.quartz.service.QuartzJobService;
import com.platform.modules.quartz.service.dto.JobQueryCriteria;
import com.platform.modules.quartz.utils.QuartzManage;
import com.platform.utils.*;
import org.quartz.CronExpression;
import org.springframework.data.domain.Pageable;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;
/**
* @author AllDataDC
* @date 2023-01-27
*/
@RequiredArgsConstructor
@Service(value = "quartzJobService")
public class QuartzJobServiceImpl implements QuartzJobService {
private final QuartzJobRepository quartzJobRepository;
private final QuartzLogRepository quartzLogRepository;
private final QuartzManage quartzManage;
private final RedisUtils redisUtils;
@Override
public Object queryAll(JobQueryCriteria criteria, Pageable pageable){
return PageUtil.toPage(quartzJobRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder),pageable));
}
@Override
public Object queryAllLog(JobQueryCriteria criteria, Pageable pageable){
return PageUtil.toPage(quartzLogRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder),pageable));
}
@Override
public List<QuartzJob> queryAll(JobQueryCriteria criteria) {
return quartzJobRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder));
}
@Override
public List<QuartzLog> queryAllLog(JobQueryCriteria criteria) {
return quartzLogRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder));
}
@Override
public QuartzJob findById(Long id) {
QuartzJob quartzJob = quartzJobRepository.findById(id).orElseGet(QuartzJob::new);
ValidationUtil.isNull(quartzJob.getId(),"QuartzJob","id",id);
return quartzJob;
}
@Override
@Transactional(rollbackFor = Exception.class)
public void create(QuartzJob resources) {
if (!CronExpression.isValidExpression(resources.getCronExpression())){
throw new BadRequestException("cron表达式格式错误");
}
resources = quartzJobRepository.save(resources);
quartzManage.addJob(resources);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void update(QuartzJob resources) {
if (!CronExpression.isValidExpression(resources.getCronExpression())){
throw new BadRequestException("cron表达式格式错误");
}
if(StringUtils.isNotBlank(resources.getSubTask())){
List<String> tasks = Arrays.asList(resources.getSubTask().split("[,]"));
if (tasks.contains(resources.getId().toString())) {
throw new BadRequestException("子任务中不能添加当前任务ID");
}
}
resources = quartzJobRepository.save(resources);
quartzManage.updateJobCron(resources);
}
@Override
public void updateIsPause(QuartzJob quartzJob) {
if (quartzJob.getIsPause()) {
quartzManage.resumeJob(quartzJob);
quartzJob.setIsPause(false);
} else {
quartzManage.pauseJob(quartzJob);
quartzJob.setIsPause(true);
}
quartzJobRepository.save(quartzJob);
}
@Override
public void execution(QuartzJob quartzJob) {
quartzManage.runJobNow(quartzJob);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void delete(Set<Long> ids) {
for (Long id : ids) {
QuartzJob quartzJob = findById(id);
quartzManage.deleteJob(quartzJob);
quartzJobRepository.delete(quartzJob);
}
}
@Async
@Override
@Transactional(rollbackFor = Exception.class)
public void executionSubJob(String[] tasks) throws InterruptedException {
for (String id : tasks) {
if (StrUtil.isBlank(id)) {
// 如果是手动清除子任务id会出现id为空字符串的问题
continue;
}
QuartzJob quartzJob = findById(Long.parseLong(id));
// 执行任务
String uuid = IdUtil.simpleUUID();
quartzJob.setUuid(uuid);
// 执行任务
execution(quartzJob);
// 获取执行状态,如果执行失败则停止后面的子任务执行
Boolean result = (Boolean) redisUtils.get(uuid);
while (result == null) {
// 休眠5秒再次获取子任务执行情况
Thread.sleep(5000);
result = (Boolean) redisUtils.get(uuid);
}
if(!result){
redisUtils.del(uuid);
break;
}
}
}
@Override
public void download(List<QuartzJob> quartzJobs, HttpServletResponse response) throws IOException {
List<Map<String, Object>> list = new ArrayList<>();
for (QuartzJob quartzJob : quartzJobs) {
Map<String,Object> map = new LinkedHashMap<>();
map.put("任务名称", quartzJob.getJobName());
map.put("Bean名称", quartzJob.getBeanName());
map.put("执行方法", quartzJob.getMethodName());
map.put("参数", quartzJob.getParams());
map.put("表达式", quartzJob.getCronExpression());
map.put("状态", quartzJob.getIsPause() ? "暂停中" : "运行中");
map.put("描述", quartzJob.getDescription());
map.put("创建日期", quartzJob.getCreateTime());
list.add(map);
}
FileUtil.downloadExcel(list, response);
}
@Override
public void downloadLog(List<QuartzLog> queryAllLog, HttpServletResponse response) throws IOException {
List<Map<String, Object>> list = new ArrayList<>();
for (QuartzLog quartzLog : queryAllLog) {
Map<String,Object> map = new LinkedHashMap<>();
map.put("任务名称", quartzLog.getJobName());
map.put("Bean名称", quartzLog.getBeanName());
map.put("执行方法", quartzLog.getMethodName());
map.put("参数", quartzLog.getParams());
map.put("表达式", quartzLog.getCronExpression());
map.put("异常详情", quartzLog.getExceptionDetail());
map.put("耗时/毫秒", quartzLog.getTime());
map.put("状态", quartzLog.getIsSuccess() ? "成功" : "失败");
map.put("创建日期", quartzLog.getCreateTime());
list.add(map);
}
FileUtil.downloadExcel(list, response);
}
}

View File

@@ -0,0 +1,27 @@
package com.platform.modules.quartz.task;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/**
* 测试用
* @author AllDataDC
* @date 2023-01-27
*/
@Slf4j
@Service
public class TestTask {
public void run(){
log.info("run 执行成功");
}
public void run1(String str){
log.info("run1 执行成功,参数为: {}" + str);
}
public void run2(){
log.info("run2 执行成功");
}
}

View File

@@ -0,0 +1,116 @@
package com.platform.modules.quartz.utils;
import cn.hutool.extra.template.Template;
import cn.hutool.extra.template.TemplateConfig;
import cn.hutool.extra.template.TemplateEngine;
import cn.hutool.extra.template.TemplateUtil;
import com.platform.config.thread.ThreadPoolExecutorUtil;
import com.platform.domain.vo.EmailVo;
import com.platform.modules.quartz.domain.QuartzJob;
import com.platform.modules.quartz.domain.QuartzLog;
import com.platform.modules.quartz.repository.QuartzLogRepository;
import com.platform.modules.quartz.service.QuartzJobService;
import com.platform.service.EmailService;
import com.platform.utils.RedisUtils;
import com.platform.utils.SpringContextHolder;
import com.platform.utils.StringUtils;
import com.platform.utils.ThrowableUtil;
import org.quartz.JobExecutionContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.quartz.QuartzJobBean;
import java.util.*;
import java.util.concurrent.*;
/**
* @author AllDataDC
* @date 2023-01-27
*/
public class ExecutionJob extends QuartzJobBean {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
// 此处仅供参考,可根据任务执行情况自定义线程池参数
private final static ExecutorService executor = ThreadPoolExecutorUtil.getPoll("el-quartz-job");
@Override
public void executeInternal(JobExecutionContext context) {
// 获取任务
QuartzJob quartzJob = (QuartzJob) context.getMergedJobDataMap().get(QuartzJob.JOB_KEY);
// 获取spring bean
QuartzLogRepository quartzLogRepository = SpringContextHolder.getBean(QuartzLogRepository.class);
QuartzJobService quartzJobService = SpringContextHolder.getBean(QuartzJobService.class);
RedisUtils redisUtils = SpringContextHolder.getBean(RedisUtils.class);
String uuid = quartzJob.getUuid();
QuartzLog log = new QuartzLog();
log.setJobName(quartzJob.getJobName());
log.setBeanName(quartzJob.getBeanName());
log.setMethodName(quartzJob.getMethodName());
log.setParams(quartzJob.getParams());
long startTime = System.currentTimeMillis();
log.setCronExpression(quartzJob.getCronExpression());
try {
// 执行任务
QuartzRunnable task = new QuartzRunnable(quartzJob.getBeanName(), quartzJob.getMethodName(), quartzJob.getParams());
Future<?> future = executor.submit(task);
future.get();
long times = System.currentTimeMillis() - startTime;
log.setTime(times);
if(StringUtils.isNotBlank(uuid)) {
redisUtils.set(uuid, true);
}
// 任务状态
log.setIsSuccess(true);
logger.info("任务执行成功,任务名称:" + quartzJob.getJobName() + ", 执行时间:" + times + "毫秒");
// 判断是否存在子任务
if(StringUtils.isNotBlank(quartzJob.getSubTask())){
String[] tasks = quartzJob.getSubTask().split("[,]");
// 执行子任务
quartzJobService.executionSubJob(tasks);
}
} catch (Exception e) {
if(StringUtils.isNotBlank(uuid)) {
redisUtils.set(uuid, false);
}
logger.error("任务执行失败,任务名称:" + quartzJob.getJobName());
long times = System.currentTimeMillis() - startTime;
log.setTime(times);
// 任务状态 0成功 1失败
log.setIsSuccess(false);
log.setExceptionDetail(ThrowableUtil.getStackTrace(e));
// 任务如果失败了则暂停
if(quartzJob.getPauseAfterFailure() != null && quartzJob.getPauseAfterFailure()){
quartzJob.setIsPause(false);
//更新状态
quartzJobService.updateIsPause(quartzJob);
}
if(quartzJob.getEmail() != null){
EmailService emailService = SpringContextHolder.getBean(EmailService.class);
// 邮箱报警
if(StringUtils.isNoneBlank(quartzJob.getEmail())){
EmailVo emailVo = taskAlarm(quartzJob, ThrowableUtil.getStackTrace(e));
emailService.send(emailVo, emailService.find());
}
}
} finally {
quartzLogRepository.save(log);
}
}
private EmailVo taskAlarm(QuartzJob quartzJob, String msg) {
EmailVo emailVo = new EmailVo();
emailVo.setSubject("定时任务【"+ quartzJob.getJobName() +"】执行失败,请尽快处理!");
Map<String, Object> data = new HashMap<>(16);
data.put("task", quartzJob);
data.put("msg", msg);
TemplateEngine engine = TemplateUtil.createEngine(new TemplateConfig("template", TemplateConfig.ResourceMode.CLASSPATH));
Template template = engine.getTemplate("email/taskAlarm.ftl");
emailVo.setContent(template.render(data));
List<String> emails = Arrays.asList(quartzJob.getEmail().split("[,]"));
emailVo.setTos(emails);
return emailVo;
}
}

View File

@@ -0,0 +1,160 @@
package com.platform.modules.quartz.utils;
import lombok.extern.slf4j.Slf4j;
import com.platform.exception.BadRequestException;
import com.platform.modules.quartz.domain.QuartzJob;
import org.quartz.*;
import org.quartz.impl.triggers.CronTriggerImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Date;
import static org.quartz.TriggerBuilder.newTrigger;
/**
* @author AllDataDC
* @date 2023-01-27
*/
@Slf4j
@Component
public class QuartzManage {
private static final String JOB_NAME = "TASK_";
@Resource
private Scheduler scheduler;
public void addJob(QuartzJob quartzJob){
try {
// 构建job信息
JobDetail jobDetail = JobBuilder.newJob(ExecutionJob.class).
withIdentity(JOB_NAME + quartzJob.getId()).build();
//通过触发器名和cron 表达式创建 Trigger
Trigger cronTrigger = newTrigger()
.withIdentity(JOB_NAME + quartzJob.getId())
.startNow()
.withSchedule(CronScheduleBuilder.cronSchedule(quartzJob.getCronExpression()))
.build();
cronTrigger.getJobDataMap().put(QuartzJob.JOB_KEY, quartzJob);
//重置启动时间
((CronTriggerImpl)cronTrigger).setStartTime(new Date());
//执行定时任务
scheduler.scheduleJob(jobDetail,cronTrigger);
// 暂停任务
if (quartzJob.getIsPause()) {
pauseJob(quartzJob);
}
} catch (Exception e){
log.error("创建定时任务失败", e);
throw new BadRequestException("创建定时任务失败");
}
}
/**
* 更新job cron表达式
* @param quartzJob /
*/
public void updateJobCron(QuartzJob quartzJob){
try {
TriggerKey triggerKey = TriggerKey.triggerKey(JOB_NAME + quartzJob.getId());
CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
// 如果不存在则创建一个定时任务
if(trigger == null){
addJob(quartzJob);
trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
}
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(quartzJob.getCronExpression());
trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
//重置启动时间
((CronTriggerImpl)trigger).setStartTime(new Date());
trigger.getJobDataMap().put(QuartzJob.JOB_KEY,quartzJob);
scheduler.rescheduleJob(triggerKey, trigger);
// 暂停任务
if (quartzJob.getIsPause()) {
pauseJob(quartzJob);
}
} catch (Exception e){
log.error("更新定时任务失败", e);
throw new BadRequestException("更新定时任务失败");
}
}
/**
* 删除一个job
* @param quartzJob /
*/
public void deleteJob(QuartzJob quartzJob){
try {
JobKey jobKey = JobKey.jobKey(JOB_NAME + quartzJob.getId());
scheduler.pauseJob(jobKey);
scheduler.deleteJob(jobKey);
} catch (Exception e){
log.error("删除定时任务失败", e);
throw new BadRequestException("删除定时任务失败");
}
}
/**
* 恢复一个job
* @param quartzJob /
*/
public void resumeJob(QuartzJob quartzJob){
try {
TriggerKey triggerKey = TriggerKey.triggerKey(JOB_NAME + quartzJob.getId());
CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
// 如果不存在则创建一个定时任务
if(trigger == null) {
addJob(quartzJob);
}
JobKey jobKey = JobKey.jobKey(JOB_NAME + quartzJob.getId());
scheduler.resumeJob(jobKey);
} catch (Exception e){
log.error("恢复定时任务失败", e);
throw new BadRequestException("恢复定时任务失败");
}
}
/**
* 立即执行job
* @param quartzJob /
*/
public void runJobNow(QuartzJob quartzJob){
try {
TriggerKey triggerKey = TriggerKey.triggerKey(JOB_NAME + quartzJob.getId());
CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
// 如果不存在则创建一个定时任务
if(trigger == null) {
addJob(quartzJob);
}
JobDataMap dataMap = new JobDataMap();
dataMap.put(QuartzJob.JOB_KEY, quartzJob);
JobKey jobKey = JobKey.jobKey(JOB_NAME + quartzJob.getId());
scheduler.triggerJob(jobKey,dataMap);
} catch (Exception e){
log.error("定时任务执行失败", e);
throw new BadRequestException("定时任务执行失败");
}
}
/**
* 暂停一个job
* @param quartzJob /
*/
public void pauseJob(QuartzJob quartzJob){
try {
JobKey jobKey = JobKey.jobKey(JOB_NAME + quartzJob.getId());
scheduler.pauseJob(jobKey);
} catch (Exception e){
log.error("定时任务暂停失败", e);
throw new BadRequestException("定时任务暂停失败");
}
}
}

View File

@@ -0,0 +1,44 @@
package com.platform.modules.quartz.utils;
import lombok.extern.slf4j.Slf4j;
import com.platform.utils.SpringContextHolder;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.ReflectionUtils;
import java.lang.reflect.Method;
import java.util.concurrent.Callable;
/**
* 执行定时任务
* @author AllDataDC
*/
@Slf4j
public class QuartzRunnable implements Callable<Object> {
private final Object target;
private final Method method;
private final String params;
QuartzRunnable(String beanName, String methodName, String params)
throws NoSuchMethodException, SecurityException {
this.target = SpringContextHolder.getBean(beanName);
this.params = params;
if (StringUtils.isNotBlank(params)) {
this.method = target.getClass().getDeclaredMethod(methodName, String.class);
} else {
this.method = target.getClass().getDeclaredMethod(methodName);
}
}
@Override
@SuppressWarnings("all")
public Object call() throws Exception {
ReflectionUtils.makeAccessible(method);
if (StringUtils.isNotBlank(params)) {
method.invoke(target, params);
} else {
method.invoke(target);
}
return null;
}
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2019-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.platform.modules.security.config;
import com.platform.modules.security.config.bean.LoginProperties;
import com.platform.modules.security.config.bean.SecurityProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @apiNote 配置文件转换Pojo类的 统一配置 类
* @author AllDataDC
* @date 2023-01-27 19:04
*/
@Configuration
public class ConfigBeanConfiguration {
@Bean
@ConfigurationProperties(prefix = "login")
public LoginProperties loginProperties() {
return new LoginProperties();
}
@Bean
@ConfigurationProperties(prefix = "jwt")
public SecurityProperties securityProperties() {
return new SecurityProperties();
}
}

View File

@@ -0,0 +1,178 @@
package com.platform.modules.security.config;
import com.platform.modules.security.security.JwtAccessDeniedHandler;
import com.platform.modules.security.security.JwtAuthenticationEntryPoint;
import com.platform.modules.security.security.TokenConfigurer;
import com.platform.modules.security.security.TokenProvider;
import lombok.RequiredArgsConstructor;
import com.platform.annotation.rest.AnonymousAccess;
import com.platform.modules.security.config.bean.SecurityProperties;
import com.platform.modules.security.service.OnlineUserService;
import com.platform.modules.security.service.UserCacheManager;
import com.platform.utils.enums.RequestMethodEnum;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.core.GrantedAuthorityDefaults;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import java.util.*;
/**
* @author AllDataDC
*/
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
private final TokenProvider tokenProvider;
private final CorsFilter corsFilter;
private final JwtAuthenticationEntryPoint authenticationErrorHandler;
private final JwtAccessDeniedHandler jwtAccessDeniedHandler;
private final ApplicationContext applicationContext;
private final SecurityProperties properties;
private final OnlineUserService onlineUserService;
private final UserCacheManager userCacheManager;
@Bean
GrantedAuthorityDefaults grantedAuthorityDefaults() {
// 去除 ROLE_ 前缀
return new GrantedAuthorityDefaults("");
}
@Bean
public PasswordEncoder passwordEncoder() {
// 密码加密方式
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
// 搜寻匿名标记 url @AnonymousAccess
RequestMappingHandlerMapping requestMappingHandlerMapping = (RequestMappingHandlerMapping) applicationContext.getBean("requestMappingHandlerMapping");
Map<RequestMappingInfo, HandlerMethod> handlerMethodMap = requestMappingHandlerMapping.getHandlerMethods();
// 获取匿名标记
Map<String, Set<String>> anonymousUrls = getAnonymousUrl(handlerMethodMap);
httpSecurity
// 禁用 CSRF
.csrf().disable()
.addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class)
// 授权异常
.exceptionHandling()
.authenticationEntryPoint(authenticationErrorHandler)
.accessDeniedHandler(jwtAccessDeniedHandler)
// 防止iframe 造成跨域
.and()
.headers()
.frameOptions()
.disable()
// 不创建会话
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// 静态资源等等
.antMatchers(
HttpMethod.GET,
"/*.html",
"/**/*.html",
"/**/*.css",
"/**/*.js",
"/webSocket/**"
).permitAll()
// swagger 文档
.antMatchers("/swagger-ui.html").permitAll()
.antMatchers("/swagger-resources/**").permitAll()
.antMatchers("/webjars/**").permitAll()
.antMatchers("/*/api-docs").permitAll()
// 文件
.antMatchers("/avatar/**").permitAll()
.antMatchers("/file/**").permitAll()
// 阿里巴巴 druid
.antMatchers("/druid/**").permitAll()
.antMatchers("/auth/**").permitAll()
// 放行OPTIONS请求
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
// 自定义匿名访问所有url放行允许匿名和带Token访问细腻化到每个 Request 类型
// GET
.antMatchers(HttpMethod.GET, anonymousUrls.get(RequestMethodEnum.GET.getType()).toArray(new String[0])).permitAll()
// POST
.antMatchers(HttpMethod.POST, anonymousUrls.get(RequestMethodEnum.POST.getType()).toArray(new String[0])).permitAll()
// PUT
.antMatchers(HttpMethod.PUT, anonymousUrls.get(RequestMethodEnum.PUT.getType()).toArray(new String[0])).permitAll()
// PATCH
.antMatchers(HttpMethod.PATCH, anonymousUrls.get(RequestMethodEnum.PATCH.getType()).toArray(new String[0])).permitAll()
// DELETE
.antMatchers(HttpMethod.DELETE, anonymousUrls.get(RequestMethodEnum.DELETE.getType()).toArray(new String[0])).permitAll()
// 所有类型的接口都放行
.antMatchers(anonymousUrls.get(RequestMethodEnum.ALL.getType()).toArray(new String[0])).permitAll()
// 所有请求都需要认证
.anyRequest().authenticated()
.and().apply(securityConfigurerAdapter());
}
private TokenConfigurer securityConfigurerAdapter() {
return new TokenConfigurer(tokenProvider, properties, onlineUserService, userCacheManager);
}
private Map<String, Set<String>> getAnonymousUrl(Map<RequestMappingInfo, HandlerMethod> handlerMethodMap) {
Map<String, Set<String>> anonymousUrls = new HashMap<>(8);
Set<String> get = new HashSet<>();
Set<String> post = new HashSet<>();
Set<String> put = new HashSet<>();
Set<String> patch = new HashSet<>();
Set<String> delete = new HashSet<>();
Set<String> all = new HashSet<>();
for (Map.Entry<RequestMappingInfo, HandlerMethod> infoEntry : handlerMethodMap.entrySet()) {
HandlerMethod handlerMethod = infoEntry.getValue();
AnonymousAccess anonymousAccess = handlerMethod.getMethodAnnotation(AnonymousAccess.class);
if (null != anonymousAccess) {
List<RequestMethod> requestMethods = new ArrayList<>(infoEntry.getKey().getMethodsCondition().getMethods());
RequestMethodEnum request = RequestMethodEnum.find(requestMethods.size() == 0 ? RequestMethodEnum.ALL.getType() : requestMethods.get(0).name());
switch (Objects.requireNonNull(request)) {
case GET:
get.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
break;
case POST:
post.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
break;
case PUT:
put.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
break;
case PATCH:
patch.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
break;
case DELETE:
delete.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
break;
default:
all.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
break;
}
}
}
anonymousUrls.put(RequestMethodEnum.GET.getType(), get);
anonymousUrls.put(RequestMethodEnum.POST.getType(), post);
anonymousUrls.put(RequestMethodEnum.PUT.getType(), put);
anonymousUrls.put(RequestMethodEnum.PATCH.getType(), patch);
anonymousUrls.put(RequestMethodEnum.DELETE.getType(), delete);
anonymousUrls.put(RequestMethodEnum.ALL.getType(), all);
return anonymousUrls;
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright 2019-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.platform.modules.security.config.bean;
import lombok.Data;
/**
* 登录验证码配置信息
*
* @author liaojinlong
* @date 2023-01-27 18:53
*/
@Data
public class LoginCode {
/**
* 验证码配置
*/
private LoginCodeEnum codeType;
/**
* 验证码有效期 分钟
*/
private Long expiration = 2L;
/**
* 验证码内容长度
*/
private int length = 2;
/**
* 验证码宽度
*/
private int width = 111;
/**
* 验证码高度
*/
private int height = 36;
/**
* 验证码字体
*/
private String fontName;
/**
* 字体大小
*/
private int fontSize = 25;
public LoginCodeEnum getCodeType() {
return codeType;
}
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2019-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.platform.modules.security.config.bean;
/**
* 验证码配置枚举
*
* @author AllDataDC
* @date 2023-01-27 17:40
*/
public enum LoginCodeEnum {
/**
* 算数
*/
ARITHMETIC,
/**
* 中文
*/
CHINESE,
/**
* 中文闪图
*/
CHINESE_GIF,
/**
* 闪图
*/
GIF,
SPEC
}

View File

@@ -0,0 +1,125 @@
/*
* Copyright 2019-2020 the original author or authors.
*
* Licensed under the Apache License, Version loginCode.length.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-loginCode.length.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.platform.modules.security.config.bean;
import com.wf.captcha.*;
import com.wf.captcha.base.Captcha;
import lombok.Data;
import com.platform.exception.BadConfigurationException;
import com.platform.utils.StringUtils;
import java.awt.*;
import java.util.Objects;
/**
* 配置文件读取
*
* @author liaojinlong
* @date loginCode.length0loginCode.length0/6/10 17:loginCode.length6
*/
@Data
public class LoginProperties {
/**
* 账号单用户 登录
*/
private boolean singleLogin = false;
private LoginCode loginCode;
public static final String cacheKey = "USER-LOGIN-DATA";
public boolean isSingleLogin() {
return singleLogin;
}
/**
* 获取验证码生产类
*
* @return /
*/
public Captcha getCaptcha() {
if (Objects.isNull(loginCode)) {
loginCode = new LoginCode();
if (Objects.isNull(loginCode.getCodeType())) {
loginCode.setCodeType(LoginCodeEnum.ARITHMETIC);
}
}
return switchCaptcha(loginCode);
}
/**
* 依据配置信息生产验证码
*
* @param loginCode 验证码配置信息
* @return /
*/
private Captcha switchCaptcha(LoginCode loginCode) {
Captcha captcha;
switch (loginCode.getCodeType()) {
case ARITHMETIC:
// 算术类型 https://gitee.com/whvse/EasyCaptcha
captcha = new FixedArithmeticCaptcha(loginCode.getWidth(), loginCode.getHeight());
// 几位数运算,默认是两位
captcha.setLen(loginCode.getLength());
break;
case CHINESE:
captcha = new ChineseCaptcha(loginCode.getWidth(), loginCode.getHeight());
captcha.setLen(loginCode.getLength());
break;
case CHINESE_GIF:
captcha = new ChineseGifCaptcha(loginCode.getWidth(), loginCode.getHeight());
captcha.setLen(loginCode.getLength());
break;
case GIF:
captcha = new GifCaptcha(loginCode.getWidth(), loginCode.getHeight());
captcha.setLen(loginCode.getLength());
break;
case SPEC:
captcha = new SpecCaptcha(loginCode.getWidth(), loginCode.getHeight());
captcha.setLen(loginCode.getLength());
break;
default:
throw new BadConfigurationException("验证码配置信息错误!正确配置查看 LoginCodeEnum ");
}
if(StringUtils.isNotBlank(loginCode.getFontName())){
captcha.setFont(new Font(loginCode.getFontName(), Font.PLAIN, loginCode.getFontSize()));
}
return captcha;
}
static class FixedArithmeticCaptcha extends ArithmeticCaptcha {
public FixedArithmeticCaptcha(int width, int height) {
super(width, height);
}
@Override
protected char[] alphas() {
// 生成随机数字和运算符
int n1 = num(1, 10), n2 = num(1, 10);
int opt = num(3);
// 计算结果
int res = new int[]{n1 + n2, n1 - n2, n1 * n2}[opt];
// 转换为字符运算符
char optChar = "+-x".charAt(opt);
this.setArithmeticString(String.format("%s%c%s=?", n1, optChar, n2));
this.chars = String.valueOf(res);
return chars.toCharArray();
}
}
}

View File

@@ -0,0 +1,58 @@
package com.platform.modules.security.config.bean;
import lombok.Data;
/**
* Jwt参数配置
*
* @author AllDataDC
* @date 2023-01-27
*/
@Data
public class SecurityProperties {
/**
* Request Headers Authorization
*/
private String header;
/**
* 令牌前缀,最后留个空格 Bearer
*/
private String tokenStartWith;
/**
* 必须使用最少88位的Base64对该令牌进行编码
*/
private String base64Secret;
/**
* 令牌过期时间 此处单位/毫秒
*/
private Long tokenValidityInSeconds;
/**
* 在线用户 key根据 key 查询 redis 中在线用户的数据
*/
private String onlineKey;
/**
* 验证码 key
*/
private String codeKey;
/**
* token 续期检查
*/
private Long detect;
/**
* 续期时间
*/
private Long renew;
public String getTokenStartWith() {
return tokenStartWith + " ";
}
}

View File

@@ -0,0 +1,133 @@
package com.platform.modules.security.rest;
import cn.hutool.core.util.IdUtil;
import com.wf.captcha.base.Captcha;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import com.platform.annotation.Log;
import com.platform.annotation.rest.AnonymousDeleteMapping;
import com.platform.annotation.rest.AnonymousGetMapping;
import com.platform.annotation.rest.AnonymousPostMapping;
import com.platform.config.RsaProperties;
import com.platform.exception.BadRequestException;
import com.platform.modules.security.config.bean.LoginCodeEnum;
import com.platform.modules.security.config.bean.LoginProperties;
import com.platform.modules.security.config.bean.SecurityProperties;
import com.platform.modules.security.security.TokenProvider;
import com.platform.modules.security.service.dto.AuthUserDto;
import com.platform.modules.security.service.dto.JwtUserDto;
import com.platform.modules.security.service.OnlineUserService;
import com.platform.utils.RsaUtils;
import com.platform.utils.RedisUtils;
import com.platform.utils.SecurityUtils;
import com.platform.utils.StringUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* @author AllDataDC
* @date 2023-01-27
* 授权、根据token获取用户详细信息
*/
@Slf4j
@RestController
@RequestMapping("/auth")
@RequiredArgsConstructor
@Api(tags = "系统:系统授权接口")
public class AuthorizationController {
private final SecurityProperties properties;
private final RedisUtils redisUtils;
private final OnlineUserService onlineUserService;
private final TokenProvider tokenProvider;
private final AuthenticationManagerBuilder authenticationManagerBuilder;
@Resource
private LoginProperties loginProperties;
@Log("用户登录")
@ApiOperation("登录授权")
@AnonymousPostMapping(value = "/login")
public ResponseEntity<Object> login(@Validated @RequestBody AuthUserDto authUser, HttpServletRequest request) throws Exception {
// 密码解密
String password = RsaUtils.decryptByPrivateKey(RsaProperties.privateKey, authUser.getPassword());
// 查询验证码
String code = (String) redisUtils.get(authUser.getUuid());
// 清除验证码
redisUtils.del(authUser.getUuid());
if (StringUtils.isBlank(code)) {
throw new BadRequestException("验证码不存在或已过期");
}
if (StringUtils.isBlank(authUser.getCode()) || !authUser.getCode().equalsIgnoreCase(code)) {
throw new BadRequestException("验证码错误");
}
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(authUser.getUsername(), password);
Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
// 生成令牌与第三方系统获取令牌方式
// UserDetails userDetails = userDetailsService.loadUserByUsername(userInfo.getUsername());
// Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
// SecurityContextHolder.getContext().setAuthentication(authentication);
String token = tokenProvider.createToken(authentication);
final JwtUserDto jwtUserDto = (JwtUserDto) authentication.getPrincipal();
// 保存在线信息
onlineUserService.save(jwtUserDto, token, request);
// 返回 token 与 用户信息
Map<String, Object> authInfo = new HashMap<String, Object>(2) {{
put("token", properties.getTokenStartWith() + token);
put("user", jwtUserDto);
}};
if (loginProperties.isSingleLogin()) {
//踢掉之前已经登录的token
onlineUserService.checkLoginOnUser(authUser.getUsername(), token);
}
return ResponseEntity.ok(authInfo);
}
@ApiOperation("获取用户信息")
@GetMapping(value = "/info")
public ResponseEntity<Object> getUserInfo() {
return ResponseEntity.ok(SecurityUtils.getCurrentUser());
}
@ApiOperation("获取验证码")
@AnonymousGetMapping(value = "/code")
public ResponseEntity<Object> getCode() {
// 获取运算的结果
Captcha captcha = loginProperties.getCaptcha();
String uuid = properties.getCodeKey() + IdUtil.simpleUUID();
//当验证码类型为 arithmetic时且长度 >= 2 时captcha.text()的结果有几率为浮点型
String captchaValue = captcha.text();
if (captcha.getCharType() - 1 == LoginCodeEnum.ARITHMETIC.ordinal() && captchaValue.contains(".")) {
captchaValue = captchaValue.split("\\.")[0];
}
// 保存
redisUtils.set(uuid, captchaValue, loginProperties.getLoginCode().getExpiration(), TimeUnit.MINUTES);
// 验证码信息
Map<String, Object> imgResult = new HashMap<String, Object>(2) {{
put("img", captcha.toBase64());
put("uuid", uuid);
}};
return ResponseEntity.ok(imgResult);
}
@ApiOperation("退出登录")
@AnonymousDeleteMapping(value = "/logout")
public ResponseEntity<Object> logout(HttpServletRequest request) {
onlineUserService.logout(tokenProvider.getToken(request));
return new ResponseEntity<>(HttpStatus.OK);
}
}

View File

@@ -0,0 +1,54 @@
package com.platform.modules.security.rest;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import com.platform.modules.security.service.OnlineUserService;
import com.platform.utils.EncryptUtils;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Set;
/**
* @author AllDataDC
*/
@RestController
@RequiredArgsConstructor
@RequestMapping("/auth/online")
@Api(tags = "系统:在线用户管理")
public class OnlineController {
private final OnlineUserService onlineUserService;
@ApiOperation("查询在线用户")
@GetMapping
@PreAuthorize("@el.check()")
public ResponseEntity<Object> queryOnlineUser(String filter, Pageable pageable){
return new ResponseEntity<>(onlineUserService.getAll(filter, pageable),HttpStatus.OK);
}
@ApiOperation("导出数据")
@GetMapping(value = "/download")
@PreAuthorize("@el.check()")
public void exportOnlineUser(HttpServletResponse response, String filter) throws IOException {
onlineUserService.download(onlineUserService.getAll(filter), response);
}
@ApiOperation("踢出用户")
@DeleteMapping
@PreAuthorize("@el.check()")
public ResponseEntity<Object> deleteOnlineUser(@RequestBody Set<String> keys) throws Exception {
for (String key : keys) {
// 解密Key
key = EncryptUtils.desDecrypt(key);
onlineUserService.kickOut(key);
}
return new ResponseEntity<>(HttpStatus.OK);
}
}

View File

@@ -0,0 +1,23 @@
package com.platform.modules.security.security;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author AllDataDC
*/
@Component
public class JwtAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
//当用户在没有授权的情况下访问受保护的REST资源时将调用此方法发送403 Forbidden响应
response.sendError(HttpServletResponse.SC_FORBIDDEN, accessDeniedException.getMessage());
}
}

View File

@@ -0,0 +1,25 @@
package com.platform.modules.security.security;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author AllDataDC
*/
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException) throws IOException {
// 当用户尝试访问安全的REST资源而不提供任何凭据时将调用此方法发送401 响应
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException==null?"Unauthorized":authException.getMessage());
}
}

View File

@@ -0,0 +1,29 @@
package com.platform.modules.security.security;
import lombok.RequiredArgsConstructor;
import com.platform.modules.security.config.bean.SecurityProperties;
import com.platform.modules.security.service.OnlineUserService;
import com.platform.modules.security.service.UserCacheManager;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
/**
* @author AllDataDC
*/
@RequiredArgsConstructor
public class TokenConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
private final TokenProvider tokenProvider;
private final SecurityProperties properties;
private final OnlineUserService onlineUserService;
private final UserCacheManager userCacheManager;
@Override
public void configure(HttpSecurity http) {
TokenFilter customFilter = new TokenFilter(tokenProvider, properties, onlineUserService, userCacheManager);
http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
}
}

View File

@@ -0,0 +1,94 @@
package com.platform.modules.security.security;
import cn.hutool.core.util.StrUtil;
import io.jsonwebtoken.ExpiredJwtException;
import com.platform.modules.security.config.bean.SecurityProperties;
import com.platform.modules.security.service.UserCacheManager;
import com.platform.modules.security.service.dto.OnlineUserDto;
import com.platform.modules.security.service.OnlineUserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.GenericFilterBean;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Objects;
/**
* @author AllDataDC
*/
public class TokenFilter extends GenericFilterBean {
private static final Logger log = LoggerFactory.getLogger(TokenFilter.class);
private final TokenProvider tokenProvider;
private final SecurityProperties properties;
private final OnlineUserService onlineUserService;
private final UserCacheManager userCacheManager;
/**
* @param tokenProvider Token
* @param properties JWT
* @param onlineUserService 用户在线
* @param userCacheManager 用户缓存工具
*/
public TokenFilter(TokenProvider tokenProvider, SecurityProperties properties, OnlineUserService onlineUserService, UserCacheManager userCacheManager) {
this.properties = properties;
this.onlineUserService = onlineUserService;
this.tokenProvider = tokenProvider;
this.userCacheManager = userCacheManager;
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
String token = resolveToken(httpServletRequest);
// 对于 Token 为空的不需要去查 Redis
if (StrUtil.isNotBlank(token)) {
OnlineUserDto onlineUserDto = null;
boolean cleanUserCache = false;
try {
onlineUserDto = onlineUserService.getOne(properties.getOnlineKey() + token);
} catch (ExpiredJwtException e) {
log.error(e.getMessage());
cleanUserCache = true;
} finally {
if (cleanUserCache || Objects.isNull(onlineUserDto)) {
userCacheManager.cleanUserCache(String.valueOf(tokenProvider.getClaims(token).get(TokenProvider.AUTHORITIES_KEY)));
}
}
if (onlineUserDto != null && StringUtils.hasText(token)) {
Authentication authentication = tokenProvider.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
// Token 续期
tokenProvider.checkRenewal(token);
}
}
filterChain.doFilter(servletRequest, servletResponse);
}
/**
* 初步检测Token
*
* @param request /
* @return /
*/
private String resolveToken(HttpServletRequest request) {
String bearerToken = request.getHeader(properties.getHeader());
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(properties.getTokenStartWith())) {
// 去掉令牌前缀
return bearerToken.replace(properties.getTokenStartWith(), "");
} else {
log.debug("非法Token{}", bearerToken);
}
return null;
}
}

View File

@@ -0,0 +1,109 @@
package com.platform.modules.security.security;
import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import io.jsonwebtoken.*;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import lombok.extern.slf4j.Slf4j;
import com.platform.modules.security.config.bean.SecurityProperties;
import com.platform.utils.RedisUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.security.Key;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
* @author AllDataDC
*/
@Slf4j
@Component
public class TokenProvider implements InitializingBean {
private final SecurityProperties properties;
private final RedisUtils redisUtils;
public static final String AUTHORITIES_KEY = "user";
private JwtParser jwtParser;
private JwtBuilder jwtBuilder;
public TokenProvider(SecurityProperties properties, RedisUtils redisUtils) {
this.properties = properties;
this.redisUtils = redisUtils;
}
@Override
public void afterPropertiesSet() {
byte[] keyBytes = Decoders.BASE64.decode(properties.getBase64Secret());
Key key = Keys.hmacShaKeyFor(keyBytes);
jwtParser = Jwts.parserBuilder()
.setSigningKey(properties.getBase64Secret())
.build();
jwtBuilder = Jwts.builder()
.signWith( SignatureAlgorithm.HS512,properties.getBase64Secret());
}
/**
* 创建Token 设置永不过期,
* Token 的时间有效性转到Redis 维护
*
* @param authentication /
* @return /
*/
public String createToken(Authentication authentication) {
return jwtBuilder
// 加入ID确保生成的 Token 都不一致
.setId(IdUtil.simpleUUID())
.claim(AUTHORITIES_KEY, authentication.getName())
.setSubject(authentication.getName())
.compact();
}
/**
* 依据Token 获取鉴权信息
*
* @param token /
* @return /
*/
Authentication getAuthentication(String token) {
Claims claims = getClaims(token);
User principal = new User(claims.getSubject(), "******", new ArrayList<>());
return new UsernamePasswordAuthenticationToken(principal, token, new ArrayList<>());
}
public Claims getClaims(String token) {
return jwtParser
.parseClaimsJws(token)
.getBody();
}
/**
* @param token 需要检查的token
*/
public void checkRenewal(String token) {
// 判断是否续期token,计算token的过期时间
long time = redisUtils.getExpire(properties.getOnlineKey() + token) * 1000;
Date expireDate = DateUtil.offset(new Date(), DateField.MILLISECOND, (int) time);
// 判断当前时间与过期时间的时间差
long differ = expireDate.getTime() - System.currentTimeMillis();
// 如果在续期检查的范围内,则续期
if (differ <= properties.getDetect()) {
long renew = time + properties.getRenew();
redisUtils.expire(properties.getOnlineKey() + token, renew, TimeUnit.MILLISECONDS);
}
}
public String getToken(HttpServletRequest request) {
final String requestHeader = request.getHeader(properties.getHeader());
if (requestHeader != null && requestHeader.startsWith(properties.getTokenStartWith())) {
return requestHeader.substring(7);
}
return null;
}
}

View File

@@ -0,0 +1,177 @@
package com.platform.modules.security.service;
import lombok.extern.slf4j.Slf4j;
import com.platform.modules.security.config.bean.SecurityProperties;
import com.platform.modules.security.service.dto.JwtUserDto;
import com.platform.modules.security.service.dto.OnlineUserDto;
import com.platform.utils.*;
import org.springframework.data.domain.Pageable;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;
/**
* @author AllDataDC
* @date 2023-01-27 日21:56:27
*/
@Service
@Slf4j
public class OnlineUserService {
private final SecurityProperties properties;
private final RedisUtils redisUtils;
public OnlineUserService(SecurityProperties properties, RedisUtils redisUtils) {
this.properties = properties;
this.redisUtils = redisUtils;
}
/**
* 保存在线用户信息
* @param jwtUserDto /
* @param token /
* @param request /
*/
public void save(JwtUserDto jwtUserDto, String token, HttpServletRequest request){
String dept = jwtUserDto.getUser().getDept().getName();
String ip = StringUtils.getIp(request);
String browser = StringUtils.getBrowser(request);
String address = StringUtils.getCityInfo(ip);
OnlineUserDto onlineUserDto = null;
try {
onlineUserDto = new OnlineUserDto(jwtUserDto.getUsername(), jwtUserDto.getUser().getNickName(), dept, browser , ip, address, EncryptUtils.desEncrypt(token), new Date());
} catch (Exception e) {
log.error(e.getMessage(),e);
}
redisUtils.set(properties.getOnlineKey() + token, onlineUserDto, properties.getTokenValidityInSeconds()/1000);
}
/**
* 查询全部数据
* @param filter /
* @param pageable /
* @return /
*/
public Map<String,Object> getAll(String filter, Pageable pageable){
List<OnlineUserDto> onlineUserDtos = getAll(filter);
return PageUtil.toPage(
PageUtil.toPage(pageable.getPageNumber(),pageable.getPageSize(), onlineUserDtos),
onlineUserDtos.size()
);
}
/**
* 查询全部数据,不分页
* @param filter /
* @return /
*/
public List<OnlineUserDto> getAll(String filter){
List<String> keys = redisUtils.scan(properties.getOnlineKey() + "*");
Collections.reverse(keys);
List<OnlineUserDto> onlineUserDtos = new ArrayList<>();
for (String key : keys) {
OnlineUserDto onlineUserDto = (OnlineUserDto) redisUtils.get(key);
if(StringUtils.isNotBlank(filter)){
if(onlineUserDto.toString().contains(filter)){
onlineUserDtos.add(onlineUserDto);
}
} else {
onlineUserDtos.add(onlineUserDto);
}
}
onlineUserDtos.sort((o1, o2) -> o2.getLoginTime().compareTo(o1.getLoginTime()));
return onlineUserDtos;
}
/**
* 踢出用户
* @param key /
*/
public void kickOut(String key){
key = properties.getOnlineKey() + key;
redisUtils.del(key);
}
/**
* 退出登录
* @param token /
*/
public void logout(String token) {
String key = properties.getOnlineKey() + token;
redisUtils.del(key);
}
/**
* 导出
* @param all /
* @param response /
* @throws IOException /
*/
public void download(List<OnlineUserDto> all, HttpServletResponse response) throws IOException {
List<Map<String, Object>> list = new ArrayList<>();
for (OnlineUserDto user : all) {
Map<String,Object> map = new LinkedHashMap<>();
map.put("用户名", user.getUserName());
map.put("部门", user.getDept());
map.put("登录IP", user.getIp());
map.put("登录地点", user.getAddress());
map.put("浏览器", user.getBrowser());
map.put("登录日期", user.getLoginTime());
list.add(map);
}
FileUtil.downloadExcel(list, response);
}
/**
* 查询用户
* @param key /
* @return /
*/
public OnlineUserDto getOne(String key) {
return (OnlineUserDto)redisUtils.get(key);
}
/**
* 检测用户是否在之前已经登录,已经登录踢下线
* @param userName 用户名
*/
public void checkLoginOnUser(String userName, String igoreToken){
List<OnlineUserDto> onlineUserDtos = getAll(userName);
if(onlineUserDtos ==null || onlineUserDtos.isEmpty()){
return;
}
for(OnlineUserDto onlineUserDto : onlineUserDtos){
if(onlineUserDto.getUserName().equals(userName)){
try {
String token =EncryptUtils.desDecrypt(onlineUserDto.getKey());
if(StringUtils.isNotBlank(igoreToken)&&!igoreToken.equals(token)){
this.kickOut(token);
}else if(StringUtils.isBlank(igoreToken)){
this.kickOut(token);
}
} catch (Exception e) {
log.error("checkUser is error",e);
}
}
}
}
/**
* 根据用户名强退用户
* @param username /
*/
@Async
public void kickOutForUsername(String username) throws Exception {
List<OnlineUserDto> onlineUsers = getAll(username);
for (OnlineUserDto onlineUser : onlineUsers) {
if (onlineUser.getUserName().equals(username)) {
String token =EncryptUtils.desDecrypt(onlineUser.getKey());
kickOut(token);
}
}
}
}

View File

@@ -0,0 +1,68 @@
package com.platform.modules.security.service;
import cn.hutool.core.util.RandomUtil;
import com.platform.modules.security.config.bean.LoginProperties;
import com.platform.modules.security.service.dto.JwtUserDto;
import com.platform.utils.RedisUtils;
import com.platform.utils.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* @author AllDataDC
* @description 用户缓存管理
* @date 2023-01-27
**/
@Component
public class UserCacheManager {
@Resource
private RedisUtils redisUtils;
@Value("${login.user-cache.idle-time}")
private long idleTime;
/**
* 返回用户缓存
* @param userName 用户名
* @return JwtUserDto
*/
public JwtUserDto getUserCache(String userName) {
if (StringUtils.isNotEmpty(userName)) {
// 获取数据
Object obj = redisUtils.hget(LoginProperties.cacheKey, userName);
if(obj != null){
return (JwtUserDto)obj;
}
}
return null;
}
/**
* 添加缓存到Redis
* @param userName 用户名
*/
@Async
public void addUserCache(String userName, JwtUserDto user) {
if (StringUtils.isNotEmpty(userName)) {
// 添加数据, 避免数据同时过期
long time = idleTime + RandomUtil.randomInt(900, 1800);
redisUtils.hset(LoginProperties.cacheKey, userName, user, time);
}
}
/**
* 清理用户缓存信息
* 用户信息变更时
* @param userName 用户名
*/
@Async
public void cleanUserCache(String userName) {
if (StringUtils.isNotEmpty(userName)) {
// 清除数据
redisUtils.hdel(LoginProperties.cacheKey, userName);
}
}
}

View File

@@ -0,0 +1,58 @@
package com.platform.modules.security.service;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import com.platform.exception.BadRequestException;
import com.platform.exception.EntityNotFoundException;
import com.platform.modules.security.service.dto.JwtUserDto;
import com.platform.modules.system.service.DataService;
import com.platform.modules.system.service.RoleService;
import com.platform.modules.system.service.UserService;
import com.platform.modules.system.service.dto.UserLoginDto;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
/**
* @author AllDataDC
* @date 2023-01-27
*/
@Slf4j
@RequiredArgsConstructor
@Service("userDetailsService")
public class UserDetailsServiceImpl implements UserDetailsService {
private final UserService userService;
private final RoleService roleService;
private final DataService dataService;
private final UserCacheManager userCacheManager;
@Override
public JwtUserDto loadUserByUsername(String username) {
JwtUserDto jwtUserDto = userCacheManager.getUserCache(username);
if(jwtUserDto == null){
UserLoginDto user;
try {
user = userService.getLoginData(username);
} catch (EntityNotFoundException e) {
// SpringSecurity会自动转换UsernameNotFoundException为BadCredentialsException
throw new UsernameNotFoundException(username, e);
}
if (user == null) {
throw new UsernameNotFoundException("");
} else {
if (!user.getEnabled()) {
throw new BadRequestException("账号未激活!");
}
jwtUserDto = new JwtUserDto(
user,
dataService.getDeptIds(user),
roleService.mapToGrantedAuthorities(user)
);
// 添加缓存数据
userCacheManager.addUserCache(username, jwtUserDto);
}
}
return jwtUserDto;
}
}

View File

@@ -0,0 +1,25 @@
package com.platform.modules.security.service.dto;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotBlank;
/**
* @author AllDataDC
* @date 2023-01-27
*/
@Getter
@Setter
public class AuthUserDto {
@NotBlank
private String username;
@NotBlank
private String password;
private String code;
private String uuid = "";
}

View File

@@ -0,0 +1,20 @@
package com.platform.modules.security.service.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
/**
* 避免序列化问题
* @author AllDataDC
* @date 2023-01-27
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class AuthorityDto implements GrantedAuthority {
private String authority;
}

View File

@@ -0,0 +1,66 @@
package com.platform.modules.security.service.dto;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.AllArgsConstructor;
import lombok.Getter;
import com.platform.modules.system.service.dto.UserLoginDto;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* @author AllDataDC
* @date 2023-01-27
*/
@Getter
@AllArgsConstructor
public class JwtUserDto implements UserDetails {
private final UserLoginDto user;
private final List<Long> dataScopes;
private final List<AuthorityDto> authorities;
public Set<String> getRoles() {
return authorities.stream().map(AuthorityDto::getAuthority).collect(Collectors.toSet());
}
@Override
@JSONField(serialize = false)
public String getPassword() {
return user.getPassword();
}
@Override
@JSONField(serialize = false)
public String getUsername() {
return user.getUsername();
}
@JSONField(serialize = false)
@Override
public boolean isAccountNonExpired() {
return true;
}
@JSONField(serialize = false)
@Override
public boolean isAccountNonLocked() {
return true;
}
@JSONField(serialize = false)
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
@JSONField(serialize = false)
public boolean isEnabled() {
return user.getEnabled();
}
}

View File

@@ -0,0 +1,59 @@
package com.platform.modules.security.service.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
/**
* 在线用户
* @author AllDataDC
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class OnlineUserDto {
/**
* 用户名
*/
private String userName;
/**
* 昵称
*/
private String nickName;
/**
* 岗位
*/
private String dept;
/**
* 浏览器
*/
private String browser;
/**
* IP
*/
private String ip;
/**
* 地址
*/
private String address;
/**
* token
*/
private String key;
/**
* 登录时间
*/
private Date loginTime;
}

View File

@@ -0,0 +1,72 @@
package com.platform.modules.system.domain;
import com.alibaba.fastjson.annotation.JSONField;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import com.platform.base.BaseEntity;
import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.Objects;
import java.util.Set;
/**
* @author AllDataDC
* @date 2023-01-27
*/
@Entity
@Getter
@Setter
@Table(name="sys_dept")
public class Dept extends BaseEntity implements Serializable {
@Id
@Column(name = "dept_id")
@NotNull(groups = Update.class)
@ApiModelProperty(value = "ID", hidden = true)
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@JSONField(serialize = false)
@ManyToMany(mappedBy = "depts")
@ApiModelProperty(value = "角色")
private Set<Role> roles;
@ApiModelProperty(value = "排序")
private Integer deptSort;
@NotBlank
@ApiModelProperty(value = "部门名称")
private String name;
@NotNull
@ApiModelProperty(value = "是否启用")
private Boolean enabled;
@ApiModelProperty(value = "上级部门")
private Long pid;
@ApiModelProperty(value = "子节点数目", hidden = true)
private Integer subCount = 0;
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Dept dept = (Dept) o;
return Objects.equals(id, dept.id) &&
Objects.equals(name, dept.name);
}
@Override
public int hashCode() {
return Objects.hash(id, name);
}
}

View File

@@ -0,0 +1,40 @@
package com.platform.modules.system.domain;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import com.platform.base.BaseEntity;
import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.List;
/**
* @author AllDataDC
* @date 2023-01-27
*/
@Entity
@Getter
@Setter
@Table(name="sys_dict")
public class Dict extends BaseEntity implements Serializable {
@Id
@Column(name = "dict_id")
@NotNull(groups = Update.class)
@ApiModelProperty(value = "ID", hidden = true)
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToMany(mappedBy = "dict",cascade={CascadeType.PERSIST,CascadeType.REMOVE})
private List<DictDetail> dictDetails;
@NotBlank
@ApiModelProperty(value = "名称")
private String name;
@ApiModelProperty(value = "描述")
private String description;
}

View File

@@ -0,0 +1,42 @@
package com.platform.modules.system.domain;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import com.platform.base.BaseEntity;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
/**
* @author AllDataDC
* @date 2023-01-27
*/
@Entity
@Getter
@Setter
@Table(name="sys_dict_detail")
public class DictDetail extends BaseEntity implements Serializable {
@Id
@Column(name = "detail_id")
@NotNull(groups = Update.class)
@ApiModelProperty(value = "ID", hidden = true)
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@JoinColumn(name = "dict_id")
@ManyToOne(fetch=FetchType.LAZY)
@ApiModelProperty(value = "字典", hidden = true)
private Dict dict;
@ApiModelProperty(value = "字典标签")
private String label;
@ApiModelProperty(value = "字典值")
private String value;
@ApiModelProperty(value = "排序")
private Integer dictSort = 999;
}

View File

@@ -0,0 +1,59 @@
package com.platform.modules.system.domain;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import com.platform.base.BaseEntity;
import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.Objects;
/**
* @author AllDataDC
* @date 2023-01-27
*/
@Entity
@Getter
@Setter
@Table(name="sys_job")
public class Job extends BaseEntity implements Serializable {
@Id
@Column(name = "job_id")
@NotNull(groups = Update.class)
@ApiModelProperty(value = "ID", hidden = true)
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotBlank
@ApiModelProperty(value = "岗位名称")
private String name;
@NotNull
@ApiModelProperty(value = "岗位排序")
private Long jobSort;
@NotNull
@ApiModelProperty(value = "是否启用")
private Boolean enabled;
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Job job = (Job) o;
return Objects.equals(id, job.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
}

View File

@@ -0,0 +1,96 @@
package com.platform.modules.system.domain;
import com.alibaba.fastjson.annotation.JSONField;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import com.platform.base.BaseEntity;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.Objects;
import java.util.Set;
/**
* @author AllDataDC
* @date 2023-01-27
*/
@Entity
@Getter
@Setter
@Table(name = "sys_menu")
public class Menu extends BaseEntity implements Serializable {
@Id
@Column(name = "menu_id")
@NotNull(groups = {Update.class})
@ApiModelProperty(value = "ID", hidden = true)
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@JSONField(serialize = false)
@ManyToMany(mappedBy = "menus")
@ApiModelProperty(value = "菜单角色")
private Set<Role> roles;
@ApiModelProperty(value = "菜单标题")
private String title;
@Column(name = "name")
@ApiModelProperty(value = "菜单组件名称")
private String componentName;
@ApiModelProperty(value = "排序")
private Integer menuSort = 999;
@ApiModelProperty(value = "组件路径")
private String component;
@ApiModelProperty(value = "路由地址")
private String path;
@ApiModelProperty(value = "菜单类型,目录、菜单、按钮")
private Integer type;
@ApiModelProperty(value = "权限标识")
private String permission;
@ApiModelProperty(value = "菜单图标")
private String icon;
@Column(columnDefinition = "bit(1) default 0")
@ApiModelProperty(value = "缓存")
private Boolean cache;
@Column(columnDefinition = "bit(1) default 0")
@ApiModelProperty(value = "是否隐藏")
private Boolean hidden;
@ApiModelProperty(value = "上级菜单")
private Long pid;
@ApiModelProperty(value = "子节点数目", hidden = true)
private Integer subCount = 0;
@ApiModelProperty(value = "外链菜单")
private Boolean iFrame;
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Menu menu = (Menu) o;
return Objects.equals(id, menu.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
}

View File

@@ -0,0 +1,85 @@
package com.platform.modules.system.domain;
import com.alibaba.fastjson.annotation.JSONField;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import com.platform.base.BaseEntity;
import com.platform.utils.enums.DataScopeEnum;
import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.Objects;
import java.util.Set;
/**
* 角色
* @author AllDataDC
* @date 2023-01-27
*/
@Getter
@Setter
@Entity
@Table(name = "sys_role")
public class Role extends BaseEntity implements Serializable {
@Id
@Column(name = "role_id")
@NotNull(groups = {Update.class})
@GeneratedValue(strategy = GenerationType.IDENTITY)
@ApiModelProperty(value = "ID", hidden = true)
private Long id;
@JSONField(serialize = false)
@ManyToMany(mappedBy = "roles")
@ApiModelProperty(value = "用户", hidden = true)
private Set<User> users;
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(name = "sys_roles_menus",
joinColumns = {@JoinColumn(name = "role_id",referencedColumnName = "role_id")},
inverseJoinColumns = {@JoinColumn(name = "menu_id",referencedColumnName = "menu_id")})
@ApiModelProperty(value = "菜单", hidden = true)
private Set<Menu> menus;
@ManyToMany
@JoinTable(name = "sys_roles_depts",
joinColumns = {@JoinColumn(name = "role_id",referencedColumnName = "role_id")},
inverseJoinColumns = {@JoinColumn(name = "dept_id",referencedColumnName = "dept_id")})
@ApiModelProperty(value = "部门", hidden = true)
private Set<Dept> depts;
@NotBlank
@ApiModelProperty(value = "名称", hidden = true)
private String name;
@ApiModelProperty(value = "数据权限,全部 、 本级 、 自定义")
private String dataScope = DataScopeEnum.THIS_LEVEL.getValue();
@Column(name = "level")
@ApiModelProperty(value = "级别,数值越小,级别越大")
private Integer level = 3;
@ApiModelProperty(value = "描述")
private String description;
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Role role = (Role) o;
return Objects.equals(id, role.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
}

View File

@@ -0,0 +1,111 @@
package com.platform.modules.system.domain;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import com.platform.base.BaseEntity;
import javax.persistence.*;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.Date;
import java.util.Objects;
import java.util.Set;
/**
* @author AllDataDC
* @date 2023-01-27
*/
@Entity
@Getter
@Setter
@Table(name="sys_user")
public class User extends BaseEntity implements Serializable {
@Id
@Column(name = "user_id")
@NotNull(groups = Update.class)
@GeneratedValue(strategy = GenerationType.IDENTITY)
@ApiModelProperty(value = "ID", hidden = true)
private Long id;
@ManyToMany(fetch = FetchType.EAGER)
@ApiModelProperty(value = "用户角色")
@JoinTable(name = "sys_users_roles",
joinColumns = {@JoinColumn(name = "user_id",referencedColumnName = "user_id")},
inverseJoinColumns = {@JoinColumn(name = "role_id",referencedColumnName = "role_id")})
private Set<Role> roles;
@ManyToMany(fetch = FetchType.EAGER)
@ApiModelProperty(value = "用户岗位")
@JoinTable(name = "sys_users_jobs",
joinColumns = {@JoinColumn(name = "user_id",referencedColumnName = "user_id")},
inverseJoinColumns = {@JoinColumn(name = "job_id",referencedColumnName = "job_id")})
private Set<Job> jobs;
@OneToOne
@JoinColumn(name = "dept_id")
@ApiModelProperty(value = "用户部门")
private Dept dept;
@NotBlank
@Column(unique = true)
@ApiModelProperty(value = "用户名称")
private String username;
@NotBlank
@ApiModelProperty(value = "用户昵称")
private String nickName;
@Email
@NotBlank
@ApiModelProperty(value = "邮箱")
private String email;
@NotBlank
@ApiModelProperty(value = "电话号码")
private String phone;
@ApiModelProperty(value = "用户性别")
private String gender;
@ApiModelProperty(value = "头像真实名称",hidden = true)
private String avatarName;
@ApiModelProperty(value = "头像存储的路径", hidden = true)
private String avatarPath;
@ApiModelProperty(value = "密码")
private String password;
@NotNull
@ApiModelProperty(value = "是否启用")
private Boolean enabled;
@ApiModelProperty(value = "是否为admin账号", hidden = true)
private Boolean isAdmin = false;
@Column(name = "pwd_reset_time")
@ApiModelProperty(value = "最后修改密码的时间", hidden = true)
private Date pwdResetTime;
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
User user = (User) o;
return Objects.equals(id, user.id) &&
Objects.equals(username, user.username);
}
@Override
public int hashCode() {
return Objects.hash(id, username);
}
}

View File

@@ -0,0 +1,21 @@
package com.platform.modules.system.domain;
import com.platform.modules.system.service.dto.UserDto;
import lombok.Data;
import java.io.Serializable;
@Data
public class UserInfo implements Serializable {
private static final long serialVersionUID=1L;
/**
* 用户基本信息
*/
private UserDto userVo;
/**
* 权限标识集合
*/
private String[] perms;
}

View File

@@ -0,0 +1,21 @@
package com.platform.modules.system.domain.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.io.Serializable;
/**
* @author AllDataDC
* @date 2023-01-27
*/
@Data
@AllArgsConstructor
public class MenuMetaVo implements Serializable {
private String title;
private String icon;
private Boolean noCache;
}

View File

@@ -0,0 +1,33 @@
package com.platform.modules.system.domain.vo;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
/**
* 构建前端路由时用到
* @author AllDataDC
* @date 2023-01-27
*/
@Data
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class MenuVo implements Serializable {
private String name;
private String path;
private Boolean hidden;
private String redirect;
private String component;
private Boolean alwaysShow;
private MenuMetaVo meta;
private List<MenuVo> children;
}

Some files were not shown because too many files have changed in this diff Show More