上一篇介绍了PC扫码登录通过微信接口获取不限制的小程序码,此篇介绍剩下的内容

element-ui框架下通过SockJS、stompjs实现PC端扫码登录跳转页面(一)

2、已扫描待确认阶段

流程图中第 6 ~ 10 阶段,我们在 PC 端登录微信时,手机扫码后,PC 端的二维码会变成已扫码,请在手机端确认。这个阶段是移动端跟服务端交互的过程。

具体代码如下:

获取小程序码参数有如下一条

params.put("page", "pages/scanLogin/scanLogin");

扫码后进入小程序的scanLogin页面。

elementUI全屏loading导致页面闪白_spring

<!--scanLogin.wxml-->
<view class="container">
  <block>
	  <image class="logo-image" src="../../static/images/PCAuth.png" mode="widthFix"></image>
	  <view class="welcome">
		欢迎来到
	  </view>
	  <view class="welcome-1">
	  	测试评价平台
	  </view>
	  <view class="welcome-2">
	  	请确认登录
	  </view>
	  <view>
	  	<button class="login-button" bindtap="getUserProfile">
	  		<text>登录</text>
	  	</button>
	  </view>
  </block>
</view>
// scanLogin.js
// 获取应用实例
const app = getApp();
const loginjs = require('../../common/js/login.js')

Page({
	data: {
		userInfo: {},
		hasuserpermistion: false,
		scene: ''
	},

	onShow() {
		//小程序隐藏左上角首页按钮
		wx.hideHomeButton(); 
	},

	onLoad(query) {
		//扫码跳转页面是携带scene参数的
		const scene = decodeURIComponent(query.scene);
		this.setData({
			scene: scene,
		});
		app.globalData.scene = scene;
	},


	//点击登录按钮
	getUserProfile: function(e) {
		var self = app;
		var that = this;
		this.setData({
			loadModal: true
		})
		//首先校验一下本地缓存sessionKey是否过期——设置48小时
		var expiredTime = wx.getStorageSync('EXPIREDTIME');
		var now = +new Date();
		if (expiredTime && now - expiredTime <= 2 * 24 * 60 * 60 * 1000) {
			//openid是小程序用户授权后获取的唯一标识
			self.globalData.openid = wx.getStorageSync('openid');
			var openid = wx.getStorageSync('openid');
			var transformdata = new Object({
				scene: this.data.scene,
				openid: openid
			});
			self._post_form('/base/shopxo/user/jumpLink', transformdata, function(res) {
				if (res.success) {
					var userType = res.result.userType;
					var token = res.result.token;
					//刷新一下缓存有效期
					var expiredTime = +new Date() + 2 * 24 * 60 * 60 * 1000;
					wx.setStorageSync('EXPIREDTIME', expiredTime);
					if (userType == 'custom') {
						wx.redirectTo({
							url: '/pages/webview/webview?token=' + token
						});
					} else {
						wx.reLaunch({
							url: '/pages/welcome/welcome'
						});
					}
				} else {
					wx.showModal({
						content: res.message,
						showCancel: false,
					})
				}
			}, null, function(res) {
				that.setData({
					loadModal: false
				})
			}, null)
		} else {
			//本地缓存过期后需要重新授权登录
			wx.getUserProfile({
				desc: '微信授权登录', // 声明获取用户个人信息后的用途,后续会展示在弹窗中,请谨慎填写
				success: (res) => {
					/* console.log("==获取用户消息==", res) */
					that.setData({
						userInfo: res.userInfo,
						hasuserpermistion: true
					})
					wx.setStorageSync('userInfo', res.userInfo)
					self.globalData.hasuserpermistion = true;
					self.globalData.userInfo = res.userInfo;
					//过期后清除缓存信息
					wx.removeStorageSync('sessionKey');
					wx.removeStorageSync('openid');
					loginjs.PCScanLogin().then(res => {
						self.globalData.sessionKey = res.result.session_key;
						wx.setStorageSync('openid', res.result.openid);
						wx.setStorageSync('sessionKey', res.result.session_key);
						if (res.result.state == 'binding') {
							wx.navigateTo({
								url: '/pages/binding/binding?openid=' + res.result.openid
							});
						}
						if (res.result.state == 'login') {
							var userType = res.result.userType;
							var token = res.result.token;
							//openid有效期2天
							var expiredTime = +new Date() + 2 * 24 * 60 * 60 * 1000;
							wx.setStorageSync('EXPIREDTIME', expiredTime);
							if (userType == 'custom') {
								wx.redirectTo({
									url: '/pages/webview/webview?token=' + token
								});
							} else {
								wx.reLaunch({
									url: '/pages/welcome/welcome'
								});
							}
						}
					});
				},
				fail: err => {
					console.log('wx.getUserProfile==>err', err)
				},
				complete: complete => {
					/* console.log('wx.getUserProfile==>complete', complete) */
					that.setData({
						loadModal: false
					})
				},
			})
		}
		
	},
});

小程序本地缓存失效后,需要一个授权登录微信的操作

const loginjs = require('../../common/js/login.js')
wx.getUserProfile({
	loginjs.PCScanLogin().then(res => {})
})

