PHP 常见的业务逻辑

php在操作断点续传时文件如何分割合并

php实现断点续传,就需要把大文件分割成多个小文件,然后单个上传。传完后在合并。

│ merge.php –合并文件脚本 \
│ merge.zip –合并后文件 \
│ socket.zip –需要分割的文件 \
│ split.php –分割文件脚本 \
│ \
└─split –分割后小文件目录

split.php

<?php
$fp = fopen("socket.zip", "rb");
$filesize = 10;
$i = 0;
$no = 1;
while(!feof($fp))
{
  $file = fread($fp, $filesize);
  $fp2 = fopen("./split/socket.port".sprintf("%04d",$no).".".$i."-".($i+$filesize).".tmp", "wb");
  fwrite($fp2, $file, $filesize);
  fclose($fp2);
  $i+=$filesize+1;
$no++;
}
fclose($fp);

merge.php

<?php
$filelist = glob('./split/*socket*.tmp');
$filesize = 10;
//print_r($filelist);
$mergeFileName = 'merg.zip';
unlink($mergeFileName);
  $fp2 = fopen($mergeFileName,"w+");
foreach($filelist as $k => $v)
{
  $fp = fopen($v, "rb");
   $content = fread($fp, $filesize);

   fwrite($fp2, $content, $filesize);
   unset($content);
   fclose($fp);
   echo $k,"\n";
}
  fclose($fp2);

一些加密方法

php 自带的加密函数 

不可逆的加密函数为:md5()、crypt()

  • md5() 用来计算 MD5 哈稀。语法为:string md5(string str);
  • crypt() 将字符串用 UNIX 的标准加密 DES 模块加密。这是单向的加密函数,无法解密。欲比对字符串,将已加密的字符串的头二个字符放在 salt 的参数中,再比对加密后的字符串。语法为:string crypt(string str, string [salt]);

可逆转的加密为:base64_encode()、urlencode() 相对应的解密函数:

  • base64_encode() 将字符串以 MIME BASE64 编码。此编码方式可以让中文字或者图片也能在网络上顺利传输。语法为string base64_encode(string data); 它的解密函数为:string base64_decode(string encoded_data); 将复回原样。
  • urlencode() 将字符串以 URL 编码。例如空格就会变成加号。语法为:string urlencode(string str); \
  • 它的解密函数为:string urldecode(string str); 将复回原样

案例

加密解密算法

<?php  
function encryptDecrypt($key, $string, $decrypt){   
    if($decrypt){   
        $decrypted = rtrim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, md5($key), base64_decode($string), MCRYPT_MODE_CBC, md5(md5($key))), "12");   
        return $decrypted;   
    }else{   
        $encrypted = base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, md5($key), $string, MCRYPT_MODE_CBC, md5(md5($key))));   
        return $encrypted;   
    }   
}   
//加密:"z0JAx4qMwcF+db5TNbp/xwdUM84snRsXvvpXuaCa4Bk="  
echo encryptDecrypt('password', 'Helloweba欢迎您',0);   
//解密:"Helloweba欢迎您"  
echo encryptDecrypt('password', 'z0JAx4qMwcF+db5TNbp/xwdUM84snRsXvvpXuaCa4Bk=',1);  
?>  
<?php  
//加密函数  
function lock_url($txt,$key='www.zhuoyuexiazai.com'){  
    $chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-=+";  
    $nh = rand(0,64);  
    $ch = $chars[$nh];  
    $mdKey = md5($key.$ch);  
    $mdKey = substr($mdKey,$nh%8, $nh%8+7);  
    $txt = base64_encode($txt);  
    $tmp = '';  
    $i=0;$j=0;$k = 0;  
    for ($i=0; $i<strlen($txt); $i++) {  
        $k = $k == strlen($mdKey) ? 0 : $k;  
        $j = ($nh+strpos($chars,$txt[$i])+ord($mdKey[$k++]))%64;  
        $tmp .= $chars[$j];  
    }  
    return urlencode($ch.$tmp);  
}  
//解密函数  
function unlock_url($txt,$key='www.zhuoyuexiazai.com'){  
    $txt = urldecode($txt);  
    $chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-=+";  
    $ch = $txt[0];  
    $nh = strpos($chars,$ch);  
    $mdKey = md5($key.$ch);  
    $mdKey = substr($mdKey,$nh%8, $nh%8+7);  
    $txt = substr($txt,1);  
    $tmp = '';  
    $i=0;$j=0; $k = 0;  
    for ($i=0; $i<strlen($txt); $i++) {  
        $k = $k == strlen($mdKey) ? 0 : $k;  
        $j = strpos($chars,$txt[$i])-$nh - ord($mdKey[$k++]);  
        while ($j<0) $j+=64;  
        $tmp .= $chars[$j];  
    }  
    return base64_decode($tmp);  
}  
?>  
<?php  

