就进度条的实时展示问题,我先前浏览了大量的解决办法,总结出来一共两种解决方案:
- 利用session解决,但是使用session无法使用在前后端分离的项目,同时session的存取在不同的api接口中也存在取不到值的问题。
- 利用redis(或者其他的第三方分布式存储方式)解决,接下来我将对此方案进行详细的说明。
实际场景:
以批量导入数据为业务场景
实现思路:
编写一个计算进度条信息的统计方法类(类里面包括:进度信息计算、以及redis存储),然后利用循环进行调用该方法,这样就会不断的更新redis里面存储的进度条信息;新写一个api接口用于前台请求redis里面的进度条信息,然后前台使用轮询的方式不断请求api接口,从而实现前台进度实时展示。
实现代码:
- 计算方法公共类
@Component
public class ProgressLister {
@Autowired
private StringRedisTemplate redisTemplate;
/**
* @param beginT 开始时间
* @param allSize 完整大小
* @param upSize 已完成大小
* @param item 进度条序列号
* @ClassName: ProgressLister
* @Description: 获取当前进度
* @Return: void
*/
public void getProgress(long beginT, long allSize, long upSize, String item) {
// 当前时间
long curT = System.currentTimeMillis();
// 已用时间
long useTime = curT - beginT;
// 速度
String speed = "0";
String percent = "0";
if (useTime != 0) {
speed = rountStr(String.valueOf((allSize * 1.0) / (useTime / 1000)));
}
// 当前进度
if (allSize != 0) {
percent = rountStr(String.valueOf(((upSize * 1.0) / allSize) * 100));
}
// 将进度更新到redis中
HashOperations<String, Object, Object> opsForHash = redisTemplate.opsForHash();
opsForHash.put("progress" + item, "speed", speed);
opsForHash.put("progress" + item, "percent", percent);
}
/**
* @param str
* @ClassName: ProgressLister
* @Description: string类型数值只取整数(即小数点之前的值)
* @Return: java.lang.String
*/
public String rountStr(String str) {
String split = new String();
if (str.contains(".")) {
split = str.split("\\.")[0];
}
return split;
}
}
- 刷新进度信息API接口
@Controller
@RequestMapping(value = "/progress")
public class ProgressController {
@Autowired
private StringRedisTemplate redisTemplate;
/**
* @param item 进度条序列号
* @ClassName: ProgressController
* @Description: 用于刷新进度条信息
*/
@ResponseBody
@RequestMapping(value = "/flushProgress")
public Map<String,Object> flushProgress(String item) throws AppException {
HashOperations<String, Object, Object> opsForHash = redisTemplate.opsForHash();
Map<String,Object> map = new HashMap<>();
Object o = opsForHash.get("progress" + item, "percent");
String percent = "0";
if (o != null) {
percent = o.toString();
}
// 当导入完成时,删除redis的值
if (percent.equals("100")) {
opsForHash.delete("progress" + item, "speed", "percent");
}
map.put("percent", percent);
return map;
}
}
- 实际循环调用
// 使用前需要先添加依赖
@Autowired
private ProgressLister progressLister;
// 用于记录开始时间,大部分是直接在循环之前定义,可以根据自己的实际逻辑使用
long beginT = System.currentTimeMillis();
// 在循环内部使用,用于记录当前进度信息,每次循环时都进行记录
progressLister.getProgress(beginT, zsLenth, i + 1, item);
- 前端轮询请求
// 创建轮询方法,注意:这里的intervalId是定义轮询后得到的一个轮询序号(即ID号),这里需要注意intervalId的作用域,尽可能使用全局变量(避免因为作用域问题导致轮询无法关闭)
var intervalId = setInterval(function () {
// item是进度条序列号,主要用于不同的进度条同时进行展示时,区分不用的数据
$.get('/progress/flushProgress', {item: item}, function (res) {
// 这里放更新进度条数据的代码,此处省略.........
// 这里放更新进度条数据的代码,此处省略.........
// 当进度为100时结束轮询,并做一系列的后续操作
if (res.percent == 100) {
closeInterval()
}
})
}, "100");
// 用于停止轮询
function closeInterval() {
clearInterval(intervalId);
}