介绍
前几天项目中碰到一个bug,人员列表的导出,有时能正常导出文件,有时文件导出会出错。
经过问题的排查分析,原来时系统用nginx负载均衡导致的问题。负载均衡有很多的策略,默认是轮询,轮询会导致,文件生成在服务器之后,下载的请求却不在同一个服务器,导致出现问题。还有一个是根据请求ip进行负载均衡,这样可以避免这个问题,但是ip策略有个问题,如果ip前三段的一致可能hash值是一致的,这样就达不到负载均衡的目的。在我们的项目中就发生了这个问题。所以只能采取利用外部中间件的形式来作为文件中继站。常用的是文件服务器,也可以使用redis或者mongodb,这里我介绍一下redis。
生产者——往redis中塞文件字符串(Base64编码)
public void uploadFileToRedis(String filePath,String filename) throws FileNotFoundException {
//根据文件路径取得文件对象;文件路径是生成文件的路径
File file=new File(filePath);
// Base64编码 (hutool的Base64)
String base64File = Base64.encode(file);
if (StringUtils.isNotEmpty(base64File)){
//这里不设置过期时间,等到下载完成后在删除即可,这里我封装 了一个redis工具类
//原生的为 redisTemplate.opsForValue().set(filename,base64File)
redisCache.setCacheObject(filename,base64File);
}
}
消费者——浏览器下载文件(从redis中将文件字符串取出,解码,然后转为文件流进行下载)
@GetMapping("/download")
public void fileDownload(String fileName HttpServletResponse response, HttpServletRequest request)
{
try
{
// 从redis中取出文件 redisTemplate.opsForValue.get(fileName)
String arrStr=redisCache.getCacheObject(fileName);
if (StringUtils.isEmpty(arrStr)){
throw new BusinessException("文件不存在");
}
// 设置文件下载的响应配置
response.setCharacterEncoding("utf-8");
response.setContentType("multipart/form-data");
response.setHeader("Content-Disposition",
"attachment;fileName=" + FileUtils.setFileDownloadHeader(request, +"文件名.xlsx"));
// 将文件字符串用base64转码后转化为流,进行下载。 直接转为文件对象可以使用 Base64.decodeToFile(arrstr,destFile); destFile 目标File对象
Base64.decodeToStream(arrStr, response.getOutputStream(), true);
}
catch (Exception e)
{
e.printStackTrace();
}finally {
// 从redis中删除文件流 redisTemplate.remove(fileName)
redisCache.deleteObject(fileName);
}
}
下面补充一下FileUtils工具类中的setFileDownloadHeader这个方法
/**
* 下载文件名重新编码
*
* @param request 请求对象
* @param fileName 文件名
* @return 编码后的文件名
*/
public static String setFileDownloadHeader(HttpServletRequest request, String fileName)
throws UnsupportedEncodingException
{
final String agent = request.getHeader("USER-AGENT");
String filename = fileName;
if (agent.contains("MSIE"))
{
// IE浏览器
filename = URLEncoder.encode(filename, "utf-8");
filename = filename.replace("+", " ");
}
else if (agent.contains("Firefox"))
{
// 火狐浏览器
filename = new String(fileName.getBytes(), "ISO8859-1");
}
else if (agent.contains("Chrome"))
{
// google浏览器
filename = URLEncoder.encode(filename, "utf-8");
}
else
{
// 其它浏览器
filename = URLEncoder.encode(filename, "utf-8");
}
return filename;
}
至此关于redis作为文件中转服务器的应用就做完了,值得注意的是,如果文件中数据过大可能对redis的性能有一些影响,所以在下载完成后一定要及时的删除上一个文件字符串。