//改进后的算法  
//加密函数  
function lock_url($txt,$key='zhuoyuexiazai'){  
    $txt = $txt.$key;  
    $chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-=+";  
    $nh = rand(0,64);  
    $ch = $chars[$nh];  
    $mdKey = md5($key.$ch);  
    $mdKey = substr($mdKey,$nh%8, $nh%8+7);  
    $txt = base64_encode($txt);  
    $tmp = '';  
    $i=0;$j=0;$k = 0;  
    for ($i=0; $i<strlen($txt); $i++) {  
        $k = $k == strlen($mdKey) ? 0 : $k;  
        $j = ($nh+strpos($chars,$txt[$i])+ord($mdKey[$k++]))%64;  
        $tmp .= $chars[$j];  
    }  
    return urlencode(base64_encode($ch.$tmp));  
}  
//解密函数  
function unlock_url($txt,$key='zhuoyuexiazai'){  
    $txt = base64_decode(urldecode($txt));  
    $chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-=+";  
    $ch = $txt[0];  
    $nh = strpos($chars,$ch);  
    $mdKey = md5($key.$ch);  
    $mdKey = substr($mdKey,$nh%8, $nh%8+7);  
    $txt = substr($txt,1);  
    $tmp = '';  
    $i=0;$j=0; $k = 0;  
    for ($i=0; $i<strlen($txt); $i++) {  
        $k = $k == strlen($mdKey) ? 0 : $k;  
        $j = strpos($chars,$txt[$i])-$nh - ord($mdKey[$k++]);  
        while ($j<0) $j+=64;  
        $tmp .= $chars[$j];  
    }  
    return trim(base64_decode($tmp),$key);  
}  
?>  
<?php  
function passport_encrypt($txt, $key = 'www.zhuoyuexiazai.com') {   
    srand((double)microtime() * 1000000);   
    $encrypt_key = md5(rand(0, 32000));   
    $ctr = 0;   
    $tmp = '';   
    for($i = 0;$i < strlen($txt); $i++) {   
    $ctr = $ctr == strlen($encrypt_key) ? 0 : $ctr;   
    $tmp .= $encrypt_key[$ctr].($txt[$i] ^ $encrypt_key[$ctr++]);   
    }   
    return urlencode(base64_encode(passport_key($tmp, $key)));   
}   
function passport_decrypt($txt, $key = 'www.zhuoyuexiazai.com') {   
    $txt = passport_key(base64_decode(urldecode($txt)), $key);   
    $tmp = '';   
    for($i = 0;$i < strlen($txt); $i++) {   
    $md5 = $txt[$i];   
    $tmp .= $txt[++$i] ^ $md5;   
    }   
    return $tmp;   
}   
function passport_key($txt, $encrypt_key) {   
    $encrypt_key = md5($encrypt_key);   
    $ctr = 0;   
    $tmp = '';   
    for($i = 0; $i < strlen($txt); $i++) {   
    $ctr = $ctr == strlen($encrypt_key) ? 0 : $ctr;   
    $tmp .= $txt[$i] ^ $encrypt_key[$ctr++];   
    }   
    return $tmp;   
}   
$txt = "1";   
$key = "testkey";   
$encrypt = passport_encrypt($txt,$key);   
$decrypt = passport_decrypt($encrypt,$key);   
echo $encrypt."<br>";   
echo $decrypt."<br>";   
?>   

discuz中使用的加密解密算法

有时我们需要使用PHP将特定的信息进行加密,也就是通过加密算法生成一个加密字符串,这个加密后的字符串可以通过解密算法进行解密,便于程序对解密后的信息进行处理。最常见的应用在用户登录以及一些API数据交换的场景。最常见的应用在用户登录以及一些API数据交换的场景。加密解密原理一般都是通过一定的加密解密算法,将密钥加入到算法中,最终得到加密解密结果。

<?php  
//非常给力的authcode加密函数,Discuz!经典代码(带详解)  
//函数authcode($string, $operation, $key, $expiry)中的$string:字符串,明文或密文;$operation:DECODE表示解密,其它表示加密;$key:密匙;$expiry:密文有效期。  
function authcode($string, $operation = 'DECODE', $key = '', $expiry = 0) {     
    // 动态密匙长度,相同的明文会生成不同密文就是依靠动态密匙     
    $ckey_length = 4;     
    // 密匙     
    $key = md5($key ? $key : $GLOBALS['discuz_auth_key']);     
    // 密匙a会参与加解密     
    $keya = md5(substr($key, 0, 16));     
    // 密匙b会用来做数据完整性验证     
    $keyb = md5(substr($key, 16, 16));     
    // 密匙c用于变化生成的密文     
    $keyc = $ckey_length ? ($operation == 'DECODE' ? substr($string, 0, $ckey_length): substr(md5(microtime()), -$ckey_length)) : '';     
    // 参与运算的密匙     
    $cryptkey = $keya.md5($keya.$keyc);     
    $key_length = strlen($cryptkey);     
    // 明文,前10位用来保存时间戳,解密时验证数据有效性,10到26位用来保存$keyb(密匙b),   
    //解密时会通过这个密匙验证数据完整性     
    // 如果是解码的话,会从第$ckey_length位开始,因为密文前$ckey_length位保存 动态密匙,以保证解密正确     
    $string = $operation == 'DECODE' ? base64_decode(substr($string, $ckey_length)) :  sprintf('%010d', $expiry ? $expiry + time() : 0).substr(md5($string.$keyb), 0, 16).$string;     
    $string_length = strlen($string);     
    $result = '';     
    $box = range(0, 255);     
    $rndkey = array();     
    // 产生密匙簿     
    for($i = 0; $i <= 255; $i++) {     
        $rndkey[$i] = ord($cryptkey[$i % $key_length]);     
    }     
    // 用固定的算法,打乱密匙簿,增加随机性,好像很复杂,实际上对并不会增加密文的强度     
    for($j = $i = 0; $i < 256; $i++) {     
        $j = ($j + $box[$i] + $rndkey[$i]) % 256;     
        $tmp = $box[$i];     
        $box[$i] = $box[$j];     
        $box[$j] = $tmp;     
    }     
    // 核心加解密部分     
    for($a = $j = $i = 0; $i < $string_length; $i++) {     
        $a = ($a + 1) % 256;     
        $j = ($j + $box[$a]) % 256;     
        $tmp = $box[$a];     
        $box[$a] = $box[$j];     
        $box[$j] = $tmp;     
        // 从密匙簿得出密匙进行异或,再转成字符     
        $result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));     
    }     
    if($operation == 'DECODE') {    
        // 验证数据有效性,请看未加密明文的格式     
        if((substr($result, 0, 10) == 0 || substr($result, 0, 10) - time() > 0) &&  substr($result, 10, 16) == substr(md5(substr($result, 26).$keyb), 0, 16)) {     
            return substr($result, 26);     
        } else {     
            return '';     
        }     
    } else {     
        // 把动态密匙保存在密文里,这也是为什么同样的明文,生产不同密文后能解密的原因     
        // 因为加密后的密文可能是一些特殊字符,复制过程可能会丢失,所以用base64编码     
        return $keyc.str_replace('=', '', base64_encode($result));     
    }     
}   
$str = 'abcdef';   
$key = 'www.helloweba.com';   
echo authcode($str,'ENCODE',$key,0); //加密   
$str = '56f4yER1DI2WTzWMqsfPpS9hwyoJnFP2MpC8SOhRrxO7BOk';   
echo authcode($str,'DECODE',$key,0); //解密   
?> 

