问题背景:app在上传图片时,同时传递参数,支持传递多个图片。本文中的环境默认已经配好了服务器的CodeIgniter框架。事实上不使用这个框架也是可以的。

一,服务器部分

1,在controllers下的helpers新建文件upload_helper.php


<?php/** * Make multifile array input complaint with CI_Upload.<br> * For use files[ ] input name you must use it method. * * @author porquero * * @example * In Controller<br> * $this->load->helper('upload');<br> * multifile_array();<br> * foreach ($_FILES as $file => $file_data) {<br> *    $this->upload->do_upload($file); * ... * * @link http://porquero.blogspot.com/2012/05/codeigniter-multifilearray-upload.html */function multifile_array(){    if(count($_FILES) == 0)        return;    $files = array();    $all_files = $_FILES['f_file']['name'];    $i = 0;    foreach ((array)$all_files as $filename) {        $files[++$i]['name'] = $filename;        $files[$i]['type'] = current($_FILES['f_file']['type']);        next($_FILES['f_file']['type']);        $files[$i]['tmp_name'] = current($_FILES['f_file']['tmp_name']);        next($_FILES['f_file']['tmp_name']);        $files[$i]['error'] = current($_FILES['f_file']['error']);        next($_FILES['f_file']['error']);        $files[$i]['size'] = current($_FILES['f_file']['size']);        next($_FILES['f_file']['size']);    }    $_FILES = $files;}

说明:

a.注意里面的key为'f_file',这就要求app或web在上传,建表单的时候将此值对应上。

b.该文件主要是遍历$_FILES,通过current得到当前file的信息转存到数组里,然后返回。注意转存后索引是从1开始的。转存的字段有name/type/tmp_name/error/size,使用next移动$_FILES['f_file']['key']的指针。

2.views里新建upload_form.php,用来在web上模拟测试上传是否成功:

<html><head>    <title>Upload Form</title></head><body><?php echo $error;?><?php $data = array('type'=>'shop', 'id'=>'1');?><?php echo form_open_multipart('upload/web_upload', '', $data);?><input type="file" name="f_file[]" multiple="multiple" size="20" /><br /><br /><input type="submit" value="upload" /></form></body></html>
注意:

a,这里使用了CI框架的form_open_multipart新建一个multipart的表单,访问的是控制器upload里的web_upload方法,第三个参数$data,用于模拟向服务器传递的post请求参数。当然你也可以在下面加几个<input>.

b,input里name对应的是f_file[],这个是跟 服务器那边统一好的。

c,若要支持多文件上传加上multiple="multiple",不加的话一次只能上传一个文件。


3,views下新建upload_success.php,显示上传成功后的界面。

<html><head>    <title>Upload Form</title></head><body><h3>Your file was successfully uploaded!</h3><ul>    <?php foreach ($upload_data as $item => $value):?>        <li><?php echo $item;?>: <?php echo $value;?></li>    <?php endforeach; ?></ul><p><?php echo anchor('upload', 'Upload Another File!'); ?></p></body></html>

注意:这里的$upload_data是控制器上传成功后传给view的数据。


4,接下来是最关键的一个类,在controllers文件夹下新建Upload.php,这是个控制器,上传最核心的。

