summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorfangshunjian <[email protected]>2024-06-21 11:00:58 +0800
committerfangshunjian <[email protected]>2024-06-21 11:00:58 +0800
commit0f9fff2f41d16a32c3c95606ca192ad2c33b810e (patch)
tree3fb53ec3cd9103d2bde87b53a2bc25fc6dacd207 /src
初始化项目HEADmain
Diffstat (limited to 'src')
-rw-r--r--src/main/java/net/geedge/asw/AppSketchWokrsApplication.java24
-rw-r--r--src/main/java/net/geedge/asw/common/config/MagicApiConfig.java104
-rw-r--r--src/main/java/net/geedge/asw/common/config/MybatisPlusConfig.java22
-rw-r--r--src/main/java/net/geedge/asw/common/config/RestTemplateConfig.java131
-rw-r--r--src/main/java/net/geedge/asw/common/config/SaTokenConfigure.java53
-rw-r--r--src/main/java/net/geedge/asw/common/config/WebConfig.java46
-rw-r--r--src/main/java/net/geedge/asw/common/config/exception/ASWExceptionHandler.java65
-rw-r--r--src/main/java/net/geedge/asw/common/util/ASWException.java103
-rw-r--r--src/main/java/net/geedge/asw/common/util/Constants.java19
-rw-r--r--src/main/java/net/geedge/asw/common/util/JasyptUtil.java51
-rw-r--r--src/main/java/net/geedge/asw/common/util/R.java82
-rw-r--r--src/main/java/net/geedge/asw/common/util/RCode.java71
-rw-r--r--src/main/java/net/geedge/asw/common/util/ResponseUtil.java64
-rw-r--r--src/main/java/net/geedge/asw/common/util/T.java1529
-rw-r--r--src/main/java/net/geedge/asw/common/util/VerifyUtil.java426
-rw-r--r--src/main/java/net/geedge/asw/module/file/controller/FileController.java110
-rw-r--r--src/main/java/net/geedge/asw/module/file/dao/FileContentDao.java12
-rw-r--r--src/main/java/net/geedge/asw/module/file/dao/FileDao.java17
-rw-r--r--src/main/java/net/geedge/asw/module/file/entity/FileContentEntity.java17
-rw-r--r--src/main/java/net/geedge/asw/module/file/entity/FileEntity.java28
-rw-r--r--src/main/java/net/geedge/asw/module/file/service/IFileService.java42
-rw-r--r--src/main/java/net/geedge/asw/module/file/service/impl/FileServiceImpl.java90
-rw-r--r--src/main/java/net/geedge/asw/module/sys/controller/BaseController.java11
-rw-r--r--src/main/java/net/geedge/asw/module/sys/controller/SysAuthController.java50
-rw-r--r--src/main/java/net/geedge/asw/module/sys/controller/SysRoleController.java78
-rw-r--r--src/main/java/net/geedge/asw/module/sys/controller/SysUserController.java103
-rw-r--r--src/main/java/net/geedge/asw/module/sys/dao/SysConfigDao.java12
-rw-r--r--src/main/java/net/geedge/asw/module/sys/dao/SysRoleDao.java21
-rw-r--r--src/main/java/net/geedge/asw/module/sys/dao/SysRoleMenuDao.java12
-rw-r--r--src/main/java/net/geedge/asw/module/sys/dao/SysUserDao.java12
-rw-r--r--src/main/java/net/geedge/asw/module/sys/dao/SysUserRoleDao.java12
-rw-r--r--src/main/java/net/geedge/asw/module/sys/entity/SysConfigEntity.java45
-rw-r--r--src/main/java/net/geedge/asw/module/sys/entity/SysMenuEntity.java30
-rw-r--r--src/main/java/net/geedge/asw/module/sys/entity/SysRoleEntity.java23
-rw-r--r--src/main/java/net/geedge/asw/module/sys/entity/SysRoleMenuEntity.java15
-rw-r--r--src/main/java/net/geedge/asw/module/sys/entity/SysUserEntity.java27
-rw-r--r--src/main/java/net/geedge/asw/module/sys/entity/SysUserRoleEntity.java15
-rw-r--r--src/main/java/net/geedge/asw/module/sys/service/ISysAuthService.java15
-rw-r--r--src/main/java/net/geedge/asw/module/sys/service/ISysConfigService.java9
-rw-r--r--src/main/java/net/geedge/asw/module/sys/service/ISysRoleMenuService.java9
-rw-r--r--src/main/java/net/geedge/asw/module/sys/service/ISysRoleService.java10
-rw-r--r--src/main/java/net/geedge/asw/module/sys/service/ISysUserRoleService.java9
-rw-r--r--src/main/java/net/geedge/asw/module/sys/service/ISysUserService.java10
-rw-r--r--src/main/java/net/geedge/asw/module/sys/service/impl/SysAuthServiceImpl.java81
-rw-r--r--src/main/java/net/geedge/asw/module/sys/service/impl/SysConfigServiceImpl.java14
-rw-r--r--src/main/java/net/geedge/asw/module/sys/service/impl/SysRoleMenuServiceImpl.java15
-rw-r--r--src/main/java/net/geedge/asw/module/sys/service/impl/SysRoleServiceImpl.java43
-rw-r--r--src/main/java/net/geedge/asw/module/sys/service/impl/SysUserRoleServiceImpl.java15
-rw-r--r--src/main/java/net/geedge/asw/module/sys/service/impl/SysUserServiceImpl.java41
-rw-r--r--src/main/resources/application-magic-api.yml77
-rw-r--r--src/main/resources/application.yml61
-rw-r--r--src/main/resources/config/asw.properties10
-rw-r--r--src/main/resources/config/logback-spring.xml67
-rw-r--r--src/main/resources/db/migration/V1.0.01__INIT_TABLES.sql136
-rw-r--r--src/main/resources/static/banner.txt8
-rw-r--r--src/main/resources/static/magic-editor.js10
-rw-r--r--src/test/java/net/geedge/asw/AppSketchWorksApplicationTests.java13
57 files changed, 4225 insertions, 0 deletions
diff --git a/src/main/java/net/geedge/asw/AppSketchWokrsApplication.java b/src/main/java/net/geedge/asw/AppSketchWokrsApplication.java
new file mode 100644
index 0000000..03f39fa
--- /dev/null
+++ b/src/main/java/net/geedge/asw/AppSketchWokrsApplication.java
@@ -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);
+ }
+
+}
diff --git a/src/main/java/net/geedge/asw/common/config/MagicApiConfig.java b/src/main/java/net/geedge/asw/common/config/MagicApiConfig.java
new file mode 100644
index 0000000..37dc0ca
--- /dev/null
+++ b/src/main/java/net/geedge/asw/common/config/MagicApiConfig.java
@@ -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;
+ }
+ }
+
+}
diff --git a/src/main/java/net/geedge/asw/common/config/MybatisPlusConfig.java b/src/main/java/net/geedge/asw/common/config/MybatisPlusConfig.java
new file mode 100644
index 0000000..68dfa32
--- /dev/null
+++ b/src/main/java/net/geedge/asw/common/config/MybatisPlusConfig.java
@@ -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;
+ }
+} \ No newline at end of file
diff --git a/src/main/java/net/geedge/asw/common/config/RestTemplateConfig.java b/src/main/java/net/geedge/asw/common/config/RestTemplateConfig.java
new file mode 100644
index 0000000..57bba8a
--- /dev/null
+++ b/src/main/java/net/geedge/asw/common/config/RestTemplateConfig.java
@@ -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();
+ }
+
+} \ No newline at end of file
diff --git a/src/main/java/net/geedge/asw/common/config/SaTokenConfigure.java b/src/main/java/net/geedge/asw/common/config/SaTokenConfigure.java
new file mode 100644
index 0000000..958c36c
--- /dev/null
+++ b/src/main/java/net/geedge/asw/common/config/SaTokenConfigure.java
@@ -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("/**");
+ }
+}
diff --git a/src/main/java/net/geedge/asw/common/config/WebConfig.java b/src/main/java/net/geedge/asw/common/config/WebConfig.java
new file mode 100644
index 0000000..5322ff8
--- /dev/null
+++ b/src/main/java/net/geedge/asw/common/config/WebConfig.java
@@ -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;
+ }
+} \ No newline at end of file
diff --git a/src/main/java/net/geedge/asw/common/config/exception/ASWExceptionHandler.java b/src/main/java/net/geedge/asw/common/config/exception/ASWExceptionHandler.java
new file mode 100644
index 0000000..c91c9db
--- /dev/null
+++ b/src/main/java/net/geedge/asw/common/config/exception/ASWExceptionHandler.java
@@ -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());
+ }
+
+} \ No newline at end of file
diff --git a/src/main/java/net/geedge/asw/common/util/ASWException.java b/src/main/java/net/geedge/asw/common/util/ASWException.java
new file mode 100644
index 0000000..47d56d9
--- /dev/null
+++ b/src/main/java/net/geedge/asw/common/util/ASWException.java
@@ -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);
+ }
+ }
+
+} \ No newline at end of file
diff --git a/src/main/java/net/geedge/asw/common/util/Constants.java b/src/main/java/net/geedge/asw/common/util/Constants.java
new file mode 100644
index 0000000..d2e343f
--- /dev/null
+++ b/src/main/java/net/geedge/asw/common/util/Constants.java
@@ -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";
+
+
+
+}
diff --git a/src/main/java/net/geedge/asw/common/util/JasyptUtil.java b/src/main/java/net/geedge/asw/common/util/JasyptUtil.java
new file mode 100644
index 0000000..3cce23a
--- /dev/null
+++ b/src/main/java/net/geedge/asw/common/util/JasyptUtil.java
@@ -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;
+ }
+
+}
diff --git a/src/main/java/net/geedge/asw/common/util/R.java b/src/main/java/net/geedge/asw/common/util/R.java
new file mode 100644
index 0000000..dfb401d
--- /dev/null
+++ b/src/main/java/net/geedge/asw/common/util/R.java
@@ -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;
+ }
+} \ No newline at end of file
diff --git a/src/main/java/net/geedge/asw/common/util/RCode.java b/src/main/java/net/geedge/asw/common/util/RCode.java
new file mode 100644
index 0000000..f242b15
--- /dev/null
+++ b/src/main/java/net/geedge/asw/common/util/RCode.java
@@ -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);
+ }
+}
diff --git a/src/main/java/net/geedge/asw/common/util/ResponseUtil.java b/src/main/java/net/geedge/asw/common/util/ResponseUtil.java
new file mode 100644
index 0000000..5d29954
--- /dev/null
+++ b/src/main/java/net/geedge/asw/common/util/ResponseUtil.java
@@ -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();
+ }
+
+}
diff --git a/src/main/java/net/geedge/asw/common/util/T.java b/src/main/java/net/geedge/asw/common/util/T.java
new file mode 100644
index 0000000..23011ac
--- /dev/null
+++ b/src/main/java/net/geedge/asw/common/util/T.java
@@ -0,0 +1,1529 @@
+package net.geedge.asw.common.util;
+
+import java.awt.Graphics;
+import java.awt.Robot;
+import java.lang.ref.PhantomReference;
+import java.lang.ref.Reference;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.SoftReference;
+import java.lang.ref.WeakReference;
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+import java.net.Socket;
+import java.nio.ByteBuffer;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.time.LocalDateTime;
+import java.time.temporal.Temporal;
+import java.time.temporal.TemporalAccessor;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Spliterator;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.crypto.Cipher;
+import javax.crypto.KeyGenerator;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+import javax.tools.JavaCompiler;
+import javax.tools.JavaFileObject;
+
+import com.baomidou.mybatisplus.core.metadata.OrderItem;
+
+import cn.hutool.core.date.DateTime;
+
+public class T {
+
+ public static final Pattern REGEX_SPECIAL_DATE = Pattern
+ .compile("(\\d{4}-\\d{1,2}-\\d{1,2}[T]\\d{1,2}:\\d{1,2}:\\d{1,2})(.\\d{1,10})((\\+\\d{1,2}:\\d{1,2})|(Z))");
+ /**
+ * 数据库操作工具类
+ */
+ public final static cn.hutool.db.DbUtil DbUtil = new cn.hutool.db.DbUtil();
+ /**
+ * 安全相关工具类<br>
+ * 加密分为三种:<br>
+ * 1、对称加密(symmetric),例如:AES、DES等<br>
+ * 2、非对称加密(asymmetric),例如:RSA、DSA等<br>
+ * 3、摘要加密(digest),例如:MD5、SHA-1、SHA-256、HMAC等<br>
+ */
+ public final static cn.hutool.crypto.SecureUtil SecureUtil = new cn.hutool.crypto.SecureUtil();
+ /**
+ * 代理工具类
+ */
+ public final static cn.hutool.aop.ProxyUtil ProxyUtil = new cn.hutool.aop.ProxyUtil();
+ /**
+ * 敏感词工具类
+ */
+ public final static cn.hutool.dfa.SensitiveUtil SensitiveUtil = new cn.hutool.dfa.SensitiveUtil();
+
+ /**
+ * json 工具类
+ */
+ public static class JSONUtil extends cn.hutool.json.JSONUtil {
+ }
+
+ /**
+ * 数据库元数据信息工具类
+ *
+ * <p>
+ * 需要注意的是,此工具类在某些数据库(比如Oracle)下无效,此时需要手动在数据库配置中增加:
+ *
+ * <pre>
+ * remarks = true
+ * useInformationSchema = true
+ * </pre>
+ *
+ * @author looly
+ */
+ public static class MetaUtil extends cn.hutool.db.meta.MetaUtil {
+ }
+
+ /**
+ * 异常工具类
+ *
+ * @author Looly
+ */
+ public static class ExceptionUtil extends cn.hutool.core.exceptions.ExceptionUtil {
+ }
+
+ /**
+ * 正则相关工具类<br>
+ * 常用正则请见 {@link cn.hutool.core.lang.Validator}
+ *
+ * @author xiaoleilu
+ */
+ public static class ReUtil extends cn.hutool.core.util.ReUtil {
+ }
+
+ /**
+ * SOAP相关工具类
+ *
+ * @author looly
+ * @since 4.5.7
+ */
+ public static class SoapUtil extends cn.hutool.http.webservice.SoapUtil {
+ }
+
+ /**
+ * 脚本工具类
+ *
+ * @author Looly
+ */
+ public static class ScriptUtil extends cn.hutool.script.ScriptUtil {
+ }
+
+ /**
+ * 提供Unicode字符串和普通字符串之间的转换
+ *
+ * @author 兜兜毛毛, looly
+ * @since 4.0.0
+ */
+ public static class UnicodeUtil extends cn.hutool.core.text.UnicodeUtil {
+ }
+
+ /**
+ * 时间工具类
+ *
+ * @author xiaoleilu
+ */
+ public static class DateUtil extends cn.hutool.core.date.DateUtil {
+ /**
+ * 为了处理 2021-05-21T12:06:29.272986526+08:00 格式时间
+ *
+ * @param utcString
+ * @return
+ */
+ public static DateTime parseUTC(String utcString) {
+ if (StrUtil.isBlank(utcString)) {
+ return null;
+ }
+ if (utcString.length() > 29) {
+ Matcher matcher = REGEX_SPECIAL_DATE.matcher(utcString);
+ if (matcher.matches()) {
+ utcString = T.StrUtil.concat(true, matcher.group(1), matcher.group(3));
+ }
+ }
+ return cn.hutool.core.date.DateUtil.parseUTC(utcString);
+ }
+
+ }
+
+ /**
+ * EC密钥参数相关工具类封装
+ *
+ * @author looly
+ * @since 5.4.3
+ */
+ public static class ECKeyUtil extends cn.hutool.crypto.ECKeyUtil {
+ }
+
+ /**
+ * 枚举工具类
+ *
+ * @author looly
+ * @since 3.3.0
+ */
+ public static class EnumUtil extends cn.hutool.core.util.EnumUtil {
+ }
+
+ /**
+ * 数学相关方法工具类<br>
+ * 此工具类与{@link cn.hutool.core.util.NumberUtil}属于一类工具,NumberUtil偏向于简单数学计算的封装,MathUtil偏向复杂数学计算
+ *
+ * @author looly
+ * @since 4.0.7
+ */
+ public static class MathUtil extends cn.hutool.core.math.MathUtil {
+ }
+
+ /**
+ * Sax方式读取Excel相关工具类
+ *
+ * @author looly
+ */
+ public static class ExcelSaxUtil extends cn.hutool.poi.excel.sax.ExcelSaxUtil {
+ }
+
+ /**
+ * 数字工具类<br>
+ * 对于精确值计算应该使用 {@link BigDecimal}<br>
+ * JDK7中<strong>BigDecimal(double val)</strong>构造方法的结果有一定的不可预知性,例如:
+ *
+ * <pre>
+ * new BigDecimal(0.1)
+ * </pre>
+ * <p>
+ * 表示的不是<strong>0.1</strong>而是<strong>0.1000000000000000055511151231257827021181583404541015625</strong>
+ *
+ * <p>
+ * 这是因为0.1无法准确的表示为double。因此应该使用<strong>new BigDecimal(String)</strong>。
+ * </p>
+ * 相关介绍:
+ * <ul>
+ * <li>http://www.oschina.net/code/snippet_563112_25237</li>
+ * <li>https://github.com/venusdrogon/feilong-core/wiki/one-jdk7-bug-thinking</li>
+ * </ul>
+ *
+ * @author Looly
+ */
+ public static class NumberUtil extends cn.hutool.core.util.NumberUtil {
+ }
+
+ /**
+ * Jsch工具类<br>
+ * Jsch是Java Secure Channel的缩写。JSch是一个SSH2的纯Java实现。<br>
+ * 它允许你连接到一个SSH服务器,并且可以使用端口转发,X11转发,文件传输等。<br>
+ *
+ * @author Looly
+ * @since 4.0.0
+ */
+ public static class JschUtil extends cn.hutool.extra.ssh.JschUtil {
+ }
+
+ /**
+ * 线程池工具
+ *
+ * @author luxiaolei
+ */
+ public static class ThreadUtil extends cn.hutool.core.thread.ThreadUtil {
+ }
+
+ /**
+ * Oshi库封装的工具类,通过此工具类,可获取系统、硬件相关信息
+ *
+ * <pre>
+ * 1、系统信息
+ * 2、硬件信息
+ * </pre>
+ *
+ * 相关内容见:https://github.com/oshi/oshi
+ *
+ * @author Looly
+ * @since 4.6.4
+ */
+ public static class OshiUtil extends cn.hutool.system.oshi.OshiUtil {
+ }
+
+ /**
+ * URL(Uniform Resource Locator)统一资源定位符相关工具类
+ *
+ * <p>
+ * 统一资源定位符,描述了一台特定服务器上某资源的特定位置。
+ * </p>
+ * URL组成:
+ *
+ * <pre>
+ * 协议://主机名[:端口]/ 路径/[:参数] [?查询]#Fragment
+ * protocol :// hostname[:port] / path / [:parameters][?query]#fragment
+ * </pre>
+ *
+ * @author xiaoleilu
+ */
+ public static class URLUtil extends cn.hutool.core.util.URLUtil {
+ }
+
+ /**
+ * ID生成器工具类,此工具类中主要封装:
+ *
+ * <pre>
+ * 1. 唯一性ID生成器:UUID、ObjectId(MongoDB)、Snowflake
+ * </pre>
+ *
+ * <p>
+ * ID相关文章见:http://calvin1978.blogcn.com/articles/uuid.html
+ *
+ * @author looly
+ * @since 4.1.13
+ */
+ public static class IdUtil extends cn.hutool.core.util.IdUtil {
+ }
+
+ /**
+ * Http请求工具类
+ *
+ * @author xiaoleilu
+ */
+ public static class HttpUtil extends cn.hutool.http.HttpUtil {
+ }
+
+ /**
+ * 桌面相关工具(平台相关)<br>
+ * Desktop 类允许 Java 应用程序启动已在本机桌面上注册的关联应用程序,以处理 URI 或文件。
+ *
+ * @author looly
+ * @since 4.5.7
+ */
+ public static class DesktopUtil extends cn.hutool.core.swing.DesktopUtil {
+ }
+
+ /**
+ * 图片处理工具类:<br>
+ * 功能:缩放图像、切割图像、旋转、图像类型转换、彩色转黑白、文字水印、图片水印等 <br>
+ * 参考:http://blog.csdn.net/zhangzhikaixinya/article/details/8459400
+ *
+ * @author Looly
+ */
+ public static class ImgUtil extends cn.hutool.core.img.ImgUtil {
+ }
+
+ /**
+ * 分页工具类
+ *
+ * @author xiaoleilu
+ */
+ public static class PageUtil extends cn.hutool.core.util.PageUtil {
+ public static OrderItem decodeOrderByStr(String orderBy) {
+ if (cn.hutool.core.util.StrUtil.isBlank(orderBy)) {
+ return null;
+ }
+ if (orderBy.startsWith("-")) {
+ return OrderItem.desc(orderBy.substring(1));
+ }
+ return OrderItem.asc(orderBy);
+ }
+ }
+
+ /**
+ * HTML工具类
+ *
+ * <p>
+ * 比如我们在使用爬虫爬取HTML页面后,需要对返回页面的HTML内容做一定处理,<br>
+ * 比如去掉指定标签(例如广告栏等)、去除JS、去掉样式等等,这些操作都可以使用此工具类完成。
+ *
+ * @author xiaoleilu
+ *
+ */
+ public static class HtmlUtil extends cn.hutool.http.HtmlUtil {
+ }
+
+ /**
+ * PEM(Privacy Enhanced Mail)格式相关工具类。(基于Bouncy Castle)
+ *
+ * <p>
+ * PEM一般为文本格式,以 -----BEGIN... 开头,以 -----END... 结尾,中间的内容是 BASE64 编码。
+ * <p>
+ * 这种格式可以保存证书和私钥,有时我们也把PEM格式的私钥的后缀改为 .key 以区别证书与私钥。
+ *
+ * @author looly
+ * @since 5.1.6
+ */
+ public static class PemUtil extends cn.hutool.crypto.PemUtil {
+ }
+
+ /**
+ * 分词工具类
+ *
+ * @author looly
+ * @since 4.3.3
+ */
+ public static class TokenizerUtil extends cn.hutool.extra.tokenizer.TokenizerUtil {
+ }
+
+ /**
+ * ExcelExtractor工具封装
+ *
+ * @author looly
+ * @since 5.4.4
+ */
+ public static class ExcelExtractorUtil extends cn.hutool.poi.excel.ExcelExtractorUtil {
+ }
+
+ /**
+ * 邮件工具类,基于javax.mail封装
+ *
+ * @author looly
+ * @since 3.1.2
+ */
+ public static class MailUtil extends cn.hutool.extra.mail.MailUtil {
+ }
+
+ /**
+ * NIO中Path对象操作封装
+ *
+ * @author looly
+ * @since 5.4.1
+ */
+ public static class PathUtil extends cn.hutool.core.io.file.PathUtil {
+ }
+
+ /**
+ * 邮件内部工具类
+ *
+ * @author looly
+ * @since 3.2.3
+ */
+ public static class InternalMailUtil extends cn.hutool.extra.mail.InternalMailUtil {
+ }
+
+ /**
+ * 定时任务工具类<br>
+ * 此工具持有一个全局{@link cn.hutool.cron.Scheduler},所有定时任务在同一个调度器中执行<br>
+ * {@link #setMatchSecond(boolean)}
+ * 方法用于定义是否使用秒匹配模式,如果为true,则定时任务表达式中的第一位为秒,否则为分,默认是分
+ *
+ * @author xiaoleilu
+ *
+ */
+ public static class CronUtil extends cn.hutool.cron.CronUtil {
+ }
+
+ /**
+ * Java的System类封装工具类。<br>
+ * 参考:http://blog.csdn.net/zhongweijian/article/details/7619383
+ *
+ * @author Looly
+ */
+ public static class SystemUtil extends cn.hutool.system.SystemUtil {
+ }
+
+ /**
+ * 压缩工具类<br>
+ * 基于commons-compress的压缩解压封装
+ *
+ * @author looly
+ * @since 5.5.0
+ */
+ public static class CompressUtil extends cn.hutool.extra.compress.CompressUtil {
+ }
+
+ /**
+ * {@link ClassLoader}工具类
+ *
+ * @author Looly
+ * @since 3.0.9
+ */
+ public static class ClassLoaderUtil extends cn.hutool.core.util.ClassLoaderUtil {
+ }
+
+ /**
+ * {@link Spliterator}相关工具类
+ *
+ * @author looly
+ * @since 5.4.3
+ */
+ public static class SpliteratorUtil extends cn.hutool.core.collection.SpliteratorUtil {
+ }
+
+ /**
+ * 监听工具类<br>
+ * 主要负责文件监听器的快捷创建
+ *
+ * @author Looly
+ * @since 3.1.0
+ */
+ public static class WatchUtil extends cn.hutool.core.io.watch.WatchUtil {
+ }
+
+ /**
+ * Excel样式工具类
+ *
+ * @author looly
+ * @since 4.0.0
+ */
+ public static class StyleUtil extends cn.hutool.poi.excel.style.StyleUtil {
+ }
+
+ /**
+ * 系统剪贴板工具类
+ *
+ * @author looly
+ * @since 3.2.0
+ */
+ public static class ClipboardUtil extends cn.hutool.core.swing.clipboard.ClipboardUtil {
+ }
+
+ /**
+ * 文件类型判断工具类
+ *
+ * <p>
+ * 此工具根据文件的前几位bytes猜测文件类型,对于文本、zip判断不准确,对于视频、图片类型判断准确
+ * </p>
+ *
+ * <p>
+ * 需要注意的是,xlsx、docx等Office2007格式,全部识别为zip,因为新版采用了OpenXML格式,这些格式本质上是XML文件打包为zip
+ * </p>
+ *
+ * @author Looly
+ */
+ public static class FileTypeUtil extends cn.hutool.core.io.FileTypeUtil {
+ }
+
+ /**
+ * 引用工具类,主要针对{@link Reference} 工具化封装<br>
+ * 主要封装包括:
+ *
+ * <pre>
+ * 1. {@link SoftReference} 软引用,在GC报告内存不足时会被GC回收
+ * 2. {@link WeakReference} 弱引用,在GC时发现弱引用会回收其对象
+ * 3. {@link PhantomReference} 虚引用,在GC时发现虚引用对象,会将{@link PhantomReference}插入{@link ReferenceQueue}。 此时对象未被真正回收,要等到{@link ReferenceQueue}被真正处理后才会被回收。
+ * </pre>
+ *
+ * @author looly
+ * @since 3.1.2
+ */
+ public static class ReferenceUtil extends cn.hutool.core.util.ReferenceUtil {
+ }
+
+ /**
+ * 布隆过滤器工具
+ *
+ * @author looly
+ * @since 4.1.5
+ */
+ public static class BloomFilterUtil extends cn.hutool.bloomfilter.BloomFilterUtil {
+ }
+
+ /**
+ * 修饰符工具类
+ *
+ * @author looly
+ * @since 4.0.5
+ */
+ public static class ModifierUtil extends cn.hutool.core.util.ModifierUtil {
+ }
+
+ /**
+ * 集合的stream操作封装
+ *
+ * @author [email protected]
+ * @since 5.5.2
+ */
+ public static class CollStreamUtil extends cn.hutool.core.collection.CollStreamUtil {
+ }
+
+ /**
+ * 摘要算法工具类
+ *
+ * @author Looly
+ */
+ public static class DigestUtil extends cn.hutool.crypto.digest.DigestUtil {
+ }
+
+ /**
+ * 对象工具类,包括判空、克隆、序列化等操作
+ *
+ * @author Looly
+ */
+ public static class ObjectUtil extends cn.hutool.core.util.ObjectUtil {
+ }
+
+ /**
+ * Hash算法大全<br>
+ * 推荐使用FNV1算法
+ *
+ * @author Goodzzp, Looly
+ */
+ public static class HashUtil extends cn.hutool.core.util.HashUtil {
+ }
+
+ /**
+ * Bouncy Castle相关工具类封装
+ *
+ * @author looly
+ * @since 4.5.0
+ */
+ public static class BCUtil extends cn.hutool.crypto.BCUtil {
+ }
+
+ /**
+ * 针对 {@link Type} 的工具类封装<br>
+ * 最主要功能包括:
+ *
+ * <pre>
+ * 1. 获取方法的参数和返回值类型(包括Type和Class)
+ * 2. 获取泛型参数类型(包括对象的泛型参数或集合元素的泛型类型)
+ * </pre>
+ *
+ * @author Looly
+ * @since 3.0.8
+ */
+ public static class TypeUtil extends cn.hutool.core.util.TypeUtil {
+ }
+
+ /**
+ * Excel文件工具类
+ *
+ * @author looly
+ * @since 4.2.1
+ */
+ public static class ExcelFileUtil extends cn.hutool.poi.excel.ExcelFileUtil {
+ }
+
+ /**
+ * NIO工具类
+ *
+ * @since 5.4.0
+ */
+ public static class NioUtil extends cn.hutool.socket.nio.NioUtil {
+ }
+
+ /**
+ * 类工具类 <br>
+ *
+ * @author xiaoleilu
+ */
+ public static class ClassUtil extends cn.hutool.core.util.ClassUtil {
+ }
+
+ /**
+ * 针对{@link Calendar} 对象封装工具类
+ *
+ * @author looly
+ * @since 5.3.0
+ */
+ public static class CalendarUtil extends cn.hutool.core.date.CalendarUtil {
+ }
+
+ /**
+ * 网络相关工具
+ *
+ */
+ public static class NetUtil extends cn.hutool.core.net.NetUtil {
+
+ public static String getLocalIPBySocket(String host, Integer port) {
+ Socket socket = null;
+ try {
+ socket = new Socket(host, port);
+ return socket.getLocalAddress().getHostAddress();
+ } catch (Exception e) {
+ return T.NetUtil.getLocalhostStr();
+ } finally {
+ T.IoUtil.close(socket);
+ }
+ }
+
+ }
+
+ /**
+ * 源码编译工具类,主要封装{@link JavaCompiler} 相关功能
+ *
+ * @author looly
+ * @since 5.5.2
+ */
+ public static class CompilerUtil extends cn.hutool.core.compiler.CompilerUtil {
+ }
+
+ /**
+ * 锁相关工具
+ *
+ * @author looly
+ * @since 5.2.5
+ */
+ public static class LockUtil extends cn.hutool.core.thread.lock.LockUtil {
+ }
+
+ /**
+ * 文件名相关工具类
+ *
+ * @author looly
+ * @since 5.4.1
+ */
+ public static class FileNameUtil extends cn.hutool.core.io.file.FileNameUtil {
+ }
+
+ /**
+ * SM国密算法工具类<br>
+ * 此工具类依赖org.bouncycastle:bcpkix-jdk15on
+ *
+ * @author looly
+ * @since 4.3.2
+ */
+ public static class SmUtil extends cn.hutool.crypto.SmUtil {
+ }
+
+ /**
+ * Props工具类<br>
+ * 提供静态方法获取配置文件
+ *
+ * @author looly
+ * @since 5.1.3
+ */
+ public static class PropsUtil extends cn.hutool.setting.dialect.PropsUtil {
+ }
+
+ /**
+ * XML工具类<br>
+ * 此工具使用w3c dom工具,不需要依赖第三方包。<br>
+ * 工具类封装了XML文档的创建、读取、写出和部分XML操作
+ *
+ * @author xiaoleilu
+ */
+ public static class XmlUtil extends cn.hutool.core.util.XmlUtil {
+ }
+
+ /**
+ * Velocity模板引擎工具类<br>
+ * 使用前必须初始化工具类
+ *
+ * @author xiaoleilu
+ * @deprecated 使用TemplateUtil替代
+ */
+ public static class VelocityEngine extends cn.hutool.extra.template.engine.velocity.VelocityEngine {
+ }
+
+ /**
+ * {@link ByteBuffer} 工具类<br>
+ * 此工具来自于 t-io 项目以及其它项目的相关部分收集<br>
+ * ByteBuffer的相关介绍见:https://www.cnblogs.com/ruber/p/6857159.html
+ *
+ * @author tanyaowu, looly
+ * @since 4.0.0
+ *
+ */
+ public static class BufferUtil extends cn.hutool.core.io.BufferUtil {
+ }
+
+ /**
+ * 表达式引擎工具类
+ *
+ * @author looly
+ * @since 5.5.0
+ */
+ public static class ExpressionUtil extends cn.hutool.extra.expression.ExpressionUtil {
+ }
+
+ /**
+ * 驱动相关工具类,包括自动获取驱动类名
+ *
+ * @author looly
+ * @since 4.0.10
+ */
+ public static class DriverUtil extends cn.hutool.db.dialect.DriverUtil {
+ }
+
+ /**
+ * IO工具类<br>
+ * IO工具类只是辅助流的读写,并不负责关闭流。原因是流可能被多次读写,读写关闭后容易造成问题。
+ *
+ * @author xiaoleilu
+ */
+ public static class IoUtil extends cn.hutool.core.io.IoUtil {
+ }
+
+ /**
+ * JDK8+中的{@link LocalDateTime} 工具类封装
+ *
+ * @author looly
+ * @since 5.3.9
+ */
+ public static class LocalDateTimeUtil extends cn.hutool.core.date.LocalDateTimeUtil {
+ }
+
+ /**
+ * 规范化对象生成工具
+ *
+ * @author looly
+ * @since 5.4.3
+ */
+ public static class InternUtil extends cn.hutool.core.lang.intern.InternUtil {
+ }
+
+ /**
+ * 压缩工具类
+ *
+ * @author Looly
+ */
+ public static class ZipUtil extends cn.hutool.core.util.ZipUtil {
+ }
+
+ /**
+ * 十六进制(简写为hex或下标16)在数学中是一种逢16进1的进位制,一般用数字0到9和字母A到F表示(其中:A~F即10~15)。<br>
+ * 例如十进制数57,在二进制写作111001,在16进制写作39。<br>
+ * 像java,c这样的语言为了区分十六进制和十进制数值,会在十六进制数的前面加上 0x,比如0x20是十进制的32,而不是十进制的20<br>
+ * <p>
+ * 参考:https://my.oschina.net/xinxingegeya/blog/287476
+ *
+ * @author Looly
+ */
+ public static class HexUtil extends cn.hutool.core.util.HexUtil {
+ }
+
+ /**
+ * 数据大小工具类
+ *
+ * @author looly
+ * @since 5.3.10
+ */
+ public static class DataSizeUtil extends cn.hutool.core.io.unit.DataSizeUtil {
+ }
+
+ /**
+ * User-Agent工具类
+ *
+ * @author looly
+ *
+ */
+ public static class UserAgentUtil extends cn.hutool.http.useragent.UserAgentUtil {
+ }
+
+ /**
+ * 统一社会信用代码工具类
+ *
+ * <pre>
+ * 第一部分:登记管理部门代码1位 (数字或大写英文字母)
+ * 第二部分:机构类别代码1位 (数字或大写英文字母)
+ * 第三部分:登记管理机关行政区划码6位 (数字)
+ * 第四部分:主体标识码(组织机构代码)9位 (数字或大写英文字母)
+ * 第五部分:校验码1位 (数字或大写英文字母)
+ * </pre>
+ *
+ * @author looly
+ * @since 5.2.4
+ */
+ public static class CreditCodeUtil extends cn.hutool.core.util.CreditCodeUtil {
+ }
+
+ /**
+ * {@link Temporal} 工具类封装
+ *
+ * @author looly
+ * @since 5.4.5
+ */
+ public static class TemporalUtil extends cn.hutool.core.date.TemporalUtil {
+ }
+
+ /**
+ * Setting工具类<br>
+ * 提供静态方法获取配置文件
+ *
+ * @author looly
+ */
+ public static class SettingUtil extends cn.hutool.setting.SettingUtil {
+ }
+
+ /**
+ * Statement和PreparedStatement工具类
+ *
+ * @author looly
+ * @since 4.0.10
+ */
+ public static class StatementUtil extends cn.hutool.db.StatementUtil {
+ }
+
+ /**
+ * 基于https://github.com/vdurmont/emoji-java的Emoji表情工具类
+ * <p>
+ * emoji-java文档以及别名列表见:https://github.com/vdurmont/emoji-java
+ *
+ * @author looly
+ * @since 4.2.1
+ */
+ public static class EmojiUtil extends cn.hutool.extra.emoji.EmojiUtil {
+ }
+
+ /**
+ * Bean工具类
+ *
+ * <p>
+ * 把一个拥有对属性进行set和get方法的类,我们就可以称之为JavaBean。
+ * </p>
+ *
+ * @author Looly
+ * @since 3.1.2
+ */
+ public static class BeanUtil extends cn.hutool.core.bean.BeanUtil {
+ }
+
+ /**
+ * Servlet相关工具类封装
+ *
+ * @author looly
+ * @since 3.2.0
+ */
+ public static class ServletUtil extends cn.hutool.extra.servlet.ServletUtil {
+ }
+
+ /**
+ * java bean 校验工具类,此工具类基于validation-api(jakarta.validation-api)封装
+ *
+ * <p>
+ * 在实际使用中,用户需引入validation-api的实现,如:hibernate-validator
+ * </p>
+ * <p>
+ * 注意:hibernate-validator还依赖了javax.el,需自行引入。
+ * </p>
+ *
+ * @author chengqiang
+ * @since 5.5.0
+ */
+ public static class ValidationUtil extends cn.hutool.extra.validation.ValidationUtil {
+ }
+
+ /**
+ * Excel工作簿相关工具类
+ *
+ * @author looly
+ * @since 4.0.7
+ *
+ */
+ public static class WorkbookUtil extends cn.hutool.poi.excel.WorkbookUtil {
+ }
+
+ /**
+ * 拼音工具类,封装了TinyPinyin、JPinyin、Pinyin4j,通过SPI自动识别。
+ *
+ * @author looly
+ */
+ public static class PinyinUtil extends cn.hutool.extra.pinyin.PinyinUtil {
+ }
+
+ /**
+ * 调用者。可以通过此类的方法获取调用者、多级调用者以及判断是否被调用
+ *
+ * @author Looly
+ * @since 4.1.6
+ */
+ public static class CallerUtil extends cn.hutool.core.lang.caller.CallerUtil {
+ }
+
+ /**
+ * 定时任务表达式工具类
+ *
+ * @author looly
+ *
+ */
+ public static class CronPatternUtil extends cn.hutool.cron.pattern.CronPatternUtil {
+ }
+
+ /**
+ * 系统运行时工具类,用于执行系统命令的工具
+ *
+ * @author Looly
+ * @since 3.1.1
+ */
+ public static class RuntimeUtil extends cn.hutool.core.util.RuntimeUtil {
+ }
+
+ /**
+ * {@link TemporalAccessor} 工具类封装
+ *
+ * @author looly
+ * @since 5.3.9
+ */
+ public static class TemporalAccessorUtil extends cn.hutool.core.date.TemporalAccessorUtil {
+ }
+
+ /**
+ * 图形验证码工具
+ *
+ * @author looly
+ * @since 3.1.2
+ */
+ public static class CaptchaUtil extends cn.hutool.captcha.CaptchaUtil {
+ }
+
+ /**
+ * 反射工具类
+ *
+ * @author Looly
+ * @since 3.0.9
+ */
+ public static class ReflectUtil extends cn.hutool.core.util.ReflectUtil {
+ }
+
+ /**
+ * IPV4地址工具类
+ *
+ * <p>
+ * pr自:https://gitee.com/loolly/hutool/pulls/161
+ * </p>
+ *
+ * @author ZhuKun
+ * @since 5.4.1
+ */
+ public static class Ipv4Util extends cn.hutool.core.net.Ipv4Util {
+ }
+
+ /**
+ * {@link Robot} 封装工具类,提供截屏等工具
+ *
+ * @author looly
+ * @since 4.1.14
+ */
+ public static class RobotUtil extends cn.hutool.core.swing.RobotUtil {
+ }
+
+ /**
+ * {@link Graphics}相关工具类
+ *
+ * @author looly
+ * @since 4.5.2
+ */
+ public static class GraphicsUtil extends cn.hutool.core.img.GraphicsUtil {
+ }
+
+ /**
+ * Cglib工具类
+ *
+ * @author looly
+ * @since 5.4.1
+ */
+ public static class CglibUtil extends cn.hutool.extra.cglib.CglibUtil {
+ }
+
+ /**
+ * 转义和反转义工具类Escape / Unescape<br>
+ * escape采用ISO Latin字符集对指定的字符串进行编码。<br>
+ * 所有的空格符、标点符号、特殊字符以及其他非ASCII字符都将被转化成%xx格式的字符编码(xx等于该字符在字符集表里面的编码的16进制数字)。
+ *
+ * @author xiaoleilu
+ */
+ public static class EscapeUtil extends cn.hutool.core.util.EscapeUtil {
+ }
+
+ /**
+ * 诊断工具类
+ *
+ * @author looly
+ * @since 5.5.2
+ */
+ public static class DiagnosticUtil extends cn.hutool.core.compiler.DiagnosticUtil {
+ }
+
+ /**
+ * Excel中的行封装工具类
+ *
+ * @author looly
+ * @since 4.0.7
+ */
+ public static class RowUtil extends cn.hutool.poi.excel.RowUtil {
+ }
+
+ /**
+ * 密钥工具类
+ *
+ * <p>
+ * 包括:
+ *
+ * <pre>
+ * 1、生成密钥(单密钥、密钥对)
+ * 2、读取密钥文件
+ * </pre>
+ *
+ * @author looly, Gsealy
+ * @since 4.4.1
+ */
+ public static class KeyUtil extends cn.hutool.crypto.KeyUtil {
+ }
+
+ /**
+ * 基于Zxing的二维码工具类
+ *
+ * @author looly
+ * @since 4.0.2
+ */
+ public static class QrCodeUtil extends cn.hutool.extra.qrcode.QrCodeUtil {
+ }
+
+ /**
+ * SQL相关工具类,包括相关SQL语句拼接等
+ *
+ * @author looly
+ * @since 4.0.10
+ */
+ public static class SqlUtil extends cn.hutool.db.sql.SqlUtil {
+ }
+
+ /**
+ * Excel工具类,不建议直接使用index直接操作sheet,在wps/excel中sheet显示顺序与index无关,还有隐藏sheet
+ *
+ * @author Looly
+ *
+ */
+ public static class ExcelUtil extends cn.hutool.poi.excel.ExcelUtil {
+ }
+
+ /**
+ * 缓存工具类
+ *
+ * @author Looly
+ * @since 3.0.1
+ */
+ public static class CacheUtil extends cn.hutool.cache.CacheUtil {
+ }
+
+ /**
+ * Word工具类
+ *
+ * @author Looly
+ * @since 4.5.16
+ */
+ public static class WordUtil extends cn.hutool.poi.word.WordUtil {
+ }
+
+ /**
+ * 注解工具类<br>
+ * 快速获取注解对象、注解值等工具封装
+ *
+ * @author looly
+ * @since 4.0.9
+ */
+ public static class AnnotationUtil extends cn.hutool.core.annotation.AnnotationUtil {
+ }
+
+ /**
+ * Excel图片工具类
+ *
+ * @author looly
+ * @since 4.0.7
+ */
+ public static class ExcelPicUtil extends cn.hutool.poi.excel.ExcelPicUtil {
+ }
+
+ /**
+ * 屏幕相关(当前显示设置)工具类
+ *
+ * @author looly
+ * @since 4.1.14
+ */
+ public static class ScreenUtil extends cn.hutool.core.swing.ScreenUtil {
+ }
+
+ /**
+ * 文件工具类
+ *
+ * @author looly
+ */
+ public static class FileUtil extends cn.hutool.core.io.FileUtil {
+ }
+
+ /**
+ * Word Document工具
+ *
+ * @author looly
+ * @since 4.4.1
+ */
+ public static class DocUtil extends cn.hutool.poi.word.DocUtil {
+ }
+
+ /**
+ * 比较工具类
+ *
+ * @author looly
+ */
+ public static class CompareUtil extends cn.hutool.core.comparator.CompareUtil {
+ }
+
+ /**
+ * Boolean类型相关工具类
+ *
+ * @author looly
+ * @since 4.1.16
+ */
+ public static class BooleanUtil extends cn.hutool.core.util.BooleanUtil {
+ }
+
+ /**
+ * {@link Iterable} 和 {@link Iterator} 相关工具类
+ *
+ * @author Looly
+ * @since 3.1.0
+ */
+ public static class IterUtil extends cn.hutool.core.collection.IterUtil {
+ }
+
+ /**
+ * 树工具类
+ *
+ * @author liangbaikai
+ */
+ public static class TreeUtil extends cn.hutool.core.lang.tree.TreeUtil {
+ }
+
+ /**
+ * 身份证相关工具类<br>
+ * see https://www.oschina.net/code/snippet_1611_2881
+ *
+ * <p>
+ * 本工具并没有对行政区划代码做校验,如有需求,请参阅(2018年10月):
+ * http://www.mca.gov.cn/article/sj/xzqh/2018/201804-12/20181011221630.html
+ * </p>
+ *
+ * @author Looly
+ * @since 3.0.4
+ */
+ public static class IdcardUtil extends cn.hutool.core.util.IdcardUtil {
+ }
+
+ /**
+ * 字符工具类<br>
+ * 部分工具来自于Apache Commons系列
+ *
+ * @author looly
+ * @since 4.0.1
+ */
+ public static class CharUtil extends cn.hutool.core.util.CharUtil {
+ }
+
+ /**
+ * Socket相关工具类
+ *
+ * @author looly
+ * @since 4.5.0
+ */
+ public static class SocketUtil extends cn.hutool.socket.SocketUtil {
+ }
+
+ /**
+ * 手机号工具类
+ *
+ * @author dahuoyzs
+ * @since 5.3.11
+ */
+ public static class PhoneUtil extends cn.hutool.core.util.PhoneUtil {
+ }
+
+ /**
+ * 模板工具类
+ *
+ * @author looly
+ * @since 4.1.0
+ */
+ public static class TemplateUtil extends cn.hutool.extra.template.TemplateUtil {
+ }
+
+ /**
+ * 集合相关工具类
+ * <p>
+ * 此工具方法针对{@link Collection}及其实现类封装的工具。
+ * <p>
+ * 由于{@link Collection} 实现了{@link Iterable}接口,因此部分工具此类不提供,而是在{@link IterUtil}
+ * 中提供
+ *
+ * @author xiaoleilu
+ * @see IterUtil
+ * @since 3.1.1
+ */
+ public static class CollUtil extends cn.hutool.core.collection.CollUtil {
+ }
+
+ /**
+ * {@link JavaFileObject} 相关工具类封装
+ *
+ * @author lzpeng, looly
+ * @since 5.5.2
+ */
+ public static class JavaFileObjectUtil extends cn.hutool.core.compiler.JavaFileObjectUtil {
+ }
+
+ /**
+ * 数组工具类
+ *
+ * @author Looly
+ */
+ public static class ArrayUtil extends cn.hutool.core.util.ArrayUtil {
+ }
+
+ /**
+ * SSL(Secure Sockets Layer 安全套接字协议)相关工具封装
+ *
+ * @author looly
+ * @since 5.5.2
+ */
+ public static class SSLUtil extends cn.hutool.core.net.SSLUtil {
+ }
+
+ /**
+ * 集合相关工具类,包括数组,是{@link CollUtil} 的别名工具类类
+ *
+ * @author xiaoleilu
+ * @see CollUtil
+ */
+ public static class CollectionUtil extends cn.hutool.core.collection.CollectionUtil {
+ }
+
+ /**
+ * Excel表格中单元格工具类
+ *
+ * @author looly
+ * @since 4.0.7
+ */
+ public static class CellUtil extends cn.hutool.poi.excel.cell.CellUtil {
+ }
+
+ /**
+ * 字符集工具类
+ *
+ * @author xiaoleilu
+ */
+ public static class CharsetUtil extends cn.hutool.core.util.CharsetUtil {
+ }
+
+ /**
+ * 字符串工具类
+ *
+ * @author xiaoleilu
+ */
+ public static class StrUtil extends cn.hutool.core.util.StrUtil {
+
+ /**
+ * 将下划线方式命名的字符串转换为驼峰式。如果转换前的下划线大写方式命名的字符串为空,则返回空字符串。<br>
+ * 例如:hello_world=》helloWorld
+ *
+ * @param name 转换前的下划线大写方式命名的字符串
+ * @param symbol 连接符
+ * @return 转换后的驼峰式命名的字符串
+ */
+ public static String toCamelCase(CharSequence name, char symbol) {
+ if (null == name) {
+ return null;
+ }
+ String symbolStr = symbol + "";
+ String name2 = name.toString();
+ if (name2.contains(symbolStr)) {
+ final StringBuilder sb = new StringBuilder(name2.length());
+ boolean upperCase = false;
+ for (int i = 0; i < name2.length(); i++) {
+ char c = name2.charAt(i);
+
+ if (c == symbol) {
+ upperCase = true;
+ } else if (upperCase) {
+ sb.append(Character.toUpperCase(c));
+ upperCase = false;
+ } else {
+ sb.append(Character.toLowerCase(c));
+ }
+ }
+ return sb.toString();
+ } else {
+ return name2;
+ }
+ }
+
+ private static final String[] LIKE_SPECIAL_CHAR = { "%", "_" };
+
+ /**
+ * 转义 msyql like 特殊字符
+ *
+ * @param cs
+ * @return
+ */
+ public static String likeEscape(String cs) {
+ if (StrUtil.isBlank(cs)) {
+ return null;
+ }
+ cs = StrUtil.replace(cs, "\\", "\\\\");
+ for (String s : LIKE_SPECIAL_CHAR) {
+ cs = StrUtil.replace(cs, s, "\\" + s);
+ }
+ return cs;
+ }
+
+ }
+
+ /**
+ * Map相关工具类
+ *
+ * @author Looly
+ * @since 3.1.1
+ */
+ public static class MapUtil extends cn.hutool.core.map.MapUtil {
+ }
+
+ /**
+ * SPI机制中的服务加载工具类,流程如下
+ *
+ * <pre>
+ * 1、创建接口,并创建实现类
+ * 2、ClassPath/META-INF/services下创建与接口全限定类名相同的文件
+ * 3、文件内容填写实现类的全限定类名
+ * </pre>
+ *
+ * 相关介绍见:https://www.jianshu.com/p/3a3edbcd8f24
+ *
+ * @author looly
+ * @since 5.1.6
+ */
+ public static class ServiceLoaderUtil extends cn.hutool.core.util.ServiceLoaderUtil {
+ }
+
+ /**
+ * Spring(Spring boot)工具封装,包括:
+ *
+ * <pre>
+ * 1、Spring IOC容器中的bean对象获取
+ * </pre>
+ *
+ * @author loolly
+ * @since 5.1.0
+ */
+ public static class SpringUtil extends cn.hutool.extra.spring.SpringUtil {
+ }
+
+ /**
+ * 随机工具类
+ *
+ * @author xiaoleilu
+ */
+ public static class RandomUtil extends cn.hutool.core.util.RandomUtil {
+ }
+
+ public static class ListUtil extends cn.hutool.core.collection.ListUtil {
+ }
+
+ /**
+ * Resource资源工具类
+ *
+ * @author Looly
+ *
+ */
+ public static class ResourceUtil extends cn.hutool.core.io.resource.ResourceUtil {
+ }
+
+ /**
+ * AWT中字体相关工具类
+ *
+ * @author looly
+ * @since 5.3.6
+ */
+ public static class FontUtil extends cn.hutool.core.img.FontUtil {
+ }
+
+ /**
+ * CSV工具
+ *
+ * @author looly
+ * @since 4.0.5
+ */
+ public static class CsvUtil extends cn.hutool.core.text.csv.CsvUtil {
+ }
+
+ /**
+ * Word中表格相关工具
+ *
+ * @author Looly
+ * @since 4.5.14
+ */
+ public static class TableUtil extends cn.hutool.poi.word.TableUtil {
+ }
+
+ /**
+ * http response 工具类
+ *
+ * @author Looly
+ * @since 4.5.14
+ */
+ public static class ResponseUtil extends net.geedge.asw.common.util.ResponseUtil {
+ }
+
+ /**
+ * 原始类型数组工具类
+ *
+ * @author looly
+ * @since 5.5.2
+ */
+ public static class PrimitiveArrayUtil extends cn.hutool.core.util.PrimitiveArrayUtil {
+ }
+
+ public static class AesUtil {
+
+ private static final String KEY_ALGORITHM = "AES";
+ private static final String DEFAULT_CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding";// 默认的加密算法
+
+ public static String encrypt(String content, byte[] key) {
+ try {
+ Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);// 创建密码器
+
+ byte[] byteContent = content.getBytes("utf-8");
+
+ cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(key));// 初始化为加密模式的密码器
+
+ byte[] result = cipher.doFinal(byteContent);// 加密
+
+ return cn.hutool.core.util.HexUtil.encodeHexStr(result);
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ }
+
+ return null;
+ }
+
+ public static String decrypt(String content, byte[] key) {
+
+ try {
+ // 实例化
+ Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);
+
+ // 使用密钥初始化,设置为解密模式
+ cipher.init(Cipher.DECRYPT_MODE, getSecretKey(key));
+
+ // 执行操作
+ byte[] result = cipher.doFinal(cn.hutool.core.util.HexUtil.decodeHex(content));
+ String s = new String(result, "utf-8");
+ return s;
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ }
+ return null;
+ }
+
+ private static SecretKeySpec getSecretKey(byte[] key) {
+ // 返回生成指定算法密钥生成器的 KeyGenerator 对象
+ KeyGenerator kg = null;
+ try {
+ kg = KeyGenerator.getInstance(KEY_ALGORITHM);
+ SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
+ random.setSeed(key);
+ // AES 要求密钥长度为 128
+ kg.init(128, random);
+ // 生成一个密钥
+ SecretKey secretKey = kg.generateKey();
+ return new SecretKeySpec(secretKey.getEncoded(), KEY_ALGORITHM);// 转换为AES专用密钥
+ } catch (NoSuchAlgorithmException ex) {
+ ex.printStackTrace();
+ }
+ return null;
+ }
+
+ }
+
+ public static class VerifyUtil extends net.geedge.asw.common.util.VerifyUtil {
+ private VerifyUtil(Object value) {
+ super(value);
+ }
+ }
+}
diff --git a/src/main/java/net/geedge/asw/common/util/VerifyUtil.java b/src/main/java/net/geedge/asw/common/util/VerifyUtil.java
new file mode 100644
index 0000000..1963051
--- /dev/null
+++ b/src/main/java/net/geedge/asw/common/util/VerifyUtil.java
@@ -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);
+ }
+
+}
diff --git a/src/main/java/net/geedge/asw/module/file/controller/FileController.java b/src/main/java/net/geedge/asw/module/file/controller/FileController.java
new file mode 100644
index 0000000..d4e9ef7
--- /dev/null
+++ b/src/main/java/net/geedge/asw/module/file/controller/FileController.java
@@ -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();
+ }
+ }
+
+}
diff --git a/src/main/java/net/geedge/asw/module/file/dao/FileContentDao.java b/src/main/java/net/geedge/asw/module/file/dao/FileContentDao.java
new file mode 100644
index 0000000..842fa7f
--- /dev/null
+++ b/src/main/java/net/geedge/asw/module/file/dao/FileContentDao.java
@@ -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> {
+
+}
diff --git a/src/main/java/net/geedge/asw/module/file/dao/FileDao.java b/src/main/java/net/geedge/asw/module/file/dao/FileDao.java
new file mode 100644
index 0000000..42bc20e
--- /dev/null
+++ b/src/main/java/net/geedge/asw/module/file/dao/FileDao.java
@@ -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);
+}
diff --git a/src/main/java/net/geedge/asw/module/file/entity/FileContentEntity.java b/src/main/java/net/geedge/asw/module/file/entity/FileContentEntity.java
new file mode 100644
index 0000000..a4973c5
--- /dev/null
+++ b/src/main/java/net/geedge/asw/module/file/entity/FileContentEntity.java
@@ -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;
+}
diff --git a/src/main/java/net/geedge/asw/module/file/entity/FileEntity.java b/src/main/java/net/geedge/asw/module/file/entity/FileEntity.java
new file mode 100644
index 0000000..330b9c3
--- /dev/null
+++ b/src/main/java/net/geedge/asw/module/file/entity/FileEntity.java
@@ -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;
+}
diff --git a/src/main/java/net/geedge/asw/module/file/service/IFileService.java b/src/main/java/net/geedge/asw/module/file/service/IFileService.java
new file mode 100644
index 0000000..1645885
--- /dev/null
+++ b/src/main/java/net/geedge/asw/module/file/service/IFileService.java
@@ -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;
+
+}
diff --git a/src/main/java/net/geedge/asw/module/file/service/impl/FileServiceImpl.java b/src/main/java/net/geedge/asw/module/file/service/impl/FileServiceImpl.java
new file mode 100644
index 0000000..3805d4a
--- /dev/null
+++ b/src/main/java/net/geedge/asw/module/file/service/impl/FileServiceImpl.java
@@ -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);
+ }
+
+}
diff --git a/src/main/java/net/geedge/asw/module/sys/controller/BaseController.java b/src/main/java/net/geedge/asw/module/sys/controller/BaseController.java
new file mode 100644
index 0000000..29ec813
--- /dev/null
+++ b/src/main/java/net/geedge/asw/module/sys/controller/BaseController.java
@@ -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();
+ }
+
+}
diff --git a/src/main/java/net/geedge/asw/module/sys/controller/SysAuthController.java b/src/main/java/net/geedge/asw/module/sys/controller/SysAuthController.java
new file mode 100644
index 0000000..0fb8979
--- /dev/null
+++ b/src/main/java/net/geedge/asw/module/sys/controller/SysAuthController.java
@@ -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);
+ }
+}
diff --git a/src/main/java/net/geedge/asw/module/sys/controller/SysRoleController.java b/src/main/java/net/geedge/asw/module/sys/controller/SysRoleController.java
new file mode 100644
index 0000000..80c00e5
--- /dev/null
+++ b/src/main/java/net/geedge/asw/module/sys/controller/SysRoleController.java
@@ -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();
+ }
+
+}
diff --git a/src/main/java/net/geedge/asw/module/sys/controller/SysUserController.java b/src/main/java/net/geedge/asw/module/sys/controller/SysUserController.java
new file mode 100644
index 0000000..328b8a5
--- /dev/null
+++ b/src/main/java/net/geedge/asw/module/sys/controller/SysUserController.java
@@ -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();
+ }
+
+}
diff --git a/src/main/java/net/geedge/asw/module/sys/dao/SysConfigDao.java b/src/main/java/net/geedge/asw/module/sys/dao/SysConfigDao.java
new file mode 100644
index 0000000..ee867c6
--- /dev/null
+++ b/src/main/java/net/geedge/asw/module/sys/dao/SysConfigDao.java
@@ -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>{
+
+}
diff --git a/src/main/java/net/geedge/asw/module/sys/dao/SysRoleDao.java b/src/main/java/net/geedge/asw/module/sys/dao/SysRoleDao.java
new file mode 100644
index 0000000..a0b8d82
--- /dev/null
+++ b/src/main/java/net/geedge/asw/module/sys/dao/SysRoleDao.java
@@ -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);
+}
diff --git a/src/main/java/net/geedge/asw/module/sys/dao/SysRoleMenuDao.java b/src/main/java/net/geedge/asw/module/sys/dao/SysRoleMenuDao.java
new file mode 100644
index 0000000..874ffc4
--- /dev/null
+++ b/src/main/java/net/geedge/asw/module/sys/dao/SysRoleMenuDao.java
@@ -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>{
+
+}
diff --git a/src/main/java/net/geedge/asw/module/sys/dao/SysUserDao.java b/src/main/java/net/geedge/asw/module/sys/dao/SysUserDao.java
new file mode 100644
index 0000000..fa2dbc4
--- /dev/null
+++ b/src/main/java/net/geedge/asw/module/sys/dao/SysUserDao.java
@@ -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>{
+
+}
diff --git a/src/main/java/net/geedge/asw/module/sys/dao/SysUserRoleDao.java b/src/main/java/net/geedge/asw/module/sys/dao/SysUserRoleDao.java
new file mode 100644
index 0000000..0de7db4
--- /dev/null
+++ b/src/main/java/net/geedge/asw/module/sys/dao/SysUserRoleDao.java
@@ -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>{
+
+}
diff --git a/src/main/java/net/geedge/asw/module/sys/entity/SysConfigEntity.java b/src/main/java/net/geedge/asw/module/sys/entity/SysConfigEntity.java
new file mode 100644
index 0000000..e61b95e
--- /dev/null
+++ b/src/main/java/net/geedge/asw/module/sys/entity/SysConfigEntity.java
@@ -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);
+ }
+}
diff --git a/src/main/java/net/geedge/asw/module/sys/entity/SysMenuEntity.java b/src/main/java/net/geedge/asw/module/sys/entity/SysMenuEntity.java
new file mode 100644
index 0000000..5be0027
--- /dev/null
+++ b/src/main/java/net/geedge/asw/module/sys/entity/SysMenuEntity.java
@@ -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;
+}
diff --git a/src/main/java/net/geedge/asw/module/sys/entity/SysRoleEntity.java b/src/main/java/net/geedge/asw/module/sys/entity/SysRoleEntity.java
new file mode 100644
index 0000000..9e67029
--- /dev/null
+++ b/src/main/java/net/geedge/asw/module/sys/entity/SysRoleEntity.java
@@ -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;
+}
diff --git a/src/main/java/net/geedge/asw/module/sys/entity/SysRoleMenuEntity.java b/src/main/java/net/geedge/asw/module/sys/entity/SysRoleMenuEntity.java
new file mode 100644
index 0000000..2c7d9b9
--- /dev/null
+++ b/src/main/java/net/geedge/asw/module/sys/entity/SysRoleMenuEntity.java
@@ -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;
+}
diff --git a/src/main/java/net/geedge/asw/module/sys/entity/SysUserEntity.java b/src/main/java/net/geedge/asw/module/sys/entity/SysUserEntity.java
new file mode 100644
index 0000000..d9ad678
--- /dev/null
+++ b/src/main/java/net/geedge/asw/module/sys/entity/SysUserEntity.java
@@ -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;
+}
diff --git a/src/main/java/net/geedge/asw/module/sys/entity/SysUserRoleEntity.java b/src/main/java/net/geedge/asw/module/sys/entity/SysUserRoleEntity.java
new file mode 100644
index 0000000..a87d771
--- /dev/null
+++ b/src/main/java/net/geedge/asw/module/sys/entity/SysUserRoleEntity.java
@@ -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;
+}
diff --git a/src/main/java/net/geedge/asw/module/sys/service/ISysAuthService.java b/src/main/java/net/geedge/asw/module/sys/service/ISysAuthService.java
new file mode 100644
index 0000000..5749b79
--- /dev/null
+++ b/src/main/java/net/geedge/asw/module/sys/service/ISysAuthService.java
@@ -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();
+
+}
diff --git a/src/main/java/net/geedge/asw/module/sys/service/ISysConfigService.java b/src/main/java/net/geedge/asw/module/sys/service/ISysConfigService.java
new file mode 100644
index 0000000..a9241fe
--- /dev/null
+++ b/src/main/java/net/geedge/asw/module/sys/service/ISysConfigService.java
@@ -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> {
+
+}
diff --git a/src/main/java/net/geedge/asw/module/sys/service/ISysRoleMenuService.java b/src/main/java/net/geedge/asw/module/sys/service/ISysRoleMenuService.java
new file mode 100644
index 0000000..8b4c1c8
--- /dev/null
+++ b/src/main/java/net/geedge/asw/module/sys/service/ISysRoleMenuService.java
@@ -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> {
+
+}
diff --git a/src/main/java/net/geedge/asw/module/sys/service/ISysRoleService.java b/src/main/java/net/geedge/asw/module/sys/service/ISysRoleService.java
new file mode 100644
index 0000000..7e25c23
--- /dev/null
+++ b/src/main/java/net/geedge/asw/module/sys/service/ISysRoleService.java
@@ -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);
+}
diff --git a/src/main/java/net/geedge/asw/module/sys/service/ISysUserRoleService.java b/src/main/java/net/geedge/asw/module/sys/service/ISysUserRoleService.java
new file mode 100644
index 0000000..5fdeaf6
--- /dev/null
+++ b/src/main/java/net/geedge/asw/module/sys/service/ISysUserRoleService.java
@@ -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> {
+
+}
diff --git a/src/main/java/net/geedge/asw/module/sys/service/ISysUserService.java b/src/main/java/net/geedge/asw/module/sys/service/ISysUserService.java
new file mode 100644
index 0000000..9dfed51
--- /dev/null
+++ b/src/main/java/net/geedge/asw/module/sys/service/ISysUserService.java
@@ -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);
+}
diff --git a/src/main/java/net/geedge/asw/module/sys/service/impl/SysAuthServiceImpl.java b/src/main/java/net/geedge/asw/module/sys/service/impl/SysAuthServiceImpl.java
new file mode 100644
index 0000000..5de1c45
--- /dev/null
+++ b/src/main/java/net/geedge/asw/module/sys/service/impl/SysAuthServiceImpl.java
@@ -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;
+ }
+}
diff --git a/src/main/java/net/geedge/asw/module/sys/service/impl/SysConfigServiceImpl.java b/src/main/java/net/geedge/asw/module/sys/service/impl/SysConfigServiceImpl.java
new file mode 100644
index 0000000..cd7a39d
--- /dev/null
+++ b/src/main/java/net/geedge/asw/module/sys/service/impl/SysConfigServiceImpl.java
@@ -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 {
+
+}
diff --git a/src/main/java/net/geedge/asw/module/sys/service/impl/SysRoleMenuServiceImpl.java b/src/main/java/net/geedge/asw/module/sys/service/impl/SysRoleMenuServiceImpl.java
new file mode 100644
index 0000000..7f405ac
--- /dev/null
+++ b/src/main/java/net/geedge/asw/module/sys/service/impl/SysRoleMenuServiceImpl.java
@@ -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 {
+
+}
diff --git a/src/main/java/net/geedge/asw/module/sys/service/impl/SysRoleServiceImpl.java b/src/main/java/net/geedge/asw/module/sys/service/impl/SysRoleServiceImpl.java
new file mode 100644
index 0000000..4a283a2
--- /dev/null
+++ b/src/main/java/net/geedge/asw/module/sys/service/impl/SysRoleServiceImpl.java
@@ -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);
+ }
+
+}
diff --git a/src/main/java/net/geedge/asw/module/sys/service/impl/SysUserRoleServiceImpl.java b/src/main/java/net/geedge/asw/module/sys/service/impl/SysUserRoleServiceImpl.java
new file mode 100644
index 0000000..9aba5d0
--- /dev/null
+++ b/src/main/java/net/geedge/asw/module/sys/service/impl/SysUserRoleServiceImpl.java
@@ -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 {
+
+}
diff --git a/src/main/java/net/geedge/asw/module/sys/service/impl/SysUserServiceImpl.java b/src/main/java/net/geedge/asw/module/sys/service/impl/SysUserServiceImpl.java
new file mode 100644
index 0000000..a89aa44
--- /dev/null
+++ b/src/main/java/net/geedge/asw/module/sys/service/impl/SysUserServiceImpl.java
@@ -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);
+ }
+
+}
diff --git a/src/main/resources/application-magic-api.yml b/src/main/resources/application-magic-api.yml
new file mode 100644
index 0000000..64266ce
--- /dev/null
+++ b/src/main/resources/application-magic-api.yml
@@ -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 # 关闭时最多等待任务执行完毕的时间 \ No newline at end of file
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
new file mode 100644
index 0000000..1300f30
--- /dev/null
+++ b/src/main/resources/application.yml
@@ -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 \ No newline at end of file
diff --git a/src/main/resources/config/asw.properties b/src/main/resources/config/asw.properties
new file mode 100644
index 0000000..d388294
--- /dev/null
+++ b/src/main/resources/config/asw.properties
@@ -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 \ No newline at end of file
diff --git a/src/main/resources/config/logback-spring.xml b/src/main/resources/config/logback-spring.xml
new file mode 100644
index 0000000..1dbdc8a
--- /dev/null
+++ b/src/main/resources/config/logback-spring.xml
@@ -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>
diff --git a/src/main/resources/db/migration/V1.0.01__INIT_TABLES.sql b/src/main/resources/db/migration/V1.0.01__INIT_TABLES.sql
new file mode 100644
index 0000000..c46f34d
--- /dev/null
+++ b/src/main/resources/db/migration/V1.0.01__INIT_TABLES.sql
@@ -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; \ No newline at end of file
diff --git a/src/main/resources/static/banner.txt b/src/main/resources/static/banner.txt
new file mode 100644
index 0000000..17eb02b
--- /dev/null
+++ b/src/main/resources/static/banner.txt
@@ -0,0 +1,8 @@
+ _____ _ _ _ __ __ _
+ /\ / ____| | | | | | \ \ / / | |
+ / \ _ __ _ __| (___ | | _____| |_ ___| |__ \ \ /\ / /__ _ __| | _____
+ / /\ \ | '_ \| '_ \\___ \| |/ / _ \ __/ __| '_ \ \ \/ \/ / _ \| '__| |/ / __|
+ / ____ \| |_) | |_) |___) | < __/ || (__| | | | \ /\ / (_) | | | <\__ \
+ /_/ \_\ .__/| .__/_____/|_|\_\___|\__\___|_| |_| \/ \/ \___/|_| |_|\_\___/
+ | | | |
+ |_| |_| Powered by GeedgeNetworks \ No newline at end of file
diff --git a/src/main/resources/static/magic-editor.js b/src/main/resources/static/magic-editor.js
new file mode 100644
index 0000000..17c6152
--- /dev/null
+++ b/src/main/resources/static/magic-editor.js
@@ -0,0 +1,10 @@
+var MAGIC_EDITOR_CONFIG = {
+ title: 'digital horizon api',
+ header: {
+ skin: true, // 屏蔽皮肤按钮
+ document: true, // 屏蔽文档按钮
+ repo: false, // 屏蔽gitee和github
+ qqGroup: false // 屏蔽加入QQ群
+ }
+ // 其它配置参考本页中其它配置项
+} \ No newline at end of file
diff --git a/src/test/java/net/geedge/asw/AppSketchWorksApplicationTests.java b/src/test/java/net/geedge/asw/AppSketchWorksApplicationTests.java
new file mode 100644
index 0000000..14e16bc
--- /dev/null
+++ b/src/test/java/net/geedge/asw/AppSketchWorksApplicationTests.java
@@ -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() {
+ }
+
+}