JingLinExtend.php 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. <?php
  2. namespace app\extra\tools;
  3. use app\extra\basic\Http;
  4. use think\exception\ValidateException;
  5. class JingLinExtend
  6. {
  7. /**
  8. * 加密方式常量
  9. */
  10. const ENCRYPT_NONE = 'NONE';
  11. const ENCRYPT_3DES_RSA = '3DES_RSA';
  12. /**
  13. * 签名算法
  14. */
  15. const SIGN_ALGO = OPENSSL_ALGO_SHA256;
  16. /**
  17. * @var string 商户私钥路径或内容
  18. */
  19. private $privateKey;
  20. /**
  21. * @var string 平台公钥路径或内容
  22. */
  23. private $publicKey;
  24. /**
  25. * @var string 3DES密钥(自动生成或外部传入)
  26. */
  27. private $secretKey;
  28. /**
  29. * @var string 加密方式
  30. */
  31. private $encryptType = self::ENCRYPT_NONE;
  32. /**
  33. * 构造函数
  34. *
  35. * @param string $privateKey 商户私钥(文件路径或内容)
  36. * @param string $publicKey 平台公钥(文件路径或内容)
  37. * @param string $encryptType 加密方式 NONE/3DES_RSA
  38. */
  39. public function __construct($privateKey, $publicKey, $encryptType = self::ENCRYPT_NONE)
  40. {
  41. $this->privateKey = $this->loadKey($privateKey, 'private');
  42. $this->publicKey = $this->loadKey($publicKey, 'public');
  43. $this->encryptType = $encryptType;
  44. }
  45. /**
  46. * 加载密钥
  47. */
  48. private function loadKey($key, $type)
  49. {
  50. // 如果是文件路径
  51. if (is_file($key)) {
  52. $key = file_get_contents($key);
  53. }
  54. if ($type === 'private') {
  55. return openssl_pkey_get_private($key);
  56. } else {
  57. return openssl_pkey_get_public($key);
  58. }
  59. }
  60. /**
  61. * 构建请求(包含签名和加密)
  62. *
  63. * @param array $bizContent 业务参数(明文)
  64. * @param array $extraParams 额外的公共参数(会自动过滤jrgw开头的)
  65. * @return array 完整的请求参数
  66. */
  67. public function buildRequest($bizContent, $extraParams = [])
  68. {
  69. $requestParams = [];
  70. // 1. 处理业务内容
  71. if ($this->encryptType === self::ENCRYPT_3DES_RSA) {
  72. // 生成随机3DES密钥
  73. $this->secretKey = $this->generate3DesKey();
  74. // 加密业务参数
  75. $encryptedBiz = $this->encrypt3Des(json_encode($bizContent), $this->secretKey);
  76. // 使用RSA加密3DES密钥
  77. $encryptedKey = $this->encryptRsa($this->secretKey);
  78. $requestParams['encrypt'] = $encryptedKey;
  79. $requestParams['biz-content'] = $encryptedBiz;
  80. } else {
  81. // 明文模式
  82. $requestParams['biz-content'] = json_encode($bizContent);
  83. }
  84. // 合并额外参数(只保留以jrgw开头的)
  85. foreach ($extraParams as $key => $value) {
  86. if (strpos($key, 'jrgw') === 0 && $value !== '' && $value !== null) {
  87. $requestParams[$key] = $value;
  88. }
  89. }
  90. // 2. 生成签名
  91. $sign = $this->generateSign($requestParams);
  92. return $sign;
  93. }
  94. /**
  95. * 解析响应(包含验签和解密)
  96. *
  97. * @param array $responseParams 响应参数
  98. * @return array 解密后的业务参数
  99. * @throws Exception
  100. */
  101. public function parseResponse($responseParams)
  102. {
  103. // 1. 验签
  104. $sign = isset($responseParams['sign']) ? $responseParams['sign'] : '';
  105. if (!$this->verifySign($responseParams, $sign)) {
  106. throw new Exception('响应签名验证失败');
  107. }
  108. // 2. 解密
  109. if ($this->encryptType === self::ENCRYPT_3DES_RSA) {
  110. if (!isset($responseParams['encrypt']) || !isset($responseParams['biz-content'])) {
  111. throw new Exception('加密响应缺少必要参数');
  112. }
  113. // 使用私钥解密3DES密钥
  114. $secretKey = $this->decryptRsa($responseParams['encrypt']);
  115. // 使用3DES解密业务内容
  116. $bizContent = $this->decrypt3Des($responseParams['biz-content'], $secretKey);
  117. return json_decode($bizContent, true);
  118. } else {
  119. // 明文模式
  120. if (!isset($responseParams['biz-content'])) {
  121. throw new Exception('响应缺少biz-content参数');
  122. }
  123. return json_decode($responseParams['biz-content'], true);
  124. }
  125. }
  126. /**
  127. * 生成签名
  128. *
  129. * @param array $params 所有参数(包含biz-content和jrgw开头的参数)
  130. * @return string 签名值
  131. */
  132. public function generateSign($params)
  133. {
  134. // 1. 筛选:获取jrgw开头的参数和biz-content
  135. $signParams = [];
  136. foreach ($params as $key => $value) {
  137. if ((strpos($key, 'jrgw') === 0 || $key === 'biz-content') && $value !== '' && $value !== null) {
  138. $signParams[$key] = $value;
  139. }
  140. }
  141. // 2. 排序:按ASCII码升序
  142. ksort($signParams, SORT_STRING);
  143. // 3. 拼接:key=value&key=value
  144. $stringToSign = [];
  145. foreach ($signParams as $key => $value) {
  146. $stringToSign[] = $key . '=' . $value;
  147. }
  148. $stringToSign = implode('&', $stringToSign);
  149. // 4. 使用私钥进行SHA256withRSA签名
  150. $signature = '';
  151. openssl_sign($stringToSign, $signature, $this->privateKey, self::SIGN_ALGO);
  152. return base64_encode($signature);
  153. }
  154. /**
  155. * 验证签名
  156. *
  157. * @param array $params 所有参数(不包含sign)
  158. * @param string $sign 待验证的签名值
  159. * @return bool
  160. */
  161. public function verifySign($params, $sign)
  162. {
  163. // 1. 筛选:获取jrgw开头的参数和biz-content(除去sign)
  164. $signParams = [];
  165. foreach ($params as $key => $value) {
  166. if ($key === 'sign') {
  167. continue;
  168. }
  169. if ((strpos($key, 'jrgw') === 0 || $key === 'biz-content') && $value !== '' && $value !== null) {
  170. $signParams[$key] = $value;
  171. }
  172. }
  173. // 2. 排序:按ASCII码升序
  174. ksort($signParams, SORT_STRING);
  175. // 3. 拼接
  176. $stringToVerify = [];
  177. foreach ($signParams as $key => $value) {
  178. $stringToVerify[] = $key . '=' . $value;
  179. }
  180. $stringToVerify = implode('&', $stringToVerify);
  181. // 4. 验签
  182. $signature = base64_decode($sign);
  183. $result = openssl_verify($stringToVerify, $signature, $this->publicKey, self::SIGN_ALGO);
  184. return $result === 1;
  185. }
  186. /**
  187. * 生成3DES密钥(24字节)
  188. *
  189. * @return string
  190. */
  191. private function generate3DesKey()
  192. {
  193. return substr(base64_encode(openssl_random_pseudo_bytes(24)), 0, 24);
  194. }
  195. /**
  196. * 3DES加密
  197. *
  198. * @param string $data 明文
  199. * @param string $key 密钥
  200. * @return string 密文(base64编码)
  201. */
  202. private function encrypt3Des($data, $key)
  203. {
  204. $iv = substr($key, 0, 8);
  205. $encrypted = openssl_encrypt($data, 'DES-EDE3-CBC', $key, OPENSSL_RAW_DATA, $iv);
  206. return base64_encode($encrypted);
  207. }
  208. /**
  209. * 3DES解密
  210. *
  211. * @param string $encryptedData 密文(base64编码)
  212. * @param string $key 密钥
  213. * @return string 明文
  214. */
  215. private function decrypt3Des($encryptedData, $key)
  216. {
  217. $iv = substr($key, 0, 8);
  218. $decrypted = openssl_decrypt(base64_decode($encryptedData), 'DES-EDE3-CBC', $key, OPENSSL_RAW_DATA, $iv);
  219. return $decrypted;
  220. }
  221. /**
  222. * RSA加密(用于加密3DES密钥)
  223. *
  224. * @param string $data 明文
  225. * @return string 密文(base64编码)
  226. * @throws Exception
  227. */
  228. private function encryptRsa($data)
  229. {
  230. $encrypted = '';
  231. if (!openssl_public_encrypt($data, $encrypted, $this->publicKey)) {
  232. throw new Exception('RSA加密失败');
  233. }
  234. return base64_encode($encrypted);
  235. }
  236. /**
  237. * RSA解密(用于解密3DES密钥)
  238. *
  239. * @param string $encryptedData 密文(base64编码)
  240. * @return string 明文
  241. * @throws Exception
  242. */
  243. private function decryptRsa($encryptedData)
  244. {
  245. $decrypted = '';
  246. if (!openssl_private_decrypt(base64_decode($encryptedData), $decrypted, $this->privateKey)) {
  247. throw new Exception('RSA解密失败');
  248. }
  249. return $decrypted;
  250. }
  251. /**
  252. * 获取当前时间(格式:yyyyMMddHHmmssSSS)
  253. *
  254. * @return string
  255. */
  256. private function getCurrentTimeMillis()
  257. {
  258. $now = new DateTime();
  259. return $now->format('YmdHis') . sprintf('%03d', floor(microtime(true) * 1000) % 1000);
  260. }
  261. public function request(string $url,array $bizContent = [])
  262. {
  263. $http = new Http();
  264. $extraParams = [
  265. 'jrgw-request-time' => $this->getCurrentTimeMillis(),
  266. 'jrgw-enterprise-user-id' => '',
  267. 'jrgw-user-id-type' => '0',
  268. 'gw-encrypt-type' => 'NONE',
  269. 'gw-sign-type' => 'SHA256withRSA',
  270. ];
  271. $extraParams['gw-sign'] = $this->buildRequest($bizContent,$extraParams);
  272. $res = $http->postRequest('https://api.jddglobal.com'.$url,$bizContent,$extraParams);
  273. $res = json_decode($res, true);
  274. if($res && $res['code'] == 00000){
  275. return $res['responseData'];
  276. }else{
  277. throw new ValidateException('错误码'.$res['errCode'].'错误信息'.$res['errDesc']);
  278. }
  279. }
  280. public function sendBrokerage(array $bizContent = [])
  281. {
  282. return $this->request('/smapi/v1/ffp-common/submitNormalSalaryApi',$bizContent);
  283. }
  284. }