限制IP访问及提交次数

原理

提交次数是肯定要往数据库里写次数这个数据的,比如用户登陆,当用户出错时就忘数据库写入出错次数1,并且出错时间,再出错写2,当满比如5次时提示不允许再登陆,请明天再试,然后用DateDiff计算出错时和now()的时间,如果大于24就再开放让他试。

封IP的话特别是给IP断就比较简单了,先说给IP段开放的情况:先取出客户访问的IP,为了解释方便,设有IP192.168.6.2,现要开放IP段为192.168.. 的断,

url=split(ip,".") '这里的ip为客户端IP
fsip="192.168.*.*"  '允许的段,可以从数据库取出,也可以这么定义
fip=split(fsip,".")
if fip(0)=url(0) and fip(1)=url(1) then
response.write "您的IP被封"
else response.write "可以通过"
end if

其实禁某IP就跟上面的方法一样。或者干脆从数据库里取出IP和客户端的直接比较就好了。

一般会把数据库里的IP段设为双精度型的,那么就需要这些:

if Request.ServerVariables("HTTP_X_FORWARDED_FOR")="" then
IP=Request.ServerVariables("REMOTE_ADDR")
else
IP=Request.ServerVariables("HTTP_X_FORWARDED_FOR")
end if
sip=IP
cip=split(ip,".")
ip=256*256*256*cip(0)+256*256*cip(1)+256*cip(2)+cip(3)-1

当然这些情况都是在数据库操作方面,没涉及cookies等。

其实封IP不是很理想,会影响无辜的人,有关的或许是因为动态IP逃过了。

封装实例

<?php
class IP{ //获取客户IP地址
  function getIpAdr(&$ip){
    $ip1=getenv("HTTP_X_FORWARDED_FOR");
    $ip2=getenv("HTTP_CLIENT_IP");
    $ip3=getenv("REMOTE_ADDR");
    if($ip1&&$ip1!='unknow')
      $ip=$ip1; else if($ip2&&$ip2!='unknow')
      $ip=$ip2; else if($ip3&&$ip3!='unknow')
      $ip=$ip3; else
      $ip='127.0.0.1';
  }
}
function get_netip($myip){ //只留客户IP地址的前三位
  $temp=explode(".",$myip);
  $netip.=$temp[0];
  $netip.=".";
  $netip.=$temp[1];
  $netip.=".";
  $netip.=$temp[2];
  return $netip;
}
$filename="test.ini";  //定义操作文件
$ip_lib=file($filename); //读取文件数据到数组中
$allow=0;
$IP=new IP;
$thisip="";
$IP->getIpAdr(&$thisip);
$thenetip=get_netip($thisip);
for ($i=0;$i<count($ip_lib);$i++){
  if(ereg($thenetip,$ip_lib[$i])){
    $allow=1;
    break;
  }
}
if ($allow==1)
{
  echo "验证通过";
} else {
  echo "<script>location.href='Error.php';</script>";
}

限制输入次数

  • 页面需要先session_start();
  • 点击登陆的时候做判断,如果确定用户输入的密码是错误的
if(用户的密码是错误的){
  if(!empty($_SESSION['login_error'])){
    if($_SESSION['login_error'] == 3){
      exit("这里已经是第三次了");
    } else{
      $_SESSION['login_error'] = $_SESSION['login_error']++;
    }
  } else{
    $_SESSION['login_error'] = 1;
  }
}

正则表达式

"^[0-9]*[1-9][0-9]*$"   //正整数 
"^((-\d+)|(0+))$"   //非正整数(负整数 + 0) 
"^-[0-9]*[1-9][0-9]*$"   //负整数 
"^-?\d+$"     //整数 
"^\d+(\.\d+)?$"   //非负浮点数(正浮点数 + 0)

"^(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*))$"   //正浮点数 

"^((-\d+(\.\d+)?)|(0+(\.0+)?))$"//非正浮点数(负浮点数 + 0) "^(-(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*)))$"   //负浮点数 
"^(-?\d+)(\.\d+)?$"   //浮点数 
"^[A-Za-z]+$"   //由26个英文字母组成的字符串 
"^[A-Z]+$"   //由26个英文字母的大写组成的字符串 
"^[a-z]+$"   //由26个英文字母的小写组成的字符串 
"^[A-Za-z0-9]+$"   //由数字和26个英文字母组成的字符串 
"^\w+$"   //由数字、26个英文字母或者下划线组成的字符串 
"^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$"    //email地址 
"^[a-zA-z]+://(\w+(-\w+)*)(\.(\w+(-\w+)*))*(\?\S*)?$"//url 
/^(d{2}|d{4})-((0([1-9]{1}))|(1[1|2]))-(([0-2]([1-9]{1}))|(3[0|1]))$/ // 年-月-日 
/^((0([1-9]{1}))|(1[1|2]))/(([0-2]([1-9]{1}))|(3[0|1]))/(d{2}|d{4})$/ // 月/日/年 
"^([w-.]+)@(([[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.)|(([w-]+.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(]?)$" //Emil 
/^((\+?[0-9]{2,4}\-[0-9]{3,4}\-)|([0-9]{3,4}\-))?([0-9]{7,8})(\-[0-9]+)?$/ //电话号码 