<?phprequire_once APPPATH . 'controllers/base/BASE_CI_Controller.php';class Upload extends BASE_CI_Controller{    private $m_type = '';    private $m_id = '';    private $m_path = './application/cache/image';    private $m_error = array();    public function __construct()    {        parent::__construct();        $this->load->helper(array('form', 'url', 'upload'));        //$this->load->model('picture_model');    }    public function index()    {        $this->load->view('upload_form', array('error' => ' ' ));    }    public function app_upload(){        $this->init_argc();        multifile_array();        foreach ($_FILES as $file => $file_data){            $this->do_upload($file);        }        if($this->m_error == NULL || count($this->m_error) == 0){            $this->output->set_output(json_encode(array('msg'=>'上传成功')));        }else{            $this->output->set_output(json_encode($this->m_error));        }    }    public function do_upload($file)    {        $config['upload_path']      =  $this->m_path;        $config['allowed_types']    = 'gif|jpg|png|jpeg';        $config['max_size']     = 10240;        $config['max_width']        = 2000;        $config['max_height']       = 2000;        $config['file_name'] = Util::random_str();        $this->load->library('upload', $config);        if ( ! $this->upload->do_upload($file)){            $this->on_upload_error($this->upload->display_errors());        }        else{            $upload_data = $this->upload->data();            $this->on_upload_success($upload_data['file_name']);        }    }    public function do_upload2($file)    {        $config['upload_path']      =  $this->m_path;        $config['allowed_types']    = 'gif|jpg|png|jpeg';        $config['max_size']     = 10240;        $config['max_width']        = 2000;        $config['max_height']       = 2000;        $config['file_name'] = Util::random_str();        $this->load->library('upload', $config);        if ( ! $this->upload->do_upload($file))        {            $error = array('error' => $this->upload->display_errors());            $this->load->view('upload_form', $error);        }        else        {            $data = array('upload_data' => $this->upload->data());            $this->load->view('upload_success', $data);        }    }    public function web_upload()    {        multifile_array();        foreach ($_FILES as $file => $file_data){            $this->do_upload2($file);        }    }    private function init_argc() {        $this->m_type = $this->getPost('type');        $this->m_id = $this->getPost('id');        $this->m_path = $this->getPath($this->m_type);    }    private function getPath($type){        $path = './application/cache/image/shop';        if($type == "shop"){            $path =  './application/cache/image/shop';        }        return $path;    }    private function on_upload_success($name){        if($this->m_type == 'shop'){            //$this->picture_model->add_shop_picture($this->m_id, $this->m_type, $name);        }else if($this->m_type == 'avatar'){            //$this->picture_model->add_user_avatar($this->m_id, $this->m_type, $name);        }    }    private function on_upload_error($error){        $this->m_error['msg'] = $error;    }}?>
解释如下:

a,这里Upload是继承的BASE_CI_Controller,也可以换成CI_Controller,在自己的Base_CI_Controller里封装了自己项目一些常用的安全校验逻辑;

b,我定义了m_type记录上传图片的类型,m_id是图片所属对象的id,m_path为路径,根据type不同路径可以做区分。m_error纪录错误。在构造函数里,注意把几个helper都load进来。除此外我还写了个Picture_model用来操作图片相关的数据库,如果不需要这个model,可以注释掉。

c,app_load()是暴露给app用来上传的,init_argc()初始化post传来的各种参数。然后就是调multifile_array();之后遍历上传。待上传完毕后根据m_error里是否为空,判断是该显示什么消息给app。在do_upload()里的Util::random_str()是个很简单的对时间戳md5,防止图片名字一样:

Util里的代码:

    /**     * 产生新的token     * @return string     */    public static function token(){        $curr = Util::time();        return md5($curr);    }    public static function random_str(){        return Util::token();    }

每次上传成功后都调on_upload_success() on_upload_error()进行更新数据库等操作。其中on_upload_success()要接收$upload_data['file_name']),表示上传成功后的文件的名字。

d,web_upload是给web上传图片用的,通过do_upload2()上传成功后就加载一个view来显示上传后的信息。PS:保证你对目的文件夹有可写权限。

先用web测试下效果:http://localhost/~yanzi/city52/index.php/upload


二,客户端:基于Volley的多文件/图片上传类的封装

这个比较简单,基于volley封装的,MultipartRequest.java

package com.android.nomnom.volley;import android.util.Log;import com.android.volley.AuthFailureError;import com.android.volley.NetworkResponse;import com.android.volley.Request;import com.android.volley.Response;import com.android.volley.VolleyLog;import com.android.volley.toolbox.HttpHeaderParser;import org.apache.http.entity.mime.MultipartEntity;import org.apache.http.entity.mime.content.FileBody;import org.apache.http.entity.mime.content.StringBody;import java.io.ByteArrayOutputStream;import java.io.File;import java.io.IOException;import java.io.UnsupportedEncodingException;import java.nio.charset.Charset;import java.util.ArrayList;import java.util.Collections;import java.util.HashMap;import java.util.List;import java.util.Map;/** * 功能: * * @author yanzi E-mail: yanzi1225627@163.com * @version 创建时间: 2015-08-09 下午4:32 */public class MultipartRequest extends Request<String>{    private MultipartEntity entity = new MultipartEntity();    private  Response.Listener<String> mListener;    private List<File> mFileParts;    private String mFilePartName;    private Map<String, String> mParams;    /**     * 单个文件+参数 上传     * @param url     * @param listener     * @param errorListener     * @param filePartName     * @param file     * @param params     */    public MultipartRequest(String url, Response.Listener<String> listener, Response.ErrorListener errorListener,                            String filePartName, File file, Map<String, String> params){        super(Method.POST, url, errorListener);        mFileParts = new ArrayList<File>();        if(file != null && file.exists()){            mFileParts.add(file);        }else{            VolleyLog.e("MultipartRequest---file not found");        }        mFilePartName = filePartName;        mListener = listener;        mParams = params;        buildMultipartEntity();    }    /**     * 多个文件+参数上传     * @param url     * @param listener     * @param errorListener     * @param filePartName     * @param files     * @param params     */    public MultipartRequest(String url,Response.Listener<String> listener,Response.ErrorListener errorListener                            , String filePartName,List<File> files, Map<String, String> params) {        super(Method.POST, url, errorListener);        mFilePartName = filePartName;        mListener = listener;        mFileParts = files;        mParams = params;        buildMultipartEntity();    }    @Override    protected Response<String> parseNetworkResponse(NetworkResponse response) {        String parsed;        try {            parsed = new String(response.data,                    HttpHeaderParser.parseCharset(response.headers));        } catch (UnsupportedEncodingException e) {            parsed = new String(response.data);        }        return Response.success(parsed,                HttpHeaderParser.parseCacheHeaders(response));    }    @Override    protected void deliverResponse(String response) {        mListener.onResponse(response);    }    @Override    public Map<String, String> getHeaders() throws AuthFailureError {        Map<String, String> headers = super.getHeaders();        if (headers == null || headers.equals(Collections.emptyMap())) {            headers = new HashMap<String, String>();        }        return headers;    }    @Override    public String getBodyContentType() {        return entity.getContentType().getValue();    }    @Override    public byte[] getBody() throws AuthFailureError {        ByteArrayOutputStream bos = new ByteArrayOutputStream();        try{            entity.writeTo(bos);        } catch (IOException e) {            VolleyLog.e("IOException writing to ByteArrayOutputStream");        }        return bos.toByteArray();    }    private void buildMultipartEntity() {        if (mFileParts != null && mFileParts.size() > 0) {            for (File file : mFileParts) {                entity.addPart(mFilePartName, new FileBody(file));            }            long l = entity.getContentLength();            Log.i("YanZi-volley", mFileParts.size() + "个,长度:" + l);        }        try {            if (mParams != null && mParams.size() > 0) {                for (Map.Entry<String, String> entry : mParams.entrySet()) {                    entity.addPart(                            entry.getKey(),                            new StringBody(entry.getValue(), Charset                                    .forName("UTF-8")));                }            }        } catch (UnsupportedEncodingException e) {            VolleyLog.e("UnsupportedEncodingException");        }    }}

使用的话new一个request,然后add到queue里就可以了,我就不写示例了。下次介绍android上传/下载文件带进度条的实现。

参考:CodeIgniterhttp://codeigniter.org.cn/

---------------本文系原创,转载注明作者yanzi1225627


2015-12-30补充说明:android端的测试demo我已经传到了git,https://github.com/yanzi1225627/TestMultipartRequest,欢迎大家fork。友情提示那个key要写 f_file[],不要写f_file.


欢迎大家加入PHP CodeIgniter社区群460132647,备注yanzi





更多相关文章

  1. 如何使用jdb调试android的java程序
  2. Firemonkey扩展增强:Android(安卓)浏览器支持Input file标签上传
  3. 移植UDT到android
  4. Android(安卓)dp方式的屏幕适配工具使用(bat批处理方式)
  5. Android编译Native C 模块
  6. android中的下载问题
  7. Android的4种文件类型
  8. Android之SQLite数据库使用
  9. android学习笔记之AIDL

随机推荐

  1. Android(安卓)获取麦克风音量
  2. 【Androidd Release】AndroidStudio 发布
  3. Android之USB Camera摄像头节点后移
  4. 如何实现Android(安卓)布局背景模糊化处
  5. Android(安卓)图文数据JSON解析,金山词霸
  6. Android逆向工程初步(一) 15.4.24
  7. Android(安卓)user defined service hand
  8. Android(安卓)Retrofit 2.0 注解的理解
  9. Android(安卓)中文API (94) ―― MediaCont
  10. Delphi XE7 GPS控件android下的新变化