碰到这样一个需求,PC端生成公众号二维码,用户用手机微信扫描,如果用户未关注公众号,则关注公众号后自动登录,如果用户已经关注过公众号,则直接登录。
前端Vue,后端Java实现
分2步来完成:
1.生成带参数的临时二维码
2. 公众号后台服务监听扫码事件并获取用户信息
https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_event_pushes.html
1.生成带参数的临时二维码
获取临时二维码的接口为微信官方提供的
https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=TOKEN,注意为POST方式
- @RequestMapping("/getQRCode")
- public Map<String,Object> getQRCode(HttpServletRequest request,String code) {
- Map<String,Object> map = new HashMap<String,Object>();
- String code_url = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=TOKEN";
- try{
- //获取公众号的Token,自行实现,我是定任务获取,存到了数据库
- PcAccessToken token = accessTokenService.findPcToken("");
- code_url = code_url.replace("TOKEN", token.getAccessToken());
- String scene_id = (System.currentTimeMillis()+"").substring(0,10);
- JSONObject json = new JSONObject();
- json.put("expire_seconds", "36000");
- json.put("action_name", "QR_SCENE");
- JSONObject info = new JSONObject();
- JSONObject scen = new JSONObject();
- scen.put("scene_id", scene_id);
- info.put("scene", scen);
- json.put("action_info", info);
- String data = HttpUtil.requestPost(code_url, json.toString());
- JSONObject resp = JSONObject.fromObject(data);
- System.out.println("data=="+data);
- map.put("data", resp);
- map.put("scene_id", scene_id);
- map.put("succ", "1");
- } catch(Exception e){
- map.put("succ", "0");
- e.printStackTrace();
- }
- return map;
- }
请求微信接口返回的数据,url参数即为二维码地址,和scene_id一起返回给前端。
前端vue使用 qrcodejs2组件,将url转为二维码图片,显示在相应位置。请求到二维码后,用sceneId查询扫码登录是否成功。
-
- <div class="" id="qrcode" ref="qrcode"></div>
-
- import QRCode from ''qrcodejs2''
- import loginApi from ''@/api/login'';
- data(){
- return{
- url: '''', //二维码地址
- sceneId:'''',//二维码带的参数,用来判断是那个用户扫码的
- timer: null,//定时器
- }
- },
- created(){
- getLoginQRCode();
- },
- methods:{
- async getLoginQRCode(){
- this.url = '''';
- window.clearInterval(this.timer);
- const res = await loginApi.getQRCode();
- console.log(''44res'',res)
- if(res.data.succ == ''1''){
- this.url = res.data.data.url;
- this.sceneId = res.data.scene_id;
- console.log(''sceneId'',res.data.scene_id);
- this.timer = setInterval(this.getUserByScene, 1000);
- this.$nextTick(() => {
- this.getQrcode(this.url)
- })
- }
- },
- //根据 sceneId 轮询查询用户信息,判断用户是否登录成功
- async getUserByScene(){
- if(!this.sceneId) return;
- const res = await loginApi.getUserBySceneId({sceneId: this.sceneId});
- console.log(''getUser'', res);
- if(res.data.succ == ''1'' && res.data.user){
- this.user = res.data.user;
- window.clearInterval(this.timer);
- this.url = '''';
- this.sceneId = '''';
- //登录成功,处理登录成功逻辑
- this.$Message.success(''登录成功'')
- }
- },
- getQrcode (text) {
- this.$refs.qrcode.innerHTML = '''' // 清除
- const qrcode = new QRCode(''qrcode'', {
- width: 160,
- height: 160,
- text: text, // 二维码地址
- colorDark: ''#000'',
- colorLight: ''#fff''
- })
- return qrcode
- },
- }
2.公众号后台服务监听扫码事件并获取用户信息
这块要在微信公众平台配置服务地址信息
开发—基本配置

