初始化项目

This commit is contained in:
fangshunjian
2024-06-21 11:00:58 +08:00
commit 0f9fff2f41
58 changed files with 4445 additions and 0 deletions

220
pom.xml Normal file
View File

@@ -0,0 +1,220 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.5</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<groupId>net.geedge</groupId>
<artifactId>asw-controller</artifactId>
<version>1.0</version>
<name>asw-controller</name>
<description>AppSketch Works Controller</description>
<properties>
<java.version>21</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-mysql</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
</dependency>
<dependency>
<groupId>org.ssssssss</groupId>
<artifactId>magic-api-spring-boot-starter</artifactId>
<version>2.1.1</version>
<exclusions>
<exclusion>
<groupId>org.ssssssss</groupId>
<artifactId>magic-script</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.restdocs</groupId>
<artifactId>spring-restdocs-mockmvc</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot3-starter</artifactId>
<version>1.37.0</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.16</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.4.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.graalvm.sdk</groupId>
<artifactId>graal-sdk</artifactId>
<version>22.3.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
<dependency>
<groupId>org.ssssssss</groupId>
<artifactId>magic-api-plugin-task</artifactId>
<version>2.1.1</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.3</version>
</dependency>
<dependency>
<groupId>xerces</groupId>
<artifactId>xercesImpl</artifactId>
<version>2.12.2</version>
</dependency>
<!-- jasypt加密 -->
<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
</dependency>
<dependency>
<groupId>org.ssssssss</groupId>
<artifactId>magic-script</artifactId>
<version>1.8.9</version>
</dependency>
<dependency>
<groupId>com.j256.simplemagic</groupId>
<artifactId>simplemagic</artifactId>
<version>1.16</version>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<excludes>
<exclude>**/logback-spring.xml</exclude>
<exclude>**/asw.properties</exclude>
</excludes>
<includes>
<!--包含文件夹以及子文件夹下所有资源 -->
<include>**/*.*</include>
</includes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>public</id>
<name>aliyun nexus</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<releases>
<enabled>true</enabled>
</releases>
</repository>
<!-- 配置私服地址 -->
<repository>
<id>nexus</id>
<name>Team Nexus Repository</name>
<url>http://192.168.40.153:8099/content/groups/public/</url>
</repository>
<repository>
<id>nexus-releases</id>
<name>Team Nexus Repository</name>
<url>http://192.168.40.153:8099/content/repositories/releases/</url>
</repository>
<repository>
<id>nexus-geedge</id>
<name>Team Nexus Repository</name>
<url>http://192.168.40.153:8099/content/repositories/geedge/</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>public</id>
<name>aliyun nexus</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</project>

View File

@@ -0,0 +1,24 @@
package net.geedge.asw;
import java.util.TimeZone;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.PropertySource;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import net.geedge.asw.common.util.Constants;
import net.geedge.asw.common.util.T;
@SpringBootApplication(proxyBeanMethods = false)
@EnableTransactionManagement
@PropertySource(value = {"file:./config/asw.properties", "classpath:./config/asw.properties"}, encoding = "utf-8", ignoreResourceNotFound = true)
public class AppSketchWokrsApplication {
public static void main(String[] args) throws Exception {
TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
System.setProperty("jasypt.encryptor.password", T.HexUtil.encodeHexStr(Constants.AES_KEY));
SpringApplication.run(AppSketchWokrsApplication.class, args);
}
}

View File

@@ -0,0 +1,104 @@
package net.geedge.asw.common.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.config.CronTask;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.web.client.RestTemplate;
import org.ssssssss.magicapi.core.config.MagicConfiguration;
import org.ssssssss.magicapi.core.service.MagicResourceStorage;
import org.ssssssss.magicapi.modules.http.HttpModule;
import org.ssssssss.magicapi.task.model.TaskInfo;
import org.ssssssss.magicapi.task.service.TaskInfoMagicResourceStorage;
import org.ssssssss.magicapi.task.service.TaskMagicDynamicRegistry;
import org.ssssssss.magicapi.task.starter.MagicTaskConfig;
import org.ssssssss.magicapi.utils.ScriptManager;
import org.ssssssss.script.MagicScriptContext;
@Configuration
public class MagicApiConfig {
@Autowired
private MagicTaskConfig config;
@Bean
public HttpModule magicHttpModule(RestTemplate restTemplate) {
return new HttpModule(restTemplate);
}
@Bean
public TaskMagicDynamicRegistry taskMagicDynamicRegistry(
TaskInfoMagicResourceStorage taskInfoMagicResourceStorage) {
MagicTaskConfig.Shutdown shutdown = config.getShutdown();
ThreadPoolTaskScheduler poolTaskScheduler = null;
if (config.isEnable()) {
poolTaskScheduler = new ThreadPoolTaskScheduler();
poolTaskScheduler.setPoolSize(config.getPool().getSize());
poolTaskScheduler.setWaitForTasksToCompleteOnShutdown(shutdown.isAwaitTermination());
if (shutdown.getAwaitTerminationPeriod() != null) {
poolTaskScheduler.setAwaitTerminationSeconds((int) shutdown.getAwaitTerminationPeriod().getSeconds());
}
poolTaskScheduler.setThreadNamePrefix(config.getThreadNamePrefix());
poolTaskScheduler.initialize();
}
return new CustomTaskMagicDynamicRegistry(taskInfoMagicResourceStorage, poolTaskScheduler, config.isLog());
}
/**
* 自定义任务动态注册context注入taskinfo对象
*/
private class CustomTaskMagicDynamicRegistry extends TaskMagicDynamicRegistry {
private final TaskScheduler taskScheduler;
private static final Logger logger = LoggerFactory.getLogger(CustomTaskMagicDynamicRegistry.class);
private final boolean showLog;
public CustomTaskMagicDynamicRegistry(MagicResourceStorage<TaskInfo> magicResourceStorage,
TaskScheduler taskScheduler, boolean showLog) {
super(magicResourceStorage, taskScheduler, showLog);
this.taskScheduler = taskScheduler;
this.showLog = showLog;
}
@Override
protected boolean register(MappingNode<TaskInfo> mappingNode) {
TaskInfo entity = mappingNode.getEntity();
if (taskScheduler != null) {
String scriptName = MagicConfiguration.getMagicResourceService().getScriptName(entity);
try {
CronTrigger trigger = new CronTrigger(entity.getCron());
CronTask cronTask = new CronTask(() -> {
if (entity.isEnabled()) {
try {
if (showLog) {
logger.info("定时任务:[{}]开始执行", scriptName);
}
MagicScriptContext magicScriptContext = new MagicScriptContext();
magicScriptContext.setScriptName(scriptName);
magicScriptContext.set("taskinfo", entity);
ScriptManager.executeScript(entity.getScript(), magicScriptContext);
} catch (Exception e) {
logger.error("scriptName:{}", scriptName);
logger.error("定时任务执行出错", e);
} finally {
if (showLog) {
logger.info("定时任务:[{}]执行完毕", scriptName);
}
}
}
}, trigger);
mappingNode.setMappingData(taskScheduler.schedule(cronTask.getRunnable(), cronTask.getTrigger()));
} catch (Exception e) {
logger.error("定时任务:[{}]注册失败", scriptName, e);
}
logger.debug("注册定时任务:[{},{}]", MagicConfiguration.getMagicResourceService().getScriptName(entity),
entity.getCron());
}
return true;
}
}
}

View File

@@ -0,0 +1,22 @@
package net.geedge.asw.common.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
@Configuration(proxyBeanMethods = false)
public class MybatisPlusConfig {
/**
* 添加分页插件
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MARIADB));//如果配置多个插件,切记分页最后添加
return interceptor;
}
}

View File

@@ -0,0 +1,131 @@
package net.geedge.asw.common.config;
import java.io.IOException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.SSLContext;
import org.apache.hc.client5.http.classic.HttpClient;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.DefaultConnectionKeepAliveStrategy;
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.client5.http.socket.ConnectionSocketFactory;
import org.apache.hc.client5.http.socket.PlainConnectionSocketFactory;
import org.apache.hc.client5.http.ssl.NoopHostnameVerifier;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
import org.apache.hc.core5.http.config.Registry;
import org.apache.hc.core5.http.config.RegistryBuilder;
import org.apache.hc.core5.ssl.SSLContexts;
import org.apache.hc.core5.ssl.TrustStrategy;
import org.apache.hc.core5.util.Timeout;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.DefaultResponseErrorHandler;
import org.springframework.web.client.RestTemplate;
/**
* Http resttemplate 配置
**/
@Configuration
public class RestTemplateConfig {
/**
* # 从连接池获取连接的timeout,不宜过大,ms
*/
@Value("${http-pool.connection-request-timeout:200}")
private int connectionRequestTimeout;
/**
* 指客户端和服务器建立连接的超时时间,ms , 最大约21秒,因为内部tcp在进行三次握手建立连接时,默认tcp超时时间是20秒
*/
@Value("${http-pool.connection-timeout:1000}")
private int connectionTimeout;
/**
* 指客户端从服务器读取数据包的间隔超时时间,不是总读取时间,也就是socket timeout,ms
*/
@Value("${http-pool.socket-timeout:180000}")
private int socketTimeout;
/**
* #每个路由的最大连接数,如果只调用一个地址,可以将其设置为最大连接数
*/
@Value("${http-pool.max-per-route:10000}")
private int maxPerRoute;
/**
* #连接池的最大连接数0代表不限如果取0需要考虑连接泄露导致系统崩溃的后果
*/
@Value("${http-pool.max-total:1000}")
private int maxTotal;
/**
* 长连接保持时间 单位s,不宜过长
*/
@Value("${http-pool.keep-alive-time:60}")
private int keepAliveTime;
@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder, ClientHttpRequestFactory requestFactory) {
RestTemplate restTemplate = builder.build();
restTemplate.setRequestFactory(requestFactory);
restTemplate.setErrorHandler(new DefaultResponseErrorHandler() {
@Override
public boolean hasError(ClientHttpResponse response) throws IOException {
int rawStatusCode = response.getRawStatusCode();
HttpStatus statusCode = HttpStatus.resolve(rawStatusCode);
return (statusCode != null ? statusCode.isError() : hasError(rawStatusCode));
}
protected boolean hasError(int unknownStatusCode) {
HttpStatus.Series series = HttpStatus.Series.resolve(unknownStatusCode);
return (series == HttpStatus.Series.CLIENT_ERROR || series == HttpStatus.Series.SERVER_ERROR);
}
@Override
public void handleError(ClientHttpResponse response) throws IOException {
}
});
return restTemplate;
}
@Bean
public ClientHttpRequestFactory httpRequestFactory(HttpClient client) {
return new HttpComponentsClientHttpRequestFactory(client);
}
@Bean
public HttpClient httpClient() throws Exception {
// 配置不校验server 证书
TrustStrategy acceptingTrustStrategy = new TrustStrategy() {
@Override
public boolean isTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
return true;
}
};
SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy).build();
SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext,
new NoopHostnameVerifier());
Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.getSocketFactory())
.register("https", sslConnectionSocketFactory).build();
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(registry);
// 设置整个连接池最大连接数 根据自己的场景决定
connectionManager.setMaxTotal(maxTotal);
// 路由是对maxTotal的细分
connectionManager.setDefaultMaxPerRoute(maxPerRoute);
RequestConfig requestConfig = RequestConfig.custom().setResponseTimeout(Timeout.ofMilliseconds(socketTimeout))// 服务器返回数据(response)的时间超过该时间抛出readtimeout
.setConnectTimeout(Timeout.ofMilliseconds(connectionTimeout))// 连接上服务器(握手成功)的时间超出该时间抛出connect timeout
.setConnectionRequestTimeout(Timeout.ofMilliseconds(connectionRequestTimeout))// 从连接池中获取连接的超时时间超过该时间未拿到可用连接会抛出org.apache.http.conn.ConnectionPoolTimeoutException:Timeout
// waiting for connection from pool
.build();
return HttpClientBuilder.create().setDefaultRequestConfig(requestConfig)
.setConnectionManager(connectionManager)
.setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy())
.build();
}
}

