文件上传、文件下载与进度回调-阿里云开发者社区

开发者社区> 开发与运维> 正文
登录阅读全文

文件上传、文件下载与进度回调

简介: 其实, 我是个小白... 如果我写的东西 有错误,麻烦指出来。。。 多文件上传 与 进度回调 前端做Http请求偶尔会遇到 multipartform-data 表单提交的问题,我们先看 OKHttp拦截器打印(印度的印)的参数, 我一共提交了4个文件, 3个字符串 LogTrack warn org.

其实, 我是个小白... 如果我写的东西 有错误,麻烦指出来。。。

多文件上传 与 进度回调

前端做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":"上传成功"}

本地磁盘有没有呢? 真的是有的,你不应该怀疑我的能力

Paste_Image.png
Paste_Image.png

先看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

Paste_Image.png
Paste_Image.png
  • 再说另外一个 上传文件限制的问题

整体门阀是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
Paste_Image.png

@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");
Paste_Image.png

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

分享:
开发与运维
使用钉钉扫一扫加入圈子
+ 订阅

集结各类场景实战经验,助你开发运维畅行无忧

其他文章