本文使用的技术栈:小程序开发、SpringBoot、MyBatis、Redis
今天要写一个商城项目中的发布商品模块,需求跟闲鱼这样的app基本一致,就是基本的上传表单数据和图片。图片模块我想采用类似朋友圈的方式来完成,符合大部分用户的操作逻辑。
现在朋友圈发图片已经可以拖拽改变图片顺序或删除图片了,但是用小程序实现拖拽功能太麻烦了。所以,我将换一种方式来实现图片删除和预览,最后,再写两个后端接口,把商品信息和图片存入数据库。
这里前端用到了Vant Weapp的UI库 https://github.com/youzan/vant-weapp
先来看看前端页面的代码
效果如下图:
这里前端用到了Vant Weapp的UI库 https://github.com/youzan/vant-weapp
效果如下图:
表单部分代码很简单,所以这里主要分析添加图片的代码逻辑。第一步,找一张添加照片的图标绑定一个wx.chooseImage事件,定义一个全局变量count作为当前选择的图片数量,使用户选择的图片在9张以内;定义一个数组,用于存放当前图片的url,在用户选择完照片后,该数组会更新,并用wx:for渲染至前端页面。
chooseimage: function () {
var me = this;
//动态更新当前用户可以上传的图片数
var count = 9 - that.data.img_url.length;
wx.chooseImage({
count: count, // 默认9
sizeType: ['original', 'compressed'], // 可以指定是原图还是压缩图,默认二者都有
sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有
success: function (res) {
if (res.tempFilePaths.length > 0) {
//把每次选择的图push进数组
let img_url = that.data.img_url;
for (let i = 0; i < res.tempFilePaths.length; i++) {
img_url.push(res.tempFilePaths[i])
}
me.setData({
img_url: img_url
})
//图如果满了9张,不显示添加图片图标
if (that.data.img_url.length >= 8) {
me.setData({
hideAdd: 1
})
} else {
me.setData({
hideAdd: 0
})
}
}
}
})
}
当图片数量达到9张,隐藏添加图片图标
chooseimage: function () {
var me = this;
//动态更新当前用户可以上传的图片数
var count = 9 - that.data.img_url.length;
wx.chooseImage({
count: count, // 默认9
sizeType: ['original', 'compressed'], // 可以指定是原图还是压缩图,默认二者都有
sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有
success: function (res) {
if (res.tempFilePaths.length > 0) {
//把每次选择的图push进数组
let img_url = that.data.img_url;
for (let i = 0; i < res.tempFilePaths.length; i++) {
img_url.push(res.tempFilePaths[i])
}
me.setData({
img_url: img_url
})
//图如果满了9张,不显示添加图片图标
if (that.data.img_url.length >= 8) {
me.setData({
hideAdd: 1
})
} else {
me.setData({
hideAdd: 0
})
}
}
}
})
}
第二步,给图片加上预览和删除功能,预览功能使用wx.previewImage绑定图片的索引值即可;删除功能则是在图片右上角加上一个删除图标来绑定删除事件,删除后实时更新图片列表。
//预览图片
previewImg:function(e){
var me = this;
var img_url = me.data.img_url;
var index = e.target.dataset.index;
wx.previewImage({
urls: img_url,
current : img_url[index],
success:function(res){
console.log(res);
}
})
},
//删除图片
deleteImg: function (e){
var me = this;
var img_url = me.data.img_url;
var index = e.target.dataset.index;
img_url.splice(index, 1);
me.setData({
img_url: img_url,
//若当前图片超过9张,则隐藏添加图标;少于9张则显示添加图标。
hideAdd: me.data.img_url.length <9 ? false : true
})
},
第三步,上传商品信息和图片,我将这个功能分为两部分完成,首先将表单数据先上传,再将图片上传。这里假设用户已经成功使用微信登录小程序,且登录态保存已保存在本地缓存。在表单数据上传成功后,会返回一个商品ID到前端,再调用上传图片方法,将商品ID和每张图片的相对路径保存至数据库。实现代码如下。
//上传商品信息
send:function(e){
var me = this;
console.log(e);
var goodsName = e.detail.value.goodsName;
var goodsDesc = e.detail.value.goodsDesc;
var goodsPrice = e.detail.value.goodsPrice;
var goodsNum = e.detail.value.goodsNum;
var goodsAddress = e.detail.value.goodsAddress;
var goodsPhone = e.detail.value.goodsPhone;
var thirdSession = wx.getStorageSync("thirdsession")
wx.showLoading({
title: '发布中--',
})
var serverUrl = app.serverUrl;
wx.request({
url: serverUrl + '/goods/uploadGoods',
data: {
thirdSession: thirdSession,
goodsName: goodsName,
goodsDesc: goodsDesc,
goodsPrice: goodsPrice,
goodsNum: goodsNum,
goodsAddress: goodsAddress,
goodsPhone: goodsPhone,
},
method: "GET",
success: function (res) {
wx.hideLoading();
console.log(res.data.data);
//商品信息上传成功后,上传商品图片
me.uploadGoodsImg(res.data.data);
}
})
}
需要注意的是,小程序的wx.uploadFile接口的资源路径是String类的。所以多图上传时,我会递归调用这个接口来完成。
//上传商品信息
send:function(e){
var me = this;
console.log(e);
var goodsName = e.detail.value.goodsName;
var goodsDesc = e.detail.value.goodsDesc;
var goodsPrice = e.detail.value.goodsPrice;
var goodsNum = e.detail.value.goodsNum;
var goodsAddress = e.detail.value.goodsAddress;
var goodsPhone = e.detail.value.goodsPhone;
var thirdSession = wx.getStorageSync("thirdsession")
wx.showLoading({
title: '发布中--',
})
var serverUrl = app.serverUrl;
wx.request({
url: serverUrl + '/goods/uploadGoods',
data: {
thirdSession: thirdSession,
goodsName: goodsName,
goodsDesc: goodsDesc,
goodsPrice: goodsPrice,
goodsNum: goodsNum,
goodsAddress: goodsAddress,
goodsPhone: goodsPhone,
},
method: "GET",
success: function (res) {
wx.hideLoading();
console.log(res.data.data);
//商品信息上传成功后,上传商品图片
me.uploadGoodsImg(res.data.data);
}
})
}
//上传图片
uploadGoodsImg: function (goodsId){
var me = this;
var imgFilePaths = me.data.img_url;
var count = me.data.count;
var serverUrl = app.serverUrl;
wx.showLoading({
title: '上传图片中--',
})
wx.uploadFile({
url: serverUrl + '/goods/uploadGoodsImg',
filePath: imgFilePaths[count],
name: 'file',
formData: {
goodsId: goodsId
},
success: function (res) {
//可统计成功上传图片数
},
fail: function (res) {
//可统计上传失败图片数
},
complete: function (res) {
count++;
me.setData({
count: count
})
if (count >= imgFilePaths.length) {
var data = JSON.parse(res.data);
console.log(data);
wx.hideLoading();
if (data.status == 200) {
wx.hideLoading();
wx.showToast({
title: '上传成功!~~',
icon: 'success'
});
me.setData({
count: 0
})
} else if (data.status == 500) {
wx.showToast({
title: data.msg,
});
}
} else {
//图片未上传完,递归调用本方法。
me.uploadGoodsImg(goodsId);
}
}
})
}
第四步,后端接口使用IO流工具IOUtils将图片写入到指定的路径下,并将相对路径保存至数据库。这里存放图片路径采用了一个单独的表。具体代码如下:
//保存商品信息
@GetMapping("/uploadGoods")
public IMoocJSonResult uploadFace(String thirdSession,String goodsName,
String goodsDesc,double goodsPrice,String goodsPhone,
int goodsNum,String goodsAddress) throws Exception {
if (StringUtils.isBlank(thirdSession)) {
return IMoocJSONResult.errorMsg("thirdSession is none");
}
String value = (String) redis.get("Wxuser-redis-session:"+thirdSession);
System.out.println(value);
if (StringUtils.isBlank(value)) {
return IMoocJSONResult.errorMsg("session timeout");
}
//解析json格式的str
JSonObject json = JSONObject.parseObject(value);
String openId = json.getString("openid");
String goodsId = sid.nextShort();
Goods goods = new Goods();
goods.setId(goodsId);
goods.setSellerId(openId);
goods.setSellerPhone(goodsPhone);
goods.setAddress(goodsAddress);
goods.setGoodsDesc(goodsDesc);
goods.setLikeCounts(0);
goods.setGoodsName(goodsName);
goods.setPrice(goodsPrice);
goods.setGoodsNum(goodsNum);
goodsService.saveGoods(goods);
return IMoocJSONResult.ok(goodsId);
}
上传图片接口
@PostMapping(value="/uploadGoodsImg" ,headers="content-type=multipart/form-data")
public IMoocJSonResult uploadGoodsImg(String goodsId,@RequestParam("file") MultipartFile[] files) throws Exception {
//文件保存的命名空间
String fileSpace="D:/cunjin-xianyu-test";
//保存到数据库的相对路径
String uploadPathDB="/"+goodsId+"/img";
FileOutputStream fileOutputStream=null;
InputStream inputStream= null;
try {
if(files != null && files.length>0) {
String fileName= files[0].getOriginalFilename();
if (StringUtils.isNotBlank(fileName)) {
//文件保存的最终路径
String finalPath = fileSpace + uploadPathDB + "/" + fileName;
//设置数据库保存的路径
uploadPathDB+=("/"+fileName);
File outFile=new File(finalPath);
if (outFile.getParentFile()!=null || !outFile.getParentFile().isDirectory()) {
//创建父文件夹
outFile.getParentFile().mkdirs();
}
fileOutputStream = new FileOutputStream(outFile);
inputStream = files[0].getInputStream();
IOUtils.copy(inputStream, fileOutputStream);
}else {
return IMoocJSONResult.errorMsg("上传出错");
}
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
return IMoocJSONResult.errorMsg("上传出错");
}finally {
if(fileOutputStream!=null) {
fileOutputStream.flush();
fileOutputStream.close();
}
}
GoodsImg goodsImg = new GoodsImg();
goodsImg.setGoodsId(goodsId);
goodsImg.setImg(uploadPathDB);
goodsService.saveGoodsImg(goodsImg);
return IMoocJSONResult.ok("上传成功");
}
好了,代码写完了,现在来进行前后端联调,测试一下。我们先选几张图,输一些数据
//保存商品信息
@GetMapping("/uploadGoods")
public IMoocJSonResult uploadFace(String thirdSession,String goodsName,
String goodsDesc,double goodsPrice,String goodsPhone,
int goodsNum,String goodsAddress) throws Exception {
if (StringUtils.isBlank(thirdSession)) {
return IMoocJSONResult.errorMsg("thirdSession is none");
}
String value = (String) redis.get("Wxuser-redis-session:"+thirdSession);
System.out.println(value);
if (StringUtils.isBlank(value)) {
return IMoocJSONResult.errorMsg("session timeout");
}
//解析json格式的str
JSonObject json = JSONObject.parseObject(value);
String openId = json.getString("openid");
String goodsId = sid.nextShort();
Goods goods = new Goods();
goods.setId(goodsId);
goods.setSellerId(openId);
goods.setSellerPhone(goodsPhone);
goods.setAddress(goodsAddress);
goods.setGoodsDesc(goodsDesc);
goods.setLikeCounts(0);
goods.setGoodsName(goodsName);
goods.setPrice(goodsPrice);
goods.setGoodsNum(goodsNum);
goodsService.saveGoods(goods);
return IMoocJSONResult.ok(goodsId);
}
@PostMapping(value="/uploadGoodsImg" ,headers="content-type=multipart/form-data")
public IMoocJSonResult uploadGoodsImg(String goodsId,@RequestParam("file") MultipartFile[] files) throws Exception {
//文件保存的命名空间
String fileSpace="D:/cunjin-xianyu-test";
//保存到数据库的相对路径
String uploadPathDB="/"+goodsId+"/img";
FileOutputStream fileOutputStream=null;
InputStream inputStream= null;
try {
if(files != null && files.length>0) {
String fileName= files[0].getOriginalFilename();
if (StringUtils.isNotBlank(fileName)) {
//文件保存的最终路径
String finalPath = fileSpace + uploadPathDB + "/" + fileName;
//设置数据库保存的路径
uploadPathDB+=("/"+fileName);
File outFile=new File(finalPath);
if (outFile.getParentFile()!=null || !outFile.getParentFile().isDirectory()) {
//创建父文件夹
outFile.getParentFile().mkdirs();
}
fileOutputStream = new FileOutputStream(outFile);
inputStream = files[0].getInputStream();
IOUtils.copy(inputStream, fileOutputStream);
}else {
return IMoocJSONResult.errorMsg("上传出错");
}
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
return IMoocJSONResult.errorMsg("上传出错");
}finally {
if(fileOutputStream!=null) {
fileOutputStream.flush();
fileOutputStream.close();
}
}
GoodsImg goodsImg = new GoodsImg();
goodsImg.setGoodsId(goodsId);
goodsImg.setImg(uploadPathDB);
goodsService.saveGoodsImg(goodsImg);
return IMoocJSONResult.ok("上传成功");
}
好了,代码写完了,现在来进行前后端联调,测试一下。我们先选几张图,输一些数据
在本地新建一个文件夹,用于存放图片,路径与代码的命名空间一致。
好了,现在我们点击发布,观察数据库和本地文件。
可以看到,我们发布的商品信息和图片相对路径都已经存入数据库了,同时,本地也已经保存了这两张图片。测试成功!
总结
- 数据库存放多张图片路径,除了用本文的方法,还可以使用一些特定符号将路径分隔开,存入商品信息表中,这样,即使上传多张图片也只会存入一行数据,不过这种方法也是在网上看过,自己还未实践过。
- 在使用wx.uploadFile接口时,要记得将content-type改为multipart/form-data。
- 使用IOUtils写文件的拷贝非常方便,无需各种输入流,然后读取line,输出到输出流。
- 数据库存放多张图片路径,除了用本文的方法,还可以使用一些特定符号将路径分隔开,存入商品信息表中,这样,即使上传多张图片也只会存入一行数据,不过这种方法也是在网上看过,自己还未实践过。
- 在使用wx.uploadFile接口时,要记得将content-type改为multipart/form-data。
- 使用IOUtils写文件的拷贝非常方便,无需各种输入流,然后读取line,输出到输出流。



