在如今的網絡開發中,上傳圖片類的需求實在是太普通不過了,可是對於怎麼樣作到上傳圖片,對於剛開始創建項目的時候,仍是有點不知所措的。也許有幸,咱們作的項目是以前已經有人寫過相似的用例了,那麼咱們只須要依葫蘆畫瓢就好了。php
好好了解下圖片上傳(文件上傳)的方式,對於認知的提高仍是有好處的。並且說不定哪天你就有個這樣的需求呢,這裏是一條龍上傳。前端
本文就一個從app到php層,再到java層的流程,演譯下整個上傳圖片的流程吧。java
1、app端獲取用戶選擇的圖片,轉化爲輸入流,上傳至php前端接口:web
package com.dia.ration; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.util.HashMap; import java.util.Map; import java.util.UUID; /** * 上傳文件到服務器類 */ public class UploadUtil { private static final String TAG = "uploadFile"; private static final int TIME_OUT = 10 * 1000; // 超時時間 private static final String CHARSET = "utf-8"; // 設置編碼 /** * Android上傳文件到服務端 * * @param file 須要上傳的文件 * @param RequestURL 請求的rul * @return 返回響應的內容 */ public static String uploadFile(File file, String RequestURL) { String result = null; String BOUNDARY = UUID.randomUUID().toString(); // 邊界標識 隨機生成 String PREFIX = "--", LINE_END = "\r\n"; String CONTENT_TYPE = "multipart/form-data"; // 內容類型 try { URL url = new URL(RequestURL); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setReadTimeout(TIME_OUT); conn.setConnectTimeout(TIME_OUT); conn.setDoInput(true); // 容許輸入流 conn.setDoOutput(true); // 容許輸出流 conn.setUseCaches(false); // 不容許使用緩存 conn.setRequestMethod("POST"); // 請求方式 conn.setRequestProperty("Charset", CHARSET); // 設置編碼 conn.setRequestProperty("connection", "keep-alive"); conn.setRequestProperty("Content-Type", CONTENT_TYPE + ";boundary=" + BOUNDARY); if (file != null) { DataOutputStream dos = new DataOutputStream(conn.getOutputStream()); StringBuffer sb = new StringBuffer(); sb.append(PREFIX); sb.append(BOUNDARY); sb.append(LINE_END); /** * 這裏重點注意: name裏面的值爲服務端須要key 只有這個key 才能夠獲得對應的文件 * filename是文件的名字,包含後綴名的 好比:abc.png */ sb.append("Content-Disposition: form-data; name=\"uploadfile\"; filename=\"" + file.getName() + "\"" + LINE_END); sb.append("Content-Type: application/octet-stream; charset=" + CHARSET + LINE_END); sb.append(LINE_END); dos.write(sb.toString().getBytes()); InputStream is = new FileInputStream(file); byte[] bytes = new byte[1024]; int len = 0; while ((len = is.read(bytes)) != -1) { dos.write(bytes, 0, len); } is.close(); dos.write(LINE_END.getBytes()); byte[] end_data = (PREFIX + BOUNDARY + PREFIX + LINE_END).getBytes(); dos.write(end_data); dos.flush(); InputStream input = conn.getInputStream(); StringBuffer sb1 = new StringBuffer(); int ss; while ((ss = input.read()) != -1) { sb1.append((char) ss); } result = sb1.toString(); } } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return result; } /** * 經過拼接的方式構造請求內容,實現參數傳輸以及文件傳輸 * * @param url Service net address * @param params text content * @param files pictures * @return String result of Service response * @throws IOException */ public static String post(String url, Map<String, String> params, Map<String, File> files) throws IOException { String BOUNDARY = UUID.randomUUID().toString(); String PREFIX = "--", LINEND = "\r\n"; String MULTIPART_FROM_DATA = "multipart/form-data"; String CHARSET = "UTF-8"; URL uri = new URL(url); HttpURLConnection conn = (HttpURLConnection) uri.openConnection(); conn.setReadTimeout(10 * 1000); // 緩存的最長時間 conn.setDoInput(true); // 容許輸入 conn.setDoOutput(true); // 容許輸出 conn.setUseCaches(false); // 不容許使用緩存 conn.setRequestMethod("POST"); conn.setRequestProperty("connection", "keep-alive"); conn.setRequestProperty("Charsert", "UTF-8"); conn.setRequestProperty("Content-Type", MULTIPART_FROM_DATA + ";boundary=" + BOUNDARY); // 首先組拼文本類型的參數 StringBuilder sb = new StringBuilder(); for (Map.Entry<String, String> entry : params.entrySet()) { sb.append(PREFIX); sb.append(BOUNDARY); sb.append(LINEND); sb.append("Content-Disposition: form-data; name=\"" + entry.getKey() + "\"" + LINEND); sb.append("Content-Type: text/plain; charset=" + CHARSET + LINEND); sb.append("Content-Transfer-Encoding: 8bit" + LINEND); sb.append(LINEND); sb.append(entry.getValue()); sb.append(LINEND); } DataOutputStream outStream = new DataOutputStream(conn.getOutputStream()); outStream.write(sb.toString().getBytes()); // 發送文件數據 if (files != null) for (Map.Entry<String, File> file : files.entrySet()) { StringBuilder sb1 = new StringBuilder(); sb1.append(PREFIX); sb1.append(BOUNDARY); sb1.append(LINEND); sb1.append("Content-Disposition: form-data; name=\"uploadfile\"; filename=\"" + file.getValue().getName() + "\"" + LINEND); sb1.append("Content-Type: application/octet-stream; charset=" + CHARSET + LINEND); sb1.append(LINEND); outStream.write(sb1.toString().getBytes()); InputStream is = new FileInputStream(file.getValue()); byte[] buffer = new byte[1024]; int len = 0; while ((len = is.read(buffer)) != -1) { outStream.write(buffer, 0, len); } is.close(); outStream.write(LINEND.getBytes()); } byte[] end_data = (PREFIX + BOUNDARY + PREFIX + LINEND).getBytes(); outStream.write(end_data); outStream.flush(); int res = conn.getResponseCode(); InputStream in = conn.getInputStream(); StringBuilder sb2 = new StringBuilder(); if (res == 200) { int ch; while ((ch = in.read()) != -1) { sb2.append((char) ch); } } outStream.close(); conn.disconnect(); return sb2.toString(); } // 測試 public static void main(String[] args) throws IOException { String requestURL = "sss"; final Map<String, String> params = new HashMap<String, String>(); params.put("send_userId", String.valueOf(1)); params.put("send_email", "ss@ss.com"); final Map<String, File> files = new HashMap<String, File>(); files.put("uploadfile", new File("/var/data/de.jpg")); final String result = UploadUtil.post(requestURL, params, files); System.out.println("result is: " + result); } }
2、php服務端接收文件,臨時保存並繼續上傳至java後端:ajax
1. 接收文件類spring
<?php namespace App\Controller; use Action\RestAction; use Api\UploadApi; class UserController extends RestAction { /** * 用戶頭像上傳 */ public function set_avatar_post($code) { $uploadApi = new UploadApi(); $res = $uploadApi->uploads('avatar'); $filename = $res['data']; $result = $uploadApi->uploadAvatar($code, $filename); @unlink($filename); //刪除圖片 if (!$result['status']) { $this->response($result); } $avatar = A("Personal", "Api")->getAvatar($code); $this->response($avatar); } }
2. 上傳類json
<?php namespace Api\Action; class UploadApi { public function __construct() { //... } public function curlGet($url, $param = array(), $timeout = 30, $ajaxResponseImmediately = true) { $opts = array( CURLOPT_TIMEOUT => $timeout, CURLOPT_RETURNTRANSFER => 1, CURLOPT_SSL_VERIFYPEER => false, CURLOPT_SSL_VERIFYHOST => false, CURLOPT_HTTPHEADER => $header ); switch (strtoupper($method)) { // case 'POST': // $opts[CURLOPT_URL] = $url; // $opts[CURLOPT_POST] = 1; // $opts[CURLOPT_POSTFIELDS] = $param; // break; default: $opts[CURLOPT_URL] = $url . '?' . http_build_query($param); break; } $ch = curl_init(); curl_setopt_array($ch, $opts); $result = curl_exec($ch); //記錄請求日誌 curl_close($ch); return $result; } public function curlPost($url, $param = array(), $timeout = 30, $ajaxResponseImmediately = true) { $opts = array( CURLOPT_TIMEOUT => $timeout, CURLOPT_RETURNTRANSFER => 1, CURLOPT_SSL_VERIFYPEER => false, CURLOPT_SSL_VERIFYHOST => false, CURLOPT_HTTPHEADER => $header ); switch (strtoupper($method)) { case 'POST': default: $opts[CURLOPT_URL] = $url; $opts[CURLOPT_POST] = 1; $opts[CURLOPT_POSTFIELDS] = $param; break; // $opts[CURLOPT_URL] = $url . '?' . http_build_query($param); // break; } $ch = curl_init(); curl_setopt_array($ch, $opts); $result = curl_exec($ch); $log_data['result'] = $result; if (!empty($param)) $log_data['param'] = $param; curl_close($ch); return $result; } public function uploads($param = '') { if ($param == '') { $param = 'imgFile'; } // 文件保存目錄路徑 $save_url = dirname(dirname(dirname(__FILE__))) . DIRECTORY_SEPARATOR . "Uploads" . DIRECTORY_SEPARATOR; // 定義容許上傳的文件擴展名 $ext_arr = array( 'image' => array('gif', 'jpg', 'jpeg', 'png', 'bmp'), ); // 最大文件大小 $max_size = 20 * 1024; // PHP上傳失敗 if (!empty ($_FILES [$param] ['error'])) { switch ($_FILES [$param] ['error']) { case '1' : $error = '超過php.ini容許的大小。'; break; case '2' : $error = '超過表單容許的大小。'; break; case '3' : $error = '圖片只有部分被上傳。'; break; case '4' : $error = '請選擇圖片。'; break; case '6' : $error = '找不到臨時目錄。'; break; case '7' : $error = '寫文件到硬盤出錯。'; break; case '8' : $error = 'File upload stopped by extension。'; break; case '999' : default : $error = '未知錯誤。'; } $result = array('status' => '0', 'error' => '111111', 'msg' => $error); } // 有上傳文件時 if (empty ($_FILES) === false) { $file_name = $_FILES [$param] ['name'];// 原文件名 $tmp_name = $_FILES [$param] ['tmp_name'];// 服務器上臨時文件名 $file_size = $_FILES [$param] ['size'];// 文件大小 // 檢查文件名 if (!$file_name) { $result = array('status' => '0', 'error' => '111111', 'msg' => '請選擇文件'); } // 檢查是否已上傳 if (@is_uploaded_file($tmp_name) === false) { $result = array('status' => '0', 'error' => '111111', 'msg' => '上傳失敗'); } // 檢查文件大小 if ($file_size > $max_size) { $result = array('status' => '0', 'error' => '111111', 'msg' => ''); } // 檢查目錄名 $dir_name = empty ($_GET ['dir']) ? 'image' : trim($_GET ['dir']); if (empty ($ext_arr [$dir_name])) { $result = array('status' => '0', 'error' => '111111', 'msg' => '目錄名不正確'); } // 得到文件擴展名 $temp_arr = explode('.', $file_name); $file_ext = array_pop($temp_arr); $file_ext = trim($file_ext); $file_ext = strtolower($file_ext); // 檢查擴展名 if (in_array($file_ext, $ext_arr [$dir_name]) === false) { $result = array('status' => '0', 'error' => '111111', 'msg' => '上傳文件擴展名是不容許的擴展名'); } // 建立文件夾 if ($dir_name !== '') { if (!file_exists($save_url)) { mkdir($save_url); } } $new_file_name = date('YmdHis') . '_' . rand(10000, 99999) . '.' . $file_ext; $file_path = $save_url . $new_file_name; if (move_uploaded_file($tmp_name, $file_path) === false) { $result['msg'] = '上傳文件失敗'; $result = array('status' => '0', 'error' => '111111', 'msg' => '上傳文件失敗'); } else { $result = array('status' => '1', 'error' => '000000', 'data' => $file_path); } @chmod($file_path, 0644); return $result; } } public function uploadAvatar($code, $avatarImageName) { $url = $this->getApiUrl(__METHOD__); $data = array( "code" => $code, "ip" => $this->params['ip'], "avatar" => !empty($avatarImageName) ? '@' . $avatarImageName : '', ); $result = $this->curlPost($url, $data); return $result; } }
這樣,php就已經接收到了來自客戶端的 圖片上傳了,而且已經上傳到java後端服務器。後端
注意:這裏有個坑,即php版本大於5.6之後,直接使用 @ 符號沒法上傳文件了,須要 加上一個安全選項:CURLOPT_SAFE_UPLOAD => false 才能夠,或者使用5.6以的高級上傳類上傳文件:緩存
curl_setopt(ch, CURLOPT_POSTFIELDS, [ 'file' => new CURLFile(realpath('image.png')), ]);
3、java後端接收php上傳的圖片安全
package com.xx.c.action; import com.xx.core.pojo.Constants; import com.xx.core.pojo.MicroException; import com.xx.core.pojo.ResponseEntity; import com.xx.core.web.spring.bind.annotation.ClientIP; import com.xx.core.web.spring.bind.annotation.SessionUserId; import com.xx.c.pojo.user.UpFileUrlBean; import com.xx.c.service.user.UserService; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartHttpServletRequest; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import java.util.Iterator; @Controller @RequestMapping(value = "upload") public class UploadAction { @Resource(name = "userService") private UserService userService; @RequestMapping(value = "/uploadAvatar", method = RequestMethod.POST, produces = "application/json") @ResponseBody public Object uploadAvatar(@RequestParam String code, @ClientIP String addIp, @SessionUserId Long userId, @ModelAttribute UpFileUrlBean bean, HttpServletRequest request) throws MicroException { bean.setAddIp(addIp); bean.setUserId(userId); try { // 轉型爲MultipartHttpRequest: MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request; // 從其中取出一個文件 後續可以使用spring 上傳文件方法:file.transferTo(destFile); MultipartFile file = null; for (Iterator<String> it = multipartRequest.getFileNames(); it.hasNext();) { file = multipartRequest.getFile((String) it.next()); } userService.uploadAvatar(file, bean); } catch (Exception e) { throw new MicroException(Constants.ErrCode.UPLOAD_AVATAR_TO_SERVER_FAILED, Constants.ErrMsg.UPLOAD_AVATAR_TO_SERVER_FAILED, e); } ResponseEntity ret = new ResponseEntity(Constants.System.OK); return ret; } }
至此,上傳流程已經完成了。(固然,後續還可能使用其餘上傳,好比dubbo調用文件系統上傳文件,調用第三方sdk上傳到文件服務器。。。, 原理大抵同樣,使用字節流進行傳輸,而後讀取出來存儲到文件)
通常爲app寫的接口中,都會涉及到加解密問題,此時,文件不該該算做加密的範疇,而應單獨給一個字段。