前言
由于之前寫過一篇關于微信小程式圖檔上傳到背景的文章參考《微信小程式上傳圖檔到伺服器(java背景以及使用springmvc)》,有讀者回報關于多圖檔上傳的問題,是以部落客決定另外寫一篇關于微信小程式多圖檔上傳的文章,但是如果隻是單純寫多圖檔上傳的話,感覺上有點單調,是以決定結合上傳至SFTP伺服器來寫這一篇文章,關于SFTP伺服器的搭建請參考《CentOS通過使用自帶SSH服務結合Nginx搭建SFTP圖檔伺服器》。
當然,本文主要講述的内容為微信小程式多圖檔的上傳功能的實作。微信小程式中主要使用到的API為wx.chooseImage以及wx.uploadFile,微信小程式API的具體介紹請參考上述提到的之前寫過的圖檔上傳文章或者參考官方說明文檔,後端語言為Java,使用的主要架構為SpringBoot和SpringMVC,使用的圖檔上傳元件為JSch,而關于圖檔伺服器搭建的問題請參考上述提到的另一篇文章,在此不多做介紹,在文章的末尾會給出小程式和後端代碼的GitHub位址。
微信小程式多圖檔上傳
頁面設計
這裡的圖檔選擇頁面直接使用了WeUI提供的布局頁面,是以在小程式代碼中引入了WeUI的CSS檔案,具體代碼可在文章末尾給出的GitHub位址中檢視,頁面效果圖如下所示:
頁面效果圖1
頁面效果圖2
頁面效果圖3
上傳成功後控制台輸出内容
前端圖檔上傳
首先,講一下實作的思路,因為微信官方并沒有提供多檔案上傳的API,是以要實作多圖檔的上傳那麼必定需要用到正常的檔案上傳的API,是以部落客實作的思路主要就是通過遞歸的方式來實作多圖檔上傳,具體思路為:定義兩個數組,分别用于存儲微信選擇圖檔後的圖檔資料(可用于圖檔上傳)以及存儲圖檔上傳後傳回的通路URL位址(用于後續送出操作),定義一個帶有下标參數的遞歸函數,首次調用時傳入下标0,進入該函數後首先會判斷目前下标是否小于所選圖檔的數組長度,若小于則使用圖檔數組目前下标的圖檔資料進行圖檔上傳操作,如果上傳成功,則将傳回的圖檔通路URL位址添加至事先定義好的URL數組中,并遞歸調用該函數,并将下标加1;若大于等于,則表示圖檔已經都上傳完成,可根據業務需求進行下一步操作,如将上傳後的所有圖檔URL位址送出至背景資料庫等操作。具體邏輯代碼如下所示:
// index.js
// 擷取應用執行個體
const app = getApp()
// 頁面對象
var that
Page({
/**
* 頁面的初始資料
*/
data: {
// 圖檔檔案數組
files: [],
// 圖檔URL位址數組
urls: [],
// 最多圖檔數
max: 3
},
/**
* 生命周期函數--監聽頁面加載
*/
onLoad: function () {
that = this
},
/**
* 生命周期函數--監聽頁面顯示
*/
onShow: function () {
},
/**
* 選擇圖檔
*/
chooseImage: () => {
wx.chooseImage({
count: that.data.max, // 可選取圖檔數量
sizeType: ['original', 'compressed'], // 可以指定是原圖還是壓縮圖,預設二者都有
sourceType: ['album', 'camera'], // 可以指定來源是相冊還是相機,預設二者都有
success: function (res) {
// 傳回標明照片的本地檔案路徑清單,tempFilePath可以作為img标簽的src屬性顯示圖檔
that.setData({
files: that.data.files.concat(res.tempFilePaths)
});
}
})
},
/**
* 圖檔顯示
* @param e 目前選中的圖檔對象
*/
previewImage: e => {
wx.previewImage({
current: e.currentTarget.id, // 目前顯示圖檔的http連結
urls: that.data.files // 需要預覽的圖檔http連結清單
})
},
/**
* 上傳按鈕點選處理函數
*/
bindUploadTap: () => {
if (that.data.files.length === 0) { // 未選擇圖檔
wx.showToast({
title: '請先選擇圖檔!',
icon: 'none'
})
return
}
wx.showLoading({
title: '圖檔上傳中...'
})
// 初始化圖檔URL位址數組
that.setData({
urls: []
})
// 執行圖檔上傳遞歸函數
that.uploadImage(0)
},
/**
* 圖檔上傳
* @param index 目前圖檔下标
*/
uploadImage: index => {
if (index < that.data.files.length) { // 如果目前下标未大于選取的圖檔數
wx.uploadFile({
url: 'http://127.0.0.1/upload',
filePath: that.data.files[index],
// 與背景接收圖檔檔案的參數名相同
name: 'file',
success: res => {
// 将伺服器傳回的結果轉換成為JSON對象
var response = JSON.parse(res.data)
if (response.code === app.globalData.code.SUCCESS) { // 操作成功
that.setData({
urls: that.data.urls.concat([response.data])
})
// 遞歸調用自身
that.uploadImage(index + 1)
} else if (response.code === app.globalData.code.FAIL) { // 操作失敗
wx.showToast({
title: response.msg,
icon: 'none'
})
}
}
})
} else {
wx.hideLoading()
wx.showToast({
title: '圖檔上傳完成!'
})
console.log('圖檔URL位址:')
console.log(that.data.urls)
}
}
})
後端圖檔接收和上傳至SFTP伺服器
上面已經講過本文後端使用的架構為SpringBoot和SpringMVC,是以自然也就是通過SpringMVC的MultipartFile來接收微信小程式上傳的圖檔,并通過JSch這個元件将圖檔上傳至SFTP伺服器中,最後再把圖檔的通路URL位址傳回給小程式端。
JSch是以純Java實作的SSH2,它能夠連接配接到sshd伺服器,并且使用端口轉發、X11轉發、檔案傳輸等,最主要的是可以将其功能內建到Java程度當中,JSch常用的通道有三種,分别是Shell、Exec、SFTP,前兩類用于執行指令,而SFTP用于檔案的上傳或下載下傳,這裡主要使用JSch用于圖檔上傳.
JSch一般使用步驟:
- 建立JSch對象;
- 從JSch對象中擷取Session;
- 為Session設定參數并連接配接;
- 打開Channel通道;
- 建立通道連接配接;
- 通道操作。
Controller層代碼如下所示:
/**
* 圖檔上傳控制器
* Created at 2019-05-28 23:39:48
* @author Allen
*/
@Controller
@RestController
public class UploadController {
@Autowired
private UploadService service;
/**
* 圖檔上傳
* @param file 圖檔MultipartFile對象
* @return Map<String, Object> {"code": 狀态碼, "msg"/"data": 額外資訊/資料}
*/
@RequestMapping(value = "upload", method = RequestMethod.POST)
public Map<String, Object> upload(MultipartFile file) {
return service.upload(file);
}
}
Service層代碼如下所示:
/**
* 圖檔上傳業務處理類
* Created at 2019-05-29 14:22:43
* @author Allen
*/
@Service
public class UploadService {
@Autowired
private JSchUtil jSchUtil;
// 狀态碼
private static final int SUCCESS = 300, FAIL = 400;
/**
* 圖檔上傳
* @param file 圖檔MultipartFile對象
* @return Map<String, Object> {"code": 狀态碼, "msg"/"data": 額外資訊/資料}
*/
public Map<String, Object> upload(MultipartFile file) {
Map<String, Object> result = new HashMap<>();
String url = "";
try {
url = jSchUtil.uploadFile(file);
} catch (Exception e) {
e.printStackTrace();
}
if (url == null || "".equals(url)) {
result.put("code", FAIL);
result.put("msg", "圖檔上傳失敗!");
} else {
result.put("code", SUCCESS);
result.put("data", url);
}
return result;
}
}
JSch工具類代碼如下所示:
/**
* SFTP檔案上傳工具類
* Created at 2019-05-14 14:13:19
* @author Allen
*/
@Component
public class JSchUtil {
@Override
public String toString() {
return "JSchUtil [SFTP_HOST=" + SFTP_HOST + ", SFTP_PORT=" + SFTP_PORT + ", SFTP_USERNAME=" + SFTP_USERNAME
+ ", SFTP_PASSWORD=" + SFTP_PASSWORD + ", PATH=" + PATH + "]";
}
// SFTP的IP位址
@Value("${sftp.host}")
private String SFTP_HOST;
// SFTP的端口
@Value("${sftp.port}")
private int SFTP_PORT;
// SFTP的使用者名
@Value("${sftp.username}")
private String SFTP_USERNAME;
// SFTP的使用者密碼
@Value("${sftp.password}")
private String SFTP_PASSWORD;
// 檔案存放路徑
@Value("${sftp.path}")
private String PATH;
// 檔案通路位址
@Value("${file.url}")
private String FILE_URL;
// Session對象
private Session session = null;
// Channel對象
private Channel channel = null;
/**
* 上傳檔案至FTP
* @param multipartFile 要上傳的檔案
* @return String 檔案URL位址
*/
public String uploadFile(MultipartFile multipartFile) throws Exception {
// 檔案路徑
String filePath;
// 檔案名
String fileName;
// 檔案類型
String fileType;
// 初始檔案名
String originalFilename = multipartFile.getOriginalFilename();
// 擷取檔案類型
fileType = originalFilename.substring(originalFilename.lastIndexOf('.') + 1).toLowerCase();
if (fileType == null && !"jpg".equals(fileType) && !"png".equals(fileType) && !"gif".equals(fileType)) {
System.out.println("檔案類型有誤:" + fileType);
return null;
}
// 設定檔案名
fileName = System.currentTimeMillis() + "." + fileType;
// 擷取輸入流
InputStream inputStream = multipartFile.getInputStream();
// 擷取SFTP連接配接對象
ChannelSftp channel = getChannel();
// 上傳模式
int mode = ChannelSftp.OVERWRITE;
/*
* OVERWRITE:完全覆寫模式(如果目标檔案已經存在,傳輸的檔案将完全覆寫目标檔案,産生新的檔案)
* RESUME:恢複模式(檔案已經傳輸一部分,這時由于網絡或其他任何原因導緻檔案傳輸中斷,如果下一次傳輸相同的檔案)
* APPEND:追加模式(如果目标檔案已存在,傳輸的檔案将在目标檔案後追加)
*/
channel.put(inputStream, PATH + fileName, mode);
channel.quit();
System.out.println("上傳完畢:" + PATH + fileName);
// 設定檔案URL位址
filePath = FILE_URL + fileName;
System.out.println("圖檔URL位址:" + filePath);
closeChannel();
return filePath;
}
/**
* 關閉Channel
*/
public void closeChannel() {
if (channel != null) {
channel.disconnect();
}
if (session != null) {
session.disconnect();
}
}
/**
* 擷取基于SSL協定的ChannelSftp對象
* @return ChannelSftp SSL協定的ChannelSftp對象
*/
public ChannelSftp getChannel() throws JSchException {
// 建立JSch對象
JSch jsch = new JSch();
// 擷取session對象
session = jsch.getSession(SFTP_USERNAME, SFTP_HOST, SFTP_PORT);
// 設定密碼
session.setPassword(SFTP_PASSWORD);
Properties config = new Properties();
config.put("StrictHostKeyChecking", "no");
// 為session對象設定properties
session.setConfig(config);
// 通過session建立連結
session.connect();
// 打開SFTP通道
channel = session.openChannel("sftp");
// 建立SFTP通道的連接配接
channel.connect();
return (ChannelSftp)channel;
}
}
application.properties配置檔案如下所示:
server.port=80
# 伺服器IP位址
sftp.host=139.196.101.87
# SFTP端口
sftp.port=22
# 使用者名
sftp.username=mysftp
# 密碼
sftp.password=mysftp
# 對應伺服器使用者家目錄
sftp.path=/images/
# 檔案通路位址
file.url=http://139.196.101.87/images/
GitHub位址
微信小程式代碼:https://github.com/HKail/image-upload-wechat
Java背景代碼:https://github.com/HKail/image-upload-sftp
緻謝
文章最後,感謝大家對本文的耐心閱讀,部落客也是抱着希望能幫助到有需要的人的态度在寫文章,是以也希望本文能夠給需要的人帶來幫助,同時對于文中有疑惑的朋友也可以在下方進行留言,部落客将會在看到留言的第一時間進行回複,也歡迎大家在留言中一起學習交流,謝謝!