"^(d{1,2}|1dd|2[0-4]d|25[0-5]).(d{1,2}|1dd|2[0-4]d|25[0-5]).(d{1,2}|1dd|2[0-4]d|25[0-5]).(d{1,2}|1dd|2[0-4]d|25[0-5])$" //IP地址

匹配中文字符的正则表达式: [\u4e00-\u9fa5] 
匹配双字节字符(包括汉字在内):[^\x00-\xff] 
匹配空行的正则表达式:\n[\s| ]*\r 
匹配HTML标记的正则表达式:/<(.*)>.*<\/\1>|<(.*) \/>/ 
匹配首尾空格的正则表达式:(^\s*)|(\s*$) 
匹配Email地址的正则表达式:\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)* 
匹配网址URL的正则表达式:^[a-zA-z]+://(\\w+(-\\w+)*)(\\.(\\w+(-\\w+)*))*(\\?\\S*)?$ 
匹配帐号是否合法(字母开头,允许5-16字节,允许字母数字下划线):^[a-zA-Z][a-zA-Z0-9_]{4,15}$ 
匹配国内电话号码:(\d{3}-|\d{4}-)?(\d{8}|\d{7})? 
匹配腾讯QQ号:^[1-9]*[1-9][0-9]*$

利用正则表达式限制网页表单里的文本框输入内容: 
用正则表达式限制只能输入中文:onkeyup="value=value.replace(/[^u4E00-u9FA5]/g,'')" onbeforepaste="clipboardData.setData('text',clipboardData.getData('text').replace(/[^u4E00-u9FA5]/g,''))" 
用正则表达式限制只能输入全角字符: onkeyup="value=value.replace(/[^uFF00-uFFFF]/g,'')" onbeforepaste="clipboardData.setData('text',clipboardData.getData('text').replace(/[^uFF00-uFFFF]/g,''))" 

用正则表达式限制只能输入数字:onkeyup="value=value.replace(/[^d]/g,'') "onbeforepaste="clipboardData.setData('text',clipboardData.getData('text').replace(/[^d]/g,''))"

用正则表达式限制只能输入数字和英文:onkeyup="value=value.replace(/[W]/g,'') "onbeforepaste="clipboardData.setData('text',clipboardData.getData('text').replace(/[^d]/g,''))"

=========常用正则式

匹配中文字符的正则表达式: [\u4e00-\u9fa5] 
匹配双字节字符(包括汉字在内):[^\x00-\xff] 
匹配空行的正则表达式:\n[\s| ]*\r 
匹配HTML标记的正则表达式:/<(.*)>.*<\/\1>|<(.*) \/>/ 
匹配首尾空格的正则表达式:(^\s*)|(\s*$) 
匹配IP地址的正则表达式:/(\d+)\.(\d+)\.(\d+)\.(\d+)/g // 
匹配Email地址的正则表达式:\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)* 
匹配网址URL的正则表达式:http://(/[\w-]+\.)+[\w-]+(/[\w- ./?%&=]*)? 
sql语句:^(select|drop|delete|create|update|insert).*$

