+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *
+ * http://www.gnu.org/licenses/lgpl.html + *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springblade.core.oss;
+
+import com.qiniu.common.Zone;
+import com.qiniu.http.Response;
+import com.qiniu.storage.BucketManager;
+import com.qiniu.storage.UploadManager;
+import com.qiniu.storage.model.FileInfo;
+import com.qiniu.util.Auth;
+import lombok.AllArgsConstructor;
+import lombok.SneakyThrows;
+import org.springblade.core.oss.model.BladeFile;
+import org.springblade.core.oss.model.OssFile;
+import org.springblade.core.oss.props.OssProperties;
+import org.springblade.core.oss.rule.OssRule;
+import org.springblade.core.tool.utils.CollectionUtil;
+import org.springblade.core.tool.utils.Func;
+import org.springblade.core.tool.utils.StringPool;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.InputStream;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * QiniuTemplate
+ *
+ * @author Chill
+ */
+@AllArgsConstructor
+public class QiniuTemplate {
+ private Auth auth;
+ private UploadManager uploadManager;
+ private BucketManager bucketManager;
+ private OssProperties ossProperties;
+ private OssRule ossRule;
+
+
+ @SneakyThrows
+ public void makeBucket(String bucketName) {
+ if (!CollectionUtil.contains(bucketManager.buckets(), getBucketName(bucketName))) {
+ bucketManager.createBucket(getBucketName(bucketName), Zone.zone0().getRegion());
+ }
+ }
+
+
+ @SneakyThrows
+ public void removeBucket(String bucketName) {
+ bucketManager.deleteBucket(getBucketName(bucketName));
+ }
+
+
+ @SneakyThrows
+ public boolean bucketExists(String bucketName) {
+ return CollectionUtil.contains(bucketManager.buckets(), getBucketName(bucketName));
+ }
+
+
+ @SneakyThrows
+ public void copyFile(String bucketName, String fileName, String destBucketName) {
+ bucketManager.copy(getBucketName(bucketName), fileName, getBucketName(destBucketName), fileName);
+ }
+
+
+ @SneakyThrows
+ public void copyFile(String bucketName, String fileName, String destBucketName, String destFileName) {
+ bucketManager.copy(getBucketName(bucketName), fileName, getBucketName(destBucketName), destFileName);
+ }
+
+
+ @SneakyThrows
+ public OssFile statFile(String fileName) {
+ return statFile(ossProperties.getBucketName(), fileName);
+ }
+
+
+ @SneakyThrows
+ public OssFile statFile(String bucketName, String fileName) {
+ FileInfo stat = bucketManager.stat(getBucketName(bucketName), fileName);
+ OssFile ossFile = new OssFile();
+ ossFile.setName(stat.key);
+ ossFile.setName(Func.isEmpty(stat.key) ? fileName : stat.key);
+ ossFile.setLink(fileLink(ossFile.getName()));
+ ossFile.setHash(stat.hash);
+ ossFile.setLength(stat.fsize);
+ ossFile.setPutTime(new Date(stat.putTime / 10000));
+ ossFile.setContentType(stat.mimeType);
+ return ossFile;
+ }
+
+
+ @SneakyThrows
+ public String filePath(String fileName) {
+ return getBucketName().concat(StringPool.SLASH).concat(fileName);
+ }
+
+
+ @SneakyThrows
+ public String filePath(String bucketName, String fileName) {
+ return getBucketName(bucketName).concat(StringPool.SLASH).concat(fileName);
+ }
+
+
+ @SneakyThrows
+ public String fileLink(String fileName) {
+ return ossProperties.getEndpoint().concat(StringPool.SLASH).concat(fileName);
+ }
+
+
+ @SneakyThrows
+ public String fileLink(String bucketName, String fileName) {
+ return ossProperties.getEndpoint().concat(StringPool.SLASH).concat(fileName);
+ }
+
+
+ @SneakyThrows
+ public BladeFile putFile(MultipartFile file) {
+ return putFile(ossProperties.getBucketName(), file.getOriginalFilename(), file);
+ }
+
+
+ @SneakyThrows
+ public BladeFile putFile(String fileName, MultipartFile file) {
+ return putFile(ossProperties.getBucketName(), fileName, file);
+ }
+
+
+ @SneakyThrows
+ public BladeFile putFile(String bucketName, String fileName, MultipartFile file) {
+ return putFile(bucketName, fileName, file);
+ }
+
+
+ @SneakyThrows
+ public BladeFile putFile(String fileName, InputStream stream) {
+ return putFile(ossProperties.getBucketName(), fileName, stream);
+ }
+
+
+ @SneakyThrows
+ public BladeFile putFile(String bucketName, String fileName, InputStream stream) {
+ return put(bucketName, stream, fileName, false);
+ }
+
+ @SneakyThrows
+ public BladeFile put(String bucketName, InputStream stream, String key, boolean cover) {
+ makeBucket(bucketName);
+ key = getFileName(key);
+ // 覆盖上传
+ if (cover) {
+ uploadManager.put(stream, key, getUploadToken(bucketName, key), null, null);
+ } else {
+ Response response = uploadManager.put(stream, key, getUploadToken(bucketName), null, null);
+ int retry = 0;
+ int retryCount = 5;
+ while (response.needRetry() && retry < retryCount) {
+ response = uploadManager.put(stream, key, getUploadToken(bucketName), null, null);
+ retry++;
+ }
+ }
+ BladeFile file = new BladeFile();
+ file.setName(key);
+ file.setLink(fileLink(bucketName, key));
+ return file;
+ }
+
+
+ @SneakyThrows
+ public void removeFile(String fileName) {
+ bucketManager.delete(getBucketName(), fileName);
+ }
+
+
+ @SneakyThrows
+ public void removeFile(String bucketName, String fileName) {
+ bucketManager.delete(getBucketName(bucketName), fileName);
+ }
+
+
+ @SneakyThrows
+ public void removeFiles(List
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springblade.core.oss.config;
+
+import com.qiniu.common.Zone;
+import com.qiniu.storage.BucketManager;
+import com.qiniu.storage.UploadManager;
+import com.qiniu.util.Auth;
+import lombok.AllArgsConstructor;
+import org.springblade.core.oss.QiniuTemplate;
+import org.springblade.core.oss.props.OssProperties;
+import org.springblade.core.oss.rule.BladeOssRule;
+import org.springblade.core.oss.rule.OssRule;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * Oss配置类
+ *
+ * @author Chill
+ */
+@Configuration
+@AllArgsConstructor
+@EnableConfigurationProperties(OssProperties.class)
+@ConditionalOnProperty(value = "oss.enable", havingValue = "true")
+public class OssConfiguration {
+
+ private OssProperties ossProperties;
+
+ @Bean
+ @ConditionalOnMissingBean(OssRule.class)
+ public OssRule ossRule() {
+ return new BladeOssRule();
+ }
+
+ @Bean
+ public com.qiniu.storage.Configuration qiniuConfiguration() {
+ return new com.qiniu.storage.Configuration(Zone.zone0());
+ }
+
+ @Bean
+ public Auth auth() {
+ return Auth.create(ossProperties.getAccessKey(), ossProperties.getSecretKey());
+ }
+
+ @Bean
+ @ConditionalOnBean(com.qiniu.storage.Configuration.class)
+ public UploadManager uploadManager(com.qiniu.storage.Configuration cfg) {
+ return new UploadManager(cfg);
+ }
+
+ @Bean
+ @ConditionalOnBean(com.qiniu.storage.Configuration.class)
+ public BucketManager bucketManager(com.qiniu.storage.Configuration cfg) {
+ return new BucketManager(auth(), cfg);
+ }
+
+ @Bean
+ @ConditionalOnMissingBean(QiniuTemplate.class)
+ @ConditionalOnBean({Auth.class, UploadManager.class, BucketManager.class, OssRule.class})
+ public QiniuTemplate qiniuTemplate(Auth auth, UploadManager uploadManager, BucketManager bucketManager, OssRule ossRule) {
+ return new QiniuTemplate(auth, uploadManager, bucketManager, ossProperties, ossRule);
+ }
+
+
+}
diff --git a/blade-core-oss/src/main/java/org/springblade/core/oss/model/BladeFile.java b/blade-core-oss/src/main/java/org/springblade/core/oss/model/BladeFile.java
new file mode 100644
index 0000000..bc02327
--- /dev/null
+++ b/blade-core-oss/src/main/java/org/springblade/core/oss/model/BladeFile.java
@@ -0,0 +1,35 @@
+/**
+ * Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
+ *
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springblade.core.oss.model;
+
+import lombok.Data;
+
+/**
+ * BladeFile
+ *
+ * @author Chill
+ */
+@Data
+public class BladeFile {
+ /**
+ * 文件地址
+ */
+ private String link;
+ /**
+ * 文件名
+ */
+ private String name;
+}
diff --git a/blade-core-oss/src/main/java/org/springblade/core/oss/model/OssFile.java b/blade-core-oss/src/main/java/org/springblade/core/oss/model/OssFile.java
new file mode 100644
index 0000000..999f6cd
--- /dev/null
+++ b/blade-core-oss/src/main/java/org/springblade/core/oss/model/OssFile.java
@@ -0,0 +1,53 @@
+/**
+ * Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
+ *
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springblade.core.oss.model;
+
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * OssFile
+ *
+ * @author Chill
+ */
+@Data
+public class OssFile {
+ /**
+ * 文件地址
+ */
+ private String link;
+ /**
+ * 文件名
+ */
+ private String name;
+ /**
+ * 文件hash值
+ */
+ public String hash;
+ /**
+ * 文件大小
+ */
+ private long length;
+ /**
+ * 文件上传时间
+ */
+ private Date putTime;
+ /**
+ * 文件contentType
+ */
+ private String contentType;
+}
diff --git a/blade-core-oss/src/main/java/org/springblade/core/oss/props/OssProperties.java b/blade-core-oss/src/main/java/org/springblade/core/oss/props/OssProperties.java
new file mode 100644
index 0000000..1a46887
--- /dev/null
+++ b/blade-core-oss/src/main/java/org/springblade/core/oss/props/OssProperties.java
@@ -0,0 +1,65 @@
+/**
+ * Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
+ *
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springblade.core.oss.props;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * Minio参数配置类
+ *
+ * @author Chill
+ */
+@Data
+@ConfigurationProperties(prefix = "oss")
+public class OssProperties {
+
+ /**
+ * 是否启用
+ */
+ private Boolean enable;
+
+ /**
+ * 对象存储名称
+ */
+ private String name;
+
+ /**
+ * 是否开启租户模式
+ */
+ private Boolean tenantMode;
+
+ /**
+ * 对象存储服务的URL
+ */
+ private String endpoint;
+
+ /**
+ * Access key就像用户ID,可以唯一标识你的账户
+ */
+ private String accessKey;
+
+ /**
+ * Secret key是你账户的密码
+ */
+ private String secretKey;
+
+ /**
+ * 默认的存储桶名称
+ */
+ private String bucketName = "bladex";
+
+}
diff --git a/blade-core-oss/src/main/java/org/springblade/core/oss/rule/BladeOssRule.java b/blade-core-oss/src/main/java/org/springblade/core/oss/rule/BladeOssRule.java
new file mode 100644
index 0000000..4e8294a
--- /dev/null
+++ b/blade-core-oss/src/main/java/org/springblade/core/oss/rule/BladeOssRule.java
@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
+ *
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springblade.core.oss.rule;
+
+import lombok.AllArgsConstructor;
+import org.springblade.core.tool.utils.DateUtil;
+import org.springblade.core.tool.utils.FileUtil;
+import org.springblade.core.tool.utils.StringPool;
+import org.springblade.core.tool.utils.StringUtil;
+
+/**
+ * 默认存储桶生成规则
+ *
+ * @author Chill
+ */
+@AllArgsConstructor
+public class BladeOssRule implements OssRule {
+
+ @Override
+ public String bucketName(String bucketName) {
+ return bucketName;
+ }
+
+ @Override
+ public String fileName(String originalFilename) {
+ return "upload" + StringPool.SLASH + DateUtil.today() + StringPool.SLASH + StringUtil.randomUUID() + StringPool.DOT + FileUtil.getFileExtension(originalFilename);
+ }
+
+}
diff --git a/blade-core-oss/src/main/java/org/springblade/core/oss/rule/OssRule.java b/blade-core-oss/src/main/java/org/springblade/core/oss/rule/OssRule.java
new file mode 100644
index 0000000..6bdecb6
--- /dev/null
+++ b/blade-core-oss/src/main/java/org/springblade/core/oss/rule/OssRule.java
@@ -0,0 +1,41 @@
+/**
+ * Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
+ *
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springblade.core.oss.rule;
+
+/**
+ * Oss通用规则
+ *
+ * @author Chill
+ */
+public interface OssRule {
+
+ /**
+ * 获取存储桶规则
+ *
+ * @param bucketName 存储桶名称
+ * @return String
+ */
+ String bucketName(String bucketName);
+
+ /**
+ * 获取文件名规则
+ *
+ * @param originalFilename 文件名
+ * @return String
+ */
+ String fileName(String originalFilename);
+
+}
diff --git a/blade-core-secure/pom.xml b/blade-core-secure/pom.xml
index 890e82d..18bafc4 100644
--- a/blade-core-secure/pom.xml
+++ b/blade-core-secure/pom.xml
@@ -5,7 +5,7 @@
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springblade.core.test;
+
+import org.junit.runner.RunWith;
+import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;
+
+/**
+ * boot test 基类
+ *
+ * @author L.cm
+ */
+@RunWith(BladeSpringRunner.class)
+public abstract class BladeBaseTest extends AbstractJUnit4SpringContextTests {
+
+}
diff --git a/blade-core-test/src/main/java/org/springblade/core/test/BladeBootTest.java b/blade-core-test/src/main/java/org/springblade/core/test/BladeBootTest.java
new file mode 100644
index 0000000..d6d4760
--- /dev/null
+++ b/blade-core-test/src/main/java/org/springblade/core/test/BladeBootTest.java
@@ -0,0 +1,57 @@
+/**
+ * Copyright (c) 2018-2028, DreamLu 卢春梦 (qq596392912@gmail.com).
+ *
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springblade.core.test;
+
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.core.annotation.AliasFor;
+
+import java.lang.annotation.*;
+
+/**
+ * 简化 测试
+ *
+ * @author L.cm
+ */
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Inherited
+@SpringBootTest
+public @interface BladeBootTest {
+ /**
+ * 服务名:appName
+ * @return appName
+ */
+ @AliasFor("appName")
+ String value() default "blade-test";
+ /**
+ * 服务名:appName
+ * @return appName
+ */
+ @AliasFor("value")
+ String appName() default "blade-test";
+ /**
+ * profile
+ * @return profile
+ */
+ String profile() default "dev";
+ /**
+ * 启用 ServiceLoader 加载 launcherService
+ * @return 是否启用
+ */
+ boolean enableLoader() default false;
+}
diff --git a/blade-core-test/src/main/java/org/springblade/core/test/BladeBootTestException.java b/blade-core-test/src/main/java/org/springblade/core/test/BladeBootTestException.java
new file mode 100644
index 0000000..16d68d2
--- /dev/null
+++ b/blade-core-test/src/main/java/org/springblade/core/test/BladeBootTestException.java
@@ -0,0 +1,29 @@
+/**
+ * Copyright (c) 2018-2028, DreamLu 卢春梦 (qq596392912@gmail.com).
+ *
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springblade.core.test;
+
+/**
+ * blade test 异常
+ *
+ * @author L.cm
+ */
+class BladeBootTestException extends RuntimeException {
+
+ BladeBootTestException(String message) {
+ super(message);
+ }
+}
diff --git a/blade-core-test/src/main/java/org/springblade/core/test/BladeSpringRunner.java b/blade-core-test/src/main/java/org/springblade/core/test/BladeSpringRunner.java
new file mode 100644
index 0000000..bb456c0
--- /dev/null
+++ b/blade-core-test/src/main/java/org/springblade/core/test/BladeSpringRunner.java
@@ -0,0 +1,80 @@
+/**
+ * Copyright (c) 2018-2028, DreamLu 卢春梦 (qq596392912@gmail.com).
+ *
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springblade.core.test;
+
+
+import org.junit.runners.model.InitializationError;
+import org.springblade.core.launch.BladeApplication;
+import org.springblade.core.launch.constant.AppConstant;
+import org.springblade.core.launch.constant.NacosConstant;
+import org.springblade.core.launch.constant.SentinelConstant;
+import org.springblade.core.launch.service.LauncherService;
+import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 设置启动参数
+ *
+ * @author L.cm
+ */
+public class BladeSpringRunner extends SpringJUnit4ClassRunner {
+
+ public BladeSpringRunner(Class> clazz) throws InitializationError {
+ super(clazz);
+ setUpTestClass(clazz);
+ }
+
+ private void setUpTestClass(Class> clazz) {
+ BladeBootTest bladeBootTest = AnnotationUtils.getAnnotation(clazz, BladeBootTest.class);
+ if (bladeBootTest == null) {
+ throw new BladeBootTestException(String.format("%s must be @BladeBootTest .", clazz));
+ }
+ String appName = bladeBootTest.appName();
+ String profile = bladeBootTest.profile();
+ boolean isLocalDev = BladeApplication.isLocalDev();
+ Properties props = System.getProperties();
+ props.setProperty("blade.env", profile);
+ props.setProperty("blade.name", appName);
+ props.setProperty("blade.is-local", String.valueOf(isLocalDev));
+ props.setProperty("blade.dev-mode", profile.equals(AppConstant.PROD_CODE) ? "false" : "true");
+ props.setProperty("blade.service.version", AppConstant.APPLICATION_VERSION);
+ props.setProperty("spring.application.name", appName);
+ props.setProperty("spring.profiles.active", profile);
+ props.setProperty("info.version", AppConstant.APPLICATION_VERSION);
+ props.setProperty("info.desc", appName);
+ props.setProperty("spring.cloud.nacos.discovery.server-addr", NacosConstant.NACOS_ADDR);
+ props.setProperty("spring.cloud.nacos.config.server-addr", NacosConstant.NACOS_ADDR);
+ props.setProperty("spring.cloud.nacos.config.prefix", NacosConstant.NACOS_CONFIG_PREFIX);
+ props.setProperty("spring.cloud.nacos.config.file-extension", NacosConstant.NACOS_CONFIG_FORMAT);
+ props.setProperty("spring.cloud.sentinel.transport.dashboard", SentinelConstant.SENTINEL_ADDR);
+ props.setProperty("spring.main.allow-bean-definition-overriding", "true");
+ // 加载自定义组件
+ if (bladeBootTest.enableLoader()) {
+ List
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springblade.core.tool.utils;
+
+/**
+ * char 常量池
+ *
+ * @author L.cm
+ */
+public interface CharPool {
+
+ char UPPER_A = 'A';
+ char LOWER_A = 'a';
+ char UPPER_Z = 'Z';
+ char LOWER_Z = 'z';
+ char DOT = '.';
+ char AT = '@';
+ char LEFT_BRACE = '{';
+ char RIGHT_BRACE = '}';
+ char LEFT_BRACKET = '(';
+ char RIGHT_BRACKET = ')';
+
+}
diff --git a/blade-core-tool/src/main/java/org/springblade/core/tool/utils/DateUtil.java b/blade-core-tool/src/main/java/org/springblade/core/tool/utils/DateUtil.java
index a7c8aff..05db0e9 100644
--- a/blade-core-tool/src/main/java/org/springblade/core/tool/utils/DateUtil.java
+++ b/blade-core-tool/src/main/java/org/springblade/core/tool/utils/DateUtil.java
@@ -1,119 +1,264 @@
-/**
- * Copyright (c) 2018-2028, DreamLu 卢春梦 (qq596392912@gmail.com).
- *
- * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.gnu.org/licenses/lgpl.html
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
package org.springblade.core.tool.utils;
+import lombok.experimental.UtilityClass;
import org.springframework.util.Assert;
import java.text.ParseException;
+import java.time.*;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.Temporal;
+import java.time.temporal.TemporalAccessor;
+import java.time.temporal.TemporalAmount;
+import java.time.temporal.TemporalQuery;
import java.util.Calendar;
import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.TimeZone;
/**
* 日期工具类
*
* @author L.cm
*/
+@UtilityClass
public class DateUtil {
+ public static final String PATTERN_DATETIME = "yyyy-MM-dd HH:mm:ss";
+ public static final String PATTERN_DATE = "yyyy-MM-dd";
+ public static final String PATTERN_TIME = "HH:mm:ss";
/**
- * 设置年
+ * 老 date 格式化
+ */
+ public static final ConcurrentDateFormat DATETIME_FORMAT = ConcurrentDateFormat.of(PATTERN_DATETIME);
+ public static final ConcurrentDateFormat DATE_FORMAT = ConcurrentDateFormat.of(PATTERN_DATE);
+ public static final ConcurrentDateFormat TIME_FORMAT = ConcurrentDateFormat.of(PATTERN_TIME);
+ /**
+ * java 8 时间格式化
+ */
+ public static final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern(DateUtil.PATTERN_DATETIME);
+ public static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern(DateUtil.PATTERN_DATE);
+ public static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern(DateUtil.PATTERN_TIME);
+
+ /**
+ * 添加年
*
- * @param date 时间
- * @param amount 年数,-1表示减少
+ * @param date 时间
+ * @param yearsToAdd 添加的年数
* @return 设置后的时间
*/
- public static Date setYears(Date date, int amount) {
- return set(date, Calendar.YEAR, amount);
+ public static Date plusYears(Date date, int yearsToAdd) {
+ return DateUtil.set(date, Calendar.YEAR, yearsToAdd);
}
/**
- * 设置月
+ * 添加月
*
- * @param date 时间
- * @param amount 月数,-1表示减少
+ * @param date 时间
+ * @param monthsToAdd 添加的月数
* @return 设置后的时间
*/
- public static Date setMonths(Date date, int amount) {
- return set(date, Calendar.MONTH, amount);
+ public static Date plusMonths(Date date, int monthsToAdd) {
+ return DateUtil.set(date, Calendar.MONTH, monthsToAdd);
}
/**
- * 设置周
+ * 添加周
*
- * @param date 时间
- * @param amount 周数,-1表示减少
+ * @param date 时间
+ * @param weeksToAdd 添加的周数
* @return 设置后的时间
*/
- public static Date setWeeks(Date date, int amount) {
- return set(date, Calendar.WEEK_OF_YEAR, amount);
+ public static Date plusWeeks(Date date, int weeksToAdd) {
+ return DateUtil.plus(date, Period.ofWeeks(weeksToAdd));
}
/**
- * 设置天
+ * 添加天
*
- * @param date 时间
- * @param amount 天数,-1表示减少
+ * @param date 时间
+ * @param daysToAdd 添加的天数
* @return 设置后的时间
*/
- public static Date setDays(Date date, int amount) {
- return set(date, Calendar.DATE, amount);
+ public static Date plusDays(Date date, long daysToAdd) {
+ return DateUtil.plus(date, Duration.ofDays(daysToAdd));
}
/**
- * 设置小时
+ * 添加小时
*
- * @param date 时间
- * @param amount 小时数,-1表示减少
+ * @param date 时间
+ * @param hoursToAdd 添加的小时数
* @return 设置后的时间
*/
- public static Date setHours(Date date, int amount) {
- return set(date, Calendar.HOUR_OF_DAY, amount);
+ public static Date plusHours(Date date, long hoursToAdd) {
+ return DateUtil.plus(date, Duration.ofHours(hoursToAdd));
}
/**
- * 设置分钟
+ * 添加分钟
*
- * @param date 时间
- * @param amount 分钟数,-1表示减少
+ * @param date 时间
+ * @param minutesToAdd 添加的分钟数
* @return 设置后的时间
*/
- public static Date setMinutes(Date date, int amount) {
- return set(date, Calendar.MINUTE, amount);
+ public static Date plusMinutes(Date date, long minutesToAdd) {
+ return DateUtil.plus(date, Duration.ofMinutes(minutesToAdd));
}
/**
- * 设置秒
+ * 添加秒
*
- * @param date 时间
- * @param amount 秒数,-1表示减少
+ * @param date 时间
+ * @param secondsToAdd 添加的秒数
* @return 设置后的时间
*/
- public static Date setSeconds(Date date, int amount) {
- return set(date, Calendar.SECOND, amount);
+ public static Date plusSeconds(Date date, long secondsToAdd) {
+ return DateUtil.plus(date, Duration.ofSeconds(secondsToAdd));
}
/**
- * 设置毫秒
+ * 添加毫秒
*
- * @param date 时间
- * @param amount 毫秒数,-1表示减少
+ * @param date 时间
+ * @param millisToAdd 添加的毫秒数
* @return 设置后的时间
*/
- public static Date setMilliseconds(Date date, int amount) {
- return set(date, Calendar.MILLISECOND, amount);
+ public static Date plusMillis(Date date, long millisToAdd) {
+ return DateUtil.plus(date, Duration.ofMillis(millisToAdd));
+ }
+
+ /**
+ * 添加纳秒
+ *
+ * @param date 时间
+ * @param nanosToAdd 添加的纳秒数
+ * @return 设置后的时间
+ */
+ public static Date plusNanos(Date date, long nanosToAdd) {
+ return DateUtil.plus(date, Duration.ofNanos(nanosToAdd));
+ }
+
+ /**
+ * 日期添加时间量
+ *
+ * @param date 时间
+ * @param amount 时间量
+ * @return 设置后的时间
+ */
+ public static Date plus(Date date, TemporalAmount amount) {
+ Instant instant = date.toInstant();
+ return Date.from(instant.plus(amount));
+ }
+
+ /**
+ * 减少年
+ *
+ * @param date 时间
+ * @param years 减少的年数
+ * @return 设置后的时间
+ */
+ public static Date minusYears(Date date, int years) {
+ return DateUtil.set(date, Calendar.YEAR, -years);
+ }
+
+ /**
+ * 减少月
+ *
+ * @param date 时间
+ * @param months 减少的月数
+ * @return 设置后的时间
+ */
+ public static Date minusMonths(Date date, int months) {
+ return DateUtil.set(date, Calendar.MONTH, -months);
+ }
+
+ /**
+ * 减少周
+ *
+ * @param date 时间
+ * @param weeks 减少的周数
+ * @return 设置后的时间
+ */
+ public static Date minusWeeks(Date date, int weeks) {
+ return DateUtil.minus(date, Period.ofWeeks(weeks));
+ }
+
+ /**
+ * 减少天
+ *
+ * @param date 时间
+ * @param days 减少的天数
+ * @return 设置后的时间
+ */
+ public static Date minusDays(Date date, long days) {
+ return DateUtil.minus(date, Duration.ofDays(days));
+ }
+
+ /**
+ * 减少小时
+ *
+ * @param date 时间
+ * @param hours 减少的小时数
+ * @return 设置后的时间
+ */
+ public static Date minusHours(Date date, long hours) {
+ return DateUtil.minus(date, Duration.ofHours(hours));
+ }
+
+ /**
+ * 减少分钟
+ *
+ * @param date 时间
+ * @param minutes 减少的分钟数
+ * @return 设置后的时间
+ */
+ public static Date minusMinutes(Date date, long minutes) {
+ return DateUtil.minus(date, Duration.ofMinutes(minutes));
+ }
+
+ /**
+ * 减少秒
+ *
+ * @param date 时间
+ * @param seconds 减少的秒数
+ * @return 设置后的时间
+ */
+ public static Date minusSeconds(Date date, long seconds) {
+ return DateUtil.minus(date, Duration.ofSeconds(seconds));
+ }
+
+ /**
+ * 减少毫秒
+ *
+ * @param date 时间
+ * @param millis 减少的毫秒数
+ * @return 设置后的时间
+ */
+ public static Date minusMillis(Date date, long millis) {
+ return DateUtil.minus(date, Duration.ofMillis(millis));
+ }
+
+ /**
+ * 减少纳秒
+ *
+ * @param date 时间
+ * @param nanos 减少的纳秒数
+ * @return 设置后的时间
+ */
+ public static Date minusNanos(Date date, long nanos) {
+ return DateUtil.minus(date, Duration.ofNanos(nanos));
+ }
+
+ /**
+ * 日期减少时间量
+ *
+ * @param date 时间
+ * @param amount 时间量
+ * @return 设置后的时间
+ */
+ public static Date minus(Date date, TemporalAmount amount) {
+ Instant instant = date.toInstant();
+ return Date.from(instant.minus(amount));
}
/**
@@ -133,14 +278,6 @@ public class DateUtil {
return c.getTime();
}
- public static final String PATTERN_DATETIME = "yyyy-MM-dd HH:mm:ss";
- public static final String PATTERN_DATE = "yyyy-MM-dd";
- public static final String PATTERN_TIME = "HH:mm:ss";
-
- public static final ConcurrentDateFormat DATETIME_FORMAT = ConcurrentDateFormat.of(PATTERN_DATETIME);
- public static final ConcurrentDateFormat DATE_FORMAT = ConcurrentDateFormat.of(PATTERN_DATE);
- public static final ConcurrentDateFormat TIME_FORMAT = ConcurrentDateFormat.of(PATTERN_TIME);
-
/**
* 日期时间格式化
*
@@ -182,6 +319,47 @@ public class DateUtil {
return ConcurrentDateFormat.of(pattern).format(date);
}
+ /**
+ * java8 日期时间格式化
+ *
+ * @param temporal 时间
+ * @return 格式化后的时间
+ */
+ public static String formatDateTime(TemporalAccessor temporal) {
+ return DATETIME_FORMATTER.format(temporal);
+ }
+
+ /**
+ * java8 日期时间格式化
+ *
+ * @param temporal 时间
+ * @return 格式化后的时间
+ */
+ public static String formatDate(TemporalAccessor temporal) {
+ return DATE_FORMATTER.format(temporal);
+ }
+
+ /**
+ * java8 时间格式化
+ *
+ * @param temporal 时间
+ * @return 格式化后的时间
+ */
+ public static String formatTime(TemporalAccessor temporal) {
+ return TIME_FORMATTER.format(temporal);
+ }
+
+ /**
+ * java8 日期格式化
+ *
+ * @param temporal 时间
+ * @param pattern 表达式
+ * @return 格式化后的时间
+ */
+ public static String format(TemporalAccessor temporal, String pattern) {
+ return DateTimeFormatter.ofPattern(pattern).format(temporal);
+ }
+
/**
* 将字符串转换为时间
*
@@ -213,4 +391,196 @@ public class DateUtil {
}
}
+ /**
+ * 将字符串转换为时间
+ *
+ * @param dateStr 时间字符串
+ * @param pattern 表达式
+ * @return 时间
+ */
+ public static
+ * When the destination file is on another file system, do a "copy and delete".
+ *
+ * @param srcFile the file to be moved
+ * @param destFile the destination file
+ * @throws NullPointerException if source or destination is {@code null}
+ * @throws IOException if source or destination is invalid
+ * @throws IOException if an IO error occurs moving the file
+ */
+ public static void moveFile(final File srcFile, final File destFile) throws IOException {
+ Assert.notNull(srcFile, "Source must not be null");
+ Assert.notNull(destFile, "Destination must not be null");
+ if (!srcFile.exists()) {
+ throw new FileNotFoundException("Source '" + srcFile + "' does not exist");
+ }
+ if (srcFile.isDirectory()) {
+ throw new IOException("Source '" + srcFile + "' is a directory");
+ }
+ if (destFile.exists()) {
+ throw new IOException("Destination '" + destFile + "' already exists");
+ }
+ if (destFile.isDirectory()) {
+ throw new IOException("Destination '" + destFile + "' is a directory");
+ }
+ final boolean rename = srcFile.renameTo(destFile);
+ if (!rename) {
+ FileUtil.copy(srcFile, destFile);
+ if (!srcFile.delete()) {
+ FileUtil.deleteQuietly(destFile);
+ throw new IOException("Failed to delete original file '" + srcFile + "' after copy to '" + destFile + "'");
+ }
+ }
+ }
+
+ /**
+ * Deletes a file, never throwing an exception. If file is a directory, delete it and all sub-directories.
+ *
+ * The difference between File.delete() and this method are:
+ *
+ *
+ *
+ * @param file file or directory to delete, can be {@code null}
+ * @return {@code true} if the file or directory was deleted, otherwise
+ * {@code false}
+ */
+ public static boolean deleteQuietly(@Nullable final File file) {
+ if (file == null) {
+ return false;
+ }
+ try {
+ if (file.isDirectory()) {
+ FileSystemUtils.deleteRecursively(file);
+ }
+ } catch (final Exception ignored) {
+ }
+
+ try {
+ return file.delete();
+ } catch (final Exception ignored) {
+ return false;
+ }
+ }
+
+}
diff --git a/blade-core-tool/src/main/java/org/springblade/core/tool/utils/StringPool.java b/blade-core-tool/src/main/java/org/springblade/core/tool/utils/StringPool.java
index b7b282c..c4a964d 100644
--- a/blade-core-tool/src/main/java/org/springblade/core/tool/utils/StringPool.java
+++ b/blade-core-tool/src/main/java/org/springblade/core/tool/utils/StringPool.java
@@ -27,7 +27,7 @@ public interface StringPool {
String AT = "@";
String ASTERISK = "*";
String STAR = ASTERISK;
- char SLASH = '/';
+ String SLASH = "/";
char BACK_SLASH = '\\';
String DOUBLE_SLASH = "#//";
String COLON = ":";
diff --git a/pom.xml b/pom.xml
index 4b4253e..7c675b3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@