解決移動端頭像引數上傳的前後臺問題(三)
這是畢設系列的第三篇。其餘兩篇在:
本地伺服器的搭建和前後端打通(一)https://www.jianshu.com/p/5d1fe5b24993
本地伺服器的搭建和前後端打通(二)https://www.jianshu.com/p/565bfa10e926
年裡面一直在走親戚,一沒好好休息,二沒好好完成專案。不過還行,自己還算順利。有幾個難點也在公司做後臺的大佬幫助下解決了,這裡和大家說下上傳頭像檔案的實現。
一:移動端部分
想必大家在網上搜索 Android 上傳頭像程式碼可以有一堆。但是隻有 Android 的程式碼,沒有後臺,沒有後臺。那各位小哥哥小姐姐你能告訴我我們自己實現的話有哪些注意點嗎。在踩了一堆亂坑之後我終於完成了,並把幾個點和大家說下。【
上程式碼:
/** * 上傳頭像的介面 */ @Multipart @POST("phoneUser/modifyAvatar") fun uploadAvatar(@Part("userid") userid: RequestBody, @Part imgs: MultipartBody.Part): Observable<BaseJackson<LoginUser>>
BaseJackson 這個根據自己後臺返回的基本格式來寫就可以
data class BaseJackson<T>( var result: String, var data: T, var code: Int, var msg: String ) : Serializable
LoginBean
/** *author:Jiwenjie *email:[email protected] *time:2018/12/17 *desc:登陸使用者的 bean *version:1.0 */ class LoginUser : Serializable { var userid: String? = null var userphone: String? = null var username: String? = null var password: String? = null var continuesigndays: Int? = null// indicate(表示, 表明) how many days have singed var signintoday: Boolean? = null// 表示今天是否簽到 var signintime: String? = null// 表示今天簽到時間 var signintotaldays: Int? = null// 表示一共簽到了幾天 var logintime: String? = null// 表示第一次登陸時間 any day's the first login time var logouttime: String? = null// 表示登出時間 var totaltime: Long? = null// 使用 App 的總時間 var avatar: String? = null// 使用者頭像在伺服器儲存的地址 var signout: Boolean = false// 標記是否退出賬號 var collectioncount: String? = null // 收藏了幾篇文章 var profile: String? = null// profile }
上傳頭像的工具類
/** *author:Jiwenjie *email:[email protected] *time:2019/02/09 *desc:上傳檔案圖片的工具類 *version:1.0 */ object UploadUtils { @SuppressLint("CheckResult") fun uploadAvatar(userid: String, imgPath: String, listener: UploadImageListener) {// true 為上傳成功,false 為上傳失敗 var file: File? = null try { file = File(imgPath) } catch (e: URISyntaxException) { e.printStackTrace() } val requestBody = RequestBody.create(MediaType.parse("multipart/form-data"), file!!) val body = MultipartBody.Part.createFormData("image", file.name, requestBody)// 這裡的 key 要和後臺接收的 body 名字相同才行 /** * 引數和圖片一起上傳的時候兩種方式,這是第一種,第二種是使用 query 關鍵字 */ val idBody = RequestBody.create(MediaType.parse("multipart/form-data"), userid) LogUtils.e("UploadUtils" + file.name) LogUtils.e("UploadUtils" + file.absolutePath) RetrofitManager.provideClient(ConstantConfig.JACKSON_BASE_URL) .create(JacksonApi::class.java) .uploadAvatar(idBody, body) .compose(RxJavaUtils.applyObservableAsync()) .subscribe({ if (it.result == "succeed") { listener.uploadSuccess(it.data) } else { listener.uploadFailed(it.msg) } }, { listener.uploadFailed(it.message.toString()) }) } interface UploadImageListener { fun uploadSuccess(user: LoginUser) fun uploadFailed(msg: String) } }
基本就在這裡了,具體的呼叫我們有展示出來。不過相信這點難不住各位啦。
這裡我使用的是 Retrofit + RxJava 的網路請求。程式碼語言是 Kt。大家可以使用 MVP 模式來寫,我頁面都是使用 MVP 完成的。這裡之所以只是單獨封裝是因為用的地方比較少而且這樣會更加方便。
那麼重點來了:
1)如何引數和圖片一起上傳。
答:首先上傳圖片必須使用 @POST 註解且 @Part imgs: MultipartBody.Part 引數中加上這句。不要問為什麼,就可 Spring 處理圖片檔案上傳的時候必須使用 MultipartFile 一樣。都是特定的類,我們只要這樣使用就行。
其次,我們正常使用 @POST 傳遞引數的時候需要加上 @Field("xxx")。這裡不必,不但如此,而且變數名隨便起,是的你沒看錯,隨便起。我一直以為這個變數名稱需要和後臺的變數相對應,可是我錯了。是需要對應但不在這裡,下面我會提到。
key code
val requestBody = RequestBody.create(MediaType.parse("multipart/form-data"), file!!) val body = MultipartBody.Part.createFormData("image", file.name, requestBody)// 這裡的 key 要和後臺接收的 body 名字相同才行
這裡我加了備註相信大家可以看的很清楚。這裡傳遞的時候 createFormData 的第一個引數是一個名稱,這就是 key。後臺也是根據這個 key 來接收資料。到此如果你只傳遞圖片的話已近完成了。關鍵程式碼就這些(前臺)
2)如果引數和圖片一起傳遞怎麼辦
答:別擔心,有辦法。
/** * 引數和圖片一起上傳的時候兩種方式,這是第一種,第二種是使用 query 關鍵字 */ val idBody = RequestBody.create(MediaType.parse("multipart/form-data"), userid)
就在這。這句話我的理解就是把你傳遞的引數以表單的形式放在 RequestBody 中。如果有錯歡迎打臉。
到這裡基本前臺的程式碼就結束了。
為什麼我說網上的程式碼很坑,
其一網上的程式碼沒說後臺接收的關鍵字是在哪裡設定,我們預設都感覺實在介面的引數變數一樣。而且很多圖片 name 傳遞的時候有 image,有 png,啥啥都有,我一開始以為必須是圖片的意思就行。。。。
其二網上關於引數和圖片一起傳遞只是給出了方法而沒有實際使用 demo。這讓我們這些菜鳥怎麼接受的了。
不過各位不用擔心,我是實用主義。給的程式碼就在自己的畢設專案中親測可以。如果有任何問題都可以私信我。話不多說,下面看後臺程式碼。
後臺程式碼:
/** * 使用者頭像上傳 */ @RequestMapping(value = "/modifyAvatar", method = RequestMethod.POST) @ResponseBody public Map<String, Object> uploadAvatar(@RequestParam("userid") String userid, @RequestBody MultipartFile image) { // 在 spring 家族中上傳頭像都是使用 MultipartFile 類。多個檔案則是陣列型別 System.out.println("檔名:" + image.getOriginalFilename() + "\n" + "userid:" + String.valueOf(userid)); Map<String, Object> map = new HashMap<>(); if (!image.isEmpty()) { String originalFileName = image.getOriginalFilename(); String mimeType = request.getServletContext().getMimeType(originalFileName); System.out.println("mimeType: " + mimeType); // 是否圖片檔案 if (mimeType != null && mimeType.startsWith("image/")) { try { //PhoneUser user = (PhoneUser) session.getAttribute(Constant.SESSION_PHONE_USER); String suffix = originalFileName.split("\\.")[1];// 副檔名 // 上傳到專案根目錄的 upload 資料夾 String avatarPath = request.getSession().getServletContext().getRealPath("/upload") + //File.separator + user.getUsername() + File.separator + "avatar" + File.separator + System.currentTimeMillis() + "." + suffix; String savePath = avatarPath.substring(avatarPath.indexOf("\\upload")); String finPath = savePath.replaceAll("\\\\", "/"); System.out.println("savePath:" + savePath); System.out.println("finPath:" + finPath); /** * 上傳到具體的硬碟路徑,此時需要配置 tomcat 虛擬路徑 */ //String avatarPath = "I:" + File.separator + "ProjectsFolder" + File.separator + "IdeaProject" //+ File.separator + "MovieProject" + File.separator + "src" + File.separator + "main" //+ File.separator + "webapp" + File.separator + "upload" + File.separator + user.getUsername() //+ File.separator + "avatar" + File.separator + System.currentTimeMillis() + "." + suffix; System.out.println("tomcatPath: " + avatarPath); File saveFile = new File(avatarPath); if (!saveFile.getParentFile().exists()) { saveFile.getParentFile().mkdirs(); saveFile.createNewFile(); } image.transferTo(saveFile);//將檔案上傳到指定的伺服器的位置 int rows = userService.updateUserAvatar(userid, finPath.substring(1));// 儲存在資料庫中的路徑就從 upload 開始就可以了, // 這裡的 sub 是為了去除第一個 ‘/’ if (rows > 0) { System.out.println("上傳頭像成功"); //// 上傳檔案成功之後查詢 user,之後把最新的 user 返回 PhoneUser user = userService.getUserInfo(userid); if (user != null) { map.put("data", user); map = CommonUtils.operationSucceed(map); } else { map = CommonUtils.operationFailed(map, "other error", HttpStatus.NOT_FOUND.value()); } } else { System.out.println("上傳頭像失敗"); map = CommonUtils.operationFailed(map, "change data failed", HttpStatus.BAD_REQUEST.value()); } } catch (IOException e) { // 上傳過程出錯 System.out.println(e.getMessage()); map = CommonUtils.operationFailed(map, "upload fail", HttpStatus.INTERNAL_SERVER_ERROR.value()); e.printStackTrace(); } } else { // 不是圖片檔案返回相關資訊 map = CommonUtils.operationFailed(map, "please upload an image file", HttpStatus.BAD_REQUEST.value()); } // 空檔案返回相關 } else { System.out.println("empty file"); map = CommonUtils.operationFailed(map, "empty file", HttpStatus.BAD_REQUEST.value()); } return map; }
這裡我都加了註釋相信大家應該都能明白。這裡的 upload 地址其實就是你的 tomcat 的地址。換句話說就是 localhost:8080 + upload 路徑 就可以在瀏覽器中訪問了。當然前提是你上傳成功了之後才行。
這裡也有幾個問題。你如果不關注我註釋的程式碼的話沒什麼問題,如果你想把路徑改為具體的硬碟上的某個地址你需要重新配置下 tomcat 的虛擬路徑對映才行。這裡我就不多說了,大家可以搜下,很簡單,只要改一下配置檔案的引數即可。
至於其中的正則替換符號是因為這裡預設的是 “\”。但是網路請求我需要的是 "/"。所以做了替換。
好了程式碼就到這裡。下次再見。
願我們都能走過迷茫,
願我們不負青春,
願我們回首可以笑看而不後悔,
你要相信所有的付出都會在某一天給你回報,
願我們成為真實的自己。