| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330 |
- <?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);
- }
- }
|