* @version : HUOSDK 8.0 */ namespace box\common\controller; use huo\controller\agent\Agent; use huo\controller\common\CommonFunc; use huo\controller\common\HuoCookie; use huo\controller\common\HuoSession; use huo\controller\game\GameCache; use huo\controller\member\Member; use huo\controller\member\MemCache; use huo\controller\request\Channel; use huo\controller\request\Crash; use huo\controller\request\Device; use huo\controller\request\Event; use huo\controller\request\Game; use huo\controller\request\Mem; use huo\controller\request\Order; use huo\controller\request\Role; use huo\logic\game\GameLogic; use huo\model\game\GameModel; use huoCheck\HuoApiV2; use huolib\constant\CommonConst; use huolib\constant\DeviceTypeConst; use huolib\constant\FormatConst; use huolib\constant\GameConst; use huolib\status\CommonStatus; use huolib\status\MemberStatus; use huolib\status\OrderStatus; use think\Config; use think\Controller; use think\Db; use think\exception\HttpResponseException; use think\exception\ValidateException; use think\Loader; use think\Response; use think\View; class V2ApiBaseController extends Controller { //token protected $token = ''; //设备类型 protected $device_type = ''; //用户 id protected $mem_id = 0; // 语言 protected $lang = 'en'; //用户 protected $user; /* 返回类型 */ protected $response_type = 'json'; //用户类型 protected $user_type; protected $allowed_device_types = ['mobile', 'android', 'iphone', 'ipad', 'web', 'pc', 'mac', 'wxapp', 'mp']; /** * @var \think\Request Request实例 */ protected $request; // 验证失败是否抛出异常 protected $fail_exception = false; // 是否批量验证 protected $batch_validate = false; protected $rq_data = []; protected $sess_config = [ 'id' => '', 'prefix' => '', 'type' => '', 'expire' => CommonConst::CONST_DAY_SECONDS ]; const USER_AGENT = 'BOX_USER_AGENT'; /** * 前置操作方法列表 * * @var array $before_action_list * @access protected */ protected $before_action_list = []; // 初始化 private function _initLang() { $_lang = $this->request->header('HS-Lang/s', 'en-us'); $this->lang = $_lang; config('default_lang', $this->lang); } /** * 获取设备类型 * * @return string */ public function getDeviceType() { if ($this->request->isWeixin()) { return DeviceTypeConst::DEVICE_TYPE_WEIXIN; } if ($this->request->isMobile()) { return DeviceTypeConst::DEVICE_TYPE_WAP; } return DeviceTypeConst::DEVICE_TYPE_PC; } /** * */ private function _initUser() { $_token = HuoCookie::getMemToken(); if (empty($_token)) { $_token = $this->request->param('token/s', ''); } $_game_data = get_val($this->rq_data, 'game', []); $_device_type = get_val($_game_data, 'mp_id'); $_wx_param['wx_app_id'] = $this->request->param('wx_app_id/s', ''); if (!empty($_wx_param['wx_app_id'])) { $this->device_type = $_wx_param['wx_app_id']; } elseif (!empty($_device_type)) { $this->device_type = $_device_type; } else { $this->device_type = DeviceTypeConst::DEVICE_TYPE_MP; } $_mem_id = $this->getMemIdByToken($_token, $this->device_type); if (!empty($_mem_id)) { $this->mem_id = $_mem_id; } if (APP_DEBUG) { /* 调试环境直接登陆 */ $_mem_id = $this->request->param('debug_mem_id/s', 0); if (!empty($_mem_id)) { $this->mem_id = $_mem_id; } } } protected function _initialize() { $this->response_type = $this->request->param('format/s', FormatConst::FORMAT_HTML); if (FormatConst::FORMAT_HTML == $this->response_type) { $this->_initializeView(); $_site = $this->request->domain(); $_agent_id = (new Agent())->getAgentIdBySite($_site); if (empty($_agent_id)) { $_agent_id = $this->request->request('agent_id', 0); } if (empty($_agent_id)) { $_agent_id = (new HuoSession($this->mem_id))->getAgentId(); } else { (new HuoSession($this->mem_id))->setAgentId($_agent_id); } $siteInfo = cmf_get_site_info($_agent_id); View::share('site_info', $siteInfo); View::share('agent_id', $_agent_id); View::share('agent_site', $_site); View::share('h5i_site', H5ISITE); View::share('static_site', STATICSITE); } Config::set('default_return_type', $this->response_type); $this->_initLang(); $this->checkParam(); // 用户验证初始化 $this->_initUser(); /* 记录请求数据 */ if ($this->request->isOptions()) { $this->success(); } } /** * 前置操作 * * @access protected * * @param string $method 前置操作方法名 * @param array $options 调用参数 ['only'=>[...]] 或者['except'=>[...]] */ protected function beforeAction($method, $options = []) { if (isset($options['only'])) { if (is_string($options['only'])) { $options['only'] = explode(',', $options['only']); } if (!in_array($this->request->action(), $options['only'])) { return; } } elseif (isset($options['except'])) { if (is_string($options['except'])) { $options['except'] = explode(',', $options['except']); } if (in_array($this->request->action(), $options['except'])) { return; } } call_user_func([$this, $method]); } /** * @param $token * @param $device_type * * @return array|bool|false|\PDOStatement|string|\think\Model */ public function getMemIdByToken($token, $device_type) { if (empty($token)) { return 0; } $_path = 'mp/wx/login'; $_is_up = $_path == request()->path() ? true : false; $_mem_id = (new Member())->getMemIdByToken($token, $device_type, $_is_up); if (empty($_mem_id)) { return 0; } return $_mem_id; } /** * 返回数据 * * @param array $data */ protected function returnData($data = []) { $_msg = $data['msg']; $_code = $data['code']; $_data = $data['data']; if (CommonStatus::NO_ERROR != $_code) { $this->error($_msg, $_data, $_code); } $this->success($_msg, $_data, $_code); } /** * 操作错误跳转的快捷方法 * * @access protected * * @param mixed $msg 提示信息,若要指定错误码,可以传数组,格式为['code'=>您的错误码,'msg'=>'您的错误消息'] * @param mixed $data 返回的数据 * @param int $code * * @param array $header 发送的Header信息 * * @param null $url * @param int $wait * * @return void */ protected function error($msg = '', $data = '', $code = 400, array $header = [], $url = null, $wait = 3) { $type = $this->getResponseType(); if ('html' == $type) { parent::error($msg, $data, $code, $header, $url, $wait); } if (empty($data)) { $data = null; } $result = [ 'code' => $code, 'msg' => $msg, 'data' => $data, ]; \think\Log::write($result, 'debug', true); $header['Access-Control-Allow-Origin'] = '*'; $header['Access-Control-Allow-Headers'] = 'X-Requested-With,Content-Type,HS-Device-Type,HS-Token,HS-Lang'; $header['Access-Control-Allow-Methods'] = 'GET,POST,PATCH,PUT,DELETE,OPTIONS'; $response = Response::create($result, $type)->header($header); throw new HttpResponseException($response); } /** * 获取当前的response 输出类型 * * @access protected * @return string */ protected function getResponseType() { return $this->response_type; } /** * 获取当前登录用户的id * * @return int */ public function getMemId() { if (empty($this->mem_id)) { $this->error(lang('NO_LOGIN'), '', 1002); } // $this->mem_id = rand(1, 10); return $this->mem_id; } /** * 设置验证失败后是否抛出异常 * * @access protected * * @param bool $fail 是否抛出异常 * * @return $this */ protected function validateFailException($fail = true) { $this->fail_exception = $fail; return $this; } /** * 验证数据 * * @access protected * * @param array $data 数据 * @param string|array $validate 验证器名或者验证规则数组 * @param array $message 提示信息 * @param bool $batch 是否批量验证 * @param mixed $callback 回调方法(闭包) * * @return array|string|true * @throws ValidateException */ protected function validate($data, $validate, $message = [], $batch = false, $callback = null) { if (is_array($validate)) { $_valid_class = Loader::validate(); $_valid_class->rule($validate); } else { if (strpos($validate, '.')) { // 支持场景 list($validate, $scene) = explode('.', $validate); } $_valid_class = Loader::validate($validate); if (!empty($scene)) { $_valid_class->scene($scene); } } // 是否批量验证 if ($batch || $this->batch_validate) { $_valid_class->batch(true); } if (is_array($message)) { $_valid_class->message($message); } if ($callback && is_callable($callback)) { call_user_func_array($callback, [$_valid_class, &$data]); } if (!$_valid_class->check($data)) { if ($this->fail_exception) { throw new ValidateException($_valid_class->getError()); } else { return $_valid_class->getError(); } } else { return true; } } /** * 操作成功跳转的快捷方法 * * @access protected * * @param mixed $msg 提示信息 * @param mixed $data 返回的数据 * @param array $header 发送的Header信息 * * @param int $code 返回码 * * @return void */ protected function success($msg = '', $data = '', $code = 200, array $header = [], $url = null, $wait = 3) { $type = $this->getResponseType(); if ('html' == $type) { parent::success($msg, $data, $code, $header, $url, $wait); } if (empty($data)) { $data = null; } $result = [ 'code' => $code, 'msg' => $msg, 'data' => $data, ]; $header['Access-Control-Allow-Origin'] = '*'; $header['Access-Control-Allow-Headers'] = 'X-Requested-With,Content-Type,HS-Device-Type,HS-Token,HS-Lang'; $header['Access-Control-Allow-Methods'] = 'GET,POST,PATCH,PUT,DELETE,OPTIONS'; $response = Response::create($result, $type)->header($header); throw new HttpResponseException($response); } protected function checkParam() { $_is_json = false; $_params = $_REQUEST; $this->rq_data = $this->getParam($_params, $_is_json); if (empty($this->rq_data)) { $this->rq_data = []; } // $this->checkSign(); // $this->checkTime(); } /** * 获取请求参数 * * @param mixed $param * @param bool $is_json * * @return bool|mixed|string */ public function getParam($param, $is_json = false) { if (empty($param)) { return ''; } if ($is_json) { $_param = json_decode($param, true); if (JSON_ERROR_NONE != json_last_error()) { return false; } } else { foreach ($param as $_k => $_v) { if (strpos($_k, '-')) { list($_k1, $_k2) = explode('-', $_k); if (is_array($_k2)) { return false; } $_param[$_k1][$_k2] = $_v; } else { $_param[$_k] = $_v; } } } if (empty($_param)) { return false; } return $_param; } /** * @return bool|string */ protected function getKey() { switch ($this->device_type) { case DeviceTypeConst::DEVICE_TYPE_IOSSDK: case DeviceTypeConst::DEVICE_TYPE_ANDSDK: case DeviceTypeConst::DEVICE_TYPE_IOSAPP: case DeviceTypeConst::DEVICE_TYPE_IOSH5APP: case DeviceTypeConst::DEVICE_TYPE_ANDAPP: case DeviceTypeConst::DEVICE_TYPE_ANDH5APP: $_key = $this->getAppKey(); break; case DeviceTypeConst::DEVICE_TYPE_IOSAPPLESDK: $_key = $this->getAppKey(true); break; default: $_key = $this->token; } return $_key; } protected function getAppKey($is_iosapplesdk = false) { $_gl_class = new GameLogic(); $_app_id = get_val($this->rq_data, 'app_id'); if (empty($_app_id) && false == $is_iosapplesdk) { return false; } $_client_id = get_val($this->rq_data, 'client_id'); if (empty($_client_id) && false == $is_iosapplesdk) { return false; } $_client_key = $_gl_class->getVersionKey($_app_id, $_client_id); if (empty($_client_key) && false == $is_iosapplesdk) { return false; } $_public_key_file = GLOBAL_CONF_PATH.'extra/key/rsa_public_key.pem'; if (!file_exists($_public_key_file)) { return false; } else { $_key_content = file_get_contents($_public_key_file); } // 以下为了初始化公钥,保证公钥不管是带格式还是不带格式都可以通过验证。 $_key_content = str_replace("-----BEGIN PUBLIC KEY-----", "", $_key_content); $_key_content = str_replace("-----END PUBLIC KEY-----", "", $_key_content); $_key_content = str_replace("\r\n", "", $_key_content); $_key_content = str_replace("\n", "", $_key_content); // Log::error('------pubic_key-----'.$_key_content); if (empty($_key_content)) { return false; } if (true == $is_iosapplesdk) { return $_key_content; } return $_client_key.'&'.$_key_content; } /** * 校验签名 */ protected function checkSign() { $_ignore_sign = [ //忽略签名的接口数组 'v8/app/getPluginList' ]; $_path = $this->request->path(); if (in_array($_path, $_ignore_sign)) { return true; } if (empty($this->device_type) || FormatConst::FORMAT_HTML == $this->response_type) { return true; } $_haV2 = new HuoApiV2(); //获取client_key $_key = $this->getKey(); $_haV2->setKey($_key); $_rs = $_haV2->check($_path, $this->rq_data, $this->request->method()); if (true != $_rs) { $this->error(OrderStatus::getMsg(OrderStatus::SIGN_ERROR), '', OrderStatus::SIGN_ERROR); } } /** * 校验请求事件 */ protected function checkTime() { $_ts = get_val($this->rq_data, 'ts', 0); if (abs($_ts - time()) < 5) { $this->error('time is error'); } } /** * 设置玩家入参 * * @param bool $is_reg * * @return Mem */ public function setMemData($is_reg = false) { $_mem = new Mem(); $_mem->setData(get_val($this->rq_data, 'mem')); $_app_id = get_val($this->rq_data, 'app_id', 0); $_mg_mem_id = (new HuoSession($this->mem_id, $_app_id))->getMgMemId(); if (false == $is_reg) { $_mem_id = $this->mem_id; if (!empty($_mem_id)) { $_mem->setMemId($_mem_id); $_mem->setMgMemId($_mg_mem_id); $_mem_data = MemCache::ins()->getInfoById($_mem_id); $_mem->setAgentId($_mem_data['agent_id']); } } return $_mem; } /** * @return Order */ public function setOrderData() { $_order = new Order(); $_order->setData(get_val($this->rq_data, 'order', [])); return $_order; } /** * @return Role */ public function setRoleData() { $_role = new Role(); $_role->setData(get_val($this->rq_data, 'role', [])); return $_role; } /** * @return Crash */ public function setCrashData() { $_crash = new Crash(); $_crash->setData(get_val($this->rq_data, 'crash', [])); return $_crash; } /** * @return Device */ public function setDeviceData() { $_device = new Device(); $_device->setData(get_val($this->rq_data, 'device', [])); $_device->setDeviceType($this->device_type); $_device->setIp($this->request->ip()); if (isset($this->rq_data['open_cnt'])) { $_device->setOpenCnt($this->rq_data['open_cnt']); // (new HuoSession($this->mem_id))->setOpenCnt($this->rq_data['open_cnt']); } else { // $_open_cnt = (new HuoSession($this->mem_id))->getOpenCnt(); if (empty($_open_cnt)) { $_open_cnt = 0; } $_device->setOpenCnt($_open_cnt); } $_device->setFromDevice($this->device_type); switch ($this->device_type) { case DeviceTypeConst::DEVICE_TYPE_IOSSDK: case DeviceTypeConst::DEVICE_TYPE_IOSAPP: case DeviceTypeConst::DEVICE_TYPE_IOSH5APP: $_device->setFrom(GameConst::GAME_IOS); break; case DeviceTypeConst::DEVICE_TYPE_IOSAPPLESDK: $_device->setFrom(GameConst::GAME_IOS_SWITCH); break; case DeviceTypeConst::DEVICE_TYPE_ANDSDK: case DeviceTypeConst::DEVICE_TYPE_ANDAPP: case DeviceTypeConst::DEVICE_TYPE_ANDH5APP: $_device->setFrom(GameConst::GAME_ANDROID); break; case DeviceTypeConst::DEVICE_TYPE_SAFARI: case DeviceTypeConst::DEVICE_TYPE_ANDBROWSER: case DeviceTypeConst::DEVICE_TYPE_WEIXIN: case DeviceTypeConst::DEVICE_TYPE_PC: case DeviceTypeConst::DEVICE_TYPE_WAP: $_device->setFrom(GameConst::GAME_H5); break; default: $_device->setFrom(GameConst::GAME_H5); } return $_device; } /** * @return Game */ public function setGameData() { $_game = new Game(); $_game->setData(get_val($this->rq_data, 'game', [])); switch ($this->device_type) { case DeviceTypeConst::DEVICE_TYPE_IOSAPPLESDK: $_game->setAppleId(get_val($this->rq_data, 'apple_id', 0)); $_app_id = (new GameModel())->getAppIdByAppleId($_game->getAppleId()); if (empty($_app_id)) { $this->error('AppleId 错误'); } $_game->setHAppId($_app_id); $this->rq_data['app_id'] = $_app_id; break; case DeviceTypeConst::DEVICE_TYPE_IOSSDK: break; case DeviceTypeConst::DEVICE_TYPE_IOSAPP: case DeviceTypeConst::DEVICE_TYPE_IOSH5APP: case DeviceTypeConst::DEVICE_TYPE_SAFARI: default: $_game->setHAppId(get_val($this->rq_data, 'app_id', 0)); } if ($_game->getHAppId() !== 0) { $_game_data = (new GameCache())->getInfoByAppId($_game->getHAppId()); if (empty($_game_data)) { $this->error('游戏参数错误'); } } return $_game; } /** * @return Channel * */ public function setChannelData() { $_channel = new Channel(); $_channel->setData(get_val($this->rq_data, 'agent', [])); $_device_data = get_val($this->rq_data, 'device', []); $_device_id = get_val($_device_data, 'device_id', ''); /* 计算AgentId */ $_agent_id = Commonfunc::getAgentId( 0, $_channel->getAgentGame(), $_channel->getAgentId(), $_device_id ); $_channel->setAgentId($_agent_id); return $_channel; } /** * @return Event */ public function setEventData() { $_event = new Event(); $_event->setData(get_val($this->rq_data, 'event', [])); return $_event; } public function _initializeView() { $cmfThemePath = config('cmf_theme_path'); $cmfDefaultTheme = cmf_get_current_theme(); $themePath = "{$cmfThemePath}{$cmfDefaultTheme}"; $root = cmf_get_root(); //使cdn设置生效 $cdnSettings = cmf_get_option('cdn_settings'); if (empty($cdnSettings['cdn_static_root'])) { $viewReplaceStr = [ '__ROOT__' => $root, '__TMPL__' => "{$root}/{$themePath}", '__STATIC__' => "{$root}/static", '__WEB_ROOT__' => $root ]; } else { $cdnStaticRoot = rtrim($cdnSettings['cdn_static_root'], '/'); $viewReplaceStr = [ '__ROOT__' => $root, '__TMPL__' => "{$cdnStaticRoot}/{$themePath}", '__STATIC__' => "{$cdnStaticRoot}/static", '__WEB_ROOT__' => $cdnStaticRoot ]; } $viewReplaceStr = array_merge(config('view_replace_str'), $viewReplaceStr); config('template.view_base', "{$themePath}/"); config('view_replace_str', $viewReplaceStr); $themeErrorTmpl = "{$themePath}/error.html"; if (file_exists_case($themeErrorTmpl)) { config('dispatch_error_tmpl', $themeErrorTmpl); } $themeSuccessTmpl = "{$themePath}/success.html"; if (file_exists_case($themeSuccessTmpl)) { config('dispatch_success_tmpl', $themeSuccessTmpl); } } /** * 加载模板输出 * * @access protected * * @param string $template 模板文件名 * @param array $vars 模板输出变量 * @param array $replace 模板替换 * @param array $config 模板参数 * * @return mixed */ protected function fetch($template = '', $vars = [], $replace = [], $config = []) { $template = $this->parseTemplate($template); $more = $this->getThemeFileMore($template); $this->assign('theme_vars', $more['vars']); $this->assign('theme_widgets', $more['widgets']); return parent::fetch($template, $vars, $replace, $config); } /** * 自动定位模板文件 * * @access private * * @param string $template 模板文件规则 * * @return string */ private function parseTemplate($template) { // 分析模板文件规则 $request = $this->request; // 获取视图根目录 if (strpos($template, '@')) { // 跨模块调用 list($module, $template) = explode('@', $template); } $viewBase = config('template.view_base'); if ($viewBase) { // 基础视图目录 $module = isset($module) ? $module : $request->module(); $path = $viewBase.($module ? $module.DS : ''); } else { $path = isset($module) ? APP_PATH.$module.DS.'view'.DS : config('template.view_path'); } $depr = config('template.view_depr'); if (0 !== strpos($template, '/')) { $template = str_replace(['/', ':'], $depr, $template); $controller = cmf_parse_name($request->controller()); if ($controller) { if ('' == $template) { // 如果模板文件名为空 按照默认规则定位 $template = str_replace('.', DS, $controller).$depr.$request->action(); } elseif (false === strpos($template, $depr)) { $template = str_replace('.', DS, $controller).$depr.$template; } } } else { $template = str_replace(['/', ':'], $depr, substr($template, 1)); } return $path.ltrim($template, '/').'.'.ltrim(config('template.view_suffix'), '.'); } /** * 获取模板文件变量 * * @param string $file * @param string $theme * * @return array */ private function getThemeFileMore($file, $theme = "") { //TODO 增加缓存 $theme = empty($theme) ? cmf_get_current_theme() : $theme; $themePath = config('cmf_theme_path'); $file = str_replace('\\', '/', $file); $file = str_replace('//', '/', $file); $file = str_replace(['.html', '.php', $themePath.$theme."/"], '', $file); $files = Db::name('theme_file')->field('more')->where(['theme' => $theme])->where( function ($query) use ($file) { $query->where(['is_public' => 1])->whereOr(['file' => $file]); } )->select(); $vars = []; $widgets = []; foreach ($files as $file) { $oldMore = json_decode($file['more'], true); if (!empty($oldMore['vars'])) { foreach ($oldMore['vars'] as $varName => $var) { $vars[$varName] = $var['value']; } } if (!empty($oldMore['widgets'])) { foreach ($oldMore['widgets'] as $widgetName => $widget) { $widgetVars = []; if (!empty($widget['vars'])) { foreach ($widget['vars'] as $varName => $var) { $widgetVars[$varName] = $var['value']; } } $widget['vars'] = $widgetVars; $widgets[$widgetName] = $widget; } } } return ['vars' => $vars, 'widgets' => $widgets]; } /** * 检查是否登陆 * * @param int $mem_id */ public function checkLogin($mem_id = 0) { $_mem_id = $mem_id; if (empty($_mem_id)) { $_mem_id = $this->mem_id; } if (empty($_mem_id)) { $_code = MemberStatus::LOGIN_IS_OUT; $this->error(lang(MemberStatus::getMsg($_code)), [], $_code); } } }