How to implement the WeChat public platform (local test account) function, dynamic QR code, authorized acquisition code, replacement of openId, the whole operation process, with pitfall avoidance, super detailed~

Table of contents

1|WeChat platform operation process

1. Preparation

2. Process sorting

3. Specific implementation (code appendix

1. Generate QR code on the front-end page (vue-elementui)

2. Authorization page

3. The Java backend receives the code parameter and obtains the user's openId.

2|WeChat public development QR code

1. Solve the # problem of H5 jump path

2. Transmit information in the QR code and obtain it on the authorization page

Three|Supplement


1|WeChat platform operation process

1. Preparation

 JS callback needs to be used when obtaining user information, etc., and is not used currently. During the test, you must scan the code and follow the test public account to test the dynamic QR code formed after scanning. (Note: The domain name itself has no specific meaning and can be defined casually, or the network domain name can be obtained through the intranet penetration tool, which can be consistent with the subsequent network callback domain name)

  1. First we need to register a test account on the WeChat public platform. Jumpable links:WeChat public platform
  2. On the successful login page, the appID and appsecret need to be used later (pay attention to protecting privacy). Because this is a local test, URL and Token information are not required.

Click "Modify" in the network authorized user information to modify the callback domain name (be careful not to add http:// here).

 Here I will talk about my thoughts on writing the network authorization callback domain name and the problems I encountered:

 At the beginning, I searched a lot of information on the Internet, and then found that it was all coded, and I didn’t know what to do. Then based on official documents and online information, I wrote a custom domain name "www.hanying.com" here. ;, Then keep the JS interface consistent, then I will generate redirect_uri=www.hanying.com in the text path of the QR code, and map the IP address to this domain name in the local hosts file, and then I call nginx static web The server listens on port 80. If it detects access to this path, it will jump to the nginx/html/bangding/xx.html page. Everything is going well here, but when I click the confirmation button on the HTML page to send an ajax request to the backend to pass the code value, it cannot be sent. Later, it boiled down to three simple problems: First, I did not introduce JS or JQuery into the html page, so using $.ajax had no effect; second, my mobile phone used traffic, and my computer used my company wife. It is not under the LAN, and the path where ajax sends the request is the local (ip) path, so it cannot accept success; thirdly, scope=SCOPE in my path has wrong permissions, so after solving the 10003 error, a 10005 error occurred. If a 10003 error occurs when scanning the QR code here, check whether the configurations in the following three places are consistent, whether http:// is added to the callback domain name, and whether the url path is encoded (the url path needs to be encoded). If a 10005 error occurs, it means insufficient permissions. Check the scope attribute to see if it is scope=snsapi_base.

2. Process sorting

You can refer to the official development documents:WeChat web development-webpage authorization

  1. Generate a QR code (I generated it through the front-end), and the user will be redirected to the custom authorization page when accessing it.
  2. The user jumps to the authorization page and obtains the code
  3. After the backend obtains the code, it exchanges the code for web page authorization access_token and openId.
  4. Save openId to the database (some require other operations, please refer to the official documentation for specific operations)

 code: WeChat authorization code, like a temporary ticket, is used to access the WeChat server to obtain the user's openId and token token.

state: status code.

scope: Permission domain, there are two permissions in WeChat authorization, one is snsapi_base, and the other is to obtain user information snsapi_userinfo.

appId: The unique ID of the visited WeChat official account.

redirect_uri: callback domain name, which can be linked to a customized authorization page, and the code information is obtained when the user accesses (URL encoding is required).

3. Specific implementation (code appendix

I referenced a lot, and the main reference was other blogs. Although I really wanted to attach the link, I couldn't find it after searching for a long time.

Note: It is mainly methods. The page is written according to your own needs. If you use JQuery, remember to include the package.

1. Generate QR code on the front-end page (vue-elementui)

<el-button 
            size="mini" 
            type="text" 
            icon="el-icon-check"
            @click="bangding(scope.row)"
            >绑定</el-button
          >
<el-dialog
      title="微信扫码绑定用户"
      :visible.sync="isShowCard"
      width="400px"
      center
      :before-close="jieBangClose"
    >
    <!-- 存放二维码的地方 -->
      <div style="display: flex; justify-content: center">
        <div id="qrCode" ref="qrCodeDiv"></div>
      </div>
    </el-dialog>
import QRCode from "qrcodejs2"; //引入生成二维码插件

data() {
    return {
        //用的时候把AppID,REDIRECT_URI 改了就可以
         url: "https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=snsapi_base&state=STATE#wechat_redirect"
    }
}

 methods: {
     jieBangClose(){
      this.isShowCard = false;
      this.getList();
    },
/** 绑定微信(生成动态二维码) */
    bangding(row) {
      const memberId = row.id;
      this.isShowCard = true;
      this.createQRCode(memberId);
    },
  createQRCode(id) {
      this.$nextTick(() => {
        this.$refs.qrCodeDiv.innerHTML = ""; //二维码清除
        this.bangdingId = id;
        new QRCode(this.$refs.qrCodeDiv, {
          text: this.url, //二维码链接,参数是否添加看需求
          width: 200, //二维码宽度
          height: 200, //二维码高度
          colorDark: "#333333", //二维码颜色
          colorLight: "#ffffff", //二维码背景色
          correctLevel: QRCode.CorrectLevel.L, //容错率,L/M/H
        });
      });
    }
 }

2. Authorization page

<button onclick="getCode()" id="right">确认</button>
<script>

    function GetQueryString(code) {
        var reg = new RegExp("(^|&)" + code + "=([^&]*)(&|$)");
        var r = window.location.search.substr(1).match(reg);
        if (r != null) return unescape(r[2]); return null;
    }

    function getCode() {
        var code = GetQueryString("code");
        alert(code);
        if (code == null || code == "") {
            var url = "http%3A%2F%2F1cee3816.r10.cpolar.top";
            window.location.href = "https://open.weixin.qq.com/connect/oauth2/authorize?" +
                "appid=APPID&redirect_uri=" + url + "&response_type=code" +
                "&scope=snsapi_base&state=STATE#wechat_redirect";
        } else {
            alert('发送')
            $.ajax({
                type: "get", //提交方式 
                url: "192.168.3.62:8080/ubp/member/test/" + code ,//路径 
                success: function (result) {//返回数据根据结果进行相应的处理 
                    alert("成功发送")
                }
            });
        }
    }
</script>

3. The Java backend receives the code parameter and obtains the user's openId.

  • pom file introduces dependencies 

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
</dependency>

<dependency>
    <groupId>net.sf.json-lib</groupId>
    <artifactId>json-lib</artifactId>
    <version>2.3</version>
    <classifier>jdk15</classifier>
</dependency>
  • util and config and manger

CommonUtil:

import com.ruoyi.framework.manager.MyX509TrustManager;
import net.sf.json.JSONObject;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ConnectException;
import java.net.URL;
import java.security.SecureRandom;

/**
 *  微信二维码扫码认证的工具类
 */
public class CommonUtil {
    /**
     * 发送https请求
     * @param requestUrl 请求地址
     * @param requestMethod 请求方式(GET、POST)
     * @param outputStr 提交的数据
     * @return JSONObject(通过JSONObject.get(key)的方式获取json对象的属性值)
     */
    public static JSONObject httpsRequest(String requestUrl, String requestMethod, String outputStr) {
        JSONObject jsonObject = null;
        try {
            // 创建SSLContext对象,并使用我们指定的信任管理器初始化
            TrustManager[] tm = { new MyX509TrustManager() };
            SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");
            sslContext.init(null, tm, new SecureRandom());
            // 从上述SSLContext对象中得到SSLSocketFactory对象
            SSLSocketFactory ssf = sslContext.getSocketFactory();
 
            URL url = new URL(requestUrl);
            HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
            conn.setSSLSocketFactory(ssf);
 
            conn.setDoOutput(true);
            conn.setDoInput(true);
            conn.setUseCaches(false);
            // 设置请求方式(GET/POST)
            conn.setRequestMethod(requestMethod);
 
            // 当outputStr不为null时向输出流写数据
            if (null != outputStr) {
                OutputStream outputStream = conn.getOutputStream();
                // 注意编码格式
                outputStream.write(outputStr.getBytes("UTF-8"));
                outputStream.close();
            }
 
            // 从输入流读取返回内容
            InputStream inputStream = conn.getInputStream();
            InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
            String str = null;
            StringBuffer buffer = new StringBuffer();
            while ((str = bufferedReader.readLine()) != null) {
                buffer.append(str);
            }
 
            // 释放资源
            bufferedReader.close();
            inputStreamReader.close();
            inputStream.close();
            inputStream = null;
            conn.disconnect();
            //jsonObject = JSONObject.fromObject(buffer.toString());
            jsonObject = JSONObject.fromObject(buffer.toString());
        } catch (ConnectException ce) {
            System.out.println("连接超时");
        } catch (Exception e) {
            System.out.println("请求异常");
        }
        return jsonObject;
    }
}

WeChatConfig:

public class WeChatConfig {
 
    /**
     * 微信服务号APPID
     */
    public static String APPID=APPID;
    /**
     * appsecret
     */
    public static String  APPSECRECT=APPSECRECT;
    /**
     * grant_type
     */
    public static String  GRANTTYPE="authorization_code";
 
 
 
}

Eat:

import javax.net.ssl.X509TrustManager;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

/**
 *  微信扫码认证的 manger
 */
public class MyX509TrustManager implements X509TrustManager {
 
    // 检查客户端证书
    public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
    }
 
    // 检查服务器端证书
    public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
    }
 
    // 返回受信任的X509证书数组
    public X509Certificate[] getAcceptedIssuers() {
        return null;
    }
}

 Controller,Service 和 ServiceImpl:

Controller:
    
@PostMapping("/getLoginQrCode")
public Object bangding( String code){

    System.out.println("走到了这个方法,打印code值为:" + code);
    return service.insertYyMemberCode(code);
}

Service:
    
    Object insertYyMemberCode(Long id, String name, String code);

ServiceImpl:
    
    @Override
    public Object insertYyMemberCode(Long id, String name, String code) {
        String WX_URL = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code";
        try {
            if ( StringUtils.isBlank(code)) {
                return "用户信息有误,绑定失败。";
            } else {
                String requestUrl = WX_URL.replace("APPID", WeChatConfig.APPID).replace("SECRET", WeChatConfig.APPSECRECT)
                        .replace("CODE", code).replace("authorization_code", WeChatConfig.GRANTTYPE);
                JSONObject jsonObject = CommonUtil.httpsRequest(requestUrl, "GET", null);
                if (jsonObject != null) {
                    try {
                        System.out.println("------jsonObject: " + jsonObject);
                        // 业务操作
                        String access_token = jsonObject.getString("access_token");
                        String openId = jsonObject.getString("openid");

                        return "绑定成功!";
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                } else {
                    System.out.println("code无效");
                    return "code无效";
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("error");
        return "error";
    }

2|WeChat public development QR code

        style:

1. Solve the # problem of H5 jump path

        Problem description: The H5 path will contain # by default, such as the path: http://localhost:8081/#/?payment=15&selected=0 And this #, will cause the nginx reverse proxy to become: http://localhost:8081/?payment=15&selected=0/#

        My first reaction was the nginx proxy path problem, but I searched a lot on the Internet and couldn't find a way to deal with "#", because "#" defaults to the comment content in the nginx.conf file. If Forcibly adding will cause two situations: nginx startup/restart fails; after a successful restart, a blank page appears when accessing.

        The final solution is to ignore "#" in the H5 path. You only need to modify the routing mode to history in the uniapp project -> manifest.json file -> Web configuration ->. (As shown in the picture)

 

Of course, such a simple modification may be accompanied by the print station reporting H5-related errors in a loop (but it does not affect program execution), so I will not solve this problem in detail here. Interested partners can share their own problems and solutions. .​ 

  nginx configuration

server {
        listen          8083;
        //这里使用了虚拟路径,因为是本地测试,所以在hosts里进行了配对,
        //正式上线应当使用正式域名。
        server_name     www.hanying.com;
        
        //这里跳转有两个页面,一个是用来绑定微信公众号,一个是为了根据绑定的openId查询用户信息,
        //出于保护用户信息的原因,不管是哪个页面操作,都要通过临时 code 去后端置换 openId,
        //所以通过不同的跳转路径,获取自己页面需要的 code。
        //注:将两个页面的code分开考虑,自己跳转时获取的code自己用)
        //跳转页面一:
        location /pages/bingPage/bindPage {
            proxy_pass http://192.168.3.62:8081;    
            //最终映射路径:http://192.168.3.62:8081/pages/bingPage/bindPage
        }
        
        //跳转页面二:
        location / {
            proxy_pass http://192.168.3.62:8081;
            //最终映射路径:http://192.168.3.62:8081
        }

2. Transmit information in the QR code and obtain it on the authorization page

Code:

        Bangding method update

/** 绑定微信(生成动态二维码),并将state赋值 */
/** 解释一:这里需要编码两次,因为浏览器会自动解码一次,但不是utf-8解码,所以中文会乱码,编码两次,浏览器解码以后,在用utf-8解码一次,就可以获取到正确的中文信息 */
/** 解释二:之所以要替换掉STATE,是因为微信访问时自动过滤掉了url上的其他信息,只保留了code和state。我最开始写的是 &id=id&name=name&state=STATE, 但是数据直接没有了,更获取不到 */
bangding(row) {
      const id = row.id;
      
      const str = row.code + "," + row.name ;//将id 和 name 获取到组成字符串
      this.url = this.url.replace("STATE", encodeURI(encodeURI(str)));//替换掉STATE
      this.isShowCard = true;
      this.createQRCode(id);
    }

Authorization page (the mui template has been changed here)

<body>
        <header class="mui-bar mui-bar-nav">
            <h1 class="mui-title">绑定</h1>
        </header>
        <div class="mui-content">
            <form id='login-form' class="mui-input-group">
                <div class="mui-input-row">
                    <label>工号</label>
                    <input id='id' type="text" class="mui-input-clear mui-input" placeholder="请输入您的工号">
                </div>
                <div class="mui-input-row">
                    <label>姓名</label>
                    <input id='name' type="text" class="mui-input-clear mui-input" placeholder="请输入您的姓名">
                </div>
            </form>

            <div class="mui-content-padded">
                <button id='login' class="mui-btn mui-btn-block mui-btn-primary">绑定</button>
            </div>
        </div>

        </div>
        <script src="js/mui.min.js"></script>
        <script src="js/mui.enterfocus.js"></script>
        <script src="js/app.js"></script>
        <script>
            (function($, doc) {
                $.init({
                    statusBarBackground: '#f7f7f7'
                });

                var loginButton = doc.getElementById('login');
                var idBox = doc.getElementById('id');
                var name = doc.getElementById('name');
                var str;
                $.ready(function(){
                    strs = GetQueryString("state").split(",")
                    console.log(strs)
                    // str = decodeURIComponent()
                    
                     idBox.value = strs[0]
                     name.value = decodeURIComponent(strs[1])
                })

                loginButton.addEventListener('tap', function(event) {
                    var code = GetQueryString("code")
                    
                    if (code == null || code == "") {
                        var url = "http%3A%2F%2Fwww.hanying.com";
                        window.location.href = "https://open.weixin.qq.com/connect/oauth2/authorize?" +
                            "appid=APPID&redirect_uri=" + url + "&response_type=code" +
                            "&scope=snsapi_base&state=STATE#wechat_redirect";
                    }
                    var loginInfo = {
                        id: idBox.value,
                        name: name.value,
                        code: code
                    };
                    

                    mui.post('http://192.168.3.62:8080/ubp/member/getLoginQrCode', loginInfo, function(data) {
                        alert(data);
                    }, 'json');

                });

                function GetQueryString(code) {
                    var reg = new RegExp("(^|&)" + code + "=([^&]*)(&|$)");
                    var r = window.location.search.substr(1).match(reg);
                    if (r != null) return unescape(r[2]);
                    return null;
                }

            }(mui, document));
        </script>
    </body>

 


Three|Supplement

Supplement 1:

        In the uniapp component, the onload life cycle was later used, and the GetQueryString method was not used. Although the data cannot be obtained when accessing WeChat locally, it can be obtained if the path is directly spliced ​​on uniapp.

        For details, please view uniapp official documentation.

Supplement 2:

        WeChat public platform and WeChat appletThe interfaces for calling and obtaining codes are different, including WeChat public platformThe scope permissions when calling the interface are also different. For details, please refer to the WeChat development platform documentation.

        For the code read by wx.login, please use the auth.code2Session interface to obtain the openid

        Please use auth.getPluginOpenPId to obtain the openid for the code read by wx.pluginLogin.

        If you use the code of the former to call the interface of the latter to obtain the openId, an error code of 10007 (has_no_permisson) will be reported.

Supplement three:

         The code called by each page should be considered separately. This is my latest pitfall. When I bind user information through the binding page and store openId in the database, when I want to query the user's basic information based on openId on the payment page, because it involves user information security, openId cannot be displayed externally, so I have to do it again Use code to replace openId to query the database.

        ​​​​The first thing I thought about here was that to get the code, I had to bind again and jump to the binding page. As a result, the data could not be obtained from the payment page and entered an infinite loop.

        Finally, under the guidance of the boss, you only need to jump to the payment page through WeChat again (refresh the page) when accessing the payment page to obtain the code value. The data is still stored in the state, and then you can send a request to the backend through the code. , replace it with the user's openId again, and query the database.

        Some friends may ask, the code is different every time, why can I get the same openId? This is because a WeChat account corresponding to an official account will generate the same openId according to a fixed encoding method (WeChat binding unique identification id). I actually think that code and openId are compared to public keys, and the private key is easier to understand. Code is used as the private key to generate jwt, and then openId is used as the public key for verification, but I don't know if this understanding is correct.

 

Guess you like

Origin blog.csdn.net/m0_70876920/article/details/132397137