非负整数:^\d+$ 
正整数:^[0-9]*[1-9][0-9]*$ 
非正整数:^((-\d+)|(0+))$ 
负整数:^-[0-9]*[1-9][0-9]*$ 
整数:^-?\d+$ 
非负浮点数:^\d+(\.\d+)?$ 
正浮点数:^((0-9)+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*))$ 
非正浮点数:^((-\d+\.\d+)?)|(0+(\.0+)?))$ 
负浮点数:^(-((正浮点数正则式)))$ 
英文字符串:^[A-Za-z]+$ 
英文大写串:^[A-Z]+$ 
英文小写串:^[a-z]+$ 
英文字符数字串:^[A-Za-z0-9]+$ 
英数字加下划线串:^\w+$ 
E-mail地址:^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$ 
URL:^[a-zA-Z]+://(\w+(-\w+)*)(\.(\w+(-\w+)*))*(\?\s*)?$ 或:^http:\/\/[A-Za-z0-9]+\.[A-Za-z0-9]+[\/=\?%\-&_~`@[\]\':+!]*([^<>\"\"])*$ 
邮政编码:^[1-9]\d{5}$ 中文:^[\u0391-\uFFE5]+$ 
电话号码:^((\(\d{2,3}\))|(\d{3}\-))?(\(0\d{2,3}\)|0\d{2,3}-)?[1-9]\d{6,7}(\-\d{1,4})?$ 
手机号码:^((\(\d{2,3}\))|(\d{3}\-))?13\d{9}$ 
双字节字符(包括汉字在内):^\x00-\xff
匹配首尾空格:(^\s*)|(\s*$)(像vbscript那样的trim函数) 
匹配HTML标记:<(.*)>.*<\/\1>|<(.*) \/> 
匹配空行:\n[\s| ]*\r 
提取信息中的网络链接:(h|H)(r|R)(e|E)(f|F) *= *('|")?(\w|\\|\/|\.)+('|"| *|>)? 
提取信息中的邮件地址:\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)* 
提取信息中的图片链接:(s|S)(r|R)(c|C) *= *('|")?(\w|\\|\/|\.)+('|"| *|>)? 
提取信息中的IP地址:(\d+)\.(\d+)\.(\d+)\.(\d+) 
提取信息中的中国手机号码:(86)*0*13\d{9} 
提取信息中的中国固定电话号码:(\(\d{3,4}\)|\d{3,4}-|\s)?\d{8} 
提取信息中的中国电话号码(包括移动和固定电话):(\(\d{3,4}\)|\d{3,4}-|\s)?\d{7,14} 
提取信息中的中国邮政编码:[1-9]{1}(\d+){5} 
提取信息中的浮点数(即小数):(-?\d*)\.?\d+ 
提取信息中的任何数字:(-?\d*)(\.\d+)? IP:(\d+)\.(\d+)\.(\d+)\.(\d+) 
电话区号:/^0\d{2,3}$/ 
腾讯QQ号:^[1-9]*[1-9][0-9]*$ 
帐号(字母开头,允许5-16字节,允许字母数字下划线):
^[a-zA-Z][a-zA-Z0-9_]{4,15}$ 
中文、英文、数字及下划线:^[\u4e00-\u9fa5_a-zA-Z0-9]+$ 

PHP领取优惠券

业务需求

根据后端设置优惠券模板,用户类型设置,优惠券活动的开始与结束时间,最后生成不同的优惠券活动链接。

代码环境

php

  • 前端需要做的设置与限制:
    • 判断优惠券是否存在或者停用
    • 判断活动开始时间与优惠券开始时间
  • 接着领取活动优惠券,需要判断以下情况:
    • 活动已结束
    • 活动为开始时
    • 活动为新用户领取,而领取的用户是老用户
    • 活动为老用户领取,而领取的用户是新用户
    • 优惠券是否领取完
    • 已领取过优惠券提示
    • 领取成功

代码实现

/**
 * Function:优惠券领取处理
 * Author:cyw0413
 * @param $params
 * @return array
 * @throws \Exception
 */
public function doCoupon($params)
{
    $activity_id = $params['activity_id'];
    if(!$params){
        throw new \Exception("参数错误!");
    }

    $preg_phone = '/^1[34578]\d{9}$/ims';
    $is_mobile = preg_match ($preg_phone, $params['mobile']);
    if ($is_mobile == 0) {
        throw new \Exception("手机号码不正确!");
    }
    //隐藏手机号码中间4位
    $str_mobile = substr_replace($params['mobile'],'****',3,4);
    $activity = $this->find($activity_id);
    if(empty($activity)){
        throw new \Exception("不存在此活动");
    }
    $activity_link = $activity->activityLink->where('coupon_status',0);  //只选择不停用的优惠券
    if(count($activity_link) <= 0){
        throw new \Exception("优惠券不存在或者已经停用");
    }else{
        //查找注册用户ID
        $showUser = $this->showUser($params['mobile']);
        //主要是过滤掉领取优惠券为0的,用laravel的同学注意看看
        $detail = $activity_link->each(function($item,$index) use ($showUser) {
            $diffCouponQuantity = $this->diffCouponQuantity($item['config_id'],$item['quantity'],$item['activity_id'],$showUser);
            $item->title  = $this->getCouponName($item['config_id'])['name'];
            $item->number = $item['quantity'];
            $item->msg    = $diffCouponQuantity ['msg'];
            $item->diff     = $diffCouponQuantity ['diff'];
            $item->code     = $diffCouponQuantity ['code'];
        })->toArray();

        if(count($detail) == 1){
            foreach($detail as $val){
                if($val['diff'] == 1 && $val['code'] == '400'){
                    throw new \Exception($detail[0]['msg']);
                }
            }
        }
        $collection_coupon = collect($detail);
        $collection_coupon = $collection_coupon->where('diff', '<=' ,'0');    //去除优惠券剩余数量为0,或者领取优惠券数量-剩余数量 > 0
    }
    //判断活动开始时间与优惠券开始时间
    $act_coupon = ActivityCouponBaseModel::where('activity_id',$activity['activity_id'])->first();
    $check_time = $this-> checkCouponTime($act_coupon['start_time'],$activity_link);
    if($check_time == 'error'){
        throw new \Exception("优惠券领取时间未开始,暂不可领取");
    }
    //领取活动有以下几种情况
    //1: 活动已结束
    if($activity['end_time'] < date("Y-m-d H:i:s") || $activity['status'] == 1){
        $result = [
            'code' => 1,
        ];
        return $result;
    }
    //6 活动为开始时
    if($activity['start_time'] > date("Y-m-d H:i:s") || $activity['status'] == 1){
        $result = [
            'code' => 6,
        ];
        return $result;
    }
    $checkUser = $this->haveUser($params['mobile']);  //检查是新用户,还是老用户 根据自己的业务需求做,这个方法就不贴了
    //2: 活动为新用户领取,而领取的用户是老用户
    if($activity['user_type'] == 1 && !empty($checkUser)){
        $result = [
            'code' => 2,
        ];
        return $result;
    }
    //3:活动为老用户领取,而领取的用户是新用户
    if($activity['user_type']==2 && empty($checkUser)){
        $result = [
            'code' => 3,
        ];
        return $result;
    }
    //4:优惠券是否领取完
    $coupon = $this->getCouponExpire($collection_coupon,$params['mobile']);  //这里提示有一个优惠券列表,根据自己的业务需求做,这个方法就不贴了
    //return $coupon;
    if($coupon == 1){
        $result = [
            'code' => 4,
        ];
        return $result;
    }
    //5:已领取过优惠券提示
    $userCoupon = '';
    $userRate = '';
    if(!empty($checkUser)){
        //user存在则为老用户,再检查是否领取过
        $userCoupon = $this->getUserCoupon($collection_coupon,$checkUser['user_id']);
        $userRate = $this->getUserCouponRate($checkUser['user_id'],$activity['activity_id']);
    }else{
        //新用户,检查是否注册过
        $var_user = UserBaseModel::where('user_name',$params['mobile'])->first();
        if(!empty($var_user)){
            $userCoupon = $this->getUserCoupon($collection_coupon,$var_user['user_id']);
            $userRate = $this->getUserCouponRate($var_user['user_id'],$activity['activity_id']);
        }
    }
    //return $userRate;
    if($userCoupon == 1){
        $result = [
            'code' => 5,
            'phone'=> $str_mobile,
            'coupon' => $userRate,
            'is_get' => false,
        ];
        return $result;
    }
    //5:领取成功
    //如果活动规定是新老用户0,新用户1,老用户2
    $getCouponSuccess = $this->getCouponSuccess($activity['user_type'],$checkUser,$collection_coupon,$params['mobile']);
    //return $getCouponSuccess;
    if($getCouponSuccess['status'] == 200){
        $result = [
            'code' => 5,
            'phone'=> $str_mobile,
            'coupon' => $getCouponSuccess['result'][0],
            'is_get' => true,
        ];
        return $result;
    }
}

用户领取优惠券并发放优惠券

/**
 * Function:用户领取活动
 * Author:cyw0413
 * @param $user_type
 */
public function getCouponSuccess($user_type,$user,$coupon,$mobile)
{
    if(count($coupon) > 0){
        switch ($user_type){
            case 1:
                //新用户领取,如果从来没注册过就要新增用户
                $res = $this->addUser($mobile,$coupon);  
                return [
                    'result' => $res,
                    'status' => 200
                ];
                break;
            case 2:
                //老用户领取
                $res = $this->insertUserCoupon($user,$coupon);
                return [
                    'result' => $res,
                    'status' => 200
                ];
                break;
            default:
                //新老用户领取,判断是新用户还是老用户,这里的$user是有无配送单,有则为老用户;
                if(empty($user)){
                    $res = $this->addUser($mobile,$coupon);
                }else{
                    $res = $this->insertUserCoupon($user,$coupon);  //老用户,直接发放优惠券
                }
                return [
                    'result' => $res,
                    'status' => 200
                ];
                break;
        }
    }else{
        throw new \Exception("优惠券不存在或者已经停用");
    }
}

如果取消一个(现金+满减+积分)订单的商品,你如何分摊优惠

  • 一个订单的商品,如果不参与某种活动的时候,那就需要分摊优惠,一般来说需要分摊的有购物金,积分,优惠券,满减等。而且是在整个订单上来使用优惠。
  • 多件N折,限制抢购,团购等都是在商品单价上面体现的,所以难点就在于如何去分摊优惠,比如一个订单用购物金,积分下单了。订单取消后需要返还。这个时候需要退给用户的钱就是商品的价格-购物金-积分之后的钱了

PHP 常见的业务逻辑

以下提供优惠分摊的方法,有需要的可以借鉴一下

<?php
/*
* useuserpoint 拆购物金分摊
* usediscount 拆代金卷分摊
* use_offer 除去购物金和折价券之外的优惠分摊,目前来说就是满减的分摊
* 
* 按照不同活动类型来计算此次参与活动商品总金额,再按参与商品金额goods_total的占比来分配分摊的优惠,
* $type就是来对应相关的优惠分摊类型
*/
function _splitPrice(&$goods_info, $tmprice, $type) {
    $goods_num = count($goods_info);
    $total = 0;
    foreach($goods_info as $k => $v) {
        if($type=='use_offer'){
            if($v['is_own_shop'] == 1) {
                if($goods_num>1){
                    if(!$v['yihe_supplier_deliver']){   //第三方发货的不要摊到满减等优惠
                                 if(!$v['more_goods_discount_info']){  //多件N折商品跳过满减优惠分摊
                             $total += $v['goods_total'];
                                 }
                    }    
                }else{
                    $total += $v['goods_total'];
                }
            }   
        }else{
            if($v['is_own_shop'] == 1) {
                $total += $v['goods_total'];
            }
        }
    }
    $tmp = $tmprice;
   //表面看很不理解,但是其他2个$type对应的值写出来就很明白就是一个根据不同优惠分摊类型经行计算总价格的,
   //下面的也是不过是计算分摊具体优惠转换成存打数据库的字段
//        foreach($goods_info as $k => $v) {
//            if($type=='use_offer'){
//                if($v['is_own_shop'] == 1) {
//                    if($goods_num>1){
//                        if(!$v['yihe_supplier_deliver']){   //第三方发货的不要摊到满减等优惠
//                                     if(!$v['more_goods_discount_info']){  //多件N折商品跳过满减优惠分摊
//                                 $total += $v['goods_total'];
//                                     }
//                        }    
//                    }else{
//                        $total += $v['goods_total'];
//                    }
//                }   
//            }elseif($type=='usediscount'){
//                if($v['is_own_shop'] == 1) {
//                    $total += $v['goods_total'];
//                }
//            }elseif($type=='useuserpoint'){
//                        if($v['is_own_shop'] == 1) {
//                    $total += $v['goods_total'];
//                }
//                        
//                    }
//        }
        foreach($goods_info as $k => $v) {
            //第三方发货的跳过
            //多件N折商品跳过满减优惠分摊
            if($type == 'use_offer'&&$goods_num>1&&$v['yihe_supplier_deliver']){
                continue;
            }
            if($type == 'use_offer'&&$goods_num>1&&$v['more_goods_discount_info']){
                continue;
            }
            if($v['is_own_shop'] == 1) {
                $splitVal = intval($v['goods_total']/$total*$tmprice) + 1;
                if($splitVal > $tmp) {
                    $splitVal = $tmp;
                }
                $tmp -= $splitVal;
                if($type == 'useuserpoint') {
                    $goods_info[$k]['goods_splituserpoint'] = $splitVal;
                } elseif($type == 'usediscount') {
                    $goods_info[$k]['goods_splitusediscount'] = $splitVal;
                }elseif($type=='use_offer'){
                    $goods_info[$k]['goods_split_use_offer'] = $splitVal;
                }
                if($tmp == 0) {
                    break;
                }
            }
        }
//                P($goods_info);
//                die;
    }

利用新浪API实现短网址生成,长网址缩短!

新浪提供了长链接转为短链接的API,可以把长链接转为 t.cn/xxx 这种格式的短链接。

API有两种格式:

http://api.t.sina.com.cn/short_url/shorten.json (返回结果是JSON格式)
http://api.t.sina.com.cn/short_url/shorten.xml (返回结果是XML格式) 

请求参数:

  • source 申请应用时分配的AppKey,调用接口时代表应用的唯一身份。url_long 需要转换的长链接,需要URLencoded,最多不超过20个。
  • 多个url参数需要使用如下方式请求:url_long=aaa&url_long=bbb

创建source方法

  • 1.进入http://open.weibo.com/ ,选择菜单 微连接->网站接入。2.点击立即接入,创建新应用,填写应用名称,点击创建。3.创建成功后,AppKey就是source参数的值,可以用于请求创建短链接。
<?php
$api = 'http://api.t.sina.com.cn/short_url/shorten.json'; // json
// $api = 'http://api.t.sina.com.cn/short_url/shorten.xml'; // xml
$source = '您申请的AppKey';
$url_long = 'https://detail.tmall.com/item.htm?spm=a21wu.241046-us.9629632455.7.193eb6cbbC9gFg&id=585958323801';
$request_url = sprintf($api.'?source=%s&url_long=%s', $source, $url_long);
$data = file_get_contents($request_url);
echo $data;
?>

返回JSON格式

[
    {
        "url_short": "http:\/\/t.cn\/Rki0twp",
        "url_long": "http:\/\/detail.tmall.com/item.htm?spm=a21wu.241046-us.9629632455.7.193eb6cbbC9gFg&id=585958323801",
        "type": 0
    }
]

返回XML格式

<?xml version="1.0" encoding="UTF-8"?>
<urls>
    <url>
        <url_short> http://t.cn/Rki0twp</url_short>
        <url_long>https://detail.tmall.com/item.htm?spm=a21wu.241046-us.9629632455.7.193eb6cbbC9gFg&id=585958323801</url_long>
        <type>0</type>
    </url>
</urls>

生成的短链接为 http://t.cn/Rki0twp ,访问会跳转到 https://detail.tmall.com/item.htm?spm=a21w...

完整的类如下:

/*
 * 生成新浪的短链接或还原新浪短链接
 */
class ShortUrl{
    //新浪APPKEY
    const APPKEY='xxxxxxxx';  //你申请的appkey
    //CURL
    private static function CURLQueryString($url){
        //设置附加HTTP头
        $addHead=array("Content-type: application/json");
        //初始化curl
        $curl_obj=curl_init();
        //设置网址
        curl_setopt($curl_obj,CURLOPT_URL,$url);
        //附加Head内容
        curl_setopt($curl_obj,CURLOPT_HTTPHEADER,$addHead);
        //是否输出返回头信息
        curl_setopt($curl_obj,CURLOPT_HEADER,0);
        //将curl_exec的结果返回
        curl_setopt($curl_obj,CURLOPT_RETURNTRANSFER,1);
        //设置超时时间
        curl_setopt($curl_obj,CURLOPT_TIMEOUT,8);
        //执行
        $result=curl_exec($curl_obj);
        //关闭curl回话
        curl_close($curl_obj);
        return $result;
    }
    //处理返回结果
    private static function doWithResult($result,$field){
        $result=json_decode($result,true);
        return isset($result[0][$field])?$result[0][$field]:'';
    }
    //获取短链接
    public static function getShort($url){
        $url='http://api.t.sina.com.cn/short_url/shorten.json?source='.self::APPKEY.'&url_long='.$url;
        $result=self::CURLQueryString($url);
        return self::doWithResult($result,'url_short');
    }
    //获取长链接
    public static function getLong($url){
        $url='http://api.t.sina.com.cn/short_url/expand.json?source='.self::APPKEY.'&url_short='.$url;
        $result=self::CURLQueryString($url);
        return self::doWithResult($result,'url_long');
    }
}

你也可以用以下完整的方法

<?php
/**
 * 调用新浪接口将长链接转为短链接
 * @param  string  $source 申请应用的AppKey
 * @param  array|string  $url_long  长链接,支持多个转换(需要先执行urlencode)
 * @return array
 */
 function getSinaShortUrl($source, $url_long){
    // 参数检查
    if(empty($source) || !$url_long){
        return false;
    }
    // 参数处理,字符串转为数组
    if(!is_array($url_long)){
        $url_long = array($url_long);
    }
    // 拼接url_long参数请求格式
    $url_param = array_map(function($value){
        return '&url_long='.urlencode($value);
    }, $url_long);
    $url_param = implode('', $url_param);  
    // 新浪生成短链接接口
    $api = 'http://api.t.sina.com.cn/short_url/shorten.json';   
    // 请求url
    $request_url = sprintf($api.'?source=%s%s', $source, $url_param);
    $result = array();
    // 执行请求
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_URL, $request_url);      
    $data = curl_exec($ch);
    if($error=curl_errno($ch)){
        return false;
    }
    curl_close($ch);
    $result = json_decode($data, true);
    return $result;
}
//您申请的AppKey
$source = 'xxxxxxxssssss';
// 单个链接转换
$url_long = 'https://detail.tmall.com/item.htm?spm=a21wu.241046-us.9629632455.7.193eb6cbbC9gFg&id=585958323801';
$data = getSinaShortUrl($source, $url_long);
print_r($data);
// 多个链接转换
$url_longs = array(
'https://detail.tmall.com/item.htm?spm=a21wu.241046-us.9629632455.7.193eb6cbbC9gFg&id=585958323801',
'https://detail.tmall.com/item.htm?spm=a21wu.241046-us.9629632455.7.193eb6cbbC9gFg&id=585958323802',
'https://detail.tmall.com/item.htm?spm=a21wu.241046-us.9629632455.7.193eb6cbbC9gFg&id=585958323803',
'https://detail.tmall.com/item.htm?spm=a21wu.241046-us.9629632455.7.193eb6cbbC9gFg&id=585958323804',
'https://detail.tmall.com/item.htm?spm=a21wu.241046-us.9629632455.7.193eb6cbbC9gFg&id=585958323805',
);
$data_arr = getSinaShortUrl($source, $url_longs);
print_r($data_arr);
?>

