commit 0a3acaa77d852bd18cc28e07f5e23b3b9437bcab Author: Yueng <14617246+YuengMeYuuer@user.noreply.gitee.com> Date: Wed Jul 24 16:28:20 2024 +0800 初始化 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..09bdfea --- /dev/null +++ b/.gitignore @@ -0,0 +1,46 @@ +###################################################################### +# Build Tools + +.gradle +/build/ +!gradle/wrapper/gradle-wrapper.jar + +target/ +!.mvn/wrapper/maven-wrapper.jar + +###################################################################### +# IDE + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### JRebel ### +rebel.xml +### NetBeans ### +nbproject/private/ +build/* +nbbuild/ +dist/ +nbdist/ +.nb-gradle/ + +###################################################################### +# Others +*.log +*.xml.versionsBackup +*.swp + +!*/build/*.java +!*/build/*.html +!*/build/*.xml \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..45521e2 --- /dev/null +++ b/pom.xml @@ -0,0 +1,107 @@ + + + + com.muyu + cloud-modules + 3.6.3 + + 4.0.0 + + cloud-modules-file + + + cloud-modules-file文件服务 + + + 17 + 17 + UTF-8 + + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-discovery + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-config + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-sentinel + + + + + org.springframework.boot + spring-boot-starter-actuator + + + + + io.minio + minio + ${minio.version} + + + + + com.muyu + cloud-common-system + + + + + com.muyu + cloud-common-api-doc + + + + + + ${project.artifactId} + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.0 + + ${maven.compiler.source} + ${maven.compiler.target} + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + true + + + + + + diff --git a/src/main/java/com/muyu/file/CloudFileApplication.java b/src/main/java/com/muyu/file/CloudFileApplication.java new file mode 100644 index 0000000..4e1c628 --- /dev/null +++ b/src/main/java/com/muyu/file/CloudFileApplication.java @@ -0,0 +1,17 @@ +package com.muyu.file; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; + +/** + * 文件服务 + * + * @author muyu + */ +@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) +public class CloudFileApplication { + public static void main (String[] args) { + SpringApplication.run(CloudFileApplication.class, args); + } +} diff --git a/src/main/java/com/muyu/file/config/MinioConfig.java b/src/main/java/com/muyu/file/config/MinioConfig.java new file mode 100644 index 0000000..af8c1bb --- /dev/null +++ b/src/main/java/com/muyu/file/config/MinioConfig.java @@ -0,0 +1,72 @@ +package com.muyu.file.config; + +import io.minio.MinioClient; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Minio 配置信息 + * + * @author muyu + */ +@Configuration +@ConfigurationProperties(prefix = "minio") +public class MinioConfig { + /** + * 服务地址 + */ + private String url; + + /** + * 用户名 + */ + private String accessKey; + + /** + * 密码 + */ + private String secretKey; + + /** + * 存储桶名称 + */ + private String bucketName; + + public String getUrl () { + return url; + } + + public void setUrl (String url) { + this.url = url; + } + + public String getAccessKey () { + return accessKey; + } + + public void setAccessKey (String accessKey) { + this.accessKey = accessKey; + } + + public String getSecretKey () { + return secretKey; + } + + public void setSecretKey (String secretKey) { + this.secretKey = secretKey; + } + + public String getBucketName () { + return bucketName; + } + + public void setBucketName (String bucketName) { + this.bucketName = bucketName; + } + + @Bean + public MinioClient getMinioClient () { + return MinioClient.builder().endpoint(url).credentials(accessKey, secretKey).build(); + } +} diff --git a/src/main/java/com/muyu/file/config/ResourcesConfig.java b/src/main/java/com/muyu/file/config/ResourcesConfig.java new file mode 100644 index 0000000..78a8be0 --- /dev/null +++ b/src/main/java/com/muyu/file/config/ResourcesConfig.java @@ -0,0 +1,48 @@ +package com.muyu.file.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import java.io.File; + +/** + * 通用映射配置 + * + * @author muyu + */ +@Configuration +public class ResourcesConfig implements WebMvcConfigurer { + /** + * 资源映射路径 前缀 + */ + @Value("${file.prefix}") + public String localFilePrefix; + /** + * 上传文件存储在本地的根路径 + */ + @Value("${file.path}") + private String localFilePath; + + @Override + public void addResourceHandlers (ResourceHandlerRegistry registry) { + /** 本地文件上传路径 */ + registry.addResourceHandler(localFilePrefix + "/**") + .addResourceLocations("file:" + localFilePath + File.separator); + } + + /** + * 开启跨域 + */ + @Override + public void addCorsMappings (CorsRegistry registry) { + // 设置允许跨域的路由 + registry.addMapping(localFilePrefix + "/**") + // 设置允许跨域请求的域名 + .allowedOrigins("*") + // 设置允许的方法 + .allowedMethods("GET"); + } +} diff --git a/src/main/java/com/muyu/file/controller/SysFileController.java b/src/main/java/com/muyu/file/controller/SysFileController.java new file mode 100644 index 0000000..ed04d11 --- /dev/null +++ b/src/main/java/com/muyu/file/controller/SysFileController.java @@ -0,0 +1,44 @@ +package com.muyu.file.controller; + +import com.muyu.common.core.domain.Result; +import com.muyu.common.core.utils.file.FileUtils; +import com.muyu.file.service.ISysFileService; +import com.muyu.common.system.domain.SysFile; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +/** + * 文件请求处理 + * + * @author muyu + */ +@RestController +public class SysFileController { + private static final Logger log = LoggerFactory.getLogger(SysFileController.class); + + @Autowired + private ISysFileService sysFileService; + + /** + * 文件上传请求 + */ + @PostMapping("/upload") + public Result upload (@RequestPart(value = "file") MultipartFile file) { + try { + // 上传并返回访问地址 + String url = sysFileService.uploadFile(file); + SysFile sysFile = new SysFile(); + sysFile.setName(FileUtils.getName(url)); + sysFile.setUrl(url); + return Result.success(sysFile); + } catch (Exception e) { + log.error("上传文件失败", e); + return Result.error(e.getMessage()); + } + } +} diff --git a/src/main/java/com/muyu/file/service/ISysFileService.java b/src/main/java/com/muyu/file/service/ISysFileService.java new file mode 100644 index 0000000..416c26c --- /dev/null +++ b/src/main/java/com/muyu/file/service/ISysFileService.java @@ -0,0 +1,21 @@ +package com.muyu.file.service; + +import org.springframework.web.multipart.MultipartFile; + +/** + * 文件上传接口 + * + * @author muyu + */ +public interface ISysFileService { + /** + * 文件上传接口 + * + * @param file 上传的文件 + * + * @return 访问地址 + * + * @throws Exception + */ + public String uploadFile (MultipartFile file) throws Exception; +} diff --git a/src/main/java/com/muyu/file/service/LocalSysFileServiceImpl.java b/src/main/java/com/muyu/file/service/LocalSysFileServiceImpl.java new file mode 100644 index 0000000..f3ce089 --- /dev/null +++ b/src/main/java/com/muyu/file/service/LocalSysFileServiceImpl.java @@ -0,0 +1,50 @@ +package com.muyu.file.service; + +import com.muyu.file.utils.FileUploadUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Primary; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +/** + * 本地文件存储 + * + * @author muyu + */ +@Primary +@Service +public class LocalSysFileServiceImpl implements ISysFileService { + /** + * 资源映射路径 前缀 + */ + @Value("${file.prefix}") + public String localFilePrefix; + + /** + * 域名或本机访问地址 + */ + @Value("${file.domain}") + public String domain; + + /** + * 上传文件存储在本地的根路径 + */ + @Value("${file.path}") + private String localFilePath; + + /** + * 本地文件上传接口 + * + * @param file 上传的文件 + * + * @return 访问地址 + * + * @throws Exception + */ + @Override + public String uploadFile (MultipartFile file) throws Exception { + String name = FileUploadUtils.upload(localFilePath, file); + String url = domain + localFilePrefix + name; + return url; + } +} diff --git a/src/main/java/com/muyu/file/service/MinioSysFileServiceImpl.java b/src/main/java/com/muyu/file/service/MinioSysFileServiceImpl.java new file mode 100644 index 0000000..9a50de4 --- /dev/null +++ b/src/main/java/com/muyu/file/service/MinioSysFileServiceImpl.java @@ -0,0 +1,50 @@ +package com.muyu.file.service; + +import com.alibaba.nacos.common.utils.IoUtils; +import com.muyu.file.config.MinioConfig; +import com.muyu.file.utils.FileUploadUtils; +import io.minio.MinioClient; +import io.minio.PutObjectArgs; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.io.InputStream; + +/** + * Minio 文件存储 + * + * @author muyu + */ +@Service +public class MinioSysFileServiceImpl implements ISysFileService { + @Autowired + private MinioConfig minioConfig; + + @Autowired + private MinioClient client; + + /** + * Minio文件上传接口 + * + * @param file 上传的文件 + * + * @return 访问地址 + * + * @throws Exception + */ + @Override + public String uploadFile (MultipartFile file) throws Exception { + String fileName = FileUploadUtils.extractFilename(file); + InputStream inputStream = file.getInputStream(); + PutObjectArgs args = PutObjectArgs.builder() + .bucket(minioConfig.getBucketName()) + .object(fileName) + .stream(inputStream, file.getSize(), -1) + .contentType(file.getContentType()) + .build(); + client.putObject(args); + IoUtils.closeQuietly(inputStream); + return minioConfig.getUrl() + "/" + minioConfig.getBucketName() + "/" + fileName; + } +} diff --git a/src/main/java/com/muyu/file/utils/FileUploadUtils.java b/src/main/java/com/muyu/file/utils/FileUploadUtils.java new file mode 100644 index 0000000..0a48cbf --- /dev/null +++ b/src/main/java/com/muyu/file/utils/FileUploadUtils.java @@ -0,0 +1,163 @@ +package com.muyu.file.utils; + +import com.muyu.common.core.exception.file.FileException; +import com.muyu.common.core.exception.file.FileNameLengthLimitExceededException; +import com.muyu.common.core.exception.file.FileSizeLimitExceededException; +import com.muyu.common.core.exception.file.InvalidExtensionException; +import com.muyu.common.core.utils.DateUtils; +import com.muyu.common.core.utils.StringUtils; +import com.muyu.common.core.utils.file.FileTypeUtils; +import com.muyu.common.core.utils.file.MimeTypeUtils; +import com.muyu.common.core.utils.uuid.Seq; +import org.apache.commons.io.FilenameUtils; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Paths; +import java.util.Objects; + +/** + * 文件上传工具类 + * + * @author muyu + */ +public class FileUploadUtils { + /** + * 默认大小 50M + */ + public static final long DEFAULT_MAX_SIZE = 50 * 1024 * 1024; + + /** + * 默认的文件名最大长度 100 + */ + public static final int DEFAULT_FILE_NAME_LENGTH = 100; + + /** + * 根据文件路径上传 + * + * @param baseDir 相对应用的基目录 + * @param file 上传的文件 + * + * @return 文件名称 + * + * @throws IOException + */ + public static final String upload (String baseDir, MultipartFile file) throws IOException { + try { + return upload(baseDir, file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION); + } catch (FileException fe) { + throw new IOException(fe.getDefaultMessage(), fe); + } catch (Exception e) { + throw new IOException(e.getMessage(), e); + } + } + + /** + * 文件上传 + * + * @param baseDir 相对应用的基目录 + * @param file 上传的文件 + * @param allowedExtension 上传文件类型 + * + * @return 返回上传成功的文件名 + * + * @throws FileSizeLimitExceededException 如果超出最大大小 + * @throws FileNameLengthLimitExceededException 文件名太长 + * @throws IOException 比如读写文件出错时 + * @throws InvalidExtensionException 文件校验异常 + */ + public static final String upload (String baseDir, MultipartFile file, String[] allowedExtension) + throws FileSizeLimitExceededException, IOException, FileNameLengthLimitExceededException, + InvalidExtensionException { + int fileNamelength = Objects.requireNonNull(file.getOriginalFilename()).length(); + if (fileNamelength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH) { + throw new FileNameLengthLimitExceededException(FileUploadUtils.DEFAULT_FILE_NAME_LENGTH); + } + + assertAllowed(file, allowedExtension); + + String fileName = extractFilename(file); + + String absPath = getAbsoluteFile(baseDir, fileName).getAbsolutePath(); + file.transferTo(Paths.get(absPath)); + return getPathFileName(fileName); + } + + /** + * 编码文件名 + */ + public static final String extractFilename (MultipartFile file) { + return StringUtils.format("{}/{}_{}.{}", DateUtils.datePath(), + FilenameUtils.getBaseName(file.getOriginalFilename()), Seq.getId(Seq.uploadSeqType), FileTypeUtils.getExtension(file)); + } + + private static final File getAbsoluteFile (String uploadDir, String fileName) throws IOException { + File desc = new File(uploadDir + File.separator + fileName); + + if (!desc.exists()) { + if (!desc.getParentFile().exists()) { + desc.getParentFile().mkdirs(); + } + } + return desc.isAbsolute() ? desc : desc.getAbsoluteFile(); + } + + private static final String getPathFileName (String fileName) throws IOException { + String pathFileName = "/" + fileName; + return pathFileName; + } + + /** + * 文件大小校验 + * + * @param file 上传的文件 + * + * @throws FileSizeLimitExceededException 如果超出最大大小 + * @throws InvalidExtensionException 文件校验异常 + */ + public static final void assertAllowed (MultipartFile file, String[] allowedExtension) + throws FileSizeLimitExceededException, InvalidExtensionException { + long size = file.getSize(); + if (size > DEFAULT_MAX_SIZE) { + throw new FileSizeLimitExceededException(DEFAULT_MAX_SIZE / 1024 / 1024); + } + + String fileName = file.getOriginalFilename(); + String extension = FileTypeUtils.getExtension(file); + if (allowedExtension != null && !isAllowedExtension(extension, allowedExtension)) { + if (allowedExtension == MimeTypeUtils.IMAGE_EXTENSION) { + throw new InvalidExtensionException.InvalidImageExtensionException(allowedExtension, extension, + fileName); + } else if (allowedExtension == MimeTypeUtils.FLASH_EXTENSION) { + throw new InvalidExtensionException.InvalidFlashExtensionException(allowedExtension, extension, + fileName); + } else if (allowedExtension == MimeTypeUtils.MEDIA_EXTENSION) { + throw new InvalidExtensionException.InvalidMediaExtensionException(allowedExtension, extension, + fileName); + } else if (allowedExtension == MimeTypeUtils.VIDEO_EXTENSION) { + throw new InvalidExtensionException.InvalidVideoExtensionException(allowedExtension, extension, + fileName); + } else { + throw new InvalidExtensionException(allowedExtension, extension, fileName); + } + } + } + + /** + * 判断MIME类型是否是允许的MIME类型 + * + * @param extension 上传文件类型 + * @param allowedExtension 允许上传文件类型 + * + * @return true/false + */ + public static final boolean isAllowedExtension (String extension, String[] allowedExtension) { + for (String str : allowedExtension) { + if (str.equalsIgnoreCase(extension)) { + return true; + } + } + return false; + } +} diff --git a/src/main/resources/banner.txt b/src/main/resources/banner.txt new file mode 100644 index 0000000..0dd5eee --- /dev/null +++ b/src/main/resources/banner.txt @@ -0,0 +1,2 @@ +Spring Boot Version: ${spring-boot.version} +Spring Application Name: ${spring.application.name} diff --git a/src/main/resources/bootstrap.yml b/src/main/resources/bootstrap.yml new file mode 100644 index 0000000..291d677 --- /dev/null +++ b/src/main/resources/bootstrap.yml @@ -0,0 +1,47 @@ +# Tomcat +server: + port: 9300 + +# nacos线上地址 +nacos: + addr: 8.130.55.157:8848 + user-name: nacos + password: nacos + namespace: cloud-service + +# Spring +spring: + application: + # 应用名称 + name: cloud-file + profiles: + # 环境配置 + active: dev + cloud: + nacos: + discovery: + # 服务注册地址 + server-addr: ${nacos.addr} + # nacos用户名 + username: ${nacos.user-name} + # nacos密码 + password: ${nacos.password} + # 命名空间 + namespace: ${nacos.namespace} + config: + # 服务注册地址 + server-addr: ${nacos.addr} + # nacos用户名 + username: ${nacos.user-name} + # nacos密码 + password: ${nacos.password} + # 命名空间 + namespace: ${nacos.namespace} + # 配置文件格式 + file-extension: yml + # 共享配置 + shared-configs: + # 系统共享配置 + - application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension} + # 系统环境Config共享配置 + - application-config-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension} diff --git a/src/main/resources/logback/dev.xml b/src/main/resources/logback/dev.xml new file mode 100644 index 0000000..30606f7 --- /dev/null +++ b/src/main/resources/logback/dev.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + ${log.pattern} + + + + + + ${log.path}/info.log + + + + ${log.path}/info.%d{yyyy-MM-dd}.log + + 60 + + + ${log.pattern} + + + + INFO + + ACCEPT + + DENY + + + + + ${log.path}/error.log + + + + ${log.path}/error.%d{yyyy-MM-dd}.log + + 60 + + + ${log.pattern} + + + + ERROR + + ACCEPT + + DENY + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/logback/prod.xml b/src/main/resources/logback/prod.xml new file mode 100644 index 0000000..151a3cb --- /dev/null +++ b/src/main/resources/logback/prod.xml @@ -0,0 +1,88 @@ + + + + + + + + + + + + ${log.pattern} + + + + + + ${log.path}/info.log + + + + ${log.path}/info.%d{yyyy-MM-dd}.log + + 60 + + + ${log.pattern} + + + + INFO + + ACCEPT + + DENY + + + + + ${log.path}/error.log + + + + ${log.path}/error.%d{yyyy-MM-dd}.log + + 60 + + + ${log.pattern} + + + + ERROR + + ACCEPT + + DENY + + + + + + + + ${log.sky.pattern} + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/logback/test.xml b/src/main/resources/logback/test.xml new file mode 100644 index 0000000..151a3cb --- /dev/null +++ b/src/main/resources/logback/test.xml @@ -0,0 +1,88 @@ + + + + + + + + + + + + ${log.pattern} + + + + + + ${log.path}/info.log + + + + ${log.path}/info.%d{yyyy-MM-dd}.log + + 60 + + + ${log.pattern} + + + + INFO + + ACCEPT + + DENY + + + + + ${log.path}/error.log + + + + ${log.path}/error.%d{yyyy-MM-dd}.log + + 60 + + + ${log.pattern} + + + + ERROR + + ACCEPT + + DENY + + + + + + + + ${log.sky.pattern} + + + + + + + + + + + + + + + + + + + + + + +