关于Yii中findByPk被sql注入的一个误区

树大招风,当然网站被恶意攻击注入是常有的事,这也是对一个程序员代码质量以及经验水平的考验。

今天公司一个项目不幸被sql注入了,根据日志查询到了注入点,是某个详情页的代码,大致就是根据某条数据id查询该条数据。

理所当然的,我们会使用Yii中Ar类提供的findByPk()方法来查询该数据,而正是这里却被注入了。

- 阅读剩余部分 -

标签: Yii

博客上阿里云了

好久没收拾博客了,折腾了好几天终于迁移到了阿里云上了,同时将SAE环境改成自己搭建的LNMP,完成了从wordpress数据的迁移转换。
接下来继续完善,自己写插件支持七牛云存储。
这篇文章就当是测试的吧。。测试评论、测试分享、测试新的编辑器等等等

标签: none

使用composer安装yii2出现"could not parse version.."的问题

如果安装时出现这个错误

[UnexpectedValueException]
Could not parse version constraint <=2.*: Invalid version string "2.*"

很有可能是composer Asset插件版本的问题

官网说明使用“composer global require "fxp/composer-asset-plugin:~1.0.0"”

可是会出错,所以如果出错了,使用这个命令"composer global require "fxp / composer-asset-plugin: 1.0.1"重新安装一遍后,再安装yii2应该会正常了

标签: none

编程中英语单词state和status的区别

state:比较常用,各种状态都可以用它,但是它更着重于一种心理状态或者物理状态。
Status:用在人的身上一般是其身份和地位,作“状态,情形”讲时,多指政治和商业。

state倾向于condition,是一种延续性的状态。status常用于描述一个过程中的某阶段(phase),类似于C语言中枚举型变量某一个固定的值,这个值属于一个已知的集合。
比如淘宝买家问卖家“我的网购现在是什么状况?”
这个问题的背景是讲话双方都清楚,交易状态有“买家选购”“买家已付款”“卖家已发货”“买家已签收”或者有“买家已
投诉”等等状态。这些状态描述一件事情发展过程中的不同阶段。而且,这些阶段的先后顺序也是双方默许的。
所以在这里可以问“What's the status of my purchase?”,此处用state不太贴切,如果硬用上去从语感上可能听着别扭。

说物态变化用state再恰当不过。如果说一个物质的四种状态,可以说“solid state”,但如果你说“solid status”,第
一,这两个词的组合不像是描述物态,更像是在说“确定的状况(solid产生歧义‘确定的/确凿的’)”;第二,这个说法即
使不被误解,也需要事先约定一组物态变化顺序,比如把这个物质从固态开始加热然后电离,可能先后经历固态、液态、气态、等离子态这四个阶段。类似先定义枚举,然后引用的方式。

 

扩展:

ajax中readyState,statusText,onreadystatechange,window.status怎么一会state一会是status都晕乎了

state所指的状态,一般都是有限的、可列举的,status则是不可确定的。
比如
readyState -- 就那么四五种值
statusText -- 描述性的文字,可以任意
onreadystatechange -- 那么四五种值之间发生变化
window.status -- 描述性的文字,可以任意

来个形象的比方,你体重多少公斤,属于status,但说你体重属于偏瘦、正常还是偏胖,那就是state.

标签: 英语

Yii事件机制分析

在Yii中使用事件需要三个步骤:1、定义事件;2、定义事件回调函数;3、将回调函数添加到事件中4、触发事件。

Yii事件机制的实现是在其底层基类CComponent类里,这是所有组件的基类。

1、如何定义事件

很简单,事件以on开头的命名方式定义,如以下定义了一个onEcho的事件:

public function onEcho($event)
{
     $this->raiseEvent('onEcho', $event);
}

这样就定义了一个事件,其中$event参数是一个CEvent或其子类的实例(但其实用到的并不多,下面再说)。

2、定义事件回调函数

回调函数可以是一个全局函数或者一个类中的函数(说白了就是一个函数而已),其需要一个参数$event,如下:

public function huidiao($event)
{
     //TODO
}

$event参数其实是接收定义事件时传入的那个参数,也就是上面第一步定义的事件函数的参数,它最终会传到这个回调函数里来。然后回调函数根据业务逻辑使用这个参数,但一般情况下我们不怎么使用到这个参数,所以意义也不是很大,不过如果你需要参数的情况下可以通过这样方式定义,因为传入的参数并不一定限制是CEvent的实例或其子类,也可以是其他类型(待会下面继续解释)。