common/js/login.js中的方法↓↓↓↓↓

function login(callback) {
	var lapp = getApp();
	return new Promise(function(resolve, reject) {
		if (!lapp.globalData.hasuserpermistion) {
			reject(new Error("未授权!"));
			return false;
		}
		wx.login({
			complete: (res) => {
				//发起网络请求star 
				var ssself = lapp;
				var transformdata = new Object({
					JSCODE: res.code,
					nickName: lapp.globalData.userInfo.nickName
				});
				ssself._post_form('/base/shopxo/user/miniProgramLogin', transformdata, function(json) {
					/* console.log("wx.login()----", json) */
					if (json.success) {
						resolve({
							result: json.result
						});
					} else {
						wx.showModal({
							content: json.message,
							showCancel: false,
						});
						/* reject(new Error(json.message)); */
					}
					
				}, null, null);
			},
		})
	})
}

function PCScanLogin(callback) {
	var lapp = getApp();
	return new Promise(function(resolve, reject) {
		if (!lapp.globalData.hasuserpermistion) {
			reject(new Error("未授权!"));
			return false;
		}
		wx.login({
			complete: (res) => {
				//发起网络请求star 
				var ssself = lapp;
				var transformdata = new Object({
					JSCODE: res.code,
					nickName: lapp.globalData.userInfo.nickName,
					scene: lapp.globalData.scene
				});
				ssself._post_form('/base/shopxo/user/PCScanLogin', transformdata, function(res) {
					/* console.log("wx.login()----", res) */
					if (res.success) {
						resolve({
							result: res.result
						});
					} else {
						wx.showModal({
							content: res.message,
							showCancel: false,
						})
					}
				}, null, null);
			},
		})
	})
}
module.exports = {
	login: login,
	PCScanLogin: PCScanLogin
};
3、已确认

流程图中的 第 11 ~ 15 步骤,这是扫码登录的最后阶段,移动端携带上一步骤中获取的唯一标识scene ,确认登录,服务端校对完成后,通过springboot webSocket SimpMessagingTemplate向前端发送消息登录成功,跳转页面,并且PC 端生成一个正式的 token ,后续 PC 端就是持有这个 token 访问服务端。

后端代码如下:
小程序本地缓存未失效情况下,走jumpLink这个方法

self._post_form('/base/shopxo/user/jumpLink', transformdata, function(res)
@Autowired
    private SimpMessagingTemplate simpMessagingTemplate;
    /**
     * 扫码授权后缓存openid---PC端跳转页面
     */
    @PostMapping(value = "/jumpLink")
    public Result<?> jumpLink(@RequestBody JSONObject jsonObject) {
        try {
            JSONObject result = new JSONObject();
            String openId = jsonObject.getString("openid");
            String scene = jsonObject.getString("scene");
            Object sceneObj = redisUtil.get(scene);
            if (sceneObj == null) {
                return Result.error("小程序码已经失效,请重新加载二维码");
            }
            LambdaQueryWrapper<MiniProgramUser> lambdaQuery = new LambdaQueryWrapper<>();
            lambdaQuery.eq(MiniProgramUser::getOpenId, openId);
            MiniProgramUser one = miniProgramUserService.getOne(lambdaQuery);
            if (one == null) {
                return Result.error("扫码登录失败,微信未绑定账号");
            }
            //授权登录成功获取token
            this.getPCUserToken(one, scene, result);
            return Result.OK("扫码登录成功", result);
        } catch (Exception e) {
            log.error("扫码登录失败", e);
            return Result.error("扫码登录失败");
        }
    }
        /**
     * 授权登录获取token
     */
    private JSONObject getPCUserToken(MiniProgramUser programUser, String scene, JSONObject result) {
        String userAuth = programUser.getUserAuth();
        result.put("userType", userAuth);
        String userId = programUser.getUserId();
        SysUser sysUser = sysUserService.getById(userId);
        String username = sysUser.getUsername();
        String password = sysUser.getPassword();

        // 生成token
        String token = JwtUtil.sign(username, password);
        // 设置token缓存有效时间
        redisUtil.set(CommonConstant.PREFIX_USER_TOKEN + token, token);
        redisUtil.expire(CommonConstant.PREFIX_USER_TOKEN + token, JwtUtil.EXPIRE_TIME * 2 / 1000);
        result.put("token", token);
        if (StringUtils.isNotBlank(scene)) {
            //websocket--PC端页面登录跳转
            simpMessagingTemplate.convertAndSendToUser(scene, "/qrCode", JSONObject.toJSONString(result));
        }
        return result;
    }
//websocket--PC端页面登录跳转,前端建立的连接在这里发挥作用PC端跳转
simpMessagingTemplate.convertAndSendToUser(scene, "/qrCode", JSONObject.toJSONString(result));

elementUI全屏loading导致页面闪白_微信小程序_02


springboot webSocket SimpMessagingTemplate单点消息详细介绍

WebStompConfig
package com.lexi.manage.xlink.stomp;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.ChannelRegistration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;

/**
 * EnableWebSocketMessageBroker 注解表明: 这个配置类不仅配置了 WebSocket,还配置了基于代理的 STOMP 消息;
 * registerStompEndpoints() 方法:添加一个服务端点,来接收客户端的连接。将 “/chat” 路径注册为 STOMP 端点。这个路径与之前发送和接收消息的目的路径有所不同,
 * 这是一个端点,客户端在订阅或发布消息到目的地址前,要连接该端点,即用户发送请求 :url=’/127.0.0.1:8080/chat’ 与 STOMP server
 * 进行连接,之后再转发到订阅url; configureMessageBroker() 方法:配置了一个 简单的消息代理,通俗一点讲就是设置消息连接请求的各种规范信息。
 *
 * @author linyun
 * @date 2018/9/13 下午5:15
 */
@Configuration
@EnableWebSocketMessageBroker
public class WebStompConfig implements WebSocketMessageBrokerConfigurer {

    @Autowired
    private WebStompInterceptor interceptor;

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        // 添加一个/chat端点,客户端就可以通过这个端点来进行连接;withSockJS作用是添加SockJS支持
        registry.addEndpoint("/xlink").setAllowedOrigins("*").withSockJS();
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        // 定义了两个客户端订阅地址的前缀信息,也就是客户端接收服务端发送消息的前缀信息
        // device 用于一对一的发送
        registry.enableSimpleBroker( "/users");
        // 定义了服务端接收地址的前缀,也即客户端给服务端发消息的地址前缀
        registry.setUserDestinationPrefix("/users/");
    }

    @Override
    public void configureClientInboundChannel(ChannelRegistration registration) {
        // 注册了一个接受客户端消息通道拦截器
        registration.interceptors(interceptor);
    }
}
//shiro拦截器中此映射加入白名单
filterChainDefinitionMap.put("/xlink/**", "anon");//xlink

