springboot jar部署 上传下载

需求

  • 通过批量手机找出id与手机关系,并且返回账号类型(未激活)
  • 例如:这批手机号从某次网上会议获取,有一部分是已经存在的用户,业务方将已存在的用户拿到后,就可以做分析、搞活动等

系统配置

  • Mac + IDEA
  • 框架和部署(SpringBoot + Thymeleaf + Jar 方式)

初版实现

  • 通过上传手机列表,然后下载id与手机号关联列表即可
  • 上传实现
# Thymeleaf页面
<form action="/upload" method="post" enctype="multipart/form-data">
    <input type="file" name="file">
    <button type="submit">上传手机列表(仅支持txt)</button>
</form>
  • 上传Java实现
@ResponseBody
@PostMapping("/upload")
public String upload(@RequestParam("file") MultipartFile file, HttpServletRequest request, HttpServletResponse response) {
     
	// commons-io工具类 将字节流、字符流、URL、URI等直接转换为字符串
	String data = IOUtils.toString(file.getInputStream());
	// 查数据库  伪代码
	List<User> list = userManager.list(wrapper);
	StringBuilder builder = new StringBuilder();
    list.stream().forEach(user -> {
     
      	builder.append(user.getId()).append("\t").append(user.phone());
        builder.append("\n");
    });
    write(builder.toString());

	// 数据量少 可以直接重定向 即可下载
	// response.sendRedirect(request.getRequestURL().toString().replace("upload", "download"));
	log.info("我成功啦,等着下载我哟");
    return "成功啦,等着下载我哟";
}

private void write(String content) throws Exception {
     
    File outFile = new File(OUT_FILE_NAME);
    // 4M   1024 * 1024 * 4
    ByteBuffer buffer = ByteBuffer.allocateDirect(4194304);
    if (!outFile.exists()) {
     
        // jar包部署到linux上后不能创建(代码只是显示创建而已)
        outFile.createNewFile();
    }

    // new FileOutputStream(file, append)  第二个参数为true表示追加写入
    @Cleanup FileChannel channel = new FileOutputStream(outFile, true).getChannel();
    buffer.put(content.getBytes());
    // 切换为读模式
    buffer.flip();

    while (buffer.hasRemaining()) {
     
        channel.write(buffer);
    }
}
  • 下载实现
@ResponseBody
@GetMapping("/download")
public String download(HttpServletRequest request, HttpServletResponse response) {
     
	File outFile = new File(OUT_FILE_NAME);
    if (!outFile.exists()) {
     
        return "需要下载的文件不存在,请先上传手机列表";
    }

    response.setContentType("application/vnd.ms-excel;charset=UTF-8");
    response.setCharacterEncoding("UTF-8");
    response.setHeader("Content-Disposition", "attachment;fileName=" + URLEncoder.encode(OUT_FILE_NAME, "UTF-8"));
    @Cleanup OutputStream os = response.getOutputStream();

    os.write(uidPhoneStr.getBytes());
    deleteFile();
}

/**
 * 删除服务器上文件
 */
private void deleteFile() throws Exception {
     
    File outFile = new File(OUT_FILE_NAME);
    if (outFile.exists()) {
     
        boolean delete = outFile.delete();
        if (!delete) {
     
            log.warn("删除文件失败, fileName-> {}", OUT_FILE_NAME);
        }
    }
}
  • 修改上传和请求文件大小(application.yml)
spring:
	 servlet:
	    multipart:
	      max-file-size: 10MB
	      max-request-size: 10MB
  • 本地测试OK
  • 丢到服务器上测试 Mac、Windows上传异常
  • 出现原因在这里 outFile.createNewFile();
  • 初版文件路径在项目根路径下 和src同级

第2版实现

  • 基于上一版,做如下修改
  • 想到不让创建,那就在resources目录下创建一个空文件,上传时直接写入,上传完后删除文件内容
  • 上传方法不用修改,当访问下载链接时,将文件内容清空

