在现在的网络开发中,上传图片类的需求实在是太普通不过了,但是对于怎么样做到上传图片,对于刚开始建立项目的时候,还是有点不知所措的。也许有幸,我们做的项目是之前已经有人写过类似的用例了,那么我们只需要依葫芦画瓢就行了。
好好了解下图片上传(文件上传)的方式,对于认知的提升还是有好处的。而且说不定哪天你就有个这样的需求呢,这里是一条龙上传。
本文就一个从 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 finalString TAG = "uploadFile";
- private static final intTIME_OUT = 10 * 1000;// 超时时间
- private static finalString 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];
- intlen = 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 staticString post(String url, Map params, Map 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 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 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];
- intlen = 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();
- intres = 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 voidmain(String[] args)throws IOException {
- String requestURL = "sss";
- finalMap params = newHashMap();
- params.put("send_userId", String.valueOf(1));
- params.put("send_email", "ss@ss.com");
- finalMap files = newHashMap();
- files.put("uploadfile",newFile("/var/data/de.jpg"));
- finalString 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;
- classUserControllerextends RestAction
- {
- /**
- * 用户头像上传
- */
- public functionset_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 functioncurlGet($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 functioncurlPost($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 functionuploads($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 functionuploadAvatar($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' =>newCURLFile(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 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 写的接口中,都会涉及到加解密问题,此时,文件不应该算作加密的范畴,而应单独给一个字段。
来源: http://www.cnblogs.com/yougewe/p/7095884.html