3、将回调函数添加到事件中

为啥还要将回调函数添加到事件中呢?因为当事件触发后总得要有程序逻辑(也就是一段php代码)去处理业务是吧。所以光光的定义一个事件而不往里面添加回调函数是没有意义的,而且即使定义了回调函数却添加到事件里也是没有意义的。就如同你只买了个电脑主机有啥意义呢?也或者你只买了一台显示器也同样没有意义,要将两者结合才是他们真正的用处。

那么如何将回调函数添加到事件中?

一种方法:$this->onEcho=array($this, 'huidiao');
另一种方法:$this->attachEventHandler('onEcho', array($this, 'huidiao'));
第一种方法利用了Yii组件中setter原理,看代码:

public function __set($name,$value)
{
	$setter='set'.$name;
	if(method_exists($this,$setter))
		return $this->$setter($value);
	elseif(strncasecmp($name,'on',2)===0 && method_exists($this,$name))
	{
		// duplicating getEventHandlers() here for performance
		$name=strtolower($name);
		if(!isset($this->_e[$name]))
			$this->_e[$name]=new CList;
		return $this->_e[$name]->add($value);
	}
	elseif(is_array($this->_m))
	{
		foreach($this->_m as $object)
		{
			if($object->getEnabled() && (property_exists($object,$name) || $object->canSetProperty($name)))
				return $object->$name=$value;
		}
	}
	if(method_exists($this,'get'.$name))
		throw new CException(Yii::t('yii','Property "{class}.{property}" is read only.',
			array('{class}'=>get_class($this), '{property}'=>$name)));
	else
		throw new CException(Yii::t('yii','Property "{class}.{property}" is not defined.',
			array('{class}'=>get_class($this), '{property}'=>$name)));
}

