spring boot:接收数组参数及多文件混合json参数(spring boot 2.3.4)
一,生产环境中的复杂参数上传的场景
1,保存排序值 :
例如:某一件商品的多张展示图片排序,提交的排序值要和图片的id相对应
2,上传多张图片,图片要和指定的变量相对应
例如:在添加商品sku时,
需要为指定有图片的属性上传图片,
让用户看上去更直观
这里演示了这两种常见的参数上传,
电商系统中的sku的添加是很关键的一个功能模块,
必须让后台的操作人员能直观的看到自己的操作结果,
这里有一些js代码可以供大家参考
说明:刘宏缔的架构森林是一个专注架构的博客,
网站:https://blog.imgtouch.com
本文: https://blog.imgtouch.com/index.php/2023/05/25/spring-boot-jie-shou-shu-zu-can-shu-ji-duo-wen-jian-hun-he/
对应的源码可以访问这里获取: https://github.com/liuhongdi/
说明:作者:刘宏缔 邮箱: 371125307@qq.com
二,演示项目的相关信息
1,项目地址:
https://github.com/liuhongdi/arrayparam
2,功能说明:
演示了提交多个数组和提交多个数组变量及多个文件
3,项目结构:如图:
三,配置文件说明
1,pom.xml
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--thymeleaf begin--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <!--validation begin--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> <!--commons-fileupload--> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.4</version> </dependency> <!--commons-io--> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.8.0</version> </dependency> <!--fastjson begin--> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.73</version> </dependency>
2,application.properties
#thymeleaf spring.thymeleaf.cache=false spring.thymeleaf.encoding=UTF-8 spring.thymeleaf.mode=HTML spring.thymeleaf.prefix=classpath:/templates/ spring.thymeleaf.suffix=.html #error server.error.include-stacktrace=always #errorlog logging.level.org.springframework.web=trace
四,java代码说明
1,GoodsController.java
@Controller @RequestMapping("/goods") public class GoodsController { //显示商品图片 @GetMapping("/image") public String goodsimage(Model model,@Min(value = 1,message = "商品id需要>0")@RequestParam("goodsId") Long goodsId) { model.addAttribute("goodsId", goodsId); return "goods/image"; } //保存各图片的排序值 @PostMapping("/contentsaveorder") @ResponseBody public String goodsContentSaveOrder(HttpServletRequest request, @Min(value = 1,message = "商品id需要>0")@RequestParam("goodsId") Long goodsId, @RequestParam(value = "imgids[]") Long[] imgids, @RequestParam(value = "orders[]") int[] orders ) { String res = "当前商品id:"+goodsId+"\n"; for (int i = 0; i < imgids.length; i++) { Long imageId = imgids[i]; int orderNum = orders[i]; res += "当前图片id:"+imageId+";排序值:"+orderNum+"\n"; } return res; } //显示商品sku @GetMapping("/sku") public String sku(Model model,@Min(value = 1,message = "商品id需要>0")@RequestParam("goodsId") Long goodsId) { model.addAttribute("goodsId", goodsId); return "goods/sku"; } //保存商品的sku @PostMapping("/skuadded") @ResponseBody public String skuadded(@RequestParam("json") String json,HttpServletRequest request) { SkuForm skuform = JSONObject.parseObject(json, SkuForm.class); String res = "goodsId:"+skuform.getGoodsId()+"\n"; for (Sku skuOne : skuform.getSkuList()) { res+="key:"+skuOne.getKey()+";price:"+skuOne.getPrice()+";stock:"+skuOne.getStock()+"\n"; } for (AttrType typeone :skuform.getTypeList()) { res+="typename:"+typeone.getTypeName()+";isimage:"+typeone.getIsImage()+";attrlist:"+typeone.getAttrList()+"\n"; } for (FormFile fileOne : skuform.getFileList()) { res+="filelist:fileName:"+fileOne.getFileName()+"\n"; } CommonsMultipartResolver multipartResolver=new CommonsMultipartResolver(request.getSession().getServletContext()); // 判断是否是多数据段提交格式 if (multipartResolver.isMultipart(request)) { System.out.println("isMultipart"); MultipartHttpServletRequest multiRequest = (MultipartHttpServletRequest)request; Iterator<String> iter = multiRequest.getFileNames(); Integer fileCount = 0; while (iter.hasNext()) { MultipartFile multipartFile = multiRequest.getFile(iter.next()); String varName = multipartFile.getName(); String fileName = multipartFile.getOriginalFilename(); if(fileName == null || fileName.trim().equals("")){ continue; } else { res+="file:fileName:"+fileName+";varName:"+varName+"\n"; } } } else { System.out.println("not isMultipart"); } return res; } }
2,SkuForm.java
public class SkuForm { //goodsid private Long goodsId; public Long getGoodsId() { return this.goodsId; } public void setGoodsId(Long goodsId) { this.goodsId = goodsId; } //type list private List<AttrType> typeList; public List<AttrType> getTypeList() { return this.typeList; } public void setTypeList(List<AttrType> typeList) { this.typeList = typeList; } //sku list private List<Sku> skuList; public List<Sku> getSkuList() { return this.skuList; } public void setSkuList(List<Sku> skuList) { this.skuList = skuList; } //file list private List<FormFile> fileList; public List<FormFile> getFileList() { return this.fileList; } public void setFileList(List<FormFile> fileList) { this.fileList = fileList; } }
3,AttrType.java
public class AttrType { //typename private String typeName; public String getTypeName() { return this.typeName; } public void setTypeName(String typeName) { this.typeName = typeName; } //isImage private int isImage; public int getIsImage() { return this.isImage; } public void setIsImage(int isImage) { this.isImage = isImage; } //attr list private List<String> attrList; public List<String> getAttrList() { return this.attrList; } public void setAttrList(List<String> attrList) { this.attrList = attrList; } }
4,Sku.java
public class Sku { //key private String key; public String getKey() { return this.key; } public void setKey(String key) { this.key = key; } //price private String price; public String getPrice() { return this.price; } public void setPrice(String price) { this.price = price; } //stock private int stock; public int getStock() { return this.stock; } public void setStock(int stock) { this.stock = stock; } }
5,FormFile.java
public class FormFile { //fileName private String fileName; public String getFileName() { return this.fileName; } public void setFileName(String fileName) { this.fileName = fileName; } }
五,html/js代码说明:
1,image.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script type="text/javascript" language="JavaScript" src="/js/jquery-1.6.2.min.js"></script> </head> <body> <form id='content_order'> <div style='width:320px;background-color: #000000;font-size: 14px;height: 20px;color: #ffffff;text-align: center;line-height: 20px;'>商品详情</div> <div id="main" style="display: flex;flex-direction: column;"> <div style='width: 320px;'> <div style='width:150px;float:left;line-height: 0px;'><img src='/img/3.jpg' style='width: 150px;' /></div> <div style='width:150px;float:left;'> 图片id:3 <br/> 排序值:<br/><input type='text' style='width:30px;' name='orderNum_3' value='' /> </div> </div> <div style='width: 320px;'> <div style='width:150px;float:left;line-height: 0px;'><img src='/img/4.jpg' style='width: 150px;' /></div> <div style='width:150px;float:left;'> 图片id:4 <br/> 排序值:<br/><input type='text' style='width:30px;' name='orderNum_4' value='' /> </div> </div> <div style='width: 320px;'> <div style='width:150px;float:left;line-height: 0px;'><img src='/img/5.jpg' style='width: 150px;' /></div> <div style='width:100px;float:left;'> 图片id:5 <br/> 排序值:<br/><input type='text' style='width:30px;' name='orderNum_5' value='' /> </div> </div> </div> <!--main end--> </form> <input type="button" value="保存排序值" th:onclick="saveOrder([[${goodsId}]])" /> <script> //保存排序值 function saveOrder(goodsId){ var form = document.getElementById("content_order"); var tagElements = form.getElementsByTagName('input'); var postdata = {}; var imgids = []; var orders = []; for (var j = 0; j < tagElements.length; j++){ var curid = tagElements[j].name.replace("orderNum_", ""); imgids.push(curid); orders.push(tagElements[j].value); } postdata['imgids'] = imgids; postdata['orders'] = orders; postdata['goodsId'] = goodsId; $.ajax({ type:"POST", url:"/goods/contentsaveorder", data:postdata, //返回数据的格式 datatype: "text",//"xml", "html", "script", "json", "jsonp", "text". //成功返回之后调用的函数 success:function(data){ alert(data); }, //调用执行后调用的函数 complete: function(XMLHttpRequest, textStatus){ //alert(XMLHttpRequest.responseText); //alert(textStatus); }, //调用出错执行的函数 error: function(){ //请求出错处理 alert('error'); } }); } </script> </body> </html>
2,sku.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script type="text/javascript" language="JavaScript" src="/js/jquery-1.6.2.min.js"></script> </head> <body> <form id='content_order'> <div id="main" style="display: flex;flex-direction: column;"> <div> <input type="text" id="attrtype" name="attrtype" value="" /> <input type="button" value="添加sku分类" onclick="addattrtype()"> </div> <div id="attrlist" style="width:500px;background: #ffffff;overflow: hidden;"> </div> <div id="skulist" style="width:500px;background: #ffffff;display: flex;flex-direction: column;overflow: hidden;"> </div> </div> <!--main end--> </form> <input type="button" value="保存sku" th:onclick="savesku([[${goodsId}]])" /> <script> //------------------------------------------------------------global var //分类列表 var global_type = []; //分类1的属性列表 var attr_list_0 = []; //分类2的属性列表 var attr_list_1 = []; //-------------------------------------------------------------method //添加分类 function addattrtype() { if (global_type.length >=2 ){ alert("属性种类数量不能超过2类"); return false; } var typename = document.getElementById('attrtype').value; if (global_type.indexOf(typename)>=0) { alert(typename+"已存在"); return false; } if (typename == "") { alert("类别名称不可为空"); return false; } global_type.push(typename); //addonetype(typename); var html = "<div id='type"+typename+"' style='background: #ffffff;width:500px;min-height:130px;'>" + "<div style='background: #eeeeee;text-align: left;height:30px;border-radius:5px;'>"+ "<div style='float:left;margin-left:10px;'>"+typename+"<input id='checkimage"+typename+"' type='checkbox' onclick='checkimage(\""+typename+"\")'/>图片</div>" + "<div style='height:30px;float:right;'>" + "<input type='text' id='"+typename+"attrnew' value='' />" + "<input type='button' value='添加属性' onclick='addattr(\""+typename+"\")'/>" + "<input type='button' value='删除分类' onclick='delete_type(\""+typename+"\")'/>" + "</div></div>"+ "<div id='"+typename+"attrlist' style='background: #ffffff;width:500px;min-height:100px;overflow:hidden;'>" + "</div>"+ "</div>"; //document.getElementById('attrlist').innerHTML += html; $("#attrlist").append(html); } //设置是否上传图片 function checkimage(typename) { var ischecked = document.getElementById("checkimage"+typename).checked; alert(typename+":"+ischecked); if (ischecked == true) { for(i = 0; i < global_type.length; i++) { //var curdispid = "dispup"+cur_attr_list[i]; //document.getElementById(curdispid).style.display="block"; if (global_type[i] == typename) { continue; } else { var isotherchecked = document.getElementById("checkimage"+global_type[i]).checked; if (isotherchecked == true) { alert(global_type[i]+"已被选中使用图片,如果要设置"+typename+"使用图片请先把其他使用图片的分类取消"); document.getElementById("checkimage"+typename).checked = false; return false; } } } } if (ischecked == true) { settypeimagetrue(typename) } else { settypeimagefalse(typename) } } //使一个分类下的attr支持上传图片 function settypeimagetrue(typename) { var typeidx = global_type.indexOf(typename); var cur_attr_list; if (typeidx == 0) { cur_attr_list = attr_list_0; } else { cur_attr_list = attr_list_1; } for(i = 0; i < cur_attr_list.length; i++) { var curdispid = "dispup"+cur_attr_list[i]; document.getElementById(curdispid).style.display="block"; } } //使一个分类下的attr不支持上传图片 function settypeimagefalse(typename) { var typeidx = global_type.indexOf(typename); var cur_attr_list; if (typeidx == 0) { cur_attr_list = attr_list_0; } else { cur_attr_list = attr_list_1; } for(i = 0; i < cur_attr_list.length; i++) { var curdispid = "dispup"+cur_attr_list[i]; document.getElementById(curdispid).style.display="none"; //清除图片 document.getElementById("img"+cur_attr_list[i]).src=""; document.getElementById("img"+cur_attr_list[i]).style.display="none"; //清除file的选中文件 document.getElementById("file"+cur_attr_list[i]).value=''; } } //预览图片 function preview(attrname) { var fileObj = document.getElementById("file"+attrname); var file=fileObj.files[0]; var r = new FileReader(); r.readAsDataURL(file); r.onload=function(e){ document.getElementById("img"+attrname).src = e.target.result; document.getElementById("img"+attrname).style.display="block"; } } //添加属性 function addattr(typename) { var typeidx = global_type.indexOf(typename); var cur_attr_list; if (typeidx == 0) { cur_attr_list = attr_list_0; } else { cur_attr_list = attr_list_1; } var attrname = document.getElementById(typename+'attrnew').value; if (attr_list_0.indexOf(attrname)>=0) { alert(attrname+"已存在"); return false; } if (attr_list_1.indexOf(attrname)>=0) { alert(attrname+"已存在"); return false; } if (attrname == "") { alert("属性名称不可为空"); return false; } cur_attr_list.push(attrname); var ischecked = document.getElementById("checkimage"+typename).checked; var imagetextdisp = ""; if (ischecked == true) { imagetextdisp = "block"; } else { imagetextdisp = "none"; } var html = "<div id='attr"+attrname+"' style='margin-left:8px;margin-top:8px;width:80px;height:80px;line-height:80px;text-align:center;background: #eeeeee;float:left;border-radius:10px;position:relative;overflow:hidden;'>"+ "<img id='img"+attrname+"' src='' style='position:absolute;width:80px;height:80px;display: none;' />"+ "<div style='width:20px;height:20px;line-height:20px;background: #eeeeee;right:0px;position:absolute;text-align: center;' onclick='delete_attr(\""+typename+"\",\""+attrname+"\")'>x</div>"+ "<div id='dispup"+attrname+"' style='width:80px;height:20px;opacity:0.9;line-height:20px;background: rgb(138, 138, 238);bottom:0px;position:absolute;text-align: center;display: "+imagetextdisp+";'>"+ "<div style='position:relative;'>" + "<input type='file' name='file"+attrname+"' id='file"+attrname+"' onchange='preview(\""+attrname+"\")' style='position:absolute;z-index:100;width:80px;height:20px;left:0px;background: #ff0000;opacity:0;'/>"+ "<div style='position: absolute;width:80px;height:20px;line-height:20px;opacity:0.9;font-size:12px;text-align:center;'>上传图片</div>"+ "</div></div>"+ attrname+ "</div>"; document.getElementById(typename+'attrlist').innerHTML += html; refresh_sku_list(); } //删除分类 function delete_type(typename) { if (confirm("确认删除分类:"+typename+"吗?") == false) { return false; } //清空数组 var typeidx = global_type.indexOf(typename); if (typeidx == 0) { attr_list_0.length = 0; //如果删除的是第一个分类? attr_list_0 = attr_list_1.concat(); attr_list_1.length = 0; } else { attr_list_1.length = 0; } //从type数组中删除 var typeidx = global_type.indexOf(typename); global_type.splice(typeidx,1); //从div中删除 $("#type"+typename).remove(); //刷新sku refresh_sku_list(); } //删除属性 function delete_attr(typename,attrname) { if (confirm("确认删除属性:"+attrname+"吗?") == false) { return false; } var typeidx = global_type.indexOf(typename); //alert(typename+";idx:"+typeidx); var cur_attr_list; if (typeidx == 0) { cur_attr_list = attr_list_0; } else { cur_attr_list = attr_list_1; } //从数组中删除 var attridx = cur_attr_list.indexOf(attrname); cur_attr_list.splice(attridx,1); //从div中删除 $("#attr"+attrname).remove(); //刷新sku refresh_sku_list(); } //刷新sku列表 function refresh_sku_list() { //只有一个为空 if (attr_list_0.length == 0 && attr_list_1.length == 0) { document.getElementById('skulist').innerHTML = ""; } if (attr_list_0.length > 0 && attr_list_1.length == 0) { var html = "<div style='border-radius:8px;margin-left:5px;width:490px;background:#eeeeee;'>"+ "<div style='padding-left:10px;width:200px;float:left;'>属性</div>"+ "<div style='width:100px;float:left;'>价格</div>"+ "<div style='width:100px;float:left;'>库存</div>"+ "</div>"; document.getElementById('skulist').innerHTML = html; // //if (attr_list_0.length) var htmlattr= "<div style='border-radius:8px;margin-left:5px;margin-top:10px;width:490px;background:#eeeeee;display: flex;flex-direction: column;'>"; for(i = 0; i < attr_list_0.length; i++) { htmlattr += "<div>"+ "<div style='padding-left:10px;width:200px;float:left;'>"+attr_list_0[i]+"</div>"+ "<div style='width:100px;float:left;'><input type='text' name='price_'"+attr_list_0[i]+" value='0.00'></div>"+ "<div style='width:100px;float:left;'><input type='text' name='stock_'"+attr_list_0[i]+" value='0'></div>"+ "</div>"; } htmlattr +="</div>"; document.getElementById('skulist').innerHTML += htmlattr; } if (attr_list_0.length > 0 && attr_list_1.length > 0) { var html = "<div style='border-radius:8px;margin-left:5px;width:490px;background:#eeeeee;'>"+ "<div style='padding-left:10px;width:210px;float:left;'>属性</div>"+ "<div style='width:100px;float:left;'>价格</div>"+ "<div style='width:120px;float:left;'>库存</div>"+ "</div>"; document.getElementById('skulist').innerHTML = html; //得到每大行高度 var blockheight = attr_list_1.length*30; for(i = 0; i < attr_list_0.length; i++) { var html = "<div style='background: #eeeeee;border-radius: 5px;margin-left:5px;margin-top:5px;width:490px;'>"+ "<div style='padding-left:10px;width:90px;float:left;height:"+blockheight+"px;line-height:"+blockheight+"px;'>"+attr_list_0[i]+"</div>"+ "<div style='width:380px;float:left;display: flex;flex-direction: column;'>"; for(j = 0; j < attr_list_1.length; j++) { html += "<div style='width:400px;'><div style='width:100px;float:left;height:30px;line-height:30px;'>"+attr_list_1[j]+"</div>"+ "<div style='float:left;width:100px;'><input type='text' id='price_"+attr_list_0[i]+"_"+attr_list_1[j]+"' value='0.00'></div>"+ "<div style='float:left;width:100px;'><input type='text' id='stock_"+attr_list_0[i]+"_"+attr_list_1[j]+"' value='0'></div></div>"; } html += "</div>"+ "</div>"; document.getElementById('skulist').innerHTML += html; } } } //提交sku的数据 function savesku(goodsId) { //alert(goodsId); var postdata = {}; postdata['goodsId'] = goodsId; var typelist = []; //得到两个分类 for(i = 0; i < global_type.length; i++) { var typeobj = {"typeName":global_type[i]}; var isotherchecked = document.getElementById("checkimage"+global_type[i]).checked; if (isotherchecked == true) { typeobj["isImage"] = 1; } else { typeobj["isImage"] = 0; } //得到下面的属性: var cur_attr_list; if (i == 0) { cur_attr_list = attr_list_0; } else { cur_attr_list = attr_list_1; } var attrlist=[]; for(j = 0; j < cur_attr_list.length; j++) { attrlist.push(cur_attr_list[j]); } typeobj['attrList'] = attrlist; //typeobj typelist.push(typeobj); } postdata["typeList"] = typelist; //遍历sku: var skulist = []; for(i = 0; i < attr_list_0.length; i++) { for(j = 0; j < attr_list_1.length; j++) { var key = attr_list_0[i]+"_"+attr_list_1[j]; var price = document.getElementById('price_'+key).value; var stock = document.getElementById('stock_'+key).value; var skuone = {"key":key,"price":price,"stock":stock}; skulist.push(skuone); } } postdata["skuList"] = skulist; var filelist = []; var cur_attr_list; if (i == 0) { cur_attr_list = attr_list_0; } else { cur_attr_list = attr_list_1; } for(i = 0; i < global_type.length; i++) { var typeobj = {"typename": global_type[i]}; var isotherchecked = document.getElementById("checkimage" + global_type[i]).checked; if (isotherchecked == true) { if (i == 0) { cur_attr_list = attr_list_0; } else if (i == 1) { cur_attr_list = attr_list_1; } } } //var postdatafile = {}; var postdatafile=new FormData(); // for(i = 0; i < cur_attr_list.length; i++) { var fileid = "file"+cur_attr_list[i]; if ("undefined" == typeof($("#"+fileid)[0].files[0])) { continue; } else { // var fileobj = {"fileName":cur_attr_list[i],"file":$("#"+fileid)[0].files[0]}; postdatafile.append(["file_"+cur_attr_list[i]],$("#"+fileid)[0].files[0]); filelist.push(fileobj); } } postdata["fileList"] = filelist; console.log(postdata); var postdatajson = {}; //postdatafile['json'] = JSON.stringify(postdata); postdatafile.append("json",JSON.stringify(postdata)); console.log(postdatafile); alert("begin ajax"); $.ajax({ type:"POST", url:"/goods/skuadded", data:postdatafile, //返回数据的格式 datatype: "text",//"xml", "html", "script", "json", "jsonp", "text". processData:false, // 将数据转换成对象,不对数据做处理,故 processData: false contentType:false, // 不设置数据类型 //成功返回之后调用的函数 success:function(data){ alert(data); }, //调用执行后调用的函数 complete: function(XMLHttpRequest, textStatus){ //alert(XMLHttpRequest.responseText); //alert(textStatus); }, //调用出错执行的函数 error: function(){ //请求出错处理 alert('error'); } }); } </script> </body> </html>
六,测试效果
1,测试提交排序值:
访问:
http://127.0.0.1:8080/goods/image?goodsId=2
如图:
服务端能正确接收到图片id和提交的图片的排序值
2,测试提交创建商品sku:
访问:
http://127.0.0.1:8080/goods/sku?goodsId=2
如图:
可以看到服务端可以接收到属性对应的图片文件和属性的对应关系,以及sku的价格库存信息
七,查看spring boot版本
. ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.3.4.RELEASE)