몽촌토성 인터뷰
꿈꾸는 인터뷰 매거진
这两天有个需求就是说将线上的信息通过计算后导入到 Excel 文件中,之后并可提供下载,下载后这个文件就不需要存在服务器上了,只要用户成功下载后,文件爱怎么样怎么样。
好吧开始做吧。
Java 文件下载
初步打算做法如下:
- 异步请求(需求所需,不是重点),通过 java 计算,将数据写入到 Excel 文件,并保存到服务器;
- 然后下载文件;
哦,除了计算,写入到有 47 列的 Excel 文件麻烦点,好像没什么了。。。开始码代码了。
- 首先将文件写入到服务器的磁盘下;
-
然后发起一个 http 请求从服务器上下载文件,简单代码如下:
/** * 文件下载 * * @param uniqueId * @param response * @return */ @RequestMapping(value = "/excels/{uniqueId}/download") public HttpServletResponse download( @PathVariable(value = "uniqueId") String uniqueId, HttpServletRequest request, HttpServletResponse response) { // ... File file = fileService.getFile(uniqueId); try { InputStream fis = new BufferedInputStream(new FileInputStream(file)); byte[] buffer = new byte[fis.available()]; fis.read(buffer); fis.close(); response.reset(); response.addHeader("Content-Disposition", "attachment;filename=" + new String(file.getName())); response.addHeader("Content-Length", "" + file.length()); OutputStream toClient = new BufferedOutputStream(response.getOutputStream()); response.setContentType("application/octet-stream"); toClient.write(buffer); toClient.flush(); toClient.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { file.delete(); } return response; }
- 客户端发起一个 get 请求就可以将文件下载下来了,在日常环境和预发环境表现都是正常的;
- 然后我就正式上线了,结果出问题了,有时候无法下载成功。
无法成功下载文件
那什么问题呢?
突然意识到,日常和预发环境都是一台机器,那么无论怎么读写文件都是在同一台机器上进行操作,而正式环境有两台机器,挂在一个 vip 下,那么我在请求计算数据的时候,会将 Excel 文件写入到一台服务器 A 中,那么第二次请求下载的时候,nginx 不一定会将请求转到写入文件的机器 A 上,可能是 A,也可能是 B,当请求到了B 服务器上,那么查找文件就自然不存在了。
考虑到这个文件的量不大,每个文件大的也就几 M,没有必要再引入文件服务器上,为了减少不必要的依赖,维护,那么就用 mysql 了,反正两台服务器访问的数据都是一个。
然后新建一个有关文件的表 t_files,大概表结果如下:
id, unique_id, name, content, create_time, create_by, deleted, update_time, udpate_by
那么在 mysql 里存储文件的时候又不能存储文件的路径,因为不一定文件会存在哪台机器上,还会出现那个问题,所以就将文件 Base64 编码后写入到 content 中,下次读取文件的时候再将 content decode 一下写入到文件中,这里 content 字段采用 TEXT 类型,那事实证明是可行的,但是我发现有的文件在下载后,无法正常打开,损坏了,原因是 TEXT 类型的字段存储长度过小,无法完整的存下信息。
LANGTEXT:4294967295/3=1431655765个汉字,14亿,存储空间占用:4294967295/1024/1024/1024=4G的数据;
MEDIUMTEXT:16777215/3=5592405个汉字,560万,存储空间占用:16777215/1024/1024=16M的数据;
TEXT:65535/3=21845个汉字,约20000,存储空间占用:65535/1024=64K的数据;
因为计算后的生成的文件之前也说过不会有超过 10M 的,就选择了 MEDIUMTEXT 类型,恩,就这样解决了刚刚的那个问题。
那在写代码的时候出现了一个问题,问题是,当我将文件 Base64 编码后,可以成功写入数据库,这里查找 t_files 我是根据 unique_id 作为主键进行查找的,但是我无法获取 content 内容,其他信息正常,只有 content 为空。我是使用 mybatis 作为 ORM 进行查询的,然后查看对应的 mapper.xml 文件后发现,对于 TEXT 类型的字段,和其他 VARCHAR 类型的字段是有区别的,mybatis 自动对 TEXT 类型进行了特殊处理,无法查找。解决办法如下:
- 可以自己手写对应的 mapper 文件,生成对应的 SQL 语句;
- 通过主键 id 进行查询 selectByPrimaryKey(id) 就可以将信息全部查到,这里我先通过 unique_id 将对象查出,然后又根据 id 重新查找了一次,解决了上面的问题。
好了,正式上线了。
哦,是时候找个机会学习下集群、分布式的问题了。