├── .gitignore ├── README.md ├── amazon-s3-starter ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── ykrenz │ │ └── s3 │ │ └── starter │ │ ├── AmazonS3AutoConfiguration.java │ │ ├── Constants.java │ │ ├── S3ApplicationListener.java │ │ └── S3Properties.java │ └── resources │ └── META-INF │ └── spring.factories ├── api.png ├── db.png ├── db └── file.sql ├── file-server ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── ykrenz │ │ └── fileserver │ │ ├── FileServerApplication.java │ │ ├── auth │ │ ├── AbstractAuthInterceptor.java │ │ ├── AuthInterceptor.java │ │ ├── AuthUser.java │ │ ├── UserContext.java │ │ └── WebMvcConfiguration.java │ │ ├── config │ │ ├── Knife4jConfiguration.java │ │ ├── MyBatisPlusConfiguration.java │ │ ├── RedisConfiguration.java │ │ ├── StorageConfiguration.java │ │ ├── StorageProperties.java │ │ └── StorageType.java │ │ ├── controller │ │ └── FileController.java │ │ ├── entity │ │ ├── BaseEntity.java │ │ ├── FileInfo.java │ │ ├── FileLock.java │ │ └── FilePartInfo.java │ │ ├── ex │ │ ├── ApiException.java │ │ └── ExceptionAdvice.java │ │ ├── mapper │ │ ├── FileInfoMapper.java │ │ ├── FilePartInfoMapper.java │ │ └── LockMapper.java │ │ ├── model │ │ ├── ErrorCode.java │ │ ├── ResultUtil.java │ │ ├── request │ │ │ ├── CancelPartRequest.java │ │ │ ├── CompletePartRequest.java │ │ │ ├── FileInfoRequest.java │ │ │ ├── InitUploadMultipartRequest.java │ │ │ ├── SimpleUploadRequest.java │ │ │ └── UploadMultipartRequest.java │ │ └── result │ │ │ ├── FileInfoResult.java │ │ │ └── InitMultipartResult.java │ │ ├── objectname │ │ ├── Md5Generator.java │ │ ├── ObjectNameGenerator.java │ │ ├── TimestampGenerator.java │ │ └── UuidGenerator.java │ │ └── service │ │ ├── FileService.java │ │ ├── FileServiceImpl.java │ │ ├── RedisService.java │ │ └── client │ │ ├── FastDfsServerClient.java │ │ ├── FileServerClient.java │ │ ├── LockClient.java │ │ └── SimpleMysqlLock.java │ └── resources │ ├── application-dev.yml │ ├── application-prod.yml │ ├── application-test.yml │ ├── application.yml │ ├── banner.txt │ ├── logback-spring.xml │ ├── public │ └── index.html │ └── static │ ├── crc32 │ └── crc32.js │ ├── fine-uploader │ ├── LICENSE │ ├── continue.gif │ ├── dnd.js │ ├── dnd.js.map │ ├── dnd.min.js │ ├── dnd.min.js.map │ ├── edit.gif │ ├── fine-uploader-gallery.css │ ├── fine-uploader-gallery.min.css │ ├── fine-uploader-gallery.min.css.map │ ├── fine-uploader-new.css │ ├── fine-uploader-new.min.css │ ├── fine-uploader-new.min.css.map │ ├── fine-uploader.core.js │ ├── fine-uploader.core.js.map │ ├── fine-uploader.core.min.js │ ├── fine-uploader.core.min.js.map │ ├── fine-uploader.css │ ├── fine-uploader.js │ ├── fine-uploader.js.map │ ├── fine-uploader.min.css │ ├── fine-uploader.min.css.map │ ├── fine-uploader.min.js │ ├── fine-uploader.min.js.map │ ├── jquery.fine-uploader.js │ ├── jquery.fine-uploader.js.map │ ├── jquery.fine-uploader.min.js │ ├── jquery.fine-uploader.min.js.map │ ├── loading.gif │ ├── pause.gif │ ├── placeholders │ │ ├── not_available-generic.png │ │ └── waiting-generic.png │ ├── processing.gif │ ├── retry.gif │ ├── templates │ │ ├── default.html │ │ ├── gallery.html │ │ └── simple-thumbnails.html │ └── trash.gif │ ├── jquery │ ├── jquery-3.5.1.js │ └── jquery.min.js │ └── md5 │ ├── spark-md5.js │ └── spark-md5.min.js ├── pom.xml ├── request.png └── show.gif /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | 35 | log/ 36 | logs/ 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # springboot-file-server 2 | springboot 文件服务 mybatis-plus web h5 大文件 分片 断点续传 fastdfs oss minio 七牛云等兼容s3协议的 文件服务器 3 | 4 | 代码正在重构... 5 | 6 | 启动步骤:localhost:3000 7 | 8 | swagger接口文档localhost:3000/doc.html 9 | 10 | Fastdfs: fastdfs测试完成 通过crc32校验保证文件可靠性 11 | 12 | fastdfs starter客户端 https://github.com/ykrenz/fastdfs-client-spring-boot-starter 13 | 14 | 前端案例组件为fine-uploader https://github.com/FineUploader/fine-uploader 15 | 16 | ![show](show.gif) 17 | ![db](db.png) 18 | ![api](api.png) 19 | ![api](request.png) 20 | 21 | TODO s3云存储。。。 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /amazon-s3-starter/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | spring-boot-file-server 7 | io.github.ykrenz 8 | 1.0.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | amazon-s3-starter 13 | 14 | 15 | 16 | org.springframework.boot 17 | spring-boot-autoconfigure 18 | compile 19 | 20 | 21 | org.springframework.boot 22 | spring-boot-configuration-processor 23 | compile 24 | true 25 | 26 | 27 | com.amazonaws 28 | aws-java-sdk-s3 29 | 30 | 31 | org.slf4j 32 | slf4j-api 33 | 34 | 35 | -------------------------------------------------------------------------------- /amazon-s3-starter/src/main/java/com/ykrenz/s3/starter/AmazonS3AutoConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.ykrenz.s3.starter; 2 | 3 | import com.amazonaws.auth.AWSCredentials; 4 | import com.amazonaws.auth.AWSStaticCredentialsProvider; 5 | import com.amazonaws.auth.BasicAWSCredentials; 6 | import com.amazonaws.client.builder.AwsClientBuilder; 7 | import com.amazonaws.services.s3.AmazonS3; 8 | import com.amazonaws.services.s3.AmazonS3ClientBuilder; 9 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 10 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 11 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 12 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 13 | import org.springframework.context.annotation.Bean; 14 | import org.springframework.context.annotation.Configuration; 15 | 16 | /** 17 | * @Description 18 | * @Author ren 19 | * @Since 1.0 20 | */ 21 | @Configuration 22 | @ConditionalOnClass(AmazonS3.class) 23 | @EnableConfigurationProperties(S3Properties.class) 24 | @ConditionalOnProperty(prefix = Constants.PREFIX, name = "enabled", matchIfMissing = true) 25 | public class AmazonS3AutoConfiguration { 26 | @Bean 27 | @ConditionalOnMissingBean 28 | public AmazonS3 s3(S3Properties s3Properties) { 29 | String accessKey = s3Properties.getAccessKey(); 30 | String secretKey = s3Properties.getSecretKey(); 31 | String endpoint = s3Properties.getEndpoint(); 32 | String region = s3Properties.getRegion(); 33 | AWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey); 34 | AwsClientBuilder.EndpointConfiguration endpointConfiguration = 35 | new AwsClientBuilder. 36 | EndpointConfiguration(endpoint, region); 37 | 38 | return AmazonS3ClientBuilder.standard() 39 | .withCredentials(new AWSStaticCredentialsProvider(credentials)) 40 | .withEndpointConfiguration(endpointConfiguration) 41 | .build(); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /amazon-s3-starter/src/main/java/com/ykrenz/s3/starter/Constants.java: -------------------------------------------------------------------------------- 1 | package com.ykrenz.s3.starter; 2 | 3 | /** 4 | * @Description 常量 5 | * @Author ren 6 | * @Since 1.0 7 | */ 8 | public final class Constants { 9 | 10 | public static final String PREFIX = "s3"; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /amazon-s3-starter/src/main/java/com/ykrenz/s3/starter/S3ApplicationListener.java: -------------------------------------------------------------------------------- 1 | package com.ykrenz.s3.starter; 2 | 3 | import com.amazonaws.services.s3.AmazonS3; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 7 | import org.springframework.context.ApplicationListener; 8 | import org.springframework.context.event.ContextClosedEvent; 9 | 10 | import java.util.Map; 11 | 12 | public class S3ApplicationListener implements ApplicationListener { 13 | 14 | private static final Logger log = LoggerFactory.getLogger(S3ApplicationListener.class); 15 | 16 | @Override 17 | public void onApplicationEvent(ContextClosedEvent event) { 18 | Map AmazonS3ClientMap = event.getApplicationContext() 19 | .getBeansOfType(AmazonS3.class); 20 | log.info("{} AmazonS3Clients will be shutdown soon", AmazonS3ClientMap.size()); 21 | AmazonS3ClientMap.keySet().forEach(beanName -> { 22 | log.info("shutdown AmazonS3Client: {}", beanName); 23 | AmazonS3ClientMap.get(beanName).shutdown(); 24 | }); 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /amazon-s3-starter/src/main/java/com/ykrenz/s3/starter/S3Properties.java: -------------------------------------------------------------------------------- 1 | package com.ykrenz.s3.starter; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | import org.springframework.boot.context.properties.ConfigurationProperties; 6 | 7 | /** 8 | * @Description s3协议通用配置 9 | * @Author ren 10 | * @Since 1.0 11 | */ 12 | @ConfigurationProperties(Constants.PREFIX) 13 | @Setter 14 | @Getter 15 | public class S3Properties { 16 | 17 | /** 18 | * 是否开启s3配置 19 | */ 20 | private boolean enabled; 21 | /** 22 | * your accessKey 23 | */ 24 | private String accessKey; 25 | /** 26 | * your secretKey 27 | */ 28 | private String secretKey; 29 | /** 30 | * your endpoint 31 | */ 32 | private String endpoint; 33 | /** 34 | * your region 35 | */ 36 | private String region; 37 | /** 38 | * your bucketName 39 | */ 40 | private String bucketName; 41 | } -------------------------------------------------------------------------------- /amazon-s3-starter/src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ 2 | com.ykrenz.s3.starter.AmazonS3AutoConfiguration 3 | 4 | org.springframework.context.ApplicationListener=\ 5 | com.ykrenz.s3.starter.S3ApplicationListener -------------------------------------------------------------------------------- /api.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ykrenz/springboot-file-server/e86bfe64f6d69c142a07d05b4c403362a4f70572/api.png -------------------------------------------------------------------------------- /db.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ykrenz/springboot-file-server/e86bfe64f6d69c142a07d05b4c403362a4f70572/db.png -------------------------------------------------------------------------------- /db/file.sql: -------------------------------------------------------------------------------- 1 | -- -------------------------------------------------------- 2 | -- 主机: 192.168.24.130 3 | -- 服务器版本: 5.7.25 - MySQL Community Server (GPL) 4 | -- 服务器操作系统: Linux 5 | -- HeidiSQL 版本: 11.3.0.6295 6 | -- -------------------------------------------------------- 7 | 8 | /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; 9 | /*!40101 SET NAMES utf8 */; 10 | /*!50503 SET NAMES utf8mb4 */; 11 | /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; 12 | /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; 13 | /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; 14 | 15 | 16 | -- 导出 filedb 的数据库结构 17 | CREATE DATABASE IF NOT EXISTS `filedb` /*!40100 DEFAULT CHARACTER SET utf8mb4 */; 18 | USE `filedb`; 19 | 20 | -- 导出 表 filedb.file_info 结构 21 | CREATE TABLE IF NOT EXISTS `file_info` ( 22 | `id` varchar(50) NOT NULL DEFAULT '', 23 | `fileName` varchar(255) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '文件名称', 24 | `bucketName` varchar(50) NOT NULL COMMENT '存储桶 fastdfs对应group', 25 | `objectName` varchar(50) NOT NULL COMMENT '文件路径 fastdfs对应path', 26 | `fileSize` bigint(20) DEFAULT '0' COMMENT '文件大小', 27 | `md5` varchar(50) DEFAULT NULL COMMENT '文件md5值', 28 | `crc32` bigint(20) NOT NULL DEFAULT '0' COMMENT '文件crc32值', 29 | `create_time` datetime DEFAULT NULL, 30 | `update_time` datetime DEFAULT NULL, 31 | `status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '状态 1 正常 -1删除', 32 | KEY `objectName` (`objectName`), 33 | KEY `md5` (`md5`) 34 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='文件信息'; 35 | 36 | -- 数据导出被取消选择。 37 | 38 | -- 导出 表 filedb.file_lock 结构 39 | CREATE TABLE IF NOT EXISTS `file_lock` ( 40 | `lock_key` varchar(32) NOT NULL DEFAULT '' COMMENT '锁名称', 41 | `create_time` datetime NOT NULL COMMENT '生成时间', 42 | `expire_time` datetime NOT NULL COMMENT '过期时间', 43 | PRIMARY KEY (`lock_key`) USING BTREE 44 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文件锁'; 45 | 46 | -- 数据导出被取消选择。 47 | 48 | -- 导出 表 filedb.file_part_info 结构 49 | CREATE TABLE IF NOT EXISTS `file_part_info` ( 50 | `id` varchar(50) NOT NULL DEFAULT '', 51 | `uploadId` varchar(50) DEFAULT NULL COMMENT '分片上传唯一标识', 52 | `fileName` varchar(255) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '文件名称', 53 | `fileSize` bigint(20) NOT NULL DEFAULT '0' COMMENT '文件大小', 54 | `partNumber` int(11) NOT NULL DEFAULT '0' COMMENT '分片索引', 55 | `partSize` bigint(20) NOT NULL DEFAULT '0' COMMENT '分片大小', 56 | `bucketName` varchar(50) NOT NULL COMMENT '存储桶 fastdfs对应group', 57 | `objectName` varchar(50) NOT NULL COMMENT '文件路径 fastdfs对应path', 58 | `create_time` datetime DEFAULT NULL, 59 | `update_time` datetime DEFAULT NULL, 60 | `status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '状态 1 正常 -1删除 0 终止', 61 | PRIMARY KEY (`id`), 62 | KEY `uploadId` (`uploadId`) 63 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='文件分片信息'; 64 | 65 | -- 数据导出被取消选择。 66 | 67 | /*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */; 68 | /*!40014 SET FOREIGN_KEY_CHECKS=IFNULL(@OLD_FOREIGN_KEY_CHECKS, 1) */; 69 | /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; 70 | /*!40111 SET SQL_NOTES=IFNULL(@OLD_SQL_NOTES, 1) */; 71 | -------------------------------------------------------------------------------- /file-server/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | spring-boot-file-server 7 | io.github.ykrenz 8 | 1.0.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | file-server 13 | File Server 14 | 15 | 16 | 17 | io.github.ykrenz 18 | fastdfs-client-spring-boot-starter 19 | 20 | 21 | io.github.ykrenz 22 | amazon-s3-starter 23 | 24 | 25 | org.springframework.boot 26 | spring-boot-starter-web 27 | 28 | 29 | org.springframework.boot 30 | spring-boot-starter-actuator 31 | 32 | 33 | org.springframework.boot 34 | spring-boot-starter-aop 35 | 36 | 37 | org.springframework.boot 38 | spring-boot-starter-validation 39 | 40 | 41 | org.springframework.boot 42 | spring-boot-configuration-processor 43 | compile 44 | true 45 | 46 | 47 | mysql 48 | mysql-connector-java 49 | 50 | 51 | com.baomidou 52 | mybatis-plus-boot-starter 53 | 54 | 55 | com.github.xiaoymin 56 | knife4j-spring-boot-starter 57 | 58 | 59 | org.springframework.boot 60 | spring-boot-starter-test 61 | test 62 | 63 | 64 | org.springframework.boot 65 | spring-boot-starter-data-redis 66 | 67 | 68 | 69 | 70 | 71 | 72 | org.springframework.boot 73 | spring-boot-maven-plugin 74 | 75 | 76 | 77 | org.projectlombok 78 | lombok 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /file-server/src/main/java/com/ykrenz/fileserver/FileServerApplication.java: -------------------------------------------------------------------------------- 1 | package com.ykrenz.fileserver; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.transaction.annotation.EnableTransactionManagement; 6 | 7 | @EnableTransactionManagement 8 | @SpringBootApplication 9 | public class FileServerApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(FileServerApplication.class, args); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /file-server/src/main/java/com/ykrenz/fileserver/auth/AbstractAuthInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.ykrenz.fileserver.auth; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.springframework.web.servlet.HandlerInterceptor; 5 | 6 | import javax.servlet.http.HttpServletRequest; 7 | import javax.servlet.http.HttpServletResponse; 8 | 9 | /** 10 | * @author Mr Ren 11 | * @Description: 认证拦截器抽象类 12 | */ 13 | @Slf4j 14 | public abstract class AbstractAuthInterceptor implements HandlerInterceptor { 15 | 16 | @Override 17 | public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 18 | AuthUser authUser = getAuthUser(request); 19 | UserContext.setAuthUser(authUser); 20 | return true; 21 | } 22 | 23 | @Override 24 | public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { 25 | UserContext.setAuthUser(null); 26 | } 27 | 28 | /** 29 | * 验证用户信息 30 | * 31 | * @param request 32 | * @return 33 | */ 34 | protected abstract AuthUser getAuthUser(HttpServletRequest request); 35 | 36 | } 37 | -------------------------------------------------------------------------------- /file-server/src/main/java/com/ykrenz/fileserver/auth/AuthInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.ykrenz.fileserver.auth; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | import javax.servlet.http.HttpServletRequest; 6 | 7 | /** 8 | * @author Mr Ren 9 | * @Description: 认证拦截器 10 | */ 11 | @Component 12 | public class AuthInterceptor extends AbstractAuthInterceptor { 13 | 14 | @Override 15 | protected AuthUser getAuthUser(HttpServletRequest request) { 16 | //TODO 验证用户信息 17 | AuthUser authUser = new AuthUser(); 18 | authUser.setUserId("1"); 19 | return authUser; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /file-server/src/main/java/com/ykrenz/fileserver/auth/AuthUser.java: -------------------------------------------------------------------------------- 1 | package com.ykrenz.fileserver.auth; 2 | 3 | import io.swagger.annotations.ApiModel; 4 | import io.swagger.annotations.ApiModelProperty; 5 | import lombok.Data; 6 | 7 | /** 8 | * @author Mr Ren 9 | * @Description: 上下文用户信息 10 | * @date 2021/2/22 9:26 11 | */ 12 | @Data 13 | @ApiModel("登录授权用户信息") 14 | public class AuthUser { 15 | private static final long serialVersionUID = 1L; 16 | @ApiModelProperty("用户ID") 17 | private String userId; 18 | 19 | } -------------------------------------------------------------------------------- /file-server/src/main/java/com/ykrenz/fileserver/auth/UserContext.java: -------------------------------------------------------------------------------- 1 | package com.ykrenz.fileserver.auth; 2 | 3 | /** 4 | * @author Mr Ren 5 | * @Description: 用户上下文信息 6 | * @date 2021/2/22 9:25 7 | */ 8 | public class UserContext { 9 | private static final ThreadLocal LOGIN_USER_INFO_THREAD_LOCAL = new ThreadLocal<>(); 10 | 11 | /** 12 | * 设置登录用户信息 13 | * 14 | * @param authUser 15 | */ 16 | public static void setAuthUser(AuthUser authUser) { 17 | if (authUser != null) { 18 | LOGIN_USER_INFO_THREAD_LOCAL.set(authUser); 19 | } else { 20 | LOGIN_USER_INFO_THREAD_LOCAL.remove(); 21 | } 22 | } 23 | 24 | /** 25 | * 获取用户登录信息 26 | * 27 | * @return 28 | */ 29 | public static AuthUser getAuthUser() { 30 | return LOGIN_USER_INFO_THREAD_LOCAL.get(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /file-server/src/main/java/com/ykrenz/fileserver/auth/WebMvcConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.ykrenz.fileserver.auth; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.web.servlet.config.annotation.CorsRegistry; 5 | import org.springframework.web.servlet.config.annotation.InterceptorRegistry; 6 | import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; 7 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 8 | 9 | /** 10 | * 拦截配置 11 | */ 12 | @Configuration 13 | public class WebMvcConfiguration implements WebMvcConfigurer { 14 | 15 | private final AuthInterceptor authInterceptor; 16 | 17 | public WebMvcConfiguration(AuthInterceptor authInterceptor) { 18 | this.authInterceptor = authInterceptor; 19 | } 20 | 21 | /** 22 | * 跨域配置 23 | * 24 | * @param registry 25 | */ 26 | @Override 27 | public void addCorsMappings(CorsRegistry registry) { 28 | registry.addMapping("/**") 29 | .allowedOriginPatterns("*") 30 | .allowedMethods("*") 31 | .allowCredentials(true) 32 | .maxAge(3600) 33 | .allowedHeaders("*"); 34 | } 35 | 36 | @Override 37 | public void addInterceptors(InterceptorRegistry registry) { 38 | registry.addInterceptor(authInterceptor) 39 | .addPathPatterns("/**"); 40 | } 41 | 42 | @Override 43 | public void addResourceHandlers(ResourceHandlerRegistry registry) { 44 | registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/"); 45 | } 46 | } -------------------------------------------------------------------------------- /file-server/src/main/java/com/ykrenz/fileserver/config/Knife4jConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.ykrenz.fileserver.config; 2 | 3 | import io.swagger.annotations.ApiOperation; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import springfox.documentation.builders.ApiInfoBuilder; 7 | import springfox.documentation.builders.PathSelectors; 8 | import springfox.documentation.builders.RequestHandlerSelectors; 9 | import springfox.documentation.service.Contact; 10 | import springfox.documentation.spi.DocumentationType; 11 | import springfox.documentation.spring.web.plugins.Docket; 12 | import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc; 13 | 14 | /** 15 | * @author ykren 16 | * @description: swagger Knife4j 17 | */ 18 | @Configuration 19 | @EnableSwagger2WebMvc 20 | public class Knife4jConfiguration { 21 | 22 | @Bean() 23 | public Docket docket() { 24 | return new Docket(DocumentationType.SWAGGER_2) 25 | .apiInfo(new ApiInfoBuilder() 26 | .description("文件服务 fastdfs oss 七牛云等s3协议 大文件 断点续传") 27 | .termsOfServiceUrl("https://github.com/ykrenz/springboot-file-server") 28 | .contact(new Contact("ykren", "", "ykren888@163.com")) 29 | .version("1.0") 30 | .build()) 31 | //分组名称 32 | .groupName("文件服务") 33 | .select() 34 | //这里指定Controller扫描包路径 35 | .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class)) 36 | .paths(PathSelectors.any()) 37 | .build(); 38 | } 39 | } -------------------------------------------------------------------------------- /file-server/src/main/java/com/ykrenz/fileserver/config/MyBatisPlusConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.ykrenz.fileserver.config; 2 | 3 | import com.baomidou.mybatisplus.annotation.DbType; 4 | import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; 5 | import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor; 6 | import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; 7 | import org.mybatis.spring.annotation.MapperScan; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | 11 | /** 12 | * @author Mr Ren 13 | * @Description: mybatis plus配置 14 | * @date 2021/2/24 14:04 15 | */ 16 | @Configuration 17 | @MapperScan(MyBatisPlusConfiguration.MapperScanPackage) 18 | public class MyBatisPlusConfiguration { 19 | 20 | public static final String MapperScanPackage = "com.ykrenz.fileserver.mapper"; 21 | 22 | @Bean 23 | public MybatisPlusInterceptor mybatisPlusInterceptor() { 24 | MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); 25 | interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); 26 | addPaginationInnerInterceptor(interceptor); 27 | return interceptor; 28 | } 29 | 30 | private void addPaginationInnerInterceptor(MybatisPlusInterceptor interceptor) { 31 | interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /file-server/src/main/java/com/ykrenz/fileserver/config/RedisConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.ykrenz.fileserver.config; 2 | 3 | import com.fasterxml.jackson.annotation.JsonAutoDetect; 4 | import com.fasterxml.jackson.annotation.JsonInclude; 5 | import com.fasterxml.jackson.annotation.PropertyAccessor; 6 | import com.fasterxml.jackson.databind.DeserializationFeature; 7 | import com.fasterxml.jackson.databind.MapperFeature; 8 | import com.fasterxml.jackson.databind.ObjectMapper; 9 | import com.fasterxml.jackson.databind.SerializationFeature; 10 | import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator; 11 | import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; 12 | import org.springframework.context.annotation.Bean; 13 | import org.springframework.context.annotation.Configuration; 14 | import org.springframework.data.redis.connection.RedisConnectionFactory; 15 | import org.springframework.data.redis.core.RedisTemplate; 16 | import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; 17 | 18 | /** 19 | * redis配置类 20 | */ 21 | @Configuration 22 | public class RedisConfiguration { 23 | 24 | @Bean 25 | public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) { 26 | RedisTemplate template = new RedisTemplate<>(); 27 | template.setConnectionFactory(redisConnectionFactory); 28 | Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class); 29 | ObjectMapper objectMapper = new ObjectMapper(); 30 | objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); 31 | objectMapper.configure(MapperFeature.USE_ANNOTATIONS, false); 32 | objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); 33 | objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); 34 | // 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会跑出异常 35 | objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL); 36 | objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); 37 | // 解决jackson2无法反序列化LocalDateTime的问题 38 | objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); 39 | objectMapper.registerModule(new JavaTimeModule()); 40 | serializer.setObjectMapper(objectMapper); 41 | template.setDefaultSerializer(serializer); 42 | return template; 43 | } 44 | 45 | } -------------------------------------------------------------------------------- /file-server/src/main/java/com/ykrenz/fileserver/config/StorageConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.ykrenz.fileserver.config; 2 | 3 | import com.ykrenz.fileserver.service.client.FastDfsServerClient; 4 | import com.ykrenz.fileserver.service.client.FileServerClient; 5 | import com.ykrenz.fastdfs.FastDfs; 6 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 7 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 8 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 9 | import org.springframework.context.annotation.Bean; 10 | import org.springframework.context.annotation.Configuration; 11 | 12 | /** 13 | * @author ykren 14 | * @date 2022/3/1 15 | */ 16 | @Configuration 17 | @EnableConfigurationProperties(StorageProperties.class) 18 | public class StorageConfiguration { 19 | 20 | @Bean 21 | @ConditionalOnMissingBean() 22 | @ConditionalOnProperty(value = "file.storage", havingValue = "fastdfs") 23 | public FileServerClient fstDfsClient(FastDfs fastDfs, StorageProperties properties) { 24 | return new FastDfsServerClient(fastDfs, properties); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /file-server/src/main/java/com/ykrenz/fileserver/config/StorageProperties.java: -------------------------------------------------------------------------------- 1 | package com.ykrenz.fileserver.config; 2 | 3 | import lombok.Data; 4 | import org.springframework.boot.context.properties.ConfigurationProperties; 5 | import org.springframework.stereotype.Component; 6 | import org.springframework.util.unit.DataSize; 7 | 8 | /** 9 | * @Description: upload配置 10 | * @date 2020/6/10 11:35 11 | */ 12 | @Data 13 | @ConfigurationProperties("file") 14 | public class StorageProperties { 15 | 16 | /** 17 | * 存储方式 18 | */ 19 | private StorageType storage = StorageType.fastdfs; 20 | 21 | /** 22 | * 普通上传支持最大文件Size 23 | */ 24 | private DataSize maxUploadSize = DataSize.ofMegabytes(1L); 25 | /** 26 | * 上传分片支持最小Size 27 | */ 28 | private DataSize multipartMinSize = DataSize.ofBytes(1L); 29 | /** 30 | * 上传分片支持最大Size 31 | */ 32 | private DataSize multipartMaxSize = DataSize.ofBytes(1L); 33 | 34 | 35 | private FastDfs fastdfs = new FastDfs(); 36 | 37 | @Data 38 | public static class FastDfs { 39 | /** 40 | * 分片过期天数 默认7天 41 | */ 42 | private int partExpireDays = 7; 43 | 44 | /** 45 | * 清理分片周期 默认1小时 46 | */ 47 | private long partEvictableSeconds = 3600L; 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /file-server/src/main/java/com/ykrenz/fileserver/config/StorageType.java: -------------------------------------------------------------------------------- 1 | package com.ykrenz.fileserver.config; 2 | 3 | /** 4 | * 文件 存储类型 枚举 5 | */ 6 | public enum StorageType { 7 | /** 8 | * FastDfs 9 | */ 10 | fastdfs, 11 | /** 12 | * S3协议云存储 13 | */ 14 | S3, 15 | } 16 | -------------------------------------------------------------------------------- /file-server/src/main/java/com/ykrenz/fileserver/controller/FileController.java: -------------------------------------------------------------------------------- 1 | package com.ykrenz.fileserver.controller; 2 | 3 | import com.ykrenz.fileserver.model.ResultUtil; 4 | import com.ykrenz.fileserver.model.request.CancelPartRequest; 5 | import com.ykrenz.fileserver.model.request.CompletePartRequest; 6 | import com.ykrenz.fileserver.model.request.FileInfoRequest; 7 | import com.ykrenz.fileserver.model.request.InitUploadMultipartRequest; 8 | import com.ykrenz.fileserver.model.request.SimpleUploadRequest; 9 | import com.ykrenz.fileserver.model.request.UploadMultipartRequest; 10 | import com.ykrenz.fileserver.model.result.FileInfoResult; 11 | import com.ykrenz.fileserver.model.result.InitMultipartResult; 12 | import com.ykrenz.fileserver.service.FileService; 13 | import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport; 14 | import io.swagger.annotations.Api; 15 | import io.swagger.annotations.ApiOperation; 16 | import org.springframework.beans.factory.annotation.Autowired; 17 | import org.springframework.validation.annotation.Validated; 18 | import org.springframework.web.bind.annotation.PostMapping; 19 | import org.springframework.web.bind.annotation.RestController; 20 | 21 | /** 22 | * @Description 文件接口 23 | * @Author ren 24 | * @Since 1.0 25 | */ 26 | @RestController 27 | @Api(tags = "文件") 28 | public class FileController { 29 | 30 | @Autowired 31 | private FileService fileService; 32 | 33 | @ApiOperation(value = "简单上传", notes = "只支持小文件上传") 34 | @ApiOperationSupport(order = 1) 35 | @PostMapping("/upload") 36 | public ResultUtil upload(@Validated SimpleUploadRequest request) { 37 | return ResultUtil.success(fileService.upload(request)); 38 | } 39 | 40 | @ApiOperation("初始化分片上传+秒传+断点续传") 41 | @ApiOperationSupport(order = 2) 42 | @PostMapping("/initMultipart") 43 | public ResultUtil initMultipart(@Validated InitUploadMultipartRequest request) { 44 | return ResultUtil.success(fileService.initMultipart(request)); 45 | } 46 | 47 | @ApiOperation("上传文件分片") 48 | @ApiOperationSupport(order = 3) 49 | @PostMapping("/uploadMultipart") 50 | public ResultUtil uploadMultipart(@Validated UploadMultipartRequest uploadMultipartRequest) { 51 | fileService.uploadMultipart(uploadMultipartRequest); 52 | return ResultUtil.success(); 53 | } 54 | 55 | @ApiOperation("完成分片上传") 56 | @ApiOperationSupport(order = 4) 57 | @PostMapping("/completeMultipart") 58 | public ResultUtil completeMultipart(@Validated CompletePartRequest request) { 59 | return ResultUtil.success(fileService.completeMultipart(request)); 60 | } 61 | 62 | @ApiOperation("取消分片上传") 63 | @ApiOperationSupport(order = 5) 64 | @PostMapping("/cancelMultipart") 65 | public ResultUtil cancelMultipart(@Validated CancelPartRequest request) { 66 | fileService.cancelMultipart(request); 67 | return ResultUtil.success(); 68 | } 69 | 70 | @ApiOperation("查询文件信息") 71 | @ApiOperationSupport(order = 6) 72 | @PostMapping("/info") 73 | public ResultUtil info(@Validated FileInfoRequest request) { 74 | return ResultUtil.success(fileService.info(request)); 75 | } 76 | 77 | // @ApiOperation("删除所有文件-测试使用") 78 | // @ApiOperationSupport(order = 8) 79 | // @PostMapping("/deleteAllFiles") 80 | // public ResultUtil deleteAllFiles() { 81 | // fileService.deleteAllFiles(); 82 | // return ResultUtil.success(); 83 | // } 84 | } 85 | -------------------------------------------------------------------------------- /file-server/src/main/java/com/ykrenz/fileserver/entity/BaseEntity.java: -------------------------------------------------------------------------------- 1 | package com.ykrenz.fileserver.entity; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableId; 5 | import io.swagger.annotations.ApiModelProperty; 6 | import lombok.Data; 7 | 8 | import java.io.Serializable; 9 | import java.time.LocalDateTime; 10 | 11 | /** 12 | * @author ykren 13 | * @date 2022/3/1 14 | */ 15 | @Data 16 | public abstract class BaseEntity implements Serializable { 17 | 18 | private static final long serialVersionUID = 1L; 19 | 20 | protected BaseEntity() { 21 | this.createTime = LocalDateTime.now(); 22 | this.updateTime = LocalDateTime.now(); 23 | this.status = 1; 24 | } 25 | 26 | @ApiModelProperty(value = "主键") 27 | @TableId(value = "id", type = IdType.ASSIGN_ID) 28 | private String id; 29 | 30 | @ApiModelProperty(value = "创建时间") 31 | private LocalDateTime createTime; 32 | 33 | @ApiModelProperty(value = "更新时间") 34 | private LocalDateTime updateTime; 35 | 36 | @ApiModelProperty(value = "状态 1 正常 -1删除 0 终止") 37 | private Integer status; 38 | } 39 | -------------------------------------------------------------------------------- /file-server/src/main/java/com/ykrenz/fileserver/entity/FileInfo.java: -------------------------------------------------------------------------------- 1 | package com.ykrenz.fileserver.entity; 2 | 3 | import com.baomidou.mybatisplus.annotation.TableField; 4 | import io.swagger.annotations.ApiModel; 5 | import io.swagger.annotations.ApiModelProperty; 6 | import lombok.Data; 7 | import lombok.EqualsAndHashCode; 8 | 9 | /** 10 | * @author ykren 11 | * @date 2022/3/1 12 | */ 13 | @Data 14 | @EqualsAndHashCode(callSuper = false) 15 | @ApiModel(value = "FileInfo", description = "文件信息") 16 | public class FileInfo extends BaseEntity { 17 | 18 | @ApiModelProperty(value = "文件md5值") 19 | private String md5; 20 | 21 | @ApiModelProperty(value = "文件crc32值") 22 | private Long crc32; 23 | 24 | @ApiModelProperty(value = "文件名称") 25 | @TableField("fileName") 26 | private String fileName; 27 | 28 | @ApiModelProperty(value = "文件大小") 29 | @TableField("fileSize") 30 | private Long fileSize; 31 | 32 | @ApiModelProperty(value = "bucketName", notes = "存储桶 fastdfs对应group") 33 | @TableField("bucketName") 34 | private String bucketName; 35 | 36 | @ApiModelProperty(value = "objectName", notes = "文件路径 fastdfs对应path") 37 | @TableField("objectName") 38 | private String objectName; 39 | 40 | } 41 | -------------------------------------------------------------------------------- /file-server/src/main/java/com/ykrenz/fileserver/entity/FileLock.java: -------------------------------------------------------------------------------- 1 | package com.ykrenz.fileserver.entity; 2 | 3 | import com.baomidou.mybatisplus.annotation.TableId; 4 | import io.swagger.annotations.ApiModel; 5 | import io.swagger.annotations.ApiModelProperty; 6 | import lombok.Data; 7 | 8 | import java.io.Serializable; 9 | import java.time.LocalDateTime; 10 | 11 | /** 12 | * @author ykren 13 | * @date 2022/3/1 14 | */ 15 | @Data 16 | @ApiModel(value = "Lock", description = "锁") 17 | public class FileLock implements Serializable { 18 | private static final long serialVersionUID = 1L; 19 | @ApiModelProperty(value = "锁key") 20 | @TableId 21 | private String lockKey; 22 | @ApiModelProperty(value = "生成时间") 23 | private LocalDateTime createTime; 24 | @ApiModelProperty(value = "过期时间") 25 | private LocalDateTime expireTime; 26 | } 27 | -------------------------------------------------------------------------------- /file-server/src/main/java/com/ykrenz/fileserver/entity/FilePartInfo.java: -------------------------------------------------------------------------------- 1 | package com.ykrenz.fileserver.entity; 2 | 3 | import com.baomidou.mybatisplus.annotation.TableField; 4 | import io.swagger.annotations.ApiModel; 5 | import io.swagger.annotations.ApiModelProperty; 6 | import lombok.Data; 7 | import lombok.EqualsAndHashCode; 8 | 9 | /** 10 | * @author ykren 11 | * @date 2022/3/1 12 | */ 13 | @Data 14 | @EqualsAndHashCode(callSuper = false) 15 | @ApiModel(value = "FilePartInfo", description = "分片文件") 16 | public class FilePartInfo extends BaseEntity { 17 | 18 | @ApiModelProperty(value = "分片上传唯一标识") 19 | @TableField("uploadId") 20 | private String uploadId; 21 | 22 | @ApiModelProperty(value = "文件名称") 23 | @TableField("fileName") 24 | private String fileName; 25 | 26 | @ApiModelProperty(value = "分片索引") 27 | @TableField("partNumber") 28 | private Integer partNumber; 29 | 30 | @ApiModelProperty(value = "文件大小") 31 | @TableField("fileSize") 32 | private Long fileSize; 33 | 34 | @ApiModelProperty(value = "分片大小") 35 | @TableField("partSize") 36 | private Long partSize; 37 | 38 | @ApiModelProperty(value = "bucketName", notes = "存储桶 fastdfs对应group") 39 | @TableField("bucketName") 40 | private String bucketName; 41 | 42 | @ApiModelProperty(value = "objectName", notes = "文件路径 fastdfs对应path") 43 | @TableField("objectName") 44 | private String objectName; 45 | 46 | } 47 | -------------------------------------------------------------------------------- /file-server/src/main/java/com/ykrenz/fileserver/ex/ApiException.java: -------------------------------------------------------------------------------- 1 | package com.ykrenz.fileserver.ex; 2 | 3 | import com.ykrenz.fileserver.model.ErrorCode; 4 | import lombok.Getter; 5 | 6 | /** 7 | * @author Mr Ren 8 | * @Description: api异常 9 | * @date 2021/4/7 10:57 10 | */ 11 | @Getter 12 | public class ApiException extends RuntimeException { 13 | 14 | private final String error; 15 | 16 | private boolean reset; 17 | 18 | public ApiException(ErrorCode errorCode) { 19 | super(errorCode.getMessage()); 20 | this.error = errorCode.getMessage(); 21 | } 22 | 23 | public ApiException(ErrorCode errorCode, boolean reset) { 24 | super(errorCode.getMessage()); 25 | this.error = errorCode.getMessage(); 26 | this.reset = reset; 27 | } 28 | 29 | public ApiException(String error) { 30 | super(error); 31 | this.error = error; 32 | } 33 | 34 | public ApiException(String error, boolean reset) { 35 | super(error); 36 | this.error = error; 37 | this.reset = reset; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /file-server/src/main/java/com/ykrenz/fileserver/ex/ExceptionAdvice.java: -------------------------------------------------------------------------------- 1 | package com.ykrenz.fileserver.ex; 2 | 3 | import com.ykrenz.fileserver.model.ErrorCode; 4 | import com.ykrenz.fileserver.model.ResultUtil; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.http.HttpStatus; 7 | import org.springframework.validation.BindException; 8 | import org.springframework.validation.FieldError; 9 | import org.springframework.web.HttpMediaTypeNotSupportedException; 10 | import org.springframework.web.HttpRequestMethodNotSupportedException; 11 | import org.springframework.web.bind.MethodArgumentNotValidException; 12 | import org.springframework.web.bind.annotation.ExceptionHandler; 13 | import org.springframework.web.bind.annotation.ResponseStatus; 14 | import org.springframework.web.bind.annotation.RestControllerAdvice; 15 | import org.springframework.web.multipart.MaxUploadSizeExceededException; 16 | import org.springframework.web.multipart.MultipartException; 17 | 18 | import javax.validation.ConstraintViolation; 19 | import javax.validation.ConstraintViolationException; 20 | import javax.validation.ValidationException; 21 | import java.util.List; 22 | import java.util.Set; 23 | 24 | /** 25 | * 异常通用处理 26 | */ 27 | @Slf4j 28 | @RestControllerAdvice 29 | public class ExceptionAdvice { 30 | 31 | /** 32 | * 返回状态码:400 33 | */ 34 | @ExceptionHandler(value = {MethodArgumentNotValidException.class, BindException.class}) 35 | @ResponseStatus(HttpStatus.BAD_REQUEST) 36 | public ResultUtil handleFieldError(Exception e) { 37 | StringBuilder builder = new StringBuilder(); 38 | List fieldErrors = null; 39 | if (e instanceof MethodArgumentNotValidException) { 40 | MethodArgumentNotValidException ex = (MethodArgumentNotValidException) e; 41 | fieldErrors = ex.getBindingResult().getFieldErrors(); 42 | } else { 43 | BindException ex = (BindException) e; 44 | fieldErrors = ex.getBindingResult().getFieldErrors(); 45 | } 46 | for (FieldError fieldError : fieldErrors) { 47 | builder.append(fieldError.getField()).append(fieldError.getDefaultMessage()); 48 | } 49 | return paramError(builder.toString()); 50 | } 51 | 52 | /** 53 | * 返回状态码:400 54 | */ 55 | @ExceptionHandler(ValidationException.class) 56 | @ResponseStatus(HttpStatus.BAD_REQUEST) 57 | public ResultUtil handle(ValidationException e) { 58 | StringBuilder builder = new StringBuilder(); 59 | if (e instanceof ConstraintViolationException) { 60 | ConstraintViolationException exs = (ConstraintViolationException) e; 61 | Set> violations = exs.getConstraintViolations(); 62 | for (ConstraintViolation item : violations) { 63 | builder.append(item.getMessage()); 64 | } 65 | } else { 66 | builder.append(e.getMessage()); 67 | } 68 | return paramError(builder.toString()); 69 | } 70 | 71 | /** 72 | * 返回状态码:405 73 | */ 74 | @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED) 75 | @ExceptionHandler({HttpRequestMethodNotSupportedException.class}) 76 | public ResultUtil handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) { 77 | return ResultUtil.error(ErrorCode.HTTP_METHOD_ERROR); 78 | } 79 | 80 | /** 81 | * 返回状态码:400 82 | */ 83 | @ResponseStatus(HttpStatus.BAD_REQUEST) 84 | @ExceptionHandler({MultipartException.class}) 85 | public ResultUtil multipartException(MultipartException e) { 86 | if (e instanceof MaxUploadSizeExceededException) { 87 | return ResultUtil.error(ErrorCode.FILE_TO_LARGE); 88 | } 89 | return ResultUtil.error(ErrorCode.HTTP_METHOD_ERROR); 90 | } 91 | 92 | /** 93 | * 返回状态码:415 94 | */ 95 | @ResponseStatus(HttpStatus.UNSUPPORTED_MEDIA_TYPE) 96 | @ExceptionHandler({HttpMediaTypeNotSupportedException.class}) 97 | public ResultUtil handleHttpMediaTypeNotSupportedException(HttpMediaTypeNotSupportedException e) { 98 | return ResultUtil.error(ErrorCode.HTTP_MEDIA_ERROR); 99 | } 100 | 101 | /** 102 | * 业务异常直接抛给客户端 103 | */ 104 | @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) 105 | @ExceptionHandler(ApiException.class) 106 | public ResultUtil apiException(ApiException e) { 107 | return ResultUtil.error(e.getMessage(), e.isReset()); 108 | } 109 | 110 | /** 111 | * 服务器内部异常 返回处理 112 | */ 113 | @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) 114 | @ExceptionHandler(Exception.class) 115 | public ResultUtil handleException(Exception e) { 116 | return errorServerHandler(e); 117 | } 118 | 119 | /** 120 | * 参数异常 121 | */ 122 | private ResultUtil paramError(String msg) { 123 | String message = ErrorCode.PARAM_ERROR.getMessage(); 124 | return ResultUtil.error(message + msg); 125 | } 126 | 127 | /** 128 | * 服务器内部异常处理 129 | */ 130 | private ResultUtil errorServerHandler(Exception e) { 131 | log.error("服务异常::", e); 132 | return ResultUtil.error(ErrorCode.SERVER_ERROR); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /file-server/src/main/java/com/ykrenz/fileserver/mapper/FileInfoMapper.java: -------------------------------------------------------------------------------- 1 | package com.ykrenz.fileserver.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.ykrenz.fileserver.entity.FileInfo; 5 | 6 | /** 7 | *