View File

@@ -0,0 +1,53 @@
package net.geedge.asw.common.config;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.interceptor.SaInterceptor;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.stp.StpUtil;
@Configuration(proxyBeanMethods = false)
public class SaTokenConfigure implements WebMvcConfigurer {
/**
* satoken timeout
* key: satoken.timeout
* 单位:分钟
* 默认30天
*/
@Value("${satoken.timeout:#{24 * 60 * 30}}")
private Long timeout;
// Sa-Token 参数配置参考文档https://sa-token.cc
// 此配置会与 application.yml 中的配置合并 (代码配置优先)
@Autowired
public void configSaToken(SaTokenConfig config) {
config.setTokenName("satoken"); // token 名称(同时也是 cookie 名称)
Long satokenTimeout = this.timeout == -1L ? -1L : TimeUnit.MINUTES.toSeconds(this.timeout);
config.setTimeout(satokenTimeout); // token 有效期单位默认30天-1代表永不过期
config.setActiveTimeout(-1); // token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
config.setIsConcurrent(true); // 是否允许同一账号多地同时登录(为 true 时允许一起登录,为 false 时新登录挤掉旧登录)
config.setIsShare(true); // 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token为 false 时每次登录新建一个 token
config.setTokenStyle("simple-uuid"); // token 风格
config.setIsLog(false); // 是否输出操作日志
config.setIsPrint(false);
// config.setIsReadCookie(false);
}
// 注册 Sa-Token 拦截器,打开注解式鉴权功能
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册 Sa-Token 拦截器,打开注解式鉴权功能
registry.addInterceptor(new SaInterceptor(handler -> {
SaRouter.match("/file/**").notMatch("/file/content/*").check(r -> StpUtil.checkLogin());
SaRouter.match("/sys/**").notMatch("/sys/login").check(r -> StpUtil.checkLogin());
})).addPathPatterns("/**");
}
}

View File

@@ -0,0 +1,46 @@
package net.geedge.asw.common.config;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration(proxyBeanMethods = false)
public class WebConfig implements WebMvcConfigurer {
/**
* CORS 基于Filter的全局配置
*
* @return
*/
@Bean
public FilterRegistrationBean<CorsFilter> corsFilter() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
final CorsConfiguration config = new CorsConfiguration();
// 允许cookies跨域
config.setAllowCredentials(true);
// #允许向该服务器提交请求的URI*表示全部允许在SpringMVC中如果设成*会自动转成当前请求头中的Origin
config.addAllowedOrigin("*");
// #允许访问的头信息,*表示全部
config.addAllowedHeader("*");
// 预检请求的缓存时间(秒),即在这个时间段里,对于相同的跨域请求不会再预检了
config.setMaxAge(18000L);
// 允许提交请求的方法,*表示全部允许
config.addAllowedMethod("OPTIONS");
config.addAllowedMethod("HEAD");
config.addAllowedMethod("GET");
config.addAllowedMethod("PUT");
config.addAllowedMethod("POST");
config.addAllowedMethod("DELETE");
config.addAllowedMethod("PATCH");
source.registerCorsConfiguration("/**", config);
FilterRegistrationBean<CorsFilter> bean = new FilterRegistrationBean<CorsFilter>(new CorsFilter(source));
// 设置监听器的优先级
bean.setOrder(0);
return bean;
}
}

View File

@@ -0,0 +1,65 @@
package net.geedge.asw.common.config.exception;
import org.apache.catalina.connector.ClientAbortException;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import cn.dev33.satoken.exception.NotLoginException;
import cn.hutool.log.Log;
import jakarta.servlet.http.HttpServletRequest;
import net.geedge.asw.common.util.ASWException;
import net.geedge.asw.common.util.R;
import net.geedge.asw.common.util.RCode;
/**
* 异常处理器
*/
@RestControllerAdvice
public class ASWExceptionHandler {
private static final Log log = Log.get();
/**
* 处理自定义异常
*/
@ExceptionHandler(ASWException.class)
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
public R handleDHException(ASWException e, HttpServletRequest request) {
log.warn(e, "Request uri: {}", request.getRequestURI());
return R.error(e.getCode(), e.getMsg());
}
/**
* 用户未登录
* @param e
* @param request
* @return
*/
@ExceptionHandler(NotLoginException.class)
@ResponseStatus(value = HttpStatus.FORBIDDEN)
public R handleNotLoginException(NotLoginException e, HttpServletRequest request) {
log.warn(e, "Request uri: {}", request.getRequestURI());
return R.error(RCode.USER_NO_LOGIN);
}
@ExceptionHandler(DuplicateKeyException.class)
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
public R handleDuplicateKeyException(DuplicateKeyException e, HttpServletRequest request) {
log.error(e, "Request uri: {}", request.getRequestURI());
return R.error(RCode.SYS_DUPLICATE_RECORD);
}
@ExceptionHandler(Exception.class)
@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
public R handleException(Exception e, HttpServletRequest request) {
if (e instanceof ClientAbortException) {
return null;
}
log.error(e, "Request uri: {}", request.getRequestURI());
return R.error().put("msg", e.getMessage());
}
}

View File

@@ -0,0 +1,103 @@
package net.geedge.asw.common.util;
import lombok.Builder;
import lombok.Data;
@Builder
@Data
public class ASWException extends RuntimeException {
private static final long serialVersionUID = 1L;
private String msg = RCode.ERROR.getMsg();
private int code = RCode.ERROR.getCode();
private Object[] param = new Object[] {};
public ASWException(RCode rCode) {
super(rCode.getMsg());
this.code = rCode.getCode();
this.msg = rCode.getMsg();
this.param = rCode.getParam();
}
public ASWException(String msg) {
super(msg);
this.msg = msg;
}
public ASWException(String msg, Throwable e) {
super(msg, e);
this.msg = msg;
}
public ASWException(String msg, int code) {
super(msg);
this.msg = msg;
this.code = code;
}
public ASWException(String msg, int code, Object[] param, Throwable e) {
super(msg, e);
this.msg = msg;
this.code = code;
this.param = param;
}
public ASWException(String msg, int code, Throwable e) {
super(msg, e);
this.msg = msg;
this.code = code;
}
public void throwException() {
throw this;
}
public static ASWExceptionBuilder builder() {
return new ASWExceptionBuilder();
}
public static class ASWExceptionBuilder {
private int code = RCode.ERROR.getCode();
private String msg = RCode.ERROR.getMsg();
private Throwable e;
private Object[] param;
private ASWExceptionBuilder() {
}
public ASWExceptionBuilder rcode(RCode rcode) {
if (T.ObjectUtil.isNotNull(rcode)) {
this.code = rcode.getCode();
this.msg = rcode.getMsg();
this.param = rcode.getParam();
}
return this;
}
public ASWExceptionBuilder code(int code) {
this.code = code;
return this;
}
public ASWExceptionBuilder msg(String msg) {
this.msg = msg;
return this;
}
public ASWExceptionBuilder param(Object[] param) {
this.param = param;
return this;
}
public ASWExceptionBuilder throwable(Throwable e) {
this.e = e;
return this;
}
public ASWException build() {
return new ASWException(this.msg, this.code, this.param, this.e);
}
}
}

View File

@@ -0,0 +1,19 @@
package net.geedge.asw.common.util;
import java.io.File;
public class Constants {
/**
* AES 加密密钥
*/
public static final byte[] AES_KEY = T.HexUtil.decodeHex("bde5430614b21baf1c53bd6f616d1a39");
/**
* 临时目录
*/
public static final String TEMP_PATH = System.getProperty("user.dir") + File.separator + "tmp";
}

View File

