web应用特别是cms管理系统,经常出现远程图片本地化的需求实现,很多已插件方式提供,很多只是配置使用,但是做到自己定制化实现特定的需求可能就力不从心了。我们来实现一下远程图片本地化的功能。
远程图片本地化,主要实现思路是,远程图片可能是一个url地址,也可能是一个base64编码的图片,我们只要将这个图片通过方法下载到当前服务器路径下,然后改变存储路径,一般可能是一篇文章中包含的图片,那么我们只要遍历其中的图片,用上述方法做本地化,然后把这个文章的html串中的图片远程路径替换成本地化的路径,再重新保存就可以了。还有一个需求是将一篇文章的第一张图片保存成文章封面,这个就将那个html串中的第一个图片路径提取出来,保存为封面路径即可。实现思路大致这些,可能其中会遇到各种各样的问题。
我们先看下代码:
远程图片url获取到本地的函数
private static function grabImage($a_url,$a_savePath){
if($a_url != ""){ //如果图片地址为空
$a_url=str_replace(['&'],['&'],$a_url); //url中特定字符替换
//获取图片信息大小
$imgSize = getImageSize($a_url);
if(!in_array($imgSize['mime'],array('image/jpg', 'image/gif', 'image/png', 'image/jpeg'),true)){ return ''; }
//获取后缀名
$_mime = explode('/', $imgSize['mime']);
$ext = '.'.end($_mime);
$new_file = $a_savePath ."/";
if (!file_exists($new_file)) {
//检查是否有该文件夹,如果没有就创建,并给予最高权限
mkdir($new_file, 0700);
}
$filename_r = time().rand(10,9000).$ext; //给图片命名
$filename = $new_file.$filename_r;//.'/'
ob_start(); //打开缓冲区
readfile($a_url);
$imgInfo = ob_get_contents(); //获得缓冲区的内容
ob_end_clean(); //清除并关闭缓冲区
//$fp = fopen($filename,'a'); fwrite($fp,$imgInfo); fclose($fp);
$fp = fopen($filename, 'a');
$imgLen = strlen($imgInfo); //计算图片源码大小
$_inx = 1024; //每次写入1k
$_time = ceil($imgLen/$_inx);
for($i=0; $i<$_time; $i++){
fwrite($fp,substr($imgInfo, $i*$_inx, $_inx));
}
fclose($fp); unset($imgInfo,$a_url);
return $filename;
}else{
return '';
}
}
Base64图片保存到本地
private static function base64_image($a_img_htmls, $a_path){
//正则匹配出图片的格式
if (preg_match('/^(data:\s*image\/(\w+);base64,)/', $a_img_htmls, $result)) {
$type = $result[2]; //获取图片格式
$new_file = $a_path . "/";
if (!file_exists($new_file)) {
//检查是否有该文件夹,如果没有就创建,并给予最高权限
mkdir($new_file, 0700);
}
$new_file = $new_file . time() . ".{$type}";
if (file_put_contents($new_file, base64_decode(str_replace($result[1], '', $a_img_htmls)))) {
return $new_file; //'/' .
} else {
return '';
}
} else {
return '';
}
}
取得文章中第一张图片
private function get_html_first_imgurl($a_contents){
$result="";
$pattern= '~<img.*?src=["\']+(.*?)["\']+~';
preg_match_all($pattern,$a_contents,$matchContent);
//return var_dump('matchContent=',$matchContent);
if(!empty($matchContent)){ $result=$matchContent[1][0];
$result=str_replace('/'.C('C_DATA_DIR'),'',$result);
}
return $result;
}
转换内容html串中的图片本地化并解析替换
public static function transImgLocal($html_contents,$a_baseSavePath,$a_localPathFlag){
$result=$html_contents;
$html_contents = stripslashes($html_contents);//返回已剥离反斜杠的字符串
//echo $html_contents; exit;
$img_array = array();
//处理远程图片url
preg_match_all("/(src|SRC)=[\"|'| ]{0,}((http|https):\/\/(.*).(gif|jpg|jpeg|bmp|png))/isU", $html_contents, $img_array );
// 匹配出来的不重复图片
if(!empty($img_array)&& count($img_array)>=2){
$img_array = array_unique ( $img_array [2] );
//var_dump('img_array=',$img_array); exit;
//echo $img_array[0];
foreach($img_array as $item){
if(strpos($item,$a_localPathFlag) !== false){ continue; }
$filename=self::grabImage($item,$a_baseSavePath);
//echo $filename;
$html_contents=str_replace($item,'/'.$filename,$html_contents);
}
}
//处理base64图片格式
preg_match_all("/(src|SRC)=[\"|'| ]{0,}(data:image\/(\w+);base64,(.*))(\")/isU", $html_contents, $img_array2);
if(!empty($img_array2)){
$img_array2 = array_unique ( $img_array2 [2] );
//var_dump('img_array2=',$img_array2); exit;
foreach($img_array2 as $item){
if(strpos($item,$a_localPathFlag) !== false){ continue; }
$filename=self::base64_image($item,$a_baseSavePath);
//echo $filename; exit;
if(!empty($filename)){ $html_contents=str_replace($item,'/'.$filename,$html_contents); }
}
}
$result=$html_contents;
return $result;
}
这样,我们调用transImgLocal,传入参数,即可将内容hmtl串中图片本地化,如果指定特定目录,或者其他需求,在此基础上改造即可。
遇到的问题
- 转换图片本地化后,发现转换成莫名其妙的图片,并不是需要指定的远程的图片,是类似这样,如下(原始图片不是这样)
解决:原来是代码中解析的url路径中包含&被当做特定路径字符串进行解析了,解析成这样的图片资源,后来替换成&,重新下载即可解决。
- 不能转换远程地址
现象:有时发现有些图片地址不能解析成本地化,仍然保持网络链接的地址。
解决:原来有些图片地址是伪链接,如:https://p6-tt.byteimg.com/origin/pgc-image/72964e39d4dd4054b456fcf9d6b8baf3?from=pc。不包含.jpg,或.png等图片后缀,这样需要修改识别图片链接的正则表达式,
如改为:$reg="/(src|SRC)=[\"|'| ]{0,}((http|https):\/\/(.*))(\")/isU"; 调用正则表达式即可解决。
本文持续改进完善...