LayUI+Springboot多图上传及并发插入数据库数据出现的主键id重复问题解决
程序员文章站
2022-03-03 20:02:01
...
LayUI提供了非常方便的文件上传控制
,uploadListIns = upload.render({
elem: '#testList' //选择文件按钮的id
,url: '../../../file/uploadCarousel'
,accept: 'images'
,multiple: true
,auto: false
,bindAction: '#testListAction' //开始上传按钮的id
,choose: function(obj){
var files = this.files = obj.pushFile(); //将每次选择的文件追加到文件队列
//读取本地文件
obj.preview(function(index, file, result){
//console.log(index); //得到文件索引
//console.log(file); //得到文件对象
//console.log(result); //得到文件base64编码,比如图片
var tr = $(['<tr id="upload-'+ index +'">'
,'<td><img src='+result+' style="width:60px;height:60px"></td>'
,'<td>'+ (file.size/1014).toFixed(1) +'kb</td>'
,'<td>等待上传</td>'
,'<td>'
,'<button class="layui-btn layui-btn-mini layui-btn-danger demo-delete">删除</button>'
,'</td>'
,'</tr>'].join(''));//join() 方法用于把数组中的所有元素转换一个字符串。
//删除
tr.find('.demo-delete').on('click', function(){
delete files[index]; //删除对应的文件
tr.remove();
uploadListIns.config.elem.next()[0].value = ''; //清空 input file 值,以免删除后出现同名文件不可选
});
demoListView.append(tr);
});
}
,done: function(res, index, upload){
//res(服务端响应信息)、index(当前文件的索引)、upload(重新上传的方法,一般在文件上传失败后使用)
if(res.status == "success"){
var tr = demoListView.find('tr#upload-'+ index), tds = tr.children();
tds.eq(2).html('<span style="color: #5FB878;">上传成功</span>');
tds.eq(3).html(''); //清空操作
var str = '<input type="hidden" name="photo[]" value='+res.image+'>';
$('#demoList').append(str);
return delete this.files[index]; //删除文件队列已经上传成功的文件
}
this.error(index, upload);
}
,error: function(index, upload){
var tr = demoListView.find('tr#upload-'+ index)
,tds = tr.children();
tds.eq(2).html('<span style="color: #FF5722;">上传失败</span>');
tds.eq(3).find('.demo-reload').removeClass('layui-hide'); //显示重传
}
});
});
直接使用upload.render方法
mutiple指明可多图上传
后端代码
@RequestMapping(value = "/uploadCarousel",method = RequestMethod.POST)
public Map<String, String> uploadCarousel(MultipartFile file, HttpServletRequest request, HttpServletResponse response) throws IllegalStateException, IOException{
Map<String, String> map = new HashMap<>();
SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd");//设置日期格式 HH:mm:ss
String date = df.format(new Date());// new Date()为获取当前系统时间,也可使用当前时间戳
String path = request.getSession().getServletContext().getRealPath("/upload/carousel");
File filepath = new File(path);
if (!filepath.exists()) {
filepath.mkdirs();
}
String originalFilename = file.getOriginalFilename();
String fileName = getRandomFileName() + originalFilename;
File dir = new File(path, fileName);
//文件上传
try {
file.transferTo(dir);
map.put("status", "success");
map.put("src", contextPath+"/upload/carousel/" + fileName);
//轮播图文件上传后在数据库内添加该记录
Carousel carousel = new Carousel();
carousel.setImage(map.get("src"));
Map<String,Object> returnMap = new HashMap<>();
returnMap = carouselService.insert(carousel);
//如果操作不成功就删除文件
if(returnMap.get("message") == null){}
else{
if(dir.isFile()){
dir.delete();
map.put("status", "error");
map.put("message", "上传失败!");
}
else{
map.put("status", "error");
map.put("message", "上传失败!");
}
}
} catch (IOException e) {
map.put("status", "error");
map.put("message", "上传失败!");
}
return map;
}
事实上这个后端接口多图单图上传都可以调用,因为多图的话应该只是多创建几个线程同时上传而已,但是多图上传时就会出现上传失败的问题,其原因就在于多图上传应该是调用了好几次这个接口,但是相互之间的间隔又很多,就导致并发场景下出现数据库写数据主键重复的问题,这也是很常见的并发场景下的问题。
解决方法:
由于我们的项目是跑在单服务器下,因此可以使用java的synchronized (this)同步锁上锁,当进入这段代码操作的时候,其他线程无法操作,进入阻塞状态,因为是单服务器,所以这段代码只有一个,也就不存在分布式相关的问题。
实测解决了多图上传出现上传失败的问题。
需要注意的点
synchronized不要写在service里,因为service里事物控制只有在事物完成之后才会进行提交,这期间同步锁锁住还是可能会出现并发访问的问题,因此应该在controller里写该方法上锁。
贴上上锁的代码:
@RequestMapping(value = "/insert",method = RequestMethod.POST)
private Map<String,Object> insert(Carousel carousel){
Map<String, Object> modelMap = new HashMap<String, Object>();
Map<String, Object> returnMap = new HashMap<String, Object>();
synchronized (this) { //同步锁 单服务器下保证只能一个线程访问 结束之后再释放
returnMap = carouselService.insert(carousel);
if (returnMap.get("message") == null) {
modelMap.put("status", "success");
} else {
modelMap.put("status", "error");
modelMap.put("message", returnMap.get("message"));
}
}
return modelMap;
}