概念确认
文件上传,在网页上通过Ajax异步或者同步Post Form将文件搞到服务器上。
文件服务,包含两个部分,文件上传客户端,文件接收服务端。
不把百度搜烂的同学不是好同学
百度 site:jfinal.com 文件上传 (都看看, 看看别人是什么被坑出翔, 有助于快速出坑.)
JFinal文件上传文档 (基础文档都不看, 谈别的都是耍流氓)
用httpUrlConnection实现文件上传 (HttpClient太繁琐, HttpKit挺好就是基于这个的, 所以得先看看理论)
用JFinal上传文件时报错:Separation boundary was not specified
第三方文件上传客户端 (还是各种报错, 同HttpClient, 太繁琐 OkHttp 比那玩意还好用一点点)
看到这个的同学, 可能绝望了, 波总把锅都甩给了客户端(^v^至少小白会这样认为, 但实际上真的是因为不会用客户端,以及不了解Http文件上传协议, 导致了这些令人窒息的报错, 道理我都懂, 就是上传报错,MMP)
看见没, 大佬在不同的时空解释了, 万恶的根源都是你不够了解http协议规范.
好了, 万恶的根源解释完了, 下面要亮绝招了!
作为一名Eovaer,一定要寻求最简单的套路,什么Http 协议并不想关心。
我相信和我一样学渣的你,一定是懒的,嫌麻烦的。
那么我也相信你写过增删改查,写过登录,写过API,写过post json,写过renderJson(Ret.ok());
如果这些都没搞过,连学渣都不如, 就可以考虑回炉重构了。
好至少写过post json, 懂这个套路就够了。
那下面再聊5毛钱的理论。
万物皆对象,万物皆字符串,万物皆1和0,那么由此可推导出 文件=File=String=byte[]
由此可得到一个理论 我会post json, 我就会 post file=我会上传文件.
OK, 顺着这个思路, 就可以完成我们今天要做的文件上传服务(客户端和服务端), 让操蛋的Http协议见鬼去吧, 我就不看.
理论结束, 下面上代码了.
com.jfinal.kit.HttpKit.class 核心代码 data.getBytes(CHARSET) 就是提交二进制文件内容
public static String post(String url, Map<String, String> queryParas, String data, Map<String, String> headers) { HttpURLConnection conn = null; try { conn = getHttpConnection(buildUrlWithQueryString(url, queryParas), POST, headers); conn.connect(); if (data != null) { OutputStream out = conn.getOutputStream(); out.write(data.getBytes(CHARSET)); out.flush(); out.close(); } return readResponseString(conn); } catch (Exception e) { throw new RuntimeException(e); } finally { if (conn != null) { conn.disconnect(); } } }
能提交 string txt json 为什么不能提交 jpg zip avi ?
你已经会提交 json, 提交zip, 不就差一个 zip to Bytes吗? 来来来....百度找了一个, 真香!
/** * 文件转二进制 * @param filePath * @return * @throws IOException */ public static byte[] fileToByte(File file) { byte[] bt = null; FileInputStream fis = null; try { fis = new FileInputStream(file); bt = new byte[(int) file.length()]; fis.read(bt); } catch (IOException e) { e.printStackTrace(); } finally { try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } return bt; }
有了File to 二进制, 反之呢?
/** * 将inputStream转化为file * @param is * @param file 要输出的文件目录 */ public static void inputStream2File(InputStream is, File file) throws IOException { OutputStream os = null; try { os = new FileOutputStream(file); int len = 0; byte[] buffer = new byte[1024]; while ((len = is.read(buffer)) != -1) { os.write(buffer, 0, len); } } finally { os.close(); is.close(); } }
如上代码By eova-pro 3.4.1 com/eova/common/utils/io/FileUtil.java
public class TestController extends BaseController { // 文件上传服务端 public void server() { String fileName = get("fileName"); fileName = System.currentTimeMillis() + "@" + fileName; System.out.println(fileName); String baseDir = "C:\\server\\"; HttpServletRequest request = getRequest(); try { File file = new File(baseDir + fileName); FileUtil.inputStream2File(request.getInputStream(), file); } catch (Exception e) { renderJson(Ret.fail("msg", e.getMessage())); return; } renderJson(Ret.ok("name", fileName)); } // 文件上传客户端 public void client() { String url = "http://127.0.0.1/test/server"; File file = new File("C:\\logs.rar"); String result = HttpUtils.cs().postFile(url, file.getName(), file); renderText(result); } }
PS: com.eova.common.utils.HttpUtils; 也是基于JFinal HttpKit改造的.
public String postFile(String url, String fileName, File file) { this.contentType = "application/octet-stream"; // 你只post了一坨文件数据, 需要单独把文件名给弄过去. HashMap<String, String> queryParas = new HashMap<>(); queryParas.put("fileName", fileName); return post(url, queryParas, FileUtil.fileToByte(file), null); }
下面上测试图:
小结
这个方案主要用于日常小文件跨服务发送,比如私有云盘服务,传200M以内的文件是没啥问题的,如果是私有AV云还是考虑用115网盘吧,可以直接离线观看。
173M的EOVA视频教程, 传输时间是1.4S (内网), 基本上能满足大部分日常文件的需求了.
那我们挑战一下传一部AV 1.9GB
显然会内存溢出, 因为JVM默认没配这么多.转二进制就溢出了.
Caused by: java.lang.OutOfMemoryError: Java heap space at java.util.Arrays.copyOf(Arrays.java:3236) at java.io.ByteArrayOutputStream.grow(ByteArrayOutputStream.java:118) at java.io.ByteArrayOutputStream.ensureCapacity(ByteArrayOutputStream.java:93) at java.io.ByteArrayOutputStream.write(ByteArrayOutputStream.java:153) at sun.net.www.http.PosterOutputStream.write(PosterOutputStream.java:78) at java.io.OutputStream.write(OutputStream.java:75)
如何支持更大的文件-->增加JVM内存上限.
警告:如果文件较大,建议单独一个服务跑文件服务, 否则分分钟宕机.
基本上1G以内的文件是没啥问题的, 如果真要做AV云就要考虑更多的问题了, 分片上传, 多线程上传, 断点续传等问题.
JFinal-4.8 action report -------- 2020-03-29 13:33:57 -------------------------- Url : POST /test/server Controller : com.oss.test.TestController.(TestController.java:1) Method : server Interceptor : com.eova.interceptor.LoginInterceptor.(LoginInterceptor.java:1) com.eova.interceptor.AuthInterceptor.(AuthInterceptor.java:1) Parameter : fileName=111.mp4 -------------------------------------------------------------------------------- 上传成功: Load Cost Time:3220ms 文件大小: 375M
1585460867549@EKAI-003.avi 1.5G -Xms512m -Xmx4096m
Load Cost Time:21525ms 没有啥事是加内存解决不了的
总结:
无招胜有招
你永远不知道你有多优秀
你掌握的本领已经可以改变世界,可能还没融汇贯通
你在网上可能还能收到FTP上传,运维不给你权限你只能骂娘,这个方案是应用层解决文件上传与接收!
我们不生产源码,我们只生产各种奇技赢巧。
喜欢的可以去下载EOVA源码,扒各种骚操作。