header('device', 0, 'intval'); if ($device > 0) { $this->device = $device; // if($device == 4){ // $this->aesKey = 'aes_key_h5'; // } } } /** * 初始化操作 */ public function _initialize() { self::initDevice(); //密文 $data = file_get_contents('php://input'); log_message('Api@_initialize.sign_input:' . $data, 'log', LOG_PATH . 'apilog/'); $decryptStr = $this->opensslDecrypt($data, Env::get($this->aesKey)); // 解密后的明文 $api_debug = Request::instance()->header('qmapidebug', false, 'boolval'); // 是否开启API调试模式 // 解密后的明文为空时 if (!$decryptStr || $api_debug == true) { // 设置不加密 if ($api_debug == true) { $this->isApiDebug = true; } $this->input = input(); if(count($this->input) <= 0){ $this->jsonResult('', -111, 'API数据解密失败,请确认加密数据!'); } } else { $params = json_decode($decryptStr, true); if (JSON_ERROR_NONE == json_last_error()) { $this->input = $params; } else { $this->jsonResult('', -112, 'API解码失败,请检查数据格式!'); } } // 获取 body 的设备号(主要针对 安卓/IOS客户端) $device = $this->input('device', 0, 'intval'); if ($device > 0 && $this->device == 0) { $this->device = $device; } // trace('input:' . json_encode($this->input), 'Api@_initialize.input'); log_message("Api@_initialize.input: {$device} -- " . json_encode($this->input), 'log', LOG_PATH . 'apilog/'); /********************* 接口签名验证 start *********************/ $appid = $this->input('appid'); //游戏的appid if (empty($appid)) { $this->jsonResult('', -121, '密钥ID不能为空'); } else { $appModel = new App; $this->appInfo = $appModel->field('id,appkey,client_appkey,gameid')->where(['id' => $appid])->find(); if (empty($this->appInfo)) { $this->jsonResult('', -122, '当前应用不存在!'); } elseif (empty($this->appInfo['appkey'])) { $this->jsonResult('', -123, '当前应用配置有误!'); } //签名验证 if (!$this->md5Verify($this->input, $this->input('sign')) && Config::get('api_debug') == false && $api_debug == false) { $this->jsonResult('', -124, '签名不能通过,被拒绝访问'); } } /********************* 接口签名验证 end *********************/ // 母包渠道ID if (empty($this->input['channel_id'])) { $this->input['channel_id'] = Config::get('initial_channel_id'); } $this->version = str_ireplace('v', '', $this->input('version')); // 校验登录 $this->checkLogin(); } /** * md5方式接口数据验签 * * @param $data array 待签名数据 * @param $sign string 签名结果 * * @return boolean 验签结果 */ private function md5Verify($data, $sign) { $data = $this->paraFilter($data); ksort($data); // 按key升序排列 reset($data); //拼接待签名字符串 $arg = ""; foreach ($data as $key => $val) { $arg .= $key . "=" . $val; } //如果存在转义字符,那么去掉转义 if (get_magic_quotes_gpc()) { $arg = stripslashes($arg); } //添加客户端appkey参数值 $arg .= $this->appInfo['client_appkey']; //全部转化成小写 $arg = mb_strtolower($arg, 'UTF-8'); $arg = $this->romanToUppercase($arg); $mysgin = md5($arg); if (strcasecmp($mysgin, $sign) == 0){ // trace('数据验签 - 客户端sign:' . $sign . ' - 服务端sign:' . $mysgin . ' - ' . $arg, 'Api@md5Verify.error'); return true; } else { trace('数据验签失败 - 客户端sign:' . $sign . ' - 服务端sign:' . $mysgin . ' - ' . $arg, 'Api@md5Verify.error'); return false; } } private function romanToUppercase($roman) { $uppercase = array( 'Ⅰ', 'Ⅱ', 'Ⅲ', 'Ⅳ', 'Ⅴ', 'Ⅵ', 'Ⅶ', 'Ⅷ', 'Ⅸ', 'Ⅹ', 'Ⅺ', 'Ⅻ' ); $lowercase = array( 'ⅰ', 'ⅱ', 'ⅲ', 'ⅳ', 'ⅴ', 'ⅵ', 'ⅶ', 'ⅷ', 'ⅸ', 'ⅹ', 'ⅺ', 'ⅻ' ); return str_replace($uppercase, $lowercase, $roman); } /** * 过滤不参与签名的参数 * * @param $para array 待签名数据 * * @return array */ private function paraFilter($para) { $para_filter = array(); // while (list ($key, $val) = each ($para)) { foreach ($para as $key => $val) { if ($key == "sign") continue; else $para_filter[$key] = $para[$key]; } return $para_filter; } /** * 使用openssl库进行加密 * @param string $data 加密数据 * @param string $key 加密用的key * @param string $method 加密方法 * @return string 加密后的密文 */ private function opensslEncrypt($data, $key, $method = 'AES-128-ECB') { $str = openssl_encrypt($data, $method, $key); return $str; } /** * 使用openssl库进行解密 * @param string $data 解密密文 * @param string $key 解密用的key * @param string $method 解密方法 * * @return string 解密后的明文 */ protected function opensslDecrypt($data, $key, $method = 'AES-128-ECB') { try { $str = openssl_decrypt($data, $method, $key); } catch (\Exception $e) { $this->jsonResult('', 0, '接口参数解密失败' . $e->getMessage() . ',请检查参数格式!'); } return $str; } /** * 获取参数 * @param string $key 数组index * @param string $default 默认值 * @return mixed|null */ protected function input($key, $default = null) { return isset($this->input[$key]) ? $this->input[$key] : ($default !== null ? $default : null); } /** * 校验登录 */ protected function checkLogin() { $controller = request()->controller(); $action = request()->action(); if (strpos($controller, '.') !== false) { list($version, $controller) = explode('.', $controller); } // 需要检验登录的操作 if (!in_array($controller, ['register', 'sendSms', 'CheckUpdate', 'complexLogin', 'startup', 'forget', 'privacy', 'config', 'Third']) && !in_array($action, $this->noNeedLogin)) { $imeil = $this->input('imeil'); if (empty($imeil)) { $this->jsonResult('', -131, '设备IMEI不能为空! '); } $token = $this->input('token', Request::instance()->header('token')); if (empty($token)) { $this->jsonResult('', -132, '未登录! '); } $redisImeil = Cache::store('redis')->get($token); if (!$redisImeil) { $this->jsonResult('', -133, '登陆失效,请重新登录!', 'json'); } elseif ($redisImeil != $imeil) { $this->jsonResult('', -134, '您的账号已在其他设备上登录!'); } try { $tokenStr = auth_code($token, "DECODE", Env::get($this->authKey)); if(!$tokenStr){ $this->jsonResult('', -135, '登陆异常,请重新登录!', 'json'); } $tokenArr = array_pad(explode('|', $tokenStr), 7, ''); // version > 3.6.8 之后才会有 v3 标识,用于自动登录时候判断老版本的jwt_token的 list($userid, $username, $gameid, $sub_username, $token_random, $type, $token_version) = $tokenArr; // 103487|13121711111|244|103487|1tRh9iUu|sdk|v3 // if($this->input('device') == 4){ // // $userInfo = model('common/Member')->where(['userid' => $userid, 'gameid' => $gameid])->find(); // } }catch (\Exception $e){ $this->jsonResult('', -136, '登陆异常,请重新登录!!', 'json'); } $redisTokenRandom = Cache::store('redis')->get('token|' . $type . '|' . $userid . '|' . $gameid); //redis中玩家登录安全控制的随机码不存在时 if (!$redisTokenRandom) { Cache::store('redis')->rm($token); $this->jsonResult('', -137, '登陆异常,请重新登录!', 'json'); } //两个token随机码不同时 elseif ($redisTokenRandom != $token_random) { Cache::store('redis')->rm($token); $this->jsonResult('', -138, '登陆失败,请重新登录!', 'json'); } $this->input['userid'] = $userid; $this->input['gameid'] = $gameid; $this->input['username'] = $username; $this->input['token'] = $token; $this->input['sub_username'] = $sub_username; $this->input['member_channel_id'] = getChannelId($userid, $gameid); // 当前玩家绑定的渠道 $this->input['token_version'] = $token_version; $path = request()->path(); if(in_array($path, $this->request_frequency_handle)){ if(requestFrequencyHandle($username, $path) == false){ $this->jsonResult('', -138, '操作过于频繁!', 'json'); } } } } /** * 重新给客户端提交的参数赋值 * * @param $key :键 * @param $value :值 */ public function setInput($key, $value) { $this->input[$key] = $value; } /** * 保存设备信息 * * @param $game_id : int 游戏ID * @param $imei : string 游戏imei码 * @param $platform : int 0:安卓 1:IOS * * @return boolean */ protected function saveDevices($game_id, $imei, $platform) { $os_version = trim($this->input('os_version')); //操作系统版本 if (empty($os_version) || $platform == null || empty($this->input('phone_brand')) || empty($this->input('phone_model'))) { return false; } //设备信息记录不存在时 if (!Db::table('nw_devices')->where(['game_id' => $game_id, 'imei' => $imei, 'os_version' => $os_version])->find()) { $devicesData['game_id'] = $game_id; $devicesData['platform'] = !empty($platform) ? 1 : 0; $devicesData['imei'] = $imei; $devicesData['phone_brand'] = $this->input('phone_brand'); $devicesData['phone_model'] = $this->input('phone_model'); $devicesData['os_version'] = $os_version; $devicesData['update_time'] = NOW_TIMESTAMP; $devicesData['create_time'] = NOW_TIMESTAMP; Db::table('nw_devices')->insert($devicesData); return true; } return false; } /** * 保存玩家游戏区服信息 * * @param $game_id : int 游戏ID * @param $imei : string 游戏imei码 * @param $platform : int 0:安卓 1:IOS * * @return boolean */ protected function saveGameServer($userid, $gameid, $imeil = '', $source = 'role') { $serverid = $this->input('serverid', '', 'trim'); $servername = $this->input('servername', '', 'trim'); $roleid = $this->input('roleid', '', 'trim'); $rolename = $this->input('rolename', '', 'trim'); $viplevel = $this->input('viplevel', '', 'trim'); $rolelevel = $this->input('rolelevel', '', 'trim'); //游戏ID不能为空 if (empty($gameid)) { return false; } elseif (empty($userid) || empty($servername) || empty($serverid) || empty($roleid) || empty($rolename) || empty($imeil)) { return false; } $memberGameInfo = model('MemberChannelGame')->where(['member_id' => $userid, 'game_id' => $gameid])->find(); if (empty($memberGameInfo)) { return false; } // 修复部分仙剑的区服ID位数不对问题 if($handleServcerId = repairRoleServerid($gameid, $serverid)){ $serverid = $handleServcerId; } $memberGameServerInfo = model('MemberGameServer')->where(['member_id' => $userid, 'game_id' => $gameid, 'roleid' => $roleid])->find(); if ($memberGameServerInfo) { $updRoleData = array(); $updRoleData['serverid'] = $serverid; $updRoleData['servername'] = $servername; $updRoleData['roleid'] = $roleid; $updRoleData['rolename'] = $rolename; if ($rolelevel && $rolelevel <> '0') { $updRoleData['rolelevel'] = $rolelevel; } if ($viplevel && $viplevel <> '0') { $updRoleData['viplevel'] = $viplevel; } $updRoleData['last_ip'] = request()->ip(); $updRoleData['last_imeil'] = $imeil; $updRoleData['update_time'] = NOW_TIMESTAMP; $updResult = model('MemberGameServer')->where(['member_id' => $userid, 'game_id' => $gameid, 'roleid' => $roleid])->update($updRoleData); } else { $cahce_key = md5($userid . $gameid . $serverid . $roleid); if(!Cache::get(':Role_add:MemberGameServer:'.$cahce_key)){ Cache::set(':Role_add:MemberGameServer:'.$cahce_key,'1',3600); if ($memberGameInfo) { $addRoleData = array(); $addRoleData['member_id'] = $memberGameInfo['member_id']; $addRoleData['channel_id'] = $memberGameInfo['channel_id']; $addRoleData['game_id'] = $memberGameInfo['game_id']; $addRoleData['serverid'] = $serverid; $addRoleData['ip'] = request()->ip(); $addRoleData['imeil'] = $imeil; $addRoleData['create_time'] = NOW_TIMESTAMP; $addRoleData['servername'] = $servername; $addRoleData['roleid'] = $roleid; $addRoleData['rolename'] = $rolename; $addRoleData['rolelevel'] = $rolelevel; $addRoleData['viplevel'] = $viplevel; $addRoleData['last_ip'] = request()->ip(); $addRoleData['last_imeil'] = $imeil; $addRoleData['update_time'] = NOW_TIMESTAMP; $insertId = model('MemberGameServer')->insert($addRoleData); } } } $gameServerInfo = model('GameServer')->where(['game_id' => $gameid, 'serverid' => $serverid])->find(); if ($gameServerInfo) { $updServerData = array(); $updServerData['servername'] = $servername; $updServerData['update_time'] = NOW_TIMESTAMP; $updServerResult = model('GameServer')->where(['game_id' => $gameid, 'serverid' => $serverid])->update($updServerData); } else { $cahce_key = md5($gameid.$serverid); if(!Cache::get(':Role_add:GameServer:'.$cahce_key)) { Cache::set(':Role_add:MemberGameServer:' . $cahce_key, '1', 3600); $addServerData = array(); $addServerData['game_id'] = $gameid; $addServerData['serverid'] = $serverid; $addServerData['create_time'] = NOW_TIMESTAMP; $addServerData['servername'] = $servername; $addServerData['update_time'] = NOW_TIMESTAMP; $insertServerId = model('GameServer')->insert($addServerData); } } $clientIp = request()->ip(); if ($gameid && $serverid && $clientIp) { $addServerIpData = array(); $addServerIpData['channel_id'] = $memberGameInfo['channel_id']; $addServerIpData['member_id'] = $memberGameInfo['member_id']; $addServerIpData['game_id'] = $gameid; $addServerIpData['serverid'] = $serverid; $addServerIpData['create_src'] = $source; $addServerIpData['create_time'] = NOW_TIMESTAMP; $addServerIpData['servername'] = $servername; $addServerIpData['ip'] = $clientIp; $insertServerIpId = model('GameServerIp')->insert($addServerIpData); } if ($gameid && $serverid && $imeil) { $addServerImeilData = array(); $addServerImeilData['channel_id'] = $memberGameInfo['channel_id']; $addServerImeilData['member_id'] = $memberGameInfo['member_id']; $addServerImeilData['game_id'] = $gameid; $addServerImeilData['serverid'] = $serverid; $addServerImeilData['create_src'] = $source; $addServerImeilData['create_time'] = NOW_TIMESTAMP; $addServerImeilData['servername'] = $servername; $addServerImeilData['imeil'] = $imeil; try { $insertServerImeilId = model('GameServerImeil')->insert($addServerImeilData); }catch (\Exception $e){ } } return true; } /** * 获取游戏配置参数(有些游戏有多个参数时需要特殊处理) * @param $game_id 游戏id * @param $channel_mark 渠道标识 * @param string $data 返回的数据 * @param string $code 返回的code * @param string $msg 返回的msg * @return mixed */ public function getGameParam($game_id, $channel_mark, $data = '', $code = '0', $msg = '该游戏的验签参数还未设置') { $polyChannelGameModel = new PolychannelGame; $param = $polyChannelGameModel->alias('a') ->join('nw_channel b', 'b.id = a.channel_id') ->where(['a.game_id' => $game_id, 'b.mark' => $channel_mark]) ->value('a.param'); if (empty($param)) { log_message('后台渠道游戏中,用于该游戏的验签参数还未设置,game_id=' . $game_id . ',渠道标识=' . $channel_mark, 'error', LOG_PATH . '../complexPaylog/'); $this->jsonResult($data, $code, $msg); } $param = unserialize($param); return $param; } /**登录验证返回的数据 * @param $code 1 登录验证成功 0验证失败 * @param string $msg 登录验证提示语 * @param array $data 需要重写的参数 key=》val key必须和complexLogin 里的key一致 * @param string $exparam 扩展参数 * @return array */ public function reParam($code, $msg = '', $data = [], $exparam = '') { return ['code' => $code, 'msg' => $msg, 'extparam' => $exparam, 'data' => $data]; } /** * 返回封装后的 API json数据到客户端 * @access protected * @param mixed $data 要返回的数据 * @param int $code 返回的 code * @param mixed $msg 提示信息 * @param $isExit boolean 是否强制退出,默认true * @return void * */ public function jsonResult($data, $code = 0, $msg = '', $isExit = true) { $err_code = $code; // if($this->device != 4){ // if ($code >= 200) { // $code = 1; // } else { // $code = 0; // } // } if ($code > 0) { $code = 1; } else { $code = 0; } $result = [ 'code' => $code, 'msg' => $msg, 'time' => request()->server('REQUEST_TIME'), 'data' => $data, 'err_code' => $err_code, ]; log_message('Api@jsonResult.result:'. ' code:'. $code." - result:". json_encode($result), 'log', LOG_PATH . 'apilog/'); // debug模式下返回明文 if ($this->isApiDebug) { // echo json_encode($result); if($err_code < 0){ $msg .= '(err:'.$err_code.')'; } $this->result($data,$code,$msg,'json'); //api debug模式下fastcgi_finish_request无法使用 } //正式环境返回密文 else { log_message('Api@jsonResultUnescape.result:'. $this->opensslEncrypt(json_encode($result, JSON_UNESCAPED_SLASHES), Env::get($this->aesKey)), 'log', LOG_PATH . 'apilog/'); echo $this->opensslEncrypt(json_encode($result), Env::get($this->aesKey)); } //为了使用fastcgi_finish_request等函数时,后续的执行能够继续生效 if ($isExit) exit; } public function jsonResultUnescape($data, $code = 0, $msg = '', $isExit = true) { // if($this->device != 4){ // if ($code > 200) { // $code = 1; // } else { // $code = 0; // } // } if ($code > 0) { $code = 1; } else { $code = 0; } $result = [ 'code' => $code, 'msg' => $msg, 'time' => request()->server('REQUEST_TIME'), 'data' => $data, ]; log_message('Api@jsonResultUnescape.result:'. ' code:'. $code ." - result:". json_encode($result), 'log', LOG_PATH . 'apilog/'); //debug模式下返回明文 if ($this->isApiDebug) { echo json_encode($result, JSON_UNESCAPED_SLASHES); //$this->result($data,$code,$msg,'json'); //api debug模式下fastcgi_finish_request无法使用 } //正式环境返回密文 else { // log_message('Api@jsonResultUnescape.result:'. $this->opensslEncrypt(json_encode($result, JSON_UNESCAPED_SLASHES), Env::get($this->aesKey)), 'log', LOG_PATH . 'apilog/'); echo $this->opensslEncrypt(json_encode($result, JSON_UNESCAPED_SLASHES), Env::get($this->aesKey)); } //为了使用fastcgi_finish_request等函数时,后续的执行能够继续生效 if ($isExit) exit; } /** * 返回封装后的 API json数据到客户端 * @access protected * @param mixed $data 要返回的数据 * @param int $code 返回的 code * @param mixed $msg 提示信息 * @param $isExit boolean 是否强制退出,默认true * @return void * */ public function jsonResultTrue($data, $code = 0, $msg = '',$isExit=true) { // if($this->device != 4){ // if ($code > 0) { // $code = 1; // } else { // $code = 0; // } // } if ($code > 0) { $code = 1; } else { $code = 0; } $result = [ 'code' => $code, 'msg' => $msg, 'time' => request()->server('REQUEST_TIME'), 'data' => $data, ]; //debug模式下返回明文 echo json_encode($result); //为了使用fastcgi_finish_request等函数时,后续的执行能够继续生效 if ($isExit) exit; } // 空方法 public function _empty() { return "url error!"; } }