├── .gitignore
├── LICENSE
├── README.md
├── build.gradle
├── settings.gradle
└── src
├── main
├── java
│ └── win
│ │ └── pangniu
│ │ └── learn
│ │ ├── App.java
│ │ ├── controller
│ │ └── IndexController.java
│ │ ├── param
│ │ └── MultipartFileParam.java
│ │ ├── service
│ │ ├── StorageService.java
│ │ └── impl
│ │ │ └── StorageServiceImpl.java
│ │ ├── utils
│ │ ├── Constants.java
│ │ └── FileMD5Util.java
│ │ └── vo
│ │ ├── ResultStatus.java
│ │ └── ResultVo.java
├── resources
│ ├── application-dev.yml
│ ├── application.yml
│ ├── logback.xml
│ ├── public
│ │ └── index.html
│ └── static
│ │ ├── css
│ │ └── webuploader.css
│ │ └── js
│ │ ├── Uploader.swf
│ │ └── webuploader.min.js
└── webapp
│ └── index2.jsp
└── test
└── java
└── win
└── pangniu
└── learn
└── test
├── AppTest.java
└── BaseTest.java
/.gitignore:
--------------------------------------------------------------------------------
1 | *.class
2 |
3 | # Package Files #
4 | *.jar
5 | *.war
6 | *.ear
7 |
8 | # Build Artifacts
9 |
10 | .gradle/
11 | build/
12 | target/
13 | bin/
14 | dependency-reduced-pom.xml
15 |
16 | # Eclipse Project Files
17 |
18 | .classpath
19 | .project
20 | .settings/
21 |
22 | # IntelliJ IDEA Files
23 |
24 | *.iml
25 | *.ipr
26 | *.iws
27 | *.idea
28 |
29 | README.html
--------------------------------------------------------------------------------
/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, and
10 | distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by the copyright
13 | owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all other entities
16 | that control, are controlled by, or are under common control with that entity.
17 | For the purposes of this definition, "control" means (i) the power, direct or
18 | indirect, to cause the direction or management of such entity, whether by
19 | contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
20 | outstanding shares, or (iii) beneficial ownership of such entity.
21 |
22 | "You" (or "Your") shall mean an individual or Legal Entity exercising
23 | permissions granted by this License.
24 |
25 | "Source" form shall mean the preferred form for making modifications, including
26 | but not limited to software source code, documentation source, and configuration
27 | files.
28 |
29 | "Object" form shall mean any form resulting from mechanical transformation or
30 | translation of a Source form, including but not limited to compiled object code,
31 | generated documentation, and conversions to other media types.
32 |
33 | "Work" shall mean the work of authorship, whether in Source or Object form, made
34 | available under the License, as indicated by a copyright notice that is included
35 | in or attached to the work (an example is provided in the Appendix below).
36 |
37 | "Derivative Works" shall mean any work, whether in Source or Object form, that
38 | is based on (or derived from) the Work and for which the editorial revisions,
39 | annotations, elaborations, or other modifications represent, as a whole, an
40 | original work of authorship. For the purposes of this License, Derivative Works
41 | shall not include works that remain separable from, or merely link (or bind by
42 | name) to the interfaces of, the Work and Derivative Works thereof.
43 |
44 | "Contribution" shall mean any work of authorship, including the original version
45 | of the Work and any modifications or additions to that Work or Derivative Works
46 | thereof, that is intentionally submitted to Licensor for inclusion in the Work
47 | by the copyright owner or by an individual or Legal Entity authorized to submit
48 | on behalf of the copyright owner. For the purposes of this definition,
49 | "submitted" means any form of electronic, verbal, or written communication sent
50 | to the Licensor or its representatives, including but not limited to
51 | communication on electronic mailing lists, source code control systems, and
52 | issue tracking systems that are managed by, or on behalf of, the Licensor for
53 | the purpose of discussing and improving the Work, but excluding communication
54 | that is conspicuously marked or otherwise designated in writing by the copyright
55 | owner as "Not a Contribution."
56 |
57 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf
58 | of whom a Contribution has been received by Licensor and subsequently
59 | incorporated within the Work.
60 |
61 | 2. Grant of Copyright License.
62 |
63 | Subject to the terms and conditions of this License, each Contributor hereby
64 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
65 | irrevocable copyright license to reproduce, prepare Derivative Works of,
66 | publicly display, publicly perform, sublicense, and distribute the Work and such
67 | Derivative Works in Source or Object form.
68 |
69 | 3. Grant of Patent License.
70 |
71 | Subject to the terms and conditions of this License, each Contributor hereby
72 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
73 | irrevocable (except as stated in this section) patent license to make, have
74 | made, use, offer to sell, sell, import, and otherwise transfer the Work, where
75 | such license applies only to those patent claims licensable by such Contributor
76 | that are necessarily infringed by their Contribution(s) alone or by combination
77 | of their Contribution(s) with the Work to which such Contribution(s) was
78 | submitted. If You institute patent litigation against any entity (including a
79 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a
80 | Contribution incorporated within the Work constitutes direct or contributory
81 | patent infringement, then any patent licenses granted to You under this License
82 | for that Work shall terminate as of the date such litigation is filed.
83 |
84 | 4. Redistribution.
85 |
86 | You may reproduce and distribute copies of the Work or Derivative Works thereof
87 | in any medium, with or without modifications, and in Source or Object form,
88 | provided that You meet the following conditions:
89 |
90 | You must give any other recipients of the Work or Derivative Works a copy of
91 | this License; and
92 | You must cause any modified files to carry prominent notices stating that You
93 | changed the files; and
94 | You must retain, in the Source form of any Derivative Works that You distribute,
95 | all copyright, patent, trademark, and attribution notices from the Source form
96 | of the Work, excluding those notices that do not pertain to any part of the
97 | Derivative Works; and
98 | If the Work includes a "NOTICE" text file as part of its distribution, then any
99 | Derivative Works that You distribute must include a readable copy of the
100 | attribution notices contained within such NOTICE file, excluding those notices
101 | that do not pertain to any part of the Derivative Works, in at least one of the
102 | following places: within a NOTICE text file distributed as part of the
103 | Derivative Works; within the Source form or documentation, if provided along
104 | with the Derivative Works; or, within a display generated by the Derivative
105 | Works, if and wherever such third-party notices normally appear. The contents of
106 | the NOTICE file are for informational purposes only and do not modify the
107 | License. You may add Your own attribution notices within Derivative Works that
108 | You distribute, alongside or as an addendum to the NOTICE text from the Work,
109 | provided that such additional attribution notices cannot be construed as
110 | modifying the License.
111 | You may add Your own copyright statement to Your modifications and may provide
112 | additional or different license terms and conditions for use, reproduction, or
113 | distribution of Your modifications, or for any such Derivative Works as a whole,
114 | provided Your use, reproduction, and distribution of the Work otherwise complies
115 | with the conditions stated in this License.
116 |
117 | 5. Submission of Contributions.
118 |
119 | Unless You explicitly state otherwise, any Contribution intentionally submitted
120 | for inclusion in the Work by You to the Licensor shall be under the terms and
121 | conditions of this License, without any additional terms or conditions.
122 | Notwithstanding the above, nothing herein shall supersede or modify the terms of
123 | any separate license agreement you may have executed with Licensor regarding
124 | such Contributions.
125 |
126 | 6. Trademarks.
127 |
128 | This License does not grant permission to use the trade names, trademarks,
129 | service marks, or product names of the Licensor, except as required for
130 | reasonable and customary use in describing the origin of the Work and
131 | reproducing the content of the NOTICE file.
132 |
133 | 7. Disclaimer of Warranty.
134 |
135 | Unless required by applicable law or agreed to in writing, Licensor provides the
136 | Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
137 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
138 | including, without limitation, any warranties or conditions of TITLE,
139 | NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
140 | solely responsible for determining the appropriateness of using or
141 | redistributing the Work and assume any risks associated with Your exercise of
142 | permissions under this License.
143 |
144 | 8. Limitation of Liability.
145 |
146 | In no event and under no legal theory, whether in tort (including negligence),
147 | contract, or otherwise, unless required by applicable law (such as deliberate
148 | and grossly negligent acts) or agreed to in writing, shall any Contributor be
149 | liable to You for damages, including any direct, indirect, special, incidental,
150 | or consequential damages of any character arising as a result of this License or
151 | out of the use or inability to use the Work (including but not limited to
152 | damages for loss of goodwill, work stoppage, computer failure or malfunction, or
153 | any and all other commercial damages or losses), even if such Contributor has
154 | been advised of the possibility of such damages.
155 |
156 | 9. Accepting Warranty or Additional Liability.
157 |
158 | While redistributing the Work or Derivative Works thereof, You may choose to
159 | offer, and charge a fee for, acceptance of support, warranty, indemnity, or
160 | other liability obligations and/or rights consistent with this License. However,
161 | in accepting such obligations, You may act only on Your own behalf and on Your
162 | sole responsibility, not on behalf of any other Contributor, and only if You
163 | agree to indemnify, defend, and hold each Contributor harmless for any liability
164 | incurred by, or claims asserted against, such Contributor by reason of your
165 | accepting any such warranty or additional liability.
166 |
167 | END OF TERMS AND CONDITIONS
168 |
169 | APPENDIX: How to apply the Apache License to your work
170 |
171 | To apply the Apache License to your work, attach the following boilerplate
172 | notice, with the fields enclosed by brackets "{}" replaced with your own
173 | identifying information. (Don't include the brackets!) The text should be
174 | enclosed in the appropriate comment syntax for the file format. We also
175 | recommend that a file or class name and description of purpose be included on
176 | the same "printed page" as the copyright notice for easier identification within
177 | third-party archives.
178 |
179 | Copyright 2017 Fourwenwen
180 |
181 | Licensed under the Apache License, Version 2.0 (the "License");
182 | you may not use this file except in compliance with the License.
183 | You may obtain a copy of the License at
184 |
185 | http://www.apache.org/licenses/LICENSE-2.0
186 |
187 | Unless required by applicable law or agreed to in writing, software
188 | distributed under the License is distributed on an "AS IS" BASIS,
189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
190 | See the License for the specific language governing permissions and
191 | limitations under the License.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Breakpoint-http
2 |
3 | 
4 |
5 | ## 目录
6 | * [背景介绍](#背景介绍)
7 | * [项目介绍](#项目介绍)
8 | * [使用说明](#使用说明)
9 | * [获取代码](#获取代码)
10 | * [需要知识点](#需要知识点)
11 | * [启动项目](#启动项目)
12 | * [项目示范](#项目示范)
13 | * [核心讲解](#功能分析)
14 | * [重要原理](#重要原理)
15 | * [功能分析](#功能分析)
16 | * [分块上传](#分块上传)
17 | * [秒传功能](#秒传功能)
18 | * [断点续传](#断点续传)
19 | * [总结](#总结)
20 |
21 |
22 | ## 背景介绍
23 |
24 | *Breakpoint-http*,是不是觉得这个名字有点low,break point断点。这是一个大文件上传的一种实现。因为本来很久没写过前端了,本来想自己好好写一番js,可惜
25 | 因为种种原因而作罢了。该项目是基于一款百度开源的前端上传控件:WebUploader(百度开源的东西文档一如既往的差,哈哈。或者是我理解能力差)。
26 |
27 | *Breakpoint-http*,当初想实现这一块web大文件上传,是因为有一天同时询问我这方面的知识,我发现好像在实战中没写过这类的代码啊。既然知道了自己不足那
28 | 肯定要狠狠补一下。所以才有了这个项目。
29 |
30 | 对了这个项目是gradle+Spring Boot可能有部分人还没接触过这两个东西,这里就不进行讲解了,毕竟这不是重点,把gradle当成maven吧,虽然它还有更出色的功能。
31 | Spring Boot用来简化Spring应用的初始搭建以及开发过程,一个约定大于规范的框架。
32 |
33 |
34 | ## 项目介绍
35 |
36 | *Breakpoint-http* 是一个基于大文件上传,并参考网盘上传文件,而基于web的大文件上传实现项目。web中上传大文件没有桌面软件那么容易,还好现在是身处于
37 | 一个html5的时代。我们web端上传文件常用的做法就是用表单上传了,一旦上传的文件大小较大,一旦带宽跟不上,那用户只能在哪里一直等着,不能做刷新页面的操作,
38 | 并且一旦产生网络波动,那么用户所做的一切就白费了。
39 | *Breakpoint-http*就是为了保证在web端上传大文件能达到基本的可靠性的一种方案,方法多种,可能的方案会更出色,欢迎讨论。要让大文件上传能达到可用性,我们需要做到怎么样的程度呢?
40 | * **断点续传** 最主要的功能之一,在断网或者在暂停的情况下,能够在上传断点中继续上传。
41 | * **分块上传** 也是断点续传的基础之一,把大文件通过前端分块,然后后台在组在一起。
42 | * **文件妙传** 这个相信大家在网盘中见过不少了,就是服务中已经有人上传过得文件,其他人再上传这个文件就秒上传到服务中去。
43 | * **其他功能** 把下面这些功能归类到其他,是因为它们基本都是通过WebUploader()来实现的,很简单。
44 | * *多线程上传* 多个线程上传不同的块文件。
45 | * *文件进度显示* 显示文件的上传完成情况。
46 | * UI等等。
47 |
48 |
49 | ## 使用说明
50 |
51 | ### 获取代码
52 | * GitHub:
53 | * OSChina项目主页:
54 | 持续更新。
55 |
56 |
57 | ### 需要知识点
58 | - 基于spring boot开发的。
59 | - WebUploader,WebUploader是由Baidu WebFE(FEX)团队开发的一个简单的以HTML5为主,FLASH为辅的现代文件上传组件。
60 | - redis,key-value存储系统,在这里我把redis用作存储文件路径来使用。
61 | - Gradle,Gradle是一个基于JVM的构建工具。这里我用Gradle顶替了Maven。嗯,多学点东西。
62 |
63 |
64 | ### 启动项目
65 |
66 | 1. main方法直接运行:
67 | (1)找到App启动类(win.pangniu.learn包下)
68 | (2)执行main方法。
69 | (3)然后用浏览器访问:
70 |
71 | 2. tomcat运行:
72 | (1)执行命令gradle war。
73 | (2)在out目录下找到bphttp.war包。
74 | (3)拷贝到tomcat,然后运行tomcat。
75 | (4)然后用浏览器访问:
76 |
77 |
78 | ### 项目示范
79 |
80 | 1. 上传完后的页面
81 | 
82 | 2. 妙传功能演示页面
83 | 
84 | 详情自己运行就知道。
85 |
86 |
87 | ## 核心讲解
88 |
89 | ### 核心原理
90 | 该项目核心就是文件分块上传。前后端要高度配合,需要双方约定好一些数据,才能完成大文件分块,我们在项目中要重点解决的以下问题。
91 | * 如何分片;
92 | * 如何合成一个文件;
93 | * 中断了从哪个分片开始。
94 | 如何分,利用强大的js库,来减轻我们的工作,市场上已经能有关于大文件分块的轮子,虽然程序员的天性曾迫使我重新造轮子。但是因为时间的关系还有工作的关系,我只能罢休了。最后我选择了百度的WebUploader来实现前端所需。
95 | 如何合,在合之前,我们还得先解决一个问题,我们如何区分分块所属那个文件的。刚开始的时候,我是采用了前端生成了唯一uuid来做文件的标志,在每个分片请求上带上。不过后来在做秒传的时候我放弃了,采用了Md5来维护分块和文件关系。
96 | 在服务端合并文件,和记录分块的问题,在这方面其实行业已经给了很好的解决方案了。参考迅雷,你会发现,每次下载中的时候,都会有两个文件,一个文件主体,另外一个就是文件临时文件,临时文件存储着每个分块对应字节位的状态。
97 | 这些都是需要前后端密切联系才能做好,前端需要根据固定大小对文件进行分片,并且请求中要带上分片序号和大小。前端发送请求顺利到达后台后,服务器只需要按照请求数据中给的分片序号和每片分块大小(分片大小是固定且一样的)算出开始位置,与读取到的文件片段数据,写入文件即可。
98 |
99 |
100 |
101 | ## 功能分析
102 |
103 |
104 | ### 分块上传
105 | 分块上传可以说是我们整个项目的基础,像断点续传、暂停这些都是需要用到分块。
106 | 分块这块相对来说比较简单。前端是采用了webuploader,分块等基础功能已经封装起来,使用方便。
107 | 借助webUpload提供给我们的文件API,前端就显得异常简单。
108 |
109 |
110 |
111 | ```
112 | // 实例化wu
113 | var uploader = WebUploader.create({
114 | pick: {
115 | id: '#picker',
116 | label: '点击选择文件'
117 | },
118 | formData: {
119 | uid: 0,
120 | md5: '',
121 | chunkSize: chunkSize
122 | },
123 | //dnd: '#dndArea',
124 | //paste: '#uploader',
125 | swf: 'js/Uploader.swf',
126 | chunked: true,
127 | chunkSize: chunkSize, // 字节 1M分块
128 | threads: 3,
129 | server: 'index/fileUpload',
130 | auto: false,
131 |
132 | // 禁掉全局的拖拽功能。这样不会出现图片拖进页面的时候,把图片打开。
133 | disableGlobalDnd: true,
134 | fileNumLimit: 1024,
135 | fileSizeLimit: 1024 * 1024 * 1024, // 200 M
136 | fileSingleSizeLimit: 1024 * 1024 * 1024 // 50 M
137 | });
138 | ```
139 | 分则必合。把大文件分片了,但是分片了就没有原本文件功能,所以我们要把分片合成为原本的文件。我们只需要把分片按原本位置写入到文件中去。因为前面原理那一部我们已经讲到了,我们知道分块大小和分块序号,我就可以知道该分块在文件中的起始位置。所以这里使用RandomAccessFile是明智的,RandomAccessFile能在文件里面前后移动。但是在andomAccessFile的绝大多数功能,已经被JDK1.4的NIO的“内存映射文件(memory-mapped files)”取代了。我在该项目中分别写了使用RandomAccessFile与MappedByteBuffer来合成文件。分别对应的方法是uploadFileRandomAccessFile和uploadFileByMappedByteBuffer。两个方法代码如下。
140 |
141 | ```
142 | public void uploadFileRandomAccessFile(MultipartFileParam param) throws IOException {
143 | String fileName = param.getName();
144 | String tempDirPath = finalDirPath + param.getMd5();
145 | String tempFileName = fileName + "_tmp";
146 | File tmpDir = new File(tempDirPath);
147 | File tmpFile = new File(tempDirPath, tempFileName);
148 | if (!tmpDir.exists()) {
149 | tmpDir.mkdirs();
150 | }
151 |
152 | RandomAccessFile accessTmpFile = new RandomAccessFile(tmpFile, "rw");
153 | long offset = CHUNK_SIZE * param.getChunk();
154 | //定位到该分片的偏移量
155 | accessTmpFile.seek(offset);
156 | //写入该分片数据
157 | accessTmpFile.write(param.getFile().getBytes());
158 | // 释放
159 | accessTmpFile.close();
160 |
161 | boolean isOk = checkAndSetUploadProgress(param, tempDirPath);
162 | if (isOk) {
163 | boolean flag = renameFile(tmpFile, fileName);
164 | System.out.println("upload complete !!" + flag + " name=" + fileName);
165 | }
166 | }
167 |
168 | public void uploadFileByMappedByteBuffer(MultipartFileParam param) throws IOException {
169 | String fileName = param.getName();
170 | String uploadDirPath = finalDirPath + param.getMd5();
171 | String tempFileName = fileName + "_tmp";
172 | File tmpDir = new File(uploadDirPath);
173 | File tmpFile = new File(uploadDirPath, tempFileName);
174 | if (!tmpDir.exists()) {
175 | tmpDir.mkdirs();
176 | }
177 |
178 | RandomAccessFile tempRaf = new RandomAccessFile(tmpFile, "rw");
179 | FileChannel fileChannel = tempRaf.getChannel();
180 |
181 | //写入该分片数据
182 | long offset = CHUNK_SIZE * param.getChunk();
183 | byte[] fileData = param.getFile().getBytes();
184 | MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, offset, fileData.length);
185 | mappedByteBuffer.put(fileData);
186 | // 释放
187 | FileMD5Util.freedMappedByteBuffer(mappedByteBuffer);
188 | fileChannel.close();
189 |
190 | boolean isOk = checkAndSetUploadProgress(param, uploadDirPath);
191 | if (isOk) {
192 | boolean flag = renameFile(tmpFile, fileName);
193 | System.out.println("upload complete !!" + flag + " name=" + fileName);
194 | }
195 | }
196 | ```
197 |
198 |
199 | ### 秒传功能
200 |
201 | 秒传功能,相信大家都体现过了,网盘上传的时候,发现上传的文件秒传了。其实原理稍微有研究过的同学应该知道,其实就是检验文件MD5,记录下上传到系统的文件的MD5,在一个文件上传前先获取文件内容MD5值或者部分取值MD5,然后在匹配系统上的数据。
202 | *Breakpoint-http*实现秒传原理,客户端选择文件之后,点击上传的时候触发获取文件MD5值,获取MD5后调用系统一个接口(/index/checkFileMd5),查询该MD5是否已经存在(我在该项目中用redis来存储数据,用文件MD5值来作key,value是文件存储的地址。)接口返回检查状态,然后再进行下一步的操作。相信大家看代码就能明白了。
203 | 嗯,前端的MD5取值也是用了webuploader自带的功能,这还是个不错的工具。
204 |
205 |
206 | ### 断点续传
207 | 断点续传,就是在文件上传的过程中发生了中断,人为因素(暂停)或者不可抗力(断网或者网络差)导致了文件上传到一半失败了。然后在环境恢复的时候,重新上传该文件,而不至于是从新开始上传的。
208 | 前面也已经讲过,断点续传的功能是基于分块上传来实现的,把一个大文件分成很多个小块,服务端能够把每个上传成功的分块都落地下来,客户端在上传文件开始时调用接口快速验证,条件选择跳过某个分块。
209 | 实现原理,就是在每个文件上传前,就获取到文件MD5取值,在上传文件前调用接口(/index/checkFileMd5,没错也是秒传的检验接口)如果获取的文件状态是未完成,则返回所有的还没上传的分块的编号,然后前端进行条件筛算出哪些没上传的分块,然后进行上传。
210 | ```
211 | /**
212 | * 秒传判断,断点判断
213 | *
214 | * @return
215 | */
216 | @RequestMapping(value = "checkFileMd5", method = RequestMethod.POST)
217 | @ResponseBody
218 | public Object checkFileMd5(String md5) throws IOException {
219 | Object processingObj = stringRedisTemplate.opsForHash().get(Constants.FILE_UPLOAD_STATUS, md5);
220 | if (processingObj == null) {
221 | return new ResultVo(ResultStatus.NO_HAVE);
222 | }
223 | String processingStr = processingObj.toString();
224 | boolean processing = Boolean.parseBoolean(processingStr);
225 | String value = stringRedisTemplate.opsForValue().get(Constants.FILE_MD5_KEY + md5);
226 | if (processing) {
227 | return new ResultVo(ResultStatus.IS_HAVE, value);
228 | } else {
229 | File confFile = new File(value);
230 | byte[] completeList = FileUtils.readFileToByteArray(confFile);
231 | List missChunkList = new LinkedList<>();
232 | for (int i = 0; i < completeList.length; i++) {
233 | if (completeList[i] != Byte.MAX_VALUE) {
234 | missChunkList.add(i + "");
235 | }
236 | }
237 | return new ResultVo<>(ResultStatus.ING_HAVE, missChunkList);
238 | }
239 | }
240 | ```
241 |
242 |
243 | ## 总结
244 | 身为一个具有拖延症的程序猿,写个文档及其不容易,这方面还是优待加强,写代码时间都还没写这个文档长,并且写了那么久还那么烂的文档。实在抱歉,望谅解。
245 | 项目的Bug和改进点,可在评论去留言或者在GitHub或者OSChina上以issue的方式直接提交给我,谢谢大家。
246 |
247 | ##参考文献
248 |
249 | [1]http://fex.baidu.com/webuploader/
250 | [2]http://www.zuidaima.com/blog/2819949848316928.htm
251 | [3]https://my.oschina.net/feichexia/blog/212318
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | group 'win.pangniu.learn'
2 | version '1.0-SNAPSHOT'
3 |
4 | apply plugin: 'java'
5 | apply plugin: 'war'
6 | apply plugin: 'maven'
7 | apply plugin: 'spring-boot'
8 |
9 | sourceCompatibility = 1.7
10 |
11 | buildscript {
12 | repositories {
13 | mavenLocal()
14 | mavenCentral()
15 | jcenter()
16 | }
17 | dependencies {
18 | classpath(
19 | "org.springframework.boot:spring-boot-gradle-plugin:1.5.2.RELEASE"
20 | )
21 | }
22 | }
23 |
24 | repositories {
25 | mavenCentral()
26 | }
27 |
28 | dependencies {
29 | compile(
30 | 'org.springframework.boot:spring-boot-starter-web',
31 | 'org.springframework.boot:spring-boot-starter-data-redis'
32 | )
33 | // https://mvnrepository.com/artifact/commons-io/commons-io
34 | compile group: 'commons-io', name: 'commons-io', version: '2.5'
35 | // https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload
36 | compile group: 'commons-fileupload', name: 'commons-fileupload', version: '1.3.2'
37 | // https://mvnrepository.com/artifact/org.apache.commons/commons-lang3
38 | compile group: 'org.apache.commons', name: 'commons-lang3', version: '3.5'
39 | //providedCompile("org.springframework.boot:spring-boot-starter-tomcat")
40 | testCompile group: 'junit', name: 'junit', version: '4.12'
41 | testCompile('org.springframework.boot:spring-boot-starter-test')
42 | }
43 |
44 | // war
45 | war {
46 | archiveName 'bphttp.war'
47 | }
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'breakpoint-http'
2 |
3 |
--------------------------------------------------------------------------------
/src/main/java/win/pangniu/learn/App.java:
--------------------------------------------------------------------------------
1 | package win.pangniu.learn;
2 |
3 | import org.springframework.boot.CommandLineRunner;
4 | import org.springframework.boot.SpringApplication;
5 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
6 | import org.springframework.boot.autoconfigure.SpringBootApplication;
7 | import org.springframework.boot.web.support.SpringBootServletInitializer;
8 | import org.springframework.context.annotation.Bean;
9 | import org.springframework.context.annotation.ComponentScan;
10 | import win.pangniu.learn.service.StorageService;
11 |
12 | /**
13 | * 启动类
14 | * Created by wenwen on 2017/4/11.
15 | * version 1.0
16 | */
17 | @SpringBootApplication
18 | @ComponentScan
19 | @EnableAutoConfiguration
20 | public class App extends SpringBootServletInitializer {
21 |
22 | public static void main(String[] args) {
23 | SpringApplication.run(App.class, args);
24 | }
25 |
26 | @Bean
27 | CommandLineRunner init(final StorageService storageService) {
28 | return new CommandLineRunner() {
29 | @Override
30 | public void run(String... args) throws Exception {
31 | storageService.deleteAll();
32 | storageService.init();
33 | }
34 | };
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/java/win/pangniu/learn/controller/IndexController.java:
--------------------------------------------------------------------------------
1 | package win.pangniu.learn.controller;
2 |
3 | import org.apache.commons.fileupload.servlet.ServletFileUpload;
4 | import org.apache.commons.io.FileUtils;
5 | import org.slf4j.Logger;
6 | import org.slf4j.LoggerFactory;
7 | import org.springframework.beans.factory.annotation.Autowired;
8 | import org.springframework.data.redis.core.StringRedisTemplate;
9 | import org.springframework.http.ResponseEntity;
10 | import org.springframework.stereotype.Controller;
11 | import org.springframework.web.bind.annotation.RequestMapping;
12 | import org.springframework.web.bind.annotation.RequestMethod;
13 | import org.springframework.web.bind.annotation.ResponseBody;
14 | import win.pangniu.learn.param.MultipartFileParam;
15 | import win.pangniu.learn.service.StorageService;
16 | import win.pangniu.learn.utils.Constants;
17 | import win.pangniu.learn.vo.ResultStatus;
18 | import win.pangniu.learn.vo.ResultVo;
19 |
20 | import javax.servlet.http.HttpServletRequest;
21 | import java.io.File;
22 | import java.io.IOException;
23 | import java.util.LinkedList;
24 | import java.util.List;
25 |
26 | /**
27 | * 默认控制层
28 | * Created by wenwen on 2017/4/11.
29 | * version 1.0
30 | */
31 | @Controller
32 | @RequestMapping(value = "/index")
33 | public class IndexController {
34 |
35 | private Logger logger = LoggerFactory.getLogger(IndexController.class);
36 |
37 | @Autowired
38 | private StringRedisTemplate stringRedisTemplate;
39 |
40 | @Autowired
41 | private StorageService storageService;
42 |
43 | /**
44 | * 秒传判断,断点判断
45 | *
46 | * @return
47 | */
48 | @RequestMapping(value = "checkFileMd5", method = RequestMethod.POST)
49 | @ResponseBody
50 | public Object checkFileMd5(String md5) throws IOException {
51 | Object processingObj = stringRedisTemplate.opsForHash().get(Constants.FILE_UPLOAD_STATUS, md5);
52 | if (processingObj == null) {
53 | return new ResultVo(ResultStatus.NO_HAVE);
54 | }
55 | String processingStr = processingObj.toString();
56 | boolean processing = Boolean.parseBoolean(processingStr);
57 | String value = stringRedisTemplate.opsForValue().get(Constants.FILE_MD5_KEY + md5);
58 | if (processing) {
59 | return new ResultVo(ResultStatus.IS_HAVE, value);
60 | } else {
61 | File confFile = new File(value);
62 | byte[] completeList = FileUtils.readFileToByteArray(confFile);
63 | List missChunkList = new LinkedList<>();
64 | for (int i = 0; i < completeList.length; i++) {
65 | if (completeList[i] != Byte.MAX_VALUE) {
66 | missChunkList.add(i + "");
67 | }
68 | }
69 | return new ResultVo<>(ResultStatus.ING_HAVE, missChunkList);
70 | }
71 | }
72 |
73 | /**
74 | * 上传文件
75 | *
76 | * @param param
77 | * @param request
78 | * @return
79 | * @throws Exception
80 | */
81 | @RequestMapping(value = "/fileUpload", method = RequestMethod.POST)
82 | @ResponseBody
83 | public ResponseEntity fileUpload(MultipartFileParam param, HttpServletRequest request) {
84 | boolean isMultipart = ServletFileUpload.isMultipartContent(request);
85 | if (isMultipart) {
86 | logger.info("上传文件start。");
87 | try {
88 | // 方法1
89 | //storageService.uploadFileRandomAccessFile(param);
90 | // 方法2 这个更快点
91 | storageService.uploadFileByMappedByteBuffer(param);
92 | } catch (IOException e) {
93 | e.printStackTrace();
94 | logger.error("文件上传失败。{}", param.toString());
95 | }
96 | logger.info("上传文件end。");
97 | }
98 | return ResponseEntity.ok().body("上传成功。");
99 | }
100 |
101 |
102 | }
103 |
--------------------------------------------------------------------------------
/src/main/java/win/pangniu/learn/param/MultipartFileParam.java:
--------------------------------------------------------------------------------
1 | package win.pangniu.learn.param;
2 |
3 |
4 | import org.springframework.web.multipart.MultipartFile;
5 |
6 | /**
7 | * Created by wenwen on 2017/4/16.
8 | * version 1.0
9 | */
10 | public class MultipartFileParam {
11 |
12 | // 用户id
13 | private String uid;
14 | //任务ID
15 | private String id;
16 | //总分片数量
17 | private int chunks;
18 | //当前为第几块分片
19 | private int chunk;
20 | //当前分片大小
21 | private long size = 0L;
22 | //文件名
23 | private String name;
24 | //分片对象
25 | private MultipartFile file;
26 | // MD5
27 | private String md5;
28 |
29 | public String getUid() {
30 | return uid;
31 | }
32 |
33 | public void setUid(String uid) {
34 | this.uid = uid;
35 | }
36 |
37 | public String getId() {
38 | return id;
39 | }
40 |
41 | public void setId(String id) {
42 | this.id = id;
43 | }
44 |
45 | public int getChunks() {
46 | return chunks;
47 | }
48 |
49 | public void setChunks(int chunks) {
50 | this.chunks = chunks;
51 | }
52 |
53 | public int getChunk() {
54 | return chunk;
55 | }
56 |
57 | public void setChunk(int chunk) {
58 | this.chunk = chunk;
59 | }
60 |
61 | public long getSize() {
62 | return size;
63 | }
64 |
65 | public void setSize(long size) {
66 | this.size = size;
67 | }
68 |
69 | public String getName() {
70 | return name;
71 | }
72 |
73 | public void setName(String name) {
74 | this.name = name;
75 | }
76 |
77 | public MultipartFile getFile() {
78 | return file;
79 | }
80 |
81 | public void setFile(MultipartFile file) {
82 | this.file = file;
83 | }
84 |
85 | public String getMd5() {
86 | return md5;
87 | }
88 |
89 | public void setMd5(String md5) {
90 | this.md5 = md5;
91 | }
92 |
93 | @Override
94 | public String toString() {
95 | return "MultipartFileParam{" +
96 | "uid='" + uid + '\'' +
97 | ", id='" + id + '\'' +
98 | ", chunks=" + chunks +
99 | ", chunk=" + chunk +
100 | ", size=" + size +
101 | ", name='" + name + '\'' +
102 | ", file=" + file +
103 | ", md5='" + md5 + '\'' +
104 | '}';
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/src/main/java/win/pangniu/learn/service/StorageService.java:
--------------------------------------------------------------------------------
1 | package win.pangniu.learn.service;
2 |
3 | import win.pangniu.learn.param.MultipartFileParam;
4 |
5 | import java.io.IOException;
6 |
7 | /**
8 | * 存储操作的service
9 | * Created by 超文 on 2017/5/2.
10 | */
11 | public interface StorageService {
12 |
13 | /**
14 | * 删除全部数据
15 | */
16 | void deleteAll();
17 |
18 | /**
19 | * 初始化方法
20 | */
21 | void init();
22 |
23 | /**
24 | * 上传文件方法1
25 | *
26 | * @param param
27 | * @throws IOException
28 | */
29 | void uploadFileRandomAccessFile(MultipartFileParam param) throws IOException;
30 |
31 | /**
32 | * 上传文件方法2
33 | * 处理文件分块,基于MappedByteBuffer来实现文件的保存
34 | *
35 | * @param param
36 | * @throws IOException
37 | */
38 | void uploadFileByMappedByteBuffer(MultipartFileParam param) throws IOException;
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/win/pangniu/learn/service/impl/StorageServiceImpl.java:
--------------------------------------------------------------------------------
1 | package win.pangniu.learn.service.impl;
2 |
3 | import org.apache.commons.io.FileUtils;
4 | import org.slf4j.Logger;
5 | import org.slf4j.LoggerFactory;
6 | import org.springframework.beans.factory.annotation.Autowired;
7 | import org.springframework.beans.factory.annotation.Value;
8 | import org.springframework.data.redis.core.StringRedisTemplate;
9 | import org.springframework.stereotype.Service;
10 | import org.springframework.util.FileSystemUtils;
11 | import win.pangniu.learn.param.MultipartFileParam;
12 | import win.pangniu.learn.service.StorageService;
13 | import win.pangniu.learn.utils.Constants;
14 | import win.pangniu.learn.utils.FileMD5Util;
15 |
16 | import java.io.File;
17 | import java.io.IOException;
18 | import java.io.RandomAccessFile;
19 | import java.nio.MappedByteBuffer;
20 | import java.nio.channels.FileChannel;
21 | import java.nio.file.FileAlreadyExistsException;
22 | import java.nio.file.Files;
23 | import java.nio.file.Path;
24 | import java.nio.file.Paths;
25 |
26 | /**
27 | * Created by 超文 on 2017/5/2.
28 | */
29 | @Service
30 | public class StorageServiceImpl implements StorageService {
31 |
32 | private final Logger logger = LoggerFactory.getLogger(StorageServiceImpl.class);
33 | // 保存文件的根目录
34 | private Path rootPaht;
35 |
36 | @Autowired
37 | private StringRedisTemplate stringRedisTemplate;
38 |
39 | //这个必须与前端设定的值一致
40 | @Value("${breakpoint.upload.chunkSize}")
41 | private long CHUNK_SIZE;
42 |
43 | @Value("${breakpoint.upload.dir}")
44 | private String finalDirPath;
45 |
46 | @Autowired
47 | public StorageServiceImpl(@Value("${breakpoint.upload.dir}") String location) {
48 | this.rootPaht = Paths.get(location);
49 | }
50 |
51 | @Override
52 | public void deleteAll() {
53 | logger.info("开发初始化清理数据,start");
54 | FileSystemUtils.deleteRecursively(rootPaht.toFile());
55 | stringRedisTemplate.delete(Constants.FILE_UPLOAD_STATUS);
56 | stringRedisTemplate.delete(Constants.FILE_MD5_KEY);
57 | logger.info("开发初始化清理数据,end");
58 | }
59 |
60 | @Override
61 | public void init() {
62 | try {
63 | Files.createDirectory(rootPaht);
64 | } catch (FileAlreadyExistsException e) {
65 | logger.error("文件夹已经存在了,不用再创建。");
66 | } catch (IOException e) {
67 | logger.error("初始化root文件夹失败。", e);
68 | }
69 | }
70 |
71 | @Override
72 | public void uploadFileRandomAccessFile(MultipartFileParam param) throws IOException {
73 | String fileName = param.getName();
74 | String tempDirPath = finalDirPath + param.getMd5();
75 | String tempFileName = fileName + "_tmp";
76 | File tmpDir = new File(tempDirPath);
77 | File tmpFile = new File(tempDirPath, tempFileName);
78 | if (!tmpDir.exists()) {
79 | tmpDir.mkdirs();
80 | }
81 |
82 | RandomAccessFile accessTmpFile = new RandomAccessFile(tmpFile, "rw");
83 | long offset = CHUNK_SIZE * param.getChunk();
84 | //定位到该分片的偏移量
85 | accessTmpFile.seek(offset);
86 | //写入该分片数据
87 | accessTmpFile.write(param.getFile().getBytes());
88 | // 释放
89 | accessTmpFile.close();
90 |
91 | boolean isOk = checkAndSetUploadProgress(param, tempDirPath);
92 | if (isOk) {
93 | boolean flag = renameFile(tmpFile, fileName);
94 | System.out.println("upload complete !!" + flag + " name=" + fileName);
95 | }
96 | }
97 |
98 | @Override
99 | public void uploadFileByMappedByteBuffer(MultipartFileParam param) throws IOException {
100 | String fileName = param.getName();
101 | String uploadDirPath = finalDirPath + param.getMd5();
102 | String tempFileName = fileName + "_tmp";
103 | File tmpDir = new File(uploadDirPath);
104 | File tmpFile = new File(uploadDirPath, tempFileName);
105 | if (!tmpDir.exists()) {
106 | tmpDir.mkdirs();
107 | }
108 |
109 | RandomAccessFile tempRaf = new RandomAccessFile(tmpFile, "rw");
110 | FileChannel fileChannel = tempRaf.getChannel();
111 |
112 | //写入该分片数据
113 | long offset = CHUNK_SIZE * param.getChunk();
114 | byte[] fileData = param.getFile().getBytes();
115 | MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, offset, fileData.length);
116 | mappedByteBuffer.put(fileData);
117 | // 释放
118 | FileMD5Util.freedMappedByteBuffer(mappedByteBuffer);
119 | fileChannel.close();
120 |
121 | boolean isOk = checkAndSetUploadProgress(param, uploadDirPath);
122 | if (isOk) {
123 | boolean flag = renameFile(tmpFile, fileName);
124 | System.out.println("upload complete !!" + flag + " name=" + fileName);
125 | }
126 | }
127 |
128 | /**
129 | * 检查并修改文件上传进度
130 | *
131 | * @param param
132 | * @param uploadDirPath
133 | * @return
134 | * @throws IOException
135 | */
136 | private boolean checkAndSetUploadProgress(MultipartFileParam param, String uploadDirPath) throws IOException {
137 | String fileName = param.getName();
138 | File confFile = new File(uploadDirPath, fileName + ".conf");
139 | RandomAccessFile accessConfFile = new RandomAccessFile(confFile, "rw");
140 | //把该分段标记为 true 表示完成
141 | System.out.println("set part " + param.getChunk() + " complete");
142 | accessConfFile.setLength(param.getChunks());
143 | accessConfFile.seek(param.getChunk());
144 | accessConfFile.write(Byte.MAX_VALUE);
145 |
146 | //completeList 检查是否全部完成,如果数组里是否全部都是(全部分片都成功上传)
147 | byte[] completeList = FileUtils.readFileToByteArray(confFile);
148 | byte isComplete = Byte.MAX_VALUE;
149 | for (int i = 0; i < completeList.length && isComplete == Byte.MAX_VALUE; i++) {
150 | //与运算, 如果有部分没有完成则 isComplete 不是 Byte.MAX_VALUE
151 | isComplete = (byte) (isComplete & completeList[i]);
152 | System.out.println("check part " + i + " complete?:" + completeList[i]);
153 | }
154 |
155 | accessConfFile.close();
156 | if (isComplete == Byte.MAX_VALUE) {
157 | stringRedisTemplate.opsForHash().put(Constants.FILE_UPLOAD_STATUS, param.getMd5(), "true");
158 | stringRedisTemplate.opsForValue().set(Constants.FILE_MD5_KEY + param.getMd5(), uploadDirPath + "/" + fileName);
159 | return true;
160 | } else {
161 | if (!stringRedisTemplate.opsForHash().hasKey(Constants.FILE_UPLOAD_STATUS, param.getMd5())) {
162 | stringRedisTemplate.opsForHash().put(Constants.FILE_UPLOAD_STATUS, param.getMd5(), "false");
163 | }
164 | if (stringRedisTemplate.hasKey(Constants.FILE_MD5_KEY + param.getMd5())) {
165 | stringRedisTemplate.opsForValue().set(Constants.FILE_MD5_KEY + param.getMd5(), uploadDirPath + "/" + fileName + ".conf");
166 | }
167 | return false;
168 | }
169 | }
170 |
171 | /**
172 | * 文件重命名
173 | *
174 | * @param toBeRenamed 将要修改名字的文件
175 | * @param toFileNewName 新的名字
176 | * @return
177 | */
178 | public boolean renameFile(File toBeRenamed, String toFileNewName) {
179 | //检查要重命名的文件是否存在,是否是文件
180 | if (!toBeRenamed.exists() || toBeRenamed.isDirectory()) {
181 | logger.info("File does not exist: " + toBeRenamed.getName());
182 | return false;
183 | }
184 | String p = toBeRenamed.getParent();
185 | File newFile = new File(p + File.separatorChar + toFileNewName);
186 | //修改文件名
187 | return toBeRenamed.renameTo(newFile);
188 | }
189 |
190 | }
191 |
--------------------------------------------------------------------------------
/src/main/java/win/pangniu/learn/utils/Constants.java:
--------------------------------------------------------------------------------
1 | package win.pangniu.learn.utils;
2 |
3 | import java.util.HashMap;
4 | import java.util.Map;
5 |
6 | /**
7 | * 常量表
8 | * Created by 超文 on 2017/05/02.
9 | * version 1.0
10 | */
11 | public interface Constants {
12 | /**
13 | * 异常信息统一头信息
14 | * 非常遗憾的通知您,程序发生了异常
15 | */
16 | public static final String Exception_Head = "boom。炸了。";
17 | /**
18 | * 缓存键值
19 | */
20 | public static final Map, String> cacheKeyMap = new HashMap<>();
21 | /**
22 | * 保存文件所在路径的key,eg.FILE_MD5:1243jkalsjflkwaejklgjawe
23 | */
24 | public static final String FILE_MD5_KEY = "FILE_MD5:";
25 | /**
26 | * 保存上传文件的状态
27 | */
28 | public static final String FILE_UPLOAD_STATUS = "FILE_UPLOAD_STATUS";
29 |
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/java/win/pangniu/learn/utils/FileMD5Util.java:
--------------------------------------------------------------------------------
1 | package win.pangniu.learn.utils;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 |
6 | import java.io.File;
7 | import java.io.FileInputStream;
8 | import java.io.FileNotFoundException;
9 | import java.io.IOException;
10 | import java.lang.reflect.Method;
11 | import java.math.BigInteger;
12 | import java.nio.MappedByteBuffer;
13 | import java.nio.channels.FileChannel;
14 | import java.security.AccessController;
15 | import java.security.MessageDigest;
16 | import java.security.PrivilegedAction;
17 |
18 | /**
19 | * 文件md5值
20 | * Created by 超文 on 2016/10/10.
21 | * version 1.0
22 | */
23 | public class FileMD5Util {
24 |
25 | private final static Logger logger = LoggerFactory.getLogger(FileMD5Util.class);
26 |
27 | public static String getFileMD5(File file) throws FileNotFoundException {
28 | String value = null;
29 | FileInputStream in = new FileInputStream(file);
30 | MappedByteBuffer byteBuffer = null;
31 | try {
32 | byteBuffer = in.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, file.length());
33 | MessageDigest md5 = MessageDigest.getInstance("MD5");
34 | md5.update(byteBuffer);
35 | BigInteger bi = new BigInteger(1, md5.digest());
36 | value = bi.toString(16);
37 | if (value.length() < 32) {
38 | value = "0" + value;
39 | }
40 | } catch (Exception e) {
41 | e.printStackTrace();
42 | } finally {
43 | if (null != in) {
44 | try {
45 | in.getChannel().close();
46 | in.close();
47 | } catch (IOException e) {
48 | logger.error("get file md5 error!!!", e);
49 | }
50 | }
51 | if (null != byteBuffer) {
52 | freedMappedByteBuffer(byteBuffer);
53 | }
54 | }
55 | return value;
56 | }
57 |
58 | /**
59 | * 在MappedByteBuffer释放后再对它进行读操作的话就会引发jvm crash,在并发情况下很容易发生
60 | * 正在释放时另一个线程正开始读取,于是crash就发生了。所以为了系统稳定性释放前一般需要检 查是否还有线程在读或写
61 | *
62 | * @param mappedByteBuffer
63 | */
64 | public static void freedMappedByteBuffer(final MappedByteBuffer mappedByteBuffer) {
65 | try {
66 | if (mappedByteBuffer == null) {
67 | return;
68 | }
69 |
70 | mappedByteBuffer.force();
71 | AccessController.doPrivileged(new PrivilegedAction',c.html(a)},getFlash:function(){return this._flash?this._flash:(this._flash=g("#"+this.uid).get(0),this._flash)}}),f.register=function(a,c){return c=i[a]=b.inherits(d,g.extend({flashExec:function(){var a=this.owner,b=this.getRuntime();return b.flashExec.apply(a,arguments)}},c))},e()>=11.4&&c.addRuntime(h,f),f}),b("runtime/flash/filepicker",["base","runtime/flash/runtime"],function(a,b){var c=a.$;return b.register("FilePicker",{init:function(a){var b,d,e=c.extend({},a);for(b=e.accept&&e.accept.length,d=0;b>d;d++)e.accept[d].title||(e.accept[d].title="Files");delete e.button,delete e.id,delete e.container,this.flashExec("FilePicker","init",e)},destroy:function(){this.flashExec("FilePicker","destroy")}})}),b("runtime/flash/image",["runtime/flash/runtime"],function(a){return a.register("Image",{loadFromBlob:function(a){var b=this.owner;b.info()&&this.flashExec("Image","info",b.info()),b.meta()&&this.flashExec("Image","meta",b.meta()),this.flashExec("Image","loadFromBlob",a.uid)}})}),b("runtime/flash/transport",["base","runtime/flash/runtime","runtime/client"],function(b,c,d){var e=b.$;return c.register("Transport",{init:function(){this._status=0,this._response=null,this._responseJson=null},send:function(){var a,b=this.owner,c=this.options,d=this._initAjax(),f=b._blob,g=c.server;d.connectRuntime(f.ruid),c.sendAsBinary?(g+=(/\?/.test(g)?"&":"?")+e.param(b._formData),a=f.uid):(e.each(b._formData,function(a,b){d.exec("append",a,b)}),d.exec("appendBlob",c.fileVal,f.uid,c.filename||b._formData.name||"")),this._setRequestHeader(d,c.headers),d.exec("send",{method:c.method,url:g,forceURLStream:c.forceURLStream,mimeType:"application/octet-stream"},a)},getStatus:function(){return this._status},getResponse:function(){return this._response||""},getResponseAsJson:function(){return this._responseJson},abort:function(){var a=this._xhr;a&&(a.exec("abort"),a.destroy(),this._xhr=a=null)},destroy:function(){this.abort()},_initAjax:function(){var b=this,c=new d("XMLHttpRequest");return c.on("uploadprogress progress",function(a){var c=a.loaded/a.total;return c=Math.min(1,Math.max(0,c)),b.trigger("progress",c)}),c.on("load",function(){var d,e=c.exec("getStatus"),f=!1,g="";return c.off(),b._xhr=null,e>=200&&300>e?f=!0:e>=500&&600>e?(f=!0,g="server"):g="http",f&&(b._response=c.exec("getResponse"),b._response=decodeURIComponent(b._response),d=a.JSON&&a.JSON.parse||function(a){try{return new Function("return "+a).call()}catch(b){return{}}},b._responseJson=b._response?d(b._response):{}),c.destroy(),c=null,g?b.trigger("error",g):b.trigger("load")}),c.on("error",function(){c.off(),b._xhr=null,b.trigger("error","http")}),b._xhr=c,c},_setRequestHeader:function(a,b){e.each(b,function(b,c){a.exec("setRequestHeader",b,c)})}})}),b("runtime/flash/blob",["runtime/flash/runtime","lib/blob"],function(a,b){return a.register("Blob",{slice:function(a,c){var d=this.flashExec("Blob","slice",a,c);return new b(d.uid,d)}})}),b("runtime/flash/md5",["runtime/flash/runtime"],function(a){return a.register("Md5",{init:function(){},loadFromBlob:function(a){return this.flashExec("Md5","loadFromBlob",a.uid)}})}),b("preset/all",["base","widgets/filednd","widgets/filepaste","widgets/filepicker","widgets/image","widgets/queue","widgets/runtime","widgets/upload","widgets/validator","widgets/md5","runtime/html5/blob","runtime/html5/dnd","runtime/html5/filepaste","runtime/html5/filepicker","runtime/html5/imagemeta/exif","runtime/html5/androidpatch","runtime/html5/image","runtime/html5/transport","runtime/html5/md5","runtime/flash/filepicker","runtime/flash/image","runtime/flash/transport","runtime/flash/blob","runtime/flash/md5"],function(a){return a}),b("widgets/log",["base","uploader","widgets/widget"],function(a,b){function c(a){var b=e.extend({},d,a),c=f.replace(/^(.*)\?/,"$1"+e.param(b)),g=new Image;g.src=c}var d,e=a.$,f=" http://static.tieba.baidu.com/tb/pms/img/st.gif??",g=(location.hostname||location.host||"protected").toLowerCase(),h=g&&/baidu/i.exec(g);if(h)return d={dv:3,master:"webuploader",online:/test/.exec(g)?0:1,module:"",product:g,type:0},b.register({name:"log",init:function(){var a=this.owner,b=0,d=0;a.on("error",function(a){c({type:2,c_error_code:a})}).on("uploadError",function(a,b){c({type:2,c_error_code:"UPLOAD_ERROR",c_reason:""+b})}).on("uploadComplete",function(a){b++,d+=a.size}).on("uploadFinished",function(){c({c_count:b,c_size:d}),b=d=0}),c({c_usage:1})}})}),b("webuploader",["preset/all","widgets/log"],function(a){return a}),c("webuploader")});
--------------------------------------------------------------------------------
/src/main/webapp/index2.jsp:
--------------------------------------------------------------------------------
1 | <%@ page import="java.util.HashMap" %>
2 | <%@ page import="java.util.Map" %><%--
3 | Created by IntelliJ IDEA.
4 | User: wenwen
5 | Date: 2017/4/11
6 | Time: 22:03
7 | To change this template use File | Settings | File Templates.
8 | --%>
9 | <%@ page contentType="text/html;charset=UTF-8" language="java" %>
10 | <%
11 | // 设置请求的编码为UTF-8;
12 | request.setCharacterEncoding("UTF-8");
13 | // 设置响应的编码为UTF-8
14 | response.setCharacterEncoding("UTF-8");
15 |
16 | // 帐号密码数据准备
17 | Map accountMap = new HashMap<>();
18 | accountMap.put("ftpuser", "password");
19 |
20 | // 获取参数
21 | String userName = request.getParameter("userName");
22 | System.err.println(userName);
23 | String result;
24 | if (userName == null || "".equals(userName.trim())) {
25 | result = "{\"code\":101,\"msg\":\"参数为空\"}";
26 | } else {
27 | String password = accountMap.get(userName);
28 | if (password == null || "".equals(password)) {
29 | result = "{\"code\":102,\"msg\":\"结果为空。\"}";
30 | } else {
31 | char[] array = password.toCharArray();
32 | for (int i = 0; i < array.length; i++) {
33 | array[i] = (char) (array[i] ^ 20160831);
34 | }
35 | System.out.println("结果如下:");
36 | System.out.println(new String(array));//输出加密或者解密结果
37 | result = "{\"code\":100,\"msg\":'成功',\"data\":\"" + new String(array) + "\"}";
38 | }
39 | }
40 |
41 | // 将处理后的结果返回给客户端
42 | response.getWriter().write("" + result);
43 | %>
--------------------------------------------------------------------------------
/src/test/java/win/pangniu/learn/test/AppTest.java:
--------------------------------------------------------------------------------
1 | package win.pangniu.learn.test;
2 |
3 | import org.junit.Test;
4 | import org.springframework.beans.factory.annotation.Autowired;
5 | import org.springframework.dao.DataAccessException;
6 | import org.springframework.data.redis.core.RedisOperations;
7 | import org.springframework.data.redis.core.RedisTemplate;
8 | import org.springframework.data.redis.core.SessionCallback;
9 | import org.springframework.data.redis.core.StringRedisTemplate;
10 |
11 | import java.io.BufferedReader;
12 | import java.io.InputStreamReader;
13 | import java.net.URL;
14 | import java.net.URLConnection;
15 | import java.util.List;
16 |
17 | public class AppTest extends BaseTest {
18 |
19 | @Autowired
20 | private StringRedisTemplate stringRedisTemplate;
21 |
22 | @Autowired
23 | private RedisTemplate redisTemplate;
24 |
25 | @Test
26 | public void test() {
27 | long startTime = System.currentTimeMillis();
28 | int[][] data = new int[10000][10000];
29 | for (int i = 0; i < 10000; i++) {
30 | for (int j = 0; j < 10000; j++) {
31 | data[i][j] = i + j;
32 | }
33 | }
34 | System.err.println((System.currentTimeMillis() - startTime) + "ms");
35 | }
36 |
37 | @Test
38 | public void testRedisMap() {
39 | Object object = redisTemplate.execute(new SessionCallback() {
40 | @Override
41 | public Object execute(RedisOperations operations) throws DataAccessException {
42 | operations.multi();
43 | operations.opsForValue().get("test");
44 | operations.delete("test");
45 | operations.opsForValue().set("test", "6");
46 | List rs = operations.exec();
47 | System.out.println(" rs:" + rs.toString());
48 | return rs;
49 | }
50 | });
51 | List