Showing
8 changed files
with
204 additions
and
0 deletions
| ... | @@ -48,6 +48,7 @@ dependencies { | ... | @@ -48,6 +48,7 @@ dependencies { |
| 48 | implementation 'com.aliyun:aliyun-java-sdk-core:4.6.4' //阿里云SDK核心库 | 48 | implementation 'com.aliyun:aliyun-java-sdk-core:4.6.4' //阿里云SDK核心库 |
| 49 | implementation 'com.aliyun:aliyun-java-sdk-dysmsapi:2.2.1'//阿里云短信服务SDK | 49 | implementation 'com.aliyun:aliyun-java-sdk-dysmsapi:2.2.1'//阿里云短信服务SDK |
| 50 | implementation 'com.aliyun:aliyun-java-sdk-dm:3.3.2'//阿里云邮件服务SDK | 50 | implementation 'com.aliyun:aliyun-java-sdk-dm:3.3.2'//阿里云邮件服务SDK |
| 51 | + implementation "com.aliyun.oss:aliyun-sdk-oss:3.11.1"//阿里云OSS服务 | ||
| 51 | 52 | ||
| 52 | implementation "io.zipkin.brave:brave:5.12.5" | 53 | implementation "io.zipkin.brave:brave:5.12.5" |
| 53 | implementation "io.zipkin.brave:brave-context-slf4j:5.12.5" | 54 | implementation "io.zipkin.brave:brave-context-slf4j:5.12.5" | ... | ... |
| ... | @@ -3,11 +3,14 @@ package com.infoloop.tianting.config; | ... | @@ -3,11 +3,14 @@ package com.infoloop.tianting.config; |
| 3 | import com.aliyuncs.DefaultAcsClient; | 3 | import com.aliyuncs.DefaultAcsClient; |
| 4 | import com.aliyuncs.profile.DefaultProfile; | 4 | import com.aliyuncs.profile.DefaultProfile; |
| 5 | import com.fasterxml.jackson.dataformat.xml.XmlMapper; | 5 | import com.fasterxml.jackson.dataformat.xml.XmlMapper; |
| 6 | +import com.infoloop.tianting.server.StorageService; | ||
| 7 | +import com.infoloop.tianting.server.StorageServiceFactory; | ||
| 6 | import com.infoloop.tianting.store.LoginCodeStore; | 8 | import com.infoloop.tianting.store.LoginCodeStore; |
| 7 | import com.infoloop.tianting.utils.SmsUtil; | 9 | import com.infoloop.tianting.utils.SmsUtil; |
| 8 | import io.lettuce.core.api.StatefulRedisConnection; | 10 | import io.lettuce.core.api.StatefulRedisConnection; |
| 9 | import org.springframework.beans.factory.annotation.Autowired; | 11 | import org.springframework.beans.factory.annotation.Autowired; |
| 10 | import org.springframework.beans.factory.annotation.Qualifier; | 12 | import org.springframework.beans.factory.annotation.Qualifier; |
| 13 | +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; | ||
| 11 | import org.springframework.context.annotation.Bean; | 14 | import org.springframework.context.annotation.Bean; |
| 12 | import org.springframework.context.annotation.Configuration; | 15 | import org.springframework.context.annotation.Configuration; |
| 13 | import org.springframework.context.annotation.Import; | 16 | import org.springframework.context.annotation.Import; |
| ... | @@ -35,6 +38,12 @@ public class AppConfig { | ... | @@ -35,6 +38,12 @@ public class AppConfig { |
| 35 | } | 38 | } |
| 36 | 39 | ||
| 37 | @Bean | 40 | @Bean |
| 41 | + @ConditionalOnProperty(name = "aliyun.oss.endpoint") | ||
| 42 | + public StorageService storageService(@Autowired OssConfig ossConfig) { | ||
| 43 | + return StorageServiceFactory.createStorageService(ossConfig.getEndpoint(), ossConfig.getAccessKeyId(), ossConfig.getAccessKeySecret()); | ||
| 44 | + } | ||
| 45 | + | ||
| 46 | + @Bean | ||
| 38 | public MappingJackson2XmlHttpMessageConverter mappingJackson2XmlHttpMessageConverter() { | 47 | public MappingJackson2XmlHttpMessageConverter mappingJackson2XmlHttpMessageConverter() { |
| 39 | return new MappingJackson2XmlHttpMessageConverter(new XmlMapper()); | 48 | return new MappingJackson2XmlHttpMessageConverter(new XmlMapper()); |
| 40 | } | 49 | } | ... | ... |
| 1 | +package com.infoloop.tianting.config; | ||
| 2 | + | ||
| 3 | +import lombok.Data; | ||
| 4 | +import org.springframework.boot.context.properties.ConfigurationProperties; | ||
| 5 | +import org.springframework.context.annotation.PropertySource; | ||
| 6 | +import org.springframework.stereotype.Component; | ||
| 7 | + | ||
| 8 | +@Component | ||
| 9 | +@ConfigurationProperties(prefix = "aliyun.oss") | ||
| 10 | +@Data | ||
| 11 | +@PropertySource(encoding = "UTF-8", value = "classpath:application.properties", ignoreResourceNotFound = true) | ||
| 12 | +public class OssConfig { | ||
| 13 | + | ||
| 14 | + private String accessKeyId; | ||
| 15 | + | ||
| 16 | + private String accessKeySecret; | ||
| 17 | + | ||
| 18 | + private String endpoint; | ||
| 19 | + | ||
| 20 | + private String host; | ||
| 21 | + | ||
| 22 | + private String bucket; | ||
| 23 | +} |
| 1 | +package com.infoloop.tianting.controller; | ||
| 2 | + | ||
| 3 | +import cn.dev33.satoken.annotation.SaIgnore; | ||
| 4 | +import cn.hutool.core.io.IoUtil; | ||
| 5 | +import cn.hutool.core.io.file.FileNameUtil; | ||
| 6 | +import cn.hutool.core.util.IdUtil; | ||
| 7 | +import com.github.xiaoymin.knife4j.annotations.ApiSupport; | ||
| 8 | +import com.infoloop.tianting.config.OssConfig; | ||
| 9 | +import com.infoloop.tianting.server.StorageService; | ||
| 10 | +import io.swagger.annotations.Api; | ||
| 11 | +import io.swagger.annotations.ApiOperation; | ||
| 12 | +import lombok.extern.slf4j.Slf4j; | ||
| 13 | +import org.springframework.beans.factory.annotation.Autowired; | ||
| 14 | +import org.springframework.http.HttpStatus; | ||
| 15 | +import org.springframework.http.MediaType; | ||
| 16 | +import org.springframework.util.Assert; | ||
| 17 | +import org.springframework.validation.annotation.Validated; | ||
| 18 | +import org.springframework.web.bind.annotation.GetMapping; | ||
| 19 | +import org.springframework.web.bind.annotation.PostMapping; | ||
| 20 | +import org.springframework.web.bind.annotation.RequestParam; | ||
| 21 | +import org.springframework.web.bind.annotation.ResponseStatus; | ||
| 22 | +import org.springframework.web.bind.annotation.RestController; | ||
| 23 | +import org.springframework.web.multipart.MultipartFile; | ||
| 24 | + | ||
| 25 | +import javax.annotation.Nullable; | ||
| 26 | +import javax.servlet.ServletOutputStream; | ||
| 27 | +import javax.servlet.http.HttpServletResponse; | ||
| 28 | +import java.io.File; | ||
| 29 | +import java.io.InputStream; | ||
| 30 | +import java.net.URLEncoder; | ||
| 31 | +import java.nio.charset.StandardCharsets; | ||
| 32 | +import java.time.LocalDateTime; | ||
| 33 | +import java.time.format.DateTimeFormatter; | ||
| 34 | + | ||
| 35 | +@Api(tags = "上传、下载管理") | ||
| 36 | +@ApiSupport(order = 99) | ||
| 37 | +@Slf4j | ||
| 38 | +@Validated | ||
| 39 | +@RestController | ||
| 40 | +public class UploadController { | ||
| 41 | + | ||
| 42 | + private final OssConfig ossConfig; | ||
| 43 | + | ||
| 44 | + private final StorageService storageService; | ||
| 45 | + | ||
| 46 | + @Autowired(required = false) | ||
| 47 | + public UploadController(OssConfig ossConfig, @Nullable StorageService storageService) { | ||
| 48 | + this.ossConfig = ossConfig; | ||
| 49 | + this.storageService = storageService; | ||
| 50 | + } | ||
| 51 | + | ||
| 52 | + @SaIgnore | ||
| 53 | + @ApiOperation("上传") | ||
| 54 | + @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) | ||
| 55 | + @ResponseStatus(HttpStatus.OK) | ||
| 56 | + public String upload(@RequestParam("file") MultipartFile file, | ||
| 57 | + @RequestParam(value = "folder", defaultValue = "upload") String folder) throws Exception { | ||
| 58 | + Assert.notNull(storageService, "存储服务未初始化,请检查配置 aliyun.oss.endpoint"); | ||
| 59 | + String extension = FileNameUtil.getSuffix(file.getOriginalFilename()); | ||
| 60 | + String path = generateUploadPath(folder, extension); | ||
| 61 | + File tempFile = File.createTempFile(IdUtil.fastSimpleUUID(), null); | ||
| 62 | + try { | ||
| 63 | + file.transferTo(tempFile); | ||
| 64 | + storageService.putObject(ossConfig.getBucket(), path, tempFile); | ||
| 65 | + } catch (Exception e) { | ||
| 66 | + log.error("上传失败: {}", file.getOriginalFilename(), e); | ||
| 67 | + throw new RuntimeException("文件上传失败,请稍后重试"); | ||
| 68 | + } finally { | ||
| 69 | + tempFile.delete(); | ||
| 70 | + } | ||
| 71 | + return String.format("%s/%s", ossConfig.getHost(), path); | ||
| 72 | + } | ||
| 73 | + | ||
| 74 | + @SaIgnore | ||
| 75 | + @ApiOperation("下载") | ||
| 76 | + @GetMapping(value = "/download", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE) | ||
| 77 | + @ResponseStatus(HttpStatus.OK) | ||
| 78 | + public void download(@RequestParam("path") String path, HttpServletResponse response) { | ||
| 79 | + Assert.notNull(storageService, "存储服务未初始化,请检查配置 aliyun.oss.endpoint"); | ||
| 80 | + try (InputStream inputStream = storageService.getObjectInputStream(ossConfig.getBucket(), path); | ||
| 81 | + ServletOutputStream outputStream = response.getOutputStream()) { | ||
| 82 | + String fileName = path.substring(path.lastIndexOf("/") + 1); | ||
| 83 | + response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); | ||
| 84 | + response.setHeader("Content-Disposition", "attachment; filename=\"" + URLEncoder.encode(fileName, StandardCharsets.UTF_8) + "\""); | ||
| 85 | + IoUtil.copy(inputStream, outputStream); | ||
| 86 | + } catch (Exception e) { | ||
| 87 | + log.error("下载失败: {}", path, e); | ||
| 88 | + throw new RuntimeException("文件下载失败,请稍后重试"); | ||
| 89 | + } | ||
| 90 | + } | ||
| 91 | + | ||
| 92 | + private String generateUploadPath(String folder, String extension) { | ||
| 93 | + String datePath = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")); | ||
| 94 | + return String.format("nutri-api/%s/%s/%s.%s", folder, datePath, IdUtil.fastSimpleUUID(), extension); | ||
| 95 | + } | ||
| 96 | + | ||
| 97 | +} |
| 1 | +package com.infoloop.tianting.server; | ||
| 2 | + | ||
| 3 | +import com.aliyun.oss.OSS; | ||
| 4 | +import com.aliyun.oss.OSSClientBuilder; | ||
| 5 | +import com.aliyun.oss.model.GetObjectRequest; | ||
| 6 | + | ||
| 7 | +import java.io.ByteArrayInputStream; | ||
| 8 | +import java.io.File; | ||
| 9 | +import java.io.InputStream; | ||
| 10 | + | ||
| 11 | +public class OssStorageService implements StorageService{ | ||
| 12 | + private final OSS ossClient; | ||
| 13 | + | ||
| 14 | + public OssStorageService(String endpoint, String accessKeyId, String accessKeySecret) { | ||
| 15 | + this.ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret); | ||
| 16 | + } | ||
| 17 | + | ||
| 18 | + @Override | ||
| 19 | + public void putObject(String bucketName, String key, byte[] data) { | ||
| 20 | + ossClient.putObject(bucketName, key, new ByteArrayInputStream(data)); | ||
| 21 | + } | ||
| 22 | + | ||
| 23 | + @Override | ||
| 24 | + public void putObject(String bucketName, String key, File file) { | ||
| 25 | + ossClient.putObject(bucketName, key, file); | ||
| 26 | + } | ||
| 27 | + | ||
| 28 | + @Override | ||
| 29 | + public void putObject(String bucketName, String key, InputStream stream) { | ||
| 30 | + ossClient.putObject(bucketName, key, stream); | ||
| 31 | + } | ||
| 32 | + | ||
| 33 | + @Override | ||
| 34 | + public InputStream getObjectInputStream(String bucketName, String key) { | ||
| 35 | + return ossClient.getObject(new GetObjectRequest(bucketName, key)).getObjectContent(); | ||
| 36 | + } | ||
| 37 | + | ||
| 38 | + @Override | ||
| 39 | + public void close() throws Exception { | ||
| 40 | + ossClient.shutdown(); | ||
| 41 | + } | ||
| 42 | +} |
| 1 | +package com.infoloop.tianting.server; | ||
| 2 | + | ||
| 3 | +import java.io.File; | ||
| 4 | +import java.io.InputStream; | ||
| 5 | + | ||
| 6 | +public interface StorageService extends AutoCloseable { | ||
| 7 | + | ||
| 8 | + void putObject(String bucketName, String key, byte[] data) throws Exception; | ||
| 9 | + | ||
| 10 | + void putObject(String bucketName, String key, File file) throws Exception; | ||
| 11 | + | ||
| 12 | + void putObject(String bucketName, String key, InputStream stream) throws Exception; | ||
| 13 | + | ||
| 14 | + InputStream getObjectInputStream(String bucketName, String key); | ||
| 15 | +} | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
| 1 | +package com.infoloop.tianting.server; | ||
| 2 | + | ||
| 3 | +public class StorageServiceFactory { | ||
| 4 | + public static StorageService createStorageService(String endpoint, String accessKey, String accessSecret) { | ||
| 5 | + if (endpoint.contains("aliyuncs.com")) { | ||
| 6 | + return new OssStorageService(endpoint, accessKey, accessSecret); | ||
| 7 | + } else { | ||
| 8 | + throw new IllegalArgumentException("Unsupported storage type: " + endpoint); | ||
| 9 | + } | ||
| 10 | + } | ||
| 11 | +} | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
| ... | @@ -104,6 +104,12 @@ sa-token.sign.secret-key=4Jk2giMQw8D7lHJcU8fv8CXjSOhjp5Ee | ... | @@ -104,6 +104,12 @@ sa-token.sign.secret-key=4Jk2giMQw8D7lHJcU8fv8CXjSOhjp5Ee |
| 104 | ##\u65E5\u5FD7\u914D\u7F6E | 104 | ##\u65E5\u5FD7\u914D\u7F6E |
| 105 | logging.config=classpath:logback-spring.xml | 105 | logging.config=classpath:logback-spring.xml |
| 106 | 106 | ||
| 107 | +aliyun.oss.endPoint=oss-cn-shanghai.aliyuncs.com | ||
| 108 | +aliyun.oss.host=infoloop-public-qa.oss-cn-shanghai.aliyuncs.com | ||
| 109 | +aliyun.oss.accessKeyId=LTAIo9gcY5sjIZdN | ||
| 110 | +aliyun.oss.accessKeySecret=gOOkVY4Euj3MSbPyg1tZvPbZbUssj5 | ||
| 111 | +aliyun.oss.bucket=infoloop-public-qa | ||
| 112 | + | ||
| 107 | ##\u77ED\u4FE1\u914D\u7F6E | 113 | ##\u77ED\u4FE1\u914D\u7F6E |
| 108 | sms-config.accessKeyId= | 114 | sms-config.accessKeyId= |
| 109 | sms-config.accessKeySecret= | 115 | sms-config.accessKeySecret= | ... | ... |
-
Please register or login to post a comment