需求
- 通过批量手机找出id与手机关系,并且返回账号类型(未激活)
- 例如:这批手机号从某次网上会议获取,有一部分是已经存在的用户,业务方将已存在的用户拿到后,就可以做分析、搞活动等
系统配置
- Mac + IDEA
- 框架和部署(SpringBoot + Thymeleaf + Jar 方式)
初版实现
- 通过上传手机列表,然后下载id与手机号关联列表即可
- 上传实现
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="file">
<button type="submit">上传手机列表(仅支持txt)</button>
</form>
@ResponseBody
@PostMapping("/upload")
public String upload(@RequestParam("file") MultipartFile file, HttpServletRequest request, HttpServletResponse response) {
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());
log.info("我成功啦,等着下载我哟");
return "成功啦,等着下载我哟";
}
private void write(String content) throws Exception {
File outFile = new File(OUT_FILE_NAME);
ByteBuffer buffer = ByteBuffer.allocateDirect(4194304);
if (!outFile.exists()) {
outFile.createNewFile();
}
@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("-", "");
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 {
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());
cacheCloud.del(signId);
} catch (Exception e) {
log.warn("download phone list exception-> {}", e);
}
return "已经下载成功,请查看";
}
- 此处需要注意,测试人员使用windows上传,windows(\r\n)与mac(\n)换行符不一致
- 则上传方法中需要增加系统判断拆分格式(方法开始位置处)
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处理)