@@ -0,0 +1,51 @@
package net.geedge.asw.common.util;
import org.jasypt.encryption.pbe.StandardPBEStringEncryptor;
import org.jasypt.iv.RandomIvGenerator;
import cn.hutool.core.util.HexUtil;
/**
* Jasypt 密钥加密工具类
* 默认使用 版本默认加密算法 PBEWithHMACSHA512AndAES_256
*
* @Link https://github.com/ulisesbocchio/jasypt-spring-boot
*/
public class JasyptUtil {
/**
* encrypt
*
* @param content
* @return
*/
public static String encrypt(String content) {
StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor();
// 加密算法
encryptor.setAlgorithm("PBEWithHMACSHA512AndAES_256");
encryptor.setIvGenerator(new RandomIvGenerator());
String password = HexUtil.encodeHexStr(Constants.AES_KEY);
encryptor.setPassword(password);
String encrypt = encryptor.encrypt(content);
return encrypt;
}
/**
* decrypt
*
* @param content
* @return
*/
public static String decrypt(String content) {
StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor();
// 加密算法
encryptor.setAlgorithm("PBEWithHMACSHA512AndAES_256");
encryptor.setIvGenerator(new RandomIvGenerator());
String password = HexUtil.encodeHexStr(Constants.AES_KEY);
encryptor.setPassword(password);
String decrypt = encryptor.decrypt(content);
return decrypt;
}
}

View File

@@ -0,0 +1,82 @@
package net.geedge.asw.common.util;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* 返回数据
*
* 错误码、错误内容统一在枚举类RCode中定义 错误码格式见RCode注释错误码内容必须用英文作为国际化的code 自定义的错误类型必须加注释
*/
public class R extends HashMap<String, Object> {
private static final long serialVersionUID = 1L;
public R() {
put("code", RCode.SUCCESS.getCode());
put("msg", RCode.SUCCESS.getMsg());
put("timestamp", T.DateUtil.current());
}
public static R error() {
return error(RCode.ERROR.getCode(), RCode.ERROR.getMsg());
}
public static R error(RCode rCode) {
R r = new R();
r.put("code", rCode.getCode());
r.put("msg", rCode.getMsg());
return r;
}
public static R error(Integer code, String msg) {
R r = new R();
r.put("code", code);
r.put("msg", msg);
return r;
}
public static R ok(String msg) {
R r = new R();
r.put("msg", msg);
return r;
}
public static R ok() {
return new R();
}
public static R ok(Object data) {
R r = new R();
r.put("data", data);
return r;
}
@Override
public R put(String key, Object value) {
super.put(key, value);
return this;
}
public R putData(Object value) {
this.put("data", value);
return this;
}
@SuppressWarnings("unchecked")
public R putData(String key, Object value) {
Object data = super.getOrDefault("data", new LinkedHashMap<String, Object>());
if (!(data instanceof Map)) {
throw new ASWException("data put error");
}
((Map<String, Object>) data).put(key, value);
super.put("data", data);
return this;
}
@SuppressWarnings("all")
public R putAllData(Map m) {
super.putAll(m);
return this;
}
}

View File

