From 5e2b84ea90398207683a370af562a64c4bfadeb3 Mon Sep 17 00:00:00 2001 From: Junling Bu Date: Mon, 16 Jul 2018 15:10:44 +0800 Subject: [PATCH] =?UTF-8?q?chore[litemall-os-api]=EF=BC=9A=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E8=B0=83=E6=95=B4=E4=BB=A5=E5=8F=8A=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/notify.properties | 10 ++--- litemall-os-api/.gitignore | 1 + ...torageService.java => LocalOsService.java} | 17 +++---- ...Service.java => ObjectStorageService.java} | 2 +- .../TencentOsService.java} | 18 +++----- .../litemall/os/web/OsStorageController.java | 4 +- .../src/main/resources/application.properties | 8 ++-- .../src/main/resources/tencent.properties | 12 ++--- .../org/linlinjava/litemall/os/LosTest.java | 42 ++++++++++++++++++ .../org/linlinjava/litemall/os/TosTest.java | 42 ++++++++++++++++++ .../src/test/resources/litemall.png | Bin 0 -> 8524 bytes 11 files changed, 120 insertions(+), 36 deletions(-) rename litemall-os-api/src/main/java/org/linlinjava/litemall/os/service/{FileSystemStorageService.java => LocalOsService.java} (81%) rename litemall-os-api/src/main/java/org/linlinjava/litemall/os/service/{StorageService.java => ObjectStorageService.java} (94%) rename litemall-os-api/src/main/java/org/linlinjava/litemall/os/{tencent/TencentOSService.java => service/TencentOsService.java} (90%) create mode 100644 litemall-os-api/src/test/java/org/linlinjava/litemall/os/LosTest.java create mode 100644 litemall-os-api/src/test/java/org/linlinjava/litemall/os/TosTest.java create mode 100644 litemall-os-api/src/test/resources/litemall.png diff --git a/litemall-core/src/main/resources/notify.properties b/litemall-core/src/main/resources/notify.properties index 313e3278..9606a29e 100644 --- a/litemall-core/src/main/resources/notify.properties +++ b/litemall-core/src/main/resources/notify.properties @@ -1,19 +1,19 @@ -# \u90AE\u4EF6\u53D1\u9001\u914D\u7F6E +# 邮件发送配置 sprint.mail.enable=false spring.mail.host=smtp.exmail.qq.com spring.mail.username=xxxxxx spring.mail.password=xxxxxx spring.mail.sendto=example@qq.com -# \u77ED\u4FE1\u53D1\u9001\u914D\u7F6E +# 短信发送配置 spring.sms.enable=false spring.sms.appid=111111 spring.sms.appkey=xxxxxx spring.sms.sign=xxxxxx -# \u77ED\u4FE1\u6A21\u677F\u6D88\u606F\u914D\u7F6E -# \u8BF7\u5728\u817E\u8BAF\u77ED\u4FE1\u5E73\u53F0\u914D\u7F6E\u901A\u77E5\u6D88\u606F\u6A21\u677F\uFF0C\u7136\u540E\u8FD9\u91CC\u8BBE\u7F6E\u4E0D\u540C\u77ED\u4FE1\u6A21\u677FID -# \u8BF7\u53C2\u8003LitemallNotifyService.notifySMSTemplate +# 短信模板消息配置 +# 请在腾讯短信平台配置通知消息模板,然后这里设置不同短信模板ID +# 请参考LitemallNotifyService.notifySMSTemplate spring.sms.template.paySucceed=111111 spring.sms.template.captcha=222222 diff --git a/litemall-os-api/.gitignore b/litemall-os-api/.gitignore index ee0b013a..63b6b474 100644 --- a/litemall-os-api/.gitignore +++ b/litemall-os-api/.gitignore @@ -1,3 +1,4 @@ /target/ /litemall-os-api.iml +/storage/ diff --git a/litemall-os-api/src/main/java/org/linlinjava/litemall/os/service/FileSystemStorageService.java b/litemall-os-api/src/main/java/org/linlinjava/litemall/os/service/LocalOsService.java similarity index 81% rename from litemall-os-api/src/main/java/org/linlinjava/litemall/os/service/FileSystemStorageService.java rename to litemall-os-api/src/main/java/org/linlinjava/litemall/os/service/LocalOsService.java index 3fef0c31..ebe2af6b 100644 --- a/litemall-os-api/src/main/java/org/linlinjava/litemall/os/service/FileSystemStorageService.java +++ b/litemall-os-api/src/main/java/org/linlinjava/litemall/os/service/LocalOsService.java @@ -2,7 +2,6 @@ package org.linlinjava.litemall.os.service; import java.io.IOException; -import java.io.InputStream; import java.net.MalformedURLException; import java.nio.file.*; import java.util.stream.Stream; @@ -15,10 +14,12 @@ import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; /** - * 服务器本地文件存储 + * 服务器本地对象存储服务 + * + * 缩写los(local object storage) */ -@Service("localStorage") -public class FileSystemStorageService implements StorageService { +@Service("los") +public class LocalOsService implements ObjectStorageService { @Autowired private ObjectStorageConfig osConfig; @@ -35,7 +36,7 @@ public class FileSystemStorageService implements StorageService { @Override public void store(MultipartFile file, String keyName) { try { - Files.copy(file.getInputStream(), this.rootLocation.resolve(keyName), StandardCopyOption.REPLACE_EXISTING); + Files.copy(file.getInputStream(), LocalOsService.rootLocation.resolve(keyName), StandardCopyOption.REPLACE_EXISTING); } catch (IOException e) { throw new RuntimeException("Failed to store file " + keyName, e); } @@ -44,9 +45,9 @@ public class FileSystemStorageService implements StorageService { @Override public Stream loadAll() { try { - return Files.walk(this.rootLocation, 1) - .filter(path -> !path.equals(this.rootLocation)) - .map(path -> this.rootLocation.relativize(path)); + return Files.walk(LocalOsService.rootLocation, 1) + .filter(path -> !path.equals(LocalOsService.rootLocation)) + .map(path -> LocalOsService.rootLocation.relativize(path)); } catch (IOException e) { throw new RuntimeException("Failed to read stored files", e); } diff --git a/litemall-os-api/src/main/java/org/linlinjava/litemall/os/service/StorageService.java b/litemall-os-api/src/main/java/org/linlinjava/litemall/os/service/ObjectStorageService.java similarity index 94% rename from litemall-os-api/src/main/java/org/linlinjava/litemall/os/service/StorageService.java rename to litemall-os-api/src/main/java/org/linlinjava/litemall/os/service/ObjectStorageService.java index 2354e9c1..6b2df45b 100644 --- a/litemall-os-api/src/main/java/org/linlinjava/litemall/os/service/StorageService.java +++ b/litemall-os-api/src/main/java/org/linlinjava/litemall/os/service/ObjectStorageService.java @@ -10,7 +10,7 @@ import java.util.stream.Stream; /** * 对象存储接口 */ -public interface StorageService { +public interface ObjectStorageService { /** * 存储一个文件对象 diff --git a/litemall-os-api/src/main/java/org/linlinjava/litemall/os/tencent/TencentOSService.java b/litemall-os-api/src/main/java/org/linlinjava/litemall/os/service/TencentOsService.java similarity index 90% rename from litemall-os-api/src/main/java/org/linlinjava/litemall/os/tencent/TencentOSService.java rename to litemall-os-api/src/main/java/org/linlinjava/litemall/os/service/TencentOsService.java index a3d02501..eefce850 100644 --- a/litemall-os-api/src/main/java/org/linlinjava/litemall/os/tencent/TencentOSService.java +++ b/litemall-os-api/src/main/java/org/linlinjava/litemall/os/service/TencentOsService.java @@ -1,4 +1,4 @@ -package org.linlinjava.litemall.os.tencent; +package org.linlinjava.litemall.os.service; import com.qcloud.cos.COSClient; import com.qcloud.cos.ClientConfig; @@ -8,7 +8,6 @@ import com.qcloud.cos.model.ObjectMetadata; import com.qcloud.cos.model.PutObjectRequest; import com.qcloud.cos.model.PutObjectResult; import com.qcloud.cos.region.Region; -import org.linlinjava.litemall.os.service.StorageService; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.PropertySource; import org.springframework.core.io.Resource; @@ -22,11 +21,13 @@ import java.nio.file.Path; import java.util.stream.Stream; /** - * 腾讯对象存储服务类 + * 腾讯对象存储服务 + * + * 注意:虽然腾讯对象存储英文缩写是cos(cloud object storage),但这里称之为tos(tencent object storage) */ @PropertySource(value = "classpath:tencent.properties") -@Service("tencent") -public class TencentOSService implements StorageService { +@Service("tos") +public class TencentOsService implements ObjectStorageService { @Value("${tencent.os.secretId}") private String accessKey; @@ -39,10 +40,6 @@ public class TencentOSService implements StorageService { private COSClient cosClient; - public TencentOSService() { - - } - private COSClient getCOSClient() { if (cosClient == null) { // 1 初始化用户身份信息(secretId, secretKey) @@ -56,7 +53,6 @@ public class TencentOSService implements StorageService { } private String getBaseUrl() { - //https://litemall-1256968571.cos-website.ap-guangzhou.myqcloud.com return "https://" + bucketName + ".cos-website." + region + ".myqcloud.com/"; } @@ -71,7 +67,7 @@ public class TencentOSService implements StorageService { PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, keyName, file.getInputStream(), objectMetadata); PutObjectResult putObjectResult = getCOSClient().putObject(putObjectRequest); } catch (Exception ex) { - System.console().printf(ex.getMessage()); + ex.printStackTrace(); } } diff --git a/litemall-os-api/src/main/java/org/linlinjava/litemall/os/web/OsStorageController.java b/litemall-os-api/src/main/java/org/linlinjava/litemall/os/web/OsStorageController.java index 451ce81c..13a31105 100644 --- a/litemall-os-api/src/main/java/org/linlinjava/litemall/os/web/OsStorageController.java +++ b/litemall-os-api/src/main/java/org/linlinjava/litemall/os/web/OsStorageController.java @@ -4,7 +4,7 @@ import org.linlinjava.litemall.core.util.CharUtil; import org.linlinjava.litemall.core.util.ResponseUtil; import org.linlinjava.litemall.db.domain.LitemallStorage; import org.linlinjava.litemall.db.service.LitemallStorageService; -import org.linlinjava.litemall.os.service.StorageService; +import org.linlinjava.litemall.os.service.ObjectStorageService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.Resource; import org.springframework.http.HttpHeaders; @@ -25,7 +25,7 @@ import java.util.Map; public class OsStorageController { @javax.annotation.Resource(name="${activeStorage}") - private StorageService storageService; + private ObjectStorageService storageService; @Autowired private LitemallStorageService litemallStorageService; diff --git a/litemall-os-api/src/main/resources/application.properties b/litemall-os-api/src/main/resources/application.properties index 59eb1f3b..cb2d82a3 100644 --- a/litemall-os-api/src/main/resources/application.properties +++ b/litemall-os-api/src/main/resources/application.properties @@ -3,6 +3,8 @@ server.port=8081 logging.level.org.linlinjava.litemall.os.Application=DEBUG -# \u5B58\u50A8\u5B9E\u73B0\uFF0C\u53EF\u9009\u62E9 localStorage \u6216\u8005 tencent \uFF0C\u5982\u679C\u9009\u62E9 tencent\uFF0C\u9700\u8981\u5F00\u901A\u817E\u8BAF\u5BF9\u8C61\u5B58\u50A8\u5E76\u914D\u7F6E tencent.properties -activeStorage=localStorage -#activeStorage=tencent +# 当前存储模式 +# los,本地对象存储模式,上传图片保存在服务器中 +# tos,腾讯对象存储模式,上传图片保存在腾讯云存储服务器中,请在tencent.properties配置相关信息 +activeStorage=los +#activeStorage=tos diff --git a/litemall-os-api/src/main/resources/tencent.properties b/litemall-os-api/src/main/resources/tencent.properties index 435d03ba..6d9052b6 100644 --- a/litemall-os-api/src/main/resources/tencent.properties +++ b/litemall-os-api/src/main/resources/tencent.properties @@ -1,6 +1,6 @@ - -# \u817E\u8BAF\u4E91\u5B58\u50A8\u76F8\u5173\u914D\u7F6E,\u817E\u8BAF\u4E91\u5FC5\u987B\u6253\u5F00 #\u9759\u6001\u7F51\u7AD9(https://cloud.tencent.com/document/product/436/6249) \u652F\u6301\uFF0C\u5426\u5219\u56FE\u7247\u4F1A\u76F4\u63A5\u4E0B\u8F7D\u800C\u4E0D\u662F\u663E\u793A -tencent.os.secretId="" -tencent.os.secretKey="" -tencent.os.region="" -tencent.os.bucketName="" +# 腾讯对象存储配置信息 +# 请参考 https://cloud.tencent.com/document/product/436/6249 +tencent.os.secretId="xxxxxx" +tencent.os.secretKey="xxxxxx" +tencent.os.region="xxxxxx" +tencent.os.bucketName="xxxxxx" diff --git a/litemall-os-api/src/test/java/org/linlinjava/litemall/os/LosTest.java b/litemall-os-api/src/test/java/org/linlinjava/litemall/os/LosTest.java new file mode 100644 index 00000000..002c90a2 --- /dev/null +++ b/litemall-os-api/src/test/java/org/linlinjava/litemall/os/LosTest.java @@ -0,0 +1,42 @@ +package org.linlinjava.litemall.os; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.linlinjava.litemall.os.service.LocalOsService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.core.io.Resource; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.util.FileCopyUtils; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; + +@WebAppConfiguration +@RunWith(SpringJUnit4ClassRunner.class) +@SpringBootTest +public class LosTest { + @Autowired + private LocalOsService localOsService; + + @Test + public void test() throws IOException { + String test = getClass().getClassLoader().getResource("litemall.png").getFile(); + byte[] content = (byte[])FileCopyUtils.copyToByteArray(new FileInputStream(test)); + MockMultipartFile mockMultipartFile = new MockMultipartFile("litemall.png", "litemall.png", "image/jpeg", content); + localOsService.store(mockMultipartFile, "los.png"); + Resource resource = localOsService.loadAsResource("los.png"); + String url = localOsService.generateUrl("los.png"); + System.out.println("test file " + test); + System.out.println("store file " + resource.getURI()); + System.out.println("generate url " + url); + +// localOsService.delete("los.png"); + + } + +} \ No newline at end of file diff --git a/litemall-os-api/src/test/java/org/linlinjava/litemall/os/TosTest.java b/litemall-os-api/src/test/java/org/linlinjava/litemall/os/TosTest.java new file mode 100644 index 00000000..c84d3d2e --- /dev/null +++ b/litemall-os-api/src/test/java/org/linlinjava/litemall/os/TosTest.java @@ -0,0 +1,42 @@ +package org.linlinjava.litemall.os; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.linlinjava.litemall.os.config.ObjectStorageConfig; +import org.linlinjava.litemall.os.service.TencentOsService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.core.io.Resource; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.util.FileCopyUtils; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; + +@WebAppConfiguration +@RunWith(SpringJUnit4ClassRunner.class) +@SpringBootTest +public class TosTest { + @Autowired + private TencentOsService tencentOsService; + + @Test + public void test() throws IOException { + String test = getClass().getClassLoader().getResource("litemall.png").getFile(); + byte[] content = (byte[])FileCopyUtils.copyToByteArray(new FileInputStream(test)); + MockMultipartFile mockMultipartFile = new MockMultipartFile("litemall.png", "litemall.png", "image/png", content); + tencentOsService.store(mockMultipartFile, "tos.png"); + Resource resource = tencentOsService.loadAsResource("tos.png"); + String url = tencentOsService.generateUrl("tos.png"); + System.out.println("test file " + test); + System.out.println("store file " + resource.getURI()); + System.out.println("generate url " + url); + +// tencentOsService.delete("tos.png"); + } + +} \ No newline at end of file diff --git a/litemall-os-api/src/test/resources/litemall.png b/litemall-os-api/src/test/resources/litemall.png new file mode 100644 index 0000000000000000000000000000000000000000..d16e9afa0012582aa308a4807f857195301382d5 GIT binary patch literal 8524 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 literal 0 HcmV?d00001