<?php
/**
 * 控制器中间件: token检验，权限控制等
 * 错误代码：001
 *
 * @proDoc
 * @auth JOHN.W
 * @date 2019-08-28
 * @devUrl http://techapi.repucar.test/v1/
 * @testUrl http://test-tech-api.zgytkj.com/v1/
 * @wiki http://47.108.71.246:8090/techs.html
 * @terminalType  pc
 * @qType api web
 * @roleType plat
 * @token f7219cc66f20bf0a291a23166a2e0513_api(or user)
 * @version v1.0
 * @title 精誉美车技师端API
 * @desc 技师端APP接口 token f7219cc66f20bf0a291a23166a2e0513_api(or user)
 * @input-type application/x-www-form-urlencoded
 * @output-type application/json
 */
namespace app\modules\engineer\v1\controllers;
use app\modules\sys\log\ImLog;
use Yii;
use app\modules\logic\services\UserService;
use yii\web\Controller;
use \yii\web\Response;
use app\modules\v6\components\database\System;

class MiddleController extends Controller
{
    //接收请求过来的post 或 get的参数
    public $para = array();
    public $user_id;
    public $has_return = false;

    /**
     * 返回json
     * @param $code
     * @param string $controller
     * @param string $msg
     * @param string $info
     * @return array
     */
    protected function response($code, $msg = '', $info = "", $controller = 'middle')
    {
        $db = Yii::$app->db;
        $db->close();
        //初始化  获取配置的错误信息，优先级：控制器错误》中间控制器》默认错误》未知错误
        Yii::$app->response->format = Response::FORMAT_JSON;
        $error_arr = Yii::$app->params['returnInfo'];
        if (!isset($error_arr[$controller]) or !isset($error_arr[$controller][$code])) {
            if (isset($error_arr['middle'][$code])) {
                $result = $error_arr['middle'][$code];
            } elseif (isset($error_arr['default_error'][$code])) {
                $result = $error_arr['default_error'][$code];
            } else {
                $result = $error_arr['default_error']['not_find_error'];
            }
        } else {
            $result = $error_arr[$controller][$code];
        }
        //数据格式化
        $result['msg'] = ($msg == '' ? $result['msg'] : $msg);
        if (isset($result['info'])) {
            $result['info'] = ($info !== null ? $info : null);
        }
        Yii::$app->response->data = $result;
        return Yii::$app->response->send();
    }

    public function init()
    {
        $data = Yii::$app->request->post();

        //去除输入字符两边的空格
        $this->para = $this->trimData($data);
        if (!empty($_GET['debug']) and YII_ENV == 'dev') {
            $this->user_id = 1;
            return;
        }

        //校验公共参数和token,返回uid
        $this->checkToken();
        //频率限制
        $this->protect();
        
        Yii::$app->frontParam->setData($this->para);

    }

    //去除字符串两边的空格
    private function trimData($data)
    {
        if (!empty($data)) {
            foreach ($data as $k => $val) {
                if (is_array($val)) {
                    $data[$k] = $this->trimData($val);
                } elseif (!empty($val)) {
                    $data[$k] = trim($val);
                }
            }
        }
        return $data;
    }

    //校验公共参数和token,返回uid
    private function checkToken()
    {
        //请求需要 token参数
        if (!isset($this->para['token'])) {
            return $this->response('token_miss');
        }

        //验证 token参数值的格式
        $arr_token = explode("_", $this->para['token']);

        if (count($arr_token) != 2) {
            return $this->response('token_format_error');
        }

        //A角色类型:user-用户
        if (!isset($this->para['roleType']) || !in_array($this->para['roleType'], array('user'))) {
            return $this->response('para_error_pub');
        }

        //B终端类型:android ios pc
        if (!isset($this->para['terminalType']) || !in_array($this->para['terminalType'], array('android', 'ios', 'pc'))) {
            return $this->response('para_error_pub');
        }

        //C请求类型:api-返回json;web-返回html页面
        if (!isset($this->para['qType']) || !in_array($this->para['qType'], array('api', 'web'))) {
            return $this->response('para_error_pub');
        }


        if (!in_array($arr_token[1], array('api', 'user'))) {
            return $this->response('token_format_error');
        }
        //$this->checkSign();
        $user_id = 0;
        if ($arr_token[1] == 'api') {
            $token_server = md5('RP_TECHAPP_API');
            if ($arr_token[0] != $token_server) {
                return $this->response('token_check_false');
            }
        } elseif ($arr_token[1] == 'user') {
            $redis = Yii::$app->redis;
            $redis_key_token = $arr_token[0];
            $userInfo = $redis->get(UserService::$token_key_tech_pre . $redis_key_token);
            if (!$userInfo) {
                $redis->get($redis_key_token);
            }
            if ($userInfo) {
                $user_id = unserialize($userInfo)['user_id'];
                $this->saveUserReqInfo($user_id);

                $login_key = unserialize($userInfo)['login_key'];
                $time = explode('_', $login_key)[0];
                if ((time() - $time) > LOGIN_EXP_TIME) {
                    return $this->response('token_error');
                }
            } else {
                return $this->response('token_error');
            }
        }
        $this->user_id = $user_id;
    }

