问题背景: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>
4,接下来是最关键的一个类,在controllers文件夹下新建Upload.php,这是个控制器,上传最核心的。
<?php
require_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封装的,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: [email protected]
* @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");
}
}
}
参考:CodeIgniter http://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