其实, 我是个小白... 如果我写的东西 有错误,麻烦指出来。。。
多文件上传 与 进度回调
前端做Http请求偶尔会遇到 multipartform-data 表单提交的问题,我们先看 OKHttp拦截器打印(印度的印)的参数, 我一共提交了4个文件, 3个字符串
LogTrack warn org.alex.okhttp [ (HttpLogInterceptor.java:145)#printLog] 打印请求参数:
POST contentType = multipartform-data
请求体:
Content-Disposition: form-data; name="file"; filename="黄喉蜂虎.png"
Content-Type: image/png
Content-Length: 321229
富媒体文件, 无法用字符串描述
7533c20a-9a0b-4c3d-8e3d-c0af3fae4d32
Content-Disposition: form-data; name="userPortrait"; filename="黄喉蜂虎.png"
Content-Type: image/png
Content-Length: 321229
富媒体文件, 无法用字符串描述
7533c20a-9a0b-4c3d-8e3d-c0af3fae4d32
Content-Disposition: form-data; name="file"; filename="001.gif"
Content-Type: image/gif
Content-Length: 123453
富媒体文件, 无法用字符串描述
7533c20a-9a0b-4c3d-8e3d-c0af3fae4d32
Content-Disposition: form-data; name="file"; filename="b5h4.mp4"
Content-Type: application/octet-stream
Content-Length: 7159248
富媒体文件, 无法用字符串描述
7533c20a-9a0b-4c3d-8e3d-c0af3fae4d32
Content-Disposition: form-data; name="id"
Content-Length: 1
1
7533c20a-9a0b-4c3d-8e3d-c0af3fae4d32
Content-Disposition: form-data; name="phone"
Content-Length: 11
131460xxxxx
7533c20a-9a0b-4c3d-8e3d-c0af3fae4d32
Content-Disposition: form-data; name="pwd"
Content-Length: 6
123456
7533c20a-9a0b-4c3d-8e3d-c0af3fae4d32
LogTrack error com.alex.httpapp.module.fileupload [ (FileUploadView.kt:31)#FileUploadView$onCreateData$1#onNext] WrapperBean{code='100', message='SUCCESS', data=上传成功}
LogTrack error com.alex.httpapp.baselibrary.mvp [ (AbsView.kt:30)#onCreate] ************** 1 继续请求,其他取消请求 **************
LogTrack warn org.alex.okhttp [ (HttpLogInterceptor.java:145)#printLog] 打印返回数据:
POST http://127.0.0.1:8081/AppServer/uploadUserPortrait (709ms)
body:{"code":"100","message":"SUCCESS","requestUrl":"http://127.0.0.1:8081/AppServer/uploadUserPortrait","data":"上传成功"}
在看看进度
LogTrack warn com.alex.httpapp.module.fileupload [ (FileUploadModel.kt:37)#FileUploadModel$uploadUserPortrait$onUploadListener$1#onUploadProgress] 7926217 8192 0.0
LogTrack warn com.alex.httpapp.module.fileupload [ (FileUploadModel.kt:37)#FileUploadModel$uploadUserPortrait$onUploadListener$1#onUploadProgress] 7926217 16384 0.0
LogTrack warn com.alex.httpapp.module.fileupload [ (FileUploadModel.kt:37)#FileUploadModel$uploadUserPortrait$onUploadListener$1#onUploadProgress] 7926217 24576 0.0
LogTrack warn com.alex.httpapp.module.fileupload [ (FileUploadModel.kt:37)#FileUploadModel$uploadUserPortrait$onUploadListener$1#onUploadProgress] 7926217 32768 0.0
LogTrack warn com.alex.httpapp.module.fileupload [ (FileUploadModel.kt:37)#FileUploadModel$uploadUserPortrait$onUploadListener$1#onUploadProgress] 7926217 40960 0.0
LogTrack warn com.alex.httpapp.module.fileupload [ (FileUploadModel.kt:37)#FileUploadModel$uploadUserPortrait$onUploadListener$1#onUploadProgress] 7926217 49152 0.0
LogTrack warn com.alex.httpapp.module.fileupload [ (FileUploadModel.kt:37)#FileUploadModel$uploadUserPortrait$onUploadListener$1#onUploadProgress] 7926217 57344 0.0
LogTrack warn com.alex.httpapp.module.fileupload [ (FileUploadModel.kt:37)#FileUploadModel$uploadUserPortrait$onUploadListener$1#onUploadProgress] 7926217 65536 0.0
LogTrack warn com.alex.httpapp.module.fileupload [ (FileUploadModel.kt:37)#FileUploadModel$uploadUserPortrait$onUploadListener$1#onUploadProgress] 7926217 73728 0.0
LogTrack warn com.alex.httpapp.module.fileupload [ (FileUploadModel.kt:37)#FileUploadModel$uploadUserPortrait$onUploadListener$1#onUploadProgress] 7926217 81920 1.0
LogTrack warn com.alex.httpapp.module.fileupload [ (FileUploadModel.kt:37)#FileUploadModel$uploadUserPortrait$onUploadListener$1#onUploadProgress] 7926217 90112 1.0
LogTrack warn com.alex.httpapp.module.fileupload [ (FileUploadModel.kt:37)#FileUploadModel$uploadUserPortrait$onUploadListener$1#onUploadProgress] 7926217 98304 1.0
LogTrack warn com.alex.httpapp.module.fileupload [ (FileUploadModel.kt:37)#FileUploadModel$uploadUserPortrait$onUploadListener$1#onUploadProgress] 7926217 106496 1.0
......
LogTrack warn com.alex.httpapp.module.fileupload [ (FileUploadModel.kt:37)#FileUploadModel$uploadUserPortrait$onUploadListener$1#onUploadProgress] 7926217 7905280 99.0
LogTrack warn com.alex.httpapp.module.fileupload [ (FileUploadModel.kt:37)#FileUploadModel$uploadUserPortrait$onUploadListener$1#onUploadProgress] 7926217 7913472 99.0
LogTrack warn com.alex.httpapp.module.fileupload [ (FileUploadModel.kt:37)#FileUploadModel$uploadUserPortrait$onUploadListener$1#onUploadProgress] 7926217 7921664 99.0
LogTrack warn com.alex.httpapp.module.fileupload [ (FileUploadModel.kt:37)#FileUploadModel$uploadUserPortrait$onUploadListener$1#onUploadProgress] 7926217 7926217 100.0
看一下 后端的 接受情况
LogTrack[ (Native Method) #invoke0] 匹配到 2017-07-23 22:17:18 UploadController uploadUserPortrait (com.alex.appserver.module.upload.UploadController)
请求参数:
拼接串:http://127.0.0.1:8081/AppServer/uploadUserPortrait
请求行:id=1&phone=13146008029&pwd=123456
LogTrack[ (Native Method) #invoke0] ds2 UserEntity com.alex.appserver.module.usercenter.UserService.findEntityById(String)
2017-07-23 22:17:18.169 debug 12772 --- [.1-8081-exec-10] c.a.a.m.u.UserDao.findEntityById : ==> Preparing: select nickname, gender, phone, account_balance, point_balance from t_user where id=?
2017-07-23 22:17:18.169 debug 12772 --- [.1-8081-exec-10] c.a.a.m.u.UserDao.findEntityById : ==> Parameters: 1(String)
2017-07-23 22:17:18.171 debug 12772 --- [.1-8081-exec-10] c.a.a.m.u.UserDao.findEntityById : <== Total: 1
LogTrack[ (Native Method) #invoke0] ds2 UserEntity com.alex.appserver.module.usercenter.UserService.findEntityById(String)
LogTrack[ (<generated>) UploadController$$FastClassBySpringCGLIB$$7a2c9a15#invoke] UserEntity{id=null, nickname='张三2', gender='1', phone='13146008029', pwd='null', accountBalance=null, pointBalance=null}
LogTrack[ (<generated>) UploadController$$FastClassBySpringCGLIB$$7a2c9a15#invoke] D:/WorkSpace/DataStore/AppServer/FileUpload/file/黄喉蜂虎.png 耗时 1 毫秒
LogTrack[ (<generated>) UploadController$$FastClassBySpringCGLIB$$7a2c9a15#invoke] D:/WorkSpace/DataStore/AppServer/FileUpload/file/001.gif 耗时 0 毫秒
LogTrack[ (<generated>) UploadController$$FastClassBySpringCGLIB$$7a2c9a15#invoke] D:/WorkSpace/DataStore/AppServer/FileUpload/file/b5h4.mp4 耗时 35 毫秒
LogTrack[ (<generated>) UploadController$$FastClassBySpringCGLIB$$7a2c9a15#invoke] D:/WorkSpace/DataStore/AppServer/FileUpload/userPortrait/黄喉蜂虎.png 耗时 4 毫秒
LogTrack[ (<generated>) UploadController$$FastClassBySpringCGLIB$$7a2c9a15#invoke] 所有文件耗时 40 毫秒
LogTrack[ (ResponseUtil.java:35) #resp] 返回参数:{"code":"100","message":"SUCCESS","data":"上传成功"}
本地磁盘有没有呢? 真的是有的,你不应该怀疑我的能力
先看Android 端的代码
原谅我用 ItelliJ 写的烂代码
interface ApiService {
@POST("uploadUserPortrait")
fun uploadUserPortrait(@Body body: RequestBody): Observable<WrapperBean<Any>>
}
class FileUploadModel : AbsModel(), FileUploadContract.Model {
override fun uploadUserPortrait(): Observable<WrapperBean<Any>> {
val mp4File = File(AppCon.CacheEnum.TMP_MP4_PATH)
val gifFile = File(AppCon.CacheEnum.TMP_GIF_PATH)
val pngFile = File(AppCon.CacheEnum.TMP_PNG_PATH)
val builder = MultipartBody.Builder().setType(MultipartBody.FORM)
builder.addFormDataPart("file", pngFile.name, pngFile.guessRequestBody())
builder.addFormDataPart("userPortrait", pngFile.name, pngFile.guessRequestBody())
builder.addFormDataPart("file", gifFile.name, gifFile.guessRequestBody())
builder.addFormDataPart("file", mp4File.name, mp4File.guessRequestBody())
builder.addFormDataPart("id", "1")
builder.addFormDataPart("phone", "13146008029")
builder.addFormDataPart("pwd", "123456")
val onUploadListener = OnUploadListener { countLength, currLength, progress ->
LogTrack.w("$countLength $currLength $progress")
}
return RetrofitUtil.getService(onUploadListener).uploadUserPortrait(builder.build())
}
}
fun String.guessMimeType(): String {
val fileNameMap = URLConnection.getFileNameMap()
var contentTypeFor: String? = null
try {
contentTypeFor = fileNameMap.getContentTypeFor(URLEncoder.encode(this, "UTF-8"))
} catch (e: UnsupportedEncodingException) {
e.printStackTrace()
}
if (contentTypeFor == null) {
contentTypeFor = "application/octet-stream"
}
return contentTypeFor
}
fun File.guessRequestBody(): RequestBody = RequestBody.create(MediaType.parse(this.name.guessMimeType()), this)
UploadProgressInterceptor
public class UploadProgressInterceptor implements Interceptor {
OnUploadListener onUploadListener;
public UploadProgressInterceptor setOnUploadListener(OnUploadListener onUploadListener) {
this.onUploadListener = onUploadListener;
return this;
}
@Override
public Response intercept(Chain chain) throws IOException {
Request originalRequest = chain.request();
if (originalRequest.body() == null || onUploadListener == null) {
return chain.proceed(originalRequest);
}
ProgressRequestBody progressRequestBody = new ProgressRequestBody().setOriginalRequestBody(originalRequest.body()).setOnUploadListener(onUploadListener);
Request progressRequest = originalRequest.newBuilder().method(originalRequest.method(), progressRequestBody).build();
return chain.proceed(progressRequest);
}
}
class ProgressRequestBody extends RequestBody {
private RequestBody originalRequestBody;
private OnUploadListener onUploadListener;
private CountingSink countingSink;
public ProgressRequestBody setOnUploadListener(OnUploadListener onUploadListener) {
this.onUploadListener = onUploadListener;
return this;
}
public ProgressRequestBody setOriginalRequestBody(RequestBody requestBody) {
this.originalRequestBody = requestBody;
return this;
}
@Override
public MediaType contentType() {
return originalRequestBody.contentType();
}
@Override
public long contentLength() {
try {
return originalRequestBody.contentLength();
} catch (IOException e) {
e.printStackTrace();
}
return -1;
}
@Override
public void writeTo(BufferedSink sink) throws IOException {
BufferedSink bufferedSink;
countingSink = new CountingSink(sink);
bufferedSink = Okio.buffer(countingSink);
originalRequestBody.writeTo(bufferedSink);
bufferedSink.flush();
}
protected final class CountingSink extends ForwardingSink {
private long bytesWritten = 0;
public CountingSink(Sink delegate) {
super(delegate);
}
@Override
public void write(Buffer source, long byteCount) throws IOException {
super.write(source, byteCount);
bytesWritten += byteCount;
//listener.progress("正在上传", bytesWritten, contentLength());
double progress = (bytesWritten * 100 / contentLength());
//LogTrack.w("progress = " + progress);
if (onUploadListener != null) {
onUploadListener.onUploadProgress(contentLength(), bytesWritten, progress);
}
}
}
}
public interface OnUploadListener {
/**
* 进度监听
*
* @param countLength 总 字节数
* @param currLength 当前 已经上传的 字节数
* @param progress 上传的 进度值 99.99 [0.00, 100.00] 展示2位小数 的double
*/
void onUploadProgress(long countLength, long currLength, double progress);
}
为什么 有 java 也有 kotlin
怪我咯,,, 我比较懒惰的
再说说 SpringBoot 遇到的无知
- 关于 druid 我按照网上的配置,
感觉没有 任何 问题啊, 就是看不到任何 效果? 开始怀疑人生了, 我有这么笨吗?
事实上 我已经做出来了, 只是我对力量一无所知, 只要在浏览器访问以下就可以了,
- 怪我咯。。。
http://127.0.0.1:8081/druid/weburi.html
- 再说另外一个 上传文件限制的问题
整体门阀是100MB, 具体接口暂时没做限制, 以后再写吧
spring.http.multipart.max-file-size=100MB
spring.http.multipart.max-request-size=100MB
@Configuration
public class MultipartConfiguration {
@Bean
public MultipartConfigElement multipartConfigElement() {
MultipartConfigFactory config = new MultipartConfigFactory();
config.setMaxFileSize("100MB");
config.setMaxRequestSize("100MB");
return config.createMultipartConfig();
}
}
UploadController
这里 要用 MultipartHttpServletRequest 不能用 HttpServletRequest , 否则会报
org.apache.catalina.connector.RequestFacade cannot be cast to org.springframework.web.multipart.MultipartHttpServletRequest
@Controller
@RequestMapping(value = AppCon.BASE_URL)
public class UploadController extends AbsController<UserService> {
/**
* 上传 用户 头像
*
* @return
*/
@FormWholeCheck
@ResponseBody
@RequestMapping(value = {"uploadUserPortrait", "UploadUserPortrait"}, method = {RequestMethod.POST/*, RequestMethod.GET*/})
public Wrapper uploadUserPortrait(String id, MultipartHttpServletRequest request) throws IOException {
UserEntity userEntity = getService().findEntityById(id);
LogTrack.w(userEntity);
if (TrivialUtil.isEmpty(userEntity)) {
return ResponseUtil.resp("没有查到对用用户信息", CodeEnum.FAIL);
}
CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver(request.getServletContext());
if (!multipartResolver.isMultipart(request)) {
return ResponseUtil.resp("没有任何文件被提交", CodeEnum.FAIL);
}
List<MultipartFile> fileList = new ArrayList<>();
fileList.addAll(request.getFiles("file"));
fileList.addAll(request.getFiles("userPortrait"));
long totalStartTimeMillis = System.currentTimeMillis();
for (int i = 0; i < fileList.size(); i++) {
long startTimeMillis = System.currentTimeMillis();
MultipartFile multipartFile = fileList.get(i);
if (multipartFile != null) {
String originalFilename = multipartFile.getOriginalFilename();
String filePath = CacheEnum.getFileUploadPath() + multipartFile.getName() + "/" + originalFilename;
File localFile = new File(filePath);
FileUtil.createOrExistsFile(localFile);
multipartFile.transferTo(localFile);
LogTrack.w(filePath + " 耗时 " + (System.currentTimeMillis() - startTimeMillis) + " 毫秒");
}
}
LogTrack.w("所有文件耗时 " + (System.currentTimeMillis() - totalStartTimeMillis) + " 毫秒");
return ResponseUtil.resp("上传成功", CodeEnum.SUCCESS);
}
}
文件下载
ApiService
@FormUrlEncoded
@Streaming
@POST("downloadFile")
fun downloadFile(@FieldMap params: Map<String, String>): Observable<ResponseBody>
RetrofitUtil
public class RetrofitUtil {
private static Map<String, Retrofit> retrofitMap = new HashMap<>();
private static Retrofit.Builder getRetrofitBuilder(String baseUrl, OnUploadListener onUploadListener, OnDownloadListener onDownloadListener) {
OkHttpClient.Builder okHttpBuilder = new OkHttpClient.Builder();
okHttpBuilder.connectTimeout(HttpEnum.connectTimeout, TimeUnit.MILLISECONDS);
okHttpBuilder.readTimeout(HttpEnum.readTimeout, TimeUnit.MILLISECONDS);
okHttpBuilder.writeTimeout(HttpEnum.writeTimeout, TimeUnit.MILLISECONDS);
okHttpBuilder.retryOnConnectionFailure(true);
HttpsUtil.sslSocketFactory(okHttpBuilder, BaseUtil.getInstance().application(), "hack.cer");
okHttpBuilder.addInterceptor(new ParamsInterceptor(new SimpleParamsProvider()));
okHttpBuilder.addInterceptor(HttpLogInterceptor.getInstance());
if (onUploadListener != null) {
okHttpBuilder.addInterceptor(new UploadProgressInterceptor().setOnUploadListener(onUploadListener));
}
if (onDownloadListener != null) {
okHttpBuilder.addInterceptor(new DownloadProgressInterceptor().setOnDownloadListener(onDownloadListener));
}
Retrofit.Builder retrofitBuilder = new Retrofit.Builder().baseUrl(baseUrl).client(okHttpBuilder.build());
retrofitBuilder.addCallAdapterFactory(RxJava2CallAdapterFactory.create());
if (onDownloadListener == null) {
retrofitBuilder.addConverterFactory(GsonConverterFactory.create());
}
return retrofitBuilder;
}
public static ApiService getService() {
return getRetrofit(UrlEnum.baseApiUrl, null, null).create(ApiService.class);
}
public static ApiService getService(OnUploadListener onUploadListener, OnDownloadListener onDownloadListener) {
return getRetrofit(UrlEnum.baseApiUrl, onUploadListener, onDownloadListener).create(ApiService.class);
}
public static ApiService getService(String baseUrl, OnUploadListener onUploadListener, OnDownloadListener onDownloadListener) {
return getRetrofit(baseUrl, onUploadListener, onDownloadListener).create(ApiService.class);
}
public static <T> T getService(Class<T> service, OnUploadListener onUploadListener, OnDownloadListener onDownloadListener) {
return getRetrofit(UrlEnum.baseApiUrl, onUploadListener, onDownloadListener).create(service);
}
public static <T> T getService(String baseUrl, Class<T> service, OnUploadListener onUploadListener, OnDownloadListener onDownloadListener) {
return getRetrofit(baseUrl, onUploadListener, onDownloadListener).create(service);
}
private static Retrofit getRetrofit(String baseUrl, OnUploadListener onUploadListener, OnDownloadListener onDownloadListener) {
StringBuilder retrofitKeyBuilder = new StringBuilder();
if (baseUrl != null) {
retrofitKeyBuilder.append(baseUrl);
}
if (onDownloadListener != null) {
retrofitKeyBuilder.append(onDownloadListener.getClass().getSimpleName());
}
if (onUploadListener != null) {
retrofitKeyBuilder.append(onUploadListener.getClass().getSimpleName());
}
Retrofit retrofit = retrofitMap.get(retrofitKeyBuilder.toString());
if (retrofit != null) {
return retrofit;
}
retrofit = getRetrofitBuilder(baseUrl, onUploadListener, onDownloadListener).build();
retrofitMap.put(retrofitKeyBuilder.toString(), retrofit);
return retrofit;
}
}
DownloadProgressInterceptor
public class DownloadProgressInterceptor implements Interceptor {
private OnDownloadListener onDownloadListener;
public DownloadProgressInterceptor setOnDownloadListener(OnDownloadListener onDownloadListener) {
this.onDownloadListener = onDownloadListener;
return this;
}
@Override
public Response intercept(Chain chain) throws IOException {
Response originalResponse = chain.proceed(chain.request());
if (onDownloadListener == null) {
return originalResponse;
}
return originalResponse.newBuilder()
.body(new ProgressResponseBody(originalResponse.body()).setOnDownloadListener(onDownloadListener))
.build();
}
}
ProgressResponseBody
public class ProgressResponseBody extends ResponseBody {
private final ResponseBody responseBody;
private OnDownloadListener onDownloadListener;
private BufferedSource bufferedSource;
ProgressResponseBody(ResponseBody responseBody) {
this.responseBody = responseBody;
}
ProgressResponseBody setOnDownloadListener(OnDownloadListener listener) {
this.onDownloadListener = listener;
return this;
}
@Override
public MediaType contentType() {
return responseBody.contentType();
}
@Override
public long contentLength() {
return responseBody.contentLength();
}
@Override
public BufferedSource source() {
if (null == bufferedSource) {
bufferedSource = Okio.buffer(source(responseBody.source()));
}
return bufferedSource;
}
private Source source(Source source) {
return new ForwardingSource(source) {
/**当前 下载 进度*/
long currLength = 0L;
@Override
public long read(Buffer sink, long byteCount) throws IOException {
long bytesRead = super.read(sink, byteCount);
currLength += bytesRead != -1 ? bytesRead : 0;
double progress = 100 * ((double) currLength) / ((double) responseBody.contentLength());
String doubleFormat = decimalFormat(progress + "", 2, "1.00");
//LogTrack.w("contentLength = "+responseBody.contentLength()+" currLength = "+currLength+" progress = "+doubleFormat);
BaseUtil.getInstance().mainHandler().post(new Runnable() {
@Override
public void run() {
onDownloadListener.onDownloadProgress(responseBody.contentLength(), currLength, doubleFormat);
}
});
return bytesRead;
}
};
}
private String decimalFormat(String sourceNum) {
return decimalFormat(sourceNum, 2, "0.00");
}
private String decimalFormat(String sourceNum, String defaultValue) {
return decimalFormat(sourceNum, 2, defaultValue);
}
private String decimalFormat(String sourceNum, int length, String defaultValue) {
if (isEmpty(sourceNum)) {
return defaultValue;
}
char firstChar = sourceNum.charAt(0);
if (sourceNum.charAt(0) < '0' || firstChar > '9') {
return defaultValue;
}
StringBuilder trailBuilder = new StringBuilder();
for (int i = 0; i < length + 2; i++) {
trailBuilder.append("0");
}
int indexDot = sourceNum.indexOf('.');
if (indexDot >= 0) {
sourceNum += trailBuilder.toString();
}
if (indexDot < 0) {
indexDot = 1;
sourceNum += ("." + trailBuilder.toString());
}
return sourceNum.substring(0, indexDot + length + 1);
}
private boolean isEmpty(Object text) {
return text == null || text.toString().length() <= 0;
}
private boolean isNotEmpty(Object text) {
return !isEmpty(text);
}
}
DownloadPresenter
class DownloadModel : DownloadContract.Model {
override fun downloadTextFile(params: Map<String, String>, onDownloadListener: OnDownloadListener): Observable<ResponseBody> =
RetrofitUtil.getService(null, onDownloadListener).downloadFile(params)
}
class DownloadPresenter(view: DownloadContract.View) : AbsPresenter<DownloadContract.View, DownloadContract.Model>(view), DownloadContract.Presenter {
/**
* 生成 数据模型
*/
override fun createModel() = DownloadModel()
@Suppress("ObjectLiteralToLambda")
override fun doDownloadFile(phone: String, fileType: String) {
var filename = "RecyclerView.java"
if ("text".equals(fileType, ignoreCase = true)) {
filename = "RecyclerView.java"
}
if ("image".equals(fileType, ignoreCase = true)) {
filename = "黄喉蜂虎.png"
}
if ("video".equals(fileType, ignoreCase = true)) {
filename = "UI设计.mp4"
}
val params = hashMapOf<String, String>("fileType" to fileType, "phone" to phone)
model.downloadTextFile(params, OnDownloadListener { countLength, currLength, progress ->
"$countLength $currLength $progress".logW()
view.onUploadProgress(progress)
}).map {
DownloadHelper.getInstance().diskFilePath(CacheEnum.cachePath + filename).saveSync(it.byteStream())
}.compose(RxHelper.defaultTransformer(view))
.subscribe(object : LiteObserver<Any>() {
override fun onNext(result: Any) {
LogTrack.e(result)
}
})
}
}
DownloadHelper
public class DownloadHelper {
private static DownloadHelper instance;
private String filePath;
private DownloadHelper() {
}
public static DownloadHelper getInstance() {
if (instance == null) {
synchronized (DownloadHelper.class) {
instance = instance == null ? new DownloadHelper() : instance;
}
}
return instance;
}
/**
* @param filePath 文件 要存储在 SD 卡的 目标路径(必须携带SD卡的根目录),无论源文件是否存在, 都是清空写入
*/
public DownloadHelper diskFilePath(String filePath) {
this.filePath = filePath;
createOrExistsFile(filePath);
return this;
}
public void saveAsync(InputStream inputStream) {
new Thread() {
boolean saveFinish = false;
@Override
public void run() {
while (!saveFinish) {
saveFinish = saveSync(inputStream);
try {
sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
}
public boolean saveSync(InputStream inputStream) {
/**此处 获取 inputStream 的 .available() 是没有意义的*/
File file = new File(filePath);
FileOutputStream out = null;
try {
out = new FileOutputStream(file);
// LogTrack.e(file.getAbsolutePath() + " 文件 大小 = " + FileUtil.byte2FitSize());
byte[] buffer = new byte[1024 * 128];
int len;
long currCount = 0;
while ((len = inputStream.read(buffer)) != -1) {
currCount += len;
//LogTrack.e("读取 " + FileUtil.byte2FitSize(currCount));
out.write(buffer, 0, len);
}
out.flush();
} catch (FileNotFoundException e) {
LogTrack.e(e);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (out != null) {
out.close();
}
} catch (IOException e) {
}
try {
if (inputStream != null) {
inputStream.close();
}
} catch (IOException e) {
}
}
return true;
}
/**
* 判断文件是否存在,不存在则判断是否创建成功
*
* @param filePath 文件路径
* @return {@code true}: 存在或创建成功<br>{@code false}: 不存在或创建失败
*/
private boolean createOrExistsFile(String filePath) {
return createOrExistsFile(getFile(filePath));
}
/**
* 判断文件是否存在,不存在则判断是否创建成功
*
* @param file 文件
* @return {@code true}: 存在或创建成功<br>{@code false}: 不存在或创建失败
*/
private boolean createOrExistsFile(File file) {
if (file == null) return false;
// 如果存在,是文件则返回true,是目录则返回false
if (file.exists()) return file.isFile();
if (!mkdirs(file.getParentFile())) return false;
try {
return file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
/**
* 判断目录是否存在,不存在则判断是否创建成功
*
* @param file 文件
* @return {@code true}: 存在或创建成功<br>{@code false}: 不存在或创建失败
*/
private boolean mkdirs(File file) {
// 如果存在,是目录则返回true,是文件则返回false,不存在则返回是否创建成功
return file != null && (file.exists() ? file.isDirectory() : file.mkdirs());
}
/**
* 根据文件路径获取文件
*
* @param filePath 文件路径
* @return 文件
*/
private File getFile(String filePath) {
return new File(filePath);
}
}
后端 遇到的问题
ClientAbortException
org.apache.catalina.connector.ClientAbortException: java.io.IOException: 你的主机中的软件中止了一个已建立的连接。
at org.apache.catalina.connector.OutputBuffer.realWriteBytes(OutputBuffer.java:356)
at org.apache.catalina.connector.OutputBuffer.appendByteArray(OutputBuffer.java:785)
at org.apache.catalina.connector.OutputBuffer.append(OutputBuffer.java:714)
at org.apache.catalina.connector.OutputBuffer.writeBytes(OutputBuffer.java:391)
at org.apache.catalina.connector.OutputBuffer.write(OutputBuffer.java:369)
at org.apache.catalina.connector.CoyoteOutputStream.write(CoyoteOutputStream.java:96)
at com.alex.appserver.module.multipartfile.MultipartFileController.downloadTextFile(MultipartFileController.java:99)
at com.alex.appserver.module.multipartfile.MultipartFileController$$FastClassBySpringCGLIB$$f6856409.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:738)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:85)
at com.alex.appserver.interceptor.PrintParamsInterceptorController.interceptor(PrintParamsInterceptorController.java:49)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:629)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:618)
at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:70)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:168)
at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:85)
at com.alex.appserver.interceptor.FormWholeCheckInterceptorController.formWholeInterceptor(FormWholeCheckInterceptorController.java:80)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:629)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:618)
at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:70)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:168)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:673)
at com.alex.appserver.module.multipartfile.MultipartFileController$$EnhancerBySpringCGLIB$$1ce62f07.downloadTextFile(<generated>)
.....
....
怎么解决
Generally, you can just ignore it. This exception will be thrown when the client has abruptly aborted the HTTP request while the page is still loading.
This will occur when the client pressed Esc, or hastily navigated away, or closed the browser, or got network outage, or even caught fire. All of this is totally out your control.
byte[] buff = new byte[16 * 1024];
BufferedInputStream bufferedInputStream = null;
OutputStream outputStream;
try {
outputStream = response.getOutputStream();
bufferedInputStream = new BufferedInputStream(new FileInputStream(file));
LogTrack.e(file.getAbsolutePath() + " 文件 大小 = " + FileUtil.byte2FitSize(file.length()));
int charResult = bufferedInputStream.read(buff);
long currCount = 0;
while (charResult != -1) {
currCount += buff.length;
LogTrack.e("读取 " + FileUtil.byte2FitSize(currCount));
try {
outputStream.write(buff, 0, buff.length);
} catch (ClientAbortException ex) {
}
try {
outputStream.flush();
} catch (ClientAbortException ex) {
}
charResult = bufferedInputStream.read(buff);
}
} catch (ClientAbortException ex) {
LogTrack.e("不关心 ClientAbortException");
ex.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (bufferedInputStream != null) {
try {
bufferedInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
响应报文头中包含中文, 但是乱码
response.setContentType(ContentType.application_octet_stream);
response.setHeader("Accept-Ranges", "bytes");
response.setHeader("Content-Length", String.valueOf(file.length()));
/**
* 解决 响应头 中文 乱码问题, 此时 前端 解析 响应体 编码集 必须使用 UTF-8
* response.setHeader("Content-Disposition", "attachment;filename=" + new String(filename.getBytes("UTF-8"), "ISO8859-1"));
* 第一个 UTF-8 对应 当前 编辑器的 编码集
* 第二个 ISO8859-1 为什么是这样? 我自己也不知道
* */
response.setHeader("Content-Disposition", "attachment;filename=" + new String(filename.getBytes("UTF-8"), "ISO8859-1"));
response.setCharacterEncoding("UTF-8");