在thinkphp3.2基础上封装上传图片接口
2015-04-11 16:16
351 查看
参数配置:::
接口:::
读取私有路径图片:::
调用获取私有图片simple:::
如果涉及到跨域上传图片,这里会遇到一个问题那就是出现安全问题,安全沙箱冲突:这个是由flash的跨域传输数据的安全问题所引起的。
flash跨域策略文件crossdomain.xml配置详解
0x01 简介
0x02 crossdomain.xml的配置
0x03 总结
0x01 简介
flash在跨域时唯一的限制策略就是crossdomain.xml文件,该文件限制了flash是否可以跨域读写数据以及允许从什么地方跨域读写数据。
位于www.a.com域中的SWF文件要访问www.b.com的文件时,SWF首先会检查www.b.com服务器目录下是否有crossdomain.xml文件,如果没有,则访问不成功;若crossdomain.xml文件存在,且里边设置了允许www.a.com域访问,那么通信正常。所以要使Flash可以跨域传输数据,其关键就是crossdomain.xml。
本文将着重介绍crossdomain.xml文件的配置方法及不同配置对flash跨域的影响。
0x02 crossdomain.xml的配置
2.1 crossdomain.xml的放置位置
自flash 10以后,如有跨域访问需求,必须在目标域的根目录下放置crossdomain.xml文件,且该根目录下的配置文件称为“主策略文件”。若不存在主策略文件,则该域将禁止任何第三方域的flash跨域请求。
主策略文件对全站的跨域访问起控制作用。
也可以单独在某路径下放置仅对该路径及其子路径生效的crossdomain.xml配置文件,这需要在flash的AS脚本中使用如下语句来加载该配置文件:[具体的加载权限限制,将受后文中site-control策略的影响]
2.2 crossdomain.xml的配置方法及影响
crossdomain.xml需严格遵守XML语法,有且仅有一个根节点cross-domain-policy,且不包含任何属性。在此根节点下只能包含如下的子节点:site-control、allow-access-from、allow-access-from-identity、allow-http-request-headers-from。下面将分别介绍这四个子节点:
2.2.1site-control:通过检查该节点的属性值,确认是否可以允许加载其他策略文件。[如果该策略文件并非主策略文件,则此节点被自动忽略]
每个site-control标签有且仅有属性permitted-cross-domain-policies,该属性指定相对于非主策略文件的其他策略文件的加载策略。permitted-cross-domain-policies属性值有如下情况:
none: 不允许使用loadPolicyFile方法加载任何策略文件,包括此主策略文件。
master-only: 只允许使用主策略文件[默认值]。
by-content-type:只允许使用loadPolicyFile方法加载HTTP/HTTPS协议下Content-Type 为text/x-cross-domain-policy的文件作为跨域策略文件。
by-ftp-filename:只允许使用loadPolicyFile方法加载FTP协议下文件名为 crossdomain.xml的文件作为跨域策略文件。
all: 可使用loadPolicyFile方法加载目标域上的任何文件作为跨域策略文件,甚至是一 个JPG也可被加载为策略文件![使用此选项那就等着被xx吧!]
如需要对网站某子目录做单独的flash跨域限制策略,在主策略文件中必须进行相应的site-control设置。
下面的例子将site-control策略配置为可加载本服务器中其它的text/x-cross-domain-policy文件作为跨域策略文件。
2.2.2 allow-access-from:通过检查该节点的属性值,确认能够读取本域内容的flash文件来源域。
allow-access-from标签有三个属性:
·domain:该属性指定一个确切的 IP 地址、一个确切的域或一个通配符域(任何域)。只有domain中指定的域,才有权限通过flash读取本域中的内容。
可采用下列两种方式之一来表示通配符域:
1)单个星号(*),如:<allow-access-fromdomain="*" />,表示匹配所有域和所有 IP 地 址,此时任何域均可跨域访问本域上的内容。[这是极不安全的!]
2)后接后缀的星号,表示只匹配那些以指定后缀结尾的域,如*.qq.com可匹配 game.qq.com、qq.com。形如www.q*.com或www.qq.*的为无效配置。
Tips:当domain被指定为IP地址时,只接受使用该IP作为网址来访问的来源请求[此时ip地址也就相当于一个域名而已],如domain被设置为192.168.1.100时,使用http://192.168.1.100/flash.swf 来请求该域内容是允许的,但是使用指向192.168.1.100的域名www.a.com来访问时[http://www.a.com/flash.swf]将会被拒绝,因为flash不懂得dns解析:)
·to-ports:该属性值表明允许访问读取本域内容的socket连接端口范围。可使用to-ports="1100,1120-1125"这样的形式来限定端口范围,也可使用通配符(*)表示允许所有端口。
·secure:该属性值指明信息是否经加密传输。当crossdomain.xml文件使用https加载时,secure默认设为true。此时将不允许flash传输非https加密内容。若手工设置为false则允许flash传输非https加密内容。
下面的例子配置为允许所有qq.com下的所有二级域名[包括qq.com本身]通过https访问本域中的内容。
2.2.3allow-access-from-identity:该节点配置跨域访问策略为允许有特定证书的来源跨域访问本域上的资源。每个allow-access-from-identity节点最多只能包含一个signatory子节点。形如:
2.2.4allow-http-request-headers-from:此节点授权第三方域flash向本域发送用户定义的http头。
allow-access-from节点授权第三域提取本域中的数据,而 allow-http-request-headers-from 节点授权第三方域将数据以http头的形式发送到本域中。[简而言之,allow-access-from是控制读取权限,allow-http-request-headers-from是控制以http头形式的写入权限]
allow-http-request-headers-from包含三个属性:
·domain:作用及参数格式与allow-access-from节点中的domain类似。
·headers:以逗号隔开的列表,表明允许发送的http头。可用通配符(*)表示全部 http头。
·secure:作用及用法与allow-access-from节点中的secure相同。
在下面的示例中,任何域都可以向当前域发送 SOAPAction 标头:
0x03 总结
不正确的crossdomain.xml策略将导致严重的安全问题,如信息泄露、CSRF等。从上文中可以看出,在进行安全评估时,我们应重点关注以下几点:
1)allow-access-from标签的domain属性检测:domain属性应根据最小化原则按需设置,仅允许可信任的来源跨域请求本域内容。禁止将该属性值设置为“*”。
2)allow-http-request-headers-from标签的domain属性检测:domain属性应根据最小化原则按需设置,仅允许可信任的来源向本域跨域发送内容。禁止将该属性值设置为“*”。
3) site-control标签的permitted-cross-domain-policies属性检测:根据业务的实际需求及可行性,对该属性做相应设置。禁止将该属性值设置为“all”。
/* 图片上传相关配置 */ 'PICTURE_UPLOAD' => array( 'saveName' => array('uniqid',''), // 'rootPath' => UPLOAD, //保存路径 'maxSize' => 10*1024*1024, //上传的文件大小限制 (0-不做限制) 'exts' => array('jpg', 'gif', 'png', 'jpeg','rar','zip','txt','swf'), //允许上传的文件后缀 'autoSub' => true, //自动子目录保存文件 'thumb' => false, //是否需要对图片文件进行缩略图处理,默认为false 'subName' => array('date','Ymd'), 'replace' => true, //存在同名是否覆盖 'callback' => false, //检测文件是否存在回调函数,如果存在返回文件信息数组 ), //图片上传相关配置(文件上传类配置)
接口:::
<?php namespace Upload\Controller; use Think\Controller; class UploadFileController extends Controller{ private $error_msg = ''; //上传错误信息 private $return = array();//返回数据 public function index(){ $_GET= array_change_key_case ($_GET);//参数转换小写 $site = I('get.site/s','default');//站点 $savename = I('get.savename/s','auto');//上传文件保存规则 $subname = I('get.subname/s','');//上传子目录保存规则 $size = I('get.size/d','','int');//上传文件大小限制 int 单位:M $output = I('get.output/s','');//数据返回格式 默认数组,可选json $is_secret = I('get.is_secret/d',0,'int');//是否上传至保密路径,1是, 0否, 默认0 //定义上传主路径 if(!in_array($site, C('site'))){//是否在允许上传的站点 $this->return = array('status'=>'error','content'=>'上传图片所属站点不允许或者设置错误'); if($output=='json'){ $this->ajaxReturn($this->return,'json');exit; }else{ var_dump($this->return);exit; } }else{ //判断上传普通目录还是上传私密目录 if($is_secret == 1 ){//上传私密目录 C('PICTURE_UPLOAD.rootPath',PRI_UPLOAD.$site."/"); }else{//上传普通目录 C('PICTURE_UPLOAD.rootPath',UPLOAD.$site."/"); } if(!is_dir(C('PICTURE_UPLOAD.rootPath'))){//自动创建子目录 mkdir(C('PICTURE_UPLOAD.rootPath'),0777,true); } } //自定义保存子目录(默认日期) if(!empty($subname)){ C('PICTURE_UPLOAD.subName',$subname); } //定义上传文件的保存名称(默认savename='auto'unique生成/savename='字符串'自定义名字/savename='origin',使用原来名称) if($savename=='auto'){//使用unique生成 C('PICTURE_UPLOAD.saveName','uniqid'); }elseif($savename=='origin'){ C('PICTURE_UPLOAD.saveName',''); }else{ C('PICTURE_UPLOAD.saveName',$savename); } //定义上传文件的大小(默认maxSize=10M,自定义大小不超过10M) if(!empty($size) && $size<=10){ C('PICTURE_UPLOAD.maxSize',$size*1024*1024); } /* 调用文件上传组件上传文件 */ $info = $this->upload_file(C('PICTURE_UPLOAD')); /* 记录图片信息 */ if($info){ $this->return=array('status'=>'success','content'=>$info); } else { $this->return= array('status'=>'error','content'=>$this->error_msg); } if($output=='json'){ /* 返回JSON数据 */ $this->ajaxReturn($this->return,'json'); }else{ var_dump($this->return);exit; } } /** * 文件上传 * @param array $files 要上传的文件列表(通常是$_FILES数组) * @param array $setting 文件上传配置 * @param string $driver 上传驱动名称 * @param array $config 上传驱动配置 * @return array 文件上传成功后的信息 */ public function upload_file($config){ /* 上传文件 */ import('ORG.Net.UploadFile'); $upload = new \Think\Upload($config);// 实例化上传类 // 上传文件 $info = $upload->upload(); if(!$info) {// 上传错误提示错误信息 $this->error_msg = $upload->getError(); return false; }else{// 上传成功 获取上传文件信息 return $info; } } } ?>
读取私有路径图片:::
<?php /** * 获取私密文件 * */ namespace Download\Controller; use Think\Controller; class DownloadFileController extends Controller{ private $status = 'error'; //返回状态 private $type = 'text/html';//返回数据的MIME类型 private $content = ''; //返回数据内容 //入口 public function index(){ $path = I('path/s','');//需要读取的文件路径 Private文件夹下 $timestamp = I('timestamp',0,'int');//用户调用接口时间戳 (用于验证) if(empty($path)){ //验证请求路径 $this->content = "缺少请求文件路径"; $this->flush(); } //身份验证 if($this->checkSignature($path,$timestamp)){//身份验证成功 $full_path = PRI_UPLOAD.$path; //请求文件全路径 if(is_file($full_path)){//请求文件是否存在 $mime = $this->get_mime($full_path);//获取文件MIME类型 $content = file_get_contents($full_path);//文件内容 $this->status = 'success'; $this->type = $mime; $this->content= $content; $this->flush(); }else{ $this->content = "没有找到请求的文件"; $this->flush(); } }else{//验证失败 $this->content = "验证失败"; $this->flush(); } } /**验证请求是否合法 * $path 请求的private 下文件路径 * $timestamp 用户携带过来的时间戳 */ private function checkSignature($path,$timestamp){ $key = C('key'); //密钥 $signature =I('signature/s','');//使用接口者用同样方式处理这三个参数构造验证字符串 // 加密/校验流程如下: // 1. 将key、path、timestamp三个参数进行字典序排序 // 2. 将三个参数字符串拼接成一个字符串进行sha1加密 // 3. 开发者获得加密后的字符串可与signature对比,标识该请求来源允许 $tmpArr = array($key,$path,$timestamp); //传过来的参数组合成数组 sort($tmpArr, SORT_STRING); //进行字典序排序 $tmpStr = implode( $tmpArr ); //将三个参数字符串拼接成一个字符串进行sha1加密 $tmpStr = sha1( $tmpStr ); if($tmpStr===$signature){//验证成功 return true; }else{ //验证失败 return false; } } /** * 输出结果(序列化) * status 状态 success成功/error失败 * type 返回数据的MIME类型 * content 返回数据内容 */ private function flush(){ $return =array( 'status'=>$this->status, 'type'=>$this->type, 'content'=>$this->content ); echo serialize($return); exit; } /** * 获取文件Mime-Type * $full_path 请求文件的全路径 * */ private function get_mime($full_path){ $ext = strtolower(pathinfo($full_path, PATHINFO_EXTENSION));//获取文件后缀名 switch ($ext){ case 'jpg': return 'image/jpeg'; break; case 'jpeg': return 'image/jpeg'; break; case 'png': return 'image/png'; break; case 'gif': return 'image/gif'; break; case 'zip': return 'application/octet-stream'; break; case 'rar': return 'application/octet-stream'; break; case 'swf': return 'application/x-shockwave-flash'; break; case 'txt': return 'text/plain'; break; default: return 'text/html'; } } } ?>
调用获取私有图片simple:::
<?php //示例代码SDK //调用 http://www.upload.com/Download/DownloadFile/index.html接口 $key='5dbb068f303f8d60076c9a8a4b6bad8c'; //身份验证密钥 $timestamp=time(); //时间戳 $path=$_GET['path']; //请求文件的路径,default/test/test.jpg 相对于Private文件夹下 $url="http://www.upload.com/Download/DownloadFile/index.html";//接口url if(empty($path)){ return false; } //组合验证条件 // 加密/校验流程如下: // 1. 将key、path、timestamp三个参数进行字典序排序 // 2. 将三个参数字符串拼接成一个字符串进行sha1加密 // 3. 开发者获得加密后的字符串可与signature对比,标识该请求来源允许 $tmpArr = array($key,$path,$timestamp); sort($tmpArr, SORT_STRING); $tmpStr = implode( $tmpArr ); $signature = sha1( $tmpStr ); //curl ->post 数据 $data = array( 'key'=>$key, 'timestamp'=>$timestamp, 'path'=>$path, 'signature'=>$signature ); //获取数据并且反序列化 $res = cpost($url,$data); $data = unserialize($res); if($data['status']=='error'){ //读取文件失败 echo $data['content']; }else{ //成功 if($data['type']=='application/octet-stream'){ //如果是rar zip等则以附件形式下载 $ext = strtolower(pathinfo($path, PATHINFO_EXTENSION)); $file_name = 'dowload.'.$ext; //下载保存时候的文件名 Header("Content-Disposition: attachment; filename=".$file_name); } Header("Content-Type:{$data['type']}"); echo $data['content']; } exit; /* * curl post 模拟提交数据函数 */ function cpost($url,$data,$timeout=10) { $curl = curl_init(); // 启动一个CURL会话 curl_setopt($curl, CURLOPT_URL, $url); // 要访问的地址 curl_setopt($curl, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']); // 模拟用户使用的浏览器 curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); // 使用自动跳转 curl_setopt($curl, CURLOPT_AUTOREFERER, 1); // 自动设置Referer curl_setopt($curl, CURLOPT_POST, 1); // 发送一个常规的Post请求 curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($data)); // Post提交的数据包 curl_setopt($curl, CURLOPT_TIMEOUT, $timeout); // 设置超时限制防止死循环 curl_setopt($curl, CURLOPT_HEADER, 0); // 显示返回的Header区域内容 curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); // 获取的信息以文件流的形式返回 $tmpInfo = curl_exec($curl); // 执行操作 if (curl_errno($curl)) { echo 'Errno:'.curl_error($curl);//捕抓异常 } curl_close($curl); // 关闭CURL会话 return $tmpInfo; // 返回数据 } ?>
如果涉及到跨域上传图片,这里会遇到一个问题那就是出现安全问题,安全沙箱冲突:这个是由flash的跨域传输数据的安全问题所引起的。
flash跨域策略文件crossdomain.xml配置详解
0x01 简介
0x02 crossdomain.xml的配置
0x03 总结
0x01 简介
flash在跨域时唯一的限制策略就是crossdomain.xml文件,该文件限制了flash是否可以跨域读写数据以及允许从什么地方跨域读写数据。
位于www.a.com域中的SWF文件要访问www.b.com的文件时,SWF首先会检查www.b.com服务器目录下是否有crossdomain.xml文件,如果没有,则访问不成功;若crossdomain.xml文件存在,且里边设置了允许www.a.com域访问,那么通信正常。所以要使Flash可以跨域传输数据,其关键就是crossdomain.xml。
本文将着重介绍crossdomain.xml文件的配置方法及不同配置对flash跨域的影响。
0x02 crossdomain.xml的配置
2.1 crossdomain.xml的放置位置
自flash 10以后,如有跨域访问需求,必须在目标域的根目录下放置crossdomain.xml文件,且该根目录下的配置文件称为“主策略文件”。若不存在主策略文件,则该域将禁止任何第三方域的flash跨域请求。
主策略文件对全站的跨域访问起控制作用。
也可以单独在某路径下放置仅对该路径及其子路径生效的crossdomain.xml配置文件,这需要在flash的AS脚本中使用如下语句来加载该配置文件:[具体的加载权限限制,将受后文中site-control策略的影响]
Security.loadPolicyFile("http://www.xxx.com/subdir/crossdomain.xml") |
2.2 crossdomain.xml的配置方法及影响
crossdomain.xml需严格遵守XML语法,有且仅有一个根节点cross-domain-policy,且不包含任何属性。在此根节点下只能包含如下的子节点:site-control、allow-access-from、allow-access-from-identity、allow-http-request-headers-from。下面将分别介绍这四个子节点:
2.2.1site-control:通过检查该节点的属性值,确认是否可以允许加载其他策略文件。[如果该策略文件并非主策略文件,则此节点被自动忽略]
每个site-control标签有且仅有属性permitted-cross-domain-policies,该属性指定相对于非主策略文件的其他策略文件的加载策略。permitted-cross-domain-policies属性值有如下情况:
none: 不允许使用loadPolicyFile方法加载任何策略文件,包括此主策略文件。
master-only: 只允许使用主策略文件[默认值]。
by-content-type:只允许使用loadPolicyFile方法加载HTTP/HTTPS协议下Content-Type 为text/x-cross-domain-policy的文件作为跨域策略文件。
by-ftp-filename:只允许使用loadPolicyFile方法加载FTP协议下文件名为 crossdomain.xml的文件作为跨域策略文件。
all: 可使用loadPolicyFile方法加载目标域上的任何文件作为跨域策略文件,甚至是一 个JPG也可被加载为策略文件![使用此选项那就等着被xx吧!]
如需要对网站某子目录做单独的flash跨域限制策略,在主策略文件中必须进行相应的site-control设置。
下面的例子将site-control策略配置为可加载本服务器中其它的text/x-cross-domain-policy文件作为跨域策略文件。
<cross-domain-policy> <site-control permitted-cross-domain-policies="by-content-type" /> </cross-domain-policy> |
2.2.2 allow-access-from:通过检查该节点的属性值,确认能够读取本域内容的flash文件来源域。
allow-access-from标签有三个属性:
·domain:该属性指定一个确切的 IP 地址、一个确切的域或一个通配符域(任何域)。只有domain中指定的域,才有权限通过flash读取本域中的内容。
可采用下列两种方式之一来表示通配符域:
1)单个星号(*),如:<allow-access-fromdomain="*" />,表示匹配所有域和所有 IP 地 址,此时任何域均可跨域访问本域上的内容。[这是极不安全的!]
2)后接后缀的星号,表示只匹配那些以指定后缀结尾的域,如*.qq.com可匹配 game.qq.com、qq.com。形如www.q*.com或www.qq.*的为无效配置。
Tips:当domain被指定为IP地址时,只接受使用该IP作为网址来访问的来源请求[此时ip地址也就相当于一个域名而已],如domain被设置为192.168.1.100时,使用http://192.168.1.100/flash.swf 来请求该域内容是允许的,但是使用指向192.168.1.100的域名www.a.com来访问时[http://www.a.com/flash.swf]将会被拒绝,因为flash不懂得dns解析:)
·to-ports:该属性值表明允许访问读取本域内容的socket连接端口范围。可使用to-ports="1100,1120-1125"这样的形式来限定端口范围,也可使用通配符(*)表示允许所有端口。
·secure:该属性值指明信息是否经加密传输。当crossdomain.xml文件使用https加载时,secure默认设为true。此时将不允许flash传输非https加密内容。若手工设置为false则允许flash传输非https加密内容。
下面的例子配置为允许所有qq.com下的所有二级域名[包括qq.com本身]通过https访问本域中的内容。
<cross-domain-policy> <allow-access-from domain="*.qq.com" secure="true" /> </cross-domain-policy> |
2.2.3allow-access-from-identity:该节点配置跨域访问策略为允许有特定证书的来源跨域访问本域上的资源。每个allow-access-from-identity节点最多只能包含一个signatory子节点。形如:
<allow-access-from-identity> <signatory> <certificate fingerprint="01:23:45:67:89:ab:cd:ef:01:23:45:67:89:ab:cd:ef:01:23:45:67" fingerprint-algorithm="sha-1"/> </signatory> </allow-access-from-identity> |
2.2.4allow-http-request-headers-from:此节点授权第三方域flash向本域发送用户定义的http头。
allow-access-from节点授权第三域提取本域中的数据,而 allow-http-request-headers-from 节点授权第三方域将数据以http头的形式发送到本域中。[简而言之,allow-access-from是控制读取权限,allow-http-request-headers-from是控制以http头形式的写入权限]
allow-http-request-headers-from包含三个属性:
·domain:作用及参数格式与allow-access-from节点中的domain类似。
·headers:以逗号隔开的列表,表明允许发送的http头。可用通配符(*)表示全部 http头。
·secure:作用及用法与allow-access-from节点中的secure相同。
在下面的示例中,任何域都可以向当前域发送 SOAPAction 标头:
<cross-domain-policy> <allow-http-request-headers-from domain="*" headers="SOAPAction" /> </cross-domain-policy> |
0x03 总结
不正确的crossdomain.xml策略将导致严重的安全问题,如信息泄露、CSRF等。从上文中可以看出,在进行安全评估时,我们应重点关注以下几点:
1)allow-access-from标签的domain属性检测:domain属性应根据最小化原则按需设置,仅允许可信任的来源跨域请求本域内容。禁止将该属性值设置为“*”。
2)allow-http-request-headers-from标签的domain属性检测:domain属性应根据最小化原则按需设置,仅允许可信任的来源向本域跨域发送内容。禁止将该属性值设置为“*”。
3) site-control标签的permitted-cross-domain-policies属性检测:根据业务的实际需求及可行性,对该属性做相应设置。禁止将该属性值设置为“all”。
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE cross-domain-policy SYSTEM "http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd" > <cross-domain-policy> <site-control permitted-cross-domain-policies="master-only"/> <allow-access-from domain="*"/> <allow-http-request-headers-from domain="*" headers="*"/> </cross-domain-policy>
相关文章推荐
- thinkphp3.2 后台获取app前端多个图片上传及参数 的接口写法(upload()函数)
- thinkphp3.2实现上传图片的控制器方法
- ThinkPHP微信实例——JSSDK图像接口多张图片上传下载并将图片流写入本地
- 用封装好的okHttp3上传图片到接口
- thinkphp3.2实现上传图片的控制器方法
- Thinkphp3.2+ 微信小程序图片上传
- thinkphp 3.2 异步上传图片,出现在<pre>标签,不执行success
- thinkphp3.2处理多张图片上传(已成功)
- thinkphp实现上传图文消息中的图片的接口
- ThinkPHP微信开发实例——JSSDK图像接口上传下载并将图片流写入本地
- Thinkphp框架3.2支持七牛图片上传的配置
- Thinkphp3.2图片上传服务器端接收
- ThinkPHP3.2上传缩放.png图片出现白底问题
- thinkphp3.2 上传图片找不到根目录问题
- ThinkPHP3.2上传图片
- ThinkPHP3.2结合jq实现图片的异步上传
- 基于iframe的图片无刷新上传和用ajax生成缩略图和即时删除,封装于thinkphp
- 基于thinkphp3.2使用uploadify单图片上传
- ThinkPHP 图片上传接口类
- thinkPHP3.2上传透明背景,png图片出现白底解决办法