以下对应前端页面

// 添加一个/chat端点,客户端就可以通过这个端点来进行连接;withSockJS作用是添加SockJS支持
        registry.addEndpoint("/xlink").setAllowedOrigins("*").withSockJS();
        // device 用于一对一的发送
        registry.enableSimpleBroker( "/users");
        // 定义了服务端接收地址的前缀,也即客户端给服务端发消息的地址前缀
        registry.setUserDestinationPrefix("/users/");

elementUI全屏loading导致页面闪白_Time_03

WebStompInterceptor
package com.lexi.manage.xlink.stomp;

import com.sun.security.auth.UserPrincipal;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.simp.stomp.StompCommand;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.messaging.support.ChannelInterceptor;
import org.springframework.messaging.support.MessageHeaderAccessor;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.security.Principal;

/**
 * @author linyun
 * @date 2018/9/13 下午5:57
 */
@Component
public class WebStompInterceptor implements ChannelInterceptor {

    /**
     * 绑定user到websocket conn上
     *
     * @param message
     * @param channel
     * @return
     */
    @Override
    public Message<?> preSend(Message<?> message, MessageChannel channel) {
        StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
        if (StompCommand.CONNECT.equals(accessor.getCommand())) {
            String token = accessor.getFirstNativeHeader("xlinkToken");
            //System.out.print(token);
            if (StringUtils.isEmpty(token)) {
                return null;
            }
            // 绑定user
            Principal principal = new UserPrincipal(token);
            accessor.setUser(principal);
        }
        return message;
    }
}

后端代码如下:
小程序本地缓存失效情况下,需要授权登录微信获取客户唯一识别码openid

ssself._post_form('/base/shopxo/user/PCScanLogin', transformdata, function(res)
/**
     * 微信小程序PC端扫码授权登录
     *
     * @param jsonObject
     * @return
     */
    @PostMapping(value = "/PCScanLogin")
    public Result<?> PCScanLogin(@RequestBody JSONObject jsonObject) {
        JSONObject result = new JSONObject();
        try {
            String code = jsonObject.getString("JSCODE");
            String scene = jsonObject.getString("scene");
            Object sceneObj = redisUtil.get(scene);
            if (sceneObj == null) {
                return Result.error("小程序码已经失效,请重新加载二维码");
            }
            JSONObject objectResult = weiXinConfigComponent.code2Session(code);
            String openid = objectResult.getString("openid");
            String session = objectResult.getString("session_key");
            result.put("session_key", session);
            result.put("openid", openid);
            LambdaQueryWrapper<MiniProgramUser> lambdaQuery = new LambdaQueryWrapper<>();
            lambdaQuery.eq(MiniProgramUser::getOpenId, openid);
            MiniProgramUser one = miniProgramUserService.getOne(lambdaQuery);
            if (oConvertUtils.isEmpty(one)) {
                result.put("state", "binding");
            } else {
                result.put("state", "login");
                //授权登录获取token
                this.getPCUserToken(one, scene, result);
            }
            return Result.OK(result);
        } catch (Exception e) {
            e.printStackTrace();
            if (e.getMessage().contains("客户登录权限")) {
                return Result.error(e.getMessage());
            }
            return Result.error("网络错误!");
        }
    }

以上即为PC端扫码登录流程及代码展示。