private static final String OUT_FILE_NAME = "user_phone.txt";

private static String CLASSPATH;

static {
     
    try {
     
        CLASSPATH = ResourceUtils.getURL("classpath:").getPath();
    } catch (Exception e) {
     
    }
}

private void deleteFile() throws Exception {
     
    File outFile = new File(CLASSPATH + OUT_FILE_NAME);
    if (outFile.exists()) {
     
        @Cleanup FileChannel channel = new FileOutputStream(outFile).getChannel();
        ByteBuffer buffer = ByteBuffer.allocateDirect(1);
        buffer.put("".getBytes());
        // 切换为读模式
        buffer.flip();
        channel.write(buffer);
        boolean delete = outFile.delete();
        if (!delete) {
     
            log.warn("删除文件失败, fileName-> {}", OUT_FILE_NAME);
        }
    }
}
  • 经测试,还是这里问题 outFile.createNewFile();
  • 测试直接在resources下读取文件内容处理没有问题
  • 在本地测试,resources目录下的文件内容为空时(文件存在),程序执行创建出来的文件是在 /target/classes/user_phone.txt
  • 之前jar包部署,创建文件也是出现这种问题。后来想到用缓存,此处使用redis缓存,如果流量小,或者接入redis方便,可以使用本地缓存

第3版实现

  • 上传代码
String signId = UUID.randomUUID().toString().replace("-", "");
// 底层是redis实现 公司集成的
cacheCloud.setEx(signId, builder.toString(), 180);
  • 下载代码(屏蔽删除文件操作)
@ResponseBody
@GetMapping("/download")
public String download(HttpServletRequest request, HttpServletResponse response) {
     
    String signId = request.getParameter("signId");
    if (StringUtils.isBlank(signId)) {
     
        return "找不到签名,请确认来源";
    }
    String uidPhoneStr = cacheCloud.get(signId);
    if (StringUtils.isBlank(uidPhoneStr)) {
     
        return "找不到上传的数据,请确认是否上传";
    }

    try {
     
        //File outFile = new File(CLASSPATH + OUT_FILE_NAME);
        //if (!outFile.exists()) {
     
        //    return "需要下载的文件不存在,请先上传手机列表";
        //}

        response.setContentType("application/vnd.ms-excel;charset=UTF-8");
        response.setCharacterEncoding("UTF-8");
        response.setHeader("Content-Disposition", "attachment;fileName=" + URLEncoder.encode(OUT_FILE_NAME, "UTF-8"));
        @Cleanup OutputStream os = response.getOutputStream();

        os.write(uidPhoneStr.getBytes());
        //deleteFile();
        cacheCloud.del(signId);
    } catch (Exception e) {
     
        log.warn("download phone list exception-> {}", e);
    }

    return "已经下载成功,请查看";
}
  • 此处需要注意,测试人员使用windows上传,windows(\r\n)与mac(\n)换行符不一致
  • 则上传方法中需要增加系统判断拆分格式(方法开始位置处)
// commons-io工具类 将字节流、字符流、URL、URI等直接转换为字符串
String data = IOUtils.toString(file.getInputStream());
String userAgent = request.getHeader("User-Agent");
List<String> phoneList;
if (userAgent.toLowerCase().indexOf(WINNDOWS) >= Constant.ZERO) {
     
    phoneList = Arrays.asList(data.split("\r\n"));
} else if (userAgent.toLowerCase().indexOf(MAC) >= Constant.ZERO) {
     
    phoneList = Arrays.asList(data.split("\n"));
} else {
     
    return "不支持的操作系统操作";
}
  • 最后就能成功上传和下载了
  • 待完善1 需要考虑到10w+数据时,程序能进行拆分,将redis单个key最多存储1w(待确认是否需要继续拆分)对应的value值
  • 待完善2 由于上传文件可能会很慢,需要等上传完后才能下载(AtomicBoolean处理)

你可能感兴趣的:(Spring,Boot,知识总结)