├── .editorconfig ├── .gitignore ├── LICENSE ├── README.md ├── pom.xml └── src └── main ├── java └── com │ └── gomyck │ └── fastdfs │ └── starter │ ├── GomyckFastDFSConfiguration.java │ ├── common │ ├── Constant.java │ ├── DownloadFileNumException.java │ ├── FDFSUtil.java │ ├── FileNotFoundException.java │ └── IllegalParameterException.java │ ├── controller │ ├── ChunkDownloadHandler.java │ ├── ChunkUploadHandler.java │ ├── CkErrorController.java │ ├── CkExceptionAdviceController.java │ ├── DoorChainHandler.java │ ├── SimpleFileDownloadHandler.java │ └── UploadManageHandler.java │ ├── database │ ├── ServiceCheck.java │ ├── SimpleUploadService.java │ ├── UploadService.java │ └── entity │ │ ├── BatchDownLoadParameter.java │ │ └── CkFileInfo.java │ ├── doorchain │ ├── ChaimEntity.java │ ├── ExecuteChain.java │ ├── HashToken.java │ ├── Interceptor.java │ ├── SimpleChain.java │ ├── TempToken.java │ ├── Token.java │ ├── TokenValidator.java │ └── Validator.java │ ├── job │ └── FileCleanTask.java │ ├── lock │ ├── FileLock.java │ ├── SimpleMapFileLock.java │ └── SimpleRedisFileLock.java │ └── profile │ └── FileServerProfile.java └── resources └── META-INF ├── resources └── ck-fastdfs │ ├── css │ ├── errorPage.css │ └── webuploader.css │ ├── font │ ├── texgyreschola-bold-webfont.eot │ ├── texgyreschola-bold-webfont.ttf │ ├── texgyreschola-bold-webfont.woff │ ├── texgyreschola-regular-webfont.eot │ ├── texgyreschola-regular-webfont.ttf │ └── texgyreschola-regular-webfont.woff │ ├── image │ ├── 404.gif │ ├── 404_s-divider.jpg │ ├── 404_search.png │ ├── bg_noise.jpg │ └── logo.png │ ├── js │ ├── ckFastDFS.js │ └── webuploader.nolog.js │ ├── swf │ └── Uploader.swf │ └── view │ ├── ckFastDFS.html │ └── error.html └── spring.factories /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | charset = utf-8 7 | end_of_line = lf 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.{json,yml,ymal}] 12 | indent_size = 2 13 | 14 | [*.md] 15 | insert_final_newline = false 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | .sts4-cache 12 | 13 | ### IntelliJ IDEA ### 14 | .idea 15 | *.iws 16 | *.iml 17 | *.ipr 18 | 19 | ### NetBeans ### 20 | /nbproject/private/ 21 | /build/ 22 | /nbbuild/ 23 | /dist/ 24 | /nbdist/ 25 | /.nb-gradle/ 26 | .DS_Store -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [http://www.gomyck.com] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | Thanks for JetBrains IDEA 3 | 4 | 5 | 6 | ## 开始使用 7 | 8 | ### 当前MASTER为2.0.1-SNAPSHOT版本 9 | 10 | 如果使用 SNAPSHOT 版本 , 请勾选 develop-fastdfs 11 | 12 | 13 | ### 一.环境配置 14 | 15 | #### 1.在pom文件中加入依赖: 16 | ```xml 17 | 18 | com.gomyck 19 | gomyck-fastdfs-spring-boot-starter 20 | 2.0.1-RELEASE 21 | 22 | ``` 23 | #### 2.编辑yml文件(以下为全量配置): 24 | ```text 25 | #单个文件上传大小限制 26 | spring: 27 | servlet: 28 | multipart: 29 | max-file-size: 5000MB 30 | #fastdfs客户端配置 31 | fdfs: 32 | connect-timeout: 1601 33 | thumb-image: 34 | width: 150 35 | height: 150 36 | pool: 37 | jmx-name-prefix: 1 38 | jmx-name-base: 1 39 | max-wait-millis: 102 40 | tracker-list: 41 | - 192.168.1.1:22122 #fastdfs服务地址 42 | so-timeout: 1501 43 | pool: 44 | max-total: 153 45 | 46 | gomyck: 47 | config: 48 | redis: true #是否使用redis存储文件上传信息以及上传锁 49 | redis: 50 | host: 127.0.0.1 51 | password: xxxxx 52 | port: 6379 53 | fastdfs: #fastdfs上传配置 54 | chunk-size: 5 #分块大小, 上传文件分块的大小 单位: MB 55 | download-chunk-size: 100 #分块下载大小 单位: byte 56 | group-id: group1 #fastdfs的组, 文件会被存到这个组下 57 | file-server-protocol: http #远程文件服务连接协议 58 | file-server-url: 192.168.1.196 #远程文件服务连接地址 59 | ``` 60 | 61 | #### 3.在静态资源映射表中加入以下配置(可选, 重写resources.staticLocations时要根据实际配置决定是否加入下述代码) 62 | #### 请注意, 如果你的项目中存在 WebMvcConfigurationSupport 子类, 那么必须添加静态资源配置, 否则将请求不到本项目资源 63 | ```java 64 | @Configuration 65 | public class Config extends WebMvcConfigurationSupport { 66 | 67 | @Override 68 | protected void addResourceHandlers(ResourceHandlerRegistry registry) { 69 | registry.addResourceHandler("ck-fastdfs/**").addResourceLocations("classpath:/META-INF/resources/ck-fastdfs/"); 70 | registry.addResourceHandler("ck-util/**").addResourceLocations("classpath:/META-INF/resources/ck-util/"); 71 | registry.addResourceHandler("ck-3pty/**").addResourceLocations("classpath:/META-INF/resources/ck-3pty/"); 72 | super.addResourceHandlers(registry); 73 | } 74 | } 75 | ``` 76 | #### 4.启动服务, 访问示例页面: host{:port}/{contextPath/}ck-fastdfs/view/ckFastDFS.html (可以渲染表示环境配置成功) 77 | 78 | ### 二.开发文档 79 | 80 | 本项目后端服务不在文档说明范围内, 高玩可以自行阅读修改, 只针对前端JS的使用做说明注释 81 | 82 | #### 1.在需要开发文件上传的页面(你的业务页面), 引入js: 83 | 84 | > {schema://}host{:port}{/contextPath}/ck-3pty/jquery/jquery-core.min.js 85 | > {schema://}host{:port}{/contextPath}/ck-fastdfs/js/webuploader.nolog.js 86 | > {schema://}host{:port}{/contextPath}/ck-fastdfs/js/ckFastDFS.js 87 | 88 | #### 2.开发文档 89 | 90 | ##### 1.实例化前端上传实例: 91 | ```javascript 92 | const option = { 93 | //config something..... 94 | }; 95 | const cfd = new CkFastDFS(option); 96 | ``` 97 | 每个实例可以绑定多个上传按钮, 支持id选择器, 类选择器等jq插件支持的选择器类型 98 | 99 | 多实例存在的场景: 多个文件服务分组, 当不同的按钮上传文件到不同分组时, 可能需要页面多实例来处理 100 | 101 | ##### 2.option参数说明: 102 | ```javascript 103 | { 104 | baseURI: "../../", //后端服务URI(包括上下文) (非必填) 105 | fastDFSGroup: "group1", //文件上传至fastdfs的组名 (非必填) 106 | uploaderConfig: {}, //webUploader配置 (非必填) 107 | uploadButton: { //按钮配置 (必填) 108 | buttonId: "#btn1", //选择器 (必填), 支持jq插件所支持的所有selector类型 109 | multiple: true //是否允许多文件选择 (非必填, 默认false) 110 | }, 111 | uploadProgressBar: { //进度条 (非必填) 112 | changeBar: function (refer, file, progressVal) { //文件上传中, 进度改变时会触发该方法 113 | console.log("进度条改变: " + refer + "|||" + file.id + "|||" + progressVal); 114 | } 115 | }, 116 | uploadListener: { //上传监听(非必填) 117 | 118 | // 参数说明: 119 | // refer:点击上传的按钮jq对象 120 | // file:上传的文件 121 | // result:后端服务返回的结果 122 | // reason:错误类型, 通常为字符串: server 123 | 124 | //添加文件信息 125 | appendFileInfo: function (refer, file) { 126 | console.log("选择文件: " + refer + "|||" + file.id); 127 | }, 128 | //添加到上传队列之前 129 | beforeAppendFileInQueued: function (refer, file) { 130 | console.log("添加到队列之前: " + refer + "|||" + file.id); 131 | return true; 132 | }, 133 | //开始上传 134 | beginUpload: function (refer, file) { 135 | console.log("开始上传: " + refer + "|||" + file.id); 136 | }, 137 | //分块上传成功 138 | chunkUploadSuccess: function (refer, file, result) { 139 | console.log("分块上传成功:" + refer + "|||" + file.id + "|||" + JSON.stringify(result)) 140 | }, 141 | //上传出错 142 | uploadError: function (refer, file, reason) { 143 | console.log("上传失败: " + refer + "|||" + file.id + "|||" + reason); 144 | }, 145 | //上传成功 146 | uploadSuccess: function (refer, file, result) { 147 | console.log("上传成功: " + refer + "|||" + file.id + "|||" + JSON.stringify(result)); 148 | }, 149 | //上传完成(不管上传成功失败, 都会触发该方法) 150 | uploadComplete: function (refer, file) { 151 | console.log("上传完成: " + refer + "|||" + file.id); 152 | }, 153 | //全局错误 154 | error: function (type, tips) { 155 | console.log("全局错误: " + type + "|||" + tips) 156 | } 157 | } 158 | } 159 | ``` 160 | #### 3.实例函数说明: 161 | ```javascript 162 | cfd.addButton(selector); //向实例中添加按钮 163 | cfd.pauseUpload(param); //暂停上传 param 为布尔值时, 为暂停正在上传的文件, file类型时, 暂停指定file的上传, null|undefined时为全部暂停 164 | cfd.cancleUpload(file); //取消指定文件的上传 165 | ``` 166 | #### 4.其他说明 167 | 1. 如何在自己的项目中添加fastdfs客户端实例: 168 | 169 | 使用@Autowired即可注入以下接口实例 170 | 171 | TrackerClient - TrackerServer接口 172 | 173 | GenerateStorageClient - 一般文件存储接口 (StorageServer接口) 174 | 175 | FastFileStorageClient - 为方便项目开发集成的简单接口(StorageServer接口) 176 | 177 | AppendFileStorageClient - 支持文件续传操作的接口 (StorageServer接口) 178 | 179 | 2. 如何扩展前端js: 180 | 181 | 请参考类:CkFastDFS.js 182 | 183 | 请参考webUploader官网API 184 | 185 | 当然, 你也可以fork master分支代码, 打包后供他人使用(修改后的分支代码, pom文件中的parent请删掉, 并手动指定依赖版本, 否则可能会出现版本不一致问题) 186 | 187 | ### 1.0.2-Release版本实现: 188 | > 1. 分块下载(服务端分块写到客户端, 不必等所有文件都加载到服务端内存中在一次性写出) 189 | > 2. 断点续传前后端校验(历史上传的块与本次上传的块大小是否一致, 否则会导致修改块大小配置导致上传文件有问题) 190 | 191 | ### 1.1.3-Release版本实现: 192 | > 1. 调整了一些方法, 有些列表可以分页查询了 193 | > 2. 调整了文件在 redis 中的存储方式 194 | > 3. 增加了错误页面, 具体使用 看源代码 195 | > 4. 修复了一个小 BUG, 该 BUG 可能导致 redis 连接得不到正确释放 196 | > 5. 新增了略缩图, 具体使用方式看代码, 图片上传之后默认生成略缩图 197 | > 6. 新增了文件过期时间, 默认不失效, 请看 js 代码配置(例子) 198 | > 7. 打包下载优化 199 | > 8. new version new life 200 | 201 | 202 | ### 下一版本实现: 203 | > 1. 文件下载防盗链实现(基于当前服务访问文件) 204 | > 2. fastdfs token获取(直连文件服务访问文件) 205 | > ...... 206 | 207 | ## 提供科学查资料渠道, 查资料更便捷(小范围, 更稳定可靠 https://blog.gomyck.com/about/) 208 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | com.gomyck 9 | gomyck-fastdfs 10 | 2.0.3-SNAPSHOT 11 | 12 | 13 | jar 14 | gomyck-fastdfs-spring-boot-starter 15 | gomyck-fastdfs-spring-boot-starter 16 | 17 | 18 | 19 | 20 | org.springframework.boot 21 | spring-boot-configuration-processor 22 | true 23 | 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-autoconfigure 28 | 29 | 30 | 31 | com.github.tobato 32 | fastdfs-client 33 | 34 | 35 | hibernate-validator 36 | org.hibernate.validator 37 | 38 | 39 | 40 | 41 | 42 | com.gomyck 43 | gomyck-redis-spring-boot-starter 44 | 45 | 46 | 47 | org.springframework.boot 48 | spring-boot-starter-web 49 | provided 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | true 58 | 59 | develop-fastdfs 60 | 61 | 62 | sonatype-Release 63 | https://oss.sonatype.org/content/repositories/releases/ 64 | 65 | 66 | sonatype-Snapshot 67 | https://oss.sonatype.org/content/repositories/snapshots/ 68 | 69 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /src/main/java/com/gomyck/fastdfs/starter/GomyckFastDFSConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.gomyck.fastdfs.starter; 2 | 3 | import com.github.tobato.fastdfs.FdfsClientConfig; 4 | import com.gomyck.cache.redis.starter.core.redis.RedisCache; 5 | import com.gomyck.fastdfs.starter.database.SimpleUploadService; 6 | import com.gomyck.fastdfs.starter.database.UploadService; 7 | import com.gomyck.fastdfs.starter.lock.FileLock; 8 | import com.gomyck.fastdfs.starter.lock.SimpleMapFileLock; 9 | import com.gomyck.fastdfs.starter.lock.SimpleRedisFileLock; 10 | import com.gomyck.fastdfs.starter.profile.FileServerProfile; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; 14 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 15 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 16 | import org.springframework.context.annotation.Bean; 17 | import org.springframework.context.annotation.ComponentScan; 18 | import org.springframework.context.annotation.Configuration; 19 | import org.springframework.context.annotation.Import; 20 | import org.springframework.scheduling.annotation.EnableScheduling; 21 | 22 | /** 23 | * starter 24 | * 25 | * @author gomyck 26 | * -------------------------------- 27 | * | qq: 474798383 | 28 | * | email: hao474798383@163.com | 29 | * | blog: https://blog.gomyck.com | 30 | * -------------------------------- 31 | * @version [1.0.0] 32 | * @since 2021/7/20 33 | */ 34 | @Configuration 35 | @Import(FdfsClientConfig.class) 36 | @ComponentScan("com.gomyck.fastdfs.starter") 37 | @EnableConfigurationProperties({FileServerProfile.class}) 38 | @EnableScheduling 39 | public class GomyckFastDFSConfiguration { 40 | 41 | Logger log = LoggerFactory.getLogger(GomyckFastDFSConfiguration.class); 42 | 43 | @Bean 44 | @ConditionalOnBean(value = RedisCache.class) 45 | public FileLock initFileLockRedis(){ 46 | log.info("Initializing redis file lock...."); 47 | return new SimpleRedisFileLock(); 48 | } 49 | 50 | @Bean 51 | @ConditionalOnMissingBean(UploadService.class) 52 | @ConditionalOnBean(value = RedisCache.class) 53 | public UploadService initUploadService(){ 54 | log.info("Initializing redis file upload Service...."); 55 | return new SimpleUploadService(); 56 | } 57 | 58 | @Bean 59 | @ConditionalOnMissingBean(FileLock.class) 60 | public FileLock initFileLockDefault(){ 61 | log.info("Initializing memory file lock, if you want open redis file lock, please config gomyck.config.redis: true in yml and config the jedisPool"); 62 | return new SimpleMapFileLock(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/com/gomyck/fastdfs/starter/common/Constant.java: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (c) 2019 gomyck 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy 6 | * of this software and associated documentation files (the "Software"), to deal 7 | * in the Software without restriction, including without limitation the rights 8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in all 13 | * copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | * SOFTWARE. 22 | */ 23 | 24 | package com.gomyck.fastdfs.starter.common; 25 | 26 | /** 27 | * 常量类 28 | * 29 | * @author gomyck 30 | * -------------------------------- 31 | * | qq: 474798383 | 32 | * | email: hao474798383@163.com | 33 | * | blog: https://blog.gomyck.com | 34 | * -------------------------------- 35 | * @version [1.0.0] 36 | * @since 2021/4/9 37 | */ 38 | public class Constant { 39 | 40 | //上传标识前缀 41 | private final static String UPLOADING = "Uploading:"; 42 | //全部上传成功的文件_map(方便取单条) 43 | public final static String COMPLETED_MAP = UPLOADING + "completedMap"; 44 | //正在上传的文件 45 | public final static String FILE_INFO = UPLOADING + "fileInfo"; 46 | 47 | //上传锁 48 | private final static String LOCK = "Lock:"; 49 | //整体文件锁 50 | public final static String FILE_LOCK = LOCK + "fileLock"; 51 | 52 | //异常信息 53 | public final static String THROWABLE = "Throwable:"; 54 | //异常序列 55 | public final static String EXCEPTION_ID = THROWABLE + "exceptionId"; 56 | 57 | private Constant() { 58 | 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/com/gomyck/fastdfs/starter/common/DownloadFileNumException.java: -------------------------------------------------------------------------------- 1 | package com.gomyck.fastdfs.starter.common; 2 | 3 | /** 4 | * 下载数量异常 5 | * 6 | * @author gomyck 7 | * -------------------------------- 8 | * | qq: 474798383 | 9 | * | email: hao474798383@163.com | 10 | * | blog: https://blog.gomyck.com | 11 | * -------------------------------- 12 | * @version [1.0.0] 13 | * @since 2021/4/9 14 | */ 15 | public class DownloadFileNumException extends RuntimeException { 16 | 17 | //下载数量异常 18 | public DownloadFileNumException(String message) { 19 | super(message); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/gomyck/fastdfs/starter/common/FDFSUtil.java: -------------------------------------------------------------------------------- 1 | package com.gomyck.fastdfs.starter.common; 2 | 3 | import com.github.tobato.fastdfs.domain.fdfs.FileInfo; 4 | import com.github.tobato.fastdfs.service.FastFileStorageClient; 5 | import com.gomyck.fastdfs.starter.database.UploadService; 6 | import com.gomyck.fastdfs.starter.database.entity.CkFileInfo; 7 | import com.gomyck.util.CkFile; 8 | import com.gomyck.util.CkId; 9 | import lombok.extern.slf4j.Slf4j; 10 | 11 | import java.io.IOException; 12 | import java.util.zip.ZipEntry; 13 | import java.util.zip.ZipOutputStream; 14 | 15 | /** 16 | * 文件存储工具类 17 | * 18 | * @author gomyck 19 | * -------------------------------- 20 | * | qq: 474798383 | 21 | * | email: hao474798383@163.com | 22 | * | blog: https://blog.gomyck.com | 23 | * -------------------------------- 24 | * @version [1.0.0] 25 | * @since 2021/4/9 26 | */ 27 | @Slf4j 28 | public class FDFSUtil { 29 | 30 | /** 31 | * 获取文件信息 32 | * @param us 上传 service 33 | * @param fileMd5 文件摘要 34 | * @return 文件信息 35 | */ 36 | public static CkFileInfo getFileInfo(UploadService us, String fileMd5) { 37 | CkFileInfo fileInfo = us.getFileByMessageDigest(fileMd5); 38 | if (fileInfo == null) { 39 | log.error("从数据库查询文件信息出错, 文件MD5: {}", fileMd5); 40 | throw new FileNotFoundException("数据列表中不存在该文件"); 41 | } 42 | return fileInfo; 43 | } 44 | 45 | public static FileInfo getFileInfoRemote(FastFileStorageClient ffsc, CkFileInfo fileInfo) { 46 | FileInfo remoteFileInfo; 47 | try { 48 | remoteFileInfo = ffsc.queryFileInfo(fileInfo.getGroup(), fileInfo.getUploadPath()); 49 | if (remoteFileInfo == null) throw new FileNotFoundException("文件服务器中不存在该文件"); 50 | } catch (Exception e) { 51 | log.error("从文件服务器查询文件信息出错, 分组: {}, 路径: {}", fileInfo.getGroup(), fileInfo.getUploadPath()); 52 | throw new FileNotFoundException("文件服务器中不存在该文件"); 53 | } 54 | return remoteFileInfo; 55 | } 56 | 57 | // 处理重名 58 | public static void resolveDuplicate(ZipOutputStream zos, String zipName, ZipEntry zipEntry) throws IOException { 59 | try { 60 | zos.putNextEntry(new ZipEntry(zipEntry)); 61 | } catch (Exception e) { 62 | zipEntry = new ZipEntry(CkFile.getFileNameAndSuffix(zipName)[0] + "(" + CkId.getUUID() + ")" + "." + CkFile.getFileNameAndSuffix(zipName)[1]); 63 | zos.putNextEntry(new ZipEntry(zipEntry)); 64 | } 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/com/gomyck/fastdfs/starter/common/FileNotFoundException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 gomyck 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | package com.gomyck.fastdfs.starter.common; 23 | 24 | /** 25 | * 文件未找到异常 26 | * 27 | * @author gomyck 28 | * -------------------------------- 29 | * | qq: 474798383 | 30 | * | email: hao474798383@163.com | 31 | * | blog: https://blog.gomyck.com | 32 | * -------------------------------- 33 | * @version [1.0.0] 34 | * @since 2021/4/12 35 | */ 36 | public class FileNotFoundException extends RuntimeException { 37 | 38 | /** 39 | * 文件未找到异常 40 | * 41 | * @param message 异常信息 42 | */ 43 | public FileNotFoundException(String message) { 44 | super(message); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/gomyck/fastdfs/starter/common/IllegalParameterException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 gomyck 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | package com.gomyck.fastdfs.starter.common; 23 | 24 | /** 25 | * 非法参数异常 26 | * 27 | * @author gomyck 28 | * -------------------------------- 29 | * | qq: 474798383 | 30 | * | email: hao474798383@163.com | 31 | * | blog: https://blog.gomyck.com | 32 | * -------------------------------- 33 | * @version [1.0.0] 34 | * @since 2021/4/12 35 | */ 36 | public class IllegalParameterException extends RuntimeException { 37 | 38 | public IllegalParameterException(String message) { 39 | super(message); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/gomyck/fastdfs/starter/controller/ChunkDownloadHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 gomyck 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | package com.gomyck.fastdfs.starter.controller; 24 | 25 | import com.github.tobato.fastdfs.domain.fdfs.FileInfo; 26 | import com.github.tobato.fastdfs.domain.proto.storage.DownloadByteArray; 27 | import com.github.tobato.fastdfs.service.FastFileStorageClient; 28 | import com.gomyck.fastdfs.starter.common.DownloadFileNumException; 29 | import com.gomyck.fastdfs.starter.common.FDFSUtil; 30 | import com.gomyck.fastdfs.starter.common.IllegalParameterException; 31 | import com.gomyck.fastdfs.starter.database.UploadService; 32 | import com.gomyck.fastdfs.starter.database.entity.BatchDownLoadParameter; 33 | import com.gomyck.fastdfs.starter.database.entity.CkFileInfo; 34 | import com.gomyck.fastdfs.starter.profile.FileServerProfile; 35 | import com.gomyck.util.CkContentType; 36 | import com.gomyck.util.CkFile; 37 | import com.gomyck.util.ObjectJudge; 38 | import com.gomyck.util.log.logger.CkLogger; 39 | import com.gomyck.util.servlet.ResponseWriter; 40 | import lombok.extern.slf4j.Slf4j; 41 | import org.springframework.beans.factory.annotation.Autowired; 42 | import org.springframework.stereotype.Controller; 43 | import org.springframework.web.bind.WebDataBinder; 44 | import org.springframework.web.bind.annotation.*; 45 | 46 | import javax.servlet.ServletOutputStream; 47 | import javax.servlet.http.HttpServletResponse; 48 | import java.util.ArrayList; 49 | import java.util.stream.Stream; 50 | import java.util.zip.ZipEntry; 51 | import java.util.zip.ZipOutputStream; 52 | 53 | /** 54 | * 分块下载控制器, 可以分块下载文件, 可以控制下载带宽 55 | * 56 | * @author gomyck 57 | * -------------------------------- 58 | * | qq: 474798383 | 59 | * | email: hao474798383@163.com | 60 | * | blog: https://blog.gomyck.com | 61 | * -------------------------------- 62 | * @version [1.0.0] 63 | * @since 2021/4/12 64 | */ 65 | @Slf4j 66 | @Controller 67 | @RequestMapping("download/chunkDownload") 68 | public class ChunkDownloadHandler { 69 | 70 | @InitBinder 71 | public void initBinder(WebDataBinder binder) { 72 | binder.setAutoGrowCollectionLimit(1000); 73 | } 74 | 75 | @Autowired 76 | FastFileStorageClient ffsc; 77 | 78 | @Autowired 79 | UploadService us; 80 | 81 | @Autowired 82 | FileServerProfile profile; 83 | 84 | private static final String THUMB_FLAG_TRUE = "1"; 85 | 86 | /** 87 | * 文件下载 如果不使用当前requestMapping作为下载入口, 请在业务代码中, 注入该类实例, 调用本方法即可 88 | * 89 | * @param fileMd5 文件摘要信息 90 | * @param fileName 下载文件名 非必传 91 | * @param thumbFlag 是否是下载略缩图 非必传 1为下载略缩图, 如果略缩图没有, 就返回原图 (仅图片有此选项, 业务侧使用时自行判断) 92 | * 93 | */ 94 | @GetMapping("downloadFile") 95 | @ResponseBody 96 | public void chunkDownload(String fileMd5, String fileName, String thumbFlag) { 97 | CkFileInfo fileInfo = FDFSUtil.getFileInfo(us, fileMd5); 98 | // 如果自定义文件名 则替换 99 | if(ObjectJudge.notNull(fileName)) fileInfo.setName(CkFile.getFileNameAndSuffix(fileName)[0] + "." + CkFile.getFileNameAndSuffix(fileInfo.getName())[1]); 100 | // 如果是下载略缩图 则替换下载路径 101 | if(ObjectJudge.notNull(thumbFlag, fileInfo.getThumbImgPath()) && thumbFlag.equals(THUMB_FLAG_TRUE)) fileInfo.setUploadPath(fileInfo.getThumbImgPath()); 102 | FileInfo remoteFileInfo = FDFSUtil.getFileInfoRemote(ffsc, fileInfo); 103 | long cycle = 0L; //下载次数 104 | long offset = 0L; //当前偏移量 105 | long downloadFileSize = profile.getDownloadChunkSize(); //当前实际要下载的块大小 106 | long remoteFileSize = remoteFileInfo.getFileSize(); //文件服务器存储的文件大小 (byte为单位) 107 | DownloadByteArray callback = new DownloadByteArray(); 108 | // 如果文件大小 小于分块大小, 一次性下载 109 | if (remoteFileSize <= profile.getDownloadChunkSize()) { 110 | byte[] content = ffsc.downloadFile(fileInfo.getGroup(), fileInfo.getUploadPath(), 0, remoteFileSize, callback); 111 | ResponseWriter.writeFile(content, fileInfo.getName(), fileInfo.getType(), true); 112 | return; 113 | } 114 | for (; ; cycle = cycle + 1L) { 115 | if ((cycle + 1) * profile.getDownloadChunkSize() < remoteFileSize) { 116 | byte[] content = ffsc.downloadFile(fileInfo.getGroup(), fileInfo.getUploadPath(), offset, downloadFileSize, callback); 117 | boolean ifDownload = ResponseWriter.writeFile(content, fileInfo.getName(), fileInfo.getType(), false); 118 | if (!ifDownload) return; 119 | } else { 120 | downloadFileSize = remoteFileSize - (cycle * profile.getDownloadChunkSize()); 121 | byte[] content = ffsc.downloadFile(fileInfo.getGroup(), fileInfo.getUploadPath(), offset, downloadFileSize, callback); 122 | ResponseWriter.writeFile(content, fileInfo.getName(), fileInfo.getType(), true); 123 | return; 124 | } 125 | offset = offset + profile.getDownloadChunkSize(); //偏移量改变 126 | } 127 | 128 | } 129 | 130 | 131 | /** 132 | * 文件批量下载 133 | * 134 | * 如果不使用当前requestMapping作为下载入口, 请在业务代码中, 注入该类实例, 调用本方法即可 135 | * 136 | * @param fileMd5s 文件摘要集合, 使用逗号分隔即可注入 137 | * 138 | */ 139 | @GetMapping("batchDownloadFile") 140 | @ResponseBody 141 | public void chunkDownload4Batch(String[] fileMd5s) { 142 | if (fileMd5s.length > profile.getMaxDownloadFileNum()) throw new DownloadFileNumException("下载文件数量过多"); 143 | BatchDownLoadParameter bdlp = new BatchDownLoadParameter(); 144 | ArrayList list = new ArrayList<>(); 145 | Stream.of(fileMd5s).forEach(e -> { 146 | BatchDownLoadParameter.FileBatchDownload bdl = new BatchDownLoadParameter.FileBatchDownload(); 147 | bdl.setFileMd5(e); 148 | list.add(bdl); 149 | }); 150 | bdlp.setFiles(list); 151 | batchDownloadFileHasGroup(bdlp); 152 | } 153 | 154 | 155 | /** 156 | * 文件批量下载, 增加了在压缩包中的存储结构, 具体请看入参实体 157 | * 158 | * 如果不使用当前requestMapping作为下载入口, 请在业务代码中, 注入该类实例, 调用本方法即可 159 | * 160 | * @param downloadInfo 附加了目录信息 161 | * fileMd5: 文件摘要 162 | * zipSrc: 文件在压缩包中的路径 exp: /demo/xxx/gomyck/ 前后的 / 不可少 163 | * fileName: 文件名称, 如果为空, 则取文件服务器内的文件名 164 | */ 165 | @PostMapping("batchDownloadFileHasGroup") 166 | @ResponseBody 167 | public void batchDownloadFileHasGroup(BatchDownLoadParameter downloadInfo) { 168 | if (downloadInfo == null || downloadInfo.getFiles().size() < 1) throw new IllegalParameterException("非法的参数, 下载文件数量必须大于 1"); 169 | if (downloadInfo.getFiles().size() > profile.getMaxDownloadFileNum()) throw new DownloadFileNumException("下载文件数量过多"); 170 | HttpServletResponse response = ResponseWriter.getResponse(); 171 | try (ServletOutputStream outputStream = response.getOutputStream(); 172 | ZipOutputStream zos = new ZipOutputStream(outputStream)){ 173 | response.setHeader("Content-Disposition", "attachment; filename=\"" + ResponseWriter.fileNameWrapper(downloadInfo.getZipFileName() + ".zip\"")); 174 | response.setContentType(CkContentType.ZIP.getTypeValue()); 175 | 176 | for (BatchDownLoadParameter.FileBatchDownload bdl : downloadInfo.getFiles()) { 177 | DownloadByteArray callback = new DownloadByteArray(); 178 | CkFileInfo fileInfo = us.getFileByMessageDigest(bdl.getFileMd5()); 179 | if (fileInfo == null) { 180 | log.error("从数据库查询文件信息出错, 文件MD5: {}", bdl.getFileMd5()); 181 | continue; 182 | } 183 | FileInfo remoteFileInfo; 184 | try { 185 | remoteFileInfo = ffsc.queryFileInfo(fileInfo.getGroup(), fileInfo.getUploadPath()); 186 | if (remoteFileInfo == null) continue; 187 | } catch (Exception e) { 188 | log.error("从文件服务器查询文件信息出错, 分组: {}, 路径: {}", fileInfo.getGroup(), fileInfo.getUploadPath()); 189 | continue; 190 | } 191 | long cycle = 0L; //下载次数 192 | long offset = 0L; //当前偏移量 193 | long downloadFileSize = profile.getDownloadChunkSize(); //当前实际要下载的块大小 194 | long remoteFileSize = remoteFileInfo.getFileSize(); //文件服务器存储的文件大小 (byte为单位) 195 | // 如果文件大小 小于分块大小, 一次性下载 196 | String zipName = bdl.getZipSrc() + (ObjectJudge.isNull(bdl.getFileName()) ? fileInfo.getName() : bdl.getFileName()); 197 | ZipEntry zipEntry = new ZipEntry(zipName); 198 | if (remoteFileSize <= profile.getDownloadChunkSize()) { 199 | byte[] content = ffsc.downloadFile(fileInfo.getGroup(), fileInfo.getUploadPath(), 0, remoteFileSize, callback); 200 | FDFSUtil.resolveDuplicate(zos, zipName, zipEntry); 201 | zos.write(content); 202 | zos.flush(); 203 | zos.closeEntry(); 204 | continue; 205 | } 206 | FDFSUtil.resolveDuplicate(zos, zipName, zipEntry); 207 | for (; ; cycle = cycle + 1L) { 208 | if ((cycle + 1) * profile.getDownloadChunkSize() < remoteFileSize) { 209 | byte[] content = ffsc.downloadFile(fileInfo.getGroup(), fileInfo.getUploadPath(), offset, downloadFileSize, callback); 210 | zos.write(content); 211 | zos.flush(); 212 | } else { 213 | downloadFileSize = remoteFileSize - (cycle * profile.getDownloadChunkSize()); 214 | byte[] content = ffsc.downloadFile(fileInfo.getGroup(), fileInfo.getUploadPath(), offset, downloadFileSize, callback); 215 | zos.write(content); 216 | zos.flush(); 217 | zos.closeEntry(); 218 | break; 219 | } 220 | offset = offset + profile.getDownloadChunkSize(); //偏移量改变 221 | } 222 | } 223 | zos.finish(); 224 | } catch (Exception e) { 225 | log.error(CkLogger.getTrace(e)); 226 | throw new RuntimeException(e); 227 | } 228 | 229 | } 230 | 231 | } 232 | -------------------------------------------------------------------------------- /src/main/java/com/gomyck/fastdfs/starter/controller/ChunkUploadHandler.java: -------------------------------------------------------------------------------- 1 | 2 | 3 | /* 4 | * Copyright (c) 2019 gomyck 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.gomyck.fastdfs.starter.controller; 26 | 27 | import com.github.tobato.fastdfs.domain.fdfs.StorePath; 28 | import com.github.tobato.fastdfs.domain.proto.storage.DownloadByteArray; 29 | import com.github.tobato.fastdfs.domain.upload.FastImageFile; 30 | import com.github.tobato.fastdfs.service.AppendFileStorageClient; 31 | import com.github.tobato.fastdfs.service.FastFileStorageClient; 32 | import com.gomyck.fastdfs.starter.common.Constant; 33 | import com.gomyck.fastdfs.starter.database.ServiceCheck; 34 | import com.gomyck.fastdfs.starter.database.UploadService; 35 | import com.gomyck.fastdfs.starter.database.entity.CkFileInfo; 36 | import com.gomyck.fastdfs.starter.lock.FileLock; 37 | import com.gomyck.fastdfs.starter.profile.FileServerProfile; 38 | import com.gomyck.util.CkDate; 39 | import com.gomyck.util.CkFile; 40 | import com.gomyck.util.CkParam; 41 | import com.gomyck.util.ObjectJudge; 42 | import com.gomyck.util.log.logger.CkLogger; 43 | import com.gomyck.util.servlet.R; 44 | import com.gomyck.util.spring.CkBean; 45 | import lombok.extern.slf4j.Slf4j; 46 | import org.springframework.beans.factory.annotation.Autowired; 47 | import org.springframework.beans.factory.annotation.Value; 48 | import org.springframework.stereotype.Controller; 49 | import org.springframework.web.bind.annotation.GetMapping; 50 | import org.springframework.web.bind.annotation.PostMapping; 51 | import org.springframework.web.bind.annotation.RequestMapping; 52 | import org.springframework.web.bind.annotation.ResponseBody; 53 | import org.springframework.web.multipart.MultipartFile; 54 | import org.springframework.web.multipart.MultipartHttpServletRequest; 55 | 56 | import javax.servlet.http.HttpServletRequest; 57 | import java.io.ByteArrayInputStream; 58 | import java.util.List; 59 | import java.util.Map; 60 | 61 | /** 62 | * 分块上传控制器 63 | * 64 | * @author gomyck 65 | * -------------------------------- 66 | * | qq: 474798383 | 67 | * | email: hao474798383@163.com | 68 | * | blog: https://blog.gomyck.com | 69 | * -------------------------------- 70 | * @version [1.0.0] 71 | * @since 2021/5/13 72 | */ 73 | @Slf4j 74 | @Controller 75 | @RequestMapping("/upload/chunkUpload") 76 | public class ChunkUploadHandler { 77 | 78 | @Autowired 79 | private AppendFileStorageClient appendFileStorageClient; 80 | 81 | @Autowired 82 | private FastFileStorageClient simpleFileDownloadHandler; 83 | 84 | @Value("${spring.servlet.multipart.max-file-size: 1MB}") 85 | private String maxSize; 86 | 87 | @Autowired 88 | FileServerProfile fsp; 89 | 90 | @Autowired(required = false) 91 | UploadService us; 92 | 93 | @Autowired 94 | FileLock fl; 95 | 96 | private final static String FILE_PARAM_NAME = "file"; 97 | 98 | //获取配置 99 | @GetMapping("/config") 100 | @ResponseBody 101 | public R config() { 102 | Map stringObjectMap = CkParam.initParams(); 103 | stringObjectMap.put("maxFileSize", Long.parseLong(maxSize.replace("MB", "")) * 1024 * 1024); 104 | stringObjectMap.put("chunkSize", fsp.getChunkSize()); 105 | stringObjectMap.put("fileServerUrl", fsp.getFileServerURI()); 106 | return R.ok(stringObjectMap); 107 | } 108 | 109 | /** 110 | * 上传文件 111 | * @param fileInfo 文件信息 112 | * @param request 请求 113 | * @return 上传结果 114 | */ 115 | @PostMapping("/uploadFile") 116 | @ResponseBody 117 | public R uploadFile(CkFileInfo fileInfo, HttpServletRequest request) { 118 | ServiceCheck.uploadServiceCheck(us); 119 | R checkInfo = this.checkFile(fileInfo.getFileMd5()); 120 | if(!checkInfo.isOk() || R._302 == checkInfo.getResCode()) return checkInfo; 121 | boolean ifHasLock = false; 122 | String fileLock = Constant.FILE_LOCK + fileInfo.getFileMd5(); 123 | if (ObjectJudge.isNull(fileInfo.getChunk())) fileInfo.setChunk("0"); 124 | if (ObjectJudge.isNull(fileInfo.getChunks())) fileInfo.setChunks("0"); 125 | // 查询历史文件, 这个一般来说第一次查是没有的, 但是第二次续传一定有, 所以整体流程以这个对象操作为主 126 | CkFileInfo historyFileInfo = us.getFileUploadStatus(fileInfo.getFileMd5()); 127 | //设置文件分组 128 | if(historyFileInfo != null){ //如果历史文件不为空, 把当前的分组设置为原来的分组, 防止前端传过来的分组与历史不符 129 | fileInfo.setGroup(historyFileInfo.getGroup()); 130 | }else{ 131 | if(ObjectJudge.isNull(fileInfo.getGroup())){ //如果前端传过来的分组是空, 则设置配置的分组 132 | fileInfo.setGroup(fsp.getGroupId()); 133 | } 134 | } 135 | try { 136 | if(!fl.addLock(fileLock)){ 137 | return R.error(R._500, "当前文件正在被上传"); 138 | }else{ 139 | ifHasLock = true; 140 | } 141 | List files = ((MultipartHttpServletRequest) request).getFiles(FILE_PARAM_NAME); 142 | int hasUploadChunk = 0; //查询当前文件存储到第几块了 143 | if(historyFileInfo != null){ 144 | String chunk = historyFileInfo.getChunk(); 145 | if(ObjectJudge.notNull(chunk)){ 146 | hasUploadChunk = Integer.parseInt(chunk); 147 | } 148 | }else{ 149 | historyFileInfo = new CkFileInfo(); 150 | } 151 | int currentChunk = Integer.parseInt(fileInfo.getChunk()); 152 | if (currentChunk < hasUploadChunk) { 153 | return R.error(R._500, "当前文件块已上传, 请重试"); 154 | } else if (currentChunk > (hasUploadChunk + 1)) { 155 | return R.error(R._500, "非法的文件块, 请重试"); 156 | } 157 | StorePath path; 158 | for (final MultipartFile file : files) { 159 | if (file.isEmpty()) { 160 | continue; 161 | } 162 | try { 163 | if (currentChunk == 0) { 164 | try { 165 | path = appendFileStorageClient.uploadAppenderFile(fileInfo.getGroup(), file.getInputStream(), file.getSize(), CkFile.getFileSuffixNameByFileName(fileInfo.getName())); 166 | if (path == null) { 167 | return R.error(R._500, "文件服务器未返回存储路径, 请联系管理员"); 168 | } 169 | } catch (Exception e) { 170 | log.error(CkLogger.getTrace(e)); 171 | return R.error(R._500, "上传文件服务器文件出错" + e.getMessage()); 172 | } 173 | fileInfo.setUploadTime(CkDate.now2Str(CkDate.CN_DATETIME_FORMAT_1)); 174 | fileInfo.setGroup(fileInfo.getGroup()); 175 | fileInfo.setUploadPath(path.getPath()); 176 | } else { 177 | historyFileInfo = us.getFileUploadStatus(fileInfo.getFileMd5()); 178 | try { 179 | //appendFileStorageClient.modifyFile(fileUploadStatus.getGroup(), fileUploadStatus.getUploadPath(), file.getInputStream(), file.getSize(), (hasUploadChunk + 1) * fileInfo.getChunkSize()); 180 | // 修复丢失字节的 BUG 181 | appendFileStorageClient.appendFile(historyFileInfo.getGroup(), historyFileInfo.getUploadPath(), file.getInputStream(), file.getSize()); 182 | } catch (Exception e) { 183 | return R.error(R._500, "续传文件出错" + e.getMessage()); 184 | } 185 | } 186 | CkBean.copyProperties(fileInfo, historyFileInfo); //把本次传入的参数copy到历史数据中, 然后更新 187 | us.saveFileUploadStatus(historyFileInfo); 188 | int allChunks = Integer.parseInt(fileInfo.getChunks()); 189 | if ((currentChunk + 1) == allChunks || allChunks == 0) { 190 | historyFileInfo.setUploadTime(CkDate.now2Str(CkDate.CN_DATETIME_FORMAT_1)); 191 | generateThumbImg(historyFileInfo); 192 | us.saveUploadInfo(historyFileInfo); 193 | us.delFileUploadStatus(historyFileInfo.getFileMd5()); 194 | } 195 | } catch (Exception e) { 196 | return R.error(R._500, "上传错误: " + e.getMessage()); 197 | } 198 | break; 199 | } 200 | } finally { 201 | if (ifHasLock) fl.delLock(fileLock); 202 | } 203 | return R.ok(historyFileInfo); 204 | } 205 | 206 | /** 207 | * 生成缩略图 208 | * 209 | * @param historyFileInfo 上传的文件 210 | */ 211 | private void generateThumbImg(CkFileInfo historyFileInfo) { 212 | if(historyFileInfo.isThumbFlag() && historyFileInfo.getType().contains("image")){ 213 | try{ 214 | DownloadByteArray callback = new DownloadByteArray(); 215 | // 这里要下载一下, 因为主流程为断点续传, 图片与略缩图上传不支持断点续传, 所以要全部传完之后, 统一上传 216 | byte[] bytes = simpleFileDownloadHandler.downloadFile(historyFileInfo.getGroup(), historyFileInfo.getUploadPath(), callback); 217 | ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes); 218 | FastImageFile.Builder builder = new FastImageFile.Builder(); 219 | builder.toGroup(historyFileInfo.getGroup()); 220 | builder.withFile(byteArrayInputStream, historyFileInfo.getSize(), CkFile.getFileSuffixNameByFileName(historyFileInfo.getName())); 221 | if(ObjectJudge.notNull(historyFileInfo.getThumbImgPercent())) { 222 | builder.withThumbImage(historyFileInfo.getThumbImgPercent()); 223 | } else if(ObjectJudge.notNull(historyFileInfo.getThumbImgWidth(), historyFileInfo.getThumbImgHeight())) { 224 | builder.withThumbImage(historyFileInfo.getThumbImgWidth(), historyFileInfo.getThumbImgHeight()); 225 | } else { 226 | builder.withThumbImage(); 227 | } 228 | FastImageFile imgFile = builder.build(); 229 | builder.withFile(byteArrayInputStream, historyFileInfo.getSize(), CkFile.getFileSuffixNameByFileName(historyFileInfo.getName())); 230 | StorePath storePath = simpleFileDownloadHandler.uploadImage(imgFile); 231 | simpleFileDownloadHandler.deleteFile(storePath.getGroup(), storePath.getPath()); // 删除略缩图的原图 232 | historyFileInfo.setThumbImgPath(imgFile.getThumbImagePath(storePath.getPath())); 233 | log.info("thumb img path is: {}", historyFileInfo.getThumbImgPath()); 234 | }catch (Exception imgError){ 235 | // 一般来说, 图片格式不受支持则会报错 236 | log.error(imgError.toString()); 237 | } 238 | } 239 | } 240 | 241 | 242 | /** 243 | * 检查文件 244 | * @param fileMd5 文件 MD5 245 | * @return 上传信息 246 | */ 247 | @PostMapping("/checkFile") 248 | @ResponseBody 249 | public R checkFile(String fileMd5) { 250 | ServiceCheck.uploadServiceCheck(us); 251 | if (ObjectJudge.isNull(fileMd5)) return R.error(R._500, "fileMd5不能为空"); 252 | String fileLock = Constant.FILE_LOCK + fileMd5; 253 | if(fl.ifLock(fileLock)){ 254 | return R.error(R._500, "当前文件正在被上传, 请稍后再试"); 255 | } 256 | CkFileInfo fileByMessageDigest = us.getFileByMessageDigest(fileMd5); 257 | if (fileByMessageDigest != null) { 258 | return R.ok(R._302, fileByMessageDigest); 259 | } 260 | CkFileInfo fileUploadStatus = us.getFileUploadStatus(fileMd5); 261 | if (fileUploadStatus != null && ObjectJudge.notNull(fileUploadStatus.getChunk())) { 262 | return R.ok(fileUploadStatus); 263 | } else { 264 | fileUploadStatus = new CkFileInfo(); 265 | fileUploadStatus.setChunk("0"); 266 | return R.ok(fileUploadStatus); 267 | } 268 | 269 | } 270 | } 271 | -------------------------------------------------------------------------------- /src/main/java/com/gomyck/fastdfs/starter/controller/CkErrorController.java: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * * 4 | * * * Copyright (c) 2019 Gomyck 5 | * * * 6 | * * * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * * * of this software and associated documentation files (the "Software"), to deal 8 | * * * in the Software without restriction, including without limitation the rights 9 | * * * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * * * copies of the Software, and to permit persons to whom the Software is 11 | * * * furnished to do so, subject to the following conditions: 12 | * * * 13 | * * * The above copyright notice and this permission notice shall be included in all 14 | * * * copies or substantial portions of the Software. 15 | * * * 16 | * * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * * * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * * * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * * * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * * * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * * * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * * * SOFTWARE. 23 | * * 24 | * 25 | */ 26 | 27 | package com.gomyck.fastdfs.starter.controller; 28 | 29 | 30 | import com.gomyck.cache.redis.starter.core.redis.RedisCache; 31 | import com.gomyck.cache.redis.starter.core.redis.annotation.RedisManager; 32 | import com.gomyck.fastdfs.starter.common.Constant; 33 | import com.gomyck.util.servlet.R; 34 | import org.springframework.beans.factory.annotation.Autowired; 35 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 36 | import org.springframework.stereotype.Controller; 37 | import org.springframework.web.bind.annotation.GetMapping; 38 | import org.springframework.web.bind.annotation.ResponseBody; 39 | 40 | /** 41 | * 错误页跳转 handler 42 | * 43 | * @author gomyck 44 | * -------------------------------- 45 | * | qq: 474798383 | 46 | * | email: hao474798383@163.com | 47 | * | blog: https://blog.gomyck.com | 48 | * -------------------------------- 49 | * @version [1.0.0] 50 | * @since 2021/5/13 51 | */ 52 | @Controller 53 | @ConditionalOnProperty(value = "gomyck.fastdfs.enable-error-advice", havingValue = "true") 54 | public class CkErrorController { 55 | 56 | @Autowired 57 | private RedisCache rc; 58 | 59 | 60 | /** 61 | * 返回错误 62 | * 63 | * @param uid uid 64 | * @return R 65 | */ 66 | @GetMapping("getThrowableInfo") 67 | @ResponseBody 68 | @RedisManager 69 | public R getThrowableInfo(String uid){ 70 | String s = rc.get(Constant.EXCEPTION_ID + uid); 71 | rc.delKey(Constant.EXCEPTION_ID + uid); 72 | return R.ok(R._200, s); 73 | } 74 | 75 | 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/com/gomyck/fastdfs/starter/controller/CkExceptionAdviceController.java: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * * 4 | * * * Copyright (c) 2019 Gomyck 5 | * * * 6 | * * * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * * * of this software and associated documentation files (the "Software"), to deal 8 | * * * in the Software without restriction, including without limitation the rights 9 | * * * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * * * copies of the Software, and to permit persons to whom the Software is 11 | * * * furnished to do so, subject to the following conditions: 12 | * * * 13 | * * * The above copyright notice and this permission notice shall be included in all 14 | * * * copies or substantial portions of the Software. 15 | * * * 16 | * * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * * * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * * * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * * * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * * * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * * * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * * * SOFTWARE. 23 | * * 24 | * 25 | */ 26 | 27 | package com.gomyck.fastdfs.starter.controller; 28 | 29 | 30 | import com.gomyck.cache.redis.starter.core.redis.RedisCache; 31 | import com.gomyck.cache.redis.starter.core.redis.annotation.RedisManager; 32 | import com.gomyck.fastdfs.starter.common.Constant; 33 | import com.gomyck.fastdfs.starter.profile.FileServerProfile; 34 | import com.gomyck.util.CkId; 35 | import com.gomyck.util.DataFilter; 36 | import org.slf4j.Logger; 37 | import org.slf4j.LoggerFactory; 38 | import org.springframework.beans.factory.annotation.Autowired; 39 | import org.springframework.beans.factory.annotation.Value; 40 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 41 | import org.springframework.web.bind.annotation.ControllerAdvice; 42 | import org.springframework.web.bind.annotation.ExceptionHandler; 43 | import org.springframework.web.servlet.view.UrlBasedViewResolver; 44 | 45 | import java.io.UnsupportedEncodingException; 46 | import java.net.URLEncoder; 47 | import java.nio.charset.StandardCharsets; 48 | 49 | /** 50 | * 异常处理 handler, 使用 redirect 来重定向请求, 因为存在 form 表单提交下载(批量) 51 | * 如果不重定向, 会导致 method not support 的错误 52 | * 53 | * @author gomyck 54 | * -------------------------------- 55 | * | qq: 474798383 | 56 | * | email: hao474798383@163.com | 57 | * | blog: https://blog.gomyck.com | 58 | * -------------------------------- 59 | * @version [1.0.0] 60 | * @since 2021/6/1 61 | */ 62 | @ControllerAdvice 63 | @ConditionalOnProperty(value = "gomyck.fastdfs.enable-error-advice", havingValue = "true") 64 | public class CkExceptionAdviceController { 65 | 66 | private static final Logger log = LoggerFactory.getLogger(CkExceptionAdviceController.class); 67 | 68 | @Autowired 69 | private FileServerProfile fsp; 70 | @Value("${server.servlet.context-path:/}") 71 | private String contextPath; 72 | @Autowired 73 | private RedisCache rc; 74 | 75 | /** 76 | * 默认的异常处理器 77 | * 78 | * @param ex 异常信息 79 | * @return 查看页面 80 | * @throws UnsupportedEncodingException 编码异常 81 | */ 82 | @ExceptionHandler(Exception.class) 83 | @RedisManager 84 | public String defaultExceptionHandler(Exception ex) throws UnsupportedEncodingException { 85 | log.error(ex.getMessage()); 86 | String uuid = CkId.getUUID(); 87 | String key = Constant.EXCEPTION_ID + uuid; 88 | rc.set(key, ex.getMessage()); 89 | rc.expireKeySeconds(key, 60); 90 | return UrlBasedViewResolver.REDIRECT_URL_PREFIX + DataFilter.getFirstNotNull(fsp.getErrorPageHostName(), contextPath) + "/ck-fastdfs/view/error.html?uid=" + uuid + "&host=" + URLEncoder.encode(DataFilter.getFirstNotNull(fsp.getErrorPageHostName(), contextPath).toString(), StandardCharsets.UTF_8.toString()); 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/com/gomyck/fastdfs/starter/controller/DoorChainHandler.java: -------------------------------------------------------------------------------- 1 | package com.gomyck.fastdfs.starter.controller; 2 | 3 | import com.gomyck.util.servlet.R; 4 | import org.springframework.web.bind.annotation.RequestMapping; 5 | import org.springframework.web.bind.annotation.RestController; 6 | 7 | /** 8 | * 单文件下载控制器 9 | * 10 | * @author gomyck 11 | * -------------------------------- 12 | * | qq: 474798383 | 13 | * | email: hao474798383@163.com | 14 | * | blog: https://blog.gomyck.com | 15 | * -------------------------------- 16 | * @version [1.0.0] 17 | * @since 2021/5/31 18 | */ 19 | @RestController 20 | @RequestMapping("download/simpleDownload") 21 | public class DoorChainHandler { 22 | 23 | public R getDoorChain(String[] md5s){ 24 | 25 | 26 | return R.ok(); 27 | } 28 | 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/gomyck/fastdfs/starter/controller/SimpleFileDownloadHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 gomyck 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | package com.gomyck.fastdfs.starter.controller; 24 | 25 | import com.github.tobato.fastdfs.domain.proto.storage.DownloadByteArray; 26 | import com.github.tobato.fastdfs.service.FastFileStorageClient; 27 | import com.gomyck.fastdfs.starter.common.DownloadFileNumException; 28 | import com.gomyck.fastdfs.starter.common.FDFSUtil; 29 | import com.gomyck.fastdfs.starter.common.IllegalParameterException; 30 | import com.gomyck.fastdfs.starter.database.UploadService; 31 | import com.gomyck.fastdfs.starter.database.entity.BatchDownLoadParameter; 32 | import com.gomyck.fastdfs.starter.database.entity.CkFileInfo; 33 | import com.gomyck.fastdfs.starter.profile.FileServerProfile; 34 | import com.gomyck.util.CkContentType; 35 | import com.gomyck.util.CkFile; 36 | import com.gomyck.util.ObjectJudge; 37 | import com.gomyck.util.log.logger.CkLogger; 38 | import com.gomyck.util.servlet.ResponseWriter; 39 | import lombok.extern.slf4j.Slf4j; 40 | import org.springframework.beans.factory.annotation.Autowired; 41 | import org.springframework.stereotype.Controller; 42 | import org.springframework.web.bind.WebDataBinder; 43 | import org.springframework.web.bind.annotation.GetMapping; 44 | import org.springframework.web.bind.annotation.InitBinder; 45 | import org.springframework.web.bind.annotation.RequestMapping; 46 | import org.springframework.web.bind.annotation.ResponseBody; 47 | 48 | import java.io.ByteArrayOutputStream; 49 | import java.util.ArrayList; 50 | import java.util.List; 51 | import java.util.stream.Stream; 52 | import java.util.zip.ZipEntry; 53 | import java.util.zip.ZipOutputStream; 54 | 55 | /** 56 | * 简单文件下载器 57 | * 58 | * @author gomyck QQ:474798383 59 | * @version [1.0] 60 | * @since [2019-07-30] 61 | */ 62 | @Slf4j 63 | @Controller 64 | @RequestMapping("download/simpleDownload") 65 | public class SimpleFileDownloadHandler { 66 | 67 | @InitBinder 68 | public void initBinder(WebDataBinder binder) { 69 | binder.setAutoGrowCollectionLimit(Integer.MAX_VALUE); 70 | } 71 | 72 | @Autowired 73 | FastFileStorageClient ffsc; 74 | 75 | @Autowired 76 | UploadService us; 77 | 78 | @Autowired 79 | FileServerProfile profile; 80 | 81 | /** 82 | * 如果不使用当前requestMapping作为下载入口, 请在业务代码中, 注入该类实例, 调用本方法即可 83 | * 84 | * @param fileMd5 文件摘要 85 | * @param fileName 文件名 86 | * @param thumbFlag 是否下载的是缩略图 87 | */ 88 | @GetMapping("downloadFile") 89 | @ResponseBody 90 | public void simpleDownload(String fileMd5, String fileName, String thumbFlag) { 91 | CkFileInfo fileInfo = FDFSUtil.getFileInfo(us, fileMd5); 92 | // 如果自定义文件名 则替换 93 | if(ObjectJudge.notNull(fileName)) fileInfo.setName(CkFile.getFileNameAndSuffix(fileName)[0] + "." + CkFile.getFileNameAndSuffix(fileInfo.getName())[1]); 94 | // 如果是下载略缩图 则替换下载路径 95 | if(ObjectJudge.notNull(thumbFlag, fileInfo.getThumbImgPath()) && thumbFlag.equals("1")) fileInfo.setUploadPath(fileInfo.getThumbImgPath()); 96 | DownloadByteArray callback = new DownloadByteArray(); 97 | byte[] content = ffsc.downloadFile(fileInfo.getGroup(), fileInfo.getUploadPath(), callback); 98 | ResponseWriter.writeFile(content, fileInfo.getName(), fileInfo.getType(), true); 99 | } 100 | 101 | /** 102 | * 文件批量下载 103 | * 104 | * 如果不使用当前requestMapping作为下载入口, 请在业务代码中, 注入该类实例, 调用本方法即可 105 | * 106 | * @param fileMd5s 文件摘要集合, 使用逗号分隔即可注入 107 | * 108 | */ 109 | @GetMapping("batchDownloadFile") 110 | @ResponseBody 111 | public void simpleBatchDownload(String[] fileMd5s) { 112 | if (fileMd5s.length > profile.getMaxDownloadFileNum()) throw new DownloadFileNumException("下载文件数量过多"); 113 | BatchDownLoadParameter bdlp = new BatchDownLoadParameter(); 114 | List list = new ArrayList<>(); 115 | Stream.of(fileMd5s).forEach(e -> { 116 | BatchDownLoadParameter.FileBatchDownload bdl = new BatchDownLoadParameter.FileBatchDownload(); 117 | bdl.setFileMd5(e); 118 | list.add(bdl); 119 | }); 120 | bdlp.setFiles(list); 121 | simpleBatchDownloadHasGroup(bdlp); 122 | } 123 | 124 | 125 | /** 126 | * 文件批量下载, 增加了在压缩包中的存储结构, 具体请看入参实体 127 | * 128 | * 如果不使用当前requestMapping作为下载入口, 请在业务代码中, 注入该类实例, 调用本方法即可 129 | * 130 | * @param downloadInfo 附加了目录信息 131 | * fileMd5: 文件摘要 132 | * zipSrc: 文件在压缩包中的路径 exp: /demo/xxx/gomyck/ 前后的 / 不可少 133 | * fileName: 文件名称, 如果为空, 则取文件服务器内的文件名 134 | */ 135 | @GetMapping("batchDownloadFileHasGroup") 136 | @ResponseBody 137 | public void simpleBatchDownloadHasGroup(BatchDownLoadParameter downloadInfo) { 138 | if (downloadInfo == null || downloadInfo.getFiles().size() < 1) throw new IllegalParameterException("非法的参数, 下载文件数量必须大于 1"); 139 | if (downloadInfo.getFiles().size() > profile.getMaxDownloadFileNum()) throw new DownloadFileNumException("下载文件数量过多"); 140 | try { 141 | ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 142 | ZipOutputStream zos = new ZipOutputStream(outputStream); 143 | for (BatchDownLoadParameter.FileBatchDownload bdl : downloadInfo.getFiles()) { 144 | CkFileInfo fileInfo = us.getFileByMessageDigest(bdl.getFileMd5()); 145 | if (fileInfo == null) continue; 146 | DownloadByteArray callback = new DownloadByteArray(); 147 | byte[] content = ffsc.downloadFile(fileInfo.getGroup(), fileInfo.getUploadPath(), callback); 148 | String zipName = bdl.getZipSrc() + (ObjectJudge.isNull(bdl.getFileName()) ? fileInfo.getName() : bdl.getFileName()); 149 | ZipEntry zipEntry = new ZipEntry(zipName); 150 | FDFSUtil.resolveDuplicate(zos, zipName, zipEntry); 151 | zos.write(content); 152 | zos.closeEntry(); 153 | } 154 | zos.finish(); 155 | zos.close(); 156 | byte[] bytes = outputStream.toByteArray(); 157 | ResponseWriter.writeFile(bytes, downloadInfo.getZipFileName() + ".zip", CkContentType.ZIP); 158 | } catch (Exception e) { 159 | log.error(CkLogger.getTrace(e)); 160 | throw new RuntimeException(e); 161 | } 162 | } 163 | 164 | } 165 | -------------------------------------------------------------------------------- /src/main/java/com/gomyck/fastdfs/starter/controller/UploadManageHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 gomyck 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | package com.gomyck.fastdfs.starter.controller; 23 | 24 | import com.github.tobato.fastdfs.exception.FdfsServerException; 25 | import com.github.tobato.fastdfs.service.AppendFileStorageClient; 26 | import com.gomyck.fastdfs.starter.database.ServiceCheck; 27 | import com.gomyck.fastdfs.starter.database.UploadService; 28 | import com.gomyck.fastdfs.starter.database.entity.CkFileInfo; 29 | import com.gomyck.fastdfs.starter.profile.FileServerProfile; 30 | import com.gomyck.util.CkPage; 31 | import com.gomyck.util.CkParam; 32 | import com.gomyck.util.ObjectJudge; 33 | import com.gomyck.util.log.logger.CkLogger; 34 | import com.gomyck.util.servlet.R; 35 | import lombok.extern.slf4j.Slf4j; 36 | import org.springframework.beans.factory.annotation.Autowired; 37 | import org.springframework.stereotype.Controller; 38 | import org.springframework.web.bind.annotation.PostMapping; 39 | import org.springframework.web.bind.annotation.RequestBody; 40 | import org.springframework.web.bind.annotation.RequestMapping; 41 | import org.springframework.web.bind.annotation.ResponseBody; 42 | 43 | import java.io.File; 44 | import java.util.Map; 45 | import java.util.stream.Stream; 46 | 47 | /** 48 | * 文件上传管理 49 | * 50 | * @author gomyck 51 | * -------------------------------- 52 | * | qq: 474798383 | 53 | * | email: hao474798383@163.com | 54 | * | blog: https://blog.gomyck.com | 55 | * -------------------------------- 56 | * @version [1.0.0] 57 | * @since 2021/6/22 58 | */ 59 | @Slf4j 60 | @Controller 61 | @RequestMapping("upload/manage") 62 | public class UploadManageHandler { 63 | 64 | @Autowired 65 | private AppendFileStorageClient storageClient; 66 | 67 | @Autowired 68 | FileServerProfile fsp; 69 | 70 | @Autowired(required = false) 71 | UploadService us; 72 | 73 | /** 74 | * 查询文件列表, 分页信息可以不传 75 | * 76 | * @param pageIndex list 起始位置 77 | * @param limit list 结束位置 78 | * 79 | * @return R 结果 80 | */ 81 | @RequestMapping("/list") 82 | @ResponseBody 83 | public R uploadListByPage(Long pageIndex, Long limit) { 84 | ServiceCheck.uploadServiceCheck(us); 85 | if(pageIndex == null) pageIndex = 1L; 86 | if(limit == null) limit = -1L; 87 | Map stringObjectMap = CkParam.initParams(); 88 | stringObjectMap.put("fileServerUrl", fsp.getFileServerURI()); 89 | stringObjectMap.put("fileList", us.selectCompleteFileInfo(CkPage.getStartOfPage(pageIndex, limit), CkPage.getEndOfPage(pageIndex, limit))); 90 | return R.ok(stringObjectMap); 91 | } 92 | 93 | /** 94 | * 删除文件 95 | * 96 | * @param fileMd5 MD5 97 | * @return R 是否成功 98 | */ 99 | @PostMapping("/delFile") 100 | @ResponseBody 101 | public R delFile(String fileMd5) { 102 | ServiceCheck.uploadServiceCheck(us); 103 | CkFileInfo fileInfo = us.getFileByMessageDigest(fileMd5); 104 | if (fileInfo == null || ObjectJudge.hasNull(fileInfo.getUploadPath())) { 105 | return R.error(R._500, "文件服务器不存在该文件"); 106 | } 107 | try{ 108 | storageClient.deleteFile(fileInfo.getGroup(), fileInfo.getUploadPath().replace(fileInfo.getGroup() + File.separator, "")); 109 | }catch (FdfsServerException e){ 110 | if(e.getErrorCode() != 2) { 111 | log.error(CkLogger.getTrace(e)); 112 | return R.error(R._500, e.getMessage()); 113 | } 114 | } 115 | us.delFile(fileInfo); 116 | return R.ok(); 117 | } 118 | 119 | /** 120 | * 批量删除文件 121 | * @param fileMd5s MD5 122 | * @return R 是否成功 123 | */ 124 | @PostMapping(value = "/batchDelFile") 125 | @ResponseBody 126 | public R batchDelFile(@RequestBody String fileMd5s) { 127 | ServiceCheck.uploadServiceCheck(us); 128 | Stream.of(fileMd5s.split(",")).forEach(fileMd5 -> { 129 | CkFileInfo fileInfo = us.getFileByMessageDigest(fileMd5); 130 | if(fileInfo == null) fileInfo = us.getFileUploadStatus(fileMd5); 131 | if(fileInfo == null) return; 132 | if(ObjectJudge.isNull(fileInfo.getUploadPath())) return; 133 | try{ 134 | storageClient.deleteFile(fileInfo.getGroup(), fileInfo.getUploadPath().replace(fileInfo.getGroup() + File.separator, "")); 135 | }catch (Exception e){ 136 | log.error(CkLogger.getTrace(e)); 137 | return; 138 | } 139 | us.delFile(fileInfo); 140 | us.delFileUploadStatus(fileMd5); 141 | }); 142 | return R.ok(); 143 | } 144 | 145 | /** 146 | * 删除文件过期标记, 使文件永久有效(配合客户端传的expireTime一起使用, 如果不传expireTime, 则不需要调用此方法) 147 | * 148 | * @param fileMd5s 文件 MD5 149 | * @return R 是否成功 150 | */ 151 | @PostMapping(value = "/delExpireStatus") 152 | @ResponseBody 153 | public R delExpireStatus(@RequestBody String fileMd5s) { 154 | StringBuffer noSuchMd5 = new StringBuffer(); 155 | Stream.of(fileMd5s.split(",")).forEach(fileMd5 -> { 156 | boolean completeStatus = false; 157 | CkFileInfo fileInfo = us.getFileByMessageDigest(fileMd5); 158 | if(fileInfo == null) { 159 | fileInfo = us.getFileUploadStatus(fileMd5); 160 | }else{ 161 | completeStatus = true; 162 | } 163 | if(fileInfo == null) { 164 | noSuchMd5.append("|").append(fileMd5).append("|"); 165 | return; 166 | } 167 | us.delExpireStatus(fileInfo, completeStatus); 168 | }); 169 | if(noSuchMd5.length() > 0){ 170 | return R.error(R._500, "下述文件不存在: " + noSuchMd5); 171 | }else { 172 | return R.ok(); 173 | } 174 | } 175 | 176 | } 177 | 178 | -------------------------------------------------------------------------------- /src/main/java/com/gomyck/fastdfs/starter/database/ServiceCheck.java: -------------------------------------------------------------------------------- 1 | package com.gomyck.fastdfs.starter.database; 2 | 3 | /** 4 | * 服务类检查 5 | * 6 | * @author gomyck 7 | * -------------------------------- 8 | * | qq: 474798383 | 9 | * | email: hao474798383@163.com | 10 | * | blog: https://blog.gomyck.com | 11 | * -------------------------------- 12 | * @version [1.0.0] 13 | * @since 2021/7/5 14 | */ 15 | public class ServiceCheck { 16 | 17 | /** 18 | * 上传文件检查 19 | * 20 | * @param us 上传服务 21 | */ 22 | public static void uploadServiceCheck(UploadService us){ 23 | if(us == null) { 24 | throw new RuntimeException("please add a service impl com.gomyck.fastdfs.starter.database.UploadService"); 25 | } 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/gomyck/fastdfs/starter/database/SimpleUploadService.java: -------------------------------------------------------------------------------- 1 | 2 | 3 | package com.gomyck.fastdfs.starter.database; 4 | 5 | import com.gomyck.cache.redis.starter.core.redis.RedisCache; 6 | import com.gomyck.fastdfs.starter.common.Constant; 7 | import com.gomyck.fastdfs.starter.database.entity.CkFileInfo; 8 | import com.gomyck.util.ObjectJudge; 9 | import com.gomyck.util.serialize.CKJSON; 10 | import com.gomyck.util.servlet.R; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | import java.util.Map; 16 | 17 | /** 18 | * 简单文件上传 service 19 | * 20 | * @author gomyck 21 | * -------------------------------- 22 | * | qq: 474798383 | 23 | * | email: hao474798383@163.com | 24 | * | blog: https://blog.gomyck.com | 25 | * -------------------------------- 26 | * @version [1.0.0] 27 | * @since 2021/6/28 28 | */ 29 | public class SimpleUploadService implements UploadService { 30 | 31 | @Autowired 32 | RedisCache rs; 33 | 34 | /** 35 | * 保存文件信息 36 | * 37 | * @param fileInfo 文件信息 38 | * 39 | * @return 是否成功 40 | */ 41 | @Override 42 | public R saveUploadInfo(CkFileInfo fileInfo) { 43 | //持久化上传完成文件,也可以存储在mysql中 44 | try { 45 | rs.startDoIt(); 46 | Boolean r = rs.oneTime(e -> { 47 | String _fileInfo = CKJSON.getInstance().toJsonString(fileInfo); 48 | e.hset(Constant.COMPLETED_MAP, fileInfo.getFileMd5(), _fileInfo); 49 | return true; 50 | }); 51 | return R.unKnow(r); 52 | } finally { 53 | rs.finishDoIt(); 54 | } 55 | } 56 | 57 | /** 58 | * 查询已完成文件信息 59 | * 60 | * @param start 开始位置 61 | * @param end 结束位置 62 | * 63 | * @return 文件列表 64 | */ 65 | @Override 66 | public List selectCompleteFileInfo(Long start, Long end) { 67 | Map fileList; 68 | try { 69 | rs.startDoIt(); 70 | fileList = rs.hGetAll(Constant.COMPLETED_MAP); 71 | } finally { 72 | rs.finishDoIt(); 73 | } 74 | List result = new ArrayList<>(); 75 | if(fileList == null) return null; 76 | for(String key : fileList.keySet()){ 77 | result.add(CKJSON.getInstance().parseObject(fileList.get(key), CkFileInfo.class)); 78 | } 79 | return result; 80 | } 81 | 82 | /** 83 | * 删除文件 84 | * 85 | * @param fileInfo 文件信息 86 | * 87 | * @return 是否成功 88 | */ 89 | @Override 90 | public R delFile(CkFileInfo fileInfo) { 91 | try { 92 | rs.startDoIt(); 93 | Boolean r = rs.oneTime(e -> { 94 | e.hdel(Constant.COMPLETED_MAP, fileInfo.getFileMd5()); 95 | return true; 96 | }); 97 | return R.unKnow(r); 98 | } finally { 99 | rs.finishDoIt(); 100 | } 101 | } 102 | 103 | @Override 104 | public R delExpireStatus(CkFileInfo messageDigest, boolean completeStatus) { 105 | try { 106 | rs.startDoIt(); 107 | Boolean r = rs.oneTime(e -> { 108 | if (completeStatus) { 109 | e.hdel(Constant.COMPLETED_MAP, messageDigest.getFileMd5()); 110 | messageDigest.setExpireTime(null); 111 | String _fileInfo = CKJSON.getInstance().toJsonString(messageDigest); 112 | e.hset(Constant.COMPLETED_MAP, messageDigest.getFileMd5(), _fileInfo); 113 | } else { 114 | e.del(Constant.FILE_INFO + messageDigest.getFileMd5()); 115 | messageDigest.setExpireTime(null); 116 | e.set(Constant.FILE_INFO + messageDigest.getFileMd5(), CKJSON.getInstance().toJsonString(messageDigest)); 117 | } 118 | return true; 119 | }); 120 | return R.unKnow(r); 121 | } finally { 122 | rs.finishDoIt(); 123 | } 124 | } 125 | 126 | @Override 127 | public R saveFileUploadStatus(CkFileInfo fileInfo) { 128 | try { 129 | rs.startDoIt(); 130 | rs.set(Constant.FILE_INFO + fileInfo.getFileMd5(), CKJSON.getInstance().toJsonString(fileInfo)); 131 | } finally { 132 | rs.finishDoIt(); 133 | } 134 | return R.ok(); 135 | } 136 | 137 | @Override 138 | public CkFileInfo getFileUploadStatus(String fileMd5) { 139 | String string; 140 | try { 141 | rs.startDoIt(); 142 | string = rs.get(Constant.FILE_INFO + fileMd5); 143 | } finally { 144 | rs.finishDoIt(); 145 | } 146 | if(ObjectJudge.isNull(string)) return null; 147 | return CKJSON.getInstance().parseObject(string, CkFileInfo.class); 148 | } 149 | 150 | @Override 151 | public R delFileUploadStatus(String fileMd5) { 152 | try { 153 | rs.startDoIt(); 154 | rs.delKey(Constant.FILE_INFO + fileMd5); 155 | } finally { 156 | rs.finishDoIt(); 157 | } 158 | return R.ok(); 159 | } 160 | 161 | @Override 162 | public CkFileInfo getFileByMessageDigest(String fileMd5) { 163 | String fileInfo; 164 | try { 165 | rs.startDoIt(); 166 | fileInfo = rs.hGet(Constant.COMPLETED_MAP, fileMd5); 167 | if(ObjectJudge.isNull(fileInfo)) return null; 168 | } finally { 169 | rs.finishDoIt(); 170 | } 171 | return CKJSON.getInstance().parseObject(fileInfo, CkFileInfo.class); 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/main/java/com/gomyck/fastdfs/starter/database/UploadService.java: -------------------------------------------------------------------------------- 1 | 2 | 3 | package com.gomyck.fastdfs.starter.database; 4 | 5 | import com.gomyck.fastdfs.starter.database.entity.CkFileInfo; 6 | import com.gomyck.util.servlet.R; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * 文件上传服务 12 | * 13 | * @author gomyck QQ:474798383 14 | * @version [版本号/1.0] 15 | * @since [2019-07-24] 16 | */ 17 | public interface UploadService { 18 | 19 | /** 20 | * 保存已完成上传的文件信息(向已完成的文件列表中加入) 21 | * 22 | * @param fileInfo 文件信息 23 | * 24 | * @return R 消息实体 25 | */ 26 | R saveUploadInfo(CkFileInfo fileInfo); 27 | 28 | /** 29 | * 查询已上传完成的文件列表(查询已完成上传的文件列表) 30 | * 31 | * @param start 开始位置 32 | * @param end 结束位置 33 | * 34 | * @return ListCkFileInfo 文件列表 35 | */ 36 | List selectCompleteFileInfo(Long start, Long end); 37 | 38 | /** 39 | * 删除文件(单个, 从已完成列表中删除) 40 | * 41 | * @param fileInfo 文件信息 42 | * 43 | * @return R 消息实体 44 | */ 45 | R delFile(CkFileInfo fileInfo); 46 | 47 | 48 | /** 49 | * 设置文件过期时间为从不 50 | * 51 | * @param fileInfo 文件信息 52 | * @param completeStatus 文件完成状态 53 | * 54 | * @return R 消息实体 55 | */ 56 | R delExpireStatus(CkFileInfo fileInfo, boolean completeStatus); 57 | 58 | /** 59 | * 根据摘要, 获取文件信息(已完成列表中获取) 60 | * 61 | * @param fileMd5 摘要 62 | * 63 | * @return CkFileInfo 文件信息 64 | */ 65 | CkFileInfo getFileByMessageDigest(String fileMd5); 66 | 67 | 68 | //--------------以下为上传辅助接口, 为了保存单个文件上传临时状态----------- 69 | 70 | /** 71 | * 保存单个文件上传状态 72 | * 73 | * @param fileInfo 文件信息 74 | * 75 | * @return R 消息实体 76 | */ 77 | R saveFileUploadStatus(CkFileInfo fileInfo); 78 | 79 | /** 80 | * 删除单个文件上传状态 81 | * 82 | * @param fileMd5 摘要 83 | * 84 | * @return R 消息实体 85 | */ 86 | R delFileUploadStatus(String fileMd5); 87 | 88 | /** 89 | * 获取文件上传状态 90 | * 91 | * @param fileMd5 摘要 92 | * 93 | * @return CkFileInfo 文件信息 94 | */ 95 | CkFileInfo getFileUploadStatus(String fileMd5); 96 | 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/com/gomyck/fastdfs/starter/database/entity/BatchDownLoadParameter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 gomyck 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | package com.gomyck.fastdfs.starter.database.entity; 24 | 25 | import lombok.Data; 26 | 27 | import java.util.ArrayList; 28 | import java.util.List; 29 | 30 | /** 31 | * 批量下载参数 32 | * 33 | * @author gomyck 34 | * -------------------------------- 35 | * | qq: 474798383 | 36 | * | email: hao474798383@163.com | 37 | * | blog: https://blog.gomyck.com | 38 | * -------------------------------- 39 | * @version [1.0.0] 40 | * @since 2021/7/5 41 | */ 42 | @Data 43 | public class BatchDownLoadParameter { 44 | 45 | private String zipFileName = "归档"; //压缩包文件名 46 | 47 | private List files = new ArrayList<>(); 48 | 49 | @Data 50 | public static class FileBatchDownload{ 51 | private String fileMd5 = ""; //文件摘要 52 | 53 | private String zipSrc = ""; //在压缩包中的目录 54 | 55 | private String fileName = ""; //文件名, 可以为空, 空时取原始文件名 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/com/gomyck/fastdfs/starter/database/entity/CkFileInfo.java: -------------------------------------------------------------------------------- 1 | 2 | 3 | package com.gomyck.fastdfs.starter.database.entity; 4 | 5 | import lombok.Data; 6 | 7 | /** 8 | * 文件信息 9 | * 10 | * @author gomyck 11 | * -------------------------------- 12 | * | qq: 474798383 | 13 | * | email: hao474798383@163.com | 14 | * | blog: https://blog.gomyck.com | 15 | * -------------------------------- 16 | * @version [1.0.0] 17 | * @since 2021/7/5 18 | */ 19 | @Data 20 | public class CkFileInfo { 21 | 22 | /** 23 | * 文件 ID 24 | */ 25 | private String id; 26 | 27 | /** 28 | * 文件名称 29 | */ 30 | private String name; //文件名称 31 | 32 | private String group; //组 33 | 34 | private String uploadPath; //文件服务器路径 35 | 36 | private String fileMd5; //摘要 37 | 38 | private Long size; //文件大小 39 | 40 | private String uploadTime; //上传时间 41 | 42 | private String type; 43 | 44 | private String chunk; 45 | 46 | private String chunks; 47 | 48 | private Long chunkSize; //每块文件的大小 49 | 50 | private Long expireTime; //过期时间, 以秒为单位 51 | 52 | private boolean thumbFlag = true; //是否生成略缩图 默认生成 53 | 54 | private String thumbImgPath; //略缩图文件位置 55 | 56 | private Integer thumbImgWidth; //缩放长度 57 | 58 | private Integer thumbImgHeight; //缩放高度 59 | 60 | private Double thumbImgPercent; //缩放比例 如果存在宽高比, 那么默认按照宽高比缩放 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/gomyck/fastdfs/starter/doorchain/ChaimEntity.java: -------------------------------------------------------------------------------- 1 | package com.gomyck.fastdfs.starter.doorchain; 2 | 3 | /** 4 | * 防盗链实体 5 | * 6 | * @author gomyck 7 | * -------------------------------- 8 | * | qq: 474798383 | 9 | * | email: hao474798383@163.com | 10 | * | blog: https://blog.gomyck.com | 11 | * -------------------------------- 12 | * @version [gomyck-quickdev-1.0.0] 13 | * @since 2022/5/20 10:09 14 | */ 15 | public class ChaimEntity { 16 | 17 | /** 18 | * 分享主机 19 | */ 20 | private String shareHost; 21 | 22 | /** 23 | * 令牌 24 | */ 25 | private String token; 26 | 27 | /** 28 | * 文件路径 29 | */ 30 | private String fileUrl; 31 | 32 | /** 33 | * 文件名 34 | */ 35 | private String fileName; 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/gomyck/fastdfs/starter/doorchain/ExecuteChain.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022. Lorem ipsum dolor sit amet, consectetur adipiscing elit. 3 | * Morbi non lorem porttitor neque feugiat blandit. Ut vitae ipsum eget quam lacinia accumsan. 4 | * Etiam sed turpis ac ipsum condimentum fringilla. Maecenas magna. 5 | * Proin dapibus sapien vel ante. Aliquam erat volutpat. Pellentesque sagittis ligula eget metus. 6 | * Vestibulum commodo. Ut rhoncus gravida arcu. 7 | */ 8 | 9 | package com.gomyck.fastdfs.starter.doorchain; 10 | 11 | /** 12 | * 13 | * 执行链 14 | * 15 | * @author gomyck 16 | * -------------------------------- 17 | * | qq: 474798383 | 18 | * | email: hao474798383@163.com | 19 | * | blog: https://blog.gomyck.com | 20 | * -------------------------------- 21 | * @version [gomyck-quickdev-1.0.0] 22 | * @since 2022/5/19 08:27 23 | */ 24 | public interface ExecuteChain { 25 | 26 | /** 27 | * 执行防盗链 28 | */ 29 | void execute(); 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/gomyck/fastdfs/starter/doorchain/HashToken.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022. Lorem ipsum dolor sit amet, consectetur adipiscing elit. 3 | * Morbi non lorem porttitor neque feugiat blandit. Ut vitae ipsum eget quam lacinia accumsan. 4 | * Etiam sed turpis ac ipsum condimentum fringilla. Maecenas magna. 5 | * Proin dapibus sapien vel ante. Aliquam erat volutpat. Pellentesque sagittis ligula eget metus. 6 | * Vestibulum commodo. Ut rhoncus gravida arcu. 7 | */ 8 | 9 | package com.gomyck.fastdfs.starter.doorchain; 10 | 11 | /** 12 | * hash token 防盗链 13 | * 14 | * @author gomyck 15 | * -------------------------------- 16 | * | qq: 474798383 | 17 | * | email: hao474798383@163.com | 18 | * | blog: https://blog.gomyck.com | 19 | * -------------------------------- 20 | * @version [gomyck-quickdev-1.0.0] 21 | * @since 2022/5/20 10:08 22 | */ 23 | public class HashToken implements Token{ 24 | 25 | 26 | /** 27 | * 验证 hash token 是否过期 28 | * 29 | * @return 是否过期 30 | */ 31 | @Override 32 | public boolean valid() { 33 | return false; 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/gomyck/fastdfs/starter/doorchain/Interceptor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022. Lorem ipsum dolor sit amet, consectetur adipiscing elit. 3 | * Morbi non lorem porttitor neque feugiat blandit. Ut vitae ipsum eget quam lacinia accumsan. 4 | * Etiam sed turpis ac ipsum condimentum fringilla. Maecenas magna. 5 | * Proin dapibus sapien vel ante. Aliquam erat volutpat. Pellentesque sagittis ligula eget metus. 6 | * Vestibulum commodo. Ut rhoncus gravida arcu. 7 | */ 8 | 9 | package com.gomyck.fastdfs.starter.doorchain; 10 | 11 | /** 12 | * 拦截器 13 | * 14 | * 根据不同的维度去拦截请求 15 | * 16 | * @author gomyck 17 | * -------------------------------- 18 | * | qq: 474798383 | 19 | * | email: hao474798383@163.com | 20 | * -------------------------------- 21 | * @version [gomyck-quickdev-1.0.1] 22 | * @since 2022/5/19 08:24 23 | */ 24 | public interface Interceptor { 25 | 26 | /** 27 | * 拦截该请求 28 | * 29 | * @return 是否放过请求 30 | */ 31 | boolean intercepted(); 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/gomyck/fastdfs/starter/doorchain/SimpleChain.java: -------------------------------------------------------------------------------- 1 | package com.gomyck.fastdfs.starter.doorchain; 2 | 3 | public class SimpleChain implements ExecuteChain, Interceptor, Token, Validator{ 4 | @Override 5 | public void execute() { 6 | 7 | } 8 | 9 | @Override 10 | public boolean intercepted() { 11 | return false; 12 | } 13 | 14 | @Override 15 | public boolean valid() { 16 | return false; 17 | } 18 | 19 | @Override 20 | public boolean verifyIt(Object token) { 21 | return false; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/gomyck/fastdfs/starter/doorchain/TempToken.java: -------------------------------------------------------------------------------- 1 | //package com.gomyck.fastdfs.starter.doorchain; 2 | // 3 | ///** 4 | // * 临时防盗链, 临时性授权 5 | // * 6 | // * @author gomyck 7 | // * -------------------------------- 8 | // * | qq: 474798383 | 9 | // * | email: hao474798383@163.com | 10 | // * | blog: https://blog.gomyck.com | 11 | // * -------------------------------- 12 | // * @version [1.0.0] 13 | // * @since 2023/4/13 14 | // */ 15 | //public class TempToken implements Token{ 16 | // 17 | // 18 | // @Override 19 | // public boolean valid() { 20 | // return false; 21 | // } 22 | // 23 | //} 24 | -------------------------------------------------------------------------------- /src/main/java/com/gomyck/fastdfs/starter/doorchain/Token.java: -------------------------------------------------------------------------------- 1 | package com.gomyck.fastdfs.starter.doorchain; 2 | 3 | /** 4 | * 令牌 5 | * 6 | * @author gomyck 7 | * @version 1.0.0 8 | */ 9 | public interface Token { 10 | 11 | /** 12 | * 是否有效 13 | * @return 14 | */ 15 | boolean valid(); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/gomyck/fastdfs/starter/doorchain/TokenValidator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022. Lorem ipsum dolor sit amet, consectetur adipiscing elit. 3 | * Morbi non lorem porttitor neque feugiat blandit. Ut vitae ipsum eget quam lacinia accumsan. 4 | * Etiam sed turpis ac ipsum condimentum fringilla. Maecenas magna. 5 | * Proin dapibus sapien vel ante. Aliquam erat volutpat. Pellentesque sagittis ligula eget metus. 6 | * Vestibulum commodo. Ut rhoncus gravida arcu. 7 | */ 8 | 9 | package com.gomyck.fastdfs.starter.doorchain; 10 | 11 | import com.gomyck.cache.redis.starter.core.redis.RedisCache; 12 | import com.gomyck.util.ObjectJudge; 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | 15 | /** 16 | * token 防盗链 17 | * 18 | * @author gomyck 19 | * -------------------------------- 20 | * | qq: 474798383 | 21 | * | email: hao474798383@163.com | 22 | * | blog: https://blog.gomyck.com | 23 | * -------------------------------- 24 | * @version [gomyck-quickdev-1.0.0] 25 | * @since 2022/5/20 10:09 26 | */ 27 | public class TokenValidator implements Validator{ 28 | 29 | @Autowired 30 | RedisCache rs; 31 | 32 | /** 33 | * 验证方法 34 | * 35 | * @param token token 36 | * @return 是否成功 37 | */ 38 | @Override 39 | public boolean verifyIt(final String token) { 40 | return ObjectJudge.notNull(rs.get(token)); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/gomyck/fastdfs/starter/doorchain/Validator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022. Lorem ipsum dolor sit amet, consectetur adipiscing elit. 3 | * Morbi non lorem porttitor neque feugiat blandit. Ut vitae ipsum eget quam lacinia accumsan. 4 | * Etiam sed turpis ac ipsum condimentum fringilla. Maecenas magna. 5 | * Proin dapibus sapien vel ante. Aliquam erat volutpat. Pellentesque sagittis ligula eget metus. 6 | * Vestibulum commodo. Ut rhoncus gravida arcu. 7 | */ 8 | 9 | package com.gomyck.fastdfs.starter.doorchain; 10 | 11 | /** 12 | * 验证器 13 | * 14 | * @author gomyck 15 | * -------------------------------- 16 | * | qq: 474798383 | 17 | * | email: hao474798383@163.com | 18 | * -------------------------------- 19 | * @version [gomyck-quickdev-1.0.1] 20 | * @since 2022/5/20 10:09 21 | */ 22 | public interface Validator { 23 | 24 | /** 25 | * 验证 26 | * 27 | * @param token token 28 | * @return 是否成功 29 | */ 30 | boolean verifyIt(T token); 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/gomyck/fastdfs/starter/job/FileCleanTask.java: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * * 4 | * * * Copyright (c) 2019 Gomyck 5 | * * * 6 | * * * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * * * of this software and associated documentation files (the "Software"), to deal 8 | * * * in the Software without restriction, including without limitation the rights 9 | * * * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * * * copies of the Software, and to permit persons to whom the Software is 11 | * * * furnished to do so, subject to the following conditions: 12 | * * * 13 | * * * The above copyright notice and this permission notice shall be included in all 14 | * * * copies or substantial portions of the Software. 15 | * * * 16 | * * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * * * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * * * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * * * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * * * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * * * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * * * SOFTWARE. 23 | * * 24 | * 25 | */ 26 | 27 | package com.gomyck.fastdfs.starter.job; 28 | 29 | import com.gomyck.cache.redis.starter.core.redis.RedisCache; 30 | import com.gomyck.cache.redis.starter.core.redis.annotation.RedisManager; 31 | import com.gomyck.fastdfs.starter.common.Constant; 32 | import com.gomyck.fastdfs.starter.controller.UploadManageHandler; 33 | import com.gomyck.fastdfs.starter.database.UploadService; 34 | import com.gomyck.fastdfs.starter.database.entity.CkFileInfo; 35 | import com.gomyck.util.CkDate; 36 | import com.gomyck.util.ObjectJudge; 37 | import com.gomyck.util.servlet.R; 38 | import lombok.extern.slf4j.Slf4j; 39 | import org.springframework.beans.factory.annotation.Autowired; 40 | import org.springframework.scheduling.annotation.Scheduled; 41 | import org.springframework.stereotype.Component; 42 | 43 | import java.time.LocalDateTime; 44 | import java.time.format.DateTimeFormatter; 45 | import java.util.List; 46 | import java.util.Set; 47 | import java.util.stream.Collectors; 48 | 49 | /** 50 | * 清理过期的文件(只针对加入了过期标志的文件信息) 51 | * 52 | * @author gomyck 53 | * -------------------------------- 54 | * | qq: 474798383 | 55 | * | email: hao474798383@163.com | 56 | * | blog: https://blog.gomyck.com | 57 | * -------------------------------- 58 | * @version [1.0.0] 59 | * @since 2021/6/29 60 | */ 61 | @Component 62 | @Slf4j 63 | public class FileCleanTask { 64 | 65 | /** 66 | * 上传服务 67 | */ 68 | @Autowired 69 | UploadService uploadService; 70 | 71 | /** 72 | * 上传处理器 73 | */ 74 | @Autowired 75 | UploadManageHandler uploadManageHandler; 76 | 77 | /** 78 | * redis 缓存 79 | */ 80 | @Autowired 81 | RedisCache rc; 82 | 83 | /** 84 | * 删除临时文件 85 | */ 86 | @Scheduled(cron = "0 */30 * * * *") 87 | @RedisManager 88 | public void cleanTempFile(){ 89 | String expireFile = null; 90 | List ckFileInfos = uploadService.selectCompleteFileInfo(0L, -1L); 91 | if(ckFileInfos != null && ckFileInfos.size() > 0){ 92 | expireFile = ckFileInfos.stream().filter(e -> { 93 | Long expireTime = e.getExpireTime(); 94 | if (expireTime == null) { 95 | return false; 96 | } else { 97 | LocalDateTime createTime = LocalDateTime.parse(e.getUploadTime(), DateTimeFormatter.ofPattern(CkDate.CN_DATETIME_FORMAT_1)); 98 | return createTime.plusSeconds(expireTime).isBefore(LocalDateTime.now()); 99 | } 100 | }).map(CkFileInfo::getFileMd5).collect(Collectors.joining(",")); 101 | } 102 | doClean(expireFile); 103 | Set keys = rc.keysPattern(Constant.FILE_INFO + "*"); 104 | if(keys != null) { 105 | expireFile = keys.stream().filter(e -> { 106 | CkFileInfo fileUploadStatus = uploadService.getFileUploadStatus(e.replace(Constant.FILE_INFO, "")); 107 | Long expireTime = fileUploadStatus.getExpireTime(); 108 | if (expireTime == null) { 109 | return false; 110 | } else { 111 | LocalDateTime createTime = LocalDateTime.parse(fileUploadStatus.getUploadTime(), DateTimeFormatter.ofPattern(CkDate.CN_DATETIME_FORMAT_1)); 112 | return createTime.plusSeconds(expireTime).isBefore(LocalDateTime.now()); 113 | } 114 | }).map(e -> e.replaceAll(Constant.FILE_INFO, "")).collect(Collectors.joining(",")); 115 | } 116 | doClean(expireFile); 117 | } 118 | 119 | /** 120 | * 清除方法 121 | * 122 | * @param expireFile 过期的文件 123 | */ 124 | private void doClean(String expireFile) { 125 | if(ObjectJudge.isNull(expireFile)) return; 126 | log.info("clear expire file start : {}", expireFile); 127 | R r = uploadManageHandler.batchDelFile(expireFile); 128 | log.info("==========clear expire file end, drop file is : {}==========", r); 129 | } 130 | 131 | 132 | } 133 | -------------------------------------------------------------------------------- /src/main/java/com/gomyck/fastdfs/starter/lock/FileLock.java: -------------------------------------------------------------------------------- 1 | 2 | 3 | package com.gomyck.fastdfs.starter.lock; 4 | 5 | /** 6 | * 文件锁 7 | * 8 | * @author gomyck 9 | * -------------------------------- 10 | * | qq: 474798383 | 11 | * | email: hao474798383@163.com | 12 | * | blog: https://blog.gomyck.com | 13 | * -------------------------------- 14 | * @version [1.0.0] 15 | * @since 2021/7/13 16 | */ 17 | public interface FileLock { 18 | 19 | /** 20 | * 添加锁 21 | * 22 | * @param fileKey md5 23 | * @return 是否成功上锁 24 | */ 25 | boolean addLock(String fileKey); 26 | 27 | /** 28 | * 删除锁 29 | * 30 | * @param fileKey md5 31 | * @return 是否成功 32 | */ 33 | boolean delLock(String fileKey); 34 | 35 | /** 36 | * 是否有锁 37 | * 38 | * @param fileKey md5 39 | * @return 是否有锁 40 | */ 41 | boolean ifLock(String fileKey); 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/gomyck/fastdfs/starter/lock/SimpleMapFileLock.java: -------------------------------------------------------------------------------- 1 | 2 | 3 | package com.gomyck.fastdfs.starter.lock; 4 | 5 | import java.util.concurrent.ConcurrentHashMap; 6 | import java.util.concurrent.locks.ReentrantLock; 7 | 8 | /** 9 | * 默认的文件锁 10 | * 11 | * @author gomyck 12 | * -------------------------------- 13 | * | qq: 474798383 | 14 | * | email: hao474798383@163.com | 15 | * | blog: https://blog.gomyck.com | 16 | * -------------------------------- 17 | * @version [1.0.0] 18 | * @since 2021/7/13 19 | */ 20 | public class SimpleMapFileLock extends ConcurrentHashMap implements FileLock { 21 | 22 | /** 23 | * 文件锁, 简单的公平同步锁 24 | */ 25 | private static ReentrantLock rl = new ReentrantLock(true); 26 | 27 | @Override 28 | public boolean addLock(String fileKey) { 29 | try{ 30 | rl.lock(); 31 | Integer integer = get(fileKey); 32 | integer = (integer == null ? 0 : integer); 33 | if (integer > 0) { 34 | return false; 35 | } 36 | this.put(fileKey, 1); 37 | }finally { 38 | rl.unlock(); 39 | } 40 | return true; 41 | } 42 | 43 | @Override 44 | public boolean delLock(String fileKey) { 45 | try{ 46 | rl.lock(); 47 | remove(fileKey); 48 | //put(fileKey, 0); 49 | }finally { 50 | rl.unlock(); 51 | } 52 | return true; 53 | } 54 | 55 | @Override 56 | public boolean ifLock(String fileKey) { 57 | try{ 58 | rl.lock(); 59 | Integer integer = get(fileKey); 60 | integer = (integer == null ? 0 : integer); 61 | return integer > 0; 62 | }finally { 63 | rl.unlock(); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/gomyck/fastdfs/starter/lock/SimpleRedisFileLock.java: -------------------------------------------------------------------------------- 1 | 2 | 3 | package com.gomyck.fastdfs.starter.lock; 4 | 5 | import com.gomyck.cache.redis.starter.core.redis.RedisCache; 6 | import com.gomyck.util.ObjectJudge; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | 9 | /** 10 | * 缓存文件锁 11 | * 12 | * @author gomyck 13 | * -------------------------------- 14 | * | qq: 474798383 | 15 | * | email: hao474798383@163.com | 16 | * | blog: https://blog.gomyck.com | 17 | * -------------------------------- 18 | * @version [1.0.0] 19 | * @since 2021/7/13 20 | */ 21 | public class SimpleRedisFileLock implements FileLock { 22 | 23 | @Autowired 24 | RedisCache rc; 25 | 26 | @Override 27 | public boolean addLock(String fileKey) { 28 | long lock; 29 | try { 30 | rc.startDoIt(); 31 | lock = rc.incr(fileKey); 32 | } finally { 33 | rc.finishDoIt(); 34 | } 35 | return lock <= 1; 36 | } 37 | 38 | @Override 39 | public boolean delLock(String fileKey) { 40 | try { 41 | rc.startDoIt(); 42 | rc.delKey(fileKey); 43 | } finally { 44 | rc.finishDoIt(); 45 | } 46 | return true; 47 | } 48 | 49 | @Override 50 | public boolean ifLock(String fileKey) { 51 | String lock; 52 | try { 53 | rc.startDoIt(); 54 | lock = rc.get(fileKey); 55 | } finally { 56 | rc.finishDoIt(); 57 | } 58 | return ObjectJudge.notNull(lock) && Long.parseLong(lock) > 0; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/gomyck/fastdfs/starter/profile/FileServerProfile.java: -------------------------------------------------------------------------------- 1 | 2 | 3 | package com.gomyck.fastdfs.starter.profile; 4 | 5 | import com.gomyck.util.ObjectJudge; 6 | import lombok.Data; 7 | import org.springframework.boot.context.properties.ConfigurationProperties; 8 | 9 | /** 10 | * 配置文件 11 | * 12 | * @author gomyck 13 | * -------------------------------- 14 | * | qq: 474798383 | 15 | * | email: hao474798383@163.com | 16 | * | blog: https://blog.gomyck.com | 17 | * -------------------------------- 18 | * @version [1.0.0] 19 | * @since 2021/7/14 20 | */ 21 | @ConfigurationProperties(value = "gomyck.fastdfs") 22 | @Data 23 | public class FileServerProfile { 24 | 25 | /** 26 | * 文件服务预览时 使用协议 27 | */ 28 | private String fileServerProtocol; 29 | 30 | /** 31 | * 文件服务地址 uri 32 | */ 33 | private String fileServerUrl; 34 | 35 | /** 36 | * 默认上传文件服务分组 37 | */ 38 | private String groupId = "group1"; 39 | 40 | /** 41 | * 下载时块大小(byte 单位) 42 | */ 43 | private long downloadChunkSize = 1024 * 1024; //下载速度 byte 44 | 45 | /** 46 | * 最大下载文件数量 47 | */ 48 | private Integer maxDownloadFileNum = 100; 49 | 50 | /** 51 | * 分块大小 (MB 单位) 52 | */ 53 | private Long chunkSize = 5L; 54 | 55 | /** 56 | * 是否开启异常处理增强 57 | */ 58 | private boolean enableErrorAdvice = false; 59 | 60 | /** 61 | * 错误页 hostname, 一般来说, 就是当前的项目 hostname + port, 但是在有网关的情况下, 需要配置成网关, 或者代理服务的地址, 否则会出现被墙的问题 62 | */ 63 | private String errorPageHostName = ""; 64 | 65 | /** 66 | * 获取文件服务 URI 67 | * @return 68 | */ 69 | public String getFileServerURI(){ 70 | return ObjectJudge.isNull(fileServerProtocol) ? "http" : fileServerProtocol + "://" + fileServerUrl; 71 | } 72 | 73 | /** 74 | * 每个块的大小 75 | * 76 | * @return byte 77 | */ 78 | public Long getChunkSize() { 79 | return chunkSize * 1024 * 1024; 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/ck-fastdfs/css/errorPage.css: -------------------------------------------------------------------------------- 1 | html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video { 2 | margin: 0; 3 | padding: 0; 4 | border: 0; 5 | font-size: 100%; 6 | font: inherit; 7 | vertical-align: baseline; 8 | outline: none; 9 | } 10 | html { height: 100%; } /* always display scrollbars */ 11 | body { height: 100%; font-size: 62.5%; line-height: 1; font-family: Arial, Tahoma, Verdana, sans-serif; } 12 | 13 | article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section { display: block; } 14 | ol, ul { list-style: none; } 15 | 16 | blockquote, q { quotes: none; } 17 | blockquote:before, blockquote:after, q:before, q:after { content: ''; content: none; } 18 | strong { font-weight: bold } 19 | input { outline: none } 20 | table { 21 | border-collapse: collapse; 22 | border-spacing: 0; 23 | } 24 | img { 25 | border: 0; 26 | max-width: 100%; 27 | } 28 | a { text-decoration: none } 29 | a:hover { text-decoration: underline } 30 | .clear { clear: both } 31 | .clear:before, 32 | .container:after { 33 | content: ""; 34 | display: table; 35 | } 36 | .clear:after { clear: both } 37 | /* IE 6/7 */ 38 | .clear { zoom: 1 } 39 | body { 40 | background: #dfdfdf url(../image/bg_noise.jpg) repeat; 41 | font-family: Helvetica, Arial, sans-serif; 42 | -webkit-font-smoothing: antialiased; 43 | overflow: hidden; 44 | } 45 | @font-face { 46 | font-family: 'TeXGyreScholaRegular'; 47 | src: url('../font/texgyreschola-regular-webfont.eot'); 48 | src: url('../font/texgyreschola-regular-webfont.eot?#iefix') format('embedded-opentype'), 49 | url('../font/texgyreschola-regular-webfont.woff') format('woff'), 50 | url('../font/texgyreschola-regular-webfont.ttf') format('truetype'), 51 | url('texgyreschola-regular-webfont.svg#TeXGyreScholaRegular') format('svg'); 52 | font-weight: normal; 53 | font-style: normal; 54 | 55 | } 56 | @font-face { 57 | font-family: 'TeXGyreScholaBold'; 58 | src: url('../font/texgyreschola-bold-webfont.eot'); 59 | src: url('../font/texgyreschola-bold-webfont.eot?#iefix') format('embedded-opentype'), 60 | url('../font/texgyreschola-bold-webfont.woff') format('woff'), 61 | url('../font/texgyreschola-bold-webfont.ttf') format('truetype'), 62 | url('texgyreschola-bold-webfont.svg#TeXGyreScholaBold') format('svg'); 63 | font-weight: normal; 64 | font-style: normal; 65 | 66 | } 67 | @-webkit-keyframes main { 68 | 0% { 69 | -webkit-transform: scale3d(0.1, 0.1, 1); 70 | opacity: 0; 71 | } 72 | 45% { 73 | -webkit-transform: scale3d(1.07, 1.07, 1); 74 | opacity: 1; 75 | } 76 | 70% { -webkit-transform: scale3d(0.95, 0.95, 1) } 77 | 100% { -webkit-transform: scale3d(1, 1, 1) } 78 | } 79 | @-moz-keyframes main { 80 | 0% { 81 | -moz-transform: scale(0.1, 0.1); 82 | opacity: 0; 83 | } 84 | 45% { 85 | -moz-transform: scale(1.07, 1.07); 86 | opacity: 1; 87 | } 88 | 70% { -moz-transform: scale(0.95, 0.95) } 89 | 100% { -moz-transform: scale(1, 1) } 90 | } 91 | @-webkit-keyframes logo { 92 | 0% { opacity: 0 } 93 | 100% { opacity: 1 } 94 | } 95 | @-webkit-keyframes footer { 96 | 0% { top: 50px } 97 | 100% { top: 0 } 98 | } 99 | .clear { clear: both } 100 | .clear:before, 101 | .container:after { 102 | content: ""; 103 | display: table; 104 | } 105 | .clear:after { clear: both } 106 | .clear { zoom: 1 } 107 | .left { float: left } 108 | .right { float: right } 109 | #wrapper { 110 | height: 100%; 111 | background-image: linear-gradient(bottom, rgba(0,0,0,.0) 0%, rgba(0,0,0,.20) 100%); 112 | background-image: -o-linear-gradient(bottom, rgba(0,0,0,.0) 0%, rgba(0,0,0,.20) 100%); 113 | background-image: -moz-linear-gradient(bottom, rgba(0,0,0,.0) 0%, rgba(0,0,0,.20) 100%); 114 | background-image: -webkit-radial-gradient(rgba(0,0,0,.0) 0%, rgba(0,0,0,.20) 100%); 115 | background-image: -ms-linear-gradient(bottom, rgba(0,0,0,.0) 0%, rgba(0,0,0,.20) 100%); 116 | } 117 | .logo { 118 | position: absolute; 119 | background: url(../image/logo.png); 120 | width: 200px; 121 | height: 80px; 122 | top: 1%; 123 | left: 1%; 124 | z-index: 1; 125 | animation: logo 1.5s 1; 126 | -webkit-animation: logo 1.5s 1; 127 | -moz-animation: logo 1.5s 1; 128 | -o-animation: logo 1.5s 1; 129 | -ms-animation: logo 1.5s 1; 130 | transition: all 0.3s ease-in-out; 131 | -webkit-transition: all 0.3s ease-in-out; 132 | -moz-transition: all 0.3s ease-in-out; 133 | -o-transition: all 0.3s ease-in-out; 134 | -ms-transition: all 0.3s ease-in-out; 135 | } 136 | .logo:hover { opacity: .75 !important } 137 | #main { 138 | position: relative; 139 | width: 600px; 140 | margin: 0 auto; 141 | padding-top: 8%; 142 | animation: main .8s 1; 143 | animation-fill-mode: forwards; 144 | -webkit-animation: main .8s 1; 145 | -webkit-animation-fill-mode: forwards; 146 | -moz-animation: main .8s 1; 147 | -moz-animation-fill-mode: forwards; 148 | -o-animation: main .8s 1; 149 | -o-animation-fill-mode: forwards; 150 | -ms-animation: main .8s 1; 151 | -ms-animation-fill-mode: forwards; 152 | } 153 | #main #header h1 { 154 | position: relative; 155 | display: block; 156 | font: 72px 'TeXGyreScholaBold', Arial, sans-serif; 157 | color: #0061a5; 158 | text-shadow: 2px 2px #f7f7f7; 159 | text-align: center; 160 | } 161 | #main #header h1 span.sub { 162 | position: relative; 163 | font-size: 21px; 164 | top: -20px; 165 | padding: 0 10px; 166 | font-style: italic; 167 | } 168 | #main #header h1 span.icon { 169 | position: relative; 170 | display: inline-block; 171 | top: -6px; 172 | margin: 0 10px 5px 0; 173 | background: #0061a5; 174 | width: 50px; 175 | height: 50px; 176 | -moz-box-shadow: 1px 2px white; 177 | -webkit-box-shadow: 1px 2px white; 178 | box-shadow: 1px 2px white; 179 | -webkit-border-radius: 50px; 180 | -moz-border-radius: 50px; 181 | border-radius: 50px; 182 | color: #dfdfdf; 183 | font-size: 46px; 184 | line-height: 48px; 185 | font-weight: bold; 186 | text-align: center; 187 | text-shadow: 0 0; 188 | } 189 | #main #content { 190 | position: relative; 191 | width: 600px; 192 | background: white; 193 | -moz-box-shadow: 0 0 0 3px #ededed inset, 0 0 0 1px #a2a2a2, 0 0 20px rgba(0,0,0,.15); 194 | -webkit-box-shadow: 0 0 0 3px #ededed inset, 0 0 0 1px #a2a2a2, 0 0 20px rgba(0,0,0,.15); 195 | box-shadow: 0 0 0 3px #ededed inset, 0 0 0 1px #a2a2a2, 0 0 20px rgba(0,0,0,.15); 196 | -webkit-border-radius: 5px; 197 | -moz-border-radius: 5px; 198 | border-radius: 5px; 199 | z-index: 5; 200 | } 201 | #main #content h2 { 202 | background: url(../image/404_s-divider.jpg) no-repeat; 203 | background-position: bottom; 204 | padding: 12px 0 22px 0; 205 | font: 20px 'TeXGyreScholaRegular', Arial, sans-serif; 206 | color: #8e8e8e; 207 | text-align: center; 208 | } 209 | #main #content p { 210 | position: relative; 211 | padding: 20px; 212 | font-size: 13px; 213 | line-height: 25px; 214 | color: #b5b5b5; 215 | } 216 | #main #content .utilities { padding: 20px } 217 | #main #content .utilities form .input-container { 218 | position: relative; 219 | width: 290px; 220 | } 221 | #main #content .utilities form .input-container input[type=text] { 222 | width: 280px; 223 | height: 34px; 224 | padding: 0 8px; 225 | background: white; 226 | border: solid 1px #cdcdcd; 227 | outline: none; 228 | -moz-box-shadow: 0 3px 3px rgba(0,0,0,.05) inset; 229 | -webkit-box-shadow: 0 3px 3px rgba(0,0,0,.05) inset; 230 | box-shadow: 0 3px 3px rgba(0,0,0,.05) inset; 231 | -webkit-border-radius: 3px; 232 | -moz-border-radius: 3px; 233 | border-radius: 3px; 234 | font-size: 14px; 235 | color: #696969; 236 | -webkit-font-smoothing: antialiased; 237 | transition: all 0.3s ease-in-out; 238 | -webkit-transition: all 0.3s ease-in-out; 239 | -moz-transition: all 0.3s ease-in-out; 240 | -o-transition: all 0.3s ease-in-out; 241 | -ms-transition: all 0.3s ease-in-out; 242 | } 243 | #main #content .utilities .input-container input[type=text]:focus { border: solid 1px #9f9f9f } 244 | #main #content .utilities form .input-container button#search { 245 | position: absolute; 246 | display: block; 247 | top: 9px; 248 | right: 0; 249 | background: white url(../image/404_search.png) no-repeat; 250 | width: 18px; 251 | height: 18px; 252 | border: none; 253 | cursor: pointer; 254 | opacity: .3; 255 | transition: all 0.3s ease-in-out; 256 | -webkit-transition: all 0.3s ease-in-out; 257 | -moz-transition: all 0.3s ease-in-out; 258 | -o-transition: all 0.3s ease-in-out; 259 | -ms-transition: all 0.3s ease-in-out; 260 | } 261 | #main #content .utilities form .input-container button#search:hover { opacity: .6 } 262 | #main #content .utilities .button { 263 | display: inline-block; 264 | height: 34px; 265 | margin: 0 0 0 6px; 266 | padding: 0 18px; 267 | background: #006db0; 268 | background-image: linear-gradient(bottom, #0062a6 0%, #0079bb 100%); 269 | background-image: -o-linear-gradient(bottom, #0062a6 0%, #0079bb 100%); 270 | background-image: -moz-linear-gradient(bottom, #0062a6 0%, #0079bb 100%); 271 | background-image: -webkit-linear-gradient(bottom, #0062a6 0%, #0079bb 100%); 272 | background-image: -ms-linear-gradient(bottom, #0062a6 0%, #0079bb 100%); 273 | -moz-box-shadow: 0 0 0 1px #003255, 0 1px 3px rgba(0, 50, 85, 0.5), 0 1px #00acd8 inset; 274 | -webkit-box-shadow: 0 0 0 1px #003255, 0 1px 3px rgba(0, 50, 85, 0.5), 0 1px #00acd8 inset; 275 | box-shadow: 0 0 0 1px #003255, 0 1px 3px rgba(0, 50, 85, 0.5), 0 1px #00acd8 inset; 276 | -webkit-border-radius: 3px; 277 | -moz-border-radius: 3px; 278 | border-radius: 3px; 279 | font-size: 14px; 280 | line-height: 34px; 281 | color: white; 282 | font-weight: bold; 283 | text-shadow: 0 -1px #00385a; 284 | text-decoration: none; 285 | } 286 | #main #content .utilities .button:hover { 287 | background: #0081c6; 288 | background-image: linear-gradient(bottom, #006fbb 0%, #008dce 100%); 289 | background-image: -o-linear-gradient(bottom, #006fbb 0%, #008dce 100%); 290 | background-image: -moz-linear-gradient(bottom, #006fbb 0%, #008dce 100%); 291 | background-image: -webkit-linear-gradient(bottom, #006fbb 0%, #008dce 100%); 292 | background-image: -ms-linear-gradient(bottom, #006fbb 0%, #008dce 100%); 293 | -moz-box-shadow: 0 0 0 1px #003255, 0 1px 3px rgba(0, 50, 85, 0.5), 0 1px #00c1e4 inset; 294 | -webkit-box-shadow: 0 0 0 1px #003255, 0 1px 3px rgba(0, 50, 85, 0.5), 0 1px #00c1e4 inset; 295 | box-shadow: 0 0 0 1px #003255, 0 1px 3px rgba(0, 50, 85, 0.5), 0 1px #00c1e4 inset; 296 | } 297 | #main #content .utilities .button:active { 298 | background: #0081c6; 299 | background-image: linear-gradient(bottom, #008dce 0%, #006fbb 100%); 300 | background-image: -o-linear-gradient(bottom, #008dce 0%, #006fbb 100%); 301 | background-image: -moz-linear-gradient(bottom, #008dce 0%, #006fbb 100%); 302 | background-image: -webkit-linear-gradient(bottom, #008dce 0%, #006fbb 100%); 303 | background-image: -ms-linear-gradient(bottom, #008dce 0%, #006fbb 100%); 304 | -moz-box-shadow: 0 0 0 1px #003255, 0 1px 3px rgba(0, 50, 85, 0.5), 0 1px #00c1e4 inset; 305 | -webkit-box-shadow: 0 0 0 1px #003255, 0 1px 3px rgba(0, 50, 85, 0.5), 0 1px #00c1e4 inset; 306 | box-shadow: 0 0 0 1px #003255, 0 1px 3px rgba(0, 50, 85, 0.5), 0 1px #00c1e4 inset; 307 | } 308 | #main #content .utilities .button-container .button:focus { color: black } 309 | #main #footer { 310 | position: relative; 311 | top: 30px; 312 | padding: 5px 0; 313 | text-align: center; 314 | z-index: 1; 315 | animation: footer .4s .8s 1; 316 | animation-fill-mode: forwards; 317 | -webkit-animation: footer .4s .8s 1; 318 | -webkit-animation-fill-mode: forwards; 319 | -moz-animation: footer .4s .8s 1; 320 | -moz-animation-fill-mode: forwards; 321 | -o-animation: footer .4s .8s 1; 322 | -o-animation-fill-mode: forwards; 323 | -ms-animation: footer .4s .8s 1; 324 | -ms-animation-fill-mode: forwards; 325 | } 326 | #main #footer ul { 327 | font: 13px 'TeXGyreScholaRegular', Arial, sans-serif; 328 | text-shadow: 0 1px white; 329 | } 330 | #main #footer ul li { 331 | display: inline; 332 | margin: 0 12px; 333 | } 334 | #main #footer ul li a { 335 | font: 13px 'TeXGyreScholaRegular', Arial, sans-serif; 336 | color: #696969; 337 | text-shadow: 0 1px white; 338 | text-decoration: none; 339 | } 340 | #main #footer ul li a:hover { 341 | color: #0061a5; 342 | text-decoration: underline; 343 | } 344 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/ck-fastdfs/css/webuploader.css: -------------------------------------------------------------------------------- 1 | .webuploader-container { 2 | position: relative; 3 | } 4 | .webuploader-element-invisible { 5 | position: absolute !important; 6 | clip: rect(1px 1px 1px 1px); /* IE6, IE7 */ 7 | clip: rect(1px,1px,1px,1px); 8 | } 9 | .webuploader-pick { 10 | position: relative; 11 | display: inline-block; 12 | cursor: pointer; 13 | background: #00b7ee; 14 | padding: 10px 15px; 15 | color: #fff; 16 | text-align: center; 17 | border-radius: 3px; 18 | overflow: hidden; 19 | } 20 | .webuploader-pick-hover { 21 | background: #00a2d4; 22 | } 23 | 24 | .webuploader-pick-disable { 25 | opacity: 0.6; 26 | pointer-events:none; 27 | } 28 | 29 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/ck-fastdfs/font/texgyreschola-bold-webfont.eot: -------------------------------------------------------------------------------- 1 | 2 | 404 Not Found 3 | 4 |

