文档章节

Android应用集成微信支付

zhyihui
 zhyihui
发布于 2016/07/13 17:59
字数 5331
阅读 204
收藏 4

前言

最近的项目用到了移动支付功能,客户要求同时支持“支付宝”和“微信支付”;个人感觉相对来说支付宝较简单一些,以前也在Android应用中集成过,因此没有花费过多时间便完成了。但微信支付我是第一次接触,着实费了不少功夫,花了几天才折腾出来,便想着写篇日志记一下这个过程,后面再用到的时候也不至于再纠结一次。

微信支付

首先说一下微信支付相关的知识点(概念),大部分内容摘抄自微信支付的官方文档:https://pay.weixin.qq.com/wiki/doc/api/index.html,建议需要集成微信支付的朋友多读几遍相关部分。

支付模式

微信支付分几种支付模式,这里有详细的说明:https://pay.weixin.qq.com/wiki/doc/api/app.php?chapter=2_1。我这里仅仅关注“App支付”模式,其定义如下:

APP支付又称移动端支付,是商户通过在移动端应用APP中集成开放SDK调起微信支付模块完成支付的模式。

另外还有“刷卡支付”、“扫码支付”和“公众号支付”三种,有需要的朋友请到文档中查看,我暂时没有去研究。

协议规则

详情请查看相关文档:https://pay.weixin.qq.com/wiki/doc/api/app.php?chapter=4_1

总地来说,商户接入微信支付,调用API必须遵循以下规则:

  • POST方式提交
  • 签名算法使用MD5
  • 提交和返回数据都为XML格式,根节点名为xml

参数规定

参数规定相关文档:https://pay.weixin.qq.com/wiki/doc/api/app.php?chapter=4_2,在这里仅摘抄几个比较重要的参数说明。

  • 交易金额
    交易金额默认为人民币交易,接口中参数支付金额单位为【分】,参数值不能带小数。
  • 交易类型
    APP代表App支付,统一下单接口trade_type的传参需要使用。
  • 时间戳
    标准北京时间,时区为东八区,自1970年1月1日 0点0分0秒以来的秒数。注意:Java平台取到的值为毫秒级,需要转换成秒(10位数字)。
  • 商户订单号
    商户支付的订单号由商户自定义生成,微信支付要求商户订单号保持唯一性。

安全规范

相关文档:https://pay.weixin.qq.com/wiki/doc/api/app.php?chapter=4_3
本部分文档需要仔细阅读直到完全理解其意思,这应该是最重要的一步了,主要是讲如何获取签名。

微信支付过程中两步(统一下单和发起支付)都要采用本部分描述的方法进行签名,这两步签名过程是一样的,只有签名时的参数不同,后面贴出代码时再作进一步的介绍。

签名规则:

  • 参数名ASCII码从小到大排序(字典序);
  • 如果参数的值为空不参与签名;
  • 参数名区分大小写;
  • 验证调用返回或微信主动通知签名时,传送的sign参数不参与签名,将生成的签名与该sign值作校验。
  • 微信接口可能增加字段,验证签名时必须支持增加的扩展字段

准备工作

在进行代码开发前我们需要做一些准备工作,主要是在微信开放平台上创建移动应用,然后为其申请微信支付权限,申请通过后还要作一些微信支付环境的配置。

下面分别记录一下这个过程。

微信开放平台

首先要在“微信开放平台”注册我们的应用。注册并登录微信开放平台,到“管理中心”可看到如下界面(我登录的帐号已经创建了一个App应用,如果是新帐号这个列表应该是空的):

这里写图片描述

点击左上角的“创建移动应用”按钮即可创建一个新的移动应用,过程不复杂,这里不再赘述。创建移动应用后还需要设置App的包名和应用签名信息,引用文档中的话如下:

商户在微信开放平台申请开发应用后,微信开放平台会生成APP的唯一标识APPID。由于需要保证支付安全,需要在开放平台绑定商户应用包名和应用签名,设置好后才能正常发起支付。设置界面在【开放平台】中的栏目【管理中心 / 修改应用 / 修改开发信息】里面。

设置过程这里只是文字描述可能说不太清楚,如果移动应用创建成功,自然就可以看到这些东西,另外,这里也有个说明:https://pay.weixin.qq.com/wiki/doc/api/app.php?chapter=8_5,文档里面的截图可以更好地说明这个步骤。

