|
@@ -0,0 +1,330 @@
|
|
|
|
|
+<?php
|
|
|
|
|
+
|
|
|
|
|
+namespace app\extra\tools;
|
|
|
|
|
+
|
|
|
|
|
+use app\extra\basic\Http;
|
|
|
|
|
+use think\exception\ValidateException;
|
|
|
|
|
+
|
|
|
|
|
+class JingLinExtend
|
|
|
|
|
+{
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 加密方式常量
|
|
|
|
|
+ */
|
|
|
|
|
+ const ENCRYPT_NONE = 'NONE';
|
|
|
|
|
+ const ENCRYPT_3DES_RSA = '3DES_RSA';
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 签名算法
|
|
|
|
|
+ */
|
|
|
|
|
+ const SIGN_ALGO = OPENSSL_ALGO_SHA256;
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * @var string 商户私钥路径或内容
|
|
|
|
|
+ */
|
|
|
|
|
+ private $privateKey;
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * @var string 平台公钥路径或内容
|
|
|
|
|
+ */
|
|
|
|
|
+ private $publicKey;
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * @var string 3DES密钥(自动生成或外部传入)
|
|
|
|
|
+ */
|
|
|
|
|
+ private $secretKey;
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * @var string 加密方式
|
|
|
|
|
+ */
|
|
|
|
|
+ private $encryptType = self::ENCRYPT_NONE;
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 构造函数
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param string $privateKey 商户私钥(文件路径或内容)
|
|
|
|
|
+ * @param string $publicKey 平台公钥(文件路径或内容)
|
|
|
|
|
+ * @param string $encryptType 加密方式 NONE/3DES_RSA
|
|
|
|
|
+ */
|
|
|
|
|
+ public function __construct($privateKey, $publicKey, $encryptType = self::ENCRYPT_NONE)
|
|
|
|
|
+ {
|
|
|
|
|
+ $this->privateKey = $this->loadKey($privateKey, 'private');
|
|
|
|
|
+ $this->publicKey = $this->loadKey($publicKey, 'public');
|
|
|
|
|
+ $this->encryptType = $encryptType;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 加载密钥
|
|
|
|
|
+ */
|
|
|
|
|
+ private function loadKey($key, $type)
|
|
|
|
|
+ {
|
|
|
|
|
+ // 如果是文件路径
|
|
|
|
|
+ if (is_file($key)) {
|
|
|
|
|
+ $key = file_get_contents($key);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if ($type === 'private') {
|
|
|
|
|
+ return openssl_pkey_get_private($key);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ return openssl_pkey_get_public($key);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 构建请求(包含签名和加密)
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param array $bizContent 业务参数(明文)
|
|
|
|
|
+ * @param array $extraParams 额外的公共参数(会自动过滤jrgw开头的)
|
|
|
|
|
+ * @return array 完整的请求参数
|
|
|
|
|
+ */
|
|
|
|
|
+ public function buildRequest($bizContent, $extraParams = [])
|
|
|
|
|
+ {
|
|
|
|
|
+ $requestParams = [];
|
|
|
|
|
+
|
|
|
|
|
+ // 1. 处理业务内容
|
|
|
|
|
+ if ($this->encryptType === self::ENCRYPT_3DES_RSA) {
|
|
|
|
|
+ // 生成随机3DES密钥
|
|
|
|
|
+ $this->secretKey = $this->generate3DesKey();
|
|
|
|
|
+
|
|
|
|
|
+ // 加密业务参数
|
|
|
|
|
+ $encryptedBiz = $this->encrypt3Des(json_encode($bizContent), $this->secretKey);
|
|
|
|
|
+
|
|
|
|
|
+ // 使用RSA加密3DES密钥
|
|
|
|
|
+ $encryptedKey = $this->encryptRsa($this->secretKey);
|
|
|
|
|
+
|
|
|
|
|
+ $requestParams['encrypt'] = $encryptedKey;
|
|
|
|
|
+ $requestParams['biz-content'] = $encryptedBiz;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 明文模式
|
|
|
|
|
+ $requestParams['biz-content'] = json_encode($bizContent);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 合并额外参数(只保留以jrgw开头的)
|
|
|
|
|
+ foreach ($extraParams as $key => $value) {
|
|
|
|
|
+ if (strpos($key, 'jrgw') === 0 && $value !== '' && $value !== null) {
|
|
|
|
|
+ $requestParams[$key] = $value;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 生成签名
|
|
|
|
|
+ $sign = $this->generateSign($requestParams);
|
|
|
|
|
+
|
|
|
|
|
+ return $sign;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 解析响应(包含验签和解密)
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param array $responseParams 响应参数
|
|
|
|
|
+ * @return array 解密后的业务参数
|
|
|
|
|
+ * @throws Exception
|
|
|
|
|
+ */
|
|
|
|
|
+ public function parseResponse($responseParams)
|
|
|
|
|
+ {
|
|
|
|
|
+ // 1. 验签
|
|
|
|
|
+ $sign = isset($responseParams['sign']) ? $responseParams['sign'] : '';
|
|
|
|
|
+ if (!$this->verifySign($responseParams, $sign)) {
|
|
|
|
|
+ throw new Exception('响应签名验证失败');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 解密
|
|
|
|
|
+ if ($this->encryptType === self::ENCRYPT_3DES_RSA) {
|
|
|
|
|
+ if (!isset($responseParams['encrypt']) || !isset($responseParams['biz-content'])) {
|
|
|
|
|
+ throw new Exception('加密响应缺少必要参数');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 使用私钥解密3DES密钥
|
|
|
|
|
+ $secretKey = $this->decryptRsa($responseParams['encrypt']);
|
|
|
|
|
+
|
|
|
|
|
+ // 使用3DES解密业务内容
|
|
|
|
|
+ $bizContent = $this->decrypt3Des($responseParams['biz-content'], $secretKey);
|
|
|
|
|
+
|
|
|
|
|
+ return json_decode($bizContent, true);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 明文模式
|
|
|
|
|
+ if (!isset($responseParams['biz-content'])) {
|
|
|
|
|
+ throw new Exception('响应缺少biz-content参数');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return json_decode($responseParams['biz-content'], true);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 生成签名
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param array $params 所有参数(包含biz-content和jrgw开头的参数)
|
|
|
|
|
+ * @return string 签名值
|
|
|
|
|
+ */
|
|
|
|
|
+ public function generateSign($params)
|
|
|
|
|
+ {
|
|
|
|
|
+ // 1. 筛选:获取jrgw开头的参数和biz-content
|
|
|
|
|
+ $signParams = [];
|
|
|
|
|
+ foreach ($params as $key => $value) {
|
|
|
|
|
+ if ((strpos($key, 'jrgw') === 0 || $key === 'biz-content') && $value !== '' && $value !== null) {
|
|
|
|
|
+ $signParams[$key] = $value;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 排序:按ASCII码升序
|
|
|
|
|
+ ksort($signParams, SORT_STRING);
|
|
|
|
|
+
|
|
|
|
|
+ // 3. 拼接:key=value&key=value
|
|
|
|
|
+ $stringToSign = [];
|
|
|
|
|
+ foreach ($signParams as $key => $value) {
|
|
|
|
|
+ $stringToSign[] = $key . '=' . $value;
|
|
|
|
|
+ }
|
|
|
|
|
+ $stringToSign = implode('&', $stringToSign);
|
|
|
|
|
+
|
|
|
|
|
+ // 4. 使用私钥进行SHA256withRSA签名
|
|
|
|
|
+ $signature = '';
|
|
|
|
|
+ openssl_sign($stringToSign, $signature, $this->privateKey, self::SIGN_ALGO);
|
|
|
|
|
+
|
|
|
|
|
+ return base64_encode($signature);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 验证签名
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param array $params 所有参数(不包含sign)
|
|
|
|
|
+ * @param string $sign 待验证的签名值
|
|
|
|
|
+ * @return bool
|
|
|
|
|
+ */
|
|
|
|
|
+ public function verifySign($params, $sign)
|
|
|
|
|
+ {
|
|
|
|
|
+ // 1. 筛选:获取jrgw开头的参数和biz-content(除去sign)
|
|
|
|
|
+ $signParams = [];
|
|
|
|
|
+ foreach ($params as $key => $value) {
|
|
|
|
|
+ if ($key === 'sign') {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ if ((strpos($key, 'jrgw') === 0 || $key === 'biz-content') && $value !== '' && $value !== null) {
|
|
|
|
|
+ $signParams[$key] = $value;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 排序:按ASCII码升序
|
|
|
|
|
+ ksort($signParams, SORT_STRING);
|
|
|
|
|
+
|
|
|
|
|
+ // 3. 拼接
|
|
|
|
|
+ $stringToVerify = [];
|
|
|
|
|
+ foreach ($signParams as $key => $value) {
|
|
|
|
|
+ $stringToVerify[] = $key . '=' . $value;
|
|
|
|
|
+ }
|
|
|
|
|
+ $stringToVerify = implode('&', $stringToVerify);
|
|
|
|
|
+
|
|
|
|
|
+ // 4. 验签
|
|
|
|
|
+ $signature = base64_decode($sign);
|
|
|
|
|
+ $result = openssl_verify($stringToVerify, $signature, $this->publicKey, self::SIGN_ALGO);
|
|
|
|
|
+
|
|
|
|
|
+ return $result === 1;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 生成3DES密钥(24字节)
|
|
|
|
|
+ *
|
|
|
|
|
+ * @return string
|
|
|
|
|
+ */
|
|
|
|
|
+ private function generate3DesKey()
|
|
|
|
|
+ {
|
|
|
|
|
+ return substr(base64_encode(openssl_random_pseudo_bytes(24)), 0, 24);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 3DES加密
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param string $data 明文
|
|
|
|
|
+ * @param string $key 密钥
|
|
|
|
|
+ * @return string 密文(base64编码)
|
|
|
|
|
+ */
|
|
|
|
|
+ private function encrypt3Des($data, $key)
|
|
|
|
|
+ {
|
|
|
|
|
+ $iv = substr($key, 0, 8);
|
|
|
|
|
+ $encrypted = openssl_encrypt($data, 'DES-EDE3-CBC', $key, OPENSSL_RAW_DATA, $iv);
|
|
|
|
|
+ return base64_encode($encrypted);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 3DES解密
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param string $encryptedData 密文(base64编码)
|
|
|
|
|
+ * @param string $key 密钥
|
|
|
|
|
+ * @return string 明文
|
|
|
|
|
+ */
|
|
|
|
|
+ private function decrypt3Des($encryptedData, $key)
|
|
|
|
|
+ {
|
|
|
|
|
+ $iv = substr($key, 0, 8);
|
|
|
|
|
+ $decrypted = openssl_decrypt(base64_decode($encryptedData), 'DES-EDE3-CBC', $key, OPENSSL_RAW_DATA, $iv);
|
|
|
|
|
+ return $decrypted;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * RSA加密(用于加密3DES密钥)
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param string $data 明文
|
|
|
|
|
+ * @return string 密文(base64编码)
|
|
|
|
|
+ * @throws Exception
|
|
|
|
|
+ */
|
|
|
|
|
+ private function encryptRsa($data)
|
|
|
|
|
+ {
|
|
|
|
|
+ $encrypted = '';
|
|
|
|
|
+ if (!openssl_public_encrypt($data, $encrypted, $this->publicKey)) {
|
|
|
|
|
+ throw new Exception('RSA加密失败');
|
|
|
|
|
+ }
|
|
|
|
|
+ return base64_encode($encrypted);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * RSA解密(用于解密3DES密钥)
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param string $encryptedData 密文(base64编码)
|
|
|
|
|
+ * @return string 明文
|
|
|
|
|
+ * @throws Exception
|
|
|
|
|
+ */
|
|
|
|
|
+ private function decryptRsa($encryptedData)
|
|
|
|
|
+ {
|
|
|
|
|
+ $decrypted = '';
|
|
|
|
|
+ if (!openssl_private_decrypt(base64_decode($encryptedData), $decrypted, $this->privateKey)) {
|
|
|
|
|
+ throw new Exception('RSA解密失败');
|
|
|
|
|
+ }
|
|
|
|
|
+ return $decrypted;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 获取当前时间(格式:yyyyMMddHHmmssSSS)
|
|
|
|
|
+ *
|
|
|
|
|
+ * @return string
|
|
|
|
|
+ */
|
|
|
|
|
+ private function getCurrentTimeMillis()
|
|
|
|
|
+ {
|
|
|
|
|
+ $now = new DateTime();
|
|
|
|
|
+ return $now->format('YmdHis') . sprintf('%03d', floor(microtime(true) * 1000) % 1000);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public function request(string $url,array $bizContent = [])
|
|
|
|
|
+ {
|
|
|
|
|
+ $http = new Http();
|
|
|
|
|
+ $extraParams = [
|
|
|
|
|
+ 'jrgw-request-time' => $this->getCurrentTimeMillis(),
|
|
|
|
|
+ 'jrgw-enterprise-user-id' => '',
|
|
|
|
|
+ 'jrgw-user-id-type' => '0',
|
|
|
|
|
+ 'gw-encrypt-type' => 'NONE',
|
|
|
|
|
+ 'gw-sign-type' => 'SHA256withRSA',
|
|
|
|
|
+ ];
|
|
|
|
|
+
|
|
|
|
|
+ $extraParams['gw-sign'] = $this->buildRequest($bizContent,$extraParams);
|
|
|
|
|
+ $res = $http->postRequest('https://api.jddglobal.com'.$url,$bizContent,$extraParams);
|
|
|
|
|
+ $res = json_decode($res, true);
|
|
|
|
|
+ if($res && $res['code'] == 00000){
|
|
|
|
|
+ return $res['responseData'];
|
|
|
|
|
+ }else{
|
|
|
|
|
+ throw new ValidateException('错误码'.$res['errCode'].'错误信息'.$res['errDesc']);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public function sendBrokerage(array $bizContent = [])
|
|
|
|
|
+ {
|
|
|
|
|
+ return $this->request('/smapi/v1/ffp-common/submitNormalSalaryApi',$bizContent);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+}
|