Files
youlegames/codes/agent/game/dlweb/api/common/common.inc.php
2026-03-15 01:27:05 +08:00

2205 lines
58 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* Created by Notepad++.
* User: 应俊
* Date: 2016-08-08
* Time: 11:22
*/
/***********************************************************************************************************************
* 2016-08-11
* 原请求参数中的user_auth_token和app_auth_token参数改名为user_auth_token_1和app_auth_token_1表示自用。
* 新增请求参数user_auth_token_2和app_auth_token_2用来对接其他接口暂时对接阿里口碑
* 2016-08-30:
* 增加权限判断。
* 2016-09-12:
* 增加signature。
* 2016-11-29:
* 增加mssql和oracle的支持
* 2016-12-17:
* PDO_Insert, PDO_Update 的字段值允许指定raw属性如果raw为true则不以参数形式传递。
* PDO_Insert, PDO_Update 的字段值允许指定type属性根据不同的type拼装不同的字段描述字符。
* 所有的where条件都支持raw和type属性处理.
* 2017-02-17
* 增加PDO_Select_Page_2方法用于支持多表关联查询时只根据一张表来分页的需求。
* 该方法默认只支持内连接方式。如需要左或右连接需要在查询条件中指明mysql不支持
* 例:
* oracle中 表1.字段1 = 表2.字段2(+) 等价 表1 left join 表2 on 表1.字段1 = 表2.字段2
* sqlserver中 表1.字段1 *= 表2.字段2 等价 表1 left join 表2 on 表1.字段1 = 表2.字段2
* mysql中 mysql 不支持隐含 xxx join 的写法,所以使用该方法时只能适配内连接方式。
* 2017-03-03
* 增加PDO_Select、PDO_Select_Page方法对多表的支持。
* 增加GetSelectTables方法来处理多表表名。
* 在GetSelectFields中增加对包含.的字段名的处理(包含.则两边增加限定符)
* 2017-03-31:
* 查询方法增加group的支持.
* 2017-04-01:
* 修复了若干bug并且把BaseMethod类独立到BaseMethodHelper.php以增加可读性。
* 2017-06-28:
* 去掉了app_id, developer_id, user_auth_token_1, app_auth_token_1, user_auth_token_2, app_auth_token_2以及相关校验。
* 2017-09-25:
* 增加了redis的支持用来控制接口访问的安全控制。
* 首次调用:
* 1客户机登录
* 2生成token(格式sessionid={"RequestUser":"请求的用户","RequestName":"请求的关键字","RequestAddr":"请求的地址","RequestTime":"请求的时间"})
* 3返回给下次请求使用
* 再次调用:
* 1客户机发送上次返回的token
* 2校验token(校验本次时间呵上次保存的时间的间隔是否大于预定的时间间隔)
* 3删除原有token
* 4生成token(格式sessionid={"RequestUser":"请求的用户","RequestName":"请求的关键字","RequestAddr":"请求的地址","RequestTime":"请求的时间"})
* 5返回给下次请求使用
* 2017-10-16:
* 增加了对登录接口访问频率和两次请求客户端地址的限制。
* 2017-12-05:
* 增加随机字符串参数random_string用于解决缓存问题。
* 2017-12-25:
* 增加一个标志用来开启或关闭token的校验
* 2017-12-27:
* 增加一个标志用来限制token是否只能单次使用
* 2018-03-20:
* OpenAPITrunk中VerifyMethod里增加对token信息中requestuser域和request中user_id参数的校验
***********************************************************************************************************************/
//require_once __DIR__ . '/CommonConstant.php';
require_once __DIR__ . '/DatabaseHelper.php';
require_once __DIR__ . '/config.inc.php';
define('AU_KEY', '2347adfas……&*');
define('TYPE_ENCODE', 'ENCODE');
define('TYPE_DECODE', 'DECODE');
/**
* @param string $string 要加密或解密的字符串
* @param string $type 加密或解密DECODE=解密ENCODE=加密)
* @param string $key 密匙
* @param int $expiry 密文有效期
* @return string
* @throws Exception
*/
function CreateAuthCode($string, $type = TYPE_DECODE, $key = null, $expiry = 0)
{
if (!in_array($type, array(TYPE_DECODE, TYPE_ENCODE,)))
return '';
/// 动态密匙长度,相同的明文会生成不同密文就是依靠动态密匙
$ckey_length = 4;
/// 密匙
$key = md5(empty($key) ? AU_KEY : $key);
/// 密匙a会参与加解密
$keya = md5(substr($key, 0, 16));
/// 密匙b会用来做数据完整性验证
$keyb = md5(substr($key, 16, 16));
/// 密匙c用于变化生成的密文
$keyc = $ckey_length ? ($type == TYPE_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 = $type == TYPE_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 ($type == TYPE_DECODE)
{
/// substr($result, 0, 10) == 0 验证数据有效性
/// substr($result, 0, 10) - time() > 0 验证数据有效性
/// substr($result, 10, 16) == substr(md5(substr($result, 26).$keyb), 0, 16) 验证数据完整性
/// 验证数据有效性,请看未加密明文的格式
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));
}
}
function EncodeAuthCode($String, $Key = null, $Expiry = 0)
{
return CreateAuthCode($String, TYPE_ENCODE, $Key, $Expiry);
}
function DecodeAuthCode($String, $Key = null)
{
return CreateAuthCode($String, TYPE_DECODE, $Key);
}
/**
* RSA签名
* @param string $data 待签名数据
* @param string $private_key_path 商户私钥文件路径
* @return bool 签名结果
*/
function rsaSign($data, $private_key_path)
{
$priKey = file_get_contents($private_key_path);
$res = openssl_get_privatekey($priKey);
openssl_sign($data, $sign, $res);
openssl_free_key($res);
//base64编码
$sign = base64_encode($sign);
return $sign;
}
/**
* RSA验签
* @param string $data 待签名数据
* @param string $ali_public_key_path 支付宝的公钥文件路径
* @param string $sign 要校对的的签名结果
* @return bool 验证结果
*/
function rsaVerify($data, $ali_public_key_path, $sign)
{
$pubKey = file_get_contents($ali_public_key_path);
$res = openssl_get_publickey($pubKey);
$result = (bool)openssl_verify($data, base64_decode($sign), $res);
openssl_free_key($res);
return $result;
}
/**
* RSA解密
* @param string $content 需要解密的内容,密文
* @param string $private_key_path 商户私钥文件路径
* @return bool 解密后内容,明文
*/
function rsaDecrypt($content, $private_key_path)
{
$priKey = file_get_contents($private_key_path);
$res = openssl_get_privatekey($priKey);
//用base64将内容还原成二进制
$content = base64_decode($content);
//把需要解密的内容按128位拆开解密
$result = '';
for ($i = 0; $i < strlen($content) / 128; $i++)
{
$data = substr($content, $i * 128, 128);
openssl_private_decrypt($data, $decrypt, $res);
$result .= $decrypt;
}
openssl_free_key($res);
return $result;
}
/**
* @date 2017-03-04
* @note 给参数按key的顺序排序。支持递归
* @param mixed $parameter 要排序的参数
* @return array
* @auth 应俊
*/
function SortParam($parameter)
{
$parameter = (array)$parameter;
foreach ($parameter as $k => $v)
{
if (is_array($v) || is_object($v))
{
$parameter[$k] = SortParam($v);
}
}
/// 调用strcmp函数来排序该函数区分大小写。
uksort($parameter, 'strcmp');
return $parameter;
}
/**
* @date 2017-03-06
* @note 转换参数成字符串形式按key=value的形式用&分隔)。
* @param mixed $parameter 要转换的参数
* @return string
* @auth 应俊
*/
function ConvertParam($parameter)
{
$parameter = (array)$parameter;
$return = '';
foreach ($parameter as $k => $v)
{
if (is_array($v) || is_object($v))
$return .= sprintf('&%s={%s}', $k, ConvertParam($v));
else
$return .= sprintf('&%s=%s', $k, $v);
}
$sublen = mb_strlen('&', USEDCHARSET);
$retlen = mb_strlen($return, USEDCHARSET);
$return = mb_substr($return, $sublen, $retlen - $sublen, USEDCHARSET);
return $return;
}
/**
* @date 2017-03-04
* @note 为参数生成签名
* @param mixed $parameter 要签名的参数
* @param string $rand 随机字符串
* @param string $key 签名key
* @return string
* @auth 应俊
*/
function SignParameter($parameter, $rand = '', $key = '')
{
/// 1先把参数按参数名key从小到大排序
$parameter = SortParam($parameter);
/// 2连接参数成一个字符串按key=value的形式用&分隔)。
$return = ConvertParam($parameter);
/// 3结尾加上key=签名key
$return .= '&randstring=' . $rand . '&key=' . $key;
/// 4md5加密这个字符串
return md5($return);
}
/**
* @note
* @param mixed $message
* @return bool
*/
function OutputDebugMessage($message)
{
if (is_object($message) || is_array($message))
$message = JsonObjectToJsonString($message);
$remoteaddress = $_SERVER['REMOTE_ADDR'];
$nowdate = date('Y-m-d');
$nowtime = date('H:i:s');
$pathname = Characet(dirname($_SERVER['SCRIPT_FILENAME']) . '/debug', 'gbk');
$filename = "{$pathname}/{$nowdate}.log";
$message = Characet($message, 'gbk');
if (!is_dir($pathname))
mkdir($pathname, 0777, true);
if ($file = fopen($filename, 'a+'))
{
if (mb_strstr($message, PHP_EOL, false, USEDCHARSET) != PHP_EOL)
$message .= PHP_EOL;
fwrite($file, "{$nowtime}({$remoteaddress}) ====> {$message}");
fclose($file);
return true;
}
else
return false;
}
/**
* @param mixed $data
* @return array|mixed
*/
function ChangePostData($data)
{
switch (gettype($data))
{
case 'array':
{
foreach ($data as $key => $value)
{
$data[$key] = ChangePostData($value);
}
}
break;
case 'object':
{
$array = (array)$data;
foreach ($array as $key => $value)
{
$data->$key = ChangePostData($value);
}
}
break;
default:
{
//$data = preg_replace_callback('/\+/', function($r) { return '%2B'; }, $data);
//$data = preg_replace_callback('/\&/', function($r) { return '%26'; }, $data);
//$data = preg_replace_callback('/[\+|\&]/', function ($matches) { switch($matches[0]) {case '+': return '%2b';case '&': return '%26'; default: return $matches[0];} }, $data);
$data = str_replace('\+', '%2B', $data);
$data = str_replace('\&', '%26', $data);
}
break;
}
return $data;
}
/**
* @note 发送post请求
* @param string $url
* @param mixed $data
* @return string
*/
function SendPost($url, $data)
{
if (is_object($data) || is_array($data))
$data = http_build_query(ChangePostData($data));
else
$data = ChangePostData($data);
$opts = array(
'http' => array(
'method' => 'POST',
'header' => "Content-type: application/x-www-form-urlencoded\r\n" . "Content-Length: " . strlen($data) . "\r\n",
'content' => $data,)
);
$context = @stream_context_create($opts);
$ret = @file_get_contents($url, false, $context);
$ret = trim($ret, "\xEF\xBB\xBF");
return $ret;
}
/**
* @note 转换分隔符
* @param string $string
* @return mixed
*/
function ConvertSeparator($string)
{
$result = $string;
while (false !== mb_stripos($result, '\\', 0, USEDCHARSET))
{
$result = str_replace('\\', '/', $result);
}
while (false !== mb_stripos($result, '//', 0, USEDCHARSET))
{
$result = str_replace('//', '/', $result);
}
return $result;
}
/**
* @note 获取文件的url
* @param string $filename
* @return string
*/
function GetFileUri($filename = __FILE__)
{
//echo $_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['SERVER_NAME'] . ':' . $_SERVER['SERVER_PORT'] . $_SERVER['SCRIPT_NAME'];
//$filename = __FILE__;
$filename = ConvertSeparator($filename);
$pathname = ConvertSeparator(isset($_SERVER['CONTEXT_DOCUMENT_ROOT']) ? $_SERVER['CONTEXT_DOCUMENT_ROOT'] : '');
$pathlength = mb_strlen($pathname, USEDCHARSET);
$filelength = mb_strlen($filename, USEDCHARSET);
if (false !== stripos($filename, $pathname))
$objectname = mb_substr($filename, $pathlength, $filelength - $pathlength, USEDCHARSET);
else
$objectname = $filename;
$https =(isset($_SERVER['REQUEST_SCHEME']) && strcasecmp($_SERVER['REQUEST_SCHEME'], 'https') == 0) ||
(isset($_SERVER['HTTPS']) && (strcasecmp($_SERVER['HTTPS'], 'on') == 0 || strcasecmp($_SERVER['HTTPS'], '1') == 0));
$port = isset($_SERVER['SERVER_PORT']) ? $_SERVER['SERVER_PORT'] : 0;
$servername = isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : '';
if ($https)
$uri = 'https://' . $servername . (443 == $port ? '' : ':' . $port) . $objectname;
else
$uri = 'http://' . $servername . (80 == $port ? '' : ':' . $port) . $objectname;
return $uri;
}
//echo mb_trim($string, '\s".');
function mb_trim($string, $trim_chars = " \t\n\r\0\x0B"/*'\s'*/)
{
return preg_replace('/^[' . $trim_chars . ']*(?U)(.*)[' . $trim_chars . ']*$/u', '\\1', $string);
}
/**
* @note 注册错误处理函数
*/
function error_reg()
{
$ar = array(E_ERROR => 'error', E_WARNING => 'warning', E_PARSE => 'prase', E_NOTICE => 'notice', );
register_shutdown_function(function () use ($ar)
{
$ers = error_get_last();
if ($ers['type'] != 8 && $ers['type'])
{
$er = $ar[$ers['type']] . $ers['type'] . ': ' . ' ' . $ers['message'] . ' => ' . $ers['file'] . ' line:' . $ers['line'] . ' ' . date('Y-m-d H:i:s') . "\n";
error_log($er, 3, '/tmp/php_error.log');
}
});
set_error_handler(function ($a, $b, $c, $d) use ($ar)
{
if ($a != 8 && $a)
{
$er = $ar[$a] . $a . ': ' . $b . ' => ' . $c . ' line:' . $d . ' ' . date('Y-m-d H:i:s') . "\n";
error_log($er, 3, '/tmp/php_error.log');
}
}, E_ALL ^ E_NOTICE);
}
/**
* @note 发送post请求
* @param $url
* @param $data
* @return string
*/
function urlsend_post($url, $data)
{
//$data = http_build_query(ChangePostData($data));
$data = http_build_query($data);
$opts = array(
'http' => array(
'method' => 'POST',
'header' => "Content-type: application/x-www-form-urlencoded\r\n" . "Content-Length: " . strlen($data) . "\r\n",
'content' => ChangePostData($data),
)
);
$context = stream_context_create($opts);
return file_get_contents($url, false, $context);
}
/**
* @note 获取当前毫秒级时间戳
* @return float
*/
function GetTimeStamp()
{
/// 使用microtime()获取微秒时间戳,格式(中间空格隔开):'秒的小数部分 秒的整数部分',例如'0.69718900 1420440552'
/// 将微秒字符串explode分开, 接收 $MilliSecond=0.69718900 $Second=1420440552
list($MilliSecond, $Second) = explode(' ', microtime());
/// 转成浮点数
$MilliSecond = floatval($MilliSecond);
$Second = floatval($Second);
// /// 相加×1000
// $MilliSecond = ($Second + $MilliSecond) * 1000;
// /// 四舍五入
// $MilliSecond = round($MilliSecond, 0);
// /// 返回结果
// return $MilliSecond;
return $Second + $MilliSecond;
}
abstract class ToolBase
{
public function __construct($parameter = null)
{
if (!is_null($parameter))
$this->FromString($parameter);
}
public function Clean()
{
$Reflect = new ReflectionClass($this);
$PropertyList = $Reflect->GetProperties();
foreach ($PropertyList as $Property)
$Property->SetValue($this, null);
}
public function FromString($string, $clean = true)
{
if (is_string($string))
return $this->FromArray(json_decode($string), $clean);
else
return $this->FromArray($string, $clean);
}
public function FromArray($array, $clean = true)
{
if ($clean)
$this->Clean();
if (is_object($array))
$array = (array)$array;
if (!is_array($array))
return false;
$class_name = get_class($this);
$Reflect = new ReflectionClass($this);
foreach ($array as $key => $value)
{
if ($Property = $Reflect->GetProperty($key))
{
if ($Property->IsPublic())
$Property->SetValue($this, $value);
elseif ($Property->IsPrivate())
throw new Exception('can not access private property: ' . $class_name . '::' . $key . ' in ' . __FILE__ . ' on line ' . __LINE__);
elseif ($Property->IsProtected())
throw new Exception('can not access protected property: ' . $class_name . '::' . $key . ' in ' . __FILE__ . ' on line ' . __LINE__);
else
throw new Exception('can not access unknown property: ' . $class_name . '::' . $key . ' in ' . __FILE__ . ' on line ' . __LINE__);
$Property = null;
}
else
throw new Exception('undefined property: ' . $class_name . '::' . $key . ' in ' . __FILE__ . ' on line ' . __LINE__);
}
return true;
}
public function ToString()
{
return JsonObjectToJsonString($this);
}
public function ToXML()
{
return XmlObjectToXmlString($this);
}
public function ToArray()
{
return (array)$this;
}
}
class BaseParameter
{
public function __construct($default = null)
{
$this->ParseArray($default);
}
/**
* @note 获取对象的属性列表
* @return array
* @auther 应俊
*/
protected function GetPropertyList()
{
$reflect = new ReflectionClass($this);
return array_keys($reflect->getDefaultProperties());
}
/**
* @note 创建签名
* @param mixed $parameter
* @param string $random_string
* @param string $sign_key
* @return string
*/
protected function CreateSignature($parameter, $random_string, $sign_key)
{
return SignParameter((array)$parameter, $random_string, $sign_key);
}
/**
* 从数组中获取值
* @param array $array
* @return bool
* @link
* @auther 应俊
*/
public function ParseArray($array)
{
if (empty($array))
{
return false;
}
switch (gettype($array))
{
case 'object':
{
if (method_exists($array, 'GetObjectPropertyList'))
$keys = $array->GetObjectPropertyList();
else
$keys = array_keys((array)$array);
foreach ($keys as $k)
{
// $v = $array->$k;
// if (is_string($v) && null != ($o = JsonStringToJsonObject($v)) && is_object($o))
// $this->$k = $o;
// else
// $this->$k = $v;
$this->$k = $array->$k;
}
break;
}
case 'array':
{
foreach ($array as $k => $v)
{
// if (is_string($v) && null != ($o = JsonStringToJsonObject($v)) && is_object($o))
// $this->$k = $o;
// else
// $this->$k = $v;
$this->$k = $v;
}
break;
}
default:
return false;
}
return true;
}
}
/**
* Class RequestParameter
* @note 请求参数
* @auther 应俊
*/
class RequestParameter extends BaseParameter
{
/**
* @note appid
* @var string
*/
public $app_id;
/**
* @note 2016-08-27新增: 开发者id
* @var string
*/
public $dev_id;
/**
* @note 模块名(包.类.方法)
* @var string
*/
public $method;
/**
* @note 格式(json)
* @var string
*/
public $format;
/**
* @note 使用编码(utf-8)
* @var string
*/
public $charset;
/**
* @note 时间
* @var string|datetime
*/
public $timestamp;
/**
* @note 版本(1.0)
* @var string|int
*/
public $version;
/**
* @note 通知回调地址
* @var string
*/
public $notify_url;
/**
* @note 用户标识如果有填写该参数则服务端由该参数创建token
* @var string
*/
public $user_id;
/**
* @note 用户授权信息(用户登录信息)(自用)
* @var string
*/
public $user_auth_token;
/**
* @note 应用授权信息(自用)
* @var string
*/
public $app_auth_token;
/**
* 2016-08-11:
* @note 用户授权信息(用户登录信息)(阿里口碑)
* @var string
*/
///public $user_auth_token_2;
/**
* 2016-08-11:
* @note 应用授权信息(阿里口碑)
* @var string
*/
///public $app_auth_token_2;
/**
* @note 包体(模块需要的参数体, 由format参数指定格式, 暂只支持json)
* @var string|mixed
*/
public $biz_content;
/**
* @note 2018-12-05新增随机串
* @var string
*/
public $random_string;
/**
* @note 附带参数, 服务器不处理, 只返回
* @var string|mixed
*/
public $tag;
/**
* @note 2016-09-12新增包体的签名
* @var string
*/
public $signature;
public function ValidSignature($sign_key)
{
try
{
$signature = $this->CreateSignature($this->biz_content, $this->random_string, $sign_key);
return 0 == strcmp($this->signature, $signature);
}
catch (Exception $Exception)
{
//$this->SetErrors($Exception->GetCode(), $Exception->GetMessage());
return false;
}
}
}
/**
* Class ReturnParameter
* @note 返回参数
* @auther 应俊
*/
class ReturnParameter extends BaseParameter
{
/**
* @note 返回值
* @var int
*/
public $retcode = ERRORCODE_UNKNOWN;
/**
* @note 返回信息
* @var string
*/
public $retinfo = ERRORINFO_UNKNOWN;
/**
* @note 用户标识如果有填写该参数则服务端由该参数创建token
* @var string
*/
public $user_id = '';
/**
* @note 用户授权信息(用户登录信息)(自用)
* @var string
*/
public $user_auth_token = '';
/**
* @note 应用授权信息(自用)
* @var string
*/
public $app_auth_token = '';
/**
* @note 返回具体内容, 由format格式指定
* @var string|mixed
*/
public $biz_content = array();
/**
* @note 2018-12-05新增随机串
* @var string
*/
public $random_string = '';
/**
* @note 附带参数, 服务器不处理, 只返回
* @var string|mixed
*/
public $tag = '';
/**
* @note 2016-09-12新增包体的签名
* @var string
*/
public $signature = '';
public function __construct($default)
{
parent::__construct($default);
if (empty($this->random_string))
$this->random_string = rand(0, time());
if (empty($this->tag))
$this->tag = '';
}
/**
* @note 刷新签名字符串
* @param $sign_key
* @return bool
*/
public function RefreshSignature($sign_key)
{
try
{
$this->signature = $this->CreateSignature($this->biz_content, $this->random_string, $sign_key);
/*
ksort($content);
$data = '';
$i = 0;
foreach ($content as $k => $v)
{
if (0 != strcasecmp('sign_type', $k) && 0 != strcasecmp('sign', $k) && !empty($v) && trim($v) != '' && "@" != substr($v, 0, 1))
{
/// 转换成目标字符集
//$charset = mb_detect_encoding($v, 'UTF-8,GBK');
//$v = characet($v, USEDCHARSET);
if ($i == 0)
$data .= "$k=$v";
else
$data .= "&$k=$v";
$i++;
}
}
unset ($k, $v);
//rsaVerify($data, )
*/
return true;
}
catch (Exception $Exception)
{
$this->SetErrors($Exception->GetCode(), $Exception->GetMessage());
return false;
}
}
/*******************************
* @name SetErrors .
* @note set the last method call return code and info.
* @param int $errorcode
* @param string|mixed $errorinfo
* @return bool
*******************************/
public function SetErrors($errorcode, $errorinfo)
{
switch (gettype($errorinfo))
{
case 'object':
case 'array':
$errorinfo = JsonObjectToJsonString($errorinfo);
break;
}
$this->retcode = $errorcode;
$this->retinfo = Characet($errorinfo);
return true;
}
}
class UserAuthInfomation
{
/**
* @note 请求的用户
* @var string
*/
public $RequestUser;
/**
* @note 请求的关键字
* @var string
*/
public $RequestName;
/**
* 2017-10-16:
* @note 请求的地址
* @var string
*/
public $RequestAddr;
/**
* @note 请求的时间(时间戳)
* @var integer
*/
public $RequestTime;
public function ToString()
{
return JsonObjectToJsonString($this);
}
public function FromString($String)
{
$Object = JsonStringToJsonObject($String);
if (empty($Object))
return false;
$Reflect = new ReflectionClass($this);
foreach ((array)$Object as $Key => $Value)
{
if (!$Reflect->hasProperty($Key))
continue;
if ($Property = $Reflect->GetProperty($Key))
{
if ($Property->IsPublic())
$Property->SetValue($this, $Value);
}
}
return true;
}
}
class OpenAPITrunk
{
/**
* @note 是否调试模式
* @var bool
*/
private $DebugMode;
/**
* 2017-11-09: 增加限制服务器访问标志,用于升级时控制访问限制。
* @note 服务器是否对外开放
* @var bool
*/
private $ServerActive;
/**
* 2017-11-09: 增加限制服务器访问标志,用于升级时控制访问限制。
* @note 当服务器不对外开放时,可以访问服务器的地址
* @var array
*/
private $InternalWhiteList;
/**
* @note 是否需要定时关闭
* @var bool $TimedOffNeeded
*/
private $TimedOffNeeded;
/**
* @note 定时关闭起始时间
* @var string $TimedOffBegin
*/
private $TimedOffBegin;
/**
* @note 定时关闭终止时间
* @var string $TimedOffEnd
*/
private $TimedOffEnd;
/**
* 2018-01-22: 增加是否需要验签的设置
* @var bool
*/
private $SignEnabled = false;
/**
* @note 签名key
* @var string
*/
private $SignKey = '';
/**
* @note 是否启用token校验
* @var boolean
*/
private $TokenEnabled;
/**
* @note token是否只能使用一次
* @var boolean
*/
private $TokenSignle;
/**
* @note 是否校验token和user_id是否匹配
* @var boolean
*/
private $ValidTokenUserId;
/**
* @note 访问时间间隔(毫秒)
* @var integer
*/
private $RequestInterval;
/**
* @note token超时时间
* @var integer
*/
private $TokenExpireTickCount;
/**
* @note 当不启用redis的时候token的缓存目录
* @var string $TokenCachePath
*/
private $TokenCachePath;
/**
* @note 是否启用redis
* @var boolean
*/
private $UsedRedis;
/**
* @note redis服务器名或服务器地址
* @var string
*/
private $RedisHostname;
/**
* @note redis服务器端口号
* @var integer
*/
private $RedisHostport;
/**
* @note redis数据库名
* @var string
*/
private $RedisDatabase;
/**
* @note redis登录密码
* @var string
*/
private $RedisPassword;
/**
* @note redis对象
* @var Redis
*/
private $Redis;
/**
* @note 登录接口名表示该接口为登录接口登录接口不校验user_auth_token参数
* @var array
*/
private $LoginMethodList;
/**
* @note 本次请求是否是登录请求
* @var boolean
*/
private $MethodIsLogin;
/**
* @note 不校验也不生成user_auth_token的接口列表
* @var array
*/
private $IgnoreUserAuthTokenMethodList;
/**
* @note 本次请求是否忽略user_auth_token
* @var boolean
*/
private $MethodIsIgnoreUserAuthToken;
/**
* 2017-10-31: 是否需要校验客户机的地址。
* @note 是否校验请求地址
* @var boolean
*/
private $VerifyAddress = false;
/// ================================================================================================================
/**
* @note 错误码
* @var int
* @auther 应俊
*/
private $ErrorCode = ERRORCODE_SUCCESS;
/**
* @note 错误信息
* @var string|mixed
* @auther 应俊
*/
private $ErrorInfo = ERRORINFO_SUCCESS;
/**
* OpenAPITrunk constructor.
*/
public function __construct()
{
$this->ErrorCode = ERRORCODE_SUCCESS;
$this->ErrorInfo = ERRORINFO_SUCCESS;
$this->DebugMode = DEBUG_MODE; /// 是否调试
/// 2017-11-09: 增加限制服务器访问标志,用于升级时控制访问限制。
$this->ServerActive = SERVER_ACTIVE; /// 服务器是否对外开放
$this->InternalWhiteList = INTERNAL_WHITELIST(); /// 当服务器不对外开放时,可以访问服务器的地址
$this->TimedOffNeeded = TIMED_OFF_NEEDED; /// 是否需要定时关闭
$this->TimedOffBegin = TIMED_OFF_BEGIN; /// 定时关闭起始时间
$this->TimedOffEnd = TIMED_OFF_END; /// 定时关闭终止时间
$this->SignEnabled = SIGN_ENABLED; /// 是否需要验签
$this->SignKey = SIGN_KEY; /// 签名key
$this->RequestInterval = REQUEST_INTERVAL; /// 接口访问时间间隔
$this->TokenExpireTickCount = TOKEN_EXPIRE_TICK_COUNT; /// token超时时间
/// 2017-12-25: 增加是否校验token的设置
$this->TokenEnabled = TOKEN_ENABLED; /// 是否启用token校验
/// 2017-12-27: 增加token是否单次使用
$this->TokenSignle = TOKEN_SIGNLE; /// token是否只能使用一次
/// 2018-03-20: 增加是否校验token和user_id是否匹配
$this->ValidTokenUserId = VALID_TOKEN_USERID; /// 是否校验token和user_id是否匹配
/// 2018-02-05: 增加当不启用redis的时候token缓存的目录
$this->TokenCachePath = dirname($_SERVER['SCRIPT_FILENAME']) . '/token/';
if (!is_dir($this->TokenCachePath))
mkdir($this->TokenCachePath, 0777, true);
/// 2017-09-23: 增加redis库的支持用于记录用户token
$this->UsedRedis = REDIS_ENABLED; /// 是否启用redis
$this->RedisHostname = REDIS_HOSTNAME; /// redis服务器名或服务器地址
$this->RedisHostport = REDIS_HOSTPORT; /// redis服务器端口号
$this->RedisDatabase = REDIS_DATABASE; /// redis数据库名
$this->RedisPassword = REDIS_PASSWORD; /// redis登录密码
$this->LoginMethodList = LOGIN_METHOD_LIST(); /// 登录接口名表示该接口为登录接口登录接口不校验user_auth_token参数
$this->VerifyAddress = false; /// 是否需要校验客户机的地址。
$this->MethodIsLogin = false; /// 本次请求是否是登录请求
$this->IgnoreUserAuthTokenMethodList = IGNORE_USER_AUTH_TOKEN_METHOD_LIST(); /// 不校验也不生成user_auth_token的接口列表
$this->MethodIsIgnoreUserAuthToken = false; /// 本次请求是否忽略user_auth_token
/// 2017-09-23: 尝试连接到redis库
if ($this->UsedRedis)
{
try
{
$this->Redis = new Redis();
$this->Redis->POpen($this->RedisHostname, $this->RedisHostport);
//if (!$this->Redis->Ping())
// throw new Exception(ERRORCODE_NOCONNECTED, ERRORINFO_NOCONNECTED);
}
catch (Exception $Exception)
{
$this->SetErrors($Exception->GetCode(), $Exception->GetMessage());
if (ERRORCODE_SUCCESS == $this->ErrorCode)
$this->ErrorCode = ERRORCODE_UNKNOWN;
if (ERRORINFO_SUCCESS == $this->ErrorInfo || empty($this->ErrorInfo))
$this->ErrorInfo = ERRORINFO_UNKNOWN;
}
}
}
/**
* OpenAPITrunk destructor
*/
public function __destruct()
{
/// 2017-09-23: 回收redis资源
if ($this->UsedRedis && !empty($this->Redis))
{
try
{
$this->Redis->Close();
$this->Redis = null;
}
catch (Exception $Exception)
{
$this->SetErrors($Exception->GetCode(), $Exception->GetMessage());
if (ERRORCODE_SUCCESS == $this->ErrorCode)
$this->ErrorCode = ERRORCODE_UNKNOWN;
if (ERRORINFO_SUCCESS == $this->ErrorInfo || empty($this->ErrorInfo))
$this->ErrorInfo = ERRORINFO_UNKNOWN;
}
}
}
/**
* @note 创建新实例
* @return mixed
* @auther 应俊
*/
private function NewInstance()
{
$arguments = func_get_args();
$classname = array_shift($arguments);
$keys = array_keys($arguments);
// 🚨 PHP8兼容性create_function()替换为匿名函数,严格保持原有行为
// ⚠️ 重要:必须保持动态参数处理逻辑与原系统完全一致
array_walk($keys, function(&$value, $key, $prefix) { $value = $prefix . $value; }, '$arg_');
$paramstr = implode(', ', $keys);
// 🚨 PHP8兼容性create_function()替换为反射机制,保持功能等价
// ⚠️ 重要:必须保持类实例创建行为和参数传递机制不变
$reflectionClass = new ReflectionClass($classname);
return $reflectionClass->newInstanceArgs($arguments);
}
/**
* @note 转换参数, 把所有参数经过urldecode解码后都转换成数组, 并且key全部转成小写
* @param mixed $parameter
* @return array|string
*/
private function TranslateParameter($parameter)
{
$result = array();
switch (gettype($parameter))
{
case 'object':
$parameter = (array)$parameter;
foreach ($parameter as $k => $v)
$result[mb_strtolower(rawurldecode($k))] = $this->TranslateParameter($v);
break;
case 'array':
foreach ($parameter as $k => $v)
$result[mb_strtolower(rawurldecode($k))] = $this->TranslateParameter($v);
break;
/*
case 'string':
$parameter = rawurldecode($parameter);
$o = JsonStringToJsonObject($parameter);
if (!is_object($o))
$result = $parameter;
else
$result = $this->TranslateParameter($o);
break;
*/
default:
$result = rawurldecode($parameter);
break;
}
return $result;
}
/**
* @note 2016-08-18校验应用信息
* @param $app_id
* @return bool
*/
// private function VerifyAppInfo($app_id)
// {
// return true;
// }
/**
* @note 获取服务器端地址
* @return string|null
*/
private function GetServerAddress()
{
/// 获取服务器地址
if (isset($_SERVER['HTTP_X_FORWARDED_HOST']))
return $_SERVER['HTTP_X_FORWARDED_HOST'];
elseif (isset($_SERVER['HTTP_HOST']))
return $_SERVER['HTTP_HOST'];
else
return null;
}
/**
* @note 获取发起请求的客户机地址
* @return string|null
*/
private function GetClientAddress()
{
/// 获取发起请求的客户机地址
if (isset($_SERVER['HTTP_CLIENT_IP']))
return $_SERVER['HTTP_CLIENT_IP'];
elseif (isset($_SERVER['HTTP_X_FORWARDED_FOR']))
return $_SERVER['HTTP_X_FORWARDED_FOR'];
elseif (isset($_SERVER['REMOTE_ADDR']))
return $_SERVER['REMOTE_ADDR'];
else
return $this->_GetClientAddress();
}
/**
* 获得用户的真实IP地址
* @access private
* @return string|null
*/
private function _GetClientAddress()
{
static $_ClientAddress = null;
if ($_ClientAddress !== null)
return $_ClientAddress;
if (isset($_SERVER))
{
if (isset($_SERVER['HTTP_X_FORWARDED_FOR']))
{
$List = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
/// 取X-Forwarded-For中第一个非unknown的有效IP字符串
foreach ($List as $Item)
{
$Item = trim($Item);
if (strcasecmp($Item, 'unknown') != 0)
{
$_ClientAddress = $Item;
break;
}
}
}
elseif (isset($_SERVER['HTTP_CLIENT_IP']))
$_ClientAddress = $_SERVER['HTTP_CLIENT_IP'];
else
{
if (isset($_SERVER['REMOTE_ADDR']))
$_ClientAddress = $_SERVER['REMOTE_ADDR'];
else
$_ClientAddress = '0.0.0.0';
}
}
else
{
if (getenv('HTTP_X_FORWARDED_FOR'))
$_ClientAddress = getenv('HTTP_X_FORWARDED_FOR');
elseif (getenv('HTTP_CLIENT_IP'))
$_ClientAddress = getenv('HTTP_CLIENT_IP');
else
$_ClientAddress = getenv('REMOTE_ADDR');
}
preg_match('/[\d\.]{7,15}/', $_ClientAddress, $List);
$_ClientAddress = !empty($List[0]) ? $List[0] : null;
return $_ClientAddress;
}
/**
* @note 2017-08-10创建新的用户授权
* @param string $UserId 用户标识
* @param string $RequestName 本次发起请求的关键字
* @return string
*/
private function CreateUserAuthToken($UserId, $RequestName)
{
/// 2017-12-25: 如果不校验token则直接返回null
if (!$this->TokenEnabled)
return '';
/// 判断当本次请求忽略user_auth_token时则直接返回空数据
//if ($this->MethodIsIgnoreUserAuthToken)
// return '';
if (empty($UserId))
{
session_start();
$UserId = session_id() . time();
}
/// 创建token里的用户信息
$UserAuthInfomation = new UserAuthInfomation();
$UserAuthInfomation->RequestUser = $UserId; /// 请求的用户
$UserAuthInfomation->RequestName = $RequestName; /// 请求的关键字
$UserAuthInfomation->RequestAddr = $this->GetClientAddress(); /// 请求的地址
$UserAuthInfomation->RequestTime = GetTimeStamp(); /// 请求的时间
/// 2017-09-23: 如果启用了redis则需要缓存到redis里。
if ($this->UsedRedis && !empty($this->Redis))
{
if (!$this->Redis->Set($UserId, $UserAuthInfomation->ToString()))
return '';
}
elseif ($TokenFile = fopen($this->TokenCachePath . md5($UserId), 'w+')) /// 2018-02-05: 当没有启动redis库的时候尝试把token信息写入到文件中
{
if (flock($TokenFile, LOCK_EX))
{
fwrite($TokenFile, $UserAuthInfomation->ToString());
flock($TokenFile, LOCK_UN);
}
fclose($TokenFile);
}
else /// 如果没有开启redis则直接用这个信息去做加密并返回
$UserId = $UserAuthInfomation->ToString();
/// 用userid加密生成token
$UserAuthToken = EncodeAuthCode($UserId, null, $this->TokenExpireTickCount);
return $UserAuthToken;
}
/**
* @note 2017-08-10删除指定的用户标识
* @param string $UserId 用户授权信息,如果为空则表示使用获取到的
* @return bool
*/
private function DestroyUserAuthToken(&$UserId = null)
{
if (is_null($UserId))
return false;
/// 2017-12-25: 如果不校验token则直接返回null
if (!$this->TokenEnabled)
return false;
/// 判断当本次请求忽略user_auth_token时则不销毁原有的user_auth_token
if ($this->MethodIsIgnoreUserAuthToken)
return true;
if ($this->UsedRedis && !empty($this->Redis))
$this->Redis->Del($UserId);
elseif (file_exists($this->TokenCachePath . md5($UserId))) /// 2018-02-05: 当没有启动redis库的时候尝试把token文件中的信息清除
unlink($this->TokenCachePath . md5($UserId));
// elseif ($TokenFile = fopen($this->TokenCachePath . md5($UserId), 'r+')) /// 2018-02-05: 当没有启动redis库的时候尝试把token文件中的信息清除
// {
// if (flock($TokenFile, LOCK_EX))
// {
// ftruncate($TokenFile, 0);
// flock($TokenFile, LOCK_UN);
// }
// fclose($TokenFile);
// }
if ($this->TokenSignle) //unset($UserId);
$UserId = null;
return true;
}
/**
* @note 2016-08-18用户token转用户信息
* @param string $UserAuthToken 加密后的授权token
* @return mixed
*/
private function GetUserId($UserAuthToken)
{
return empty($UserAuthToken) ? null : DecodeAuthCode($UserAuthToken);
}
/**
* @note 2017-12-27获取redis中保存的token详情
* @param string $UserId 用户信息
* @return bool|null|string
*/
private function GetUserAuthInfo($UserId = null)
{
if (is_null($UserId))
return null;
if ($this->UsedRedis && !empty($this->Redis))
return empty($UserId) ? null : $this->Redis->Get($UserId);
/// 2018-02-05: 当没有启动redis库的时候尝试从token缓存文件中读取token信息
$TokenFileName = $this->TokenCachePath . md5($UserId);
if (file_exists($TokenFileName))
{
if ($TokenFile = fopen($TokenFileName, 'r'))
{
if (flock($TokenFile, LOCK_SH))
{
$Result = fread($TokenFile, filesize($this->TokenCachePath . md5($UserId)));
flock($TokenFile, LOCK_UN);
}
else
$Result = null;
fclose($TokenFile);
return empty($Result) ? null : $Result;
}
}
return null;
}
/**
* @note 2016-08-30校验开发者模块权限
* @param string $app_id
* @param string $developer_id
* @param string $method
* @return bool
*/
// private function VerifyDeveloperMethodPermission($app_id, $developer_id, $method)
// {
// return true;
//
// if (DEBUG_MODE)
// {
// return true;
// }
// else
// {
// /// 2016-08-30 增加权限判断接口调用.
// $url = 'http://api.willgames.cn/api/permission/check_auth/';
// $data = array(
// 'appid' => $app_id,
// 'devkey' => $developer_id,
// 'authcode' => $method,
// );
//
// $result = JsonStringToJsonObject(rawurldecode(SendPost($url, $data)));
// return isset($result->error) && (0 == $result->error);
// }
// }
/**
* @note 2016-08-18校验用户模块权限
* @param string $app_id
* @param string $user_info
* @param string $method
* @return bool
*/
// private function VerifyUserMethodPermission($app_id, $user_info, $method)
// {
// return true;
// }
/**
* @note 2016-08-18调用方法前的校验工作
* @param RequestParameter $request
* @param ReturnParameter $return
* @return bool
*/
private function VerifyMethod($request, &$return)
{
if (!('RequestParameter' == get_class($request) && 'ReturnParameter' == get_class($return)))
return false;
/// 获取服务器地址
$ServerAddress = $this->GetServerAddress();
/// 获取发起请求的客户机地址
$ClientAddress = $this->GetClientAddress();
/// 是否是登录请求
$this->MethodIsLogin = in_array($request->method, $this->LoginMethodList);
/// 本次请求是否忽略user_auth_token
$this->MethodIsIgnoreUserAuthToken = in_array($request->method, $this->IgnoreUserAuthTokenMethodList);
///OutputDebugMessage("Server Address: {$ServerAddress}; ClientAddress: {$ClientAddress}; Method: {$request->method}");
/// 2017-11-09: 增加限制服务器访问标志,用于升级时控制访问限制。
if (!($this->ServerActive || in_array($ClientAddress, $this->InternalWhiteList)))
{
$return->SetErrors(ERRORCODE_SERVERNOTALLOWED, ERRORINFO_SERVERNOTALLOWED);
return false;
}
/// 有定时关闭控制
if ($this->TimedOffNeeded)
{
$Now = date('H:i:s');
if ($Now >= $this->TimedOffBegin && $Now <= $this->TimedOffEnd)
{
$return->SetErrors(ERRORCODE_SERVERTURNOFF, sprintf(ERRORINFO_SERVERTURNOFF, $this->TimedOffBegin, $this->TimedOffEnd));
return false;
}
}
/// 2017-12-25: 校验token
if (!$this->TokenEnabled) /// 如果不校验token则直接退出
return true;
/// 如果不是登录请求那么就必须校验user_auto_token的有效性。
if (!($this->MethodIsLogin || $this->MethodIsIgnoreUserAuthToken))
{
/// 2017-09-25
/// 从token获取userid
if (empty($UserId = $this->GetUserId($request->user_auth_token)))
{
$return->SetErrors(ERRORCODE_INVALIDUSERINFO, ERRORINFO_INVALIDUSERINFO);
return false;
}
/// 获取用户信息
if (empty($UserAuthInfo = $this->GetUserAuthInfo($UserId)))
{
$this->DestroyUserAuthToken($UserId);
$return->SetErrors(ERRORCODE_INVALIDUSERINFO, ERRORINFO_INVALIDUSERINFO);
return false;
}
/// 分解并校验信息
$UserAuthInfomation = new UserAuthInfomation();
$UserAuthInfomation->FromString($UserAuthInfo);
/// 2018-03-20校验用户id
if ($this->ValidTokenUserId || !empty($request->user_id))
{
if (0 != strcmp($request->user_id, $UserAuthInfomation->RequestUser))
{
$this->DestroyUserAuthToken($UserId);
$return->SetErrors(ERRORCODE_INVALIDUSERINFO, ERRORINFO_INVALIDUSERINFO);
return false;
}
}
/// 如果要求验证客户端地址,则判断两次请求的客户端地址
if ($this->VerifyAddress && strcasecmp($ClientAddress, $UserAuthInfomation->RequestAddr) != 0)
{
//OutputDebugMessage(array('CurrentAddress' => $ClientAddress, 'LoginAddress' => $UserAuthInfomation->RequestAddr, ));
$this->DestroyUserAuthToken($UserId);
$return->SetErrors(ERRORCODE_INVALIDADDRESS, ERRORINFO_INVALIDADDRESS); /// 如果地址不同就报错退出。
return false;
}
/// 计算时间间隔
$CurrentTimeStamp = GetTimeStamp();
$TimeInterval = abs(intval(($CurrentTimeStamp - $UserAuthInfomation->RequestTime) * 1000));
/// 判断时间间隔
if ($TimeInterval < $this->RequestInterval)
{
OutputDebugMessage(['currenttime' => $CurrentTimeStamp, 'requesttime' => $UserAuthInfomation->RequestTime, 'timeinterval' => $TimeInterval, ]);
$return->SetErrors(ERRORCODE_SERVERTOBUSY, ERRORINFO_SERVERTOBUSY);
return false;
}
/// 校验通过,则删除该授权信息
$this->DestroyUserAuthToken($UserId);
}
elseif (strcmp($ServerAddress, $ClientAddress) == 0) /// 2017-10-16新的校验机制改为需要校验登录接口的调用频率以请求地址为准。
{
/// 如果服务器和客户机是同一台机器则不做校验了。
$UserId = empty($request->user_auth_token) ? $ClientAddress : $this->GetUserId($request->user_auth_token);
$this->DestroyUserAuthToken($UserId);
}
else
{
/// 2017-10-16增加对登录接口调用频次的的校验。以请求地址为标识
/// 用客户端地址作为标识
$UserId = $this->GetUserId($request->user_auth_token);
if (empty($UserId))
$UserId = $ClientAddress . '|' . $request->method;
/// redis库中用户信息不为空
if (!empty($UserAuthInfo = $this->GetUserAuthInfo($UserId)))
{
/// 分解并校验信息
$UserAuthInfomation = new UserAuthInfomation();
$UserAuthInfomation->FromString($UserAuthInfo);
/// 如果要求验证客户端地址,则判断两次请求的客户端地址
if ($this->VerifyAddress && strcasecmp($ClientAddress, $UserAuthInfomation->RequestAddr) != 0)
{
//OutputDebugMessage(array('CurrentAddress' => $ClientAddress, 'LoginAddress' => $UserAuthInfomation->RequestAddr, ));
$this->DestroyUserAuthToken($UserId);
$return->SetErrors(ERRORCODE_INVALIDADDRESS, ERRORINFO_INVALIDADDRESS); /// 如果地址不同就报错退出。
return false;
}
/// 计算时间间隔
$CurrentTimeStamp = GetTimeStamp();
$TimeInterval = abs(intval(($CurrentTimeStamp - $UserAuthInfomation->RequestTime) * 1000));
/// 判断时间间隔
if ($TimeInterval < $this->RequestInterval)
{
OutputDebugMessage(['currenttime' => $CurrentTimeStamp, 'requesttime' => $UserAuthInfomation->RequestTime, 'timeinterval' => $TimeInterval, ]);
$return->SetErrors(ERRORCODE_SERVERTOBUSY, ERRORINFO_SERVERTOBUSY);
return false;
}
}
/// 校验通过,则删除该授权信息
$this->DestroyUserAuthToken($UserId);
}
$return->user_id = $UserId;
return true;
}
// /**
// * @note 2016-08-18: 创建调用对象
// * @param string $package
// * @param string $class
// * @param string $method
// * @param string $version
// * @return callable|null
// */
// private function CreateMethodCallable($package, $class, $method, $version)
// {
// $methodfilename = sprintf('%s/lib/%s/%s.php', dirname(__DIR__), $version, $package);
// if (!file_exists($methodfilename))
// {
// return null;
// }
//
// /** @noinspection PhpIncludeInspection */
// require_once $methodfilename;
//
// if (!class_exists($class))
// {
// return null;
// }
//
// $obj = $this->NewInstance($class);
// $callable = array(&$obj, $method);
// if (!is_callable($callable))
// {
// $callable = null;
// }
// return $callable;
// }
/**
* @note 2017-06-23: 创建调用对象
* @param string $package
* @param string $class
* @param string $method
* @param string $version
* @return callable|null
* @throws Exception
*/
private function CreateCallableObject($package, $class, $method, $version)
{
$methodfilename = sprintf('%s/lib/%s/%s.php', dirname(__DIR__), $version, $package);
if (!file_exists($methodfilename))
return null;
/** @noinspection PhpIncludeInspection */
require_once $methodfilename;
if (!class_exists($class))
return null;
$object = $this->NewInstance($class);
if (empty($object))
return null;
return method_exists($object, $method) ? $object : null;
}
/**
* @note 2016-08-18: 调用方法
* @param callable $callable
* @param RequestParameter $request
* @param ReturnParameter $return
* @return bool
*/
private function CallMethodCallable($callable, $request, &$return)
{
if (!(is_callable($callable) && 'RequestParameter' == get_class($request) && 'ReturnParameter' == get_class($return)))
return false;
try
{
/// 2018-03-20复制传上来的user_id到$return对象中用于后面生成新的token
if (!empty($request->user_id))
$return->user_id = $request->user_id;
if (!($result = call_user_func_array($callable, array($request, &$return)))) /// 接口调用返回错误
throw new Exception($return->retinfo, $return->retcode);
$return->SetErrors(ERRORCODE_SUCCESS, ERRORINFO_SUCCESS);
$return->biz_content = $this->TranslateParameter($return->biz_content);
/// 2017-08-10当接口调用成功则返回新的用户授权信息。
$return->user_auth_token = $this->CreateUserAuthToken($return->user_id, $request->method);
/// 当忽略token时则不返回生成的token
if ($this->MethodIsIgnoreUserAuthToken)
$return->user_auth_token = '';
if (ERRORCODE_SUCCESS != $return->retcode)
$this->SetErrors($return->retcode, $return->retinfo);
$return->app_auth_token = '';
return $result;
}
catch(Exception $Exception)
{
$return->retcode = ERRORCODE_SUCCESS == $Exception->GetCode() ? ERRORCODE_UNKNOWN : $Exception->GetCode();
$return->retinfo = ERRORINFO_SUCCESS == $Exception->GetMessage() ? ERRORINFO_UNKNOWN : $Exception->GetMessage();
$return->biz_content = (object)null; /// 置空返回数据域
/// 2017-11-14增加对登录请求的处理当本次请求为登录请求时必须登录成功才生成新的用户授权信息。
$return->user_auth_token = $this->CreateUserAuthToken($return->user_id, $request->method);
/// 必须登录成功才生成新的用户授权信息。
if ($this->MethodIsLogin || $this->MethodIsIgnoreUserAuthToken)
$return->user_auth_token = '';
$return->app_auth_token = '';
return false;
}
}
/**
* @note 分发函数.
* @param array $parameter
* @return ReturnParameter
* @throws Exception
* @auther bahamut
*/
public function DispatchProcedure($parameter)
{
$request = null;
$return = null;
try
{
/// 转换参数,参数名全部转成小写,方便访问。
$p = $this->TranslateParameter($parameter);
if (!is_array($p) || 0 == count($p))
//return new ReturnParameter(array('retcode' => ERRORCODE_NOPARAMETER, 'retinfo' => ERRORINFO_NOPARAMETER));
throw new Exception(ERRORINFO_NOPARAMETER, ERRORCODE_NOPARAMETER);
/// 校验必须存在的参数
///$needkey = array('app_id', 'dev_id', 'method', 'format', 'charset', 'version', 'biz_content');
$needkey = array('method', 'version', );
// /// 如果需要校验token则必须传入token参数
// if ($this->TokenEnabled)
// array_push($needkey, 'user_auth_token');
// /// 如果需要校验token和user_id是否匹配则必须传入user_id参数
// if ($this->ValidTokenUserId)
// array_push($needkey, 'user_id');
foreach ($needkey as $key)
{
if (!isset($p[$key]))
//return new ReturnParameter(array('retcode' => ERRORCODE_FIELDNOTFOUND, 'retinfo' => sprintf(ERRORINFO_FIELDNOTFOUND, $key)));
throw new Exception(sprintf(ERRORINFO_FIELDNOTFOUND, $key), ERRORCODE_FIELDNOTFOUND);
}
$request = new RequestParameter($p);
$return = new ReturnParameter(array('random_string' => $request->random_string, 'tag' => $request->tag, ));
/// 设置一些参数的默认值
if (empty($request->format))
$request->format = 'json';
if (empty($request->charset))
$request->charset = USEDCHARSET;
/// 转换编码
if (strcasecmp($request->charset, USEDCHARSET))
$request->biz_content = mb_convert_encoding(@$request->biz_content, USEDCHARSET, $request->charset);
/// 处理biz_content参数
if (empty(@$request->biz_content))
$request->biz_content = array();
elseif (0 == strcasecmp('json', $request->format)) /// json
{
/// decode json string
if (is_string($request->biz_content))
$request->biz_content = JsonStringToJsonObject($request->biz_content);
/// verify json data format
if (!is_array($request->biz_content) && !is_object($request->biz_content))
{
//$return->SetErrors(ERRORCODE_INVALIDJSONDATA, sprintf(ERRORINFO_INVALIDJSONDATA, $parameter['biz_content']));
//$request = null;
//return $return;
throw new Exception(sprintf(ERRORINFO_INVALIDJSONDATA, $parameter['biz_content']), ERRORCODE_INVALIDJSONDATA);
}
if (is_object($request->biz_content))
$request->biz_content = (array)$request->biz_content;
}
elseif (0 == strcasecmp('xml', $request->format)) /// xml
{
/// decode xml string
if (is_string($request->biz_content))
$request->biz_content = XmlStringToXmlObject($request->biz_content);
/// verify xml data format
if (!is_array($request->biz_content) && !is_object($request->biz_content))
{
//$return->SetErrors(ERRORCODE_INVALIDJSONDATA, sprintf(ERRORINFO_INVALIDJSONDATA, $parameter['biz_content']));
//$request = null;
//return $return;
throw new Exception(sprintf(ERRORINFO_INVALIDXMLDATA, $parameter['biz_content']), ERRORCODE_INVALIDXMLDATA);
}
if (is_object($request->biz_content))
$request->biz_content = (array)$request->biz_content;
}
else
{
//$return->SetErrors(ERRORCODE_BADPARAMETER, sprintf(ERRORINFO_BADPARAMETER, 'format', $request->format));
//$request = null;
//return $return;
throw new Exception(sprintf(ERRORINFO_BADPARAMETER, 'format', $request->format), ERRORCODE_BADPARAMETER);
}
/// 获取模块名,并调用对应类方法(包名.类名.方法名)
/// 2016-08-11转换模块名为小写
$method_arr = explode('.', mb_strtolower(trim($request->method, ' ')));
if (count($method_arr) < 3)
{
//$return->SetErrors(ERRORCODE_INVALIDPARAMETER, sprintf(ERRORINFO_INVALIDPARAMETER, 'method'));
//$request = null;
//return $return;
throw new Exception(sprintf(ERRORINFO_INVALIDPARAMETER, 'method'), ERRORCODE_INVALIDPARAMETER);
}
/// 2018-01-22校验签名
if ($this->SignEnabled)
{
if (!$request->ValidSignature($this->SignKey))
throw new Exception(ERRORINFO_INVALIDSIGNATURE, ERRORCODE_INVALIDSIGNATURE);
}
/// 2016-08-18做调用前的校验
if (!$this->VerifyMethod($request, $return))
{
//$request = null;
//return $return;
throw new Exception($return->retinfo, $return->retcode);
}
/// 生成调用模块对象
$object = $this->CreateCallableObject($method_arr[0], $method_arr[1], $method_arr[2], $request->version);
if (null == $object)
{
//$return->SetErrors(ERRORCODE_METHODNOTFOUND, sprintf(ERRORINFO_METHODNOTFOUND, $request->method, $request->version));
//$request = null;
//return $return;
throw new Exception(sprintf(ERRORINFO_METHODNOTFOUND, $request->method, $request->version), ERRORCODE_METHODNOTFOUND);
}
/// 生成调用模块
$callable = array(&$object, $method_arr[2]);
if (!is_callable($callable))
{
//$return->SetErrors(ERRORCODE_METHODNOTFOUND, sprintf(ERRORINFO_METHODNOTFOUND, $request->method, $request->version));
//$request = null;
//return $return;
throw new Exception(sprintf(ERRORINFO_METHODNOTFOUND, $request->method, $request->version), ERRORCODE_METHODNOTFOUND);
}
/// 调用方法。
$this->CallMethodCallable($callable, $request, $return);
}
catch (Exception $Exception)
{
/// 创建返回对象
if (!$return)
$return = new ReturnParameter(array('retcode' => $Exception->GetCode(), 'retinfo' => $Exception->GetMessage(),));
else
$return->SetErrors($Exception->GetCode(), $Exception->GetMessage());
if (ERRORCODE_SUCCESS == $return->retcode)
$return->retcode = ERRORCODE_UNKNOWN;
if (ERRORINFO_SUCCESS == $return->retinfo)
$return->retinfo = ERRORINFO_UNKNOWN;
$return->biz_content = (object)null;
}
if ($return)
{
/// 去掉user_id域
//if (isset($return->user_id))
unset($return->user_id);
/// 刷新签名
$return->RefreshSignature(isset($this->SignKey) ? $this->SignKey : '');
/// 输出日志
if (ERRORCODE_SUCCESS != $return->retcode)
OutputDebugMessage('request ==> ' . ObjectToLinkString($_REQUEST) . PHP_EOL . 'return ==> ' . JsonObjectToJsonString($return));
}
$request = null;
return $return;
}
/*******************************
* @name GetErrorCode
* @note get the last method call return code.
* @param none
* @return int
*******************************/
public function GetErrorCode()
{
return $this->ErrorCode;
}
/*******************************
* @name GetErrorInfo
* @note get the last method call return info.
* @param none
* @return string|mixed
*******************************/
public function GetErrorInfo()
{
return $this->ErrorInfo;
}
/*******************************
* @name GetErrors
* @note get the last method call return code and info.
* @param none
* @return array
*******************************/
public function GetErrors()
{
return array('ErrorCode' => $this->ErrorCode, 'ErrorInfo' => $this->ErrorInfo);
}
/*******************************
* @name SetErrors .
* @note set the last method call return code and info.
* @param int $errorcode
* @param string|mixed $errorinfo
* @return bool
*******************************/
public function SetErrors($errorcode, $errorinfo)
{
switch (gettype($errorinfo))
{
case 'object':
case 'array':
$errorinfo = JsonObjectToJsonString($errorinfo);
break;
}
$this->ErrorCode = $errorcode;
$this->ErrorInfo = Characet($errorinfo);
return true;
}
}
/**
*lock_thisfile获得独享锁
*@param string $tmpFileStr 用来作为共享锁文件的文件名(可以随便起一个名字)
*@param boolean $locktype 锁类型缺省为false(非阻塞型也就是一旦加锁失败则直接返回false),设置为true则会一直等待加锁成功才返回
*@return resource|null 如果加锁成功,则返回锁实例(当使用unlock_thisfile方法的时候需要这个参数)加锁失败则返回false.
*/
function lock_thisfile($tmpFileStr, $locktype = false){
if($locktype == false)
$locktype = LOCK_EX|LOCK_NB;
$can_write = 0;
$lockfp = @fopen($tmpFileStr.".lock","w");
if($lockfp){
$can_write = @flock($lockfp,$locktype);
}
if($can_write){
return $lockfp;
}
else{
if($lockfp){
@fclose($lockfp);
@unlink($tmpFileStr.".lock");
}
return false;
}
}
/**
*unlock_thisfile对先前取得的锁实例进行解锁
*@param resource $fp lock_thisfile方法的返回值
*@param string $tmpFileStr 用来作为共享锁文件的文件名(可以随便起一个名字)
*/
function unlock_thisfile($fp,$tmpFileStr){
@flock($fp,LOCK_UN);
@fclose($fp);
@fclose($fp);
@unlink($tmpFileStr.".lock");
}