    /**
     * 存储用户app信息,存储最新的信息
     */
    private function saveUserReqInfo($uid)
    {
        $data = $this->para;
        unset($data['token']);
        if (isset($data['vin_base64'])) {
            unset($data['vin_base64']);
        }
        $data['time'] = date('Y:m:d H:i:s');
        $data['api'] = Yii::$app->request->getPathInfo();

        $data = json_encode($data, JSON_UNESCAPED_UNICODE);
        Yii::$app->redis->set('user-app:tech:equip:' . $uid, $data);
    }

    /**
     *  通过 id ，获取 path
     */
    public function get_img_path($id)
    {
        $conn = Yii::$app->db;
        $sql = "select `path` from {{%upload_file}} where id=:id";
        $cmd = $conn->createCommand($sql);
        $cmd->bindValue(':id', $id);
        $rec = $cmd->queryOne();
        $conn->close();
        if (empty($rec)) {
            return "";
        } else {
            return Yii::$app->params['imgurl'] . $rec['path'];
        }
    }

    /*
     * 防同一用户和ip 的cc攻击
     */
    public function protect()
    {
        $path = Yii::$app->request->getPathInfo();
        $redis = Yii::$app->redis;
        $ip = '';
        if (isset($_SERVER['HTTP_X_FORWARDED_FOR']) && !empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
            $ip = ($_SERVER['HTTP_X_FORWARDED_FOR']);
        }
        if (!empty($ip)) {
            $ip_path_key = "string:repucar_middle_ip_" . $ip . "_path_" . $path;
            if ($redis->incr($ip_path_key) > 5) {
                $redis->expire($ip_path_key, 1);
                return $this->response('operation_error');
            }
            $redis->expire($ip_path_key, 1);
        }

        if (!empty($this->user_id)) {
            $user_path_key = "string:repucar_middle_user_" . $this->user_id . "_path_" . $path;
            if ($redis->incr($user_path_key) > 5) {
                $redis->expire($user_path_key, 1);
                return $this->response('operation_error');
            }
            $redis->expire($user_path_key, 1);
        }
    }

    /**
     * 验证签名信息
     * @return array
     * @throws \yii\base\InvalidConfigException
     */
    public function checkSign()
    {
        $header = Yii::$app->request->getHeaders();
        $timestamp = $header->get('timestamp');
        $sign = $header->get('sign');
        if (empty($timestamp) or empty($sign)) {
            Yii::warning("数据签名错误timestamp或者sign为空,sign:{$sign},timestamp:{$timestamp}" . json_encode($this->para));
            return $this->response('para_miss', '数据签名错误');
        }
        $path = Yii::$app->request->getPathInfo();
        $path = trim($path, '/');
        $temp = explode('_', $this->para['token']);
//        $debug = '';
        $token = $temp[0];
//        $debug .= 'token:' . $token;
        $a = ord($token[0]) % 10;
//        $debug .= 'index:' . $a;
        $initStr = substr($token, $a, 8);
//        $debug .= 'iniStr:' . $initStr;
        $r = bin2hex($initStr);
//        $debug .= 'hexStr:' . $r;

        $r = hexdec($r);
//        $debug .= 'decNum:' . $r;
        $r = $r >> 22;
        $r = dechex($r);
        $key = substr($r, 0, 8);
//        $debug .= 'key:' . $key;
        $p = [
            'api' => $path,
            'count' => count($this->para),
            'method' => strtoupper(Yii::$app->request->getMethod()),
            'token' => $token,
            'timestamp' => $timestamp
        ];
        ksort($p);
        $initStr = urldecode(http_build_query($p));
        $initStr = str_replace('&', '-', $initStr);
//        $debug .= 'initStr:' . $initStr;
        $rStr = hash_hmac('sha1', $initStr, $key);
//        $debug .= 'rStr:' . $rStr;
        $sumV = 0;
        foreach (str_split($rStr) as $key => $val) {
            $ascV = ord($val);
            $sumV += $ascV * ($key % 6);
        }
//        $debug .= 'sumV:' . $sumV;
        $cv = $sumV % 16;
//        $debug .= 'CV:' . $cv;
        $char = dechex($cv);
//        $debug .= 'X:' . $char;
        $ind = (ord($rStr[0]) % 31) + 1;
//        $debug .= 'replaceIndex:' . $ind;
        $rStr[$ind] = $char;
//        ImLog::instance('checkSign.log')->Log(['debug'=>$debug,'data'=>$rStr]);
        if ($rStr != $sign) {
            Yii::warning("数据签名错误：sign不匹配,sign:{$sign},timestamp:{$timestamp},rst:{$rStr}" . json_encode($this->para));
            return $this->response('para_miss', '数据签名错误');
        }
    }

    /**
     * 用户行为日志记录，供大数据分析
     */
    protected function uaStatistics($file)
    {
        $data = $this->para;
        $data['user_id'] = $this->user_id;
        $data['timestamp'] = time();
        unset($data['token']);
        ImLog::instance($file)->Log(json_encode($data, JSON_UNESCAPED_UNICODE), false);
    }

}
