在现在的网络开发中,上传图片类的需求实在是太普通不过了,但是对于怎么样做到上传图片,对于刚开始建立项目的时候,还是有点不知所措的。也许有幸,我们做的项目是之前已经有人写过类似的用例了,那么我们只需要依葫芦画瓢就行了。
好好了解下图片上传(文件上传)的方式,对于认知的提升还是有好处的。而且说不定哪天你就有个这样的需求呢,这里是一条龙上传。
本文就一个从 app 到 php 层,再到 java 层的流程,演译下整个上传图片的流程吧。
一、app 端获取用户选择的图片,转化为输入流,上传至 php 前端接口:
- 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);
- }
- }
二、php 服务端接收文件,临时保存并继续上传至 java 后端:
1. 接收文件类
- <?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. 上传类
- <?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')),
- ]);
三、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 写的接口中,都会涉及到加解密问题,此时,文件不应该算作加密的范畴,而应单独给一个字段。
来源: