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

简介: 其实, 我是个小白... 如果我写的东西 有错误,麻烦指出来。。。多文件上传 与 进度回调前端做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
目录
相关文章
|
4月前
axios 上传显示进度
axios 上传显示进度
|
3月前
|
存储 资源调度 JavaScript
vue上传文件时显示上传进度
vue上传文件时显示上传进度
44 0
|
9月前
|
前端开发
前端:下载文件(多种方法)
前端:下载文件(多种方法)
772 0
|
前端开发
前端下载文件的几种方式
前端下载文件的几种方式
1073 0
|
移动开发 JSON 前端开发
前端H5选图预览到上传
在金融性质的App里,选择本地相册图片或者拍照,然后预览并且上传是一个典型的使用场景,比如常见的身份证信息上传。在最近接触的几家银行客户里,都反馈有类似的场景,并且在使用上都或多或少的遇到一些问题,最后找到我们,希望我们提供一些最佳实践。在这里分享下对应场景的一些优化解决方案。
324 0
前端H5选图预览到上传
|
前端开发 JavaScript API
我学会了,写一个前端下载文件功能
过去有很多次文件下载的功能,但是都没有记录下来,这次有空就把文件下载的功能从0写一遍,于是就有了这篇文章。 我会从简到难的方式去实现下载功能。从直接下载字符串到简单请求下载文件,最终通过后端返回的文件名来实现动态下载文件。
595 0
我学会了,写一个前端下载文件功能
|
前端开发
文件下载的几种方式
文件下载的几种方式
|
监控 JavaScript 前端开发
3分钟教你用原生js实现具有进度监听的文件上传预览组件
本文主要介绍如何使用原生js,通过面向对象的方式实现一个文件上传预览的组件,该组件利用FileReader来实现文件在前端的解析,预览,读取进度等功能,并对外暴露相应api来实现用户自定义的需求,比如文件上传,进度监听,自定义样式,读取成功回调等。
313 0
|
JavaScript 程序员 API
Nodejs实现图片的上传、压缩预览、定时删除。
我们程序员日常都会用到图片压缩,面对这么常用的功能,肯定要尝试实现一番。

热门文章

最新文章