404 Not Found

5 |
openresty
6 | 7 | 8 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/ck-fastdfs/font/texgyreschola-bold-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mzxc/gomyck-fastdfs-spring-boot-starter/6fbf9c68f5ae1bf768be55c3fe05adf2fd886697/src/main/resources/META-INF/resources/ck-fastdfs/font/texgyreschola-bold-webfont.ttf -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/ck-fastdfs/font/texgyreschola-bold-webfont.woff: -------------------------------------------------------------------------------- 1 | 2 | 404 Not Found 3 | 4 |

404 Not Found

5 |
openresty
6 | 7 | 8 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/ck-fastdfs/font/texgyreschola-regular-webfont.eot: -------------------------------------------------------------------------------- 1 | 2 | 404 Not Found 3 | 4 |

404 Not Found

5 |
openresty
6 | 7 | 8 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/ck-fastdfs/font/texgyreschola-regular-webfont.ttf: -------------------------------------------------------------------------------- 1 | 2 | 404 Not Found 3 | 4 |

404 Not Found

5 |
openresty
6 | 7 | 8 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/ck-fastdfs/font/texgyreschola-regular-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mzxc/gomyck-fastdfs-spring-boot-starter/6fbf9c68f5ae1bf768be55c3fe05adf2fd886697/src/main/resources/META-INF/resources/ck-fastdfs/font/texgyreschola-regular-webfont.woff -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/ck-fastdfs/image/404.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mzxc/gomyck-fastdfs-spring-boot-starter/6fbf9c68f5ae1bf768be55c3fe05adf2fd886697/src/main/resources/META-INF/resources/ck-fastdfs/image/404.gif -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/ck-fastdfs/image/404_s-divider.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mzxc/gomyck-fastdfs-spring-boot-starter/6fbf9c68f5ae1bf768be55c3fe05adf2fd886697/src/main/resources/META-INF/resources/ck-fastdfs/image/404_s-divider.jpg -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/ck-fastdfs/image/404_search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mzxc/gomyck-fastdfs-spring-boot-starter/6fbf9c68f5ae1bf768be55c3fe05adf2fd886697/src/main/resources/META-INF/resources/ck-fastdfs/image/404_search.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/ck-fastdfs/image/bg_noise.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mzxc/gomyck-fastdfs-spring-boot-starter/6fbf9c68f5ae1bf768be55c3fe05adf2fd886697/src/main/resources/META-INF/resources/ck-fastdfs/image/bg_noise.jpg -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/ck-fastdfs/image/logo.png: -------------------------------------------------------------------------------- 1 | 2 | 404 Not Found 3 | 4 |