接下来还需要为创建的移动应用申请微信支付能力,申请支付能力时好像还需要向微信缴纳300元钱费用,具体申请步骤我也不是很清楚,好像是提交相关资料由微信工作人员审核,一般几天内可以完成。

最终结果如下图所示:

这里写图片描述

从这里我们获取了AppID,记下来这个字符串,后面代码中要用。

微信商户平台

下面是微信官方文档中的一段话:

商户在微信公众平台(申请扫码支付、公众号支付)或开放平台(申请APP支付)按照相应提示,申请相应微信支付模式。微信支付工作人员审核资料无误后开通相应的微信支付权限。微信支付申请审核通过后,商户在申请资料填写的邮箱中收取到由微信支付小助手发送的邮件,此邮件包含开发时需要使用的支付账户信息。

我们在微信开放平台(我只关注App支付)创建注册移动应用并申请微信支付能力通过后,会收到如下图所示的邮件:

此邮件基本上包括我们调用微信支付的所有信息(还差一个App Key马上就提到),我们记下来这些信息,接着做下一步。

使用上面收到的邮件中提到和用户名和密码登录“微信商户平台”,找到“帐户设置 》API安全“页面,在”API密钥“部分设置一下密钥,密钥字符串可自行设计,我这里是用的MD5加密随机数字。在设置API密钥时还需要安装操作证书,这些内容在登录商户平台后设置API密钥时都有提示的,这里也不再赘述了,描述多了反而更乱,过程其实挺简单的。

