公司有一组业务系统绑定了微信服务号,最近因为运营主体调整,旧的微信服务号要迁移到新的微信服务号上,于是产生了一系列问题。

业务系统中的用户只能使用微信授权登录,所以数据库中存储了用户的 openid。而每一个微信号对于不同的微信服务号,openid 都是不同的,而且服务号迁移后,用户的 openid 会发生变化。

虽然说微信有 unionid 机制(unionid 机制说明:https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/union-id.html),但是由于种种不可抗力的原因,所有的微信服务号都没办法绑定到微信开放平台上,也获取不到 unionid,因此只能另外想办法,使服务号迁移后,业务系统中用户的 openid 也随之换新。

好在微信官方提供了 openid 转换接口,支持服务号建立迁移关系后,将旧 openid 转换为新 openid 的功能。

openid 转换接口说明:https://kf.qq.com/faq/1901177NrqMr190117nqYJze.html

需要注意,转换 openid 接口可在账号迁移审核完成后、双方管理员确认迁移前开始调用,并最多保留15天。若账号迁移没完成,调用时无返回结果或报错。账号迁移15天后,该转换接口将会失效、无法拉取到数据。

附公众号迁移流程:

依据微信提供的 openid 转换接口,写了一个转换新旧 openid 的程序:

@SpringBootTest
class ChangeopenidApplicationTests {

    // 业务系统的数据操作Mapper
    @Resource
    ChangeOpenidMapper changeOpenidMapper;

    private static final String newAppId = "";  // 新号appId
    private static final String newAppSecret = "";  // 新号appSecret
    private static final String newAccessToken = "";    // 新号access_token,通过getNewAccessToken方法获取
    private static final String oldAppId = "";  // 旧号appId

    /**
     * 获取新号access_token
     */
    @Test
    void getNewAccessToken() {
        HttpRequest request = HttpRequest
                .get("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + newAppId
                        + "&secret=" + newAppSecret);
        String resultStr = request.execute().body();

        // 获取到新号access_token后,赋值给newAccessToken,后续转换需要使用
        System.out.println("access_token => " + resultStr);
    }

    /**
     * 转换新旧openid
     * @param openidList 旧openid列表
     */
    JSONArray changeOpenid(String[] openidList) {
        HttpRequest request = HttpRequest
                .post("https://api.weixin.qq.com/cgi-bin/changeopenid?access_token=" + newAccessToken);
        String requestJson = "{\n" +
                "  \"from_appid\": \"" + oldAppId + "\",\n" +
                "  \"openid_list\": " + JSONUtil.toJsonStr(openidList) +
                "}";
        request.body(requestJson);

        // 调用openid转换接口批量转换
        String resultStr = request.execute().body();

        JSONObject resultJson = JSONUtil.parseObj(resultStr);
        return resultJson.getJSONArray("result_list");
    }

    /**
     * 批量转换
     */
    @Test
    void runBatch() {
        while (true) {
            // 根据业务系统的设计,获取旧Openid列表
            List<Map<String, Object>> tomUcenterMemberList = changeOpenidMapper.getTomUcenterMemberList();
            List<String> openidList = new ArrayList<>();
            for (Map<String, Object> tomUcenterMember : tomUcenterMemberList) {
                openidList.add((String) tomUcenterMember.get("openid"));
            }

            // 开始批量转换
            JSONArray resultList = changeOpenid(openidList.toArray(new String[0]));
            for (int i = 0; i < resultList.size(); i++) {
                // 获取转换结果
                String errMsg = resultList.getJSONObject(i).getStr("err_msg");
                String oldOpenid = resultList.getJSONObject(i).getStr("ori_openid");
                String newOpenid = resultList.getJSONObject(i).getStr("new_openid");

                if (!"ok".equals(errMsg)) {
                    // 转换失败,有可能是微信号注销/封禁等极端情况,保存为-1即可
                    System.out.println("旧Openid=>" + oldOpenid + "转换错误-1");
                    changeOpenidMapper.updateTomUcenterMember(oldOpenid, "-1");
                } else {
                    // 将业务系统中所有的旧openid替换为新openid
                    int result = changeOpenidMapper.updateTomUcenterMember(oldOpenid, newOpenid);
                }
            }

            if (tomUcenterMemberList.isEmpty()) {
                break;
            }
        }
    }

    /**
     * 多线程批量转换
     */
    @Test
    void runBatchThread() throws InterruptedException {
        int threadCount = 30;
        List<Thread> threads = new ArrayList<>();

        for (int i = 0; i < threadCount; i++) {
            Thread thread = new Thread(this::runBatch);
            thread.start();
            threads.add(thread);

            if (i < threadCount - 1) {
                Thread.sleep(2000);
            }
        }
        
        for (Thread thread : threads) {
            thread.join();
        }
    }

}