8 | * 文件信息主表 Mapper 接口 9 | *

10 | * 11 | * @author Mr Ren 12 | * @since 2021-05-24 13 | */ 14 | public interface FileInfoMapper extends BaseMapper { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /file-server/src/main/java/com/ykrenz/fileserver/mapper/FilePartInfoMapper.java: -------------------------------------------------------------------------------- 1 | package com.ykrenz.fileserver.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.ykrenz.fileserver.entity.FilePartInfo; 5 | import org.apache.ibatis.annotations.Select; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | *

11 | * 文件信息主表 Mapper 接口 12 | *

13 | * 14 | * @author Mr Ren 15 | * @since 2021-05-24 16 | */ 17 | public interface FilePartInfoMapper extends BaseMapper { 18 | 19 | @Select({ 20 | "select * from file_part_info where now() > DATE_ADD(create_time,INTERVAL #{expireDays} day) " + 21 | "and partNumber = 0 and status = 1 limit 500" 22 | }) 23 | List selectExpireUploads(int expireDays); 24 | } 25 | -------------------------------------------------------------------------------- /file-server/src/main/java/com/ykrenz/fileserver/mapper/LockMapper.java: -------------------------------------------------------------------------------- 1 | package com.ykrenz.fileserver.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.ykrenz.fileserver.entity.FileLock; 5 | import org.apache.ibatis.annotations.Insert; 6 | 7 | /** 8 | *

9 | * 文件信息主表 Mapper 接口 10 | *

11 | * 12 | * @author Mr Ren 13 | * @since 2021-05-24 14 | */ 15 | public interface LockMapper extends BaseMapper { 16 | 17 | @Insert( 18 | "insert ignore into file_lock(lock_key,create_time,expire_time) values (#{lockKey},#{createTime},#{expireTime})" 19 | ) 20 | int insertIgnore(FileLock fileLock); 21 | } 22 | -------------------------------------------------------------------------------- /file-server/src/main/java/com/ykrenz/fileserver/model/ErrorCode.java: -------------------------------------------------------------------------------- 1 | package com.ykrenz.fileserver.model; 2 | 3 | import lombok.Getter; 4 | 5 | /** 6 | * @Description: 异常枚举类 7 | * @date 2020/5/22 14:40 8 | */ 9 | @Getter 10 | public enum ErrorCode { 11 | /** 12 | * 服务异常 13 | */ 14 | SERVER_ERROR("服务器异常,请联系管理员"), 15 | /** 16 | * 参数异常 17 | */ 18 | PARAM_ERROR("参数异常"), 19 | /** 20 | * 不支持当前请求方法 21 | */ 22 | HTTP_METHOD_ERROR("不支持当前请求方法"), 23 | /** 24 | * 不支持当前媒体类型 25 | */ 26 | HTTP_MEDIA_ERROR("不支持当前媒体类型"), 27 | /** 28 | * 文件过大 29 | */ 30 | FILE_TO_LARGE("文件过大,请使用分片上传"), 31 | /** 32 | * 上传失败 33 | */ 34 | UPLOAD_ERROR("上传失败,请重试"), 35 | /** 36 | * uploadId不存在 37 | */ 38 | UPLOAD_ID_NOT_FOUND("uploadId不存在或已经过期"), 39 | /** 40 | * 文件CRC32校验失败 41 | */ 42 | FILE_CRC32_ERROR("文件CRC32校验失败,请重新上传"), 43 | /** 44 | * 文件不存在 45 | */ 46 | FILE_NOT_FOUND("文件不存在"), 47 | ; 48 | /** 49 | * 返回消息 50 | */ 51 | private final String message; 52 | 53 | ErrorCode(String message) { 54 | this.message = message; 55 | } 56 | 57 | public String getMessage() { 58 | return message; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /file-server/src/main/java/com/ykrenz/fileserver/model/ResultUtil.java: -------------------------------------------------------------------------------- 1 | package com.ykrenz.fileserver.model; 2 | 3 | import io.swagger.annotations.ApiModel; 4 | import io.swagger.annotations.ApiModelProperty; 5 | import lombok.Getter; 6 | import lombok.Setter; 7 | 8 | import java.io.Serializable; 9 | 10 | /** 11 | * 返回值封装处理 12 | * 13 | * @param 14 | */ 15 | @Setter 16 | @Getter 17 | @ApiModel(value = "Result", description = "响应结果") 18 | public class ResultUtil implements Serializable { 19 | 20 | private static final long serialVersionUID = 1L; 21 | 22 | @ApiModelProperty("成功") 23 | private boolean success; 24 | @ApiModelProperty("数据") 25 | private T data; 26 | @ApiModelProperty("错误信息") 27 | private String error; 28 | @ApiModelProperty("重置分片上传") 29 | private boolean reset; 30 | 31 | 32 | private ResultUtil(T data) { 33 | this.data = data; 34 | this.success = true; 35 | } 36 | 37 | private ResultUtil(String error, boolean reset) { 38 | this.error = error; 39 | this.success = false; 40 | this.reset = reset; 41 | } 42 | 43 | /** 44 | * success 45 | * 46 | * @param 47 | * @return 48 | */ 49 | public static ResultUtil success(T data) { 50 | return new ResultUtil<>(data); 51 | } 52 | 53 | /** 54 | * success 55 | * 56 | * @param 57 | * @return 58 | */ 59 | public static ResultUtil success() { 60 | return new ResultUtil<>(null); 61 | } 62 | 63 | /** 64 | * 失败 65 | * 66 | * @param 67 | * @return 68 | */ 69 | public static ResultUtil error(ErrorCode error) { 70 | return new ResultUtil<>(error.getMessage(), false); 71 | } 72 | 73 | public static ResultUtil error(String error) { 74 | return new ResultUtil<>(error, false); 75 | } 76 | 77 | public static ResultUtil error(String error, boolean reset) { 78 | return new ResultUtil<>(error, reset); 79 | } 80 | } -------------------------------------------------------------------------------- /file-server/src/main/java/com/ykrenz/fileserver/model/request/CancelPartRequest.java: -------------------------------------------------------------------------------- 1 | package com.ykrenz.fileserver.model.request; 2 | 3 | import io.swagger.annotations.ApiModel; 4 | import io.swagger.annotations.ApiModelProperty; 5 | import lombok.Data; 6 | 7 | import javax.validation.constraints.NotBlank; 8 | 9 | /** 10 | * @Description 取消分片上传参数 11 | * @Author ren 12 | * @Since 1.0 13 | */ 14 | @Data 15 | @ApiModel(value = "CancelPartRequest", description = "取消分片上传") 16 | public class CancelPartRequest { 17 | 18 | /** 19 | * 上传唯一标识 20 | */ 21 | @ApiModelProperty(name = "uploadId", value = "上传唯一标识") 22 | @NotBlank(message = "不能为空") 23 | private String uploadId; 24 | } 25 | -------------------------------------------------------------------------------- /file-server/src/main/java/com/ykrenz/fileserver/model/request/CompletePartRequest.java: -------------------------------------------------------------------------------- 1 | package com.ykrenz.fileserver.model.request; 2 | 3 | import io.swagger.annotations.ApiModel; 4 | import io.swagger.annotations.ApiModelProperty; 5 | import lombok.Data; 6 | 7 | import javax.validation.constraints.NotBlank; 8 | 9 | /** 10 | * @Description 合并分片参数 11 | * @Author ren 12 | * @Since 1.0 13 | */ 14 | @Data 15 | @ApiModel(value = "CompletePartRequest", description = "完成分片上传") 16 | public class CompletePartRequest { 17 | 18 | /** 19 | * 上传唯一标识 20 | */ 21 | @ApiModelProperty(name = "uploadId", value = "上传唯一标识") 22 | @NotBlank(message = "不能为空") 23 | private String uploadId; 24 | /** 25 | * 文件md5 26 | */ 27 | @ApiModelProperty(name = "fileMd5", value = "文件md5值 fastdfs只保存不做验证") 28 | private String fileMd5; 29 | /** 30 | * fastdfs可使用 保证数据完整性 31 | */ 32 | @ApiModelProperty(name = "fileCrc32", value = "文件crc32值 fastdfs保证数据完整性") 33 | private Long fileCrc32; 34 | 35 | @ApiModelProperty(name = "info", value = "是否返回文件信息") 36 | private boolean info; 37 | } 38 | -------------------------------------------------------------------------------- /file-server/src/main/java/com/ykrenz/fileserver/model/request/FileInfoRequest.java: -------------------------------------------------------------------------------- 1 | package com.ykrenz.fileserver.model.request; 2 | 3 | import io.swagger.annotations.ApiModel; 4 | import io.swagger.annotations.ApiModelProperty; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | import javax.validation.constraints.NotBlank; 10 | 11 | @Data 12 | @ApiModel(value = "FileInfoRequest", description = "文件信息") 13 | @AllArgsConstructor 14 | @NoArgsConstructor 15 | public class FileInfoRequest { 16 | 17 | @ApiModelProperty(name = "bucketName", value = "文件bucketName") 18 | @NotBlank(message = "存储桶不能为空") 19 | private String bucketName; 20 | 21 | @ApiModelProperty(name = "objectName", value = "文件objectName") 22 | @NotBlank(message = "文件路径不能为空") 23 | private String objectName; 24 | } 25 | -------------------------------------------------------------------------------- /file-server/src/main/java/com/ykrenz/fileserver/model/request/InitUploadMultipartRequest.java: -------------------------------------------------------------------------------- 1 | package com.ykrenz.fileserver.model.request; 2 | 3 | import io.swagger.annotations.ApiModel; 4 | import io.swagger.annotations.ApiModelProperty; 5 | import lombok.Data; 6 | 7 | import javax.validation.constraints.NotBlank; 8 | import javax.validation.constraints.NotNull; 9 | 10 | /** 11 | * @Description 初始化分片上传参数 12 | * @Author ren 13 | * @Since 1.0 14 | */ 15 | @Data 16 | @ApiModel(value = "InitUploadMultipartRequest", description = "初始化分片上传") 17 | public class InitUploadMultipartRequest { 18 | 19 | @ApiModelProperty(name = "md5", value = "文件md5", notes = "客户端秒传") 20 | private String fileMd5; 21 | 22 | @ApiModelProperty(name = "crc32", value = "文件crc32 fastdfs可结合md5秒传") 23 | private Long fileCrc32; 24 | 25 | @ApiModelProperty(name = "uploadId", value = "上传唯一标识 服务端返回的uploadId 可用于查询已经上传过的分片 断点续传") 26 | private String uploadId; 27 | 28 | @ApiModelProperty(name = "fileName", value = "文件名") 29 | @NotBlank(message = "不能为空") 30 | private String fileName; 31 | 32 | @ApiModelProperty(name = "fileSize", value = "文件大小") 33 | @NotNull(message = "文件大小不能为空") 34 | private Long fileSize; 35 | 36 | @ApiModelProperty(name = "partSize", value = "分片大小") 37 | @NotNull(message = "分片大小不能为空") 38 | private Long partSize; 39 | } 40 | -------------------------------------------------------------------------------- /file-server/src/main/java/com/ykrenz/fileserver/model/request/SimpleUploadRequest.java: -------------------------------------------------------------------------------- 1 | package com.ykrenz.fileserver.model.request; 2 | 3 | import io.swagger.annotations.ApiModel; 4 | import io.swagger.annotations.ApiModelProperty; 5 | import lombok.Data; 6 | import org.springframework.web.multipart.MultipartFile; 7 | 8 | import javax.validation.constraints.NotNull; 9 | 10 | /** 11 | * @Description 简单上传参数 12 | * @Author ren 13 | * @Since 1.0 14 | */ 15 | @Data 16 | @ApiModel(value = "SimpleUploadRequest", description = "上传简单文件") 17 | public class SimpleUploadRequest { 18 | 19 | /** 20 | * 文件 21 | */ 22 | @ApiModelProperty(name = "file", value = "文件") 23 | @NotNull(message = "文件不能为空") 24 | private MultipartFile file; 25 | 26 | /** 27 | * md5验证 28 | */ 29 | @ApiModelProperty(name = "md5", value = "文件md5值 fastdfs只保存不做验证") 30 | private String md5; 31 | 32 | /** 33 | * fastdfs crc32验证 34 | */ 35 | @ApiModelProperty(name = "crc32", value = "文件crc32值 fastdfs保证数据完整性") 36 | private Long crc32; 37 | 38 | /** 39 | * 是否返回文件信息 40 | */ 41 | @ApiModelProperty(name = "info", value = "是否返回文件信息") 42 | private boolean info; 43 | } 44 | -------------------------------------------------------------------------------- /file-server/src/main/java/com/ykrenz/fileserver/model/request/UploadMultipartRequest.java: -------------------------------------------------------------------------------- 1 | package com.ykrenz.fileserver.model.request; 2 | 3 | import io.swagger.annotations.ApiModel; 4 | import io.swagger.annotations.ApiModelProperty; 5 | import lombok.Data; 6 | import org.springframework.web.multipart.MultipartFile; 7 | 8 | import javax.validation.constraints.NotBlank; 9 | import javax.validation.constraints.NotNull; 10 | 11 | /** 12 | * @Description 分片上传参数 13 | * @Author ren 14 | * @Since 1.0 15 | */ 16 | @Data 17 | @ApiModel(value = "UploadMultipartRequest", description = "分片上传") 18 | public class UploadMultipartRequest { 19 | 20 | @ApiModelProperty(name = "uploadId", value = "分片上传标识") 21 | @NotBlank(message = "分片上传标识不能为空") 22 | private String uploadId; 23 | 24 | @ApiModelProperty(name = "partNumber", value = "分片索引 从0开始") 25 | @NotNull(message = "分片索引不能为空") 26 | private Integer partNumber; 27 | 28 | @ApiModelProperty(name = "file", value = "分片文件") 29 | @NotNull(message = "文件不能为空") 30 | private MultipartFile file; 31 | } 32 | -------------------------------------------------------------------------------- /file-server/src/main/java/com/ykrenz/fileserver/model/result/FileInfoResult.java: -------------------------------------------------------------------------------- 1 | package com.ykrenz.fileserver.model.result; 2 | 3 | import io.swagger.annotations.ApiModel; 4 | import io.swagger.annotations.ApiModelProperty; 5 | import lombok.Data; 6 | 7 | import java.io.Serializable; 8 | 9 | @Data 10 | @ApiModel(value = "FileInfoResult", description = "文件信息") 11 | public class FileInfoResult implements Serializable { 12 | private static final long serialVersionUID = 1L; 13 | 14 | @ApiModelProperty(value = "主键") 15 | private String id; 16 | 17 | @ApiModelProperty(value = "文件md5值") 18 | private String md5; 19 | 20 | @ApiModelProperty(value = "文件crc32值") 21 | private Long crc32; 22 | 23 | @ApiModelProperty(value = "文件名称") 24 | private String fileName; 25 | 26 | @ApiModelProperty(value = "文件大小") 27 | private Long fileSize; 28 | 29 | @ApiModelProperty(value = "存储桶 fastdfs对应group") 30 | private String bucketName; 31 | 32 | @ApiModelProperty(value = "文件路径 fastdfs对应path") 33 | private String objectName; 34 | 35 | @ApiModelProperty(value = "文件预览路径", notes = "文件预览路径") 36 | private String webPath; 37 | 38 | @ApiModelProperty(value = "文件下载路径", notes = "文件下载路径") 39 | private String downloadPath; 40 | } 41 | -------------------------------------------------------------------------------- /file-server/src/main/java/com/ykrenz/fileserver/model/result/InitMultipartResult.java: -------------------------------------------------------------------------------- 1 | package com.ykrenz.fileserver.model.result; 2 | 3 | import io.swagger.annotations.ApiModel; 4 | import io.swagger.annotations.ApiModelProperty; 5 | import lombok.Data; 6 | 7 | import java.io.Serializable; 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | /** 12 | * @Description 分片上传检测返回 13 | * @Author ren 14 | * @Since 1.0 15 | */ 16 | @ApiModel(value = "InitMultipartResult", description = "初始化分片上传结果") 17 | @Data 18 | public class InitMultipartResult implements Serializable { 19 | private static final long serialVersionUID = 1L; 20 | 21 | @ApiModelProperty("上传文件唯一标识") 22 | private String uploadId; 23 | 24 | @ApiModelProperty("是否存在 true:存在(秒传) false:不存在") 25 | private boolean exist; 26 | 27 | @ApiModelProperty("已经上传的分片数据 断点续传客户端可以跳过这些分片") 28 | private List parts = new ArrayList<>(0); 29 | 30 | public InitMultipartResult() { 31 | } 32 | 33 | public InitMultipartResult(String uploadId, boolean exist) { 34 | this.uploadId = uploadId; 35 | this.exist = exist; 36 | } 37 | 38 | public InitMultipartResult(String uploadId, List parts) { 39 | this.uploadId = uploadId; 40 | this.parts = parts; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /file-server/src/main/java/com/ykrenz/fileserver/objectname/Md5Generator.java: -------------------------------------------------------------------------------- 1 | package com.ykrenz.fileserver.objectname; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @Description 根据md5文件名称生成 7 | * @Author ren 8 | * @Since 1.0 9 | */ 10 | @Data 11 | public class Md5Generator implements ObjectNameGenerator { 12 | 13 | private String md5; 14 | 15 | private String ext; 16 | 17 | public Md5Generator(String md5, String ext) { 18 | this.md5 = md5; 19 | this.ext = ext; 20 | } 21 | 22 | @Override 23 | public String generator() { 24 | //https://help.aliyun.com/document_detail/64945.html?spm=a2c4g.11186623.6.1779.75e151b6GRk0Ab 25 | return md5.substring(0, 4) + "/" + md5 + "." + ext; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /file-server/src/main/java/com/ykrenz/fileserver/objectname/ObjectNameGenerator.java: -------------------------------------------------------------------------------- 1 | package com.ykrenz.fileserver.objectname; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * @Description 文件名称生成 7 | * @Author ren 8 | * @Since 1.0 9 | */ 10 | public interface ObjectNameGenerator extends Serializable { 11 | /** 12 | * 创建文件名称 13 | * 14 | * @return 15 | */ 16 | String generator(); 17 | } 18 | -------------------------------------------------------------------------------- /file-server/src/main/java/com/ykrenz/fileserver/objectname/TimestampGenerator.java: -------------------------------------------------------------------------------- 1 | package com.ykrenz.fileserver.objectname; 2 | 3 | import lombok.Data; 4 | import org.apache.commons.lang3.StringUtils; 5 | 6 | /** 7 | * @Description 根据时间戳文件名称生成 8 | * @Author ren 9 | * @Since 1.0 10 | */ 11 | @Data 12 | public class TimestampGenerator implements ObjectNameGenerator { 13 | 14 | private String ext; 15 | 16 | public TimestampGenerator(String ext) { 17 | this.ext = ext; 18 | } 19 | 20 | @Override 21 | public String generator() { 22 | long millis = System.currentTimeMillis(); 23 | return StringUtils.reverse(String.valueOf(millis)) + "." + ext; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /file-server/src/main/java/com/ykrenz/fileserver/objectname/UuidGenerator.java: -------------------------------------------------------------------------------- 1 | package com.ykrenz.fileserver.objectname; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.UUID; 6 | 7 | /** 8 | * @Description 根据uuid文件名称生成 9 | * @Author ren 10 | * @Since 1.0 11 | */ 12 | @Data 13 | public class UuidGenerator implements ObjectNameGenerator { 14 | 15 | private String ext; 16 | 17 | public UuidGenerator(String ext) { 18 | this.ext = ext; 19 | } 20 | 21 | @Override 22 | public String generator() { 23 | String uuid = UUID.randomUUID().toString().replace("-", ""); 24 | return uuid.substring(0, 4) + "/" + uuid + "." + ext; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /file-server/src/main/java/com/ykrenz/fileserver/service/FileService.java: -------------------------------------------------------------------------------- 1 | package com.ykrenz.fileserver.service; 2 | 3 | import com.ykrenz.fileserver.model.request.CancelPartRequest; 4 | import com.ykrenz.fileserver.model.request.CompletePartRequest; 5 | import com.ykrenz.fileserver.model.request.FileInfoRequest; 6 | import com.ykrenz.fileserver.model.request.InitUploadMultipartRequest; 7 | import com.ykrenz.fileserver.model.request.SimpleUploadRequest; 8 | import com.ykrenz.fileserver.model.request.UploadMultipartRequest; 9 | import com.ykrenz.fileserver.model.result.FileInfoResult; 10 | import com.ykrenz.fileserver.model.result.InitMultipartResult; 11 | 12 | import java.io.Serializable; 13 | 14 | /** 15 | * @Description 文件接口 16 | * @Author ren 17 | * @Since 1.0 18 | */ 19 | public interface FileService extends Serializable { 20 | 21 | /** 22 | * 上传简单文件 23 | * 24 | * @param request 25 | * @return 26 | */ 27 | FileInfoResult upload(SimpleUploadRequest request); 28 | 29 | /** 30 | * 初始化分片上传任务 31 | * 32 | * @param request 33 | * @return 34 | */ 35 | InitMultipartResult initMultipart(InitUploadMultipartRequest request); 36 | 37 | /** 38 | * 上传文件分片 39 | * 40 | * @param uploadMultipartRequest 41 | * @return 42 | */ 43 | void uploadMultipart(UploadMultipartRequest uploadMultipartRequest); 44 | 45 | /** 46 | * 合并文件分片 47 | * 48 | * @param request 49 | * @return 50 | */ 51 | FileInfoResult completeMultipart(CompletePartRequest request); 52 | 53 | /** 54 | * 取消分片上传 55 | * 56 | * @param request 57 | * @return 58 | */ 59 | void cancelMultipart(CancelPartRequest request); 60 | 61 | /** 62 | * 查询文件信息 63 | * 64 | * @param id 65 | * @return 66 | */ 67 | FileInfoResult info(FileInfoRequest id); 68 | 69 | /** 70 | * 清空所有文件 测试使用 71 | */ 72 | void deleteAllFiles(); 73 | 74 | } 75 | -------------------------------------------------------------------------------- /file-server/src/main/java/com/ykrenz/fileserver/service/FileServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.ykrenz.fileserver.service; 2 | 3 | import com.ykrenz.fileserver.config.StorageProperties; 4 | import com.ykrenz.fileserver.entity.FileInfo; 5 | import com.ykrenz.fileserver.ex.ApiException; 6 | import com.ykrenz.fileserver.model.ErrorCode; 7 | import com.ykrenz.fileserver.model.request.CancelPartRequest; 8 | import com.ykrenz.fileserver.model.request.CompletePartRequest; 9 | import com.ykrenz.fileserver.model.request.FileInfoRequest; 10 | import com.ykrenz.fileserver.model.request.InitUploadMultipartRequest; 11 | import com.ykrenz.fileserver.model.request.SimpleUploadRequest; 12 | import com.ykrenz.fileserver.model.request.UploadMultipartRequest; 13 | import com.ykrenz.fileserver.model.result.FileInfoResult; 14 | import com.ykrenz.fileserver.model.result.InitMultipartResult; 15 | import com.ykrenz.fileserver.service.client.FileServerClient; 16 | import org.springframework.stereotype.Service; 17 | 18 | import java.io.IOException; 19 | 20 | /** 21 | * @author ykren 22 | * @date 2022/3/4 23 | */ 24 | @Service 25 | public class FileServiceImpl implements FileService { 26 | 27 | private StorageProperties storageProperties; 28 | 29 | private FileServerClient fileServerClient; 30 | 31 | private final long maxUploadSize; 32 | private final long multipartMinSize; 33 | private final long multipartMaxSize; 34 | 35 | public FileServiceImpl(StorageProperties storageProperties, FileServerClient fileServerClient) { 36 | this.storageProperties = storageProperties; 37 | this.fileServerClient = fileServerClient; 38 | this.maxUploadSize = storageProperties.getMaxUploadSize().toBytes(); 39 | this.multipartMinSize = storageProperties.getMultipartMinSize().toBytes(); 40 | this.multipartMaxSize = storageProperties.getMultipartMaxSize().toBytes(); 41 | } 42 | 43 | @Override 44 | public FileInfoResult upload(SimpleUploadRequest request) { 45 | try { 46 | // 限制文件大小 47 | if (request.getFile().getSize() > maxUploadSize) { 48 | String msg = String.format("文件限制%dM,请使用分片上传", maxUploadSize / 1024 / 1024); 49 | throw new ApiException(msg); 50 | } 51 | FileInfo fileInfo = fileServerClient.upload(request); 52 | if (request.isInfo()) { 53 | return info(new FileInfoRequest(fileInfo.getBucketName(), fileInfo.getObjectName())); 54 | } 55 | return new FileInfoResult(); 56 | } catch (IOException e) { 57 | throw new ApiException(ErrorCode.UPLOAD_ERROR); 58 | } 59 | } 60 | 61 | @Override 62 | public InitMultipartResult initMultipart(InitUploadMultipartRequest request) { 63 | Long partSize = request.getPartSize(); 64 | if (partSize < multipartMinSize || partSize > multipartMaxSize) { 65 | String msg = String.format("分片大小必须在%dM~%dM之间", multipartMinSize / 1024 / 1024, multipartMaxSize / 1024 / 1024); 66 | throw new ApiException(msg); 67 | } 68 | return fileServerClient.initMultipart(request); 69 | } 70 | 71 | @Override 72 | public void uploadMultipart(UploadMultipartRequest request) { 73 | try { 74 | // 前端index=0 75 | request.setPartNumber(request.getPartNumber() + 1); 76 | fileServerClient.uploadMultipart(request); 77 | } catch (IOException e) { 78 | throw new ApiException(ErrorCode.UPLOAD_ERROR); 79 | } 80 | } 81 | 82 | @Override 83 | public FileInfoResult completeMultipart(CompletePartRequest request) { 84 | FileInfo fileInfo = fileServerClient.completeMultipart(request); 85 | if (request.isInfo()) { 86 | return info(new FileInfoRequest(fileInfo.getBucketName(), fileInfo.getObjectName())); 87 | } 88 | return new FileInfoResult(); 89 | } 90 | 91 | @Override 92 | public void cancelMultipart(CancelPartRequest request) { 93 | fileServerClient.cancelMultipart(request); 94 | } 95 | 96 | @Override 97 | public FileInfoResult info(FileInfoRequest request) { 98 | return fileServerClient.info(request); 99 | } 100 | 101 | @Override 102 | public void deleteAllFiles() { 103 | fileServerClient.deleteAllFiles(); 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /file-server/src/main/java/com/ykrenz/fileserver/service/RedisService.java: -------------------------------------------------------------------------------- 1 | package com.ykrenz.fileserver.service; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.data.redis.core.RedisTemplate; 6 | import org.springframework.stereotype.Repository; 7 | 8 | import java.util.List; 9 | import java.util.Map; 10 | import java.util.Set; 11 | import java.util.concurrent.TimeUnit; 12 | 13 | @Slf4j 14 | @Repository 15 | public class RedisService { 16 | 17 | @Autowired 18 | private RedisTemplate redisTemplate; 19 | 20 | /** 21 | * @param key 22 | * @param T 23 | * @param time 到期的分钟数 24 | */ 25 | public void add(String key, Object t, Long time) { 26 | redisTemplate.opsForValue().set(key, t, time, TimeUnit.MINUTES); 27 | } 28 | 29 | // =============================common============================ 30 | 31 | /** 32 | * 指定缓存失效时间 33 | * 34 | * @param key 键 35 | * @param time 时间(秒) 36 | * @return 37 | */ 38 | public boolean expire(String key, long time) { 39 | try { 40 | if (time > 0) { 41 | redisTemplate.expire(key, time, TimeUnit.SECONDS); 42 | } 43 | return true; 44 | } catch (Exception e) { 45 | e.printStackTrace(); 46 | return false; 47 | } 48 | } 49 | 50 | /** 51 | * 根据key 获取过期时间 52 | * 53 | * @param key 键 不能为null 54 | * @return 时间(秒) 返回0代表为永久有效 55 | */ 56 | public long getExpire(String key) { 57 | return redisTemplate.getExpire(key, TimeUnit.SECONDS); 58 | } 59 | 60 | /** 61 | * 判断key是否存在 62 | * 63 | * @param key 键 64 | * @return true 存在 false不存在 65 | */ 66 | public boolean hasKey(String key) { 67 | try { 68 | return redisTemplate.hasKey(key); 69 | } catch (Exception e) { 70 | e.printStackTrace(); 71 | return false; 72 | } 73 | } 74 | 75 | public void delete(String key) { 76 | boolean rst = redisTemplate.delete(key); 77 | log.info("Redis删除键{" + key + "}返回:" + rst); 78 | } 79 | 80 | // ============================操作String类型 81 | 82 | /** 83 | * 普通缓存获取 84 | * 85 | * @param key 键 86 | * @return 值 87 | */ 88 | public Object get(String key) { 89 | return key == null ? null : redisTemplate.opsForValue().get(key); 90 | } 91 | 92 | /** 93 | * 普通缓存放入 94 | * 95 | * @param key 键 96 | * @param value 值 97 | * @return true成功 false失败 98 | */ 99 | public boolean set(String key, Object t) { 100 | try { 101 | redisTemplate.opsForValue().set(key, t); 102 | return true; 103 | } catch (Exception e) { 104 | e.printStackTrace(); 105 | return false; 106 | } 107 | 108 | } 109 | 110 | /** 111 | * 普通缓存放入并设置时间 112 | * 113 | * @param key 键 114 | * @param value 值 115 | * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期 116 | * @return true成功 false 失败 117 | */ 118 | public boolean set(String key, Object t, long time) { 119 | try { 120 | if (time > 0) { 121 | redisTemplate.opsForValue().set(key, t, time, TimeUnit.SECONDS); 122 | } else { 123 | set(key, t); 124 | } 125 | return true; 126 | } catch (Exception e) { 127 | e.printStackTrace(); 128 | return false; 129 | } 130 | } 131 | 132 | /** 133 | * 递增 134 | * 135 | * @param key 键 136 | * @param by 要增加几(大于0) 137 | * @return 138 | */ 139 | public long incr(String key, long delta) { 140 | if (delta < 0) { 141 | throw new RuntimeException("递增因子必须大于0"); 142 | } 143 | return redisTemplate.opsForValue().increment(key, delta); 144 | } 145 | 146 | /** 147 | * 递减 148 | * 149 | * @param key 键 150 | * @param by 要减少几(小于0) 151 | * @return 152 | */ 153 | public long decr(String key, long delta) { 154 | if (delta < 0) { 155 | throw new RuntimeException("递减因子必须大于0"); 156 | } 157 | return redisTemplate.opsForValue().increment(key, -delta); 158 | } 159 | 160 | // =========================操作Set 161 | 162 | /** 163 | * 根据key获取Set中的所有值 164 | * 165 | * @param key 键 166 | * @return 167 | */ 168 | public Set sGet(String key) { 169 | try { 170 | return redisTemplate.opsForSet().members(key); 171 | } catch (Exception e) { 172 | e.printStackTrace(); 173 | return null; 174 | } 175 | } 176 | 177 | /** 178 | * 根据value从一个set中查询,是否存在 179 | * 180 | * @param key 键 181 | * @param value 值 182 | * @return true 存在 false不存在 183 | */ 184 | public boolean sHasKey(String key, Object value) { 185 | try { 186 | return redisTemplate.opsForSet().isMember(key, value); 187 | } catch (Exception e) { 188 | e.printStackTrace(); 189 | return false; 190 | } 191 | } 192 | 193 | /** 194 | * 将数据放入set缓存 195 | * 196 | * @param key 键 197 | * @param values 值 可以是多个 198 | * @return 成功个数 199 | */ 200 | public long sSet(String key, Object... t) { 201 | try { 202 | return redisTemplate.opsForSet().add(key, t); 203 | } catch (Exception e) { 204 | e.printStackTrace(); 205 | return 0; 206 | } 207 | } 208 | 209 | /** 210 | * 将set数据放入缓存 211 | * 212 | * @param key 键 213 | * @param time 时间(秒) 214 | * @param values 值 可以是多个 215 | * @return 成功个数 216 | */ 217 | public long sSetAndTime(String key, long time, Object... t) { 218 | try { 219 | Long count = redisTemplate.opsForSet().add(key, t); 220 | if (time > 0) 221 | expire(key, time); 222 | return count; 223 | } catch (Exception e) { 224 | e.printStackTrace(); 225 | return 0; 226 | } 227 | } 228 | 229 | /** 230 | * 获取set缓存的长度 231 | * 232 | * @param key 键 233 | * @return 234 | */ 235 | public long sGetSetSize(String key) { 236 | try { 237 | return redisTemplate.opsForSet().size(key); 238 | } catch (Exception e) { 239 | e.printStackTrace(); 240 | return 0; 241 | } 242 | } 243 | 244 | /** 245 | * 移除值为value的 246 | * 247 | * @param key 键 248 | * @param values 值 可以是多个 249 | * @return 移除的个数 250 | */ 251 | public long setRemove(String key, Object... T) { 252 | try { 253 | Long count = redisTemplate.opsForSet().remove(key, T); 254 | return count; 255 | } catch (Exception e) { 256 | e.printStackTrace(); 257 | return 0; 258 | } 259 | } 260 | 261 | // =========================操作list 262 | 263 | /** 264 | * 获取list缓存的内容 265 | * 266 | * @param key 键 267 | * @param start 开始 268 | * @param end 结束 0 到 -1代表所有值 269 | * @return 270 | */ 271 | public List lGet(String key, long start, long end) { 272 | try { 273 | return redisTemplate.opsForList().range(key, start, end); 274 | } catch (Exception e) { 275 | e.printStackTrace(); 276 | return null; 277 | } 278 | } 279 | 280 | /** 281 | * 获取list缓存的长度 282 | * 283 | * @param key 键 284 | * @return 285 | */ 286 | public long lGetListSize(String key) { 287 | try { 288 | return redisTemplate.opsForList().size(key); 289 | } catch (Exception e) { 290 | e.printStackTrace(); 291 | return 0; 292 | } 293 | } 294 | 295 | /** 296 | * 通过索引 获取list中的值 297 | * 298 | * @param key 键 299 | * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推 300 | * @return 301 | */ 302 | public Object lGetIndex(String key, long index) { 303 | try { 304 | return redisTemplate.opsForList().index(key, index); 305 | } catch (Exception e) { 306 | e.printStackTrace(); 307 | return null; 308 | } 309 | } 310 | 311 | /** 312 | * 将list放入缓存 313 | * 314 | * @param key 键 315 | * @param value 值 316 | * @param time 时间(秒) 317 | * @return 318 | */ 319 | public boolean lSet(String key, Object t) { 320 | try { 321 | redisTemplate.opsForList().rightPush(key, t); 322 | return true; 323 | } catch (Exception e) { 324 | e.printStackTrace(); 325 | return false; 326 | } 327 | } 328 | 329 | /** 330 | * 将list放入缓存 331 | * 332 | * @param key 键 333 | * @param value 值 334 | * @param time 时间(秒) 335 | * @return 336 | */ 337 | public boolean lSet(String key, Object t, long time) { 338 | try { 339 | redisTemplate.opsForList().rightPush(key, t); 340 | if (time > 0) 341 | expire(key, time); 342 | return true; 343 | } catch (Exception e) { 344 | e.printStackTrace(); 345 | return false; 346 | } 347 | } 348 | 349 | /** 350 | * 将list放入缓存 351 | * 352 | * @param key 键 353 | * @param value 值 354 | * @param time 时间(秒) 355 | * @return 356 | */ 357 | public boolean lSet(String key, List value) { 358 | try { 359 | redisTemplate.opsForList().rightPushAll(key, value); 360 | return true; 361 | } catch (Exception e) { 362 | e.printStackTrace(); 363 | return false; 364 | } 365 | } 366 | 367 | /** 368 | * 将list放入缓存 369 | * 370 | * @param key 键 371 | * @param value 值 372 | * @param time 时间(秒) 373 | * @return 374 | */ 375 | public boolean lSet(String key, List value, long time) { 376 | try { 377 | redisTemplate.opsForList().rightPushAll(key, value); 378 | if (time > 0) 379 | expire(key, time); 380 | return true; 381 | } catch (Exception e) { 382 | e.printStackTrace(); 383 | return false; 384 | } 385 | } 386 | 387 | /** 388 | * 根据索引修改list中的某条数据 389 | * 390 | * @param key 键 391 | * @param index 索引 392 | * @param value 值 393 | * @return 394 | */ 395 | public boolean lUpdateIndex(String key, long index, Object t) { 396 | try { 397 | redisTemplate.opsForList().set(key, index, t); 398 | return true; 399 | } catch (Exception e) { 400 | e.printStackTrace(); 401 | return false; 402 | } 403 | } 404 | 405 | /** 406 | * 移除N个值为value 407 | * 408 | * @param key 键 409 | * @param count 移除多少个 410 | * @param value 值 411 | * @return 移除的个数 412 | */ 413 | public long lRemove(String key, long count, Object value) { 414 | try { 415 | Long remove = redisTemplate.opsForList().remove(key, count, value); 416 | return remove; 417 | } catch (Exception e) { 418 | e.printStackTrace(); 419 | return 0; 420 | } 421 | } 422 | 423 | // ============================操作Map 424 | 425 | /** 426 | * HashGet 427 | * 428 | * @param key 键 不能为null 429 | * @param item 项 不能为null 430 | * @return 值 431 | */ 432 | public Object hget(String key, String item) { 433 | return redisTemplate.opsForHash().get(key, item); 434 | } 435 | 436 | /** 437 | * 获取hashKey对应的所有键值 438 | * 439 | * @param key 键 440 | * @return 对应的多个键值 441 | */ 442 | public Map hmget(String key) { 443 | return redisTemplate.opsForHash().entries(key); 444 | } 445 | 446 | /** 447 | * HashSet 448 | * 449 | * @param key 键 450 | * @param map 对应多个键值 451 | * @return true 成功 false 失败 452 | */ 453 | public boolean hmset(String key, Map map) { 454 | try { 455 | redisTemplate.opsForHash().putAll(key, map); 456 | return true; 457 | } catch (Exception e) { 458 | e.printStackTrace(); 459 | return false; 460 | } 461 | } 462 | 463 | /** 464 | * HashSet 并设置时间 465 | * 466 | * @param key 键 467 | * @param map 对应多个键值 468 | * @param time 时间(秒) 469 | * @return true成功 false失败 470 | */ 471 | public boolean hmset(String key, Map map, long time) { 472 | try { 473 | redisTemplate.opsForHash().putAll(key, map); 474 | if (time > 0) { 475 | expire(key, time); 476 | } 477 | return true; 478 | } catch (Exception e) { 479 | e.printStackTrace(); 480 | return false; 481 | } 482 | } 483 | 484 | /** 485 | * 向一张hash表中放入数据,如果不存在将创建 486 | * 487 | * @param key 键 488 | * @param item 项 489 | * @param value 值 490 | * @return true 成功 false失败 491 | */ 492 | public boolean hset(String key, String item, Object value) { 493 | try { 494 | redisTemplate.opsForHash().put(key, item, value); 495 | return true; 496 | } catch (Exception e) { 497 | e.printStackTrace(); 498 | return false; 499 | } 500 | } 501 | 502 | /** 503 | * 向一张hash表中放入数据,如果不存在将创建 504 | * 505 | * @param key 键 506 | * @param item 项 507 | * @param value 值 508 | * @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间 509 | * @return true 成功 false失败 510 | */ 511 | public boolean hset(String key, String item, Object value, long time) { 512 | try { 513 | redisTemplate.opsForHash().put(key, item, value); 514 | if (time > 0) { 515 | expire(key, time); 516 | } 517 | return true; 518 | } catch (Exception e) { 519 | e.printStackTrace(); 520 | return false; 521 | } 522 | } 523 | 524 | /** 525 | * 删除hash表中的值 526 | * 527 | * @param key 键 不能为null 528 | * @param item 项 可以使多个 不能为null 529 | */ 530 | public void hdel(String key, Object... item) { 531 | redisTemplate.opsForHash().delete(key, item); 532 | } 533 | 534 | /** 535 | * 判断hash表中是否有该项的值 536 | * 537 | * @param key 键 不能为null 538 | * @param item 项 不能为null 539 | * @return true 存在 false不存在 540 | */ 541 | public boolean hHasKey(String key, String item) { 542 | return redisTemplate.opsForHash().hasKey(key, item); 543 | } 544 | 545 | /** 546 | * hash递增 如果不存在,就会创建一个 并把新增后的值返回 547 | * 548 | * @param key 键 549 | * @param item 项 550 | * @param by 要增加几(大于0) 551 | * @return 552 | */ 553 | public double hincr(String key, String item, double by) { 554 | return redisTemplate.opsForHash().increment(key, item, by); 555 | } 556 | 557 | /** 558 | * hash递减 559 | * 560 | * @param key 键 561 | * @param item 项 562 | * @param by 要减少记(小于0) 563 | * @return 564 | */ 565 | public double hdecr(String key, String item, double by) { 566 | return redisTemplate.opsForHash().increment(key, item, -by); 567 | } 568 | 569 | } 570 | -------------------------------------------------------------------------------- /file-server/src/main/java/com/ykrenz/fileserver/service/client/FastDfsServerClient.java: -------------------------------------------------------------------------------- 1 | package com.ykrenz.fileserver.service.client; 2 | 3 | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; 4 | import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; 5 | import com.baomidou.mybatisplus.core.toolkit.StringUtils; 6 | import com.baomidou.mybatisplus.core.toolkit.Wrappers; 7 | import com.ykrenz.fastdfs.common.Crc32; 8 | import com.ykrenz.fileserver.config.StorageProperties; 9 | import com.ykrenz.fileserver.entity.FileInfo; 10 | import com.ykrenz.fileserver.entity.FilePartInfo; 11 | import com.ykrenz.fileserver.ex.ApiException; 12 | import com.ykrenz.fileserver.mapper.FileInfoMapper; 13 | import com.ykrenz.fileserver.mapper.FilePartInfoMapper; 14 | import com.ykrenz.fileserver.model.ErrorCode; 15 | import com.ykrenz.fileserver.model.request.CancelPartRequest; 16 | import com.ykrenz.fileserver.model.request.CompletePartRequest; 17 | import com.ykrenz.fileserver.model.request.FileInfoRequest; 18 | import com.ykrenz.fileserver.model.request.InitUploadMultipartRequest; 19 | import com.ykrenz.fileserver.model.request.SimpleUploadRequest; 20 | import com.ykrenz.fileserver.model.request.UploadMultipartRequest; 21 | import com.ykrenz.fileserver.model.result.FileInfoResult; 22 | import com.ykrenz.fileserver.model.result.InitMultipartResult; 23 | import com.ykrenz.fastdfs.FastDfs; 24 | import com.ykrenz.fastdfs.model.CompleteMultipartRequest; 25 | import com.ykrenz.fastdfs.model.UploadMultipartPartRequest; 26 | import com.ykrenz.fastdfs.model.fdfs.StorePath; 27 | import lombok.extern.slf4j.Slf4j; 28 | import org.apache.commons.io.FilenameUtils; 29 | import org.springframework.beans.BeanUtils; 30 | import org.springframework.context.ApplicationListener; 31 | import org.springframework.context.annotation.Configuration; 32 | import org.springframework.context.event.ContextClosedEvent; 33 | import org.springframework.context.event.ContextRefreshedEvent; 34 | import org.springframework.transaction.annotation.Transactional; 35 | import org.springframework.web.multipart.MultipartFile; 36 | 37 | import javax.annotation.Resource; 38 | import java.io.IOException; 39 | import java.time.LocalDateTime; 40 | import java.util.List; 41 | import java.util.concurrent.Executors; 42 | import java.util.concurrent.ScheduledExecutorService; 43 | import java.util.concurrent.TimeUnit; 44 | import java.util.stream.Collectors; 45 | 46 | /** 47 | * @author ykren 48 | * @date 2022/3/1 49 | */ 50 | @Slf4j 51 | public class FastDfsServerClient implements FileServerClient, ApplicationListener { 52 | 53 | private final FastDfs fastDfs; 54 | 55 | private final StorageProperties storageProperties; 56 | 57 | private final int partExpireDays; 58 | 59 | @Resource 60 | private FileInfoMapper fileInfoMapper; 61 | 62 | @Resource 63 | private FilePartInfoMapper filePartInfoMapper; 64 | 65 | @Resource 66 | private LockClient lockClient; 67 | 68 | public FastDfsServerClient(FastDfs fastDfs, StorageProperties storageProperties) { 69 | this.fastDfs = fastDfs; 70 | this.storageProperties = storageProperties; 71 | this.partExpireDays = storageProperties.getFastdfs().getPartExpireDays(); 72 | } 73 | 74 | @Override 75 | public FileInfo upload(SimpleUploadRequest request) throws IOException { 76 | MultipartFile file = request.getFile(); 77 | String originalFilename = file.getOriginalFilename(); 78 | String extension = FilenameUtils.getExtension(originalFilename); 79 | StorePath storePath = fastDfs.uploadFile(file.getInputStream(), file.getSize(), extension); 80 | checkCrc32(request.getCrc32(), storePath); 81 | FileInfo fileInfo = new FileInfo(); 82 | fileInfo.setBucketName(storePath.getGroup()); 83 | fileInfo.setObjectName(storePath.getPath()); 84 | // 注意: 这里保存的是有符号crc32 和fastdfs返回的不一致 85 | fileInfo.setCrc32(request.getCrc32()); 86 | fileInfo.setMd5(request.getMd5()); 87 | 88 | fileInfoMapper.insert(fileInfo); 89 | return fileInfo; 90 | } 91 | 92 | private void checkCrc32(Long crc32, StorePath storePath) { 93 | if (crc32 != null && crc32 > 0) { 94 | com.ykrenz.fastdfs.model.fdfs.FileInfo fileInfo = fastDfs.queryFileInfo(storePath.getGroup(), storePath.getPath()); 95 | log.debug("check crc32 client crc32={} fastdfs crc32 ={} convert crc32={}", 96 | crc32, fileInfo.getCrc32(), Crc32.convertUnsigned(fileInfo.getCrc32())); 97 | if (crc32 != Crc32.convertUnsigned(fileInfo.getCrc32())) { 98 | throw new ApiException(ErrorCode.FILE_CRC32_ERROR, true); 99 | } 100 | } 101 | } 102 | 103 | @Override 104 | @Transactional(rollbackFor = Exception.class) 105 | public InitMultipartResult initMultipart(InitUploadMultipartRequest request) { 106 | String uploadId = request.getUploadId(); 107 | if (StringUtils.isNotBlank(uploadId)) { 108 | return check(request); 109 | } 110 | String extension = FilenameUtils.getExtension(request.getFileName()); 111 | StorePath storePath = fastDfs.initMultipartUpload(request.getFileSize(), extension); 112 | FilePartInfo filePartInfo = new FilePartInfo(); 113 | filePartInfo.setFileName(request.getFileName()); 114 | filePartInfo.setBucketName(storePath.getGroup()); 115 | filePartInfo.setObjectName(storePath.getPath()); 116 | filePartInfo.setPartNumber(0); 117 | filePartInfo.setPartSize(request.getPartSize()); 118 | filePartInfo.setFileSize(request.getFileSize()); 119 | 120 | filePartInfoMapper.insert(filePartInfo); 121 | filePartInfo.setUploadId(uploadId); 122 | LambdaUpdateWrapper updateWrapper = Wrappers.lambdaUpdate() 123 | .eq(FilePartInfo::getId, filePartInfo.getId()) 124 | .set(FilePartInfo::getUploadId, filePartInfo.getId()); 125 | filePartInfoMapper.update(null, updateWrapper); 126 | return new InitMultipartResult(filePartInfo.getId(), false); 127 | } 128 | 129 | @Override 130 | @Transactional(rollbackFor = Exception.class) 131 | public FilePartInfo uploadMultipart(UploadMultipartRequest request) throws IOException { 132 | FilePartInfo initPart = checkUpload(request.getUploadId()); 133 | MultipartFile file = request.getFile(); 134 | Integer partNumber = request.getPartNumber(); 135 | UploadMultipartPartRequest multipartPartRequest = UploadMultipartPartRequest.builder() 136 | .groupName(initPart.getBucketName()) 137 | .path(initPart.getObjectName()) 138 | .streamPart(file.getInputStream(), file.getSize(), partNumber, initPart.getPartSize()) 139 | .build(); 140 | fastDfs.uploadMultipart(multipartPartRequest); 141 | 142 | FilePartInfo filePartInfo = new FilePartInfo(); 143 | filePartInfo.setUploadId(request.getUploadId()); 144 | filePartInfo.setBucketName(initPart.getBucketName()); 145 | filePartInfo.setObjectName(initPart.getObjectName()); 146 | filePartInfo.setFileName(initPart.getFileName()); 147 | filePartInfo.setPartNumber(partNumber); 148 | filePartInfo.setPartSize(initPart.getPartSize()); 149 | filePartInfo.setFileSize(file.getSize()); 150 | 151 | filePartInfoMapper.insert(filePartInfo); 152 | return filePartInfo; 153 | } 154 | 155 | @Override 156 | @Transactional(rollbackFor = Exception.class) 157 | public FileInfo completeMultipart(CompletePartRequest request) { 158 | FilePartInfo initPart = checkUpload(request.getUploadId()); 159 | StorePath storePath = new StorePath(initPart.getBucketName(), initPart.getObjectName()); 160 | CompleteMultipartRequest multipartRequest = CompleteMultipartRequest.builder() 161 | .groupName(storePath.getGroup()) 162 | .path(storePath.getPath()) 163 | // 6.0.2以下版本设置为false 164 | .regenerate(true) 165 | .build(); 166 | storePath = fastDfs.completeMultipartUpload(multipartRequest); 167 | 168 | checkCrc32(request.getFileCrc32(), storePath); 169 | 170 | FileInfo fileInfo = new FileInfo(); 171 | fileInfo.setBucketName(storePath.getGroup()); 172 | fileInfo.setObjectName(storePath.getPath()); 173 | fileInfo.setCrc32(request.getFileCrc32()); 174 | fileInfo.setMd5(request.getFileMd5()); 175 | fileInfo.setFileSize(initPart.getFileSize()); 176 | fileInfo.setFileName(initPart.getFileName()); 177 | fileInfoMapper.insert(fileInfo); 178 | 179 | //清空所有分片记录 180 | List parts = filePartInfoMapper.selectList(Wrappers.lambdaQuery() 181 | .eq(FilePartInfo::getUploadId, request.getUploadId()) 182 | .select(FilePartInfo::getId)).stream().map(FilePartInfo::getId).collect(Collectors.toList()); 183 | filePartInfoMapper.deleteBatchIds(parts); 184 | filePartInfoMapper.deleteById(request.getUploadId()); 185 | return fileInfo; 186 | } 187 | 188 | private FilePartInfo getInitPart(String uploadId) { 189 | //TODO 加入缓存 190 | return filePartInfoMapper.selectOne(Wrappers.lambdaQuery() 191 | .eq(FilePartInfo::getId, uploadId) 192 | .eq(FilePartInfo::getPartNumber, 0) 193 | .eq(FilePartInfo::getStatus, 1) 194 | ); 195 | } 196 | 197 | private InitMultipartResult check(InitUploadMultipartRequest request) { 198 | String uploadId = request.getUploadId(); 199 | checkUpload(uploadId); 200 | 201 | Long crc32 = request.getFileCrc32(); 202 | String md5 = request.getFileMd5(); 203 | if (StringUtils.isNotBlank(md5)) { 204 | LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); 205 | if (crc32 != null) { 206 | wrapper.eq(FileInfo::getCrc32, crc32); 207 | } 208 | FileInfo fileInfo = fileInfoMapper.selectOne(wrapper 209 | .eq(FileInfo::getMd5, md5).last(" limit 1")); 210 | if (fileInfo != null) { 211 | return new InitMultipartResult(request.getUploadId(), true); 212 | } 213 | } 214 | 215 | if (StringUtils.isNotBlank(request.getUploadId())) { 216 | List partList = filePartInfoMapper.selectList(Wrappers.lambdaQuery() 217 | .eq(FilePartInfo::getUploadId, request.getUploadId()) 218 | .ne(FilePartInfo::getPartNumber, 0) 219 | .select(FilePartInfo::getId, FilePartInfo::getPartNumber) 220 | ); 221 | 222 | List list = partList.stream() 223 | .map(FilePartInfo::getPartNumber) 224 | .distinct() 225 | .sorted() 226 | .collect(Collectors.toList()); 227 | return new InitMultipartResult(request.getUploadId(), list); 228 | } 229 | return new InitMultipartResult(); 230 | } 231 | 232 | private FilePartInfo checkUpload(String uploadId) { 233 | FilePartInfo initPart = getInitPart(uploadId); 234 | if (initPart == null) { 235 | throw new ApiException(ErrorCode.UPLOAD_ID_NOT_FOUND, true); 236 | } 237 | LocalDateTime createTime = initPart.getCreateTime(); 238 | if (partExpireDays > 0 && createTime != null && isExpire(createTime)) { 239 | throw new ApiException(ErrorCode.UPLOAD_ID_NOT_FOUND, true); 240 | } 241 | return initPart; 242 | } 243 | 244 | private boolean isExpire(LocalDateTime createTime) { 245 | return createTime.plusDays(partExpireDays).isBefore(LocalDateTime.now()); 246 | } 247 | 248 | @Override 249 | @Transactional(rollbackFor = Exception.class) 250 | public void cancelMultipart(CancelPartRequest request) { 251 | FilePartInfo initPart = getInitPart(request.getUploadId()); 252 | if (initPart == null) { 253 | return; 254 | } 255 | deleteUpload(request.getUploadId(), initPart.getBucketName(), initPart.getObjectName()); 256 | } 257 | 258 | private void deleteUpload(String uploadId, String bucketName, String objectName) { 259 | fastDfs.deleteFile(bucketName, objectName); 260 | //清空所有分片记录 261 | List parts = filePartInfoMapper.selectList(Wrappers.lambdaQuery() 262 | .eq(FilePartInfo::getUploadId, uploadId) 263 | .select(FilePartInfo::getId)).stream().map(FilePartInfo::getId).collect(Collectors.toList()); 264 | if (!parts.isEmpty()) { 265 | filePartInfoMapper.deleteBatchIds(parts); 266 | } 267 | filePartInfoMapper.deleteById(uploadId); 268 | } 269 | 270 | @Override 271 | public FileInfoResult info(FileInfoRequest request) { 272 | LambdaQueryWrapper wrapper = Wrappers.lambdaQuery() 273 | .eq(FileInfo::getBucketName, request.getBucketName()) 274 | .eq(FileInfo::getObjectName, request.getObjectName()); 275 | FileInfo fileInfo = fileInfoMapper.selectOne(wrapper); 276 | if (fileInfo == null) { 277 | throw new ApiException(ErrorCode.FILE_NOT_FOUND); 278 | } 279 | 280 | FileInfoResult fileInfoResult = new FileInfoResult(); 281 | BeanUtils.copyProperties(fileInfo, fileInfoResult); 282 | 283 | fileInfoResult.setWebPath(fastDfs.accessUrl(fileInfo.getBucketName(), fileInfo.getObjectName())); 284 | fileInfoResult.setDownloadPath( 285 | fastDfs.downLoadUrl(fileInfo.getBucketName(), fileInfo.getObjectName(), fileInfo.getFileName())); 286 | return fileInfoResult; 287 | } 288 | 289 | @Override 290 | public void deleteAllFiles() { 291 | List filePartInfos = filePartInfoMapper.selectList(Wrappers.emptyWrapper()); 292 | List fileInfos = fileInfoMapper.selectList(Wrappers.emptyWrapper()); 293 | for (FilePartInfo file : filePartInfos) { 294 | fastDfs.deleteFile(file.getBucketName(), file.getObjectName()); 295 | filePartInfoMapper.deleteById(file.getId()); 296 | } 297 | 298 | for (FileInfo file : fileInfos) { 299 | fastDfs.deleteFile(file.getBucketName(), file.getObjectName()); 300 | fileInfoMapper.deleteById(file.getId()); 301 | } 302 | } 303 | 304 | public void clearExpireUpload() { 305 | List uploads = getPartUploads(partExpireDays); 306 | while (!uploads.isEmpty()) { 307 | for (FilePartInfo upload : uploads) { 308 | clearUpload(upload); 309 | } 310 | uploads = getPartUploads(partExpireDays); 311 | } 312 | } 313 | 314 | private List getPartUploads(int expireDays) { 315 | return filePartInfoMapper.selectExpireUploads(expireDays); 316 | } 317 | 318 | private void clearUpload(FilePartInfo filePartInfo) { 319 | this.deleteUpload(filePartInfo.getUploadId(), filePartInfo.getBucketName(), filePartInfo.getObjectName()); 320 | } 321 | 322 | private static final ScheduledExecutorService service = Executors.newScheduledThreadPool(1); 323 | 324 | private static final String CLEAR_LOCK_KEY = "FastDfsClearPartTask"; 325 | 326 | @Override 327 | public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) { 328 | log.info("start clear part service..."); 329 | long evictableSeconds = storageProperties.getFastdfs().getPartEvictableSeconds(); 330 | service.scheduleAtFixedRate(() -> { 331 | try { 332 | if (partExpireDays <= 0) { 333 | return; 334 | } 335 | if (lockClient.tryLock(CLEAR_LOCK_KEY)) { 336 | clearExpireUpload(); 337 | } 338 | } catch (Exception e) { 339 | log.error("clear part error", e); 340 | } finally { 341 | lockClient.unlock(CLEAR_LOCK_KEY); 342 | } 343 | }, 0, evictableSeconds, TimeUnit.SECONDS); 344 | } 345 | 346 | @Configuration 347 | static class FastDfsClearShutdown implements ApplicationListener { 348 | 349 | int awaitTime = 5; 350 | 351 | @Override 352 | public void onApplicationEvent(ContextClosedEvent contextClosedEvent) { 353 | log.info("shutdown clear part service soon."); 354 | service.shutdown(); 355 | try { 356 | if (!service.awaitTermination(awaitTime, TimeUnit.SECONDS)) { 357 | log.warn("Executor did not terminate in the specified time."); 358 | service.shutdownNow(); 359 | } 360 | } catch (Exception e) { 361 | log.error("stop service awaitTermination failed.", e); 362 | service.shutdownNow(); 363 | } 364 | } 365 | } 366 | } 367 | -------------------------------------------------------------------------------- /file-server/src/main/java/com/ykrenz/fileserver/service/client/FileServerClient.java: -------------------------------------------------------------------------------- 1 | package com.ykrenz.fileserver.service.client; 2 | 3 | import com.ykrenz.fileserver.entity.FileInfo; 4 | import com.ykrenz.fileserver.entity.FilePartInfo; 5 | import com.ykrenz.fileserver.model.request.CancelPartRequest; 6 | import com.ykrenz.fileserver.model.request.CompletePartRequest; 7 | import com.ykrenz.fileserver.model.request.FileInfoRequest; 8 | import com.ykrenz.fileserver.model.request.InitUploadMultipartRequest; 9 | import com.ykrenz.fileserver.model.request.SimpleUploadRequest; 10 | import com.ykrenz.fileserver.model.request.UploadMultipartRequest; 11 | import com.ykrenz.fileserver.model.result.FileInfoResult; 12 | import com.ykrenz.fileserver.model.result.InitMultipartResult; 13 | 14 | import java.io.IOException; 15 | 16 | /** 17 | * @author ykren 18 | * @date 2022/3/1 19 | */ 20 | public interface FileServerClient { 21 | 22 | /** 23 | * 上传文件 24 | * 25 | * @param request 26 | * @return 27 | * @throws IOException 28 | */ 29 | FileInfo upload(SimpleUploadRequest request) throws IOException; 30 | 31 | /** 32 | * 初始化分片 33 | * 34 | * @param request 35 | * @return 36 | */ 37 | InitMultipartResult initMultipart(InitUploadMultipartRequest request); 38 | 39 | /** 40 | * 上传分片 41 | * 42 | * @param request 43 | * @return 44 | * @throws IOException 45 | */ 46 | FilePartInfo uploadMultipart(UploadMultipartRequest request) throws IOException; 47 | 48 | /** 49 | * 完成分片上传 50 | * 51 | * @param request 52 | * @return 53 | */ 54 | FileInfo completeMultipart(CompletePartRequest request); 55 | 56 | /** 57 | * 取消分片上传 58 | * 59 | * @param request 60 | */ 61 | void cancelMultipart(CancelPartRequest request); 62 | 63 | /** 64 | * 查询文件信息 65 | * 66 | * @param id 67 | * @return 68 | */ 69 | FileInfoResult info(FileInfoRequest id); 70 | 71 | /** 72 | * 删除数据库所有文件 73 | */ 74 | void deleteAllFiles(); 75 | } 76 | -------------------------------------------------------------------------------- /file-server/src/main/java/com/ykrenz/fileserver/service/client/LockClient.java: -------------------------------------------------------------------------------- 1 | package com.ykrenz.fileserver.service.client; 2 | 3 | public interface LockClient { 4 | /** 5 | * 尝试获取锁 6 | * 7 | * @return 8 | */ 9 | boolean tryLock(String key); 10 | 11 | /** 12 | * 释放锁 13 | */ 14 | void unlock(String key); 15 | } 16 | -------------------------------------------------------------------------------- /file-server/src/main/java/com/ykrenz/fileserver/service/client/SimpleMysqlLock.java: -------------------------------------------------------------------------------- 1 | package com.ykrenz.fileserver.service.client; 2 | 3 | import com.baomidou.mybatisplus.core.toolkit.Wrappers; 4 | import com.ykrenz.fileserver.entity.FileLock; 5 | import com.ykrenz.fileserver.mapper.LockMapper; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.stereotype.Service; 8 | import org.springframework.transaction.annotation.Transactional; 9 | 10 | import javax.annotation.Resource; 11 | import java.time.LocalDateTime; 12 | 13 | @Service 14 | @Slf4j 15 | public class SimpleMysqlLock implements LockClient { 16 | 17 | @Resource 18 | private LockMapper lockMapper; 19 | 20 | @Override 21 | @Transactional(rollbackFor = Exception.class) 22 | public boolean tryLock(String key) { 23 | try { 24 | FileLock lock = lockMapper.selectOne(Wrappers.lambdaQuery().eq(FileLock::getLockKey, key)); 25 | if (lock != null) { 26 | // 防止宕机等意外 27 | if (isExpire(lock)) { 28 | unlock(key); 29 | } 30 | return false; 31 | } 32 | lock = new FileLock(); 33 | lock.setLockKey(key); 34 | lock.setCreateTime(LocalDateTime.now()); 35 | lock.setExpireTime(LocalDateTime.now().plusHours(1)); 36 | return lockMapper.insertIgnore(lock) == 1; 37 | } catch (Exception e) { 38 | log.error("获取锁失败", e); 39 | return false; 40 | } 41 | } 42 | 43 | private boolean isExpire(FileLock lock) { 44 | return lock.getExpireTime().isAfter(LocalDateTime.now()); 45 | } 46 | 47 | @Override 48 | @Transactional(rollbackFor = Exception.class) 49 | public void unlock(String key) { 50 | lockMapper.delete(Wrappers.lambdaQuery().eq(FileLock::getLockKey, key)); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /file-server/src/main/resources/application-dev.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 3000 3 | tomcat: 4 | uri-encoding: UTF-8 5 | accept-count: 1000 6 | max-connections: 10000 7 | threads: 8 | max: 500 9 | min-spare: 10 10 | spring: 11 | servlet: 12 | multipart: 13 | max-file-size: 1024MB 14 | max-request-size: 2048MB 15 | redis: 16 | database: 0 17 | host: localhost 18 | port: 6379 19 | timeout: 5000 20 | jedis: 21 | pool: 22 | max-active: 3000 23 | max-idle: 100 24 | max-wait: 2500 25 | min-idle: 0 26 | datasource: 27 | url: jdbc:mysql://192.168.24.130:12345/filedb?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC&autoReconnect=true&failOverReadOnly=false&useSSL=false 28 | username: root 29 | password: 123456 30 | type: com.zaxxer.hikari.HikariDataSource 31 | driver-class-name: com.mysql.cj.jdbc.Driver 32 | hikari: 33 | maximum-pool-size: 100 34 | minimum-idle: 10 35 | 36 | # knife4j增强 37 | knife4j: 38 | enable: true 39 | 40 | file: 41 | #存储类型 对应的存储服务器配置要开启才可以正常使用 42 | storage: fastdfs 43 | #普通上传文件最大值 44 | max-upload-size: 5MB 45 | #分片文件最小值 小于5M minio会上传错误 46 | multipart-min-size: 5MB 47 | #分片文件最大值 48 | multipart-max-size: 100MB 49 | fastdfs: 50 | part-expire-days: 7 51 | part-evictable-seconds: 3600 52 | #fastdfs 配置 53 | fastdfs: 54 | enabled: true 55 | # tracker服务 56 | tracker-servers: "192.168.24.130:22122" 57 | # 固定分组 58 | # default-group: "group1" 59 | http: 60 | # web地址 61 | web-servers: "http://192.168.24.130:8888" 62 | # 访问地址是否包含group 63 | url-have-group: true 64 | # 开启防盗链 65 | http-anti-steal-token: true 66 | # 防盗链密钥 67 | secret-key: "FastDFS1234567890" 68 | connection: 69 | #scoket超时 70 | socket-timeout: 30000 71 | #连接超时 72 | connect-timeout: 2000 73 | #tracker重试 74 | retry-after-second: 30 75 | pool: 76 | max-wait-millis: 5000 77 | max-total-per-key: 500 78 | max-idle-per-key: 100 79 | min-idle-per-key: 10 80 | min-evictable-idle-time-millis: 1800000 81 | soft-min-evictable-idle-time-millis: 60000 82 | test-on-borrow: true 83 | 84 | # 健康检查端点 actuator/fastdfs 85 | management: 86 | endpoints: 87 | web: 88 | exposure: 89 | include: 'fastdfs' 90 | 91 | s3: 92 | enabled: false 93 | 94 | -------------------------------------------------------------------------------- /file-server/src/main/resources/application-prod.yml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ykrenz/springboot-file-server/e86bfe64f6d69c142a07d05b4c403362a4f70572/file-server/src/main/resources/application-prod.yml -------------------------------------------------------------------------------- /file-server/src/main/resources/application-test.yml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ykrenz/springboot-file-server/e86bfe64f6d69c142a07d05b4c403362a4f70572/file-server/src/main/resources/application-test.yml -------------------------------------------------------------------------------- /file-server/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: file-server 4 | profiles: 5 | active: dev 6 | # 日志 7 | logging: 8 | config: classpath:logback-spring.xml 9 | 10 | 11 | -------------------------------------------------------------------------------- /file-server/src/main/resources/banner.txt: -------------------------------------------------------------------------------- 1 | ${AnsiColor.BRIGHT_RED} 2 | ________ ___ ___ _______ ________ _______ ________ ___ ___ _______ ________ 3 | |\ _____\\ \|\ \ |\ ___ \ |\ ____\|\ ___ \ |\ __ \|\ \ / /|\ ___ \ |\ __ \ 4 | \ \ \__/\ \ \ \ \ \ \ __/| \ \ \___|\ \ __/|\ \ \|\ \ \ \ / / | \ __/|\ \ \|\ \ 5 | \ \ __\\ \ \ \ \ \ \ \_|/__ \ \_____ \ \ \_|/_\ \ _ _\ \ \/ / / \ \ \_|/_\ \ _ _\ 6 | \ \ \_| \ \ \ \ \____\ \ \_|\ \ \|____|\ \ \ \_|\ \ \ \\ \\ \ / / \ \ \_|\ \ \ \\ \| 7 | \ \__\ \ \__\ \_______\ \_______\ ____\_\ \ \_______\ \__\\ _\\ \__/ / \ \_______\ \__\\ _\ 8 | \|__| \|__|\|_______|\|_______| |\_________\|_______|\|__|\|__|\|__|/ \|_______|\|__|\|__| 9 | \|_________| 10 | Spring Boot Version: ${spring-boot.version}${spring-boot.formatted-version} 11 | ${AnsiColor.DEFAULT} -------------------------------------------------------------------------------- /file-server/src/main/resources/logback-spring.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | logback 4 | 5 | 6 | 7 | 8 | 9 | debug 10 | 11 | 12 | %d{yyyy-MM-dd HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{36} - %msg%n 13 | 14 | 15 | 16 | 17 | 18 | ${logPath}/${applicationName}_info.log 19 | 20 | 21 | ${logPath}/${applicationName}/${applicationName}_info.%d{yyyy-MM-dd}.%i.log 22 | 23 | 30 24 | 100MB 25 | 30GB 26 | 27 | 28 | %d{yyyy-MM-dd HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{36} - %msg%n 29 | 30 | 31 | 32 | 33 | 34 | 35 | ${logPath}/${applicationName}_error.log 36 | 37 | 38 | ${logPath}/${applicationName}/${applicationName}_error.%d{yyyy-MM-dd}.%i.log 39 | 40 | 30 41 | 100MB 42 | 30GB 43 | 44 | 45 | %d{yyyy-MM-dd HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{36} - %msg%n 46 | 47 | 48 | 49 | ERROR 50 | ACCEPT 51 | DENY 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /file-server/src/main/resources/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | 10 | 11 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 23 | 91 | 92 | 113 | 114 | Fine Uploader Manual Upload Trigger Demo 115 | 116 | 117 | 119 |
120 | 121 | 123 | 229 | 230 | 308 | 309 | -------------------------------------------------------------------------------- /file-server/src/main/resources/static/crc32/crc32.js: -------------------------------------------------------------------------------- 1 | function Crc32() { 2 | this.crc = -1; 3 | this.table = makeTable(); 4 | 5 | this.append = function (data) { 6 | let crc = this.crc; 7 | for (let offset = 0; offset < data.byteLength; offset++) { 8 | crc = (crc >>> 8) ^ this.table[(crc ^ data[offset]) & 0xFF] 9 | } 10 | this.crc = crc; 11 | } 12 | 13 | this.compute = function () { 14 | return (this.crc ^ -1) >>> 0 15 | } 16 | 17 | function makeTable() { 18 | const table = [] 19 | for (let i = 0; i < 256; i++) { 20 | let t = i 21 | for (let j = 0; j < 8; j++) { 22 | if (t & 1) { 23 | // IEEE 标准 24 | t = (t >>> 1) ^ 0xEDB88320 25 | } else { 26 | t >>>= 1 27 | } 28 | } 29 | table[i] = t 30 | } 31 | return table 32 | } 33 | } 34 | 35 | -------------------------------------------------------------------------------- /file-server/src/main/resources/static/fine-uploader/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2010-2012, Andrew Valums 4 | Copyright (c) 2012-2013, Andrew Valums and Raymond S. Nicholus, III 5 | Copyright (c) 2013-present, Widen Enterprises, Inc. 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | -------------------------------------------------------------------------------- /file-server/src/main/resources/static/fine-uploader/continue.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ykrenz/springboot-file-server/e86bfe64f6d69c142a07d05b4c403362a4f70572/file-server/src/main/resources/static/fine-uploader/continue.gif -------------------------------------------------------------------------------- /file-server/src/main/resources/static/fine-uploader/dnd.min.js: -------------------------------------------------------------------------------- 1 | // Fine Uploader 5.16.2 - MIT licensed. http://fineuploader.com 2 | !function(global){var qq=function(e){"use strict";return{hide:function(){return e.style.display="none",this},attach:function(t,n){return e.addEventListener?e.addEventListener(t,n,!1):e.attachEvent&&e.attachEvent("on"+t,n),function(){qq(e).detach(t,n)}},detach:function(t,n){return e.removeEventListener?e.removeEventListener(t,n,!1):e.attachEvent&&e.detachEvent("on"+t,n),this},contains:function(t){return!!t&&(e===t||(e.contains?e.contains(t):!!(8&t.compareDocumentPosition(e))))},insertBefore:function(t){return t.parentNode.insertBefore(e,t),this},remove:function(){return e.parentNode.removeChild(e),this},css:function(t){if(null==e.style)throw new qq.Error("Can't apply style to node as it is not on the HTMLElement prototype chain!");return null!=t.opacity&&"string"!=typeof e.style.opacity&&void 0!==e.filters&&(t.filter="alpha(opacity="+Math.round(100*t.opacity)+")"),qq.extend(e.style,t),this},hasClass:function(t,n){var r=new RegExp("(^| )"+t+"( |$)");return r.test(e.className)||!(!n||!r.test(e.parentNode.className))},addClass:function(t){return qq(e).hasClass(t)||(e.className+=" "+t),this},removeClass:function(t){var n=new RegExp("(^| )"+t+"( |$)");return e.className=e.className.replace(n," ").replace(/^\s+|\s+$/g,""),this},getByClass:function(t,n){var r,o=[];return n&&e.querySelector?e.querySelector("."+t):e.querySelectorAll?e.querySelectorAll("."+t):(r=e.getElementsByTagName("*"),qq.each(r,function(e,n){qq(n).hasClass(t)&&o.push(n)}),n?o[0]:o)},getFirstByClass:function(t){return qq(e).getByClass(t,!0)},children:function(){for(var t=[],n=e.firstChild;n;)1===n.nodeType&&t.push(n),n=n.nextSibling;return t},setText:function(t){return e.innerText=t,e.textContent=t,this},clearText:function(){return qq(e).setText("")},hasAttribute:function(t){var n;return e.hasAttribute?!!e.hasAttribute(t)&&null==/^false$/i.exec(e.getAttribute(t)):(n=e[t],void 0!==n&&null==/^false$/i.exec(n))}}};!function(){"use strict";qq.canvasToBlob=function(e,t,n){return qq.dataUriToBlob(e.toDataURL(t,n))},qq.dataUriToBlob=function(e){var t,n,r,o,i=function(e,t){var n=window.BlobBuilder||window.WebKitBlobBuilder||window.MozBlobBuilder||window.MSBlobBuilder,r=n&&new n;return r?(r.append(e),r.getBlob(t)):new Blob([e],{type:t})};return n=e.split(",")[0].indexOf("base64")>=0?atob(e.split(",")[1]):decodeURI(e.split(",")[1]),o=e.split(",")[0].split(":")[1].split(";")[0],t=new ArrayBuffer(n.length),r=new Uint8Array(t),qq.each(n,function(e,t){r[e]=t.charCodeAt(0)}),i(t,o)},qq.log=function(e,t){window.console&&(t&&"info"!==t?window.console[t]?window.console[t](e):window.console.log("<"+t+"> "+e):window.console.log(e))},qq.isObject=function(e){return e&&!e.nodeType&&"[object Object]"===Object.prototype.toString.call(e)},qq.isFunction=function(e){return"function"==typeof e},qq.isArray=function(e){return"[object Array]"===Object.prototype.toString.call(e)||e&&window.ArrayBuffer&&e.buffer&&e.buffer.constructor===ArrayBuffer},qq.isItemList=function(e){return"[object DataTransferItemList]"===Object.prototype.toString.call(e)},qq.isNodeList=function(e){return"[object NodeList]"===Object.prototype.toString.call(e)||e.item&&e.namedItem},qq.isString=function(e){return"[object String]"===Object.prototype.toString.call(e)},qq.trimStr=function(e){return String.prototype.trim?e.trim():e.replace(/^\s+|\s+$/g,"")},qq.format=function(e){var t=Array.prototype.slice.call(arguments,1),n=e,r=n.indexOf("{}");return qq.each(t,function(e,t){if(n=n.substring(0,r)+t+n.substring(r+2),r=n.indexOf("{}",r+t.length),r<0)return!1}),n},qq.isFile=function(e){return window.File&&"[object File]"===Object.prototype.toString.call(e)},qq.isFileList=function(e){return window.FileList&&"[object FileList]"===Object.prototype.toString.call(e)},qq.isFileOrInput=function(e){return qq.isFile(e)||qq.isInput(e)},qq.isInput=function(e,t){var n=function(e){var n=e.toLowerCase();return t?"file"!==n:"file"===n};return!!(window.HTMLInputElement&&"[object HTMLInputElement]"===Object.prototype.toString.call(e)&&e.type&&n(e.type))||!!(e.tagName&&"input"===e.tagName.toLowerCase()&&e.type&&n(e.type))},qq.isBlob=function(e){if(window.Blob&&"[object Blob]"===Object.prototype.toString.call(e))return!0},qq.isXhrUploadSupported=function(){var e=document.createElement("input");return e.type="file",void 0!==e.multiple&&"undefined"!=typeof File&&"undefined"!=typeof FormData&&void 0!==qq.createXhrInstance().upload},qq.createXhrInstance=function(){if(window.XMLHttpRequest)return new XMLHttpRequest;try{return new ActiveXObject("MSXML2.XMLHTTP.3.0")}catch(e){return qq.log("Neither XHR or ActiveX are supported!","error"),null}},qq.isFolderDropSupported=function(e){return e.items&&e.items.length>0&&e.items[0].webkitGetAsEntry},qq.isFileChunkingSupported=function(){return!qq.androidStock()&&qq.isXhrUploadSupported()&&(void 0!==File.prototype.slice||void 0!==File.prototype.webkitSlice||void 0!==File.prototype.mozSlice)},qq.sliceBlob=function(e,t,n){return(e.slice||e.mozSlice||e.webkitSlice).call(e,t,n)},qq.arrayBufferToHex=function(e){var t="",n=new Uint8Array(e);return qq.each(n,function(e,n){var r=n.toString(16);r.length<2&&(r="0"+r),t+=r}),t},qq.readBlobToHex=function(e,t,n){var r=qq.sliceBlob(e,t,t+n),o=new FileReader,i=new qq.Promise;return o.onload=function(){i.success(qq.arrayBufferToHex(o.result))},o.onerror=i.failure,o.readAsArrayBuffer(r),i},qq.extend=function(e,t,n){return qq.each(t,function(t,r){n&&qq.isObject(r)?(void 0===e[t]&&(e[t]={}),qq.extend(e[t],r,!0)):e[t]=r}),e},qq.override=function(e,t){var n={},r=t(n);return qq.each(r,function(t,r){void 0!==e[t]&&(n[t]=e[t]),e[t]=r}),e},qq.indexOf=function(e,t,n){if(e.indexOf)return e.indexOf(t,n);n=n||0;var r=e.length;for(n<0&&(n+=r);n=0},qq.safari=function(){return void 0!==navigator.vendor&&navigator.vendor.indexOf("Apple")!==-1},qq.chrome=function(){return void 0!==navigator.vendor&&navigator.vendor.indexOf("Google")!==-1},qq.opera=function(){return void 0!==navigator.vendor&&navigator.vendor.indexOf("Opera")!==-1},qq.firefox=function(){return!qq.edge()&&!qq.ie11()&&navigator.userAgent.indexOf("Mozilla")!==-1&&void 0!==navigator.vendor&&""===navigator.vendor},qq.windows=function(){return"Win32"===navigator.platform},qq.android=function(){return navigator.userAgent.toLowerCase().indexOf("android")!==-1},qq.androidStock=function(){return qq.android()&&navigator.userAgent.toLowerCase().indexOf("chrome")<0},qq.ios6=function(){return qq.ios()&&navigator.userAgent.indexOf(" OS 6_")!==-1},qq.ios7=function(){return qq.ios()&&navigator.userAgent.indexOf(" OS 7_")!==-1},qq.ios8=function(){return qq.ios()&&navigator.userAgent.indexOf(" OS 8_")!==-1},qq.ios800=function(){return qq.ios()&&navigator.userAgent.indexOf(" OS 8_0 ")!==-1},qq.ios=function(){return navigator.userAgent.indexOf("iPad")!==-1||navigator.userAgent.indexOf("iPod")!==-1||navigator.userAgent.indexOf("iPhone")!==-1},qq.iosChrome=function(){return qq.ios()&&navigator.userAgent.indexOf("CriOS")!==-1},qq.iosSafari=function(){return qq.ios()&&!qq.iosChrome()&&navigator.userAgent.indexOf("Safari")!==-1},qq.iosSafariWebView=function(){return qq.ios()&&!qq.iosChrome()&&!qq.iosSafari()},qq.preventDefault=function(e){e.preventDefault?e.preventDefault():e.returnValue=!1},qq.toElement=function(){var e=document.createElement("div");return function(t){e.innerHTML=t;var n=e.firstChild;return e.removeChild(n),n}}(),qq.each=function(e,t){var n,r;if(e)if(window.Storage&&e.constructor===window.Storage)for(n=0;n0)return e.substr(t,e.length-t)},qq.getFilename=function(e){return qq.isInput(e)?e.value.replace(/.*(\/|\\)/,""):qq.isFile(e)&&null!==e.fileName&&void 0!==e.fileName?e.fileName:e.name},qq.DisposeSupport=function(){var e=[];return{dispose:function(){var t;do t=e.shift(),t&&t();while(t)},attach:function(){var e=arguments;this.addDisposer(qq(e[0]).attach.apply(this,Array.prototype.slice.call(arguments,1)))},addDisposer:function(t){e.push(t)}}}}(),function(){"use strict";"function"==typeof define&&define.amd?define(function(){return qq}):"undefined"!=typeof module&&module.exports?module.exports=qq:global.qq=qq}(),qq.version="5.16.2",qq.supportedFeatures=function(){"use strict";function e(){var e,t=!0;try{e=document.createElement("input"),e.type="file",qq(e).hide(),e.disabled&&(t=!1)}catch(e){t=!1}return t}function t(){return(qq.chrome()||qq.opera())&&void 0!==navigator.userAgent.match(/Chrome\/[1][4-9]|Chrome\/[2-9][0-9]/)}function n(){if(window.XMLHttpRequest){return void 0!==qq.createXhrInstance().withCredentials}return!1}function r(){return void 0!==window.XDomainRequest}function o(){return!!n()||r()}function i(){return void 0!==document.createElement("input").webkitdirectory}function a(){try{return!!window.localStorage&&qq.isFunction(window.localStorage.setItem)}catch(e){return!1}}function u(){var e=document.createElement("span");return("draggable"in e||"ondragstart"in e&&"ondrop"in e)&&!qq.android()&&!qq.ios()}var c,s,l,q,f,d,p,g,h,v,m,y,b,w,x;return c=e(),q=c&&qq.isXhrUploadSupported(),s=q&&!qq.androidStock(),l=q&&u(),f=l&&function(){var e=document.createElement("input");return e.type="file",!!("webkitdirectory"in(e||document.querySelectorAll("input[type=file]")[0]))}(),d=q&&qq.isFileChunkingSupported(),p=q&&d&&a(),g=q&&t(),h=c&&(void 0!==window.postMessage||q),m=n(),v=r(),y=o(),b=i(),w=q&&void 0!==window.FileReader,x=function(){return!!q&&(!qq.androidStock()&&!qq.iosChrome())}(),{ajaxUploading:q,blobUploading:s,canDetermineSize:q,chunking:d,deleteFileCors:y,deleteFileCorsXdr:v,deleteFileCorsXhr:m,dialogElement:!!window.HTMLDialogElement,fileDrop:l,folderDrop:f,folderSelection:b,imagePreviews:w,imageValidation:w,itemSizeValidation:q,pause:d,progressBar:x,resume:p,scaling:w&&s,tiffPreviews:qq.safari(),unlimitedScaledImageSize:!qq.ios(),uploading:c,uploadCors:h,uploadCustomHeaders:q,uploadNonMultipart:q,uploadViaPaste:g}}(),qq.isGenericPromise=function(e){"use strict";return!!(e&&e.then&&qq.isFunction(e.then))},qq.Promise=function(){"use strict";var e,t,n=[],r=[],o=[],i=0;qq.extend(this,{then:function(o,a){return 0===i?(o&&n.push(o),a&&r.push(a)):i===-1?a&&a.apply(null,t):o&&o.apply(null,e),this},done:function(n){return 0===i?o.push(n):n.apply(null,void 0===t?e:t),this},success:function(){return i=1,e=arguments,n.length&&qq.each(n,function(t,n){n.apply(null,e)}),o.length&&qq.each(o,function(t,n){n.apply(null,e)}),this},failure:function(){return i=-1,t=arguments,r.length&&qq.each(r,function(e,n){n.apply(null,t)}),o.length&&qq.each(o,function(e,n){n.apply(null,t)}),this}})},qq.DragAndDrop=function(e){"use strict";function t(e,t){var n=Array.prototype.slice.call(e);l.callbacks.dropLog("Grabbed "+e.length+" dropped files."),t.dropDisabled(!1),l.callbacks.processingDroppedFilesComplete(n,t.getElement())}function n(e){var t=new qq.Promise;return e.isFile?e.file(function(n){n.qqPath=r(e),f.push(n),t.success()},function(n){l.callbacks.dropLog("Problem parsing '"+e.fullPath+"'. FileError code "+n.code+".","error"),t.failure()}):e.isDirectory&&o(e).then(function(e){var r=e.length;qq.each(e,function(e,o){n(o).done(function(){r-=1,0===r&&t.success()})}),e.length||t.success()},function(n){l.callbacks.dropLog("Problem parsing '"+e.fullPath+"'. FileError code "+n.code+".","error"),t.failure()}),t}function r(e){var t=e.name,n=e.fullPath,r=n.lastIndexOf(t);return n=n.substr(0,r),"/"===n.charAt(0)&&(n=n.substr(1)),n}function o(e,t,n,r){var i=r||new qq.Promise,a=t||e.createReader();return a.readEntries(function(t){var r=n?n.concat(t):t;t.length?setTimeout(function(){o(e,a,r,i)},0):i.success(r)},i.failure),i}function i(e,t){var r=[],o=new qq.Promise;return l.callbacks.processingDroppedFiles(),t.dropDisabled(!0),e.files.length>1&&!l.allowMultipleItems?(l.callbacks.processingDroppedFilesComplete([]),l.callbacks.dropError("tooManyFilesError",""),t.dropDisabled(!1),o.failure()):(f=[],qq.isFolderDropSupported(e)?qq.each(e.items,function(e,t){var i=t.webkitGetAsEntry();i&&(i.isFile?f.push(t.getAsFile()):r.push(n(i).done(function(){r.pop(),0===r.length&&o.success()})))}):f=e.files,0===r.length&&o.success()),o}function a(e){var n=new qq.UploadDropZone({HIDE_ZONES_EVENT_NAME:"qq-hidezones",element:e,onEnter:function(t){qq(e).addClass(l.classes.dropActive),t.stopPropagation()},onLeaveNotDescendants:function(t){qq(e).removeClass(l.classes.dropActive)},onDrop:function(e){i(e.dataTransfer,n).then(function(){t(f,n)},function(){l.callbacks.dropLog("Drop event DataTransfer parsing failed. No files will be uploaded.","error")})}});return d.addDisposer(function(){n.dispose()}),qq(e).hasAttribute("qq-hide-dropzone")&&qq(e).hide(),q.push(n),n}function u(e){var t;return qq.each(e.dataTransfer.types,function(e,n){if("Files"===n)return t=!0,!1}),t}function c(e){return qq.safari()?e.x<0||e.y<0:0===e.x&&0===e.y}function s(){var e=l.dropZoneElements,t=function(){setTimeout(function(){qq.each(e,function(e,t){qq(t).hasAttribute("qq-hide-dropzone")&&qq(t).hide(),qq(t).removeClass(l.classes.dropActive)})},10)};qq.each(e,function(t,n){var r=a(n);e.length&&qq.supportedFeatures.fileDrop&&d.attach(document,"dragenter",function(t){!r.dropDisabled()&&u(t)&&qq.each(e,function(e,t){t instanceof HTMLElement&&qq(t).hasAttribute("qq-hide-dropzone")&&qq(t).css({display:"block"})})})}),d.attach(document,"dragleave",function(e){c(e)&&t()}),d.attach(qq(document).children()[0],"mouseenter",function(e){t()}),d.attach(document,"drop",function(e){u(e)&&(e.preventDefault(),t())}),d.attach(document,"qq-hidezones",t)}var l,q=[],f=[],d=new qq.DisposeSupport;l={dropZoneElements:[],allowMultipleItems:!0,classes:{dropActive:null},callbacks:new qq.DragAndDrop.callbacks},qq.extend(l,e,!0),s(),qq.extend(this,{setupExtraDropzone:function(e){l.dropZoneElements.push(e),a(e)},removeDropzone:function(e){var t,n=l.dropZoneElements;for(t in n)if(n[t]===e)return n.splice(t,1)},dispose:function(){d.dispose(),qq.each(q,function(e,t){t.dispose()})}}),this._testing={},this._testing.extractDirectoryPath=r},qq.DragAndDrop.callbacks=function(){"use strict";return{processingDroppedFiles:function(){},processingDroppedFilesComplete:function(e,t){},dropError:function(e,t){qq.log("Drag & drop error code '"+e+" with these specifics: '"+t+"'","error")},dropLog:function(e,t){qq.log(e,t)}}},qq.UploadDropZone=function(e){"use strict";function t(){return qq.safari()||qq.firefox()&&qq.windows()}function n(e){l||(t?q.attach(document,"dragover",function(e){e.preventDefault()}):q.attach(document,"dragover",function(e){e.dataTransfer&&(e.dataTransfer.dropEffect="none",e.preventDefault())}),l=!0)}function r(e){if(!qq.supportedFeatures.fileDrop)return!1;var t,n=e.dataTransfer,r=qq.safari();return t=!(!qq.ie()||!qq.supportedFeatures.fileDrop)||"none"!==n.effectAllowed,n&&t&&(n.files&&n.files.length||!r&&n.types.contains&&n.types.contains("Files")||n.types.includes&&n.types.includes("Files"))}function o(e){return void 0!==e&&(s=e),s}function i(){function e(){t=document.createEvent("Event"),t.initEvent(u.HIDE_ZONES_EVENT_NAME,!0,!0)}var t;if(window.CustomEvent)try{t=new CustomEvent(u.HIDE_ZONES_EVENT_NAME)}catch(t){e()}else e();document.dispatchEvent(t)}function a(){q.attach(c,"dragover",function(e){if(r(e)){var t=qq.ie()&&qq.supportedFeatures.fileDrop?null:e.dataTransfer.effectAllowed;e.dataTransfer.dropEffect="move"===t||"linkMove"===t?"move":"copy",e.stopPropagation(),e.preventDefault()}}),q.attach(c,"dragenter",function(e){if(!o()){if(!r(e))return;u.onEnter(e)}}),q.attach(c,"dragleave",function(e){if(r(e)){u.onLeave(e);var t=document.elementFromPoint(e.clientX,e.clientY);qq(this).contains(t)||u.onLeaveNotDescendants(e)}}),q.attach(c,"drop",function(e){if(!o()){if(!r(e))return;e.preventDefault(),e.stopPropagation(),u.onDrop(e),i()}})}var u,c,s,l,q=new qq.DisposeSupport;u={element:null,onEnter:function(e){},onLeave:function(e){},onLeaveNotDescendants:function(e){},onDrop:function(e){}},qq.extend(u,e),c=u.element,n(),a(),qq.extend(this,{dropDisabled:function(e){return o(e)},dispose:function(){q.dispose()},getElement:function(){return c}}),this._testing={},this._testing.isValidFileDrag=r}}(window); 3 | //# sourceMappingURL=dnd.min.js.map -------------------------------------------------------------------------------- /file-server/src/main/resources/static/fine-uploader/edit.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ykrenz/springboot-file-server/e86bfe64f6d69c142a07d05b4c403362a4f70572/file-server/src/main/resources/static/fine-uploader/edit.gif -------------------------------------------------------------------------------- /file-server/src/main/resources/static/fine-uploader/fine-uploader-gallery.css: -------------------------------------------------------------------------------- 1 | /* --------------------------------------- 2 | /* Fine Uploader Gallery View Styles 3 | /* --------------------------------------- 4 | 5 | 6 | /* Buttons 7 | ------------------------------------------ */ 8 | .qq-gallery .qq-btn 9 | { 10 | float: right; 11 | border: none; 12 | padding: 0; 13 | margin: 0; 14 | box-shadow: none; 15 | } 16 | 17 | /* Upload Button 18 | ------------------------------------------ */ 19 | .qq-gallery .qq-upload-button { 20 | display: inline; 21 | width: 105px; 22 | padding: 7px 10px; 23 | float: left; 24 | text-align: center; 25 | background: #00ABC7; 26 | color: #FFFFFF; 27 | border-radius: 2px; 28 | border: 1px solid #37B7CC; 29 | box-shadow: 0 1px 1px rgba(255, 255, 255, 0.37) inset, 30 | 1px 0 1px rgba(255, 255, 255, 0.07) inset, 31 | 0 1px 0 rgba(0, 0, 0, 0.36), 32 | 0 -2px 12px rgba(0, 0, 0, 0.08) inset 33 | } 34 | .qq-gallery .qq-upload-button-hover { 35 | background: #33B6CC; 36 | } 37 | .qq-gallery .qq-upload-button-focus { 38 | outline: 1px dotted #000000; 39 | } 40 | 41 | 42 | /* Drop Zone 43 | ------------------------------------------ */ 44 | .qq-gallery.qq-uploader { 45 | position: relative; 46 | min-height: 200px; 47 | max-height: 490px; 48 | overflow-y: hidden; 49 | width: inherit; 50 | border-radius: 6px; 51 | border: 1px dashed #CCCCCC; 52 | background-color: #FAFAFA; 53 | padding: 20px; 54 | } 55 | .qq-gallery.qq-uploader:before { 56 | content: attr(qq-drop-area-text) " "; 57 | position: absolute; 58 | font-size: 200%; 59 | left: 0; 60 | width: 100%; 61 | text-align: center; 62 | top: 45%; 63 | opacity: 0.25; 64 | filter: alpha(opacity=25); 65 | } 66 | .qq-gallery .qq-upload-drop-area, .qq-upload-extra-drop-area { 67 | position: absolute; 68 | top: 0; 69 | left: 0; 70 | width: 100%; 71 | height: 100%; 72 | min-height: 30px; 73 | z-index: 2; 74 | background: #F9F9F9; 75 | border-radius: 4px; 76 | text-align: center; 77 | } 78 | .qq-gallery .qq-upload-drop-area span { 79 | display: block; 80 | position: absolute; 81 | top: 50%; 82 | width: 100%; 83 | margin-top: -8px; 84 | font-size: 16px; 85 | } 86 | .qq-gallery .qq-upload-extra-drop-area { 87 | position: relative; 88 | margin-top: 50px; 89 | font-size: 16px; 90 | padding-top: 30px; 91 | height: 20px; 92 | min-height: 40px; 93 | } 94 | .qq-gallery .qq-upload-drop-area-active { 95 | background: #FDFDFD; 96 | border-radius: 4px; 97 | } 98 | .qq-gallery .qq-upload-list { 99 | margin: 0; 100 | padding: 10px 0 0; 101 | list-style: none; 102 | max-height: 450px; 103 | overflow-y: auto; 104 | clear: both; 105 | box-shadow: none; 106 | } 107 | 108 | 109 | /* Uploaded Elements 110 | ------------------------------------------ */ 111 | .qq-gallery .qq-upload-list li { 112 | display: inline-block; 113 | position: relative; 114 | max-width: 120px; 115 | margin: 0 25px 25px 0; 116 | padding: 0; 117 | line-height: 16px; 118 | font-size: 13px; 119 | color: #424242; 120 | background-color: #FFFFFF; 121 | border-radius: 2px; 122 | box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.22); 123 | vertical-align: top; 124 | 125 | /* to ensure consistent size of tiles - may need to change if qq-max-size attr on preview img changes */ 126 | height: 186px; 127 | } 128 | 129 | .qq-gallery .qq-upload-spinner, 130 | .qq-gallery .qq-upload-size, 131 | .qq-gallery .qq-upload-retry, 132 | .qq-gallery .qq-upload-failed-text, 133 | .qq-gallery .qq-upload-delete, 134 | .qq-gallery .qq-upload-pause, 135 | .qq-gallery .qq-upload-continue { 136 | display: inline; 137 | } 138 | .qq-gallery .qq-upload-retry:hover, 139 | .qq-gallery .qq-upload-delete:hover, 140 | .qq-gallery .qq-upload-pause:hover, 141 | .qq-gallery .qq-upload-continue:hover { 142 | background-color: transparent; 143 | } 144 | .qq-gallery .qq-upload-delete, 145 | .qq-gallery .qq-upload-pause, 146 | .qq-gallery .qq-upload-continue, 147 | .qq-gallery .qq-upload-cancel { 148 | cursor: pointer; 149 | } 150 | .qq-gallery .qq-upload-delete, 151 | .qq-gallery .qq-upload-pause, 152 | .qq-gallery .qq-upload-continue { 153 | border:none; 154 | background: none; 155 | color: #00A0BA; 156 | font-size: 12px; 157 | padding: 0; 158 | } 159 | /* to ensure consistent size of tiles - only display status text before auto-retry or after failure */ 160 | .qq-gallery .qq-upload-status-text { 161 | color: #333333; 162 | font-size: 12px; 163 | padding-left: 3px; 164 | padding-top: 2px; 165 | width: inherit; 166 | display: none; 167 | width: 108px; 168 | } 169 | .qq-gallery .qq-upload-fail .qq-upload-status-text { 170 | text-overflow: ellipsis; 171 | white-space: nowrap; 172 | overflow-x: hidden; 173 | display: block; 174 | } 175 | .qq-gallery .qq-upload-retrying .qq-upload-status-text { 176 | display: inline-block; 177 | } 178 | .qq-gallery .qq-upload-retrying .qq-progress-bar-container { 179 | display: none; 180 | } 181 | 182 | .qq-gallery .qq-upload-cancel { 183 | background-color: #525252; 184 | color: #F7F7F7; 185 | font-weight: bold; 186 | font-family: Arial, Helvetica, sans-serif; 187 | border-radius: 12px; 188 | border: none; 189 | height: 22px; 190 | width: 22px; 191 | padding: 4px; 192 | position: absolute; 193 | right: -5px; 194 | top: -6px; 195 | margin: 0; 196 | line-height: 17px; 197 | } 198 | .qq-gallery .qq-upload-cancel:hover { 199 | background-color: #525252; 200 | } 201 | .qq-gallery .qq-upload-retry { 202 | cursor: pointer; 203 | position: absolute; 204 | top: 30px; 205 | left: 50%; 206 | margin-left: -31px; 207 | box-shadow: 0 1px 1px rgba(255, 255, 255, 0.37) inset, 208 | 1px 0 1px rgba(255, 255, 255, 0.07) inset, 209 | 0 4px 4px rgba(0, 0, 0, 0.5), 210 | 0 -2px 12px rgba(0, 0, 0, 0.08) inset; 211 | padding: 3px 4px; 212 | border: 1px solid #d2ddc7; 213 | border-radius: 2px; 214 | color: inherit; 215 | background-color: #EBF6E0; 216 | z-index: 1; 217 | } 218 | .qq-gallery .qq-upload-retry:hover { 219 | background-color: #f7ffec; 220 | } 221 | 222 | .qq-gallery .qq-file-info { 223 | padding: 10px 6px 4px; 224 | margin-top: -3px; 225 | border-radius: 0 0 2px 2px; 226 | text-align: left; 227 | overflow: hidden; 228 | } 229 | 230 | .qq-gallery .qq-file-info .qq-file-name { 231 | position: relative; 232 | } 233 | 234 | .qq-gallery .qq-upload-file { 235 | display: block; 236 | margin-right: 0; 237 | margin-bottom: 3px; 238 | width: auto; 239 | 240 | /* to ensure consistent size of tiles - constrain text to single line */ 241 | text-overflow: ellipsis; 242 | white-space: nowrap; 243 | overflow-x: hidden; 244 | } 245 | .qq-gallery .qq-upload-spinner { 246 | display: inline-block; 247 | background: url("loading.gif"); 248 | position: absolute; 249 | left: 50%; 250 | margin-left: -7px; 251 | top: 53px; 252 | width: 15px; 253 | height: 15px; 254 | vertical-align: text-bottom; 255 | } 256 | .qq-gallery .qq-drop-processing { 257 | display: block; 258 | } 259 | .qq-gallery .qq-drop-processing-spinner { 260 | display: inline-block; 261 | background: url("processing.gif"); 262 | width: 24px; 263 | height: 24px; 264 | vertical-align: text-bottom; 265 | } 266 | .qq-gallery .qq-upload-failed-text { 267 | display: none; 268 | font-style: italic; 269 | font-weight: bold; 270 | } 271 | .qq-gallery .qq-upload-failed-icon { 272 | display:none; 273 | width:15px; 274 | height:15px; 275 | vertical-align:text-bottom; 276 | } 277 | .qq-gallery .qq-upload-fail .qq-upload-failed-text { 278 | display: inline; 279 | } 280 | .qq-gallery .qq-upload-retrying .qq-upload-failed-text { 281 | display: inline; 282 | } 283 | .qq-gallery .qq-upload-list li.qq-upload-success { 284 | background-color: #F2F7ED; 285 | } 286 | .qq-gallery .qq-upload-list li.qq-upload-fail { 287 | background-color: #F5EDED; 288 | box-shadow: 0 0 1px 0 red; 289 | border: 0; 290 | } 291 | .qq-gallery .qq-progress-bar { 292 | display: block; 293 | background: #00abc7; 294 | width: 0%; 295 | height: 15px; 296 | border-radius: 6px; 297 | margin-bottom: 3px; 298 | } 299 | 300 | .qq-gallery .qq-total-progress-bar { 301 | height: 25px; 302 | border-radius: 9px; 303 | } 304 | 305 | .qq-gallery .qq-total-progress-bar-container { 306 | margin-left: 9px; 307 | display: inline; 308 | float: right; 309 | width: 500px; 310 | } 311 | 312 | .qq-gallery .qq-upload-size { 313 | float: left; 314 | font-size: 11px; 315 | color: #929292; 316 | margin-bottom: 3px; 317 | margin-right: 0; 318 | display: inline-block; 319 | } 320 | 321 | .qq-gallery INPUT.qq-edit-filename { 322 | position: absolute; 323 | opacity: 0; 324 | filter: alpha(opacity=0); 325 | z-index: -1; 326 | -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; 327 | } 328 | 329 | .qq-gallery .qq-upload-file.qq-editable { 330 | cursor: pointer; 331 | margin-right: 20px; 332 | } 333 | 334 | .qq-gallery .qq-edit-filename-icon.qq-editable { 335 | display: inline-block; 336 | cursor: pointer; 337 | position: absolute; 338 | right: 0; 339 | top: 0; 340 | } 341 | 342 | .qq-gallery INPUT.qq-edit-filename.qq-editing { 343 | position: static; 344 | height: 28px; 345 | width: 90px; 346 | width: -moz-available; 347 | padding: 0 8px; 348 | margin-bottom: 3px; 349 | border: 1px solid #ccc; 350 | border-radius: 2px; 351 | font-size: 13px; 352 | 353 | opacity: 1; 354 | filter: alpha(opacity=100); 355 | -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)"; 356 | } 357 | 358 | .qq-gallery .qq-edit-filename-icon { 359 | display: none; 360 | background: url("edit.gif"); 361 | width: 15px; 362 | height: 15px; 363 | vertical-align: text-bottom; 364 | } 365 | .qq-gallery .qq-delete-icon { 366 | background: url("trash.gif"); 367 | width: 15px; 368 | height: 15px; 369 | vertical-align: sub; 370 | display: inline-block; 371 | } 372 | .qq-gallery .qq-retry-icon { 373 | background: url("retry.gif"); 374 | width: 15px; 375 | height: 15px; 376 | vertical-align: sub; 377 | display: inline-block; 378 | float: none; 379 | } 380 | .qq-gallery .qq-continue-icon { 381 | background: url("continue.gif"); 382 | width: 15px; 383 | height: 15px; 384 | vertical-align: sub; 385 | display: inline-block; 386 | } 387 | .qq-gallery .qq-pause-icon { 388 | background: url("pause.gif"); 389 | width: 15px; 390 | height: 15px; 391 | vertical-align: sub; 392 | display: inline-block; 393 | } 394 | 395 | .qq-gallery .qq-hide { 396 | display: none; 397 | } 398 | 399 | 400 | /* Thumbnail 401 | ------------------------------------------ */ 402 | .qq-gallery .qq-in-progress .qq-thumbnail-wrapper { 403 | /* makes the spinner on top of the thumbnail more visible */ 404 | opacity: 0.5; 405 | filter: alpha(opacity=50); 406 | } 407 | .qq-gallery .qq-thumbnail-wrapper { 408 | overflow: hidden; 409 | position: relative; 410 | 411 | /* to ensure consistent size of tiles - should match qq-max-size attribute value on qq-thumbnail-selector IMG element */ 412 | height: 120px; 413 | width: 120px; 414 | } 415 | .qq-gallery .qq-thumbnail-selector { 416 | border-radius: 2px 2px 0 0; 417 | bottom: 0; 418 | 419 | /* we will override this in the :root thumbnail selector (to help center the preview) for everything other than IE8 */ 420 | top: 0; 421 | 422 | /* center the thumb horizontally in the tile */ 423 | margin:auto; 424 | display: block; 425 | } 426 | 427 | /* hack to ensure we don't try to center preview in IE8, since -ms-filter doesn't mimic translateY as expected in all cases */ 428 | :root *> .qq-gallery .qq-thumbnail-selector { 429 | /* vertically center preview image on tile */ 430 | position: relative; 431 | top: 50%; 432 | transform: translateY(-50%); 433 | -moz-transform: translateY(-50%); 434 | -ms-transform: translateY(-50%); 435 | -webkit-transform: translateY(-50%); 436 | } 437 | 438 | /* element styles */ 439 | .qq-gallery.qq-uploader DIALOG { 440 | display: none; 441 | } 442 | 443 | .qq-gallery.qq-uploader DIALOG[open] { 444 | display: block; 445 | } 446 | 447 | .qq-gallery.qq-uploader DIALOG { 448 | display: none; 449 | } 450 | 451 | .qq-gallery.qq-uploader DIALOG[open] { 452 | display: block; 453 | } 454 | 455 | .qq-gallery.qq-uploader DIALOG .qq-dialog-buttons { 456 | text-align: center; 457 | padding-top: 10px; 458 | } 459 | 460 | .qq-gallery.qq-uploader DIALOG .qq-dialog-buttons BUTTON { 461 | margin-left: 5px; 462 | margin-right: 5px; 463 | } 464 | 465 | .qq-gallery.qq-uploader DIALOG .qq-dialog-message-selector { 466 | padding-bottom: 10px; 467 | } 468 | 469 | .qq-gallery .qq-uploader DIALOG::backdrop { 470 | background-color: rgba(0, 0, 0, 0.7); 471 | } -------------------------------------------------------------------------------- /file-server/src/main/resources/static/fine-uploader/fine-uploader-gallery.min.css: -------------------------------------------------------------------------------- 1 | .qq-gallery .qq-btn{float:right;border:none;padding:0;margin:0;box-shadow:none}.qq-gallery .qq-upload-button{display:inline;width:105px;padding:7px 10px;float:left;text-align:center;background:#00abc7;color:#fff;border-radius:2px;border:1px solid #37b7cc;box-shadow:0 1px 1px rgba(255,255,255,.37) inset,1px 0 1px rgba(255,255,255,.07) inset,0 1px 0 rgba(0,0,0,.36),0 -2px 12px rgba(0,0,0,.08) inset}.qq-gallery .qq-upload-button-hover{background:#33b6cc}.qq-gallery .qq-upload-button-focus{outline:1px dotted #000}.qq-gallery.qq-uploader{position:relative;min-height:200px;max-height:490px;overflow-y:hidden;width:inherit;border-radius:6px;border:1px dashed #ccc;background-color:#fafafa;padding:20px}.qq-gallery.qq-uploader:before{content:attr(qq-drop-area-text) " ";position:absolute;font-size:200%;left:0;width:100%;text-align:center;top:45%;opacity:.25}.qq-gallery .qq-upload-drop-area,.qq-upload-extra-drop-area{position:absolute;top:0;left:0;width:100%;height:100%;min-height:30px;z-index:2;background:#f9f9f9;border-radius:4px;text-align:center}.qq-gallery .qq-upload-drop-area span{display:block;position:absolute;top:50%;width:100%;margin-top:-8px;font-size:16px}.qq-gallery .qq-upload-extra-drop-area{position:relative;margin-top:50px;font-size:16px;padding-top:30px;height:20px;min-height:40px}.qq-gallery .qq-upload-drop-area-active{background:#fdfdfd;border-radius:4px}.qq-gallery .qq-upload-list{margin:0;padding:10px 0 0;list-style:none;max-height:450px;overflow-y:auto;clear:both;box-shadow:none}.qq-gallery .qq-upload-list li{display:inline-block;position:relative;max-width:120px;margin:0 25px 25px 0;padding:0;line-height:16px;font-size:13px;color:#424242;background-color:#fff;border-radius:2px;box-shadow:0 1px 1px 0 rgba(0,0,0,.22);vertical-align:top;height:186px}.qq-gallery .qq-upload-continue,.qq-gallery .qq-upload-delete,.qq-gallery .qq-upload-failed-text,.qq-gallery .qq-upload-pause,.qq-gallery .qq-upload-retry,.qq-gallery .qq-upload-size,.qq-gallery .qq-upload-spinner{display:inline}.qq-gallery .qq-upload-continue:hover,.qq-gallery .qq-upload-delete:hover,.qq-gallery .qq-upload-pause:hover,.qq-gallery .qq-upload-retry:hover{background-color:transparent}.qq-gallery .qq-upload-cancel,.qq-gallery .qq-upload-continue,.qq-gallery .qq-upload-delete,.qq-gallery .qq-upload-pause{cursor:pointer}.qq-gallery .qq-upload-continue,.qq-gallery .qq-upload-delete,.qq-gallery .qq-upload-pause{border:none;background:0 0;color:#00a0ba;font-size:12px;padding:0}.qq-gallery .qq-upload-status-text{color:#333;font-size:12px;padding-left:3px;padding-top:2px;width:inherit;display:none;width:108px}.qq-gallery .qq-upload-fail .qq-upload-status-text{text-overflow:ellipsis;white-space:nowrap;overflow-x:hidden;display:block}.qq-gallery .qq-upload-retrying .qq-upload-status-text{display:inline-block}.qq-gallery .qq-upload-retrying .qq-progress-bar-container{display:none}.qq-gallery .qq-upload-cancel{background-color:#525252;color:#f7f7f7;font-weight:700;font-family:Arial,Helvetica,sans-serif;border-radius:12px;border:none;height:22px;width:22px;padding:4px;position:absolute;right:-5px;top:-6px;margin:0;line-height:17px}.qq-gallery .qq-upload-cancel:hover{background-color:#525252}.qq-gallery .qq-upload-retry{cursor:pointer;position:absolute;top:30px;left:50%;margin-left:-31px;box-shadow:0 1px 1px rgba(255,255,255,.37) inset,1px 0 1px rgba(255,255,255,.07) inset,0 4px 4px rgba(0,0,0,.5),0 -2px 12px rgba(0,0,0,.08) inset;padding:3px 4px;border:1px solid #d2ddc7;border-radius:2px;color:inherit;background-color:#ebf6e0;z-index:1}.qq-gallery .qq-upload-retry:hover{background-color:#f7ffec}.qq-gallery .qq-file-info{padding:10px 6px 4px;margin-top:-3px;border-radius:0 0 2px 2px;text-align:left;overflow:hidden}.qq-gallery .qq-file-info .qq-file-name{position:relative}.qq-gallery .qq-upload-file{display:block;margin-right:0;margin-bottom:3px;width:auto;text-overflow:ellipsis;white-space:nowrap;overflow-x:hidden}.qq-gallery .qq-upload-spinner{display:inline-block;background:url(loading.gif);position:absolute;left:50%;margin-left:-7px;top:53px;width:15px;height:15px;vertical-align:text-bottom}.qq-gallery .qq-drop-processing{display:block}.qq-gallery .qq-drop-processing-spinner{display:inline-block;background:url(processing.gif);width:24px;height:24px;vertical-align:text-bottom}.qq-gallery .qq-upload-failed-text{display:none;font-style:italic;font-weight:700}.qq-gallery .qq-upload-failed-icon{display:none;width:15px;height:15px;vertical-align:text-bottom}.qq-gallery .qq-upload-fail .qq-upload-failed-text{display:inline}.qq-gallery .qq-upload-retrying .qq-upload-failed-text{display:inline}.qq-gallery .qq-upload-list li.qq-upload-success{background-color:#f2f7ed}.qq-gallery .qq-upload-list li.qq-upload-fail{background-color:#f5eded;box-shadow:0 0 1px 0 red;border:0}.qq-gallery .qq-progress-bar{display:block;background:#00abc7;width:0;height:15px;border-radius:6px;margin-bottom:3px}.qq-gallery .qq-total-progress-bar{height:25px;border-radius:9px}.qq-gallery .qq-total-progress-bar-container{margin-left:9px;display:inline;float:right;width:500px}.qq-gallery .qq-upload-size{float:left;font-size:11px;color:#929292;margin-bottom:3px;margin-right:0;display:inline-block}.qq-gallery INPUT.qq-edit-filename{position:absolute;opacity:0;z-index:-1}.qq-gallery .qq-upload-file.qq-editable{cursor:pointer;margin-right:20px}.qq-gallery .qq-edit-filename-icon.qq-editable{display:inline-block;cursor:pointer;position:absolute;right:0;top:0}.qq-gallery INPUT.qq-edit-filename.qq-editing{position:static;height:28px;width:90px;width:-moz-available;padding:0 8px;margin-bottom:3px;border:1px solid #ccc;border-radius:2px;font-size:13px;opacity:1}.qq-gallery .qq-edit-filename-icon{display:none;background:url(edit.gif);width:15px;height:15px;vertical-align:text-bottom}.qq-gallery .qq-delete-icon{background:url(trash.gif);width:15px;height:15px;vertical-align:sub;display:inline-block}.qq-gallery .qq-retry-icon{background:url(retry.gif);width:15px;height:15px;vertical-align:sub;display:inline-block;float:none}.qq-gallery .qq-continue-icon{background:url(continue.gif);width:15px;height:15px;vertical-align:sub;display:inline-block}.qq-gallery .qq-pause-icon{background:url(pause.gif);width:15px;height:15px;vertical-align:sub;display:inline-block}.qq-gallery .qq-hide{display:none}.qq-gallery .qq-in-progress .qq-thumbnail-wrapper{/* makes the spinner on top of the thumbnail more visible */opacity:.5}.qq-gallery .qq-thumbnail-wrapper{overflow:hidden;position:relative;height:120px;width:120px}.qq-gallery .qq-thumbnail-selector{border-radius:2px 2px 0 0;bottom:0;top:0;margin:auto;display:block}:root *>.qq-gallery .qq-thumbnail-selector{position:relative;top:50%;transform:translateY(-50%);-moz-transform:translateY(-50%);-ms-transform:translateY(-50%);-webkit-transform:translateY(-50%)}.qq-gallery.qq-uploader DIALOG{display:none}.qq-gallery.qq-uploader DIALOG[open]{display:block}.qq-gallery.qq-uploader DIALOG{display:none}.qq-gallery.qq-uploader DIALOG[open]{display:block}.qq-gallery.qq-uploader DIALOG .qq-dialog-buttons{text-align:center;padding-top:10px}.qq-gallery.qq-uploader DIALOG .qq-dialog-buttons BUTTON{margin-left:5px;margin-right:5px}.qq-gallery.qq-uploader DIALOG .qq-dialog-message-selector{padding-bottom:10px}.qq-gallery .qq-uploader DIALOG::backdrop{background-color:rgba(0,0,0,.7)}/*# sourceMappingURL=fine-uploader-gallery.min.css.map */ -------------------------------------------------------------------------------- /file-server/src/main/resources/static/fine-uploader/fine-uploader-gallery.min.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["_build/fine-uploader-gallery.css"],"names":[],"mappings":"AAOA,oBAEI,MAAO,MACP,OAAQ,KACR,QAAS,EACT,OAAQ,EACR,WAAY,KAKhB,8BACI,QAAS,OACT,MAAO,MACP,QAAS,IAAI,KACb,MAAO,KACP,WAAY,OACZ,WAAY,QACZ,MAAO,KACP,cAAe,IACf,OAAQ,IAAI,MAAM,QAClB,WAAY,EAAE,IAAI,IAAI,sBAA0B,KAAK,CACrD,IAAI,EAAE,IAAI,sBAA0B,KAAK,CACzC,EAAE,IAAI,EAAE,eAAmB,CAC3B,EAAE,KAAK,KAAK,gBAAoB,MAEpC,oCACI,WAAY,QAEhB,oCACI,QAAS,IAAI,OAAO,KAMxB,wBACI,SAAU,SACV,WAAY,MACZ,WAAY,MACZ,WAAY,OACZ,MAAO,QACP,cAAe,IACf,OAAQ,IAAI,OAAO,KACnB,iBAAkB,QAClB,QAAS,KAEb,+BACI,QAAS,wBAAwB,IACjC,SAAU,SACV,UAAW,KACX,KAAM,EACN,MAAO,KACP,WAAY,OACZ,IAAK,IACL,QAAS,IAGb,iCAAkC,2BAC9B,SAAU,SACV,IAAK,EACL,KAAM,EACN,MAAO,KACP,OAAQ,KACR,WAAY,KACZ,QAAS,EACT,WAAY,QACZ,cAAe,IACf,WAAY,OAEhB,sCACI,QAAS,MACT,SAAU,SACV,IAAK,IACL,MAAO,KACP,WAAY,KACZ,UAAW,KAEf,uCACI,SAAU,SACV,WAAY,KACZ,UAAW,KACX,YAAa,KACb,OAAQ,KACR,WAAY,KAEhB,wCACI,WAAY,QACZ,cAAe,IAEnB,4BACI,OAAQ,EACR,QAAS,KAAK,EAAE,EAChB,WAAY,KACZ,WAAY,MACZ,WAAY,KACZ,MAAO,KACP,WAAY,KAMhB,+BACI,QAAS,aACT,SAAU,SACV,UAAW,MACX,OAAQ,EAAE,KAAK,KAAK,EACpB,QAAS,EACT,YAAa,KACb,UAAW,KACX,MAAO,QACP,iBAAkB,KAClB,cAAe,IACf,WAAY,EAAE,IAAI,IAAI,EAAE,gBACxB,eAAgB,IAGhB,OAAQ,MASZ,gCAFA,8BADA,mCAEA,6BAHA,6BADA,4BADA,+BAOI,QAAS,OAKb,sCAFA,oCACA,mCAFA,mCAII,iBAAkB,YAKtB,8BADA,gCAFA,8BACA,6BAGI,OAAQ,QAIZ,gCAFA,8BACA,6BAEI,OAAO,KACP,WAAY,IACZ,MAAO,QACP,UAAW,KACX,QAAS,EAGb,mCACI,MAAO,KACP,UAAW,KACX,aAAc,IACd,YAAa,IACb,MAAO,QACP,QAAS,KACT,MAAO,MAEX,mDACI,cAAe,SACf,YAAa,OACb,WAAY,OACZ,QAAS,MAEb,uDACI,QAAS,aAEb,2DACI,QAAS,KAGb,8BACI,iBAAkB,QAClB,MAAO,QACP,YAAa,IACb,YAAa,KAAK,CAAE,SAAS,CAAE,WAC/B,cAAe,KACf,OAAQ,KACR,OAAQ,KACR,MAAO,KACP,QAAS,IACT,SAAU,SACV,MAAO,KACP,IAAK,KACL,OAAQ,EACR,YAAa,KAEjB,oCACI,iBAAkB,QAEtB,6BACI,OAAQ,QACR,SAAU,SACV,IAAK,KACL,KAAM,IACN,YAAa,MACb,WAAY,EAAE,IAAI,IAAI,sBAA0B,KAAK,CACzC,IAAI,EAAE,IAAI,sBAA0B,KAAK,CACzC,EAAE,IAAI,IAAI,cAAkB,CAC5B,EAAE,KAAK,KAAK,gBAAoB,MAC5C,QAAS,IAAI,IACb,OAAQ,IAAI,MAAM,QAClB,cAAe,IACf,MAAO,QACP,iBAAkB,QAClB,QAAS,EAEb,mCACI,iBAAkB,QAGtB,0BACI,QAAS,KAAK,IAAI,IAClB,WAAY,KACZ,cAAe,EAAE,EAAE,IAAI,IACvB,WAAY,KACZ,SAAU,OAGd,wCACI,SAAU,SAGd,4BACI,QAAS,MACT,aAAc,EACd,cAAe,IACf,MAAO,KAGP,cAAe,SACf,YAAa,OACb,WAAY,OAEhB,+BACI,QAAS,aACT,WAAY,iBACZ,SAAU,SACV,KAAM,IACN,YAAa,KACb,IAAK,KACL,MAAO,KACP,OAAQ,KACR,eAAgB,YAEpB,gCACI,QAAS,MAEb,wCACI,QAAS,aACT,WAAY,oBACZ,MAAO,KACP,OAAQ,KACR,eAAgB,YAEpB,mCACI,QAAS,KACT,WAAY,OACZ,YAAa,IAEjB,mCACI,QAAQ,KACR,MAAM,KACN,OAAO,KACP,eAAe,YAEnB,mDACI,QAAS,OAEb,uDACI,QAAS,OAEb,iDACI,iBAAkB,QAEtB,8CACI,iBAAkB,QAClB,WAAY,EAAE,EAAE,IAAI,EAAE,IACtB,OAAQ,EAEZ,6BACI,QAAS,MACT,WAAY,QACZ,MAAO,EACP,OAAQ,KACR,cAAe,IACf,cAAe,IAGnB,mCACI,OAAQ,KACR,cAAe,IAGnB,6CACI,YAAa,IACb,QAAS,OACT,MAAO,MACP,MAAO,MAGX,4BACI,MAAO,KACP,UAAW,KACX,MAAO,QACP,cAAe,IACf,aAAc,EACd,QAAS,aAGb,mCACI,SAAU,SACV,QAAS,EAET,QAAS,GAIb,wCACI,OAAQ,QACR,aAAc,KAGlB,+CACI,QAAS,aACT,OAAQ,QACR,SAAU,SACV,MAAO,EACP,IAAK,EAGT,8CACI,SAAU,OACV,OAAQ,KACR,MAAO,KACP,MAAO,eACP,QAAS,EAAE,IACX,cAAe,IACf,OAAQ,IAAI,MAAM,KAClB,cAAe,IACf,UAAW,KAEX,QAAS,EAKb,mCACI,QAAS,KACT,WAAY,cACZ,MAAO,KACP,OAAQ,KACR,eAAgB,YAEpB,4BACI,WAAY,eACZ,MAAO,KACP,OAAQ,KACR,eAAgB,IAChB,QAAS,aAEb,2BACI,WAAY,eACZ,MAAO,KACP,OAAQ,KACR,eAAgB,IAChB,QAAS,aACT,MAAO,KAEX,8BACI,WAAY,kBACZ,MAAO,KACP,OAAQ,KACR,eAAgB,IAChB,QAAS,aAEb,2BACI,WAAY,eACZ,MAAO,KACP,OAAQ,KACR,eAAgB,IAChB,QAAS,aAGb,qBACI,QAAS,KAMb,kDACI,4DACA,QAAS,GAGb,kCACI,SAAU,OACV,SAAU,SAGV,OAAQ,MACR,MAAO,MAEX,mCACI,cAAe,IAAI,IAAI,EAAE,EACzB,OAAQ,EAGR,IAAK,EAGL,OAAO,KACP,QAAS,MAIb,2CAEI,SAAU,SACV,IAAK,IACL,UAAW,iBACX,eAAgB,iBAChB,cAAe,iBACf,kBAAmB,iBAIvB,+BACI,QAAS,KAGb,qCACI,QAAS,MAGb,+BACI,QAAS,KAGb,qCACI,QAAS,MAGb,kDACI,WAAY,OACZ,YAAa,KAGjB,yDACI,YAAa,IACb,aAAc,IAGlB,2DACI,eAAgB,KAGpB,0CACI,iBAAkB"} -------------------------------------------------------------------------------- /file-server/src/main/resources/static/fine-uploader/fine-uploader-new.css: -------------------------------------------------------------------------------- 1 | /* --------------------------------------- 2 | /* Fine Uploader Styles 3 | /* --------------------------------------- 4 | 5 | /* Buttons 6 | ------------------------------------------ */ 7 | .qq-btn 8 | { 9 | box-shadow: 0 1px 1px rgba(255, 255, 255, 0.37) inset, 10 | 1px 0 1px rgba(255, 255, 255, 0.07) inset, 11 | 0 1px 0 rgba(0, 0, 0, 0.36), 12 | 0 -2px 12px rgba(0, 0, 0, 0.08) inset; 13 | padding: 3px 4px; 14 | border: 1px solid #CCCCCC; 15 | border-radius: 2px; 16 | color: inherit; 17 | background-color: #FFFFFF; 18 | } 19 | .qq-upload-delete, .qq-upload-pause, .qq-upload-continue { 20 | display: inline; 21 | } 22 | .qq-upload-delete 23 | { 24 | background-color: #e65c47; 25 | color: #FAFAFA; 26 | border-color: #dc523d; 27 | text-shadow: 0 1px 1px rgba(0, 0, 0, 0.55); 28 | } 29 | .qq-upload-delete:hover { 30 | background-color: #f56b56; 31 | } 32 | .qq-upload-cancel 33 | { 34 | background-color: #F5D7D7; 35 | border-color: #e6c8c8; 36 | } 37 | .qq-upload-cancel:hover { 38 | background-color: #ffe1e1; 39 | } 40 | .qq-upload-retry 41 | { 42 | background-color: #EBF6E0; 43 | border-color: #d2ddc7; 44 | } 45 | .qq-upload-retry:hover { 46 | background-color: #f7ffec; 47 | } 48 | .qq-upload-pause, .qq-upload-continue { 49 | background-color: #00ABC7; 50 | color: #FAFAFA; 51 | border-color: #2dadc2; 52 | text-shadow: 0 1px 1px rgba(0, 0, 0, 0.55); 53 | } 54 | .qq-upload-pause:hover, .qq-upload-continue:hover { 55 | background-color: #0fbad6; 56 | } 57 | 58 | /* Upload Button 59 | ------------------------------------------ */ 60 | .qq-upload-button { 61 | display: inline; 62 | width: 105px; 63 | margin-bottom: 10px; 64 | padding: 7px 10px; 65 | text-align: center; 66 | float: left; 67 | background: #00ABC7; 68 | color: #FFFFFF; 69 | border-radius: 2px; 70 | border: 1px solid #2dadc2; 71 | box-shadow: 0 1px 1px rgba(255, 255, 255, 0.37) inset, 72 | 1px 0 1px rgba(255, 255, 255, 0.07) inset, 73 | 0 1px 0 rgba(0, 0, 0, 0.36), 74 | 0 -2px 12px rgba(0, 0, 0, 0.08) inset; 75 | } 76 | .qq-upload-button-hover { 77 | background: #33B6CC; 78 | } 79 | .qq-upload-button-focus { 80 | outline: 1px dotted #000000; 81 | } 82 | 83 | 84 | /* Drop Zone 85 | ------------------------------------------ */ 86 | .qq-uploader { 87 | position: relative; 88 | min-height: 200px; 89 | max-height: 490px; 90 | overflow-y: hidden; 91 | width: inherit; 92 | border-radius: 6px; 93 | background-color: #FDFDFD; 94 | border: 1px dashed #CCCCCC; 95 | padding: 20px; 96 | } 97 | .qq-uploader:before { 98 | content: attr(qq-drop-area-text) " "; 99 | position: absolute; 100 | font-size: 200%; 101 | left: 0; 102 | width: 100%; 103 | text-align: center; 104 | top: 45%; 105 | opacity: 0.25; 106 | } 107 | .qq-upload-drop-area, .qq-upload-extra-drop-area { 108 | position: absolute; 109 | top: 0; 110 | left: 0; 111 | width: 100%; 112 | height: 100%; 113 | min-height: 30px; 114 | z-index: 2; 115 | background: #F9F9F9; 116 | border-radius: 4px; 117 | border: 1px dashed #CCCCCC; 118 | text-align: center; 119 | } 120 | .qq-upload-drop-area span { 121 | display: block; 122 | position: absolute; 123 | top: 50%; 124 | width: 100%; 125 | margin-top: -8px; 126 | font-size: 16px; 127 | } 128 | .qq-upload-extra-drop-area { 129 | position: relative; 130 | margin-top: 50px; 131 | font-size: 16px; 132 | padding-top: 30px; 133 | height: 20px; 134 | min-height: 40px; 135 | } 136 | .qq-upload-drop-area-active { 137 | background: #FDFDFD; 138 | border-radius: 4px; 139 | border: 1px dashed #CCCCCC; 140 | } 141 | .qq-upload-list { 142 | margin: 0; 143 | padding: 0; 144 | list-style: none; 145 | max-height: 450px; 146 | overflow-y: auto; 147 | box-shadow: 0px 1px 0px rgba(15, 15, 50, 0.14); 148 | clear: both; 149 | } 150 | 151 | 152 | /* Uploaded Elements 153 | ------------------------------------------ */ 154 | .qq-upload-list li { 155 | margin: 0; 156 | padding: 9px; 157 | line-height: 15px; 158 | font-size: 16px; 159 | color: #424242; 160 | background-color: #F6F6F6; 161 | border-top: 1px solid #FFFFFF; 162 | border-bottom: 1px solid #DDDDDD; 163 | } 164 | .qq-upload-list li:first-child { 165 | border-top: none; 166 | } 167 | .qq-upload-list li:last-child { 168 | border-bottom: none; 169 | } 170 | 171 | .qq-upload-file, .qq-upload-spinner, .qq-upload-size, 172 | .qq-upload-cancel, .qq-upload-retry, .qq-upload-failed-text, 173 | .qq-upload-delete, .qq-upload-pause, .qq-upload-continue { 174 | margin-right: 12px; 175 | display: inline; 176 | } 177 | .qq-upload-file { 178 | vertical-align: middle; 179 | display: inline-block; 180 | width: 300px; 181 | text-overflow: ellipsis; 182 | white-space: nowrap; 183 | overflow-x: hidden; 184 | height: 18px; 185 | } 186 | .qq-upload-spinner { 187 | display: inline-block; 188 | background: url("loading.gif"); 189 | width: 15px; 190 | height: 15px; 191 | vertical-align: text-bottom; 192 | } 193 | .qq-drop-processing { 194 | display: block; 195 | } 196 | .qq-drop-processing-spinner { 197 | display: inline-block; 198 | background: url("processing.gif"); 199 | width: 24px; 200 | height: 24px; 201 | vertical-align: text-bottom; 202 | } 203 | .qq-upload-size, .qq-upload-cancel, .qq-upload-retry, 204 | .qq-upload-delete, .qq-upload-pause, .qq-upload-continue { 205 | font-size: 12px; 206 | font-weight: normal; 207 | cursor: pointer; 208 | vertical-align: middle; 209 | } 210 | .qq-upload-status-text { 211 | font-size: 14px; 212 | font-weight: bold; 213 | display: block; 214 | } 215 | .qq-upload-failed-text { 216 | display: none; 217 | font-style: italic; 218 | font-weight: bold; 219 | } 220 | .qq-upload-failed-icon { 221 | display:none; 222 | width:15px; 223 | height:15px; 224 | vertical-align:text-bottom; 225 | } 226 | .qq-upload-fail .qq-upload-failed-text { 227 | display: inline; 228 | } 229 | .qq-upload-retrying .qq-upload-failed-text { 230 | display: inline; 231 | } 232 | .qq-upload-list li.qq-upload-success { 233 | background-color: #EBF6E0; 234 | color: #424242; 235 | border-bottom: 1px solid #D3DED1; 236 | border-top: 1px solid #F7FFF5; 237 | } 238 | .qq-upload-list li.qq-upload-fail { 239 | background-color: #F5D7D7; 240 | color: #424242; 241 | border-bottom: 1px solid #DECACA; 242 | border-top: 1px solid #FCE6E6; 243 | } 244 | .qq-progress-bar { 245 | display: block; 246 | display: block; 247 | background: #00abc7; 248 | width: 0%; 249 | height: 15px; 250 | border-radius: 6px; 251 | margin-bottom: 3px; 252 | } 253 | 254 | .qq-total-progress-bar { 255 | height: 25px; 256 | border-radius: 9px; 257 | } 258 | 259 | .qq-total-progress-bar-container { 260 | margin-left: 9px; 261 | display: inline; 262 | float: right; 263 | width: 500px; 264 | } 265 | 266 | INPUT.qq-edit-filename { 267 | position: absolute; 268 | opacity: 0; 269 | filter: alpha(opacity=0); 270 | z-index: -1; 271 | -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; 272 | } 273 | 274 | .qq-upload-file.qq-editable { 275 | cursor: pointer; 276 | margin-right: 4px; 277 | } 278 | 279 | .qq-edit-filename-icon.qq-editable { 280 | display: inline-block; 281 | cursor: pointer; 282 | } 283 | 284 | INPUT.qq-edit-filename.qq-editing { 285 | position: static; 286 | height: 28px; 287 | padding: 0 8px; 288 | margin-right: 10px; 289 | margin-bottom: -5px; 290 | border: 1px solid #ccc; 291 | border-radius: 2px; 292 | font-size: 16px; 293 | 294 | opacity: 1; 295 | filter: alpha(opacity=100); 296 | -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)"; 297 | } 298 | 299 | .qq-edit-filename-icon { 300 | display: none; 301 | background: url("edit.gif"); 302 | width: 15px; 303 | height: 15px; 304 | vertical-align: text-bottom; 305 | margin-right: 16px; 306 | } 307 | 308 | .qq-hide { 309 | display: none; 310 | } 311 | 312 | 313 | /* Thumbnail 314 | ------------------------------------------ */ 315 | .qq-thumbnail-selector { 316 | vertical-align: middle; 317 | margin-right: 12px; 318 | } 319 | 320 | 321 | /* element styles */ 322 | .qq-uploader DIALOG { 323 | display: none; 324 | } 325 | 326 | .qq-uploader DIALOG[open] { 327 | display: block; 328 | } 329 | 330 | .qq-uploader DIALOG { 331 | display: none; 332 | } 333 | 334 | .qq-uploader DIALOG[open] { 335 | display: block; 336 | } 337 | 338 | .qq-uploader DIALOG .qq-dialog-buttons { 339 | text-align: center; 340 | padding-top: 10px; 341 | } 342 | 343 | .qq-uploader DIALOG .qq-dialog-buttons BUTTON { 344 | margin-left: 5px; 345 | margin-right: 5px; 346 | } 347 | 348 | .qq-uploader DIALOG .qq-dialog-message-selector { 349 | padding-bottom: 10px; 350 | } 351 | 352 | .qq-uploader DIALOG::backdrop { 353 | background-color: rgba(0, 0, 0, 0.7); 354 | } -------------------------------------------------------------------------------- /file-server/src/main/resources/static/fine-uploader/fine-uploader-new.min.css: -------------------------------------------------------------------------------- 1 | .qq-btn{box-shadow:0 1px 1px rgba(255,255,255,.37) inset,1px 0 1px rgba(255,255,255,.07) inset,0 1px 0 rgba(0,0,0,.36),0 -2px 12px rgba(0,0,0,.08) inset;padding:3px 4px;border:1px solid #ccc;border-radius:2px;color:inherit;background-color:#fff}.qq-upload-continue,.qq-upload-delete,.qq-upload-pause{display:inline}.qq-upload-delete{background-color:#e65c47;color:#fafafa;border-color:#dc523d;text-shadow:0 1px 1px rgba(0,0,0,.55)}.qq-upload-delete:hover{background-color:#f56b56}.qq-upload-cancel{background-color:#f5d7d7;border-color:#e6c8c8}.qq-upload-cancel:hover{background-color:#ffe1e1}.qq-upload-retry{background-color:#ebf6e0;border-color:#d2ddc7}.qq-upload-retry:hover{background-color:#f7ffec}.qq-upload-continue,.qq-upload-pause{background-color:#00abc7;color:#fafafa;border-color:#2dadc2;text-shadow:0 1px 1px rgba(0,0,0,.55)}.qq-upload-continue:hover,.qq-upload-pause:hover{background-color:#0fbad6}.qq-upload-button{display:inline;width:105px;margin-bottom:10px;padding:7px 10px;text-align:center;float:left;background:#00abc7;color:#fff;border-radius:2px;border:1px solid #2dadc2;box-shadow:0 1px 1px rgba(255,255,255,.37) inset,1px 0 1px rgba(255,255,255,.07) inset,0 1px 0 rgba(0,0,0,.36),0 -2px 12px rgba(0,0,0,.08) inset}.qq-upload-button-hover{background:#33b6cc}.qq-upload-button-focus{outline:1px dotted #000}.qq-uploader{position:relative;min-height:200px;max-height:490px;overflow-y:hidden;width:inherit;border-radius:6px;background-color:#fdfdfd;border:1px dashed #ccc;padding:20px}.qq-uploader:before{content:attr(qq-drop-area-text) " ";position:absolute;font-size:200%;left:0;width:100%;text-align:center;top:45%;opacity:.25}.qq-upload-drop-area,.qq-upload-extra-drop-area{position:absolute;top:0;left:0;width:100%;height:100%;min-height:30px;z-index:2;background:#f9f9f9;border-radius:4px;border:1px dashed #ccc;text-align:center}.qq-upload-drop-area span{display:block;position:absolute;top:50%;width:100%;margin-top:-8px;font-size:16px}.qq-upload-extra-drop-area{position:relative;margin-top:50px;font-size:16px;padding-top:30px;height:20px;min-height:40px}.qq-upload-drop-area-active{background:#fdfdfd;border-radius:4px;border:1px dashed #ccc}.qq-upload-list{margin:0;padding:0;list-style:none;max-height:450px;overflow-y:auto;box-shadow:0 1px 0 rgba(15,15,50,.14);clear:both}.qq-upload-list li{margin:0;padding:9px;line-height:15px;font-size:16px;color:#424242;background-color:#f6f6f6;border-top:1px solid #fff;border-bottom:1px solid #ddd}.qq-upload-list li:first-child{border-top:none}.qq-upload-list li:last-child{border-bottom:none}.qq-upload-cancel,.qq-upload-continue,.qq-upload-delete,.qq-upload-failed-text,.qq-upload-file,.qq-upload-pause,.qq-upload-retry,.qq-upload-size,.qq-upload-spinner{margin-right:12px;display:inline}.qq-upload-file{vertical-align:middle;display:inline-block;width:300px;text-overflow:ellipsis;white-space:nowrap;overflow-x:hidden;height:18px}.qq-upload-spinner{display:inline-block;background:url(loading.gif);width:15px;height:15px;vertical-align:text-bottom}.qq-drop-processing{display:block}.qq-drop-processing-spinner{display:inline-block;background:url(processing.gif);width:24px;height:24px;vertical-align:text-bottom}.qq-upload-cancel,.qq-upload-continue,.qq-upload-delete,.qq-upload-pause,.qq-upload-retry,.qq-upload-size{font-size:12px;font-weight:400;cursor:pointer;vertical-align:middle}.qq-upload-status-text{font-size:14px;font-weight:700;display:block}.qq-upload-failed-text{display:none;font-style:italic;font-weight:700}.qq-upload-failed-icon{display:none;width:15px;height:15px;vertical-align:text-bottom}.qq-upload-fail .qq-upload-failed-text{display:inline}.qq-upload-retrying .qq-upload-failed-text{display:inline}.qq-upload-list li.qq-upload-success{background-color:#ebf6e0;color:#424242;border-bottom:1px solid #d3ded1;border-top:1px solid #f7fff5}.qq-upload-list li.qq-upload-fail{background-color:#f5d7d7;color:#424242;border-bottom:1px solid #decaca;border-top:1px solid #fce6e6}.qq-progress-bar{display:block;display:block;background:#00abc7;width:0;height:15px;border-radius:6px;margin-bottom:3px}.qq-total-progress-bar{height:25px;border-radius:9px}.qq-total-progress-bar-container{margin-left:9px;display:inline;float:right;width:500px}INPUT.qq-edit-filename{position:absolute;opacity:0;z-index:-1}.qq-upload-file.qq-editable{cursor:pointer;margin-right:4px}.qq-edit-filename-icon.qq-editable{display:inline-block;cursor:pointer}INPUT.qq-edit-filename.qq-editing{position:static;height:28px;padding:0 8px;margin-right:10px;margin-bottom:-5px;border:1px solid #ccc;border-radius:2px;font-size:16px;opacity:1}.qq-edit-filename-icon{display:none;background:url(edit.gif);width:15px;height:15px;vertical-align:text-bottom;margin-right:16px}.qq-hide{display:none}.qq-thumbnail-selector{vertical-align:middle;margin-right:12px}.qq-uploader DIALOG{display:none}.qq-uploader DIALOG[open]{display:block}.qq-uploader DIALOG{display:none}.qq-uploader DIALOG[open]{display:block}.qq-uploader DIALOG .qq-dialog-buttons{text-align:center;padding-top:10px}.qq-uploader DIALOG .qq-dialog-buttons BUTTON{margin-left:5px;margin-right:5px}.qq-uploader DIALOG .qq-dialog-message-selector{padding-bottom:10px}.qq-uploader DIALOG::backdrop{background-color:rgba(0,0,0,.7)}/*# sourceMappingURL=fine-uploader-new.min.css.map */ -------------------------------------------------------------------------------- /file-server/src/main/resources/static/fine-uploader/fine-uploader-new.min.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["_build/fine-uploader-new.css"],"names":[],"mappings":"AAMA,QAEI,WAAY,EAAE,IAAI,IAAI,sBAA0B,KAAK,CACzC,IAAI,EAAE,IAAI,sBAA0B,KAAK,CACzC,EAAE,IAAI,EAAE,eAAmB,CAC3B,EAAE,KAAK,KAAK,gBAAoB,MAC5C,QAAS,IAAI,IACb,OAAQ,IAAI,MAAM,KAClB,cAAe,IACf,MAAO,QACP,iBAAkB,KAEe,oBAArC,kBAAmB,iBACf,QAAS,OAEb,kBAEI,iBAAkB,QAClB,MAAO,QACP,aAAc,QACd,YAAa,EAAE,IAAI,IAAI,gBAE3B,wBACI,iBAAkB,QAEtB,kBAEI,iBAAkB,QAClB,aAAc,QAElB,wBACI,iBAAkB,QAEtB,iBAEI,iBAAkB,QAClB,aAAc,QAElB,uBACI,iBAAkB,QAEJ,oBAAlB,iBACI,iBAAkB,QAClB,MAAO,QACP,aAAc,QACd,YAAa,EAAE,IAAI,IAAI,gBAEH,0BAAxB,uBACI,iBAAkB,QAKtB,kBACI,QAAS,OACT,MAAO,MACP,cAAe,KACf,QAAS,IAAI,KACb,WAAY,OACZ,MAAO,KACP,WAAY,QACZ,MAAO,KACP,cAAe,IACf,OAAQ,IAAI,MAAM,QAClB,WAAY,EAAE,IAAI,IAAI,sBAA0B,KAAK,CACzC,IAAI,EAAE,IAAI,sBAA0B,KAAK,CACzC,EAAE,IAAI,EAAE,eAAmB,CAC3B,EAAE,KAAK,KAAK,gBAAoB,MAEhD,wBACI,WAAY,QAEhB,wBACI,QAAS,IAAI,OAAO,KAMxB,aACI,SAAU,SACV,WAAY,MACZ,WAAY,MACZ,WAAY,OACZ,MAAO,QACP,cAAe,IACf,iBAAkB,QAClB,OAAQ,IAAI,OAAO,KACnB,QAAS,KAEb,oBACI,QAAS,wBAAwB,IACjC,SAAU,SACV,UAAW,KACX,KAAM,EACN,MAAO,KACP,WAAY,OACZ,IAAK,IACL,QAAS,IAEb,qBAAsB,2BAClB,SAAU,SACV,IAAK,EACL,KAAM,EACN,MAAO,KACP,OAAQ,KACR,WAAY,KACZ,QAAS,EACT,WAAY,QACZ,cAAe,IACf,OAAQ,IAAI,OAAO,KACnB,WAAY,OAEhB,0BACI,QAAS,MACT,SAAU,SACV,IAAK,IACL,MAAO,KACP,WAAY,KACZ,UAAW,KAEf,2BACI,SAAU,SACV,WAAY,KACZ,UAAW,KACX,YAAa,KACb,OAAQ,KACR,WAAY,KAEhB,4BACI,WAAY,QACZ,cAAe,IACf,OAAQ,IAAI,OAAO,KAEvB,gBACI,OAAQ,EACR,QAAS,EACT,WAAY,KACZ,WAAY,MACZ,WAAY,KACZ,WAAY,EAAI,IAAI,EAAI,mBACxB,MAAO,KAMX,mBACI,OAAQ,EACR,QAAS,IACT,YAAa,KACb,UAAW,KACX,MAAO,QACP,iBAAkB,QAClB,WAAY,IAAI,MAAM,KACtB,cAAe,IAAI,MAAM,KAE7B,+BACI,WAAY,KAEhB,8BACI,cAAe,KAInB,kBACqC,oBAArC,kBADqC,uBADrC,gBAEmB,iBADA,iBADkB,gBAApB,mBAGb,aAAc,KACd,QAAS,OAEb,gBACI,eAAgB,OAChB,QAAS,aACT,MAAO,MACP,cAAe,SACf,YAAa,OACb,WAAY,OACZ,OAAQ,KAEZ,mBACI,QAAS,aACT,WAAY,iBACZ,MAAO,KACP,OAAQ,KACR,eAAgB,YAEpB,oBACI,QAAS,MAEb,4BACI,QAAS,aACT,WAAY,oBACZ,MAAO,KACP,OAAQ,KACR,eAAgB,YAEH,kBACoB,oBAArC,kBAAmB,iBADiB,iBAApC,gBAEI,UAAW,KACX,YAAa,IACb,OAAQ,QACR,eAAgB,OAEpB,uBACI,UAAW,KACX,YAAa,IACb,QAAS,MAEb,uBACI,QAAS,KACT,WAAY,OACZ,YAAa,IAEjB,uBACI,QAAQ,KACR,MAAM,KACN,OAAO,KACP,eAAe,YAEnB,uCACI,QAAS,OAEb,2CACI,QAAS,OAEb,qCACI,iBAAkB,QAClB,MAAO,QACP,cAAe,IAAI,MAAM,QACzB,WAAY,IAAI,MAAM,QAE1B,kCACI,iBAAkB,QAClB,MAAO,QACP,cAAe,IAAI,MAAM,QACzB,WAAY,IAAI,MAAM,QAE1B,iBACI,QAAS,MACT,QAAS,MACT,WAAY,QACZ,MAAO,EACP,OAAQ,KACR,cAAe,IACf,cAAe,IAGnB,uBACI,OAAQ,KACR,cAAe,IAGnB,iCACI,YAAa,IACb,QAAS,OACT,MAAO,MACP,MAAO,MAGX,uBACI,SAAU,SACV,QAAS,EAET,QAAS,GAIb,4BACI,OAAQ,QACR,aAAc,IAGlB,mCACI,QAAS,aACT,OAAQ,QAGZ,kCACI,SAAU,OACV,OAAQ,KACR,QAAS,EAAE,IACX,aAAc,KACd,cAAe,KACf,OAAQ,IAAI,MAAM,KAClB,cAAe,IACf,UAAW,KAEX,QAAS,EAKb,uBACI,QAAS,KACT,WAAY,cACZ,MAAO,KACP,OAAQ,KACR,eAAgB,YAChB,aAAc,KAGlB,SACI,QAAS,KAMb,uBACI,eAAgB,OAChB,aAAc,KAKlB,oBACI,QAAS,KAGb,0BACI,QAAS,MAGb,oBACI,QAAS,KAGb,0BACI,QAAS,MAGb,uCACI,WAAY,OACZ,YAAa,KAGjB,8CACI,YAAa,IACb,aAAc,IAGlB,gDACI,eAAgB,KAGpB,8BACI,iBAAkB"} -------------------------------------------------------------------------------- /file-server/src/main/resources/static/fine-uploader/fine-uploader.css: -------------------------------------------------------------------------------- 1 | .qq-uploader { 2 | position: relative; 3 | width: 100%; 4 | } 5 | .qq-upload-button { 6 | display: block; 7 | width: 105px; 8 | padding: 7px 0; 9 | text-align: center; 10 | background: #880000; 11 | border-bottom: 1px solid #DDD; 12 | color: #FFF; 13 | } 14 | .qq-upload-button-hover { 15 | background: #CC0000; 16 | } 17 | .qq-upload-button-focus { 18 | outline: 1px dotted #000000; 19 | } 20 | .qq-upload-drop-area, .qq-upload-extra-drop-area { 21 | position: absolute; 22 | top: 0; 23 | left: 0; 24 | width: 100%; 25 | height: 100%; 26 | min-height: 30px; 27 | z-index: 2; 28 | background: #FF9797; 29 | text-align: center; 30 | } 31 | .qq-upload-drop-area span { 32 | display: block; 33 | position: absolute; 34 | top: 50%; 35 | width: 100%; 36 | margin-top: -8px; 37 | font-size: 16px; 38 | } 39 | .qq-upload-extra-drop-area { 40 | position: relative; 41 | margin-top: 50px; 42 | font-size: 16px; 43 | padding-top: 30px; 44 | height: 20px; 45 | min-height: 40px; 46 | } 47 | .qq-upload-drop-area-active { 48 | background: #FF7171; 49 | } 50 | .qq-upload-list { 51 | margin: 0; 52 | padding: 0; 53 | list-style: none; 54 | } 55 | .qq-upload-list li { 56 | margin: 0; 57 | padding: 9px; 58 | line-height: 15px; 59 | font-size: 16px; 60 | background-color: #FFF0BD; 61 | } 62 | .qq-upload-file, .qq-upload-spinner, .qq-upload-size, 63 | .qq-upload-cancel, .qq-upload-retry, .qq-upload-failed-text, 64 | .qq-upload-delete, .qq-upload-pause, .qq-upload-continue { 65 | margin-right: 12px; 66 | display: inline; 67 | } 68 | .qq-upload-file { 69 | } 70 | .qq-upload-spinner { 71 | display: inline-block; 72 | background: url("loading.gif"); 73 | width: 15px; 74 | height: 15px; 75 | vertical-align: text-bottom; 76 | } 77 | .qq-drop-processing { 78 | display: block; 79 | } 80 | .qq-drop-processing-spinner { 81 | display: inline-block; 82 | background: url("processing.gif"); 83 | width: 24px; 84 | height: 24px; 85 | vertical-align: text-bottom; 86 | } 87 | 88 | .qq-upload-delete, .qq-upload-pause, .qq-upload-continue { 89 | display: inline; 90 | } 91 | 92 | .qq-upload-retry, .qq-upload-delete, .qq-upload-cancel, 93 | .qq-upload-pause, .qq-upload-continue { 94 | color: #000000; 95 | } 96 | 97 | .qq-upload-size, .qq-upload-cancel, .qq-upload-retry, 98 | .qq-upload-delete, .qq-upload-pause, .qq-upload-continue { 99 | font-size: 12px; 100 | font-weight: normal; 101 | } 102 | .qq-upload-failed-text { 103 | display: none; 104 | font-style: italic; 105 | font-weight: bold; 106 | } 107 | .qq-upload-failed-icon { 108 | display:none; 109 | width:15px; 110 | height:15px; 111 | vertical-align:text-bottom; 112 | } 113 | .qq-upload-fail .qq-upload-failed-text { 114 | display: inline; 115 | } 116 | .qq-upload-retrying .qq-upload-failed-text { 117 | display: inline; 118 | color: #D60000; 119 | } 120 | .qq-upload-list li.qq-upload-success { 121 | background-color: #5DA30C; 122 | color: #FFFFFF; 123 | } 124 | .qq-upload-list li.qq-upload-fail { 125 | background-color: #D60000; 126 | color: #FFFFFF; 127 | } 128 | .qq-progress-bar { 129 | display: block; 130 | background: -moz-linear-gradient(top, rgba(30,87,153,1) 0%, rgba(41,137,216,1) 50%, rgba(32,124,202,1) 51%, rgba(125,185,232,1) 100%); /* FF3.6+ */ 131 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(30,87,153,1)), color-stop(50%,rgba(41,137,216,1)), color-stop(51%,rgba(32,124,202,1)), color-stop(100%,rgba(125,185,232,1))); /* Chrome,Safari4+ */ 132 | background: -webkit-linear-gradient(top, rgba(30,87,153,1) 0%,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%); /* Chrome10+,Safari5.1+ */ 133 | background: -o-linear-gradient(top, rgba(30,87,153,1) 0%,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%); /* Opera 11.10+ */ 134 | background: -ms-linear-gradient(top, rgba(30,87,153,1) 0%,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%); /* IE10+ */ 135 | background: linear-gradient(to bottom, rgba(30,87,153,1) 0%,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%); /* W3C */ 136 | width: 0%; 137 | height: 15px; 138 | border-radius: 6px; 139 | margin-bottom: 3px; 140 | } 141 | 142 | .qq-total-progress-bar { 143 | height: 25px; 144 | border-radius: 9px; 145 | } 146 | 147 | .qq-total-progress-bar-container { 148 | margin: 9px; 149 | } 150 | 151 | INPUT.qq-edit-filename { 152 | position: absolute; 153 | opacity: 0; 154 | filter: alpha(opacity=0); 155 | z-index: -1; 156 | -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; 157 | } 158 | 159 | .qq-upload-file.qq-editable { 160 | cursor: pointer; 161 | } 162 | 163 | .qq-edit-filename-icon.qq-editable { 164 | display: inline-block; 165 | cursor: pointer; 166 | } 167 | 168 | INPUT.qq-edit-filename.qq-editing { 169 | position: static; 170 | margin-top: -5px; 171 | margin-right: 10px; 172 | margin-bottom: -5px; 173 | 174 | opacity: 1; 175 | filter: alpha(opacity=100); 176 | -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)"; 177 | } 178 | 179 | .qq-edit-filename-icon { 180 | display: none; 181 | background: url("edit.gif"); 182 | width: 15px; 183 | height: 15px; 184 | vertical-align: text-bottom; 185 | margin-right: 5px; 186 | } 187 | 188 | .qq-hide { 189 | display: none; 190 | } 191 | 192 | /* element styles */ 193 | .qq-uploader DIALOG { 194 | display: none; 195 | } 196 | 197 | .qq-uploader DIALOG[open] { 198 | display: block; 199 | } 200 | 201 | .qq-uploader DIALOG { 202 | display: none; 203 | } 204 | 205 | .qq-uploader DIALOG[open] { 206 | display: block; 207 | } 208 | 209 | .qq-uploader DIALOG .qq-dialog-buttons { 210 | text-align: center; 211 | padding-top: 10px; 212 | } 213 | 214 | .qq-uploader DIALOG .qq-dialog-buttons BUTTON { 215 | margin-left: 5px; 216 | margin-right: 5px; 217 | } 218 | 219 | .qq-uploader DIALOG .qq-dialog-message-selector { 220 | padding-bottom: 10px; 221 | } 222 | 223 | .qq-uploader DIALOG::backdrop { 224 | background-color: rgba(0, 0, 0, 0.7); 225 | } -------------------------------------------------------------------------------- /file-server/src/main/resources/static/fine-uploader/fine-uploader.min.css: -------------------------------------------------------------------------------- 1 | .qq-uploader{position:relative;width:100%}.qq-upload-button{display:block;width:105px;padding:7px 0;text-align:center;background:#800;border-bottom:1px solid #ddd;color:#fff}.qq-upload-button-hover{background:#c00}.qq-upload-button-focus{outline:1px dotted #000}.qq-upload-drop-area,.qq-upload-extra-drop-area{position:absolute;top:0;left:0;width:100%;height:100%;min-height:30px;z-index:2;background:#ff9797;text-align:center}.qq-upload-drop-area span{display:block;position:absolute;top:50%;width:100%;margin-top:-8px;font-size:16px}.qq-upload-extra-drop-area{position:relative;margin-top:50px;font-size:16px;padding-top:30px;height:20px;min-height:40px}.qq-upload-drop-area-active{background:#ff7171}.qq-upload-list{margin:0;padding:0;list-style:none}.qq-upload-list li{margin:0;padding:9px;line-height:15px;font-size:16px;background-color:#fff0bd}.qq-upload-cancel,.qq-upload-continue,.qq-upload-delete,.qq-upload-failed-text,.qq-upload-file,.qq-upload-pause,.qq-upload-retry,.qq-upload-size,.qq-upload-spinner{margin-right:12px;display:inline}.qq-upload-spinner{display:inline-block;background:url(loading.gif);width:15px;height:15px;vertical-align:text-bottom}.qq-drop-processing{display:block}.qq-drop-processing-spinner{display:inline-block;background:url(processing.gif);width:24px;height:24px;vertical-align:text-bottom}.qq-upload-continue,.qq-upload-delete,.qq-upload-pause{display:inline}.qq-upload-cancel,.qq-upload-continue,.qq-upload-delete,.qq-upload-pause,.qq-upload-retry{color:#000}.qq-upload-cancel,.qq-upload-continue,.qq-upload-delete,.qq-upload-pause,.qq-upload-retry,.qq-upload-size{font-size:12px;font-weight:400}.qq-upload-failed-text{display:none;font-style:italic;font-weight:700}.qq-upload-failed-icon{display:none;width:15px;height:15px;vertical-align:text-bottom}.qq-upload-fail .qq-upload-failed-text{display:inline}.qq-upload-retrying .qq-upload-failed-text{display:inline;color:#d60000}.qq-upload-list li.qq-upload-success{background-color:#5da30c;color:#fff}.qq-upload-list li.qq-upload-fail{background-color:#d60000;color:#fff}.qq-progress-bar{display:block;background:-moz-linear-gradient(top,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,rgba(30,87,153,1)),color-stop(50%,rgba(41,137,216,1)),color-stop(51%,rgba(32,124,202,1)),color-stop(100%,rgba(125,185,232,1)));background:-webkit-linear-gradient(top,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);background:-o-linear-gradient(top,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);background:-ms-linear-gradient(top,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);background:linear-gradient(to bottom,rgba(30,87,153,1) 0,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%);width:0;height:15px;border-radius:6px;margin-bottom:3px}.qq-total-progress-bar{height:25px;border-radius:9px}.qq-total-progress-bar-container{margin:9px}INPUT.qq-edit-filename{position:absolute;opacity:0;z-index:-1}.qq-upload-file.qq-editable{cursor:pointer}.qq-edit-filename-icon.qq-editable{display:inline-block;cursor:pointer}INPUT.qq-edit-filename.qq-editing{position:static;margin-top:-5px;margin-right:10px;margin-bottom:-5px;opacity:1}.qq-edit-filename-icon{display:none;background:url(edit.gif);width:15px;height:15px;vertical-align:text-bottom;margin-right:5px}.qq-hide{display:none}.qq-uploader DIALOG{display:none}.qq-uploader DIALOG[open]{display:block}.qq-uploader DIALOG{display:none}.qq-uploader DIALOG[open]{display:block}.qq-uploader DIALOG .qq-dialog-buttons{text-align:center;padding-top:10px}.qq-uploader DIALOG .qq-dialog-buttons BUTTON{margin-left:5px;margin-right:5px}.qq-uploader DIALOG .qq-dialog-message-selector{padding-bottom:10px}.qq-uploader DIALOG::backdrop{background-color:rgba(0,0,0,.7)}/*# sourceMappingURL=fine-uploader.min.css.map */ -------------------------------------------------------------------------------- /file-server/src/main/resources/static/fine-uploader/fine-uploader.min.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["_build/fine-uploader.css"],"names":[],"mappings":"AAAA,aACI,SAAU,SACV,MAAO,KAEX,kBACI,QAAS,MACT,MAAO,MACP,QAAS,IAAI,EACb,WAAY,OACZ,WAAY,KACZ,cAAe,IAAI,MAAM,KACzB,MAAO,KAEX,wBACI,WAAY,KAEhB,wBACI,QAAS,IAAI,OAAO,KAExB,qBAAsB,2BAClB,SAAU,SACV,IAAK,EACL,KAAM,EACN,MAAO,KACP,OAAQ,KACR,WAAY,KACZ,QAAS,EACT,WAAY,QACZ,WAAY,OAEhB,0BACI,QAAS,MACT,SAAU,SACV,IAAK,IACL,MAAO,KACP,WAAY,KACZ,UAAW,KAEf,2BACI,SAAU,SACV,WAAY,KACZ,UAAW,KACX,YAAa,KACb,OAAQ,KACR,WAAY,KAEhB,4BACI,WAAY,QAEhB,gBACI,OAAQ,EACR,QAAS,EACT,WAAY,KAEhB,mBACI,OAAQ,EACR,QAAS,IACT,YAAa,KACb,UAAW,KACX,iBAAkB,QAGtB,kBACqC,oBAArC,kBADqC,uBADrC,gBAEmB,iBADA,iBADkB,gBAApB,mBAGb,aAAc,KACd,QAAS,OAIb,mBACI,QAAS,aACT,WAAY,iBACZ,MAAO,KACP,OAAQ,KACR,eAAgB,YAEpB,oBACI,QAAS,MAEb,4BACI,QAAS,aACT,WAAY,oBACZ,MAAO,KACP,OAAQ,KACR,eAAgB,YAGiB,oBAArC,kBAAmB,iBACf,QAAS,OAGwB,kBACnB,oBADA,kBAClB,iBADA,iBAEI,MAAO,KAGM,kBACoB,oBAArC,kBAAmB,iBADiB,iBAApC,gBAEI,UAAW,KACX,YAAa,IAEjB,uBACI,QAAS,KACT,WAAY,OACZ,YAAa,IAEjB,uBACI,QAAQ,KACR,MAAM,KACN,OAAO,KACP,eAAe,YAEnB,uCACI,QAAS,OAEb,2CACI,QAAS,OACT,MAAO,QAEX,qCACI,iBAAkB,QAClB,MAAO,KAEX,kCACI,iBAAkB,QAClB,MAAO,KAEX,iBACI,QAAS,MACT,WAAY,qHACZ,WAAY,yLACZ,WAAY,wHACZ,WAAY,mHACZ,WAAY,oHACZ,WAAY,sHACZ,MAAO,EACP,OAAQ,KACR,cAAe,IACf,cAAe,IAGnB,uBACI,OAAQ,KACR,cAAe,IAGnB,iCACI,OAAQ,IAGZ,uBACI,SAAU,SACV,QAAS,EAET,QAAS,GAIb,4BACI,OAAQ,QAGZ,mCACI,QAAS,aACT,OAAQ,QAGZ,kCACI,SAAU,OACV,WAAY,KACZ,aAAc,KACd,cAAe,KAEf,QAAS,EAKb,uBACI,QAAS,KACT,WAAY,cACZ,MAAO,KACP,OAAQ,KACR,eAAgB,YAChB,aAAc,IAGlB,SACI,QAAS,KAIb,oBACI,QAAS,KAGb,0BACI,QAAS,MAGb,oBACI,QAAS,KAGb,0BACI,QAAS,MAGb,uCACI,WAAY,OACZ,YAAa,KAGjB,8CACI,YAAa,IACb,aAAc,IAGlB,gDACI,eAAgB,KAGpB,8BACI,iBAAkB"} -------------------------------------------------------------------------------- /file-server/src/main/resources/static/fine-uploader/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ykrenz/springboot-file-server/e86bfe64f6d69c142a07d05b4c403362a4f70572/file-server/src/main/resources/static/fine-uploader/loading.gif -------------------------------------------------------------------------------- /file-server/src/main/resources/static/fine-uploader/pause.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ykrenz/springboot-file-server/e86bfe64f6d69c142a07d05b4c403362a4f70572/file-server/src/main/resources/static/fine-uploader/pause.gif -------------------------------------------------------------------------------- /file-server/src/main/resources/static/fine-uploader/placeholders/not_available-generic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ykrenz/springboot-file-server/e86bfe64f6d69c142a07d05b4c403362a4f70572/file-server/src/main/resources/static/fine-uploader/placeholders/not_available-generic.png -------------------------------------------------------------------------------- /file-server/src/main/resources/static/fine-uploader/placeholders/waiting-generic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ykrenz/springboot-file-server/e86bfe64f6d69c142a07d05b4c403362a4f70572/file-server/src/main/resources/static/fine-uploader/placeholders/waiting-generic.png -------------------------------------------------------------------------------- /file-server/src/main/resources/static/fine-uploader/processing.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ykrenz/springboot-file-server/e86bfe64f6d69c142a07d05b4c403362a4f70572/file-server/src/main/resources/static/fine-uploader/processing.gif -------------------------------------------------------------------------------- /file-server/src/main/resources/static/fine-uploader/retry.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ykrenz/springboot-file-server/e86bfe64f6d69c142a07d05b4c403362a4f70572/file-server/src/main/resources/static/fine-uploader/retry.gif -------------------------------------------------------------------------------- /file-server/src/main/resources/static/fine-uploader/templates/default.html: -------------------------------------------------------------------------------- 1 | 6 | 63 | -------------------------------------------------------------------------------- /file-server/src/main/resources/static/fine-uploader/templates/gallery.html: -------------------------------------------------------------------------------- 1 | 7 | 83 | -------------------------------------------------------------------------------- /file-server/src/main/resources/static/fine-uploader/templates/simple-thumbnails.html: -------------------------------------------------------------------------------- 1 | 7 | 65 | -------------------------------------------------------------------------------- /file-server/src/main/resources/static/fine-uploader/trash.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ykrenz/springboot-file-server/e86bfe64f6d69c142a07d05b4c403362a4f70572/file-server/src/main/resources/static/fine-uploader/trash.gif -------------------------------------------------------------------------------- /file-server/src/main/resources/static/md5/spark-md5.min.js: -------------------------------------------------------------------------------- 1 | (function(factory){if(typeof exports==="object"){module.exports=factory()}else if(typeof define==="function"&&define.amd){define(factory)}else{var glob;try{glob=window}catch(e){glob=self}glob.SparkMD5=factory()}})(function(undefined){"use strict";var add32=function(a,b){return a+b&4294967295},hex_chr=["0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"];function cmn(q,a,b,x,s,t){a=add32(add32(a,q),add32(x,t));return add32(a<>>32-s,b)}function md5cycle(x,k){var a=x[0],b=x[1],c=x[2],d=x[3];a+=(b&c|~b&d)+k[0]-680876936|0;a=(a<<7|a>>>25)+b|0;d+=(a&b|~a&c)+k[1]-389564586|0;d=(d<<12|d>>>20)+a|0;c+=(d&a|~d&b)+k[2]+606105819|0;c=(c<<17|c>>>15)+d|0;b+=(c&d|~c&a)+k[3]-1044525330|0;b=(b<<22|b>>>10)+c|0;a+=(b&c|~b&d)+k[4]-176418897|0;a=(a<<7|a>>>25)+b|0;d+=(a&b|~a&c)+k[5]+1200080426|0;d=(d<<12|d>>>20)+a|0;c+=(d&a|~d&b)+k[6]-1473231341|0;c=(c<<17|c>>>15)+d|0;b+=(c&d|~c&a)+k[7]-45705983|0;b=(b<<22|b>>>10)+c|0;a+=(b&c|~b&d)+k[8]+1770035416|0;a=(a<<7|a>>>25)+b|0;d+=(a&b|~a&c)+k[9]-1958414417|0;d=(d<<12|d>>>20)+a|0;c+=(d&a|~d&b)+k[10]-42063|0;c=(c<<17|c>>>15)+d|0;b+=(c&d|~c&a)+k[11]-1990404162|0;b=(b<<22|b>>>10)+c|0;a+=(b&c|~b&d)+k[12]+1804603682|0;a=(a<<7|a>>>25)+b|0;d+=(a&b|~a&c)+k[13]-40341101|0;d=(d<<12|d>>>20)+a|0;c+=(d&a|~d&b)+k[14]-1502002290|0;c=(c<<17|c>>>15)+d|0;b+=(c&d|~c&a)+k[15]+1236535329|0;b=(b<<22|b>>>10)+c|0;a+=(b&d|c&~d)+k[1]-165796510|0;a=(a<<5|a>>>27)+b|0;d+=(a&c|b&~c)+k[6]-1069501632|0;d=(d<<9|d>>>23)+a|0;c+=(d&b|a&~b)+k[11]+643717713|0;c=(c<<14|c>>>18)+d|0;b+=(c&a|d&~a)+k[0]-373897302|0;b=(b<<20|b>>>12)+c|0;a+=(b&d|c&~d)+k[5]-701558691|0;a=(a<<5|a>>>27)+b|0;d+=(a&c|b&~c)+k[10]+38016083|0;d=(d<<9|d>>>23)+a|0;c+=(d&b|a&~b)+k[15]-660478335|0;c=(c<<14|c>>>18)+d|0;b+=(c&a|d&~a)+k[4]-405537848|0;b=(b<<20|b>>>12)+c|0;a+=(b&d|c&~d)+k[9]+568446438|0;a=(a<<5|a>>>27)+b|0;d+=(a&c|b&~c)+k[14]-1019803690|0;d=(d<<9|d>>>23)+a|0;c+=(d&b|a&~b)+k[3]-187363961|0;c=(c<<14|c>>>18)+d|0;b+=(c&a|d&~a)+k[8]+1163531501|0;b=(b<<20|b>>>12)+c|0;a+=(b&d|c&~d)+k[13]-1444681467|0;a=(a<<5|a>>>27)+b|0;d+=(a&c|b&~c)+k[2]-51403784|0;d=(d<<9|d>>>23)+a|0;c+=(d&b|a&~b)+k[7]+1735328473|0;c=(c<<14|c>>>18)+d|0;b+=(c&a|d&~a)+k[12]-1926607734|0;b=(b<<20|b>>>12)+c|0;a+=(b^c^d)+k[5]-378558|0;a=(a<<4|a>>>28)+b|0;d+=(a^b^c)+k[8]-2022574463|0;d=(d<<11|d>>>21)+a|0;c+=(d^a^b)+k[11]+1839030562|0;c=(c<<16|c>>>16)+d|0;b+=(c^d^a)+k[14]-35309556|0;b=(b<<23|b>>>9)+c|0;a+=(b^c^d)+k[1]-1530992060|0;a=(a<<4|a>>>28)+b|0;d+=(a^b^c)+k[4]+1272893353|0;d=(d<<11|d>>>21)+a|0;c+=(d^a^b)+k[7]-155497632|0;c=(c<<16|c>>>16)+d|0;b+=(c^d^a)+k[10]-1094730640|0;b=(b<<23|b>>>9)+c|0;a+=(b^c^d)+k[13]+681279174|0;a=(a<<4|a>>>28)+b|0;d+=(a^b^c)+k[0]-358537222|0;d=(d<<11|d>>>21)+a|0;c+=(d^a^b)+k[3]-722521979|0;c=(c<<16|c>>>16)+d|0;b+=(c^d^a)+k[6]+76029189|0;b=(b<<23|b>>>9)+c|0;a+=(b^c^d)+k[9]-640364487|0;a=(a<<4|a>>>28)+b|0;d+=(a^b^c)+k[12]-421815835|0;d=(d<<11|d>>>21)+a|0;c+=(d^a^b)+k[15]+530742520|0;c=(c<<16|c>>>16)+d|0;b+=(c^d^a)+k[2]-995338651|0;b=(b<<23|b>>>9)+c|0;a+=(c^(b|~d))+k[0]-198630844|0;a=(a<<6|a>>>26)+b|0;d+=(b^(a|~c))+k[7]+1126891415|0;d=(d<<10|d>>>22)+a|0;c+=(a^(d|~b))+k[14]-1416354905|0;c=(c<<15|c>>>17)+d|0;b+=(d^(c|~a))+k[5]-57434055|0;b=(b<<21|b>>>11)+c|0;a+=(c^(b|~d))+k[12]+1700485571|0;a=(a<<6|a>>>26)+b|0;d+=(b^(a|~c))+k[3]-1894986606|0;d=(d<<10|d>>>22)+a|0;c+=(a^(d|~b))+k[10]-1051523|0;c=(c<<15|c>>>17)+d|0;b+=(d^(c|~a))+k[1]-2054922799|0;b=(b<<21|b>>>11)+c|0;a+=(c^(b|~d))+k[8]+1873313359|0;a=(a<<6|a>>>26)+b|0;d+=(b^(a|~c))+k[15]-30611744|0;d=(d<<10|d>>>22)+a|0;c+=(a^(d|~b))+k[6]-1560198380|0;c=(c<<15|c>>>17)+d|0;b+=(d^(c|~a))+k[13]+1309151649|0;b=(b<<21|b>>>11)+c|0;a+=(c^(b|~d))+k[4]-145523070|0;a=(a<<6|a>>>26)+b|0;d+=(b^(a|~c))+k[11]-1120210379|0;d=(d<<10|d>>>22)+a|0;c+=(a^(d|~b))+k[2]+718787259|0;c=(c<<15|c>>>17)+d|0;b+=(d^(c|~a))+k[9]-343485551|0;b=(b<<21|b>>>11)+c|0;x[0]=a+x[0]|0;x[1]=b+x[1]|0;x[2]=c+x[2]|0;x[3]=d+x[3]|0}function md5blk(s){var md5blks=[],i;for(i=0;i<64;i+=4){md5blks[i>>2]=s.charCodeAt(i)+(s.charCodeAt(i+1)<<8)+(s.charCodeAt(i+2)<<16)+(s.charCodeAt(i+3)<<24)}return md5blks}function md5blk_array(a){var md5blks=[],i;for(i=0;i<64;i+=4){md5blks[i>>2]=a[i]+(a[i+1]<<8)+(a[i+2]<<16)+(a[i+3]<<24)}return md5blks}function md51(s){var n=s.length,state=[1732584193,-271733879,-1732584194,271733878],i,length,tail,tmp,lo,hi;for(i=64;i<=n;i+=64){md5cycle(state,md5blk(s.substring(i-64,i)))}s=s.substring(i-64);length=s.length;tail=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];for(i=0;i>2]|=s.charCodeAt(i)<<(i%4<<3)}tail[i>>2]|=128<<(i%4<<3);if(i>55){md5cycle(state,tail);for(i=0;i<16;i+=1){tail[i]=0}}tmp=n*8;tmp=tmp.toString(16).match(/(.*?)(.{0,8})$/);lo=parseInt(tmp[2],16);hi=parseInt(tmp[1],16)||0;tail[14]=lo;tail[15]=hi;md5cycle(state,tail);return state}function md51_array(a){var n=a.length,state=[1732584193,-271733879,-1732584194,271733878],i,length,tail,tmp,lo,hi;for(i=64;i<=n;i+=64){md5cycle(state,md5blk_array(a.subarray(i-64,i)))}a=i-64>2]|=a[i]<<(i%4<<3)}tail[i>>2]|=128<<(i%4<<3);if(i>55){md5cycle(state,tail);for(i=0;i<16;i+=1){tail[i]=0}}tmp=n*8;tmp=tmp.toString(16).match(/(.*?)(.{0,8})$/);lo=parseInt(tmp[2],16);hi=parseInt(tmp[1],16)||0;tail[14]=lo;tail[15]=hi;md5cycle(state,tail);return state}function rhex(n){var s="",j;for(j=0;j<4;j+=1){s+=hex_chr[n>>j*8+4&15]+hex_chr[n>>j*8&15]}return s}function hex(x){var i;for(i=0;i>16)+(y>>16)+(lsw>>16);return msw<<16|lsw&65535}}if(typeof ArrayBuffer!=="undefined"&&!ArrayBuffer.prototype.slice){(function(){function clamp(val,length){val=val|0||0;if(val<0){return Math.max(val+length,0)}return Math.min(val,length)}ArrayBuffer.prototype.slice=function(from,to){var length=this.byteLength,begin=clamp(from,length),end=length,num,target,targetArray,sourceArray;if(to!==undefined){end=clamp(to,length)}if(begin>end){return new ArrayBuffer(0)}num=end-begin;target=new ArrayBuffer(num);targetArray=new Uint8Array(target);sourceArray=new Uint8Array(this,begin,num);targetArray.set(sourceArray);return target}})()}function toUtf8(str){if(/[\u0080-\uFFFF]/.test(str)){str=unescape(encodeURIComponent(str))}return str}function utf8Str2ArrayBuffer(str,returnUInt8Array){var length=str.length,buff=new ArrayBuffer(length),arr=new Uint8Array(buff),i;for(i=0;i>2]|=buff.charCodeAt(i)<<(i%4<<3)}this._finish(tail,length);ret=hex(this._hash);if(raw){ret=hexToBinaryString(ret)}this.reset();return ret};SparkMD5.prototype.reset=function(){this._buff="";this._length=0;this._hash=[1732584193,-271733879,-1732584194,271733878];return this};SparkMD5.prototype.getState=function(){return{buff:this._buff,length:this._length,hash:this._hash.slice()}};SparkMD5.prototype.setState=function(state){this._buff=state.buff;this._length=state.length;this._hash=state.hash;return this};SparkMD5.prototype.destroy=function(){delete this._hash;delete this._buff;delete this._length};SparkMD5.prototype._finish=function(tail,length){var i=length,tmp,lo,hi;tail[i>>2]|=128<<(i%4<<3);if(i>55){md5cycle(this._hash,tail);for(i=0;i<16;i+=1){tail[i]=0}}tmp=this._length*8;tmp=tmp.toString(16).match(/(.*?)(.{0,8})$/);lo=parseInt(tmp[2],16);hi=parseInt(tmp[1],16)||0;tail[14]=lo;tail[15]=hi;md5cycle(this._hash,tail)};SparkMD5.hash=function(str,raw){return SparkMD5.hashBinary(toUtf8(str),raw)};SparkMD5.hashBinary=function(content,raw){var hash=md51(content),ret=hex(hash);return raw?hexToBinaryString(ret):ret};SparkMD5.ArrayBuffer=function(){this.reset()};SparkMD5.ArrayBuffer.prototype.append=function(arr){var buff=concatenateArrayBuffers(this._buff.buffer,arr,true),length=buff.length,i;this._length+=arr.byteLength;for(i=64;i<=length;i+=64){md5cycle(this._hash,md5blk_array(buff.subarray(i-64,i)))}this._buff=i-64>2]|=buff[i]<<(i%4<<3)}this._finish(tail,length);ret=hex(this._hash);if(raw){ret=hexToBinaryString(ret)}this.reset();return ret};SparkMD5.ArrayBuffer.prototype.reset=function(){this._buff=new Uint8Array(0);this._length=0;this._hash=[1732584193,-271733879,-1732584194,271733878];return this};SparkMD5.ArrayBuffer.prototype.getState=function(){var state=SparkMD5.prototype.getState.call(this);state.buff=arrayBuffer2Utf8Str(state.buff);return state};SparkMD5.ArrayBuffer.prototype.setState=function(state){state.buff=utf8Str2ArrayBuffer(state.buff,true);return SparkMD5.prototype.setState.call(this,state)};SparkMD5.ArrayBuffer.prototype.destroy=SparkMD5.prototype.destroy;SparkMD5.ArrayBuffer.prototype._finish=SparkMD5.prototype._finish;SparkMD5.ArrayBuffer.hash=function(arr,raw){var hash=md51_array(new Uint8Array(arr)),ret=hex(hash);return raw?hexToBinaryString(ret):ret};return SparkMD5}); 2 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | amazon-s3-starter 7 | file-server 8 | 9 | 10 | org.springframework.boot 11 | spring-boot-starter-parent 12 | 2.4.4 13 | 14 | 15 | io.github.ykrenz 16 | spring-boot-file-server 17 | 1.0.0-SNAPSHOT 18 | spring-boot-file-server 19 | Spring Boot File Server 20 | pom 21 | 22 | 1.8 23 | 8 24 | 8 25 | 1.8 26 | 1.8 27 | UTF-8 28 | UTF-8 29 | 8.0.16 30 | 3.4.2 31 | 2.0.9 32 | 1.12.15 33 | 1.0.0 34 | 35 | 36 | 37 | 38 | org.projectlombok 39 | lombok 40 | true 41 | 42 | 43 | org.apache.commons 44 | commons-lang3 45 | 46 | 47 | 48 | 49 | 50 | 51 | io.github.ykrenz 52 | fastdfs-client-spring-boot-starter 53 | ${fastdfs-client-spring-boot-starter.version} 54 | 55 | 56 | com.amazonaws 57 | aws-java-sdk-bom 58 | ${aws-java-sdk-bom.version} 59 | pom 60 | import 61 | 62 | 63 | io.github.ykrenz 64 | amazon-s3-starter 65 | ${project.version} 66 | 67 | 68 | mysql 69 | mysql-connector-java 70 | runtime 71 | ${mysql.version} 72 | 73 | 74 | com.baomidou 75 | mybatis-plus-boot-starter 76 | ${mybatis-plus-boot-starter.version} 77 | 78 | 79 | com.github.xiaoymin 80 | knife4j-spring-boot-starter 81 | ${knife4j-spring-boot-starter.version} 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /request.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ykrenz/springboot-file-server/e86bfe64f6d69c142a07d05b4c403362a4f70572/request.png -------------------------------------------------------------------------------- /show.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ykrenz/springboot-file-server/e86bfe64f6d69c142a07d05b4c403362a4f70572/show.gif --------------------------------------------------------------------------------