PHP fopen/file_get_contents与curl性能比较

fopen/file_get_contents 每次请求都会重新做 DNS 查询,并不对 DNS 信息进行缓存。

  • 但是 CURL 会自动对 DNS 信息进行缓存。对同一域名下的网页或者图片的请求只需要一次 DNS 查询。这大大减少了 DNS 查询的次数。所以 CURL 的性能比 fopen /file_get_contents 好很多。
    • fopen/file_get_contents 在请求 HTTP 时,使用的是 http_fopen_wrapper,不会 keeplive。
  • 而 curl 却可以。这样在多次请求多个链接时,curl 效率会好一些。
    • fopen/file_get_contents 函数会受到 php.ini 文件中 allow_url_open 选项配置的影响。
  • 如果该配置关闭了,则该函数也就失效了。而 curl 不受该配置的影响。
    • curl 可以模拟多种请求,例如:POST 数据,表单提交等,用户可以按照自己的需求来定制请求。
  • 而 fopen /file_get_contents 只能使用 get 方式获取数据。
    • file_get_contents 获取远程文件时会把结果都存在一个字符串中 fiels 函数则会储存成数组形式
    • 因此,我还是比较倾向于使用 curl 来访问远程 url。Php 有 curl 模块扩展,功能很是强大。
#最近需要获取别人网站上的音乐数据。用了file_get_contents函数,但是总是会遇到获取失败的问题,尽管按照手册中的 例子设置了超时,可多数时候不会奏效:
$config['context'] = stream_context_create(array(‘http’ => array(‘method’ => “GET”,
   ’timeout’ => 5//这个超时时间不稳定,经常不奏效
   )
  ));
