// +---------------------------------------------------------------------- declare (strict_types=1); namespace app\api\controller; use app\common\crypto\Crypto; use think\response\Json; use cores\BaseController; use app\api\model\User as UserModel; use app\api\model\Store as StoreModel; use app\api\model\Merchant as MerchantModel; use app\api\service\User as UserService; use cores\exception\BaseException; use app\common\service\SybOil; /** * API控制器基类 * Class Controller * @package app\store\controller */ class Controller extends BaseController { // 当前商城ID protected $storeId; protected $latitude; protected $longitude; protected $province; /** * API基类初始化 * @throws BaseException */ public function initialize() { // 当前商城id $this->storeId = $this->getStoreId(); $this->latitude = input('get.latitude'); $this->longitude = input('get.longitude'); $res = $this->httpCurl('https://apis.map.qq.com/ws/geocoder/v1/',['location'=>$this->latitude.','.$this->longitude,'key'=>'ETFBZ-F4XCW-YOQRI-RWBQM-BAZIQ-2VFS5']); if(isset($res['status']) && $res['status'] == 0){ $this->province = $res['result']['address_component']['province']; } // 验证当前商城状态 $this->checkStore(); // 验证当前客户端状态 $this->checkClient(); } /** * 获取当前商城ID * @return int|null * @throws BaseException */ protected function getStoreId(): ?int { $storeId = getStoreId(); // app/api/common.php empty($storeId) && throwError('缺少必要的参数:storeId'); return $storeId; } /** * 验证当前商城状态 * @return void * @throws BaseException */ private function checkStore(): void { // 获取当前商城信息 $store = StoreModel::detail($this->storeId); if (empty($store)) { throwError('当前商城信息不存在'); } if ($store['is_recycle'] || $store['is_delete']) { throwError('当前商城已删除'); } } /** * 验证当前客户端是否允许访问 * @throws BaseException */ private function checkClient() { $client = getPlatform(); $settingClass = [ 'H5' => [\app\api\model\h5\Setting::class, 'checkStatus', 'H5端'] ]; if (!isset($settingClass[$client])) { return; } $status = call_user_func([$settingClass[$client][0], $settingClass[$client][1]]); $status === false && throwError('很抱歉,当前' . $settingClass[$client][2] . '端暂不支持访问'); } /** * 获取当前用户信息 * @param bool $isForce 强制验证登录 * @return UserModel|bool|null * @throws BaseException */ protected function getLoginUser(bool $isForce = true) { return UserService::getCurrentLoginUser($isForce); } /** * 返回封装后的 API 数据到客户端 * @param string|null $code 状态码 * @param string $message * @param string|null $errCode * @param string|null $errMessage * @param array $data * @return Json */ protected function oilRenderJson(string $code = null, string $message = '', string $errCode = null, string $errMessage = null, array $data = []): Json { return json(compact('code', 'message', 'errCode', 'errMessage', 'data')); } /** * 返回操作成功json * @param array|string $data * @param string $code * @param string $message * @return Json */ protected function oilRenderSuccess($data = [], string $code = '', string $message = ''): Json { if(empty($code)){ $code = '00'; } if(empty($message)){ $message = config('api_status.response_code')[$code] ?? ''; } if (is_string($data)) { $message = $data; $data = []; } return $this->oilRenderJson($code, $message, null, null, $data); } /** * 返回操作失败json * @param string $code * @param string $errCode * @param string $message * @param string $errMessage * @return Json */ protected function oilRenderError(string $code, string $errCode, string $message = '', string $errMessage = ''): Json { if(empty($message)){ $message = config('api_status.response_code')[$code] ?? ''; } if(empty($errMessage)){ $errMessage = config('api_status.error_code')[$errCode] ?? ''; } return $this->oilRenderJson($code, $message, $errCode, $errMessage); } /** * 返回封装后的 API 数据到客户端 * @param int|null $status 状态码 * @param string $message * @param array $data * @return Json */ protected function renderJson(int $status = null, string $message = '', array $data = []): Json { return json(compact('status', 'message', 'data')); } /** * 返回操作成功json * @param array|string $data * @param string $message * @return Json */ protected function renderSuccess($data = [], string $message = 'success'): Json { if (is_string($data)) { $message = $data; $data = []; } return $this->renderJson(config('status.success'), $message, $data); } /** * 返回操作失败json * @param string $message * @param array $data * @return Json */ protected function renderError(string $message = 'error', array $data = []): Json { return $this->renderJson(config('status.error'), $message, $data); } /** * 获取post数据 (数组) * @param $key * @return mixed */ protected function postData($key = null) { return $this->request->post(is_null($key) ? '' : $key . '/a'); } /** * 获取post数据 (数组) * @param string $key * @return mixed */ protected function postForm(string $key = 'form') { return $this->postData($key); } //创建sign public function makeSign($data,$appKey) { ksort($data); $string = $this->toUrlParams($data); $string = $string . "&key=" . $appKey; $string = md5($string); $result = strtoupper($string); return $result; } //检验sign是否正确 public function verifySign($data) { //check sign if (!isset($data['sign']) || !$data['sign']) { return ['code'=>1,'msg'=>'发送的数据签名不存在']; } //check sign if (!isset($data['appid']) || !$data['appid']) { return ['code'=>1,'msg'=>'发送的应用参数1不存在']; } //check timestamp if (!isset($data['timestamp']) || !$data['timestamp']) { return ['code'=>1,'msg'=>'缺少时间戳参数']; } // 验证请求, 10分钟失效 if (time() - $data['timestamp'] > 600) { return ['code'=>1,'msg'=>'验证超时, 请重新发送请求']; } $merchant = MerchantModel::detail(['appId'=>$data['appid']]); if (empty($merchant['merchant_id'])) { return ['code'=>1,'msg'=>'appid错误']; } $clientSign = $data['sign']; unset($data['sign']); $serverSign = $this->makeSign($data,$merchant['appKey']); if ($clientSign == $serverSign) { return ['code'=>0,'msg'=>'签名验证通过','merchant_id'=>$merchant['merchant_id'],'callback'=>$merchant['callback']]; } else { return ['code'=>1,'msg'=>'签名不合法']; } } //生成url字符串 private function toUrlParams($values){ $buff = ""; foreach ($values as $k => $v) { //&& $v != "" if($k != "sign" && !is_array($v)){ $buff .= $k . "=" . $v . "&"; } } $buff = trim($buff, "&"); return $buff; } /** * http请求 * * @param $url //请求地址 * @param $params //链接后拼接的参数数组 * @param $method //get/post * @param $header //请求头数组 * @return array */ function httpCurl($url, $params, $method = 'GET', $header = array(), $multi = false): array { date_default_timezone_set('PRC'); //设置默认时区 $opts = array( //请求参数 CURLOPT_TIMEOUT => 30, CURLOPT_RETURNTRANSFER => 1, //将curl_exec()获取的信息以文件流的形式返回 CURLOPT_SSL_VERIFYPEER => false, //不对认证证书来源的检查 CURLOPT_SSL_VERIFYHOST => false, //不从证书中检查SSL加密算法是否存在 CURLOPT_HTTPHEADER => $header, //Content-Type类型 CURLOPT_COOKIESESSION => true, CURLOPT_FOLLOWLOCATION => 1, // 使用自动跳转 CURLOPT_COOKIE => session_name() . '=' . session_id(), ); /* 根据请求类型设置特定参数 */ switch (strtoupper($method)) { case 'GET': // 链接后拼接参数 如xxx.com?key = value & key2=value $opts[CURLOPT_URL] = $url . '?' . http_build_query($params);//将数组变成key=value&key2=value break; case 'POST': //判断是否传输文件 $params = $multi ? $params : http_build_query($params); //同上 $opts[CURLOPT_URL] = $url; $opts[CURLOPT_POST] = 1; $opts[CURLOPT_POSTFIELDS] = $params; break; default: throw new BaseException(['message' => '不支持的请求方式!']); } /* 初始化并执行curl请求 */ $ch = curl_init(); curl_setopt_array($ch, $opts); // 数组形式设置URL和其他参数 $data = curl_exec($ch); // URL抓取并把它传递给浏览器 $error = curl_error($ch); curl_close($ch); if ($error){ $data = ['code'=>2,'message'=>$error]; }else { $data = json_decode($data, true); } return $data; } public function sendRequest($company, $url, $businessParams = [], $appendHeaders = [], $file = null) { $crypto = new Crypto($company); // 签名并组合header,并加密数据 $this->saveLog(json_encode($businessParams,320)); $requestData = $crypto->prepareRequest($appendHeaders, $businessParams); $businessParams = $requestData['encryptData']; // 构建默认的公共参数头 $headers = []; foreach ($requestData['header'] as $k=>$val){ $headers[] = $k.': '.$val; } $this->saveLog(json_encode($requestData['header'],320)); $this->saveLog(json_encode($businessParams,320)); // 使用 cURL 发送请求 $ch = curl_init(); // 设置请求 URL curl_setopt($ch, CURLOPT_URL, $url); // 设置请求方式为 POST curl_setopt($ch, CURLOPT_POST, true); // 返回结果而不是输出 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // 包含响应头在输出中 curl_setopt($ch, CURLOPT_HEADER, true); $headers[] = 'Charset: UTF-8'; if ($file) { // 如果有文件上传,设置 multipart/form-data 表单 $filePath = $file->getPathname(); $fileName = $file->getOriginalName(); $fileMime = $file->getMimeType(); $postFields = [ 'file' => new \CURLFile($filePath, $fileMime, $fileName), 'businessParams' => json_encode($businessParams, JSON_UNESCAPED_UNICODE), ]; curl_setopt($ch, CURLOPT_POSTFIELDS, $postFields); } else { // 如果没有文件上传,直接发送业务参数的 JSON 数据 $postFields = json_encode($businessParams, JSON_UNESCAPED_UNICODE); $headers[] = 'Content-Type: application/json'; curl_setopt($ch, CURLOPT_POSTFIELDS, $postFields); } curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); // 执行请求 $response = curl_exec($ch); // 检查是否有错误 if (curl_errno($ch)) { $error_msg = curl_error($ch); curl_close($ch); return ['status' => '01', 'message' => $error_msg]; } // 获取响应头大小 $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); // 分离响应头和响应体 $headerString = substr($response, 0, $header_size); $bodyString = substr($response, $header_size); // 关闭 cURL 句柄 curl_close($ch); // 解析响应头为数组 $headerArray = []; foreach (explode("\r\n", $headerString) as $header) { if (trim($header) !== '' && strpos($header, ':') !== false) { list($key, $value) = explode(': ', $header, 2); $headerArray[strtolower(trim($key))] = trim($value); } } $res = $crypto->processResponse(['header'=>$headerArray,'encryptData'=>json_decode($bodyString, true)]); if(!$res){ return ['status' => '01', 'message' => '解密失败']; } // 返回结果 return $res; } /** * xml/json数据的POST请求 * * @param $url //请求地址 * @param $xmlData //xml 或 json数据 */ public function SendData($url,$xmlData) { $curl = curl_init(); $param[CURLOPT_URL] = $url; $param[CURLOPT_HEADER] = false; $param[CURLOPT_RETURNTRANSFER] = true; $param[CURLOPT_FOLLOWLOCATION] = true; $param[CURLOPT_POST] = true; $param[CURLOPT_POSTFIELDS] = $xmlData; $param[CURLOPT_SSL_VERIFYPEER] = false; $param[CURLOPT_SSL_VERIFYHOST] = false; curl_setopt_array($curl,$param); //传参数 $data = curl_exec($curl); //执行命令 curl_close($curl); return $data; } // 循环创建目录 public function mk_dir($dir, $mode = 0777) { if (is_dir($dir) || @mkdir($dir,$mode)) return true; if (!$this->mk_dir(dirname($dir),$mode)) return false; return @mkdir($dir,$mode); } /** 记录日志 * @param $data */ public function saveLog($data){ $SybOil = new SybOil(); $SybOil->saveLogStore($data); } /** 插入订单号 * @param $order_no */ public function insertNo($order_no){ return $order_no; //return (int)$order_no-100000; } /** 获取订单号 * @param $order_no */ public function getOrderNo($order_no){ return $order_no; //return (int)$order_no+100000; } }