• 【PHP】微信支付apiV3平台代金券和商家券接口api,使用原生php处理


    微信支付平台代金券和商家券接口api,使用原生php处理

    当然,能使用composer加载的,直接使用composer加载微信官方包就完事儿了,别舍近求远得不偿失。

    微信官方链接:

    指导文档&SDK : https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay4_0.shtml

    相关V3-api : https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_1_10.shtml

    1、 背景:最近公司使用比较老的php框架需要实现微信支付apiV3版本的代金券和商家券功能。

          本来微信官方有比较成熟的GuzzleHttp包实现composer加载开箱即用,可惜公司php框架太老了,不支持namespace和composer,没办法只能手撸

    2、接口这块分2大块,一是普通接口api的签名、验签,加密和解密;二是图片上传接口的签名、验签、加密、解密;

    3、参考过的链接,非常感谢:

      1)图片上传:https://blog.csdn.net/qq_16469571/article/details/105436553

      2)普通接口签名和验签:签名+加密 https://www.cnblogs.com/inkwhite/p/11686115.html

                                         验签+解密 https://blog.csdn.net/qq_27987023/article/details/88987835,

    (tips:可以使用微信提供的postman的JSON文件示例包,测试相关证书、序列号是不是对的,链接https://github.com/wechatpay-apiv3/wechatpay-postman-script)

    4、不废话,上代码:

    设置回调验签+解密

        // 设置核销券通知地址
        public function setConsumeNotifyUrl( )
        {
            $url='https://api.mch.weixin.qq.com/v3/marketing/favor/callbacks';
            $notifyurl = C('wx_node_voucher.consume_notify');
            //echo $notifyurl,$url;
            $wxnodevoucher = D('WxNodeVoucher', 'Service');
            $data = [
                'mchid'=>$wxnodevoucher->mchId,
                'notify_url'=>$notifyurl,
            ];
            /*//生成V3请求 header认证信息
            $header = $wxnodevoucher->createAuthorization($url, 'POST', $data);
            //var_dump($header);
            $result = $wxnodevoucher->curlHttp( $url, 'POST', $data , $header );*/
    
            $result = $wxnodevoucher->send($url, 'POST', $data);
            exit(json_encode($result, JSON_UNESCAPED_UNICODE));
        }
        // 回调通知,需要验签
        public function notifyVoucher( )
        {
            $log = function($info){
                log_write("回调信息:{$info}", 'wx_voucher_notify');
            };
            $headers = $_SERVER;
            $log(json_encode($headers, JSON_UNESCAPED_UNICODE));
    
            $sign = $headers['Wechatpay-Signature'];
            $nonce = $headers['Wechatpay-Nonce'];
            $timestamp = $headers['Wechatpay-Timestamp'];
            $body = file_get_contents('php://input');
    
            $message = $timestamp."
    ".
                $nonce."
    ".
                $body."
    ";
    
            $wxnodevoucher = D('WxNodeVoucher', 'Service');
            if( true === $wxnodevoucher->checkAuthorization($sign, $message) ){
                $log('回调通知验签通过');
                $data = json_decode($body, true);
                /**
                 * ApiV3 key 执行AEAD_AES_256_GCM解密 -- https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_1_15.shtml
                 */
                $remsg = $wxnodevoucher->decryptToString($wxnodevoucher->apiv3key ,$data['resource']['associated_data'], $data['resource']['nonce'], $data['resource']['ciphertext']);
                if(false === $remsg) {
                    $log('解密失败,body=' . $body . ', apiv3key=' . $wxnodevoucher->apiv3key);
                    // TODO 发送邮件
                    return false;
                }
                $sourceArr = json_decode($remsg, 1);
                if (json_last_error() || !is_array($sourceArr)) {
                    // TODO 解析json串异常
                    $log('解析json串异常,,remsg=' . $remsg);
                }
                switch ($data['event_type']) {
                    case 'COUPON.USE':
                        // TODO 卡券核销
                        $this->consumeVoucher($data, $sourceArr);
                        break;
                    case '':
                    default:
                        // TODO 证书下载
                        break;
                }
                exit(json_encode(['code'=>'SUCCESS', 'message'=>'成功']));
            }else{
                $log('回调通知验签失败');
            }
        }

    发送普通api签名+加密

        public function initAutoload()
        {
            if (!$this->initAutoload) {
                // 商户配置
                $this->appid = C('wx_node_voucher.miniproject_appid');
                $this->mchId = C('wx_node_voucher.mch_id');
                $this->mch_serial_no = C('wx_node_voucher.mch_serial_no');
                $this->mch_private_key = C('wx_node_voucher.apiclient_key');
                $this->mch_cert_pem = C('wx_node_voucher.apiclient_cert');
                $this->apiv3key = C('wx_node_voucher.apiv3_key');
                $this->platform_cert_pem = C('wx_node_voucher.weixin_cert');
            }
        }
    
        //生成v3 Authorization
        public function createAuthorization( $url , $method = 'GET', array $data=[] ){
    
            if (!in_array('sha256WithRSAEncryption', openssl_get_md_methods(true))) {
                throw new RuntimeException("当前PHP环境不支持SHA256withRSA");
            }
            $url_parts = parse_url($url);
            $canonical_url = ($url_parts['path'] . (!empty($url_parts['query']) ? "?${url_parts['query']}" : ""));
    
            //私钥地址 -- path/to/privateKey
            $mch_private_key = $this->mch_private_key;
    //        return $mch_private_key;
            //商户号
            $merchant_id = $this->mchId;
            //当前时间戳
            $timestamp =  time();
            //随机字符串
            $nonce = $this->createNoncestr();
            //POST请求时 需要 转JSON字符串
            $this->body = !empty($data) ? json_encode($data) : '' ;
            $method = strtoupper($method);
            $message = $method."
    ".
                $canonical_url."
    ".
                $timestamp."
    ".
                $nonce."
    ".
                $this->body."
    ";
    
            //生成签名
            openssl_sign($message, $raw_sign, openssl_get_privatekey(file_get_contents($mch_private_key)), 'sha256WithRSAEncryption');
            $sign = base64_encode($raw_sign);
    //        return $sign;
            //Authorization 类型
            $schema = 'WECHATPAY2-SHA256-RSA2048';
            //生成token
            $token = sprintf('mchid="%s",serial_no="%s",nonce_str="%s",timestamp="%d",signature="%s"', $merchant_id,$this->mch_serial_no, $nonce, $timestamp,  $sign);
    
            //'User-Agent:*/*',
            $header = [
                'Content-Type:application/json',
                'Accept:application/json',
                'User-Agent : https://zh.wikipedia.org/wiki/User_agent',
                'Authorization: '.  $schema . ' ' . $token
            ];
            return $header;
        }
    
        /**
         * @Notes: 验签v3 Authorization
         *
         * @param: $sign 返回的签名串
         * @param: $data_str 构造的验签串
         * @param: $pub_key_file_path  -- 微信支付平台证书公钥路径
         * @return: bool
         * @author: Xuzhz 2021/5/28 11:31
         */
        public function checkAuthorization( $sign, $data_str, $pub_key_file_path='' ){
            if (!$pub_key_file_path ) {
                $pub_key_file_path = $this->platform_cert_pem;
            }
            $public_key = openssl_get_publickey( file_get_contents($pub_key_file_path) );
            if(empty($public_key)){
                return false;
            }
            $sign = base64_decode($sign);
            $ok = openssl_verify( $data_str, $sign, $public_key, OPENSSL_ALGO_SHA256 ); //SHA256
            openssl_free_key( $public_key );
            if ($ok == 1) {
                $result = true;
            } elseif ($ok == 0) {
                $result = false;
            } else {
                log_write('111DEBUG'. __CLASS__.' ' . __FUNCTION__ . ' 0 openssl_error_str '.json_encode(openssl_error_string()));
            }
            return $result;
        }
    
        /**
         *  作用:产生随机字符串,不长于32位
         */
        public function createNoncestr( $length = 32 )
        {
            $chars = "abcdefghijklmnopqrstuvwxyz0123456789";
            $str ="";
            for ( $i = 0; $i < $length; $i++ )  {
                $str.= substr($chars, mt_rand(0, strlen($chars)-1), 1);
            }
            return $str;
        }
    
        /**
         * Decrypt AEAD_AES_256_GCM ciphertext  V3 -- 证书下载、回调报文解密
         * link: https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay4_2.shtml
         * @param stingr    $aesKey             ApiV3_Key //商户需先在【商户平台】->【API安全】的页面设置该密钥
         * @param string    $associatedData     AES GCM additional authentication data
         * @param string    $nonceStr           AES GCM nonce
         * @param string    $ciphertext         AES GCM cipher text
         *
         * @return string|bool      Decrypted string on success or FALSE on failure
         */
        public function decryptToString($aesKey ,$associatedData, $nonceStr, $ciphertext)
        {
            if (strlen($aesKey) != 32 ) {
                throw new InvalidArgumentException('无效的ApiV3Key,长度应为32个字节');
            }
    
            $ciphertext = base64_decode($ciphertext , true);
            if (strlen($ciphertext) <= 16) {
                return false;
            }
    
            // ext-sodium (default installed on >= PHP 7.2)
            if(function_exists('sodium_crypto_aead_aes256gcm_is_available') && sodium_crypto_aead_aes256gcm_is_available() ){
                return sodium_crypto_aead_aes256gcm_decrypt($ciphertext, $associatedData, $nonceStr, $aesKey);
            }
    
            // ext-libsodium (need install libsodium-php 1.x via pecl)
            if(function_exists('Sodiumcrypto_aead_aes256gcm_is_available') && Sodiumcrypto_aead_aes256gcm_is_available()){
    
                return Sodiumcrypto_aead_aes256gcm_decrypt($ciphertext, $associatedData, $nonceStr, $aesKey);
            }
    
            // PHP >= 7.1
            if(PHP_VERSION_ID >= 70100 && in_array('aes-256-gcm', openssl_get_cipher_methods()) ){
                $ctext = substr($ciphertext, 0, -16);
                $authTag = substr($ciphertext, -16);
                return openssl_decrypt($ctext, 'aes-256-gcm', $aesKey, OPENSSL_RAW_DATA, $nonceStr,$authTag, $associatedData);
            }
    
            throw new RuntimeException('AEAD_AES_256_GCM需要PHP 7.1以上或者安装libsodium-php');
        }
        public function curlHttp( $url, $method='GET', $data=[], $headers=['Content-Type: application/json;charset=UTF-8'] )
        {
            $log = function ($info) {
                log_write($info, 'to_erp');
            };
    
            $json = json_encode($data);
            $log('request-url:' . $url);
            $log('request-data:' . $json);
    
            //弃用S::curl,会出现异常;
            $ch = curl_init();
            curl_setopt($ch, CURLOPT_TIMEOUT, 10);
            curl_setopt($ch, CURLOPT_URL, $url);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
            if( strtoupper($method) == 'POST' ) {
                curl_setopt($ch, CURLOPT_POST, TRUE);
                curl_setopt($ch, CURLOPT_POSTFIELDS, $json);
            }
            curl_setopt($ch, CURLOPT_HEADER, 1); //返回response头部信息
            curl_setopt($ch, CURLOPT_HTTPHEADER, $headers/*[
                'Content-Type: application/json;charset=UTF-8'
            ]*/);
            curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/4.0 (compatible;)' );
            curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, false );
            curl_setopt( $ch, CURLOPT_SSL_VERIFYHOST, false );
    
            $beginMicroTime = microtime();
            $result         = curl_exec($ch);
    //        print_r($result);
    
            //请求日志记录
            $endMicroTime = microtime();
            /*ApiRecord::dispatch(
                $url,
                curl_getinfo($ch, CURLINFO_HTTP_CODE),
                curl_error($ch),
                $json,
                $result,
                $beginMicroTime,
                $endMicroTime
            )->onQueue(ApiRecord::QUEUE_NAME);*/
    
            $log('response-data:' . $result);
            if( substr(curl_getinfo( $ch, CURLINFO_HTTP_CODE ), 0, 1) != '2' ){
                $errno = curl_errno( $ch );
                $log('response-code:' . $errno);
                curl_close( $ch );
                echo curl_getinfo( $ch, CURLINFO_HTTP_CODE ).PHP_EOL;
                return false;
            }
            $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
            $re_header = substr($result, 0, $headerSize);
            $log("返回的header头信息msg={$re_header}, url={$url}, data={$json}");
            $body = substr($result, $headerSize);
    
            //验签
            if(! $this->comboCheckAuth($re_header, $body)) {
                $log('验证失败');
                return false;
            }
            $log('验签成功');
    //        echo 'comboCheckAuth验签成功';
            curl_close( $ch );
            if (!$result) {
                return false;
            }
    
    //        $body = json_decode($result, 1);
            $body = $body ?? $result;
            if (is_string($body)) {
                $body = json_decode($body, 1);
                if (json_last_error() || !is_array($body)) {
                    return false;
                } else {
                    return $body;
                }
            } else if (is_array($body)) {
                return $body;
            } else {
                return false;
            }
        }
    
        // 回调通知,需要验签
        public function comboCheckAuth( $headers, $body )
        {
            $sign = $nonce = $timestamp = '';
            // 处理请求头
            $headArr = explode("
    ", $headers);
            foreach ($headArr as $loop) {
                if (strpos($loop, "Wechatpay-Signature") !== false) {
                    $sign = trim(substr($loop, strlen('Wechatpay-Signature')+2));
                }
                if (strpos($loop, "Wechatpay-Nonce") !== false) {
                    $nonce = trim(substr($loop, strlen('Wechatpay-Nonce')+2));
                }
                if (strpos($loop, "Wechatpay-Timestamp") !== false) {
                    $timestamp = trim(substr($loop, strlen('Wechatpay-Timestamp')+2));
                }
            }
            $message = $timestamp."
    ".
                $nonce."
    ".
                $body."
    ";
    
            return $a = $this->checkAuthorization($sign, $message);
        }
    
        /**
         * 获取微信支付平台证书(微信支付负责申请), 与商户API证书(商户自行申请)不是一个内容
         * link: https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay3_1.shtml
         *
         * @return 返回需要$this->decryptToString()解密
         * @return httpCode:200
         * @return {
        "data": [
        {
        "serial_no": "5157F09EFDC096DE15EBE81A47057A7232F1B8E1",
        "effective_time ": "2018-06-08T10:34:56+08:00",
        "expire_time ": "2018-12-08T10:34:56+08:00",
        "encrypt_certificate": {
        "algorithm": "AEAD_AES_256_GCM",
        "nonce": "61f9c719728a",
        "associated_data": "certificate",
        "ciphertext": "sRvt… "
        }
        },
        {...}
        ]
        }
         */
        public function getcertificates()
        {
            $url="https://api.mch.weixin.qq.com/v3/certificates";
            //生成V3请求 header认证信息
            $header = $this->createAuthorization( $url );
            $data = $this->curlHttp( $url, 'GET', $this->data , $header );
    
            //证书报文解密
            //$this->decryptToString();
            return $data;
        }
        /**
         * V3 -- 敏感信息(身份证、银行卡、手机号.etc)加密,微信支付平台证书中的(RSA)公钥加密
         * link: https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay4_3.shtml
         */
        public function getEncrypt($str){
            //$str是待加密字符串
            $public_key_path = '证书地址'; //看情况使用证书, 个别接口证书 使用的是 平台证书而不是 api证书
            $public_key = file_get_contents($public_key_path);
    
            $encrypted = '';
            if (openssl_public_encrypt($str,$encrypted,$public_key,OPENSSL_PKCS1_OAEP_PADDING)) {
                //base64编码
                $sign = base64_encode($encrypted);
            } else {
                throw new Exception('encrypt failed');
            }
            return $sign;
        }
        /**
         * V3 -- 敏感信息(身份证、银行卡、手机号.etc)解密,微信商户私钥(RSA)解密
         * link: https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay4_3.shtml
         */
        private function getDecrypt($str){
            //$str是待加密字符串
            $private_key_path = '平台证书路径';
            $private_key = file_get_contents($private_key_path);
    
            $dncrypted = '';
            if (openssl_private_decrypt(base64_decode($str),$dncrypted,$private_key,OPENSSL_PKCS1_OAEP_PADDING)) {
                # TODO
            } else {
                throw new Exception('dncrypt failed');
            }
            return $dncrypted;
        }
        //返回array | false
        protected function send($url, $method='GET', array $data=[])
        {
            // 生成V3请求 header认证信息
            $header = $this->createAuthorization($url, strtoupper($method), $data);
            // 发起curlHttp调用 & 验签
            $result = $this->curlHttp($url, strtoupper($method), $data , $header);
    
            return $result;
        }
    
        //微信api-v3上传图片
        public function wxPayUploadFile( )
        {
            header("Content-type:text/html;charset=utf-8");
    
            $url = 'https://api.mch.weixin.qq.com/v3/marketing/favor/media/image-upload';
            $filePath = APP_PATH . 'Public/Image/Wxzf/star-group.png';//'你需要上传的图片';
            $mime = mime_content_type($filePath);
            $filename = pathinfo($filePath, PATHINFO_BASENAME);
    
            //图片校验
            if(!$this->checkImgFile($filePath)){
                log_write('微信商户上传图片失败:'.$this->error);
                return false;
            }
    
            //私钥地址 -- path/to/privateKey
            $keyPath = $this->mch_private_key;
            //商户号
            $merchant_id = $this->mchId;
    
            $mess = $this->binaryEncodeImage($filePath);
            $filestr = json_encode(array('filename' => $filename, 'sha256' => hash_file("sha256", $filePath)));
    
            #准备参与签名数据
            $time = time();
            $nonce_str = $this->createNoncestr();
            $pkid = file_get_contents($keyPath);
    
            $token = $this->sign($url, "POST", $time, $nonce_str, $filestr, $pkid, $merchant_id, $this->mch_serial_no);
    
            //Authorization 类型
            $schema = 'WECHATPAY2-SHA256-RSA2048';
            #设置头部信息
            $boundary = '7derenufded';
            $headers = [
                "Authorization: " . $schema . ' ' . $token,
                "User-Agent:https://zh.wikipedia.org/wiki/User_agent",
                "Accept:application/json",
                "Content-Type:multipart/form-data;boundary=" . $boundary//切记boundary=后面这里切记这里不要加-- 和 “”
            ];
    
            #这里是构造请求body
            $boundarystr = "--{$boundary}
    ";
    
            $out = $boundarystr;
            $out .= 'Content-Disposition:form-data;name="meta"' . "
    ";#name必须是meta
            $out .= 'Content-Type: application/json; charset=UTF-8' . "
    ";
            $out .= "
    ";
            $out .= "" . $filestr . "
    ";
            $out .= $boundarystr;
            $out .= 'Content-Disposition:form-data;name="file";filename="' . $filename . '"' . "
    ";#name必须是file
            $out .= "Content-Type: {$mime}
    ";
            $out .= "
    ";
            $out .= $mess . "
    ";
            $out .= "--{$boundary}--";
    
            // 发起curlMediaHttp调用 & 验签
            return $this->curlMediaHttp($url, 'POST', $out, $headers);
        }
        public function curlMediaHttp( $url, $method='GET', $out='', $headers=['Content-Type: application/json;charset=UTF-8'] )
        {
            $log = function ($info) {
                log_write($info, 'to_erp');
            };
    
            $json = $out;
            $log('request-url:' . $url);
            $log('request-data:' . $json);
    
            //弃用S::curl,会出现异常;
            $ch = curl_init();
            curl_setopt($ch, CURLOPT_TIMEOUT, 10);
            curl_setopt($ch, CURLOPT_URL, $url);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
    //        if( strtoupper($method) == 'POST' ) {
                curl_setopt($ch, CURLOPT_POST, TRUE);
                curl_setopt($ch, CURLOPT_POSTFIELDS, $json);
    //        }
            curl_setopt($ch, CURLOPT_HEADER, 1); //返回response头部信息
            curl_setopt($ch, CURLOPT_HTTPHEADER, $headers/*[
                'Content-Type: application/json;charset=UTF-8'
            ]*/);
            curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/4.0 (compatible;)' );
            curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, false );
            curl_setopt( $ch, CURLOPT_SSL_VERIFYHOST, false );
    
            $result         = curl_exec($ch);
    //        var_dump($result);
    
            $log('response-data:' . $result);
            if( substr(curl_getinfo( $ch, CURLINFO_HTTP_CODE ), 0, 1) != '2' ){
                $errno = curl_errno( $ch );
                $log('response-code:' . $errno);
                curl_close( $ch );
                //echo curl_getinfo( $ch, CURLINFO_HTTP_CODE ).PHP_EOL;
                return false;
            }
            $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
            $re_header = substr($result, 0, $headerSize);
            $log("返回的header头信息msg={$re_header}, url={$url}, data={$json}");
            $body = substr($result, $headerSize);
    
            //验签
            if(! $this->comboCheckAuth($re_header, $body)) {
                $log('验证失败');
                return false;
            }
            $log('验签成功');
    //        echo 'comboCheckAuth验签成功';
            curl_close( $ch );
            if (!$result) {
                return false;
            }
    
    //        $body = json_decode($result, 1);
            $body = $body ?? $result;
            if (is_string($body)) {
                $body = json_decode($body, 1);
                if (json_last_error() || !is_array($body)) {
                    return false;
                } else {
                    return $body;
                }
            } else if (is_array($body)) {
                return $body;
            } else {
                return false;
            }
        }
    
        //签名
        private function sign($url,$http_method,$timestamp,$nonce,$body,$mch_private_key,$merchant_id,$serial_no){
    
            $url_parts = parse_url($url);
            $canonical_url = ($url_parts['path'] . (!empty($url_parts['query']) ? "?${url_parts['query']}" : ""));
            $message =
                $http_method."
    ".
                $canonical_url."
    ".
                $timestamp."
    ".
                $nonce."
    ".
                $body.
                "
    ";
            openssl_sign($message, $raw_sign, $mch_private_key, 'sha256WithRSAEncryption');
            $sign = base64_encode($raw_sign);
            $token = sprintf('mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"',
                $merchant_id, $nonce, $timestamp, $serial_no, $sign);
            return $token;
        }
    
        /**
         * 图片转化为二进制数据流
         * @desc  图片转化为二进制数据流
         * return string
         */
        public function binaryEncodeImage($img_file){
            header("Content-type:text/html;charset=utf-8");
            $p_size = filesize($img_file);
            $img_binary = fread(fopen($img_file, "rb"), $p_size);
            return $img_binary;
        }
    
        //判断图片类型
        public function checkImgFile($file)
        {
            if(!is_file($file)){
                $this->error='无效文件';
                return false;
            }
            $extension = strtolower(pathinfo($file, PATHINFO_EXTENSION));
            if (function_exists('exif_imagetype')) {
                $imgType=exif_imagetype($file);
            }else{
                $res = getimagesize($file);
                $imgType=$res?$res[2]:false;
            }
            if (!in_array($extension, ['jpg', 'jpeg', 'bmp', 'png']) || !in_array($imgType, [ 2, 3, 6])) {
                $this->error='无效图片文件';
                return false;
            }
    
            if(filesize($file) > 2097152){
                $this->error='图片文件大小超过 2M';
                return false;
            }
    
            return true;
        }

    5、over!

  • 相关阅读:
    ThinkPHP中的json对象
    ThinkPHP修改默认错误页面
    E签宝签署短信后回调通知数据结构示例
    E签宝电子签章接口调试请求和响应示例
    Git如何撤销本地所有的更改操作还原到更改前的代码?
    使Egg.js编写RestfulAPI接口(六)路由分组
    使用Egg.js编写RestfulAPI接口(五)资源路由配置
    使用Egg.js编写RestfulAPI接口(四)使用PostMain测试Api接口
    使用Egg.js编写RestfulAPI接口(三)编写Api接口
    使用Egg.js编写RestfulAPI接口(二)配置跨域
  • 原文地址:https://www.cnblogs.com/xuzhengzong/p/14862736.html
Copyright © 2020-2023  润新知