404 Not Found

5 |
openresty
6 | 7 | 8 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/ck-fastdfs/js/ckFastDFS.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 文件上传控制器 VUE项目请把初始化方法写在 mount 下 3 | * author: gomyck 4 | * qq: 474798383 5 | * email: hao474798383@163.com 6 | */ 7 | class CkFastDFS { 8 | 9 | constructor(option) { 10 | 11 | try{CkFastDFS.prototype.chunkMap.get(option.uploadButton.buttonId).destroy();}catch (e) {} 12 | 13 | CkFastDFS.prototype.SERVER_ERROR = "SERVER_ERROR"; //服务器错误 14 | 15 | CkFastDFS.prototype.FILE_IS_UPLOADED = "FILE_IS_UPLOADED"; //文件已上传过(秒传) 16 | 17 | CkFastDFS.prototype.Q_EXCEED_NUM_LIMIT = "Q_EXCEED_NUM_LIMIT"; //上传数量上限 18 | CkFastDFS.prototype.Q_EXCEED_SIZE_LIMIT = "Q_EXCEED_SIZE_LIMIT"; //上传总量上限 19 | CkFastDFS.prototype.Q_TYPE_DENIED = "Q_TYPE_DENIED"; //上传类型错误 20 | CkFastDFS.prototype.F_EXCEED_SIZE = "F_EXCEED_SIZE"; //单个文件上限 21 | CkFastDFS.prototype.F_DUPLICATE = "F_DUPLICATE"; //重复添加 22 | 23 | 24 | /** 25 | * 服务地址+服务上下文 26 | */ 27 | this.baseURI = option.baseURI || "/"; 28 | if (this.baseURI.lastIndexOf("/") == (this.baseURI.length - 1)) { 29 | this.baseURI = this.baseURI.substring(0, this.baseURI.length - 1); 30 | } 31 | this.checkURI = this.baseURI + '/upload/chunkUpload/checkFile'; 32 | this.uploadURI = this.baseURI + '/upload/chunkUpload/uploadFile'; 33 | this.configURI = this.baseURI + '/upload/chunkUpload/config'; 34 | this.maxFileSize = 1024 * 1024 * 200; //200MB 35 | this.chunkSize = 1024 * 1024 * 5; //5MB 36 | this.fileServerUrl = "http://127.0.0.1/";//文件服务器的IP, 如果需要预览文件或下载文件, 可能需要这个IP 37 | this.fastDFSGroup = option.fastDFSGroup || ""; 38 | 39 | /** 40 | * WebUpLoader配置 参照 webUpLoaderConfig 41 | */ 42 | this.uploaderConfig = option.uploaderConfig || {}; 43 | /** 44 | * 上传按钮 45 | * 46 | * 按钮选择器: buttonId[Selector] 47 | * 是否开启多文件选择: multiple[boolean] 48 | * 是否开启文件夹选择: directory[boolean] 49 | */ 50 | this.uploadButton = option.uploadButton; 51 | /** 52 | * 进度条 53 | * 54 | * 改变进度条: changeBar(file, progressVal[百分比进度]) 55 | */ 56 | this.uploadProgressBar = null; 57 | /** 58 | * uploader 初始化成功事件 59 | */ 60 | this.uploaderInited = option.uploaderInited; 61 | /** 62 | * 上传监听 参照 initUploaderListener() 63 | */ 64 | this.uploadListener = null; 65 | this.uploaderStatus = $.Deferred(); 66 | this.initProgressBar(option.uploadProgressBar);//初始化进度条配置 67 | this.initUploaderListener(option.uploadListener);//初始化监听配置 68 | if (!CkFastDFS.prototype.hasOwnProperty("initOnce")) { 69 | CkFastDFS.prototype.chunkMap = this.initMap(); //存储分块信息的map 70 | //初始化标记, webUploader事件只注册一次, 否则出现重复调用 71 | this.registerWebUploader(); //注册webUpLoader默认项 72 | CkFastDFS.prototype.initOnce = true; 73 | } 74 | /** 75 | * 上传插件实例 76 | */ 77 | this.uploader = null; 78 | this.uploaderId = this.initWebUpLoader();//上传插件ID 79 | this.initUpLoaderEvent();//注册事件 80 | } 81 | 82 | /** 83 | * 初始化监听配置 84 | * @param config 85 | */ 86 | initUploaderListener(config) { 87 | const defaultUploadListener = { 88 | //添加文件信息 89 | appendFileInfo: function (refer, file) { 90 | //console.log("选择文件: " + refer + "|||" + file.id); 91 | }, 92 | //添加到上传队列之前 93 | beforeAppendFileInQueued: function (refer, file) { 94 | //console.log("添加到队列之前: " + refer + "|||" + file.id); 95 | return true; 96 | }, 97 | //开始上传 98 | beginUpload: function (refer, file) { 99 | //console.log("开始上传: " + refer + "|||" + file.id); 100 | }, 101 | //分块上传成功 102 | chunkUploadSuccess: function (refer, file, result) { 103 | //console.log("分块上传成功:" + refer + "|||" + file.id + "|||" + JSON.stringify(result)) 104 | }, 105 | //上传出错 106 | uploadError: function (refer, file, reason) { 107 | //console.log("上传失败: " + refer + "|||" + file.id + "|||" + reason); 108 | }, 109 | //上传成功 110 | uploadSuccess: function (refer, file, result) { 111 | //console.log("上传成功: " + refer + "|||" + file.id + "|||" + JSON.stringify(result)); 112 | }, 113 | //上传完成 114 | uploadComplete: function (refer, file) { 115 | //console.log("上传完成: " + refer + "|||" + file.id); 116 | }, 117 | //全局错误 118 | error: function (type, tips) { 119 | //console.log("全局错误: " + type + "|||" + tips) 120 | } 121 | }; 122 | this.uploadListener = Object.assign({}, defaultUploadListener, config) 123 | } 124 | 125 | /** 126 | * 初始化进度条配置 127 | * @param config 128 | */ 129 | initProgressBar(config) { 130 | const defaultProgressBar = { 131 | changeBar: function (refer, file, progressVal) { 132 | console.log("进度条改变: " + refer + "|||" + file.id + "|||" + progressVal); 133 | } 134 | }; 135 | this.uploadProgressBar = Object.assign({}, defaultProgressBar, config) 136 | } 137 | 138 | /** 139 | * 注册一些事件(全局一次) 140 | */ 141 | registerWebUploader() { 142 | WebUploader.Uploader.register({ 143 | "before-send-file": "beforeSendFile", 144 | "before-send": "beforeSend" 145 | }, 146 | { 147 | beforeSendFile: function (file) { 148 | const fileId = file.id; //文件ID 149 | const fileName = file.name; //文件名称 150 | const fileSize = file.size; //文件大小 151 | //const directory = file.source.source.webkitRelativePath; //文件所处文件夹 152 | const task = new $.Deferred(); 153 | let currentThis = this; 154 | const _this = currentThis.owner.ckInstance; 155 | currentThis.owner.md5File(file).then(function (fileMd5) { 156 | const url = _this.checkURI; 157 | const data = { 158 | fileId: fileId, 159 | fileName: fileName, 160 | fileMd5: fileMd5, 161 | fileSize: fileSize 162 | }; 163 | // if ( directory ) { 164 | // Object.assign( data, { directory } ); 165 | // } 166 | _this.httpPostRequest(url, data, function (data) { 167 | if (!data.isOk) { 168 | _this.uploadListener.error(_this.SERVER_ERROR, data.resMsg); 169 | task.reject(); 170 | return; 171 | } 172 | if (data.resCode == 302) { 173 | _this.changeProgressBar(_this.getRefer(file), file, 1); 174 | _this.uploadListener.uploadSuccess(_this.getRefer(file), file, data); 175 | file.quickUp = true; 176 | task.reject(); 177 | return; 178 | } 179 | if (!(data.resData.chunk >= 0)) { 180 | _this.uploadListener.error(_this.SERVER_ERROR, '无法获取当前文件块, 请联系管理员'); 181 | task.reject(); 182 | return; 183 | } 184 | _this.chunkMap.put(currentThis.owner.ckId + fileId, {fileMd5: fileMd5, chunk: data.resData.chunk}); 185 | task.resolve(); 186 | }, function () { 187 | _this.uploadListener.error(_this.SERVER_ERROR, '服务器错误, 请联系管理员'); 188 | task.reject(); 189 | }) 190 | }); 191 | return $.when(task); 192 | }, 193 | beforeSend: function (block) { 194 | const task = new $.Deferred(); 195 | const fileId = this.owner.ckId + block.file.id; 196 | const chunk = this.owner.ckInstance.chunkMap.get(fileId).chunk; //当前第几块 197 | if (block.chunk < chunk) { 198 | task.reject(); 199 | } else { 200 | console.debug("第" + block.chunk + "块开始上传"); 201 | task.resolve(); 202 | } 203 | return $.when(task); 204 | } 205 | }); 206 | } 207 | 208 | /** 209 | * 获取点击上传的元素jq对象 210 | * @param file 211 | * @returns {*} 212 | */ 213 | getRefer(file) { 214 | return file.source._refer; 215 | } 216 | 217 | /** 218 | * 初始化webUploader 219 | * @returns {string} 220 | */ 221 | initWebUpLoader() { 222 | const uploaderId = "uploader_" + new Date().getTime() + "_" + parseInt(Math.random() * 1000, 10); 223 | const uploadURLString = this.uploadURI; 224 | let webUpLoaderConfig = { 225 | swf: this.baseURI + "/ck-fastdfs/swf/Uploader.swf", 226 | pick: { id: this.uploadButton.buttonId, multiple: this.uploadButton.multiple | this.uploadButton.directory | false, directory: this.uploadButton.directory | false }, //按钮的ID 227 | server: uploadURLString, 228 | accept: { 229 | title: '支持的文件类型', 230 | extensions: 'txt,jpg,jpeg,png,gif,bmp,doc,docx,pdf,xls,xlsx,ppt,pptx,zip,rar', 231 | mimeTypes: 'text/plain,/image/jpg,image/jpeg,image/png,image/gif,image/bmp,application/msword,' + 232 | 'application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/pdf,' + 233 | 'application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,' + 234 | 'application/vnd.ms-powerpoint,application/vnd.openxmlformats-officedocument.presentationml.presentation,' + 235 | 'application/x-zip-compressed,application/x-rar-compressed' 236 | }, 237 | runtimeOrder: "html5,flash", 238 | resize: false, 239 | compress: false, 240 | auto: true, 241 | chunkSize: this.chunkSize, 242 | chunked: true, 243 | fileSingleSizeLimit: this.maxFileSize, 244 | threads: 1, 245 | data: {}, 246 | headers: {} 247 | // auto: true 248 | }; 249 | const finalConfig = Object.assign({}, webUpLoaderConfig, this.uploaderConfig); 250 | const _this = this; 251 | this.loadServerConfig(function () { 252 | finalConfig.fileSingleSizeLimit = _this.maxFileSize; //从服务器读取的单个文件上传限制 253 | finalConfig.chunkSize = _this.chunkSize; //从服务器读取的单个文件上传限制 254 | _this.uploader = WebUploader.create(finalConfig); 255 | _this.uploader.ckId = uploaderId; 256 | _this.uploader.ckInstance = _this; 257 | CkFastDFS.prototype.chunkMap.put(_this.uploadButton.buttonId, _this.uploader); 258 | _this.uploaderStatus.resolve(); 259 | _this.uploaderInited && _this.uploaderInited(_this); 260 | }); 261 | return uploaderId; 262 | } 263 | 264 | /** 265 | * 初始化上传事件 266 | */ 267 | initUpLoaderEvent() { 268 | const _this = this; 269 | $.when(this.uploaderStatus).done(function () { 270 | const _uploader = _this.uploader; 271 | _uploader.on('fileQueued', function (file) { 272 | _this.uploadListener.appendFileInfo(_this.getRefer(file), file); 273 | }); 274 | _uploader.on('beforeFileQueued', function (file) { 275 | return _this.uploadListener.beforeAppendFileInQueued(_this.getRefer(file), file); 276 | }); 277 | //此处是发送给服务端的参数 278 | _uploader.on('uploadBeforeSend', function (block, data, headers) { 279 | Object.assign(data, _this.uploaderConfig.data); 280 | Object.assign(headers, _this.uploaderConfig.headers); 281 | const fileId = _this.uploader.ckId + block.file.id; 282 | data.fileMd5 = _this.chunkMap.get(fileId).fileMd5; 283 | data.chunkSize = block.blob.size; 284 | data.group = _this.fastDFSGroup; 285 | }); 286 | _uploader.on('uploadStart', function (file) { 287 | _this.uploadListener.beginUpload(_this.getRefer(file), file); 288 | }); 289 | _uploader.on('uploadProgress', function (file, percentage) { 290 | _this.changeProgressBar(_this.getRefer(file), file, percentage); 291 | }); 292 | _uploader.on('uploadAccept', function (obj, response) { 293 | try { 294 | const result = JSON.parse(response._raw); 295 | if (result.isOk) { 296 | _this.uploadListener.chunkUploadSuccess(_this.getRefer(obj.file), obj.file, result); 297 | return true; 298 | }else{ 299 | _this.uploadListener.error(_this.SERVER_ERROR, result.resMsg) 300 | return false 301 | } 302 | } catch (err) { 303 | _this.uploadListener.error(_this.SERVER_ERROR, "服务器返回信息错误, 请联系管理员"); 304 | console.error(err); 305 | return false 306 | } 307 | }); 308 | _uploader.on('uploadError', function (file, reason) { 309 | if (!file.quickUp) _this.uploadListener.uploadError(_this.getRefer(file), file, reason); 310 | }); 311 | _uploader.on('uploadSuccess', function (file, response) { 312 | _this.changeProgressBar(_this.getRefer(file), file, 1); 313 | const result = JSON.parse(response._raw); 314 | if(!result.isOk){ 315 | _this.uploadListener.error(_this.SERVER_ERROR, result.resMsg) 316 | return; 317 | } 318 | _this.uploadListener.uploadSuccess(_this.getRefer(file), file, result); 319 | 320 | }); 321 | _uploader.on('uploadComplete', function (file) { 322 | _this.uploadListener.uploadComplete(_this.getRefer(file), file); 323 | }); 324 | _uploader.on('error', function (type) { 325 | let tips = ""; 326 | switch (type) { 327 | case _this.Q_EXCEED_NUM_LIMIT: 328 | tips = "文件数量已达上限"; 329 | break; 330 | case _this.Q_EXCEED_SIZE_LIMIT: 331 | tips = "上传文件总大小超过上限"; 332 | break; 333 | case _this.Q_TYPE_DENIED: 334 | tips = "不支持的文件类型"; 335 | break; 336 | case _this.F_EXCEED_SIZE: 337 | tips = "单个文件大小已超上限"; 338 | break; 339 | case _this.F_DUPLICATE: 340 | tips = "文件队列里已存在"; 341 | break; 342 | } 343 | _this.uploadListener.error(type, tips); 344 | }); 345 | }); 346 | } 347 | 348 | /** 349 | * 改变进度条包装方法 350 | * @param refer 351 | * @param file 352 | * @param percentage 353 | */ 354 | changeProgressBar(refer, file, percentage) { 355 | const progressVal = Math.min(this.toDecimal(percentage * 100, 2), 100); 356 | this.uploadProgressBar.changeBar(refer, file, progressVal); 357 | } 358 | 359 | /** 360 | * post请求 361 | * @param url 362 | * @param data 363 | * @param success 364 | * @param error 365 | */ 366 | httpPostRequest(url, data, success, error) { 367 | if(!!data) data = Object.assign(data, this.uploaderConfig.data); 368 | $.ajax({ 369 | headers: this.uploaderConfig.headers, 370 | type: "POST", 371 | url: url, 372 | data: data, 373 | cache: false, 374 | dataType: "json", 375 | success: success, 376 | error: error 377 | }); 378 | } 379 | 380 | /** 381 | * get请求 382 | * @param url 383 | * @param data 384 | * @param success 385 | * @param error 386 | */ 387 | httpGetRequest(url, data, success, error) { 388 | if(!!data) data = Object.assign(data, this.uploaderConfig.data); 389 | $.ajax({ 390 | headers: this.uploaderConfig.headers, 391 | type: "GET", 392 | url: url, 393 | data: data, 394 | cache: false, 395 | dataType: "json", 396 | success: success, 397 | error: error 398 | }); 399 | } 400 | 401 | /** 402 | * map工具类 403 | */ 404 | initMap() { 405 | const _this = {}; 406 | _this.elements = []; 407 | //获取MAP元素个数 408 | _this.size = function () { 409 | return _this.elements.length; 410 | }; 411 | //判断MAP是否为空 412 | _this.isEmpty = function () { 413 | return (_this.elements.length < 1); 414 | }; 415 | //删除MAP所有元素 416 | _this.clear = function () { 417 | _this.elements = []; 418 | }; 419 | //向MAP中增加元素(key, value) 420 | _this.put = function (_key, _value) { 421 | const exist = _this.containsKey(_key); 422 | if (exist) { 423 | _this.remove(_key); 424 | } 425 | _this.elements.push({ 426 | key: _key, 427 | value: _value 428 | }); 429 | }; 430 | //删除指定KEY的元素,成功返回True,失败返回False 431 | _this.remove = function (_key) { 432 | let bln = false; 433 | try { 434 | for (let i = 0; i < _this.elements.length; i++) { 435 | if (_this.elements[i].key == _key) { 436 | _this.elements.splice(i, 1); 437 | return true; 438 | } 439 | } 440 | } catch (e) { 441 | bln = false; 442 | } 443 | return bln; 444 | }; 445 | //获取指定KEY的元素值VALUE,失败返回NULL 446 | _this.get = function (_key) { 447 | try { 448 | for (let i = 0; i < _this.elements.length; i++) { 449 | if (_this.elements[i].key == _key) { 450 | return _this.elements[i].value; 451 | } 452 | } 453 | } catch (e) { 454 | return null; 455 | } 456 | }; 457 | 458 | //获取指定索引的元素(使用element.key,element.value获取KEY和VALUE),失败返回NULL 459 | _this.element = function (_index) { 460 | if (_index < 0 || _index >= _this.elements.length) { 461 | return null; 462 | } 463 | return _this.elements[_index]; 464 | }; 465 | 466 | //判断MAP中是否含有指定KEY的元素 467 | _this.containsKey = function (_key) { 468 | let bln = false; 469 | try { 470 | for (let i = 0; i < _this.elements.length; i++) { 471 | if (_this.elements[i].key == _key) { 472 | bln = true; 473 | } 474 | } 475 | } catch (e) { 476 | bln = false; 477 | } 478 | return bln; 479 | }; 480 | 481 | //判断MAP中是否含有指定VALUE的元素 482 | _this.containsValue = function (_value) { 483 | let bln = false; 484 | try { 485 | for (let i = 0; i < _this.elements.length; i++) { 486 | if (_this.elements[i].value == _value) { 487 | bln = true; 488 | } 489 | } 490 | } catch (e) { 491 | bln = false; 492 | } 493 | return bln; 494 | }; 495 | 496 | //获取MAP中所有VALUE的数组(ARRAY) 497 | _this.values = function () { 498 | let arr = []; 499 | for (let i = 0; i < _this.elements.length; i++) { 500 | arr.push(_this.elements[i].value); 501 | } 502 | return arr; 503 | }; 504 | 505 | //获取MAP中所有KEY的数组(ARRAY) 506 | _this.keys = function () { 507 | let arr = []; 508 | for (let i = 0; i < _this.elements.length; i++) { 509 | arr.push(_this.elements[i].key); 510 | } 511 | return arr; 512 | }; 513 | 514 | return _this; 515 | } 516 | 517 | /** 518 | * 转化进度条百分比 519 | * @param x 520 | * @param n 521 | * @returns {number} 522 | */ 523 | toDecimal(x, n) { 524 | n = arguments[1] ? arguments[1] : 2; 525 | let f = parseFloat(x); 526 | 527 | function getN(n) { 528 | let sum = 1; 529 | for (let i = 0; i < n; i++) { 530 | sum *= 10; 531 | } 532 | return sum; 533 | } 534 | 535 | if (isNaN(f)) { 536 | return 0; 537 | } 538 | f = Math.round(x * getN(n)) / getN(n); 539 | return f; 540 | } 541 | 542 | /** 543 | * 获取服务器配置 544 | * @param callBack 545 | */ 546 | loadServerConfig(callBack) { 547 | const _this = this; 548 | if(sessionStorage.ckFastDFSConfig){ 549 | const ckFastDFSConfig = JSON.parse(sessionStorage.ckFastDFSConfig); 550 | this.maxFileSize = ckFastDFSConfig.maxFileSize; //从服务器读取的单个文件上传限制 551 | this.chunkSize = ckFastDFSConfig.chunkSize; //从服务器读取的单个文件上传限制 552 | this.fileServerUrl = ckFastDFSConfig.fileServerUrl; //从服务器读取的单个文件上传限制 553 | callBack();//如果是 vue 项目使用该方法, 必须把 new CkFastDFS 放在 mounted 钩子上, 否则会出现元素未渲染完, 找不到元素的错误 554 | return; 555 | } 556 | this.httpGetRequest( this.configURI, null, function ( data ) { 557 | _this.maxFileSize = data.resData.maxFileSize; 558 | _this.chunkSize = data.resData.chunkSize; 559 | _this.fileServerUrl = data.resData.fileServerUrl; 560 | 561 | const ckFastDFSConfig = {}; 562 | ckFastDFSConfig.maxFileSize = data.resData.maxFileSize;; //从服务器读取的单个文件上传限制 563 | ckFastDFSConfig.chunkSize = data.resData.chunkSize; //从服务器读取的单个文件上传限制 564 | ckFastDFSConfig.fileServerUrl = data.resData.fileServerUrl; //从服务器读取的单个文件上传限制 565 | sessionStorage.ckFastDFSConfig = JSON.stringify(ckFastDFSConfig); 566 | 567 | callBack(); 568 | }, function () { 569 | console.error('获取配置失败!'); 570 | }); 571 | }; 572 | 573 | 574 | //-----一下为webUploader包装方法, 如需更多, 请参考webUploader api 575 | 576 | /** 577 | * 为当前上传实例新增一个按钮 578 | * @param selector 579 | */ 580 | addButton(selector) { 581 | const _this = this; 582 | $.when(this.uploaderStatus).done(function () { 583 | if (typeof selector == "string") selector = [selector]; 584 | for (const index in selector) { 585 | _this.uploader.addButton({ 586 | id: selector[index] 587 | }); 588 | } 589 | }); 590 | } 591 | 592 | /** 593 | * 暂停上传 594 | * @param param 布尔值时, 为暂停正在上传的文件, file类型时, 暂停指定file的上传, null|undefined时为暂停 595 | */ 596 | pauseUpload(param) { 597 | if (param) { 598 | this.uploader.stop(param); 599 | } else { 600 | this.uploader.stop(); 601 | } 602 | } 603 | 604 | /** 605 | * 取消上传 606 | * @param file 文件信息 607 | */ 608 | cancelUpload(file) { 609 | if (file) { 610 | this.uploader.cancelFile(file); 611 | } else { 612 | console.error("no file info can be cancel") 613 | } 614 | } 615 | 616 | addFiles(files) { 617 | if(files) { 618 | this.uploader.addFiles(files); 619 | } 620 | } 621 | 622 | } 623 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/ck-fastdfs/swf/Uploader.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mzxc/gomyck-fastdfs-spring-boot-starter/6fbf9c68f5ae1bf768be55c3fe05adf2fd886697/src/main/resources/META-INF/resources/ck-fastdfs/swf/Uploader.swf -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/ck-fastdfs/view/ckFastDFS.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 上传管理 6 | 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 | 21 | 22 |
23 |
24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 | 32 | 33 |
文件上传列表:
34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 |
序号文件名文件类型上传路径md5大小上传时间操作
46 |
47 | 48 | 49 | 50 | 51 | 220 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/ck-fastdfs/view/error.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 500 - 对不起,服务器内部错误! 6 | 7 | 8 | 9 | 10 |
11 | 12 |
13 | 16 |
17 |

服务器内部错误!

18 |

当您看到这个页面,表示服务器内部错误,此网站可能遇到技术问题,无法执行您的请求,请稍后重试或联系管理员进行处理!

19 | 20 | 26 | 27 |
28 |
29 | 15秒后自动跳转… 30 |
31 | 返回... 32 | 联系管理员 33 | 错误信息 34 |
35 |
36 |
37 | 38 |
39 |
40 | 41 | 42 | 43 | 94 | 95 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.gomyck.fastdfs.starter.GomyckFastDFSConfiguration --------------------------------------------------------------------------------