#这时候,看一下服务器的连接池,会发现一堆类似的错误,让我头疼万分:
file_get_contents(http://***): failed to open stream…
#现在改用了curl库,写了一个函数替换:
function curl_file_get_contents($durl){
  $ch = curl_init();
  curl_setopt($ch, CURLOPT_URL, $durl);
  curl_setopt($ch, CURLOPT_TIMEOUT, 5);
  curl_setopt($ch, CURLOPT_USERAGENT, _USERAGENT_);
  curl_setopt($ch, CURLOPT_REFERER,_REFERER_);
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  $r = curl_exec($ch);
  curl_close($ch);
   return $r;
}

file_get_contents 抓取 google.com 需用秒数:

2.31319094
2.30374217
2.21512604
3.30553889
2.30124092

curl 使用的时间:

0.68719101
0.64675593
0.64326
0.81983113
0.63956594

curl 与 file_get_contents 性能对比 PHP 源代码如下:

<?php
/**
* 通过淘宝IP接口获取IP地理位置
* @param string $ip
* @return: string
**/
function getCityCurl($ip)
{
    $url="http://ip.taobao.com/service/getIpInfo.php?ip=".$ip;
    $ch = curl_init();
    $timeout = 5;
    curl_setopt ($ch, CURLOPT_URL, $url);
    curl_setopt ($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt ($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
    $file_contents = curl_exec($ch);
    curl_close($ch);
    $ipinfo=json_decode($file_contents);
    if($ipinfo->code=='1'){
        return false;
    }
    $city = $ipinfo->data->region.$ipinfo->data->city;
    return $city;
}
function getCity($ip)
{
    $url="http://ip.taobao.com/service/getIpInfo.php?ip=".$ip;
    $ipinfo=json_decode(file_get_contents($url));
    if($ipinfo->code=='1'){
        return false;
    }
    $city = $ipinfo->data->region.$ipinfo->data->city;
    return $city;
}
// for file_get_contents
$startTime=explode(' ',microtime());
$startTime=$startTime[0] + $startTime[1];
for($i=1;$i<=10;$i++)
{
   echo getCity("121.207.247.202")."</br>";
}
$endTime = explode(' ',microtime());
$endTime = $endTime[0] + $endTime[1];
$totalTime = $endTime - $startTime;
echo 'file_get_contents:'.number_format($totalTime, 10, '.', "")." seconds</br>";
//for curl
$startTime2=explode(' ',microtime());
$startTime2=$startTime2[0] + $startTime2[1];
for($i=1;$i<=10;$i++)
{
   echo getCityCurl('121.207.247.202')."</br>";
}
$endTime2 = explode(' ',microtime());
$endTime2=$endTime2[0] + $endTime2[1];
$totalTime2 = $endTime2 - $startTime2;
echo "curl:".number_format($totalTime2, 10, '.', "")." seconds";
?>
  • file_get_contents 速度:4.2404510975 seconds
  • curl 速度:2.8205530643 seconds
  • curl 比 file_get_contents 速度快了 30% 左右,最重要的是服务器负载更低.

file_get_contents 处理频繁小的时候,用它感觉挺好的。没什么异常。如果你的文件被 1k + 人处理。那么你的服务器 cpu 就等着高升吧。

本帖由系统于 5天前 自动加精
讨论数量: 3
晓鹤
1个月前 评论

值得细细品尝

4天前 评论
lmaster

mark

4天前 评论

请勿发布不友善或者负能量的内容。与人为善,比聪明更重要!