这是一个魔术方法__set,6-13行是给事件添加回调函数的步骤。如果类里面存在以on开头的函数,那么将事件放到私有变量$_e里,$_e是个数组,其键名是事件名称,其值是一个CList实例(也可以理解为一个数组,因为CList实现了一个整数索引的集合类,可以用数组的形式往里面添加元素 ,可详见CList),再使用CList中add方法往数组形式的值里面添加回调函数,也等同于$_e['onecho'][]=array($this,'huidiao')。那么按我上面添加一个事件来说,添加一个回调函数huidiao后$_e的内容应该是array('onecho'=>array(array($this,'huidiao'))

第二种方法使用了类中的attachEventHandler函数,代码如下:

public function attachEventHandler($name,$handler)
{
	$this->getEventHandlers($name)->add($handler);
}

public function getEventHandlers($name)
{
	if($this->hasEvent($name))
	{
		$name=strtolower($name);
		if(!isset($this->_e[$name]))
			$this->_e[$name]=new CList;
		return $this->_e[$name];
	}
	else
		throw new CException(Yii::t('yii','Event "{class}.{event}" is not defined.',
			array('{class}'=>get_class($this), '{event}'=>$name)));
}

public function hasEvent($name)
{
	return !strncasecmp($name,'on',2) && method_exists($this,$name);
}

attachEventHandler函数首先用getEventHandler函数获取需要添加回调函数的事件,用hasEvent判断是否存在这个事件函数名,如果存在的话(也就是定义事件函数的话)就和第一种方法原理一样,获得$_e['onecho']的值(CList实例),使用其方法add往数组形式的值里添加回调函数。

至此,回调函数都已经添加到事件中去了。

4、触发事件

这个比较简单,在你需要执行这个事件的地方调用第1步定义的事件,如$this->onEcho($event),函数执行里面的一句话$this->raiseEvent('onEcho'.$event),raiseEvent这个方法代码如下:

public function raiseEvent($name,$event)
{
	$name=strtolower($name);
	if(isset($this->_e[$name]))
	{
		foreach($this->_e[$name] as $handler)
		{
			if(is_string($handler))
				call_user_func($handler,$event);
			elseif(is_callable($handler,true))
			{
				if(is_array($handler))
				{
					// an array: 0 - object, 1 - method name
					list($object,$method)=$handler;
					if(is_string($object))	// static method call
						call_user_func($handler,$event);
					elseif(method_exists($object,$method))
						$object->$method($event);
					else
						throw new CException(Yii::t('yii','Event "{class}.{event}" is attached with an invalid handler "{handler}".',
							array('{class}'=>get_class($this), '{event}'=>$name, '{handler}'=>$handler[1])));
				}
				else // PHP 5.3: anonymous function
					call_user_func($handler,$event);
			}
			else
				throw new CException(Yii::t('yii','Event "{class}.{event}" is attached with an invalid handler "{handler}".',
					array('{class}'=>get_class($this), '{event}'=>$name, '{handler}'=>gettype($handler))));
			// stop further handling if param.handled is set true
			if(($event instanceof CEvent) && $event->handled)
				return;
		}
	}
	elseif(YII_DEBUG && !$this->hasEvent($name))
		throw new CException(Yii::t('yii','Event "{class}.{event}" is not defined.',
			array('{class}'=>get_class($this), '{event}'=>$name)));
}

这个方法会获得$_e['onecho']值,那么这个值是数组形式对吧,而且数组的每个元素都是一个回调函数,那么用foreach对其进行遍历(代码第6行),循环部分的代码最终都是调用了call_user_func,把回调函数和$event传入进去,那么回调函数就被执行了,并且该回调函数得到的参数是触发事件时$this->onEcho($event)的这个$event参数。所以我在第2步中说道,这个$event参数并不一定要CEvent的实例或其子类,因为要看你回调函数需要什么参数。这么一看,通过foreach后,注册到这个事件onEcho中的所有回调函数都会被执行一遍。

那么就这样完成了整个事件了,还比较容易理解吧?有任何疑问可以留言,一起探讨

 

标签: PHP, Yii, 事件

PHP命名大小写敏感规则

一直觉得PHP中各种大小写规则理不清,就连工作多年的老手们也不一定能对PHP大小写敏感问题足够了解。在PHP中,大小写敏感问题的处理比较乱,大家一定要注意。即使某些地方大小写不敏感,但在编程过程中能始终坚持“大小写敏感”是最好不过的。下面整理了一些大小写问题注意点:

大小写敏感

1. 变量名区分大小写
所有变量均区分大小写,包括普通变量以 及$_GET,$_POST,$_REQUEST,$_COOKIE,$_SESSION,$GLOBALS,$_SERVER,$_FILES,$_ENV 等;

<?php
$abc = 'abc';
echo $abc;    //输出'abc'
echo $aBc;    //无输出
echo $ABC;    //无输出
?>

2、常量名区分大小写

使用define定义的常量是区分大小写的。

<?php
define('BLOGGER','Veitor');
echo BLOGGER;    //输出'Veitor'
echo BLOgger;    //报NOTICE提示,并输出'BLOgger'
echo blogger;    //报NOTICE提示,并输出'blogger'
?>

3、数组索引(键名)区分大小写

<?php
$arr = array('one'=>'first');
echo $arr['one'];    //输出'first'
echo $arr['One'];    //无输出并报错
echo $Arr['one'];    //上面讲过,变量名区分大小写,所以无输出并报错
?>

 

大小写不敏感

1. 函数名、方法名、类名不区分大小写

虽然这些不区分大小写,但坚持“大小写敏感”原则,建议还是使用与定义时相同大小写的名字

<?php
class Test
{
    static public function Ceshi()
    {
        echo '123';
    }

    public funcion Dxx()
    {
        echo '321';
    }
}

$obj = new Test;
$obj->Dxx();    //成功实例化Test类,并调用Dxx方法输出'321'
$obj->dxx();    //成功实例化Test类,并调用Dxx方法输出'321'
$obj = new test;
$obj->Dxx();    //成功实例化Test类,并调用Dxx方法输出'321'
$obj->dxx();    //成功实例化Test类,并调用Dxx方法输出'321'

Test::Ceshi();    //输出'123'
test::Ceshi();    //输出'123'
Test::ceshi();    //输出'123'
test::ceshi();    //输出'123'
?>

2、魔术常量不区分大小写

一些魔术常量包括:__LINE__、__FILE__、__DIR__、__FUNCTION__、__CLASS__、__METHOD__、 __NAMESPACE__等都不区分大小写。

<?php
echo __LINE__;    //输出2
echo __line__;    //输出3
?>

 3、 NULL、TRUE、FALSE不区分大小写

这个知道的人应该比较多就不举例了。

4、强制类型转换不区分大小写

如这些

(int),(integer) – 转换成整型
(bool),(boolean) – 转换成布尔型
(float),(double),(real) – 转换成浮点型
(string) – 转换成字符串
(array) – 转换成数组
(object) – 转换成对象

一般我们都小写,这个问题不大。

 

总的来说,容易搞不明白的就是变量、常量、类名、方法名和函数名,把这些记住对自己会有帮助的。

标签: PHP

Web前后端分离开发思路

1. 问题的提出

开发一个Web应用的时候我们一般都会简单地分为前端工程师和后端工程师(注:在一些比较复杂的系统中,前端可以细分为外观和逻辑,后端可以分为CGI和Server)。前端工程师负责浏览器端用户交互界面和逻辑等,后端负责数据的处理和存储等。前后端的关系可以浅显地概括为:后端提供数据,前端负责显示数据。

在这种前后端的分工下,会经常有一些疑惑:既然前端数据是由后端提供,那么后端数据接口还没有完成,前端是否就无法进行编码?怎么样才能做到前后端独立开发?

考虑这么一个场景:Alex和Bob是一对好基友,他们有个可以颠覆世界的idea,准备把它实现出来,但是他们不需要程序员,因为他们就是程序员。说干就干,两个就干上了。Alex写前端,Bob写后端。

Alex和Bob都经过良好的训练,按部就班地把产品的主要功能设计,交互原型,视觉设计做好了,然后他们根据产品功能和交互制定了一堆叼炸天的前后端交互的API,这套API就类似于一套前后端开发的“协议”,Alex和Bob开发的时候都需要遵守。例如其中一个发表评论的功能:

// API: Create New Comment v2
// Ajax, JSON, RESTful
url: /comments
type: POST
request: {content: "comment content.", userId: 123456}
response: 
    - status: 200
        data: {result: "SUCCESS", msg: "The comment has been created."}
    - status: 404
        data: {result: "failed", msg: "User is not found."}

Alex的前端需要向/comments这个url以POST的方式发送类似于{content: "comment content.", userId: 123456}这样的JSON请求数据;Bob的服务端识别后以后,操作成功则返回200状态和上面的JSON的数据,不同的操作状态有不同的响应数据(为了简单起见只列出了两种,200和404)。

API制定完以后,Alex和Bob就开始编码了。Alex把评论都外观和交互写完了,但是写到发表评论功能就纳闷了:Alex现在需要发Ajax过去,但是只能把Ajax代码写好,因为是本地服务器,却无法获取到数据:

// jQuery Ajax
$.ajax({ // 这个ajax直接报错,因为这个是Alex的前端服务器,请求无法获取数据;
    url: "/comments",
    type: "POST",
    data: {content: content, userId: userId},
    success: funtion(data) {
        // 这里不会被执行
    }
})

相比起来Bob就没有这个烦恼,因为后端是基于测试驱动开发,且后端可以轻易地模拟前端发送请求,可以对前端没有依赖地进行开发和测试。

Alex把这种情况和Bob说了,Bob就说,要不我们把代码弄到你本地前后端连接一下,这不就可以测试了吗。Alex觉得Bob简直是天才。

他们把前后端代码代码都部署到Alex的本地服务器以后,经过一系列的测试,调试,终于把这个API连接成功了。但是他们发现这个方法简直不科学:难道每写一个API都要把前后端链接测试一遍吗?而且,Alex的如果需要测试某个API,而Bob的这个API还没写好,Alex这个功能模块的进度就“阻塞”了。

后面还有168个API需要写,不能这么做。Alex和Bob就开始思考这个问题的解决方案。

2. 解决思路

在这个场景下,前后端是有比较强的数据依赖的关系,后端依赖前端的请求,前端依赖后端的响应。而后端可以轻松模拟前端请求(基本上能写后端的语言都可以直接发送HTTP请求),前端没有一个比较明显的方案来可以做到模拟响应,所以这里的需要解决的点就是:如何给前端模拟的响应数据

先来一句非常形而上的话:如果两个对象具有强耦合的关系,我们一般只要引入第三个对象就可以打破这种强耦合的关系。

QQ截图20150201223003

在我们上述开发的过程中,前后端的耦合性太强了,我们需要借助额外的东西来打破它们的耦合性。所以,在前后端接口定下来以后,我们根据接口构建另外一个Server,这个Server会一一响应前端的请求,并且根据接口返回数据。当然这些数据都是假数据。我们把这个Server叫做

Mock Server

,而Bob真正在开发的Server叫做

Real Server

QQ截图20150201223038

Mock Server是根据API实现的,但是是没有数据逻辑的,只是非常简单地返回数据。例如上面Alex和Bob的发表评论的接口在Mock Server上是这样的:

// Mock Server
// Create New Comment API
route.post("/comments", function(req, res) {
    res.send(200, {result: "Success"});
})

Alex在开发的时候向Mock Server发出请求,而不是向Bob的服务器发出请求:

// Sending Request to Mock Server
// jQuery Ajax
$.ajax({ 
    url: config.HOST + "/comments",
    type: "POST",
    data: {content: content, userId: userId},
    success: funtion(data) {
        // OK
    }
})

注意上面的config.HOST,我们把服务器配置放在一个全局共用的模块当中:

// Front-end Configuration Module
var config = modules.exports;
config.HOST = "http://192.169.10.20" // Mock Server IP

那么上面我们其实是向IP为http://192.169.10.20的Mock Server发出请求http://192.169.10.20/comments发出POST的请求。

当Alex和Bob都代码写好了以后,需要连接调试了,Alex只要简单地改一下配置文件即可把所有的请求都转向Bob所开发的Real Server:

// Front-end Configuration Module
var config = module.exports;
// config.HOST = "http://192.169.10.20" // Mock Server IP
config.HOST = "http://changing-world-app.com" // Real Server Domain

然后Alex和Bob就可以愉快地分离独立开发,而最后只需要联合调试就可以了。

总结一下基本上前后端分离开发包括下面几个步骤:

  1. 根据功能制定前后端接口(API)。
  2. 根据接口构建Mock Server工程及其部署。
  3. 前后端独立开发,前端向Mock Server发送请求,获取模拟的数据进行开发和测试。
  4. 前后端都完成后,前后端连接调试(前端修改配置向Real Server而不是Mock Server发送请求)。

当然要注意,如果接口修改了,Mock Server要同步修改。

3. 实现方案

Mock Server具体应该如何构建?应该存放在哪里?应该怎么维护?

前后端是不同的两个工程,它们各自占用一个仓库。Mock Server应该和它们分离出来,独立进行开发和维护,也就是说会有三个仓库,Mock Server是一个单独的工程。

Mock Server可以部署在本地,也可以部署到远程服务器,两者之间各有优劣。


3.1 远程Mock Server

做法:把Mock Server工程部署到一个远程的always on的远程服务器上,前端开发的时候向该服务器发请求。

优点

  1. 没有给原有的前后端工程增加负担。
  2. 每个前端开发人员向同一个Mock Server服务器发送请求,保持所有人获取响应请求的一致性。

缺点

  1. 有跨域问题(思考:locahost如何向192.169.10.20发请求?)。
  2. 需要额外的远程服务器支持。

(在写这篇博客的时候,逛Hacker News,刚好看到有人做了一个开发辅助工具(http://reqr.es/),可以用于开发时响应前端请求,其实也就是这里所说的远程Mock Server。真是不能再巧更多。)

3.2 本地Mock Server

做法:前端把Mock Server克隆到本地,开发的时候,开启前端工程服务器和Mock Server,所有的请求都发向本地服务器,获取到Mock数据。

优点

  1. 节约资源,不需要依赖远程服务器。环保节能。
  2. 没有跨域问题。

缺点

  1. 增加前端工程开发流程复杂程度。
  2. 每个前端开发人员自己部署服务器在本地,可能会有仓库没有及时更新导致API不一致的情况。


Mock Server工程一般可以由后端开发人员来维护。因为在开发的过程中,后端因为各种原因可能需要修改API,后端人员是最熟悉请求的响应数据和格式的人,可以同步维护Mock Server和Real Server,更好保证数据的一致。Mock Server维护起来并不复杂,对于比较大多工程来说,这样的前期准备和过程的维护是非常值得的。

最后

所以要点就是:根据API构建可以模拟服务器响应的Mock Server,用于前端请求模拟数据进行测试

再重复总结一下前后端分离开发包括下面几个步骤:

  1. 根据功能制定前后端接口。
  2. 根据接口构建Mock Server工程及其部署。
  3. 前后端独立开发,前端向Mock Server发送请求,获取模拟的数据进行开发和测试。
  4. 前后端都完成后,前后端连接调试。

当开发只有我一个人的时候,我更喜欢后端独立开发,开发前端的时候开个Real Server来做响应。又爽又快。其实如果团队的人是full-stack的话,完全可以按照功能模块来划分任务,而不是分为前端工程师和后端工程师。

但一般来说还是会选择前后端职能划分,对于这种情况下的多人开发的工程来说,前后端分离开发的方式确实需要考虑和构建的,可以更好帮助我们构建一个高效,规范化,流程化的开发流程。

还是那句话,没有银弹,所有的东西都需要根据实际情况来构建独特的流程。

原文链接:http://segmentfault.com/blog/livoras/1190000002413526

标签: 前端, 转载, 后端

关于php中curl慢的问题

今天在自己的服务器上运行程序时需要远程请求一个接口,发现此时程序响应特别慢,平均时间都要十几秒。

最后断定问题出现在了curl地方,使用服务器命令(我的是centos)curl一个域名也很慢,但curl一个IP却能得到及时响应。(因此其实本文标题说是PHP中的问题是不严谨的)

这说明问题可能就出现在了DNS设置上,后来改了一下DNS后发现正常了,在此写文记一下。

配置DNS:

输入命令vi /etc/resolv.conf

设置一下DNS,我这里使用的阿里DNS

nameserver 223.5.5.5
nameserver 223.6.6.6

最后再重启下php和nginx

/etc/init.d/php-fpm restart
/etc/init.d/php-fpm restart

 

标签: PHP, DNS

关于swfupload插件无法上传文件的问题

因为项目上所使用的flash上传图片插件名叫swfupload,但里面的js写法好像另一个上传插件sapload,所以我也搞不清到底是哪个,但最终了解到这两个插件都是有共同的一个问题,就是对需要进行身份才能上传文件的方式会失效。

我们都知道服务器端要对识别当前用户登录状态需要客户端cookie的支持,请求URL时会带着cookie,服务器端才能找到对应的session。而使用这个flash插件上传文件请求接口时,如果被请求的接口需要登陆才能传文件的话,那很可能就会失败。

一种原因是说:因为该插件会忽略部分浏览器下的cookie,IE和chrome下是正常的,像火狐等浏览器上传文件时,服务器端会发现请求带过来的cookie是空的(建议尝试打印一下cookie)

另一种可能的说法:该插件是通过soket套接字进行通信,相当于建立了一次新对话,即使请求带着cookie过去了,但打印出来发现存储的sessionid并不是当前登录时的,因此用户身份又验证失败。(建议多打印cookie并查看)

归根到底,无法上传文件的原因就是无法验证身份。

解决方法有两种:

1、在初始化flash插件时,在js代码中设置一下传递参数(可参考其插件手册)。swfupload插件中增加一个post_params选项,格式如post_params:{"PHPSESSID": <?php echo session_id(); ?>} ,sapload插件中增加一个args选项,格式如args:"PHPSESSID=<?php echo session_id(); ?>" 。这两个意思都是在post文件时,带上参数PHPSESSID(注:后端是PHP脚本)。

咱用不了cookie中的值,那就用POST过来值,然后在PHP脚本中处理一下:

if(isset($_POST['PHPSESSID'])){
    session_id($_POST['PHPSESSID']);
    session_start()
}

这样就可以使用当前登录正确的session id进行一系列操作了。

2、取消上传URL接口的登陆验证。(有点蛋疼,非必要情况下还是使用第一种方法吧)

 

 

附(这是给我自己看的,swfupload好像格式有所不同,如本文开头提到的,不知道是不是同一个插件):

flash 接口文档:

upUrl :后台文件的地址,必须要绝对路径

etmsg:每次文件上传成功后需不需要回调函数(0|1),默认回调函数为sapLoadMsg(x)函数。

ltmsg:最后一个文件上传成功后需不需要回调函数(0|1),默认回调函数为sapLoadMsg(x)函数。

types:上传文件的类型(如*.apk;*.jpg)用分号分隔各个类型

args:需要传递的参数(如apkName=123;apkName1=1234)用分号分隔各个类型

fileName:此参数可选,允许你自定义文件域名称,默认是Filedata。

maxNum:当最大上传数为1的时候自动切换到单文件上传模式,用户将不能在使用多选选取文件。

标签: PHP, flash, session