[TOC] # **文件中心** ## SpringBoot 文件上传原理 > SpringBoot 中的`DispatcherServlet`在处理请求时会调用`MultipartResolver`中的方法判断此请求是不是文件上传请求,如果是的话`DispatcherServlet`将调用`MultipartResolver`的`resolveMultipart(request)`方法对该请求对象进行装饰并返回一个新的`MultipartHttpServletRequest`供后继处理流程使用,注意!此时的请求对象会由`HttpServletRequest`类型转换成`MultipartHttpServletRequest`类型,这个类中会包含所上传的文件对象可供后续流程直接使用而无需自行在代码中实现对文件内容的读取逻辑。 ![](https://img.kancloud.cn/51/4d/514d909e2d015bdaa0a59acceda07bbd_1041x760.png) ## 依赖关系 ![](https://img.kancloud.cn/81/e4/81e46a2ad34763e579d1691a17ead59a_538x401.png) ``` <!-- 资源服务器 --> <dependency> <groupId>com.open.capacity</groupId> <artifactId>uaa-client-spring-boot-starter</artifactId> </dependency> ``` ## swagger测试文件接口 * 查询接口测试 ![](https://img.kancloud.cn/a5/70/a5708ba2acb12799cf55ab1be3e26b7d_1919x973.png) * 上传文件测试 ![](https://img.kancloud.cn/96/9b/969bb3ff36fae301037db128dfdda5f0_1918x383.png) * swagger测试 ![](https://img.kancloud.cn/3a/61/3a611a8719f9e690eab02d0a5148c98d_1920x791.png) ## file-center 授权流程 ![](https://box.kancloud.cn/7eea410bb58d317640e825e7a3e7c20e_588x703.png) ``` public Authentication authenticate(Authentication authentication) throws AuthenticationException { if (authentication == null) { throw new InvalidTokenException("Invalid token (token not found)"); } String token = (String) authentication.getPrincipal(); OAuth2Authentication auth = tokenServices.loadAuthentication(token); if (auth == null) { throw new InvalidTokenException("Invalid token: " + token); } Collection<String> resourceIds = auth.getOAuth2Request().getResourceIds(); if (resourceId != null && resourceIds != null && !resourceIds.isEmpty() && !resourceIds.contains(resourceId)) { throw new OAuth2AccessDeniedException("Invalid token does not contain resource id (" + resourceId + ")"); } checkClientDetails(auth); if (authentication.getDetails() instanceof OAuth2AuthenticationDetails) { OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) authentication.getDetails(); // Guard against a cached copy of the same details if (!details.equals(auth.getDetails())) { // Preserve the authentication details from the one loaded by token services details.setDecodedDetails(auth.getDetails()); } } auth.setDetails(authentication.getDetails()); auth.setAuthenticated(true); return auth; ``` * token校验redis配置以及文件数据库配置 ``` spring: datasource: dynamic: enable: true druid: # JDBC 配置(驱动类自动从url的mysql识别,数据源类型自动识别) core: url: jdbc:mysql://59.110.164.254:3306/file_center?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver log: url: jdbc:mysql://59.110.164.254:3306/log-center?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver #连接池配置(通常来说,只需要修改initialSize、minIdle、maxActive initial-size: 1 max-active: 20 min-idle: 1 # 配置获取连接等待超时的时间 max-wait: 60000 #打开PSCache,并且指定每个连接上PSCache的大小 pool-prepared-statements: true max-pool-prepared-statement-per-connection-size: 20 validation-query: SELECT 'x' test-on-borrow: false test-on-return: false test-while-idle: true #配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 time-between-eviction-runs-millis: 60000 #配置一个连接在池中最小生存的时间,单位是毫秒 min-evictable-idle-time-millis: 300000 filters: stat,wall # WebStatFilter配置,说明请参考Druid Wiki,配置_配置WebStatFilter #是否启用StatFilter默认值true web-stat-filter.enabled: true web-stat-filter.url-pattern: /* web-stat-filter.exclusions: "*.js , *.gif ,*.jpg ,*.png ,*.css ,*.ico , /druid/*" web-stat-filter.session-stat-max-count: 1000 web-stat-filter.profile-enable: true # StatViewServlet配置 #展示Druid的统计信息,StatViewServlet的用途包括:1.提供监控信息展示的html页面2.提供监控信息的JSON API #是否启用StatViewServlet默认值true stat-view-servlet.enabled: true #根据配置中的url-pattern来访问内置监控页面,如果是上面的配置,内置监控页面的首页是/druid/index.html例如: #http://110.76.43.235:9000/druid/index.html #http://110.76.43.235:8080/mini-web/druid/index.html stat-view-servlet.url-pattern: /druid/* #允许清空统计数据 stat-view-servlet.reset-enable: true stat-view-servlet.login-username: admin stat-view-servlet.login-password: admin #StatViewSerlvet展示出来的监控信息比较敏感,是系统运行的内部情况,如果你需要做访问控制,可以配置allow和deny这两个参数 #deny优先于allow,如果在deny列表中,就算在allow列表中,也会被拒绝。如果allow没有配置或者为空,则允许所有访问 #配置的格式 #<IP> #或者<IP>/<SUB_NET_MASK_size>其中128.242.127.1/24 #24表示,前面24位是子网掩码,比对的时候,前面24位相同就匹配,不支持IPV6。 #stat-view-servlet.allow= #stat-view-servlet.deny=128.242.127.1/24,128.242.128.1 # Spring监控配置,说明请参考Druid Github Wiki,配置_Druid和Spring关联监控配置 #aop-patterns= # Spring监控AOP切入点,如x.y.z.service.*,配置多个英文逗号分隔 ################### mysq end ########################## # zipkin: # base-url: http://127.0.0.1:11008 redis: ################### redis 单机版 start ########################## host: 59.110.164.254 port: 6379 timeout: 6000 database: 1 lettuce: pool: max-active: 10 # 连接池最大连接数(使用负值表示没有限制),如果赋值为-1,则表示不限制;如果pool已经分配了maxActive个jedis实例,则此时pool的状态为exhausted(耗尽) max-idle: 8 # 连接池中的最大空闲连接 ,默认值也是8 max-wait: 100 # # 等待可用连接的最大时间,单位毫秒,默认值为-1,表示永不超时。如果超过等待时间,则直接抛出JedisConnectionException min-idle: 2 # 连接池中的最小空闲连接 ,默认值也是0 shutdown-timeout: 100ms ################### redis 单机版 end ########################## # cluster: # nodes: 130.75.131.237:7000,130.75.131.238:7000,130.75.131.239:7000,130.75.131.237:7001,130.75.131.238:7001,130.75.131.239:7001 # #130.75.131.237:7000,130.75.131.238:7000,130.75.131.239:7000,130.75.131.237:7001,130.75.131.238:7001,130.75.131.239:7001 # #192.168.3.157:7000,192.168.3.158:7000,192.168.3.159:7000,192.168.3.157:7001,192.168.3.158:7001,192.168.3.159:7001 # timeout: 1000 # 连接超时时间(毫秒) # lettuce: # pool: # max-active: 10 # 连接池最大连接数(使用负值表示没有限制),如果赋值为-1,则表示不限制;如果pool已经分配了maxActive个jedis实例,则此时pool的状态为exhausted(耗尽) # max-idle: 8 # 连接池中的最大空闲连接 ,默认值也是8 # max-wait: 100 # # 等待可用连接的最大时间,单位毫秒,默认值为-1,表示永不超时。如果超过等待时间,则直接抛出JedisConnectionException # min-idle: 2 # 连接池中的最小空闲连接 ,默认值也是0 # shutdown-timeout: 100ms #mybatis: mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl config-location: classpath:mybatis.cfg.xml mapper-locations: classpath*:com/open/**/dao/*.xml security: oauth2: ignored: /files-anon/** , /doc.html ,/upload.html , /uploads.html ,/js/** ,/document.html token: store: type: redis #设置最大超时时间 ribbon: httpclient: enabled: false okhttp: enabled: true ReadTimeout: 90000 ConnectTimeout: 90000 OkToRetryOnAllOperations: true MaxAutoRetries: 1 MaxAutoRetriesNextServer: 1 #设置最大容错超时时间 hystrix: command: default: execution: timeout: enabled: true isolation: thread: timeoutInMilliseconds: 90000 logging: level: com.open.capacity: INFO org.hibernate: INFO org.hibernate.type.descriptor.sql.BasicBinder: TRACE org.hibernate.type.descriptor.sql.BasicExtractor: TRACE # com.neusoft: DEBUG # com.netflix: DEBUG #用于心跳检测输出的日志 ``` ## OSS 存储服务 ![](https://img.kancloud.cn/06/05/0605a29fa79f2ff7532896095f53aed3_1371x636.png) * 支持阿里云oss,七牛oss,本地,fastdfs四种配置 ![](https://img.kancloud.cn/d8/8c/d88c59385158e3a584f8360bd1ba9ecd_1459x394.png) ``` #阿里云文件上传 aliyun: oss: access-key: LTAIMjvZWiXRuClW accessKeySecret: YwJar7gkdZx3Q3Zk6TRuEAWaAz6n8y endpoint: oss-cn-beijing.aliyuncs.com bucketName: owenwangwen domain: https://owenwangwen.oss-cn-beijing.aliyuncs.com #七牛文件上传 qiniu: oss: access-key: owGiAWGn6DpU5zlrfLP4K9iQusahmspTW6PxRABW accessKeySecret: 5CBWKFd1pP-OSiusd1Bvhokp-ih4i3bs2QA2r-U2 endpoint: http://q4c5xw7eb.bkt.clouddn.com bucketName: ocpowenwangwen ## 本地文件上传 file: oss: domain: http://127.0.0.1:9200/api-file path: d:/uploadshp prefix: /statics #fastDFS配置 fdfs: oss : ##nginx需要安装fastdfs插件 domain: http://192.168.235.173/ soTimeout: 1500 connectTimeout: 600 pool: jmx-enabled: false trackerList: 192.168.235.173:22122     ``` * 代码默认采用七牛云上传,如果要改用别的方式注意修改FileType.QINIU ![](https://img.kancloud.cn/7a/27/7a278bbbfa9bd943ee57cb7a5bb84ee2_1491x466.png) * 七牛云,七牛云核心配置 ![](https://box.kancloud.cn/cf44ac3e04f702f399d69821f7e9363b_1727x647.png) * 七牛云上传核心类 ![](https://box.kancloud.cn/29c024ee23404cccb6110de75752640f_1107x467.png) ## 大文件分片上传 ### 前端webuploader 分片与并发结合,将一个大文件分割成多块,并发上传,极大地提高大文件的上传速度。 当网络问题导致传输错误时,只需要重传出错分片,而不是整个文件。另外分片传输能够更加实时的跟踪上传进度。 ### 文件分片 ``` /** * 上传大文件 * @param file * @param chunks */ @PostMapping(value = "/files-anon/bigFile") // @ResponseStatus(code= HttpStatus.INTERNAL_SERVER_ERROR,reason="server error") public ResponseEntity bigFile( String guid, Integer chunk, MultipartFile file, Integer chunks){ try { fileServiceFactory.getFileService(FileType.LOCAL.toString()).chunk(guid,chunk,file,chunks,localFilePath); return ResponseEntity.succeed("操作成功"); }catch (Exception ex){ return ResponseEntity.failed("操作失败"); } } ``` ### 文件合并 ``` /** * 合并文件 * @param mergeFileDTO */ @RequestMapping(value = "/files-anon/merge",method =RequestMethod.POST ) public ResponseEntity mergeFile(@RequestBody MergeFileDTO mergeFileDTO){ try { return ResponseEntity.succeed(fileServiceFactory.getFileService(FileType.LOCAL.toString()).merge(mergeFileDTO.getGuid(),mergeFileDTO.getFileName(),localFilePath),"操作成功"); }catch (Exception ex){ return ResponseEntity.failed("操作失败"); } } ``` ## FastDFS文件系统详解 ### FastDFS介绍 FastDFS的开发者——淘宝的架构师余庆老师 FastDFS开源地址:https://github.com/happyfish100 ### FastDFS是什么? FastDFS是一个开源的轻量级分布式文件系统 FastDFS 不是通用的文件系统,只能通过装有API访问,追求高性能,高扩展性 FastDFS 是一款用户态的分布式文件系统 ### FastDFS能做什么? 它的主要功能包括:文件存储,文件同步和文件访问,以及高容量和负载平衡。主要解决了海量数据存储问题,特别适合以中小文件(建议范围:4KB < file_size <500MB)为载体的在线服务。在底层存储上通过逻辑的分组概念,使得通过在同组内配置多个Storage,从而实现软RAID10 相关术语   • Tracker Server:跟踪服务器,主要做调度工作,起到均衡的作用;负责管理所有的 storage server和 group,每个 storage 在启动后会连接 Tracker,告知自己所属 group 等信息,并保持周期性心跳。tracker根据storage的心跳信息,建立group==>[storage server list]的映射表。由此可见Tracker的作用至关重要,也就增加了系统的单点故障,为此FastDFS支持多个备用的Tracker   • Storage Server:存储服务器,主要提供容量和备份服务;以 group 为单位,每个 group 内可以有多台 storage server,数据互为备份。   • Client:客户端,上传下载数据的服务器,也就是我们自己的项目所部署在的服务器。 • group:组,也可称为卷。同组内服务器上的文件是完全相同的。 • 文件标识:包括两部分:组名和文件名(包含路径)。 • meta data:文件相关属性,键值对(Key Value Pair)方式,如:width=1024,heigth=768。 ### FastDFS架构图 ![](https://img.kancloud.cn/5c/ec/5cec60e49e7522393ba0fe25fce202bb_713x888.png) ### 文件系统的对比 ![](https://img.kancloud.cn/30/4c/304ce8efcd65feecfd7da0000efdc2ac_814x446.png) ### 文件上传流程 ![](https://img.kancloud.cn/d9/57/d9577e9bdc751da73ea2090580d3c68f_873x604.png) ### 文件下载流程 ![](https://img.kancloud.cn/45/f7/45f7cd0e7ca9e52e510e7eb5675050ec_859x616.png) ### 同步机制 •同一组内的storage server之间是对等的,文件上传、删除等操作可以在任意一台storage server上进行; •文件同步只在同组内的storage server之间进行,采用push方式,即源服务器同步给目标服务器; •源头数据才需要同步,备份数据不需要再次同步,否则就构成环路了; •上述第二条规则有个例外,就是新增加一台storage server时,由已有的一台storage server将已有的所有数据(包括源头数据和备份数据)同步给该新增服务器。 ### 运行时目录结构-tracker server ![](https://img.kancloud.cn/32/b0/32b06b72556da7304f365d9657c5f643_695x220.png) ### 运行时目录结构-storage server ![](https://img.kancloud.cn/48/2b/482be4d7281482fa728ddb0c44d6e426_745x418.png) ### 快速搭建 服务器192.168.235.173准备 | 软件 | 版本 | 备注 | | --- | --- |--- | | centos| 7.5 | | | docker-ce| 18.06.1 | | ``` docker pull registry.cn-beijing.aliyuncs.com/tianzuo/fastdfs mkdir /var/local/fdfs docker run \ --net=host \ --name=fastdfs \ -e IP=192.168.235.173 \ -e WEB_PORT=80 \ -v $PWD/fdfs:/var/local/fdfs \ -d registry.cn-beijing.aliyuncs.com/tianzuo/fastdfs ``` 确保80、22122和23000这3个端口未被占用 IP:配置所在服务器的ip WEB_PORT:配置nginx的端口,用于文件下载