@@ -0,0 +1,71 @@
package net.geedge.asw.common.util;
import java.text.MessageFormat;
public enum RCode {
/**
* 10**** : 系统认证 或 通用错误提示 20**** : screen module
*/
ERROR(999, "error"), // 通用错误/未知错误
SYS_USER_PWD_ERROR(100001, "username or password error"), // 用户名 或密码错误
SYS_DUPLICATE_RECORD(100002, "duplicate record"), // 数据库中已存在该记录
SYS_NO_AUTH(100003, "Permission denied"), // 没有权限
ID_CANNOT_EMPTY(100004, "id cannot be empty"), // ID 不能为空
NAME_CANNOT_EMPTY(100005, "name cannot be empty"), // name 不能为空
PARAM_CANNOT_EMPTY(100006, "parameter cannot be empty"), // parameter 不能为空
USER_NO_LOGIN(100007, "user not login"), // 用户未登录
SYS_RECORD_NOT_FOUND(100008, "record not found"),// 未找到记录
SCREEN_ID_CANNOT_EMPTY(200001, "id cannot be empty"),
SCREE_DATASOURCE_DEFAULT_CANNOT_BE_DELETE(300001,"The default data source cannot be deleted."),
SCREE_DATASOURCE_REPEAT(300002,"Screen datasource name duplicate"),
/**
* import
*/
EXCELFILE_TYPE_ERROR(400001, "The type can only be xlsx, json, csv"),
EXCELFILE_PARSE_ERROR(400002, "Import file resolution failed"),
EXCELFILE_HEADER_TEMPLATE_ERROR(400003,"The header row of the import template is inconsistent with the system template"),
EXCELFILE_HEADER_LANGUAGE_ERROR(400004, "Language must be en, zh or ru"),
EXCELFILE_IMPORT_FILE_ISNULL(400005, "Import file is null"),
EXCELFILE_HEADER_LANGUAGE_ISNULL(400006, "Language can not be empty"),
EXCELFILE_IMPORT_ERROR(400007, "File import error"),
EXCELFILE_SCHEDULE_TASK_IS_NULL(400008, "Schedule task can not be empty"),
EXCELFILE_SCHEDULE_CRON_IS_NULL(400009, "Schedule cron can not be empty"),
EXCELFILE_SCHEDULE_ENABLE_IS_NULL(400010, "Schedule enable can not be empty"),
EXCELFILE_SCHEDULE_SCRIPT_IS_NULL(400011, "Schedule script can not be empty"),
SUCCESS(200, "success"); // 成功
private RCode(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
private Integer code;
private String msg;
private Object[] param;
public RCode setParam(Object... param) {
this.param = param;
return this;
}
public Object[] getParam() {
return param;
}
public Integer getCode() {
return code;
}
public String getMsg() {
return MessageFormat.format(msg, param);
}
}

View File

@@ -0,0 +1,64 @@
package net.geedge.asw.common.util;
import java.io.IOException;
import org.springframework.http.MediaType;
import com.j256.simplemagic.ContentInfo;
import com.j256.simplemagic.ContentInfoUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import jakarta.servlet.http.HttpServletResponse;
public class ResponseUtil {
/**
* downloadFile
*
* @param response
* @param contentType
* @param filename
* @param data
* @throws IORuntimeException
* @throws IOException
*/
public static void downloadFile(HttpServletResponse response, String contentType, String filename, byte[] data) throws IORuntimeException, IOException {
String fileName = T.URLUtil.encode(filename, T.CharsetUtil.CHARSET_UTF_8);
ReflectUtil.invoke(response, "addHeader", "Content-Disposition", "attachment; filename=" + fileName);
ReflectUtil.invoke(response, "addHeader", "Content-Length", "" + data.length);
ReflectUtil.invoke(response, "setHeader", "Access-Control-Expose-Headers", "Content-Disposition");
ReflectUtil.invoke(response, "setContentType", StrUtil.emptyToDefault(contentType, MediaType.APPLICATION_OCTET_STREAM_VALUE));
T.IoUtil.write(response.getOutputStream(), false, data);
}
/**
* reponse 下载 byte数据
* @param response
* @param filename
* @param data
* @throws IORuntimeException
* @throws IOException
*/
public static void downloadFile(HttpServletResponse response, String filename, byte[] data)
throws IORuntimeException, IOException {
response.setContentType(ResponseUtil.getDownloadContentType(filename));
String fileName = T.URLUtil.encode(filename, T.CharsetUtil.CHARSET_UTF_8);
// response.addHeader("Content-Disposition", "attachment; filename=" + fileName);
// response.addHeader("Content-Length", "" + data.length);
// response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
ReflectUtil.invoke(response, "addHeader", "Content-Disposition", "attachment; filename=" + fileName);
ReflectUtil.invoke(response, "addHeader", "Content-Length", "" + data.length);
ReflectUtil.invoke(response, "setHeader", "Access-Control-Expose-Headers", "Content-Disposition");
T.IoUtil.write(response.getOutputStream(), false, data);
}
public static String getDownloadContentType(String filename){
ContentInfo extensionMatch = ContentInfoUtil.findExtensionMatch(filename);
return T.ObjectUtil.isEmpty(extensionMatch) ? MediaType.APPLICATION_OCTET_STREAM_VALUE : extensionMatch.getMimeType();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,426 @@
package net.geedge.asw.common.util;
import java.util.Collection;
import java.util.Map;
import cn.hutool.core.lang.Validator;
/**
* ValidateUtil
*/
public class VerifyUtil {
private Object value;
protected VerifyUtil(Object value) {
this.value = value;
}
/**
* 新建校验实例,传入目标对象
*
* @param value 校验对象
* @return ValidateUtils
*/
public static VerifyUtil is(Object value) {
return new VerifyUtil(value);
}
/**
* 切换目标对象,不重新创建实例
*
* @param value 校验对象
* @return ValidateUtils
*/
public VerifyUtil and(Object value) {
this.value = value;
return this;
}
/**
* 非空校验
*
* @return ValidateUtils
*/
public VerifyUtil notNull() {
return notNull(null);
}
/**
* 非空校验
*
* @param msg 错误信息
* @return ValidateUtils
*/
public VerifyUtil notNull(RCode code) {
if (T.ObjectUtil.isNull(value)) {
throw ASWException.builder().rcode(code).build();
}
return this;
}
/**
* 非空校验
*
* @return ValidateUtils
*/
public VerifyUtil notEmpty() {
return notEmpty(null);
}
/**
* 非空校验
*
* @param msg 错误信息
* @return ValidateUtils
*/
public VerifyUtil notEmpty(RCode code) {
notNull(code);
switch(value) {
case String v -> {
if(T.StrUtil.isEmpty(v)) {
throw ASWException.builder().rcode(code).build();
}
}
case Collection<?> v -> {
if(T.CollUtil.isEmpty(v)) {
throw ASWException.builder().rcode(code).build();
}
}
case Object[] v -> {
if(T.ArrayUtil.length(v) == 0) {
throw ASWException.builder().rcode(code).build();
}
}
default ->{}
}
return this;
}
public VerifyUtil equalsIgnoreCase(CharSequence sequence) {
return equalsIgnoreCase(sequence, null);
}
public VerifyUtil equalsIgnoreCase(CharSequence sequence, RCode code) {
if (!T.StrUtil.equalsIgnoreCase(sequence, T.StrUtil.toStringOrNull(value))) {
throw ASWException.builder().rcode(code).build();
}
return this;
}
/**
* 正则校验
*
* @param regex 正则表达式
* @return ValidateUtils
*/
public VerifyUtil regex(String regex) {
return regex(regex, null);
}
/**
* 正则校验
*
* @param regex 正则表达式
* @param msg 错误信息
* @return ValidateUtils
*/
public VerifyUtil regex(String regex, RCode code) {
if (!T.ReUtil.isMatch(regex, T.StrUtil.toStringOrNull(value))) {
throw ASWException.builder().rcode(code).build();
}
return this;
}
/**
* 最大值校验
*
* @param max 最大值
* @return ValidateUtils
*/
public VerifyUtil max(Number max) {
return max(max, null);
}
/**
* 最大值校验
*
* @param max 最大值
* @param msg 错误信息
* @return ValidateUtils
*/
public VerifyUtil max(Number max, RCode code) {
switch (value) {
case null -> {
}
case Integer v -> {
if (T.NumberUtil.compare(v, max.intValue()) > 0) {
throw ASWException.builder().rcode(code).build();
}
}
case Long v -> {
if (T.NumberUtil.compare(v, max.longValue()) > 0) {
throw ASWException.builder().rcode(code).build();
}
}
case Double v -> {
if (T.NumberUtil.compare(v, max.doubleValue()) > 0) {
throw ASWException.builder().rcode(code).build();
}
}
case Float v -> {
if (T.NumberUtil.compare(v, max.floatValue()) > 0) {
throw ASWException.builder().rcode(code).build();
}
}
case Short v -> {
if (T.NumberUtil.compare(v, max.shortValue()) > 0) {
throw ASWException.builder().rcode(code).build();
}
}
case Byte v -> {
if (T.NumberUtil.compare(v, max.byteValue()) > 0) {
throw ASWException.builder().rcode(code).build();
}
}
default -> {
}
}
return this;
}
/**
* 最小值校验
*
* @param min 最小值
* @return ValidateUtils
*/
public VerifyUtil min(Number min) {
return min(min, null);
}
/**
* 最小值校验
*
* @param min 最小值
* @param msg 错误信息
* @return ValidateUtils
*/
public VerifyUtil min(Number min, RCode code) {
switch (value) {
case null -> {
}
case Integer v -> {
if (T.NumberUtil.compare(min.intValue(), v) > 0) {
throw ASWException.builder().rcode(code).build();
}
}
case Long v -> {
if (T.NumberUtil.compare(min.longValue(), v) > 0) {
throw ASWException.builder().rcode(code).build();
}
}
case Double v -> {
if (T.NumberUtil.compare(min.doubleValue(), v) > 0) {
throw ASWException.builder().rcode(code).build();
}
}
case Float v -> {
if (T.NumberUtil.compare(min.floatValue(), v) > 0) {
throw ASWException.builder().rcode(code).build();
}
}
case Short v -> {
if (T.NumberUtil.compare(min.shortValue(), v) > 0) {
throw ASWException.builder().rcode(code).build();
}
}
case Byte v -> {
if (T.NumberUtil.compare(min.byteValue(), v) > 0) {
throw ASWException.builder().rcode(code).build();
}
}
default -> {
}
}
return this;
}
/**
* 最大长度校验
*
* @param max 最大长度
* @return ValidateUtils
*/
public VerifyUtil maxLength(int max) {
return maxLength(max, null);
}
/**
* 最大长度校验
*
* @param max 最大长度
* @param msg 错误信息
* @return ValidateUtils
*/
public VerifyUtil maxLength(int max, RCode code) {
switch (value) {
case null -> {
}
case String v -> {
if (T.StrUtil.length(v) > max) {
throw ASWException.builder().rcode(code).build();
}
}
case Collection v -> {
if (T.CollectionUtil.size(v) > max) {
throw ASWException.builder().rcode(code).build();
}
}
case Object[] v -> {
if (T.ArrayUtil.length(v) > max) {
throw ASWException.builder().rcode(code).build();
}
}
default -> {
}
}
return this;
}
/**
* 最小长度校验
*
* @param min 最小长度
* @return ValidateUtils
*/
public VerifyUtil minLength(int min) {
return minLength(min, null);
}
/**
* 最小长度校验
*
* @param min 最小长度
* @param msg 错误信息
* @return ValidateUtils
*/
public VerifyUtil minLength(int min, RCode code) {
switch (value) {
case null -> {
}
case String v -> {
if (T.StrUtil.length(v) < min) {
throw ASWException.builder().rcode(code).build();
}
}
case Collection v -> {
if (T.CollectionUtil.size(v) < min) {
throw ASWException.builder().rcode(code).build();
}
}
case Object[] v -> {
if (T.ArrayUtil.length(v) < min) {
throw ASWException.builder().rcode(code).build();
}
}
default -> {
}
}
return this;
}
/**
* 自定义日期格式校验
*
* @param format 格式
* @return ValidateUtils
*/
public VerifyUtil date(String format) {
return date(format, null);
}
/**
* 自定义日期格式校验
*
* @param format 格式
* @param msg 错误信息
* @return ValidateUtils
*/
public VerifyUtil date(String format, RCode code) {
try {
T.DateUtil.parse(T.StrUtil.toStringOrNull(value), format);
} catch (Exception e) {
throw ASWException.builder().rcode(code).build();
}
return this;
}
/**
* IP地址校验
*
* @return ValidateUtils
*/
public VerifyUtil ip() {
return ip(null);
}
/**
* IP地址校验
*
* @param msg 错误信息
* @return ValidateUtils
*/
public VerifyUtil ip(RCode code) {
if (!Validator.isIpv4(T.StrUtil.toStringOrNull(value)) && !Validator.isIpv6(T.StrUtil.toStringOrNull(value))) {
throw ASWException.builder().rcode(code).build();
}
return this;
}
public VerifyUtil port(RCode code) {
if (!T.NetUtil.isValidPort(T.NumberUtil.parseInt(T.StrUtil.toStringOrNull(value)))) {
throw ASWException.builder().rcode(code).build();
}
return this;
}
/**
* 多参数校验,不能同时为空
*
* @param code
* @param validates
*/
public static VerifyUtil notAllNull(RCode code, Object... validates) {
boolean allNull = true;
for (Object validate : validates) {
if (validate != null) {
if (validate instanceof String) {
if (T.ObjectUtil.isNull((String) validate))
continue;
} else if (validate instanceof Number) {
if (((Number) validate) == null)
continue;
} else if (validate instanceof Collection) {
if (T.ObjectUtil.isNull((Collection) validate))
continue;
} else if (validate instanceof Map) {
if (T.ObjectUtil.isNull((Map) validate))
continue;
} else if (validate instanceof Object[]) {
if (T.ObjectUtil.isNull((Object[]) validate))
continue;
}
allNull = false;
break;
}
}
if (allNull) {
throw new ASWException(code);
}
return new VerifyUtil(validates);
}
}

View File

@@ -0,0 +1,110 @@
package net.geedge.asw.module.file.controller;
import java.io.IOException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import cn.hutool.log.Log;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import net.geedge.asw.common.util.ASWException;
import net.geedge.asw.common.util.R;
import net.geedge.asw.common.util.RCode;
import net.geedge.asw.common.util.T;
import net.geedge.asw.module.file.entity.FileContentEntity;
import net.geedge.asw.module.file.entity.FileEntity;
import net.geedge.asw.module.file.service.IFileService;
import net.geedge.asw.module.sys.controller.BaseController;
@RestController
@RequestMapping("/file")
public class FileController extends BaseController {
private static final Log log = Log.get();
@Autowired
private IFileService fileService;
// 单位: s默认1天
@Value("${file.expires:86400}")
private Integer fileExpires;
@GetMapping("/{id}")
public R detail(@PathVariable("id") String id) {
FileEntity entity = fileService.getById(id);
return R.ok().putData("record", entity);
}
@GetMapping
public R list(String q, @RequestParam(defaultValue = "1") Integer current,
@RequestParam(defaultValue = "20") Integer size, @RequestParam(defaultValue = "name") String orderBy) {
QueryWrapper<FileEntity> queryWrapper = new QueryWrapper<FileEntity>();
queryWrapper.and(T.StrUtil.isNotBlank(q), wrapper -> {
wrapper.like("name", q).or().like("remark", q).or().like("path", q);
});
Page<FileEntity> page = Page.of(current, size);
page.addOrder(T.PageUtil.decodeOrderByStr(orderBy));
page = fileService.page(page, queryWrapper);
page.getRecords().forEach(entity -> {
entity.setUri(T.StrUtil.concat(true, "/file/content/", entity.getId(), ".", entity.getSuffix()));
});
return R.ok(page);
}
@PostMapping
public R add(String remark, @RequestParam(value = "file", required = false) MultipartFile file) throws IOException {
if (T.ObjectUtil.isNull(file)) {
throw ASWException.builder().rcode(RCode.PARAM_CANNOT_EMPTY).build();
}
FileEntity savedFile = fileService.saveFile(remark, file);
return R.ok().putData("record", savedFile);
}
@DeleteMapping
public R delete(String[] ids) {
T.VerifyUtil.is(ids).notEmpty();
fileService.deleteFile(ids);
log.info("delete files, ids: {}", T.ArrayUtil.toString(ids));
return R.ok();
}
/**
* 下载文件内容
*
* @param name name = id + suffix , eq: adfadfadf.txt
* @param request
* @param response
* @throws IOException
*/
@GetMapping("/content/{name}")
public void download(@PathVariable("name") String name, HttpServletRequest request, HttpServletResponse response)
throws IOException {
String id = T.FileNameUtil.getPrefix(name);
FileEntity fileEntity = fileService.getById(id);
if (T.ObjectUtil.isNull(fileEntity)) {
response.sendError(HttpStatus.NOT_FOUND.value(), HttpStatus.NOT_FOUND.getReasonPhrase());
} else {
FileContentEntity fileContentEntity = fileService.getFileContentEntityById(id);
response.setStatus(200);
response.setContentType(fileEntity.getContentType());
response.setContentLength(fileEntity.getSize());
response.setHeader("Content-disposition", "attachment; filename=" + fileEntity.getName());
// 设置缓存响应头
response.setDateHeader("Expires", T.DateUtil.current() + fileExpires * 1000);
response.setHeader("Cache-Control", "public, max-age=" + fileExpires);
response.getOutputStream().write(fileContentEntity.getContent());
response.flushBuffer();
}
}
}

View File

@@ -0,0 +1,12 @@
package net.geedge.asw.module.file.dao;
import org.apache.ibatis.annotations.Mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import net.geedge.asw.module.file.entity.FileContentEntity;
@Mapper
public interface FileContentDao extends BaseMapper<FileContentEntity> {
}

View File

@@ -0,0 +1,17 @@
package net.geedge.asw.module.file.dao;
import org.apache.ibatis.annotations.Mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import net.geedge.asw.module.file.entity.FileEntity;
@Mapper
public interface FileDao extends BaseMapper<FileEntity> {
// public void saveFileContent(FileContentEntity entity);
//
// public FileContentEntity getFileContentById(String id);
//
// public boolean deleteFileContentById(List<String> ids);
}

View File

@@ -0,0 +1,17 @@
package net.geedge.asw.module.file.entity;
import org.apache.ibatis.type.JdbcType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
@Data
@TableName("sys_file_content")
public class FileContentEntity {
private String id;
@TableField(jdbcType = JdbcType.BLOB)
private byte[] content;
}

View File

@@ -0,0 +1,28 @@
package net.geedge.asw.module.file.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
@Data
@TableName("sys_file")
public class FileEntity {
/**
* 以文件md5作为id
*/
@TableId(type = IdType.NONE)
private String id;
private String name;
private String suffix;
private String contentType;
private Integer size;
private String path;
private String remark;
private Long createTimestamp;
@TableField(exist = false)
private String uri;
}

View File

@@ -0,0 +1,42 @@
package net.geedge.asw.module.file.service;
import java.io.File;
import java.io.IOException;
import org.springframework.web.multipart.MultipartFile;
import com.baomidou.mybatisplus.extension.service.IService;
import net.geedge.asw.module.file.entity.FileContentEntity;
import net.geedge.asw.module.file.entity.FileEntity;
public interface IFileService extends IService<FileEntity> {
/**
* 保存文件信息及内容
*
* @param remark
* @param file
* @return
* @throws IOException
*/
FileEntity saveFile(String remark, MultipartFile file) throws IOException;
/**
* 根据文件 id 获取内容
*
* @param id
* @return
*/
public FileContentEntity getFileContentEntityById(String id);
/**
* 删除文件和文件内容
*
* @param ids
*/
void deleteFile(String[] ids);
public void saveFileAndContent(FileEntity entity, File file) throws IOException;
}

View File

@@ -0,0 +1,90 @@
package net.geedge.asw.module.file.service.impl;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.http.MediaTypeFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import net.geedge.asw.common.util.T;
import net.geedge.asw.module.file.dao.FileContentDao;
import net.geedge.asw.module.file.dao.FileDao;
import net.geedge.asw.module.file.entity.FileContentEntity;
import net.geedge.asw.module.file.entity.FileEntity;
import net.geedge.asw.module.file.service.IFileService;
@Service
public class FileServiceImpl extends ServiceImpl<FileDao, FileEntity> implements IFileService {
@Autowired
private FileContentDao fileContentDao;
@Override
@Transactional
public FileEntity saveFile(String remark, MultipartFile file) throws IOException {
String md5 = T.DigestUtil.md5Hex(file.getBytes());
// 根据md5判断是否已经存在存在直接响应
FileEntity entity = this.getOne(new QueryWrapper<FileEntity>().lambda().eq(FileEntity::getId, md5));
if (T.ObjectUtil.isNull(entity)) {
entity = new FileEntity();
entity.setId(md5);
entity.setSize((int) file.getSize());
entity.setName(file.getOriginalFilename());
entity.setSuffix(T.FileNameUtil.getSuffix(entity.getName()));
entity.setCreateTimestamp(T.DateUtil.current());
Optional<MediaType> mediaType = MediaTypeFactory.getMediaType(entity.getName());
if (mediaType.isPresent()) {
entity.setContentType(mediaType.get().toString());
} else {
entity.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
}
entity.setRemark(remark);
this.saveFileAndContent(entity, file);
}
entity.setUri(T.StrUtil.concat(true, "/file/content/", entity.getId(), ".", entity.getSuffix()));
return entity;
}
public void saveFileAndContent(FileEntity entity, MultipartFile file) throws IOException {
// 默认统一保存到数据库中
entity.setPath(T.StrUtil.concat(true, "db:", entity.getId()));
this.save(entity);
FileContentEntity content = new FileContentEntity();
content.setId(entity.getId());
content.setContent(file.getBytes());
fileContentDao.insert(content);
}
@Override
public void saveFileAndContent(FileEntity entity, File file) throws IOException {
// 默认统一保存到数据库中
entity.setPath(T.StrUtil.concat(true, "db:", entity.getId()));
this.save(entity);
FileContentEntity content = new FileContentEntity();
content.setId(entity.getId());
content.setContent(T.FileUtil.readBytes(file));
fileContentDao.insert(content);
}
@Override
@Transactional
public void deleteFile(String[] ids) {
List<String> list = T.ListUtil.of(ids);
this.removeByIds(list);
fileContentDao.deleteBatchIds(list);
}
@Override
public FileContentEntity getFileContentEntityById(String id) {
return fileContentDao.selectById(id);
}
}

View File

@@ -0,0 +1,11 @@
package net.geedge.asw.module.sys.controller;
import cn.dev33.satoken.stp.StpUtil;
public abstract class BaseController {
protected String getSysUserId() {
return StpUtil.getLoginIdAsString();
}
}

View File

@@ -0,0 +1,50 @@
package net.geedge.asw.module.sys.controller;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import cn.dev33.satoken.stp.SaTokenInfo;
import cn.dev33.satoken.stp.StpUtil;
import net.geedge.asw.common.util.R;
import net.geedge.asw.common.util.RCode;
import net.geedge.asw.common.util.T;
import net.geedge.asw.module.sys.entity.SysUserEntity;
import net.geedge.asw.module.sys.service.ISysAuthService;
@RestController
@RequestMapping("/sys")
public class SysAuthController {
@Autowired
private ISysAuthService authService;
record AuthRecord(String userName, String pwd) {}
@PostMapping("/login")
public R login(@RequestBody AuthRecord record) {
T.VerifyUtil.is(record.userName()).notEmpty(RCode.SYS_USER_PWD_ERROR).and(record.pwd())
.notEmpty(RCode.SYS_USER_PWD_ERROR);
SysUserEntity userEntity = authService.login(record.userName(), record.pwd());
SaTokenInfo tokenInfo = StpUtil.getTokenInfo();
userEntity.setPwd(null);
return R.ok().putData("tokenInfo", tokenInfo).putData("user", userEntity);
}
@RequestMapping("/logout")
public R logout() {
authService.logout();
return R.ok();
}
@GetMapping("/permissions")
public R permissions() {
Map<String, Object> permissions = authService.userPermissions();
return R.ok(permissions);
}
}

View File

@@ -0,0 +1,78 @@
package net.geedge.asw.module.sys.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import cn.hutool.log.Log;
import net.geedge.asw.common.util.ASWException;
import net.geedge.asw.common.util.R;
import net.geedge.asw.common.util.RCode;
import net.geedge.asw.common.util.T;
import net.geedge.asw.module.sys.entity.SysRoleEntity;
import net.geedge.asw.module.sys.service.ISysRoleService;
@RestController
@RequestMapping("/sys/role")
public class SysRoleController {
private static final Log log = Log.get();
@Autowired
private ISysRoleService roleService;
@GetMapping("/{id}")
public R detail(@PathVariable("id") String id) {
SysRoleEntity entity = roleService.getById(id);
return R.ok().putData("record", entity);
}
@GetMapping
public R list(String ids, String name, @RequestParam(defaultValue = "1") Integer current,
@RequestParam(defaultValue = "20") Integer size, @RequestParam(defaultValue = "name") String orderBy) {
QueryWrapper<SysRoleEntity> queryWrapper = new QueryWrapper<SysRoleEntity>();
queryWrapper.like(T.StrUtil.isNotBlank(name), "name", name).in(T.StrUtil.isNotBlank(ids), "id", ids.split(","));
Page<SysRoleEntity> page = Page.of(current, size);
page.addOrder(T.PageUtil.decodeOrderByStr(orderBy));
page = roleService.page(page, queryWrapper);
return R.ok(page);
}
@PostMapping
public R add(@RequestBody SysRoleEntity entity) {
T.VerifyUtil.is(entity).notNull().and(entity.getName()).notEmpty(RCode.NAME_CANNOT_EMPTY);
SysRoleEntity one = roleService.getOne(new QueryWrapper<SysRoleEntity>().lambda().eq(SysRoleEntity::getName, entity.getName()));
if (T.ObjectUtil.isNotNull(one)) {
throw ASWException.builder().rcode(RCode.SYS_DUPLICATE_RECORD).build();
}
entity.setCreateTimestamp(T.DateUtil.current());
roleService.saveOrUpdateRole(entity);
return R.ok().putData("id", entity.getId());
}
@PutMapping
public R update(@RequestBody SysRoleEntity entity) {
T.VerifyUtil.is(entity).notNull().and(entity.getId()).notEmpty(RCode.ID_CANNOT_EMPTY);
entity.setCreateTimestamp(null);
roleService.saveOrUpdateRole(entity);
return R.ok().putData("id", entity.getId());
}
@DeleteMapping
public R delete(String[] ids) {
T.VerifyUtil.is(ids).notEmpty();
roleService.removeBatchByIds(T.ListUtil.of(ids));
log.info("delete Role, ids: {}", T.ArrayUtil.toString(ids));
return R.ok();
}
}

View File

@@ -0,0 +1,103 @@
package net.geedge.asw.module.sys.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import cn.hutool.log.Log;
import net.geedge.asw.common.util.ASWException;
import net.geedge.asw.common.util.Constants;
import net.geedge.asw.common.util.R;
import net.geedge.asw.common.util.RCode;
import net.geedge.asw.common.util.T;
import net.geedge.asw.module.sys.entity.SysRoleEntity;
import net.geedge.asw.module.sys.entity.SysUserEntity;
import net.geedge.asw.module.sys.service.ISysRoleService;
import net.geedge.asw.module.sys.service.ISysUserService;
@RestController
@RequestMapping("/sys/user")
public class SysUserController {
private static final Log log = Log.get();
@Autowired
private ISysUserService userService;
@Autowired
private ISysRoleService roleService;
@GetMapping("/{id}")
public R detail(@PathVariable("id") String id) {
SysUserEntity entity = userService.getById(id);
entity.setPwd(null);
List<SysRoleEntity> roleList = roleService.listByIds(T.ListUtil.of(entity.getRoleIds()));
entity.setRoles(roleList);
return R.ok().putData("record", entity);
}
@GetMapping
public R list(String name, @RequestParam(defaultValue = "1") Integer current,
@RequestParam(defaultValue = "20") Integer size, @RequestParam(defaultValue = "name") String orderBy) {
QueryWrapper<SysUserEntity> queryWrapper = new QueryWrapper<SysUserEntity>();
// 不查询 pwd 列
queryWrapper.select(SysUserEntity.class, entity -> !entity.getColumn().equals("pwd"));
if (T.StrUtil.isNotBlank(name)) {
queryWrapper.and(wrapper -> wrapper.like("name", name).or().like("user_name", name));
}
Page<SysUserEntity> page = Page.of(current, size);
page.addOrder(T.PageUtil.decodeOrderByStr(orderBy));
page = userService.page(page, queryWrapper);
return R.ok(page);
}
@PostMapping
public R add(@RequestBody SysUserEntity entity) {
T.VerifyUtil.is(entity).notNull().and(entity.getName()).notEmpty(RCode.NAME_CANNOT_EMPTY)
.and(entity.getUserName()).notEmpty().and(entity.getPwd()).notEmpty();
SysUserEntity one = userService.getOne(
new QueryWrapper<SysUserEntity>().lambda().eq(SysUserEntity::getUserName, entity.getUserName()));
if (T.ObjectUtil.isNotNull(one)) {
throw ASWException.builder().rcode(RCode.SYS_DUPLICATE_RECORD).build();
}
// 密码加密
entity.setPwd(T.AesUtil.encrypt(entity.getPwd(), Constants.AES_KEY));
entity.setCreateTimestamp(T.DateUtil.current());
userService.saveOrUpdateUser(entity);
return R.ok().putData("id", entity.getId());
}
@PutMapping
public R update(@RequestBody SysUserEntity entity) {
T.VerifyUtil.is(entity).notNull().and(entity.getId()).notEmpty(RCode.ID_CANNOT_EMPTY);
if (T.StrUtil.isNotBlank(entity.getPwd())) {
// 密码加密
entity.setPwd(T.AesUtil.encrypt(entity.getPwd(), Constants.AES_KEY));
} else {
entity.setPwd(null);
}
entity.setUserName(null);// username 不允许修改
entity.setCreateTimestamp(null);
userService.saveOrUpdateUser(entity);
return R.ok().putData("id", entity.getId());
}
@DeleteMapping
public R delete(String[] ids) {
T.VerifyUtil.is(ids).notEmpty();
userService.removeBatchByIds(T.ListUtil.of(ids));
log.info("delete user, ids: {}", T.ArrayUtil.toString(ids));
return R.ok();
}
}

View File

@@ -0,0 +1,12 @@
package net.geedge.asw.module.sys.dao;
import org.apache.ibatis.annotations.Mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import net.geedge.asw.module.sys.entity.SysConfigEntity;
@Mapper
public interface SysConfigDao extends BaseMapper<SysConfigEntity>{
}

View File

@@ -0,0 +1,21 @@
package net.geedge.asw.module.sys.dao;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import net.geedge.asw.module.sys.entity.SysMenuEntity;
import net.geedge.asw.module.sys.entity.SysRoleEntity;
@Mapper
public interface SysRoleDao extends BaseMapper<SysRoleEntity> {
@Select("select sr.* from sys_role sr left join sys_user_role sur on sr.id = sur.role_id where sur.user_id = #{userId}")
public List<SysRoleEntity> findRoleByUserId(String userId);
@Select("select sm.* from sys_menu sm LEFT JOIN sys_role_menu srm on sm.id = srm.menu_id LEFT JOIN sys_user_role sur on srm.role_id = sur.role_id where sur.user_id = #{userId} and sm.state = 1 order by sm.order")
public List<SysMenuEntity> findMenuByUserId(String userId);
}

View File

@@ -0,0 +1,12 @@
package net.geedge.asw.module.sys.dao;
import org.apache.ibatis.annotations.Mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import net.geedge.asw.module.sys.entity.SysRoleMenuEntity;
@Mapper
public interface SysRoleMenuDao extends BaseMapper<SysRoleMenuEntity>{
}

View File

@@ -0,0 +1,12 @@
package net.geedge.asw.module.sys.dao;
import org.apache.ibatis.annotations.Mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import net.geedge.asw.module.sys.entity.SysUserEntity;
@Mapper
public interface SysUserDao extends BaseMapper<SysUserEntity>{
}

View File

@@ -0,0 +1,12 @@
package net.geedge.asw.module.sys.dao;
import org.apache.ibatis.annotations.Mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import net.geedge.asw.module.sys.entity.SysUserRoleEntity;
@Mapper
public interface SysUserRoleDao extends BaseMapper<SysUserRoleEntity>{
}

View File

@@ -0,0 +1,45 @@
package net.geedge.asw.module.sys.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Builder;
import lombok.Data;
import net.geedge.asw.common.util.T;
@Data
@Builder
@TableName("sys_config")
public class SysConfigEntity {
@TableId(type = IdType.INPUT)
private String paramKey;
private String paramValue;
private Integer status;
private String remark;
public enum KeyEnum {
SCREEN_PUBLISH_NUM("screen_publish_num");
private String key;
KeyEnum(String key) {
this.key = key;
}
public String getKey() {
return this.key;
}
}
public static void main(String[] args) {
System.out.println(SysConfigEntity.KeyEnum.SCREEN_PUBLISH_NUM.getKey());
}
public Integer getParamValueAsInt() {
return T.NumberUtil.parseInt(this.paramValue);
}
public Long getParamValueAsLong() {
return T.NumberUtil.parseLong(this.paramValue);
}
}

View File

@@ -0,0 +1,30 @@
package net.geedge.asw.module.sys.entity;
import java.util.List;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
@Data
@TableName("sys_menu")
public class SysMenuEntity {
@TableId(type = IdType.ASSIGN_UUID)
private String id;
private String name;
private String i18n;
private String pid;
private String type;
private String perms;
private String route;
private String icon;
private Integer order;
private Integer state;
private Long createTimestamp;
@TableField(exist = false)
private List<SysMenuEntity> children;
}

View File

@@ -0,0 +1,23 @@
package net.geedge.asw.module.sys.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
@Data
@TableName("sys_role")
public class SysRoleEntity {
@TableId(type = IdType.ASSIGN_UUID)
private String id;
private String name;
private String i18n;
private String remark;
private Integer buildIn;
@TableField(exist = false)
private String[] menuIds;
private Long createTimestamp;
}

View File

@@ -0,0 +1,15 @@
package net.geedge.asw.module.sys.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
@TableName("sys_role_menu")
public class SysRoleMenuEntity {
private String roleId;
private String menuId;
}

View File

@@ -0,0 +1,27 @@
package net.geedge.asw.module.sys.entity;
import java.util.List;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
@Data
@TableName("sys_user")
public class SysUserEntity {
@TableId(type = IdType.ASSIGN_UUID)
private String id;
private String name;
@TableField("user_name")
private String userName;
private String pwd;
@TableField(exist = false)
private String[] roleIds;
@TableField(exist = false)
private List<SysRoleEntity> roles;
private Long createTimestamp;
}

View File

@@ -0,0 +1,15 @@
package net.geedge.asw.module.sys.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
@TableName("sys_user_role")
public class SysUserRoleEntity {
private String userId;
private String roleId;
}

View File

@@ -0,0 +1,15 @@
package net.geedge.asw.module.sys.service;
import java.util.Map;
import net.geedge.asw.module.sys.entity.SysUserEntity;
public interface ISysAuthService{
public SysUserEntity login(String userName, String pwd);
public void logout();
public Map<String, Object> userPermissions();
}

View File

@@ -0,0 +1,9 @@
package net.geedge.asw.module.sys.service;
import com.baomidou.mybatisplus.extension.service.IService;
import net.geedge.asw.module.sys.entity.SysConfigEntity;
public interface ISysConfigService extends IService<SysConfigEntity> {
}

View File

@@ -0,0 +1,9 @@
package net.geedge.asw.module.sys.service;
import com.baomidou.mybatisplus.extension.service.IService;
import net.geedge.asw.module.sys.entity.SysRoleMenuEntity;
public interface ISysRoleMenuService extends IService<SysRoleMenuEntity> {
}

View File

@@ -0,0 +1,10 @@
package net.geedge.asw.module.sys.service;
import com.baomidou.mybatisplus.extension.service.IService;
import net.geedge.asw.module.sys.entity.SysRoleEntity;
public interface ISysRoleService extends IService<SysRoleEntity>{
public void saveOrUpdateRole(SysRoleEntity entity);
}

View File

@@ -0,0 +1,9 @@
package net.geedge.asw.module.sys.service;
import com.baomidou.mybatisplus.extension.service.IService;
import net.geedge.asw.module.sys.entity.SysUserRoleEntity;
public interface ISysUserRoleService extends IService<SysUserRoleEntity> {
}

View File

@@ -0,0 +1,10 @@
package net.geedge.asw.module.sys.service;
import com.baomidou.mybatisplus.extension.service.IService;
import net.geedge.asw.module.sys.entity.SysUserEntity;
public interface ISysUserService extends IService<SysUserEntity>{
public void saveOrUpdateUser(SysUserEntity entity);
}

View File

@@ -0,0 +1,81 @@
package net.geedge.asw.module.sys.service.impl;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.log.Log;
import net.geedge.asw.common.util.ASWException;
import net.geedge.asw.common.util.Constants;
import net.geedge.asw.common.util.RCode;
import net.geedge.asw.common.util.T;
import net.geedge.asw.module.sys.dao.SysRoleDao;
import net.geedge.asw.module.sys.dao.SysUserDao;
import net.geedge.asw.module.sys.entity.SysMenuEntity;
import net.geedge.asw.module.sys.entity.SysRoleEntity;
import net.geedge.asw.module.sys.entity.SysUserEntity;
import net.geedge.asw.module.sys.service.ISysAuthService;
@Service
public class SysAuthServiceImpl implements ISysAuthService {
private static final Log log = Log.get();
@Autowired
private SysUserDao userDao;
@Autowired
private SysRoleDao roleDao;
@Override
public SysUserEntity login(String userName, String pwd) {
SysUserEntity userEntity = userDao
.selectOne(new QueryWrapper<SysUserEntity>().lambda().eq(SysUserEntity::getUserName, userName));
if (T.ObjectUtil.isNull(userEntity)
|| !T.StrUtil.equals(userEntity.getPwd(), T.AesUtil.encrypt(pwd, Constants.AES_KEY))) {
log.warn("user login error, username: {}", userName);
throw ASWException.builder().rcode(RCode.SYS_USER_PWD_ERROR).build();
}
StpUtil.login(userEntity.getId());
log.info("user login success, userName: {}", userName);
return userEntity;
}
@Override
public void logout() {
StpUtil.logout();
}
/**
* 获取登录用户权限
*/
@Override
public Map<String, Object> userPermissions() {
Map<String, Object> result = T.MapUtil.newHashMap();
String userId = StpUtil.getLoginIdAsString();
List<SysRoleEntity> roleList = roleDao.findRoleByUserId(userId);
result.put("roles", roleList);
// 组织 menu数据
List<SysMenuEntity> menuList = roleDao.findMenuByUserId(userId);
List<String> buttonList = menuList.stream().filter(menu -> T.StrUtil.equalsIgnoreCase(menu.getType(), "button"))
.map(menu -> menu.getName()).collect(Collectors.toList());
result.put("buttons", buttonList);
//生成 menu tree结构
Map<String, List<SysMenuEntity>> groupMap = menuList.stream()
.filter(menu -> T.StrUtil.equalsIgnoreCase(menu.getType(), "menu"))
.collect(Collectors.groupingBy(SysMenuEntity::getPid));
menuList.forEach(menu -> {
menu.setChildren(groupMap.get(menu.getId()));
});
List<SysMenuEntity> collect = menuList.stream().filter(menu -> T.StrUtil.isBlank(menu.getPid()))
.collect(Collectors.toList());
result.put("menus", collect);
return result;
}
}

View File

@@ -0,0 +1,14 @@
package net.geedge.asw.module.sys.service.impl;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import net.geedge.asw.module.sys.dao.SysConfigDao;
import net.geedge.asw.module.sys.entity.SysConfigEntity;
import net.geedge.asw.module.sys.service.ISysConfigService;
@Service
public class SysConfigServiceImpl extends ServiceImpl<SysConfigDao, SysConfigEntity> implements ISysConfigService {
}

View File

@@ -0,0 +1,15 @@
package net.geedge.asw.module.sys.service.impl;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import net.geedge.asw.module.sys.dao.SysRoleMenuDao;
import net.geedge.asw.module.sys.entity.SysRoleMenuEntity;
import net.geedge.asw.module.sys.service.ISysRoleMenuService;
@Service
public class SysRoleMenuServiceImpl extends ServiceImpl<SysRoleMenuDao, SysRoleMenuEntity>
implements ISysRoleMenuService {
}

View File

@@ -0,0 +1,43 @@
package net.geedge.asw.module.sys.service.impl;
import java.util.List;
import java.util.stream.Stream;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import net.geedge.asw.common.util.T;
import net.geedge.asw.module.sys.dao.SysRoleDao;
import net.geedge.asw.module.sys.entity.SysRoleEntity;
import net.geedge.asw.module.sys.entity.SysRoleMenuEntity;
import net.geedge.asw.module.sys.service.ISysRoleMenuService;
import net.geedge.asw.module.sys.service.ISysRoleService;
@Service
public class SysRoleServiceImpl extends ServiceImpl<SysRoleDao, SysRoleEntity> implements ISysRoleService {
@Autowired
private ISysRoleMenuService roleMenuService;
@Override
@Transactional
public void saveOrUpdateRole(SysRoleEntity entity) {
// 删除 sys_role_menu 关联数据
if (T.StrUtil.isBlank(entity.getId())) {
roleMenuService.remove(new QueryWrapper<SysRoleMenuEntity>().eq("role_id", entity.getId()));
}
// 保存 role
this.saveOrUpdate(entity);
// 保存 role 和 menu关联关系
List<SysRoleMenuEntity> rmList = T.ListUtil.list(false);
Stream.of(entity.getMenuIds()).forEach(menuId -> {
rmList.add(SysRoleMenuEntity.builder().roleId(entity.getId()).menuId(menuId).build());
});
roleMenuService.saveBatch(rmList);
}
}

View File

@@ -0,0 +1,15 @@
package net.geedge.asw.module.sys.service.impl;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import net.geedge.asw.module.sys.dao.SysUserRoleDao;
import net.geedge.asw.module.sys.entity.SysUserRoleEntity;
import net.geedge.asw.module.sys.service.ISysUserRoleService;
@Service
public class SysUserRoleServiceImpl extends ServiceImpl<SysUserRoleDao, SysUserRoleEntity>
implements ISysUserRoleService {
}

View File

@@ -0,0 +1,41 @@
package net.geedge.asw.module.sys.service.impl;
import java.util.List;
import java.util.stream.Stream;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import net.geedge.asw.common.util.T;
import net.geedge.asw.module.sys.dao.SysUserDao;
import net.geedge.asw.module.sys.entity.SysUserEntity;
import net.geedge.asw.module.sys.entity.SysUserRoleEntity;
import net.geedge.asw.module.sys.service.ISysUserService;
@Service
public class SysUserServiceImpl extends ServiceImpl<SysUserDao, SysUserEntity> implements ISysUserService {
@Autowired
private SysUserRoleServiceImpl userRoleService;
@Override
@Transactional
public void saveOrUpdateUser(SysUserEntity entity) {
if (T.StrUtil.isNotBlank(entity.getId())) {
userRoleService.remove(new QueryWrapper<SysUserRoleEntity>().eq("user_id", entity.getId()));
}
// 保存 user
this.saveOrUpdate(entity);
// 保存 user role关系
List<SysUserRoleEntity> urList = T.ListUtil.list(false);
Stream.of(entity.getRoleIds()).forEach(roleId -> {
urList.add(SysUserRoleEntity.builder().roleId(roleId).userId(entity.getId()).build());
});
userRoleService.saveBatch(urList);
}
}

View File

@@ -0,0 +1,77 @@
magic-api:
web: /magic/web # UI请求的界面以及UI服务地址
resource: #配置存储方式
type: database # 配置存储在数据库中
tableName: magic_api_file # 数据库中的表名
# datasource: magic #指定数据源(单数据源时无需配置,多数据源时默认使用主数据源,如果存在其他数据源中需要指定。)
# prefix: /magic-api # key前缀
readonly: false # 是否是只读模式
prefix: / # 接口前缀,可以不配置
auto-import-module: db,http,log # 自动导入的模块
auto-import-package: java.lang.*,java.util.*,net.geedge.digitalhorizon.common.util.T #自动导包
allow-override: false #禁止覆盖应用接口
sql-column-case: camel #启用驼峰命名转换
editor-config: classpath:./static/magic-editor.js #编辑器配置
support-cross-domain: true # 跨域支持,默认开启
# secret-key: 123456789 # 远程推送时的秘钥,未配置则不开启推送
push-path: /_magic-api-sync #远程推送的路径,默认为/_magic-api-sync
show-sql: true #配置打印SQL
compile-cache-size: 500 #配置编译缓存容量
persistence-response-body: true #是否持久化保存ResponseBody
date-pattern: # 配置请求参数支持的日期格式
- yyyy-MM-dd
- yyyy-MM-dd HH:mm:ss
- yyyyMMddHHmmss
- yyyyMMdd
response: |- #配置JSON格式格式为magic-script中的表达式
{
code: code,
message: message,
data,
timestamp,
requestTime,
executeTime,
}
response-code:
success: 200 #执行成功的code值
invalid: 400 #参数验证未通过的code值
exception: 500 #执行出现异常的code值
banner: false # 打印banner
thread-pool-executor-size: 8 # async语句的线程池大小
throw-exception: false #执行出错时是否抛出异常
backup: #备份相关配置
enable: true #是否启用
max-history: -1 #备份保留天数,-1为永久保留
# datasource: magic #指定数据源(单数据源时无需配置,多数据源时默认使用主数据源,如果存在其他数据源中需要指定。)
table-name: magic_api_backup #使用数据库存储备份时的表名
crud: # CRUD相关配置
logic-delete-column: deleted #逻辑删除列
logic-delete-value: 1 #逻辑删除值
cache: # 缓存相关配置
capacity: 10000 #缓存容量
ttl: -1 # 永不过期
enable: true # 启用缓存
page:
size: size # 页大小的参数名称
page: current # 页码的参数名称
default-page: 1 # 未传页码时的默认首页
default-size: 20 # 未传页大小时的默认页大小
security: # 安全配置
username: admin # 登录用的用户名
password: 123456 # 登录用的密码
swagger:
version: 1.0
description: Digital Horizon API 接口信息
title: DH API Swagger Docs
name: DH API 接口
location: /v2/api-docs/dh-api/swagger2.json
debug:
timeout: 60 # 断点超时时间默认60s
task:
thread-name-prefix: dh-task- #线程池名字前缀
log: true # 打印日志
pool:
size: 8 #线程池大小默认值为CPU核心数
shutdown:
awaitTermination: false #关闭时是否等待任务执行完毕默认为false
awaitTerminationPeriod: 10s # 关闭时最多等待任务执行完毕的时间

View File

@@ -0,0 +1,61 @@
spring:
banner:
location: classpath:static/banner.txt
web:
resources:
static-locations: ./public/
cache:
cachecontrol:
no-cache: true
# max-age: 30d
# cache-public: true
profiles:
active: prod
include: magic-api
datasource:
driver-class-name: org.mariadb.jdbc.Driver
url: jdbc:mariadb://${database.host}:${database.port}/${database.name}?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8
username: ${database.user}
password: ENC(${database.pin})
flyway:
enabled: true # 是否开启
encoding: UTF-8 # 编码
sql-migration-prefix: V # 脚本文件的前缀默认为V
sql-migration-separator: __ # 双下划线
baseline-on-migrate: true # 连接数据库中存在表时设置为true
locations: classpath:db/migration # 脚本路径
clean-disabled: false # flyway 的 clean 命令会删除指定 schema 下的所有 table, 生产务必禁掉。这个默认值是 false 理论上作为默认配置是不科学的
validate-on-migrate: true # 执行迁移时是否自动调用验证 当你的 版本不符合逻辑 比如 你先执行了 DML 而没有 对应的DDL 会抛出异常
placeholder-replacement: false # 不做取值替换 默认替换为 ${} 初始化sql中有sql语句存在freemarker替换所以禁用此项
servlet:
multipart:
max-file-size: 200MB
max-request-size: 200MB
enabled: true
main:
allow-circular-references: true
server:
# port: 2023
servlet:
context-path: /
tomcat:
uri-encoding: UTF-8
max-threads: 1000
min-spare-threads: 5
connection-timeout: 60000
keep-alive-timeout: 600000
compression:
enabled: true
mime-types: text/xml,text/plain,text/css,text/javascript,application/javascript,application/json,application/xml
mybatis-plus:
mapper-locations: classpath:db/mapper/*Mapper.xml
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
banner: false
logging:
config: ./config/logback-spring.xml

View File

@@ -0,0 +1,10 @@
server.port=80
database.port=3306
#database.host=192.168.44.23
#database.name=dh-test-f
#database.user=nezha
database.host=127.0.0.1
database.name=asw
database.user=user
#database.pin=NTV/OauZb0eHc9tqzQzG0bbfmzH0VRILYhGbU2ssJ1fDr5gqJ+gd5ELnND3wZ6EB
database.pin=TPe6maTtzzu0zD0n0H+UTqug9nnb6l+eESqvILQ1dy3e1bEVZHTXfsKjqKp3vAXQ

View File

@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds">
<include resource="org/springframework/boot/logging/logback/defaults.xml" />
<logger name="org.springframework.web" level="info" />
<logger name="org.springboot.sample" level="warn" />
<logger name="org.apache" level="warn" />
<logger name="org.springframework" level="info" />
<logger name="druid.sql" level="info" />
<property name="log.path" value="/var/log/dh/" />
<!-- 输出格式 -->
<property name="out.pattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n" />
<!-- 活动文件的大小 -->
<property name="max.file.size" value="100MB"/>
<!-- 保留的归档文件的最大数量 -->
<property name="max.history" value="30"/>
<!-- 控制所有归档日志文件的总大小 -->
<property name="total.size.cap" value="10GB"/>
<!-- 2.2 level为 INFO 日志,时间滚动输出 -->
<appender name="LOG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 正在记录的日志文档的路径及文档名 -->
<file>${log.path}/dh-web.log</file>
<!--日志文档输出格式 -->
<encoder>
<pattern>${out.pattern}</pattern>
<charset>UTF-8</charset>
</encoder>
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${log.path}/dh-web-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxFileSize>${max.file.size}</maxFileSize>
<maxHistory>${max.history}</maxHistory>
<totalSizeCap>${total.size.cap}</totalSizeCap>
</rollingPolicy>
</appender>
<!-- 2.1 level为 ERROR 日志,时间滚动输出 -->
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 正在记录的日志文档的路径及文档名 -->
<file>${log.path}/dh-web-error.log</file>
<!--日志文档输出格式 -->
<encoder>
<pattern>${out.pattern}</pattern>
<charset>UTF-8</charset>
</encoder>
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${log.path}/dh-web-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxFileSize>${max.file.size}</maxFileSize>
<maxHistory>${max.history}</maxHistory>
<totalSizeCap>${total.size.cap}</totalSizeCap>
</rollingPolicy>
<!-- 此日志文档只记录debug级别的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>error</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<root level="INFO">
<appender-ref ref="LOG_FILE" />
<appender-ref ref="ERROR_FILE" />
</root>
</configuration>

View File

@@ -0,0 +1,136 @@
/**
* 1、新增 sys_user 表
* 2、添加默认用户 admin/admin
*/
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user` (
`id` varchar(64) NOT NULL,
`name` varchar(255) NOT NULL,
`user_name` varchar(255) NOT NULL,
`pwd` varchar(255) NOT NULL,
`create_timestamp` bigint(20) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_user_name` (`user_name`) USING BTREE,
KEY `idx_name` (`name`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO `sys_user`(`id`, `name`, `user_name`, `pwd`, `create_timestamp`) VALUES ('admin', 'admin', 'admin', 'ad9d757e620d5d9cd8e32c3dbcf37525', UNIX_TIMESTAMP(NOW())*1000);
/**
* 1、新增 sys_role 表
* 2、新增 sys_menu 表
* 3、新增 sys_user_role 表
* 4、新增 sys_role_menu 表
* 5、添加初始化数据
*/
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role` (
`id` varchar(64) NOT NULL,
`name` varchar(255) NOT NULL,
`i18n` varchar(255) NOT NULL,
`remark` varchar(255) NOT NULL,
`build_in` int(10) NOT NULL DEFAULT 0,
`create_timestamp` bigint(20) NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_name` (`name`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
DROP TABLE IF EXISTS `sys_menu`;
CREATE TABLE `sys_menu` (
`id` varchar(64) NOT NULL,
`name` varchar(255) NOT NULL,
`i18n` varchar(255) NOT NULL,
`pid` varchar(64) NOT NULL DEFAULT '',
`type` varchar(64) NOT NULL,
`perms` varchar(255) NOT NULL DEFAULT '',
`route` varchar(255) NOT NULL DEFAULT '',
`icon` varchar(255) NOT NULL DEFAULT '',
`order` int(10) NOT NULL DEFAULT 1,
`create_timestamp` bigint(20) NOT NULL,
`state` int(10) NOT NULL DEFAULT 1,
PRIMARY KEY (`id`),
KEY `idx_name` (`name`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
DROP TABLE IF EXISTS `sys_user_role`;
CREATE TABLE `sys_user_role` (
`user_id` varchar(64) NOT NULL,
`role_id` varchar(64) NOT NULL,
PRIMARY KEY (`user_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
DROP TABLE IF EXISTS `sys_role_menu`;
CREATE TABLE `sys_role_menu` (
`menu_id` varchar(64) NOT NULL,
`role_id` varchar(64) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 添加内置角色
INSERT INTO `sys_role` (`id`, `name`, `i18n`, `remark`, `build_in`, `create_timestamp`) VALUES ('admin', 'admin', 'admin', 'admin', 1, UNIX_TIMESTAMP(NOW())*1000);
/**
* 1、新增 sys_config 表
*/
DROP TABLE IF EXISTS `sys_config`;
CREATE TABLE `sys_config` (
`param_key` varchar(50) NOT NULL COMMENT 'key',
`param_value` text DEFAULT NULL,
`status` tinyint(4) DEFAULT 1 COMMENT '状态 0隐藏 1显示',
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`param_key`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统配置表';
/**
初始化 magic api 时区配置 ,默认 Asia/Shanghai
*/
INSERT INTO `sys_config` (`param_key`, `param_value`, `status`, `remark`) VALUES ( 'timezone', 'Asia/Shanghai', 1, '时区配置');
/**
* 新增 magic api file 表
*/
DROP TABLE IF EXISTS `magic_api_file`;
CREATE TABLE `magic_api_file` (
`file_path` varchar(512) NOT NULL,
`file_content` mediumtext,
PRIMARY KEY (`file_path`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
DROP TABLE IF EXISTS `magic_api_backup`;
CREATE TABLE `magic_api_backup` (
`id` varchar(32) NOT NULL COMMENT '原对象ID',
`create_date` bigint(13) NOT NULL COMMENT '备份时间',
`tag` varchar(32) DEFAULT NULL COMMENT '标签',
`type` varchar(32) DEFAULT NULL COMMENT '类型',
`name` varchar(64) DEFAULT NULL COMMENT '原名称',
`content` blob COMMENT '备份内容',
`create_by` varchar(64) DEFAULT NULL COMMENT '操作人',
PRIMARY KEY (`id`,`create_date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
/**
* 新增 sys_file sys_file_content 表
*/
DROP TABLE IF EXISTS `sys_file`;
CREATE TABLE `sys_file` (
`id` varchar(64) NOT NULL COMMENT '文件md5值',
`name` varchar(255) NOT NULL,
`suffix` varchar(255) NOT NULL DEFAULT '' COMMENT '文件名后缀',
`content_type` varchar(255) DEFAULT NULL,
`size` bigint(20) NOT NULL,
`path` varchar(512) NOT NULL COMMENT 'file:相对路径 或 db:md5',
`create_timestamp` bigint(20) NOT NULL,
`remark` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_name` (`name`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
DROP TABLE IF EXISTS `sys_file_content`;
CREATE TABLE `sys_file_content` (
`id` varchar(64) NOT NULL,
`content` longblob NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

View File

@@ -0,0 +1,8 @@
_____ _ _ _ __ __ _
/\ / ____| | | | | | \ \ / / | |
/ \ _ __ _ __| (___ | | _____| |_ ___| |__ \ \ /\ / /__ _ __| | _____
/ /\ \ | '_ \| '_ \\___ \| |/ / _ \ __/ __| '_ \ \ \/ \/ / _ \| '__| |/ / __|
/ ____ \| |_) | |_) |___) | < __/ || (__| | | | \ /\ / (_) | | | <\__ \
/_/ \_\ .__/| .__/_____/|_|\_\___|\__\___|_| |_| \/ \/ \___/|_| |_|\_\___/
| | | |
|_| |_| Powered by GeedgeNetworks

View File

@@ -0,0 +1,10 @@
var MAGIC_EDITOR_CONFIG = {
title: 'digital horizon api',
header: {
skin: true, // 屏蔽皮肤按钮
document: true, // 屏蔽文档按钮
repo: false, // 屏蔽gitee和github
qqGroup: false // 屏蔽加入QQ群
}
// 其它配置参考本页中其它配置项
}

View File

@@ -0,0 +1,13 @@
package net.geedge.asw;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class AppSketchWorksApplicationTests {
@Test
void contextLoads() {
}
}