通过以上的准备工作,最终我们拿到了微信支付所需要的所有信息:AppID、微信支付商户号和API密钥(见https://pay.weixin.qq.com/wiki/doc/api/app.php?chapter=3_1#):

参数 API参数名 详细说明
AppID appid appid是微信公众账号或开放平台APP的唯一标识,在公众平台申请公众账号或者在开放平台申请APP账号后,微信会自动分配对应的appid,用于标识该应用。可在微信公众平台–>开发者中心查看,商户的微信支付审核通过邮件中也会包含该字段值。
微信支付商户号 mch_id 商户申请微信支付后,由微信支付分配的商户收款账号。
API密钥 key 交易过程生成签名的密钥,仅保留在商户系统和微信支付后台,不会在网络中传播。商户妥善保管该Key,切勿在网络中传输,不能在其他客户端中存储,保证key不会被泄漏。商户可根据邮件提示登录微信商户平台进行设置。也可按一下路径设置:微信商户平台(pay.weixin.qq.com)–>账户设置–>API安全–>密钥设置

支付步骤

微信支付主要分两步:统一下单和调用App支付。在调用微信支付之前根据业务还有可能存在诸如加入购物车、提交订单等步骤,这些跟微信支付是没有任何关系的,这些都由我们应用的后台服务来完成,最终要生成订单号及商品详情等数据,除此之外,还要有时间戳、签名及随机字符串等数据,这些数据在统一下单或调用App支付时会用到。

统一下单

除被扫支付场景以外,商户系统先调用该接口在微信支付服务后台生成预支付交易单,返回正确的预支付交易回话标识后再按扫码、JSAPI、APP等不同场景生成交易串调起支付。

统一下单是通过HTTP协议来完成的,也就是说在调用微信App支付之前需要向微信支付后台服务发一个HTTP请求,这个HTTP请求会携带一些重要的参数数据以及这些参数的签名,微信支付后台在签名验证通过后生成预支付交易单并返回交易单ID,然后我们才能以交易单ID为参数发起App支付请求。

本步骤需要以XML形式传递参数,具体过程在微信支付示例中都有,相关参数及说明也可以去相关文档中查阅,下面的代码部分也会说明这些。

统一下单接口链接:https://api.mch.weixin.qq.com/pay/unifiedorder
统一下单相关文档:https://pay.weixin.qq.com/wiki/doc/api/app.php?chapter=9_1

调用App支付

统一下单请求完成后我们会得到一个XML字符串,分析可得到预支付交易单ID(prepare_id),此ID就是本步骤请求中要携带的参数。

使用预支付交易单ID以及其它一些参数(应用appId、商户号、随机字符串及时间戮等)经过与上一步样的步骤生成签名,然后将被签名的参数以及签名结果一起创建App支付请求对象,最后调用微信支付接口将该请求发送即可,再完成回调就可以了。

相关文档:https://pay.weixin.qq.com/wiki/doc/api/app.php?chapter=9_12&index=2

代码编写

下面是微信支付的基本代码,网络请求我使用的是okhttp框架并作了一些封装,但本文重点是微信支付,请使用其它网络框架的的同学不要介意。在这之前我假设已经通过应用服务器接口拿到了订单号及商品详情等业务数据,以参数形式传到微信支付的入口方法。

// 调用服务器接口获取相关数据后发起“统一下单”微信接口
private void payByWechat(String tradeNo, String desp, String notifyUrl, String amount) {
    // 构建产品参数, 结果为XML字符串
    String xmlParam = buildProductArgs(tradeNo, desp, notifyUrl, amount);
    // 调用“统一下单”接口获取预支付ID
    OkHttpClientManager.postAsyncXml(ApiHelper.URL_WECHAT_PAY_UNIFIEDORDER, xmlParam, new Listener<String>() {
        @Override
        public void onSuccess(String response) {
            try {
                Map<String, String> resultMap = decodeXml(response);
                String prePayOrderId = resultMap.get("prepay_id");
                mIWXAPI.sendReq(buildWechatPayReq(prePayOrderId));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    });
}

上面这个方法包含了微信支付的整个过程,首先是构建统一下单接口所需要的XML数据;接着使用该XML数据为参数调用统一下单接口;然后分析统一下单接口响应数据得到预支付交易单ID;然后再使用获取得的预支付交易单ID及其它数据构建App支付参数对象;最后发起App支付请求。

下面分开来看这几步的代码。

构建统一下单接口参数XML

/** * 构建产品参数, 结果为符合微信“统一下单”接口的XML串 * * @param tradeNo 系统订单号 * @param desp 描述 * @param notifyUrl 回调URL * @param amount 金额 * @return XML结构字符串 */
private String buildProductArgs(String tradeNo, String desp, String notifyUrl, String amount) {
    // 构建基本的参数列表
    List<TwoTuple<String, String>> paramList = new ArrayList<>();
    paramList.add(new TwoTuple<>("appid", Constants.WX_APP_ID));
    paramList.add(new TwoTuple<>("body", desp));
    paramList.add(new TwoTuple<>("mch_id", Constants.WX_PARTNER_ID));
    paramList.add(new TwoTuple<>("nonce_str", UUID.randomUUID().toString().replace("-", "")));
    paramList.add(new TwoTuple<>("notify_url", notifyUrl));
    paramList.add(new TwoTuple<>("out_trade_no", tradeNo));
    paramList.add(new TwoTuple<>("spbill_create_ip", "127.0.0.1"));
    paramList.add(new TwoTuple<>("total_fee", amount));
    paramList.add(new TwoTuple<>("trade_type", "APP"));

    // 获取MD5签名并追加到参数列表中
    String sign = generateWechatMD5Signature(paramList);
    paramList.add(new TwoTuple<>("sign", sign));

    // 构建XML参数
    StringBuilder xmlBuilder = new StringBuilder();
    xmlBuilder.append("<xml>");
    for (TwoTuple<String, String> paramTuple : paramList) {
        xmlBuilder.append("<").append(paramTuple.first).append(">");
        xmlBuilder.append(paramTuple.second);
        xmlBuilder.append("</").append(paramTuple.first).append(">");
    }
    xmlBuilder.append("</xml>");

    return xmlBuilder.toString();
}

从上面的代码可以看出,首先按照要求将所需要的参数组织好,然后生成签名并将签名也加入参数中,最后将所有参数(连带签名)组织成规定格式的XML字符串即可。这里只是大体说一下步骤,至于具体要求请参数微信支付官方文档,上面的说明很是详细。

其中TwoTuple是我自定义的一个工具类(二元组)。

代码中还有一处要注意的是签名,这里我抽出来了个方法,因为发起App支付的时候也需要用到签名,而且步骤是一样的,下面是签名方法代码

// 根据参数列表生成MD5签名
private String generateWechatMD5Signature(List<TwoTuple<String, String>> paramList) {
    StringBuilder sb = new StringBuilder();
    for (TwoTuple<String, String> paramTuple : paramList) {
        sb.append(paramTuple.first);
        sb.append('=');
        sb.append(paramTuple.second);
        sb.append('&');
    }
    sb.append("key=").append(Constants.WX_SECURITY_KEY);

    return MD5Utils.getMD5Code(sb.toString()).toUpperCase();
}

很简单的代码,无非是将组织的参数以&符号连接起来,最后调用MD5算法生成签名并转换成大写形式,至于MD5算法就不放代码了,网上随便一找即可。

通过上面的代码我们拿到了目标XML数据,这个XML字符串将用于发起统一下单请求。统一下单使用HTTP协议,这里就不放这部分代码了,要不会显得特别乱;我在应用中使用的是okhttp框架,发起XML请求还是比较简单的。

假设我们已经成功发起统一下单请求,代码回调到我们的监听器中,接下来需要我们分析响应获取预支付交易单ID

解析统一下单响应XML

统一下单响应也是XML数据,响应示例:

<xml>
   <return_code><![CDATA[SUCCESS]]></return_code>
   <return_msg><![CDATA[OK]]></return_msg>
   <appid><![CDATA[wx2421b1c4370ec43b]]></appid>
   <mch_id><![CDATA[10000100]]></mch_id>
   <nonce_str><![CDATA[IITRi8Iabbblz1Jc]]></nonce_str>
   <sign><![CDATA[7921E432F65EB8ED0CE9755F0E86D72F]]></sign>
   <result_code><![CDATA[SUCCESS]]></result_code>
   <prepay_id><![CDATA[wx201411101639507cbf6ffd8b0779950874]]></prepay_id>
   <trade_type><![CDATA[JSAPI]]></trade_type>
</xml>

解析代码如下

// 解析“统一下单”接口返回的XML获取预支付订单ID
private Map<String, String> decodeXml(String xml) throws Exception {
    Map<String, String> resultMap = new HashMap<>();
    XmlPullParser parser = Xml.newPullParser();
    parser.setInput(new StringReader(xml));
    int event = parser.getEventType();
    while (event != XmlPullParser.END_DOCUMENT) {
        String nodeName = parser.getName();
        switch (event) {
            case XmlPullParser.START_DOCUMENT:
                break;
            case XmlPullParser.START_TAG:
                if (!"xml".equals(nodeName)) {
                    resultMap.put(nodeName, parser.nextText());
                }
                break;
            case XmlPullParser.END_TAG:
                break;
        }
        event = parser.next();
    }
    return resultMap;
}

这部分是常规的XML解析,逻辑很简单,就是将响应数据放入Map中返回;至于返回的详细数据在官方文档中有详细说明,也有示例,这里不再赘述了。

正常情况下,为了安全,在解析获取的响应后我们可以拿到微信的签名,还需要验证签名,这一步在这里我没有做。

通过这一步我们拿到了预支付交易单ID(在上面代码返回的Map中,key为prepare_id),接下来可以构建参数并发起App支付请求了。

构建微信App支付请求对象

// 构建微信支付请求对象
private PayReq buildWechatPayReq(String preOrderId) {
    PayReq payReq = new PayReq();
    payReq.appId = Constants.WX_APP_ID;
    payReq.partnerId = Constants.WX_PARTNER_ID;
    payReq.prepayId = preOrderId;
    payReq.packageValue = "Sign=WXPay";
    payReq.nonceStr = UUID.randomUUID().toString().replace("-", "");
    payReq.timeStamp = String.valueOf(System.currentTimeMillis() / 1000);

    // 构建基本的参数列表
    List<TwoTuple<String, String>> paramList = new ArrayList<>();
    paramList.add(new TwoTuple<>("appid", payReq.appId));
    paramList.add(new TwoTuple<>("noncestr", payReq.nonceStr));
    paramList.add(new TwoTuple<>("package", payReq.packageValue));
    paramList.add(new TwoTuple<>("partnerid", payReq.partnerId));
    paramList.add(new TwoTuple<>("prepayid", payReq.prepayId));
    paramList.add(new TwoTuple<>("timestamp", payReq.timeStamp));

    payReq.sign = generateWechatMD5Signature(paramList);

    return payReq;
}

创建支付请求对象PayReq,并将需要的各参数组织好返回即可。本步的签名与统一下单参数一样,唯一不同的就是具体参数项不同,但同样要注意参数名ASCII码从小到大排序(字典序)

发起微信App支付请求

发起App支付请求使用 IWXAPI 类的 sendReq 方法, IWXAPI 实例创建注册代码如下所示:

mIWXAPI = WXAPIFactory.createWXAPI(getActivity(), null);
mIWXAPI.registerApp(Constants.WX_APP_ID);

支付结果回调

通过以上几步,如果所有参数都正确,在发起App支付请求后会启动微信支付,要求用户输入支付密码并确认支付,正常情况下支付会成功,然后微信支付会调用我们程序。

至于微信支付的响应接收要严格按照规范来做,要不无法成功调用。我们需要在应用包中新建一个wxapi包(在AndroidManifest.xml文件中指定的包下)并创建一个 WXPayEntryActivity 类(包名或类名不一致会造成无法回调)。

在WXPayEntryActivity类中实现onResp函数,支付完成后,微信APP会返回到商户APP并回调onResp函数,开发者需要在该函数中接收通知,判断返回错误码,如果支付成功则去后台查询支付结果再展示用户实际支付结果。注意一定不能以客户端返回作为用户支付的结果,应以服务器端的接收的支付通知或查询API返回的结果为准。

示例程序

package com.witmoon.eab.wxapi;

import android.content.Intent;
import android.os.Bundle;

import com.tencent.mm.sdk.constants.ConstantsAPI;
import com.tencent.mm.sdk.modelbase.BaseReq;
import com.tencent.mm.sdk.modelbase.BaseResp;
import com.tencent.mm.sdk.openapi.IWXAPI;
import com.tencent.mm.sdk.openapi.IWXAPIEventHandler;
import com.tencent.mm.sdk.openapi.WXAPIFactory;
import com.witmoon.eab.ui.base.BaseActivity;
import com.witmoon.eab.util.Constants;
import com.witmoon.svprogresshud.SVProgressHUD;

/** * 微信支付回调Activity * Created by zhyh on 2015/11/28. */
public class WXPayEntryActivity extends BaseActivity implements IWXAPIEventHandler {

    private IWXAPI api;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        api = WXAPIFactory.createWXAPI(this, Constants.WX_APP_ID);
        api.handleIntent(getIntent(), this);
    }

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        setIntent(intent);
        api.handleIntent(intent, this);
    }

    @Override
    public void onReq(BaseReq baseReq) {
    }

    @Override
    public void onResp(BaseResp resp) {
        if (resp.getType() == ConstantsAPI.COMMAND_PAY_BY_WX) {
            int errCode = resp.errCode;
            if (errCode == -1) {
                SVProgressHUD.showErrorWithStatus(this, "支付出现错误");
            } else if (errCode == 0) {
                SVProgressHUD.showSuccessWithStatus(this, "支付完成");
            }
            finish();
        }
    }
}

详细说明可参照:https://pay.weixin.qq.com/wiki/doc/api/app.php?chapter=8_5

另外一种回调方式是在参数中提供notify_url参数,该参数指定一个服务器地址即可,微信在支付完成后会调用并传递相关参数。

该部分文档:https://pay.weixin.qq.com/wiki/doc/api/app.php?chapter=9_7&index=3

支付完成后,微信会把相关支付结果和用户信息发送给商户,商户需要接收处理,并返回应答。

对后台通知交互时,如果微信收到商户的应答不是成功或超时,微信认为通知失败,微信会通过一定的策略(如30分钟共8次)定期重新发起通知,尽可能提高通知的成功率,但微信不保证通知最终能成功。 (通知频率为15/15/30/180/1800/1800/1800/1800/3600,单位:秒)

注意:同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。

推荐的做法是,当收到通知进行处理时,首先检查对应业务数据的状态,判断该通知是否已经处理过,如果没有处理过再进行处理,如果处理过直接返回结果成功。在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。

特别提醒:商户系统对于支付结果通知的内容一定要做签名验证,防止数据泄漏导致出现“假通知”,造成资金损失。

总结

总地来说,微信支付相对于支付宝支付还是显得复杂一些,尤其是统一下单要求以XML形式传递参数;但是如果能理顺其步骤也不是特别难。下面总结一下需要注意的内容,包括上面描述和代码没有提到的或比较重要的。

  • 参数签名时一定要保证有序(按参数名ASCII码从小到大排序),签名要转换成大写形式
  • 参数名区分大小写
  • 验证调用返回或微信主动通知签名时,传送的sign参数不参与签名,将生成的签名与该sign值作校验
  • 时间戮以秒为单位,Java中获取当前时间是以毫秒为单位的,因此需要转换一下
  • 金额以分为单位,其值不能带有小数
  • 随机字符串不能超过32位,如果使用UUID生成,需要截取(我在代码中将-去掉)

补充

在上面的代码中我使用了一个二元组的工具类TwoTuple,非常简单,就是用来携带两个元素的,代码如下:

/** * 两个元素的元组,用于在一个方法里返回两种类型的值 * Created by zhyh on 2015/5/5. */
public class TwoTuple<A, B> {

    public final A first;
    public final B second;

    public TwoTuple(A a, B b) {
        first = a;
        second = b;
    }

    /** * 创建一个二元组, 用所给参数初始化 * @param a 第一个参数 * @param b 第二个参数 * @param <A> 第一个参数的类型 * @param <B> 第二参数的类型 * @return 包含所给参数的二元组 */
    public static <A, B> TwoTuple<A, B> tuple(A a, B b) {
        return new TwoTuple<>(a, b);
    }
}

© 著作权归作者所有

zhyihui
粉丝 0
博文 5
码字总数 10549
作品 0
德州
程序员
私信 提问
Android项目实战(五十):微信支付 坑总结

大部分APP必备需求,使用总结  Android接入文章在此:官方文档   文档很简单,Android分为四步: 1、后台配置 2、Android 内 注册appId 3、Android 内 调起支付 4、Android 内 支付结果回...

听着music睡
2018/08/30
0
0
android产品二次开发

在已有的app产品基础上做二次开发: 1、对接13个接口(接口已经设计好,只需要对接,都是http请求,就是一般的发请求拿数据) 2、福利界面:只需要一个webview加载url就好 3、充值界面(集成...

电饭锅烧豆腐干
2017/08/23
61
1
Android开发:使用EasyPay打造全能移动支付框架

前言 在这之前,笔者发布了两篇移动app支付相关博文,得到一些关注,但是由于博文中代码零碎,有些读者私信博主,以及加笔者qq咨询相关问题。考虑到这些,笔者把之前项目中的支付相关代码从业...

MichaelX
2018/10/30
0
0
应用内支付-个人开发者福利

其实很多应用都可以实现盈利。 比如某些特定功能的开放权限、游戏内商品或道具的购买、VIP会员的购买和续费或整个应用的付费使用等等。 相信了解或接触过这些支付文档的人应该知道,支付宝、...

2013020735
2017/02/10
624
2
android利用微信intent-filter进行支付

最近项目内嵌入了webview 访问了“有赞” 平台进行交易,iOS 上默认就可以直接打开微信支付,我们Android客户端也他们说没有做任何处理,现在是无法跳转支付。也就是说用我们的app访问一个网...

AlienJun
2016/05/03
1K
0

没有更多内容

加载失败,请刷新页面

加载更多

DDD(五)

1、引言 之前学习了解了DDD中实体这一概念,那么接下来需要了解的就是值对象、唯一标识。值对象,值就是数字1、2、3,字符串“1”,“2”,“3”,值时对象的特征,对象是一个事物的具体描述...

MrYuZixian
今天
3
0
数据库中间件MyCat

什么是MyCat? 查看官网的介绍是这样说的 一个彻底开源的,面向企业应用开发的大数据库集群 支持事务、ACID、可以替代MySQL的加强版数据库 一个可以视为MySQL集群的企业级数据库,用来替代昂贵...

沉浮_
今天
4
0
解决Mac下VSCode打开zsh乱码

1.乱码问题 iTerm2终端使用Zsh,并且配置Zsh主题,该主题主题需要安装字体来支持箭头效果,在iTerm2中设置这个字体,但是VSCode里这个箭头还是显示乱码。 iTerm2展示如下: VSCode展示如下: 2...

HelloDeveloper
今天
6
0
常用物流快递单号查询接口种类及对接方法

目前快递查询接口有两种方式可以对接,一是和顺丰、圆通、中通、天天、韵达、德邦这些快递公司一一对接接口,二是和快递鸟这样第三方集成接口一次性对接多家常用快递。第一种耗费时间长,但是...

程序的小猿
今天
6
0
Python机器学习之数据探索可视化库yellowbrick

背景介绍 从学sklearn时,除了算法的坎要过,还得学习matplotlib可视化,对我的实践应用而言,可视化更重要一些,然而matplotlib的易用性和美观性确实不敢恭维。陆续使用过plotly、seaborn,...

yeayee
今天
8
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部