服务器配置信息提交的时候要验证 TOKEN, 验证通过才能提交。checkToken接口GET请求用来验证TOKEN,
- import java.security.MessageDigest;
- //...
- @RequestMapping(value="/checkToken",method=RequestMethod.GET)
- public String checkToken(HttpServletRequest req, HttpServletResponse resp)throws IOException {
- req.setCharacterEncoding("UTF-8");
- resp.setCharacterEncoding("UTF-8");
- String message = "success";
- String signature = req.getParameter("signature");
- String timestamp = req.getParameter("timestamp");
- String nonce = req.getParameter("nonce");
- String echostr = req.getParameter("echostr");
- try {
- String[] arr = {"sjyx", timestamp, nonce};
- Arrays.sort(arr);
-
- StringBuilder content = new StringBuilder();
- for (int i = 0; i < arr.length; i++) {
- content.append(arr[i]);
- }
- //sha1Hex 加密
- MessageDigest md = null;
- String temp = null;
- md = MessageDigest.getInstance("SHA-1");
- byte[] digest = md.digest(content.toString().getBytes());
- temp = byteToStr(digest);
- if ((temp.toLowerCase()).equals(signature)){
- return echostr;
- }
- return null;
- } catch (Exception e) {
- e.printStackTrace();
- }
- return message;
- }
- private static String byteToStr(byte[] byteArray){
- String strDigest = "";
- for (int i = 0; i < byteArray.length; i++) {
- strDigest += byteToHexStr(byteArray[i]);
- }
- return strDigest;
- }
- private static String byteToHexStr(byte mByte){
- char[] Digit = {''0'', ''1'', ''2'', ''3'', ''4'', ''5'', ''6'', ''7'', ''8'', ''9'', ''A'',''B'', ''C'', ''D'', ''E'', ''F'' };
- char[] tempArr = new char[2];
- tempArr[0] = Digit[(mByte >>> 4)& 0X0F];
- tempArr[1] = Digit[mByte & 0X0F];
- String s = new String(tempArr);
- return s;
- }
checkToken接口POST请求用来监听扫码事件,并获取用户信息
- @RequestMapping(value="/checkToken",method=RequestMethod.POST)
- public String responseEvent(HttpServletRequest req, HttpServletResponse resp) {
- String message = "success";
- try {
- //把微信返回的xml信息转义成map
- Map<String, String> map = XmlUtil.xmlToMap(req);
- System.out.println("微信接收到的消息为:"+map.toString());
- String openId = map.get("FromUserName");//消息来源用户标识
- String toUserName = map.get("ToUserName");//消息目的用户标识
- String msgType = map.get("MsgType");//消息类型(event或者text)
- String EventKey = map.get("EventKey");//消息来源用户标识
- if(EventKey.contains("_")){ //scene_id首次关注会有前缀,去掉就和生成二维码时带的一致了
- EventKey = EventKey.split("_")[1];
- }
- String scene_Id = EventKey;
- String eventType = map.get("Event");//事件类型
- if("subscribe".equals(eventType) || "SCAN".equals(eventType) ){ //关注 or 浏览
- //获取缓存的最新的公众号 token
- PcAccessToken token = accessTokenService.findPcToken("");
- String USER_INFO_URL = "https://api.weixin.qq.com/cgi-bin/user/info?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN";
- USER_INFO_URL = USER_INFO_URL.replace("ACCESS_TOKEN", token.getAccessToken());
- USER_INFO_URL = USER_INFO_URL.replace("OPENID", openId);
- String info = HttpUtil.getRequest(USER_INFO_URL);
- System.out.println("----user-----"+info);
- JSONObject obj = JSONObject.fromObject(info);
- String unionid = obj.getString("unionid");
- String nickname = obj.getString("nickname");
- String headimgurl = obj.getString("headimgurl");
- String city = obj.getString("city");
- String province = obj.getString("province");
- String qr_scene = obj.getString("qr_scene");
- //获取到用户信息后处理登录逻辑,比如保存用户信息,用户表scene_id 字段,保存二维码的scend_id,供前端查询用户登录情况。
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- return message;
- }
xml转Map工具类
- import java.io.IOException;
- import java.io.InputStream;
- import java.util.HashMap;
- import java.util.List;
- import java.util.Map;
- import javax.servlet.http.HttpServletRequest;
- import org.dom4j.Document;
- import org.dom4j.DocumentException;
- import org.dom4j.Element;
- import org.dom4j.io.SAXReader;
- public class XmlUtil {
- /*
- * xml转map
- */
- public static Map<String, String> xmlToMap(HttpServletRequest request) throws IOException, DocumentException{
- HashMap<String, String> map = new HashMap<String,String>();
- SAXReader reader = new SAXReader();
-
- InputStream ins = request.getInputStream();
- Document doc = reader.read(ins);
-
- Element root = doc.getRootElement();
- @SuppressWarnings("unchecked")
- List<Element> list = (List<Element>)root.elements();
-
- for(Element e:list){
- map.put(e.getName(), e.getText());
- }
- ins.close();
- return map;
- }
- }