引言
最近公司需要做個沖印類型的小程序,一陣徘徊后,才決定快速集成一個微型產品來。
正題
幾乎每個程序都需要用到圖片。
在小程序中我們可以通過image組件顯示圖片。
當然小程序也是可以上傳圖片的,參照微信小程序官方文檔。
step_1 選擇圖片
- 通過
wx.chooseImage(OBJECT)
實現
官方示例代碼:
wx.chooseImage({
count: 1, // 默認9
sizeType: ['original', 'compressed'], // 可以指定是原圖還是壓縮圖,默認二者都有
sourceType: ['album', 'camera'], // 可以指定來源是相冊還是相機,默認二者都有
success: function (res) {
// 返回選定照片的本地文件路徑列表,tempFilePath可以作為img標簽的src屬性顯示圖片
var tempFilePaths = res.tempFilePaths }
})
圖片最多只可以選擇9張, 也可以通過拍攝照片實現,當選擇完圖片之后會獲取到圖片路徑, 這個路徑在本次啟動期間有效。
如果需要保存就需要用wx.saveFile(OBJECT)
step_2 上傳圖片
- 通過
wx.uploadFile(OBJECT)
可以將本地資源文件上傳到服務器。
原理就是客戶端發起一個 HTTPS POST
請求,其中 content-type
為 multipart/form-data
。
官方示例代碼
wx.chooseImage({
success: function(res) {
var tempFilePaths = res.tempFilePaths
wx.uploadFile({
url: 'http://example.weixin.qq.com/upload', //僅為示例,非真實的接口地址
filePath: tempFilePaths[0],
name: 'file',
formData:{
'user': 'test'
},
success: function(res){
var data = res.data
//do something
}
})
}
})
示例代碼
看完了文檔, 寫一個上傳圖片就沒有那么麻煩了,下面是真實場景的代碼
import constant from '../../common/constant';
Page({
data: {
src: "../../image/photo.png", //綁定image組件的src
//略...
},
onLoad: function (options) {
//略...
},
uploadPhoto() {
var that = this;
wx.chooseImage({
count: 1, // 默認9
sizeType: ['compressed'], // 可以指定是原圖還是壓縮圖,默認二者都有
sourceType: ['album', 'camera'], // 可以指定來源是相冊還是相機,默認二者都有
success: function (res) {
// 返回選定照片的本地文件路徑列表,tempFilePath可以作為img標簽的src屬性顯示圖片
var tempFilePaths = res.tempFilePaths;
upload(that, tempFilePaths);
}
})
}
})
function upload(page, path) {
wx.showToast({
icon: "loading",
title: "正在上傳"
}),
wx.uploadFile({
url: constant.SERVER_URL + "/FileUploadServlet",
filePath: path[0],
name: 'file',
header: { "Content-Type": "multipart/form-data" },
formData: {
//和服務器約定的token, 一般也可以放在header中
'session_token': wx.getStorageSync('session_token')
},
success: function (res) {
console.log(res);
if (res.statusCode != 200) {
wx.showModal({
title: '提示',
content: '上傳失敗',
showCancel: false
})
return;
}
var data = res.data
page.setData({ //上傳成功修改顯示頭像
src: path[0]
})
},
fail: function (e) {
console.log(e);
wx.showModal({
title: '提示',
content: '上傳失敗',
showCancel: false
})
},
complete: function () {
wx.hideToast(); //隱藏Toast
}
})
}
后端代碼
后端是用java寫的,一開始的時候,后端開始用了一些框架接收上傳的圖片,出現了各種問題,后來使用了純粹的Servlet就沒有了問題, 把代碼貼出來省的以后麻煩了。
注意: 代碼使用了公司內部的框架,建議修改后再使用
public class FileUploadServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private static Logger logger = LoggerFactory.getLogger(FileUploadServlet.class);
public FileUploadServlet() {
super();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
JsonMessage<Object> message = new JsonMessage<Object>();
EOSResponse eosResponse = null;
String sessionToken = null;
FileItem file = null;
InputStream in = null;
ByteArrayOutputStream swapStream1 = null;
try {
request.setCharacterEncoding("UTF-8");
//1、創建一個DiskFileItemFactory工廠
DiskFileItemFactory factory = new DiskFileItemFactory();
//2、創建一個文件上傳解析器
ServletFileUpload upload = new ServletFileUpload(factory);
//解決上傳文件名的中文亂碼
upload.setHeaderEncoding("UTF-8");
// 1. 得到 FileItem 的集合 items
List<FileItem> items = upload.parseRequest(request);
logger.info("items:{}", items.size());
// 2. 遍歷 items:
for (FileItem item : items) {
String name = item.getFieldName();
logger.info("fieldName:{}", name);
// 若是一個一般的表單域, 打印信息
if (item.isFormField()) {
String value = item.getString("utf-8");
if("session_token".equals(name)){
sessionToken = value;
}
}else {
if("file".equals(name)){
file = item;
}
}
}
//session校驗
if(StringUtils.isEmpty(sessionToken)){
message.setStatus(StatusCodeConstant.SESSION_TOKEN_TIME_OUT);
message.setErrorMsg(StatusCodeConstant.SESSION_TOKEN_TIME_OUT_MSG);
}
String userId = RedisUtils.hget(sessionToken,"userId");
logger.info("userId:{}", userId);
if(StringUtils.isEmpty(userId)){
message.setStatus(StatusCodeConstant.SESSION_TOKEN_TIME_OUT);
message.setErrorMsg(StatusCodeConstant.SESSION_TOKEN_TIME_OUT_MSG);
}
//上傳文件
if(file == null){
}else{
swapStream1 = new ByteArrayOutputStream();
in = file.getInputStream();
byte[] buff = new byte[1024];
int rc = 0;
while ((rc = in.read(buff)) > 0) {
swapStream1.write(buff, 0, rc);
}
Usr usr = new Usr();
usr.setObjectId(Integer.parseInt(userId));
final byte[] bytes = swapStream1.toByteArray();
eosResponse= ServerProxy.getSharedInstance().saveHeadPortrait(usr, new RequestOperation() {
@Override
public void addValueToRequest(EOSRequest request) {
request.addMedia("head_icon_media", new EOSMediaData(EOSMediaData.MEDIA_TYPE_IMAGE_JPEG, bytes));
}
});
// 請求成功的場合
if (eosResponse.getCode() == 0) {
message.setStatus(ConstantUnit.SUCCESS);
} else {
message.setStatus(String.valueOf(eosResponse.getCode()));
}
}
} catch (Exception e) {
e.printStackTrace();
} finally{
try {
if(swapStream1 != null){
swapStream1.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if(in != null){
in.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
PrintWriter out = response.getWriter();
out.write(JSONObject.toJSONString(message));
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
小程序前端效果____仿朋友圈的照片上傳。
很粗糙、很簡陋,請勿怪。
先上圖:
首先說明一下我的前端基本訴求:
- 一行明確指定圖片顯示個數
- 可從照片圖庫一次多選若干張照片并顯示在前端
- 照片與照片之間有明顯的間隔
- 達到一定數量照片時將添加按鈕隱藏
- 添加按鈕加上一些樣式
index.wxml
<view class='uploadContainer'>
<view wx:for="{{image}} wx:key="*this"">
<image src='{{image[index]}}' bindload='imageLoad' style='width:{{pic_width}}px;height:{{pic_height}}px;margin:{{margin_length}}px' />
<button bindtap='deleteImg' data-num='{{index}}' class='deleteBtn' style='width:{{pic_width}}px;margin:{{margin_length}}px'>刪除</button>
</view>
<image src='../../icons/add.png' bindtap='uploadImg' class='addimg' style='display:{{img_button}};width:{{pic_width-2}}px;height:{{pic_height-2}}px ;margin:{{margin_length}}px' />
</view>
index.wxss
.uploadContainer {
display: flex;
flex-direction: row;
justify-content: flex-start;
flex-wrap: wrap;
}
.addimg {
border-style: inset;
border-width: 1px;
border-color: #ddd;
}
index.js
//index.js
//獲取應用實例
var app = getApp()
var px2rpx = 2, windowWidth = 375;
Page({
/**
* 頁面的初始數據
*/
data: {
pic_width: 300, //初始化值
pic_height: 300, //初始化值
img_button: 'inline-block',
image: [],
msg: '',
margin_length: 3,//顯示圖片的外邊距margin值(單位:px像素)
},
//事件處理函數
onLoad: function () {
console.log('onLoad');
wx.getSystemInfo({
success: function (res) {
windowWidth = res.windowWidth;
px2rpx = 750 / windowWidth;
}
})
this.setData({
pic_width: (windowWidth - 8 * this.data.margin_length) / 4,
pic_height: (windowWidth - 8 * this.data.margin_length) / 4
})
},
onPullDownRefresh: function () {
wx.stopPullDownRefresh()
},
onShareAppMessage: function () {
return {
title: 'xx公司',
path: 'pages/index/index'
}
},
uploadImg: function () {
var that = this;
var image = this.data.image;
//設定最終一起上傳照片的最大數量
if (this.data.image.length < 101) {
wx.chooseImage({
count: 9, // 默認9
sizeType: ['original'], // 可以指定是原圖還是壓縮圖,默認二者都有
sourceType: ['album', 'camera'], // 可以指定來源是相冊還是相機,默認二者都有
success: function (res) {
wx.uploadFile({
url: app.appUrl.url + 'upload',//這個方法就是后臺處理上傳的方法
filePath: res.tempFilePaths[0], //獲取到上傳的圖片
name: 'file',
success: function (info) {
console.log(info);//info.data就是上傳成功的圖片名稱 您可以在wxml里面搞一個隱藏域存儲起來,在上面Submit提交里拼裝一塊提交出去
}
})
},
complete: function (res) {
// 返回選定照片的本地文件路徑列表,tempFilePath可以作為img標簽的src屬性顯示圖片
console.log(res.tempFilePaths);
//判空處理,不能使用res.tempFilePaths.length方法,會報空指針
//解決了取消選擇照片時會將空照片上傳的問題
if(res.tempFilePaths!=null){
//語法:concat的用法,concat之后返回的是一個新的對象,因此要賦值給image
image=image.concat(res.tempFilePaths);
that.setData({ image: image })
//當有100張照片上傳時,將上傳圖片的按鈕隱藏掉
if (that.data.image.length == 100) {
that.setData({
img_button: 'none',
})
}
}
}
})
}
},
deleteImg: function (e) {
var image = this.data.image;
image.splice(e.currentTarget.dataset.num, 1);
this.setData({
image: image,
img_button: 'inline-block',
})
},
})