基于websocket實現掃描小程序碼登錄web頁面和微信小程序(雙端登錄)

SpringBoot+WebSocket+Redis控制二維碼失效時間

類似于騰訊文檔網頁版掃描小程序碼登錄的模式

騰訊文檔采用的是輪詢的方式,我在這里采用websocket的方式.

這里運用了github上基于微信SDK的更易用的SDK weixin-java-miniapp

第一步:導入需要的maven依賴



<!--springboot整合了websocket-->

? <dependency>

? ? ? ? ? ? <groupId>org.springframework.boot</groupId>

? ? ? ? ? ? <artifactId>spring-boot-starter-websocket</artifactId>

? ? ? ? </dependency>

? ? ? ? <dependency>

? ? ? ? ? ? <groupId>com.github.binarywang</groupId>

? ? ? ? ? ? <artifactId>weixin-java-miniapp</artifactId>

? ? ? ? ? ? <version>3.3.0</version>

? ? ? ? </dependency>

第二步:配置websocket



@Configuration

public class WebSocketConfig {

? ? @Bean

? ? public ServerEndpointExporter serverEndpointExporter(){

? ? ? ? return new ServerEndpointExporter();

? ? }

}

這里的socketKey對應@PathParam定義的名字,用來識別唯一的socket連接


@Component

@ServerEndpoint(value = "/socketLogin/{socketKey}")

public class LoginSocket {

? ? private static Logger log= LoggerFactory.getLogger(LoginSocket.class);

? ? private static ConcurrentHashMap<String, Session> sessionMap = new ConcurrentHashMap<>();

? ? private Session session;

? ? /**

? ? * 連接建立成功調用的方法*/

? ? @OnOpen

? ? public void onOpen(Session session, @PathParam("socketKey")String socketKey) {

? ? ? ? this.session=session;

? ? ? ? log.info("[微信小程序websocket]socketKey:{}",socketKey +"-->建立連接");

? ? ? ? sessionMap.put(socketKey,session);

? ? }

? ? /**

? ? * 連接關閉調用的方法

? ? */

? ? @OnClose

? ? public void onClose(@PathParam("socketKey") String socketKey) {

? ? ? ? log.info("[微信小程序websocket]socketKey:{}",socketKey +"-->斷開連接");

? ? ? ? sessionMap.remove(socketKey);

? ? }

? ? public ConcurrentHashMap<String, Session> getSessionMap() {

? ? ? ? return sessionMap;

? ? }

}

第三步:完成socket配置后,正式開始微信小程序開發

開發之前需要準備好申請的小程序APPID SECRET

編寫工具類(整合weixin-java-miniapp),這個工具類根據自身需求,靈活設置WxMaInMemoryConfig里面的內容



@Configuration

public class WxMaConfiguration {

? ? private static String appId;

? ? private static String secret;

? ? @Value("${weixin.applet_appid}")

? ? public void setAppId(String appId) {

? ? ? ? WxMaConfiguration.appId = appId;

? ? }

? ? @Value("${weixin.applet_secret}")

? ? public void setSecret(String secret) {

? ? ? ? WxMaConfiguration.secret = secret;

? ? }

? ? private static WxMaService wxMaService=null;

? ? @Bean

? ? public Object services(){

? ? ? ? WxMaInMemoryConfig config = new WxMaInMemoryConfig();

? ? ? ? config.setAppid(appId);

? ? ? ? config.setSecret(secret);

? ? ? ? wxMaService = new WxMaServiceImpl();

? ? ? ? wxMaService.setWxMaConfig(config);

? ? ? ? return Boolean.TRUE;

? ? }

? ? public static WxMaService getWxMaService(){

? ? ? ? return wxMaService;

? ? }

}

第四步:網頁端獲取小程序二維碼接口

建議小程序二維碼里面的scene參數和建立websocket連接的key保持一致

pathStr:小程序登錄成功后微信小程序的跳轉地址,而不是網頁的跳轉地址.如:pages/index/index

注意此處:3.3.0之前版本的 weixin-java-miniapp無法通過createWxaCodeUnlimitBytes返回byte字節,返回的都是File文件


/**

? ? * 獲取微信小程序帶參數的二維碼

? ? *

? ? * @return

? ? */

? ? @RequestMapping("/getAppletQrCode")

? ? public void getAppletCode(@RequestParam("sceneStr") String sceneStr, @RequestParam("pathStr") String pathStr, HttpServletResponse response) {

? ? ? ? logger.info("[微信小程序]獲取微信小程序二維碼,參數->sceneStr:"+sceneStr+", pathStr:"+pathStr);

? ? ? ? WxMaService wxMaService = WxMaConfiguration.getWxMaService();

? ? ? ? // 獲取小程序二維碼生成實例

? ? ? ? WxMaQrcodeService wxMaQrcodeService = wxMaService.getQrcodeService();

? ? ? ? // 設置小程序二維碼線條顏色為黑色

? ? ? ? WxMaCodeLineColor lineColor = new WxMaCodeLineColor("0", "0", "0");

? ? ? ? // 生成二維碼圖片字節流?

? ? ? ? byte[] qrCodeBytes = null;

? ? ? ? try {

? ? ? ? ? ? qrCodeBytes = wxMaQrcodeService.createWxaCodeUnlimitBytes(sceneStr, pathStr, 430, false, lineColor, false);

? ? ? ? ? ? //設置二維碼180s失效

? ? ? ? ? ? redisUtils.setex("qrcode:"+sceneStr,3*60,sceneStr);

? ? ? ? } catch (Exception e) {

? ? ? ? ? ? logger.error("[微信小程序]生成小程序碼出現異常:{}",e);

? ? ? ? }

? ? ? ? response.setContentType("image/png");

? ? ? ? //寫入response的輸出流中

? ? ? ? try {

? ? ? ? ? ? OutputStream outputStream = response.getOutputStream();

? ? ? ? ? ? outputStream.write(Base64.encodeBase64(qrCodeBytes));

? ? ? ? ? ? outputStream.flush();

? ? ? ? ? ? outputStream.close();

? ? ? ? } catch (Exception e) {

? ? ? ? ? ? logger.error("[微信小程序]輸出流寫出小程序碼出現異常:{}",e);

? ? ? ? }

? ? }

第五步:小程序登錄接口,對于前端來說以上接口都在網頁端調取,而此接口在小程序端調取,要做好兼容



/**

? ? * 初次未授權登錄

? ? * 微信小程序授權登錄

? ? *

? ? * @param code

? ? * @return https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code

? ? */

? ? @RequestMapping("/weChatAppletLogin")

? ? @ResponseBody

? ? public ResultBean weChatAuthLogin(@RequestParam("code") String code, @RequestParam("encryptedData") String encryptedData, @RequestParam("ivStr") String ivStr, @RequestParam("nickname") String nickname, @RequestParam("headurl") String headurl, @RequestParam("socketKey") String socketKey,HttpServletResponse response) {

? ? ? ? logger.info("[微信小程序]微信小程序和網頁端登錄,參數->code:"+code+", encryptedData:"+encryptedData+", nickname:"+nickname+", headurl:"+headurl+", socketKey:"+socketKey);

? ? ? ? if (StringUtils.isBlank(code)) {

? ? ? ? ? ? return ResultBean.setError(1, "code碼為空");

? ? ? ? }

? ? ? ? //微信接口參數

? ? ? ? Map<String, String> params = new HashMap<>();

? ? ? ? params.put("appid", APPLET_APPID);

? ? ? ? params.put("secret", APPLET_SECRET);

? ? ? ? params.put("js_code", code);

? ? ? ? params.put("grant_type", "authorization_code");

? ? ? ? try {

? ? ? ? ? ? String wxResult = HttpUtil.doGet(APPLET_URL, params);

? ? ? ? ? ? JSONObject jsonObject = JSONObject.parseObject(wxResult);

? ? ? ? ? ? if (jsonObject.get("errcode") != null && StringUtils.equalsIgnoreCase(jsonObject.get("errcode").toString(), "40163")) {

? ? ? ? ? ? ? ? return ResultBean.setError(1, "code碼失效");

? ? ? ? ? ? }

? ? ? ? ? ? String session_key = jsonObject.get("session_key").toString();

? ? ? ? ? ? //第一次登錄沒有unionid

? ? ? ? ? ? Object unionidObj = jsonObject.get("unionid");

? ? ? ? ? ? Map<String,String> userInfo=null;

? ? ? ? ? ? String unionid=null;

? ? ? ? ? ? //第一次授權

? ? ? ? ? ? if (unionidObj == null) {

? ? ? ? ? ? ? ? userInfo = getAuthUnionId(session_key, encryptedData, ivStr);

? ? ? ? ? ? ? ? unionid = userInfo.get("unionid");

? ? ? ? ? ? }else{

? ? ? ? ? ? ? ? unionid=jsonObject.get("unionid").toString();

? ? ? ? ? ? }

? ? ? ? ? ? WXUser wxUser = wxService.findWXUserByunionid(unionid);

? ? ? ? ? ? WXUser user = new WXUser();

? ? ? ? ? ? user.setUnionid(unionid);

? ? ? ? ? ? user.setNickname(nickname);

? ? ? ? ? ? user.setHeadimgurl(headurl);

? ? ? ? ? ? //微信信息存在

? ? ? ? ? ? if (wxUser != null) {

? ? ? ? ? ? ? ? wxService.updateCloudWXUser(user);

? ? ? ? ? ? } else {

? ? ? ? ? ? ? ? //第一次登陸

? ? ? ? ? ? ? ? wxService.addWXUser(user);

? ? ? ? ? ? }

? ? ? ? ? ? Map<String, String> result = new HashMap<>();

? ? ? ? ? ? result.put("unionid", unionid);

? ? ? ? ? ? //設置小程序的登錄狀態緩存(若不設置,微信默認30天內沒有操作就重新登錄)

? ? ? ? ? ? //redisUtils.setex("applet:"+unionid,30*24*60*60,unionid);

? ? ? ? ? ? //這是網頁端的socket返回數據

? ? ? ? ? ? ConcurrentHashMap<String, Session> sessionMap = loginSocket.getSessionMap();

? ? ? ? ? ? if (StringUtils.isNotBlank(socketKey)) {

? ? ? ? ? ? ? ? Session currentSession = sessionMap.get(socketKey);

? ? ? ? ? ? ? ? if (currentSession != null) {

? ? ? ? ? ? ? ? ? ? String codeKey = redisUtils.getString("qrcode:" + socketKey);

? ? ? ? ? ? ? ? ? ? ResultBean resultBean=null;

? ? ? ? ? ? ? ? ? ? if(StringUtils.isBlank(codeKey)){

? ? ? ? ? ? ? ? ? ? ? ? resultBean = ResultBean.setOk(1, "二維碼已失效");

? ? ? ? ? ? ? ? ? ? }else{

? ? ? ? ? ? ? ? ? ? ? ? //掃描后刪除緩存的二維碼

? ? ? ? ? ? ? ? ? ? ? ? redisUtils.delString("qrcode:" + socketKey);

? ? ? ? ? ? ? ? ? ? ? ? //設置網頁端登錄有效時長為6小時

? ? ? ? ? ? ? ? ? ? ? ? redisUtils.setex(unionid, 3600 * 6, unionid);

? ? ? ? ? ? ? ? ? ? ? ? resultBean = ResultBean.setOk(0, "掃碼登錄成功",unionid);

? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? String res = JSON.toJSONString(resultBean);

? ? ? ? ? ? ? ? ? ? currentSession.getAsyncRemote().sendText(res);

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? ? ? return ResultBean.setOk(0, "授權登錄小程序成功", result);

? ? ? ? } catch (Exception e) {

? ? ? ? ? ? logger.error("[微信小程序]小程序授權登錄出現異常:{}",e);

? ? ? ? ? ? return ResultBean.setError(1, "授權登錄小程序失敗");

? ? ? ? }

? ? }

/**

? ? * 授權過后的再次免授權登錄

? ? *

? ? * @param sessionKey

? ? * @param encryptedData

? ? * @param ivStr

? ? * @return 利用微信工具sdk對獲取的用戶信息解密

? ? */

? ? public Map<String,String> getAuthUnionId(String sessionKey, String encryptedData,String ivStr) {

? ? ? ? try {

? ? ? ? ? ? WxMaService wxMaService = WxMaConfiguration.getWxMaService();

? ? ? ? ? ? WxMaUserService userService = new WxMaUserServiceImpl(wxMaService);

? ? ? ? ? ? WxMaUserInfo userInfo = userService.getUserInfo(sessionKey, encryptedData, ivStr);

? ? ? ? ? ? String unionId = userInfo.getUnionId();

? ? ? ? ? ? String nickName = userInfo.getNickName();

? ? ? ? ? ? String avatarUrl = userInfo.getAvatarUrl();

? ? ? ? ? ? Map<String, String> userInfoMap = new HashMap<>();

? ? ? ? ? ? userInfoMap.put("unionid", unionId);

? ? ? ? ? ? userInfoMap.put("nickname", nickName);

? ? ? ? ? ? userInfoMap.put("headurl",avatarUrl);

? ? ? ? ? ? return userInfoMap;

? ? ? ? } catch (Exception e) {

? ? ? ? ? ? logger.error("[微信小程序]已授權登錄出現異常:{}",e);

? ? ? ? ? ? return null;

? ? ? ? }

? ? }

前端demo

<!DOCTYPE html>

<html lang="en">

<head>

? ? <meta charset="UTF-8">

? ? <title>Title</title>

? ? <script src="http://libs.baidu.com/jquery/1.9.1/jquery.js"></script>

? ? <script type="text/javascript">

? ? ? ? $(function () {

? ? ? ? ? ? var ip="xxx.xxx.xxx.xxx";

? ? ? ? ? ? var sceneStr = "scend-" + new Date().getTime() + Math.ceil(Math.random() * 888888 + 1000000);

? ? ? ? ? ? //建立websocket192.168.101.123

? ? ? ? ? ? //var s = encodeURIComponent(sceneStr);

? ? ? ? ? ? var ws = new WebSocket("ws://"+ip+":端口號/項目名(沒有不寫)/websocket路徑/" + sceneStr)

? ? ? ? ? ? ws.onopen = function () {

? ? ? ? ? ? ? ? console.log("websocket建立連接")

? ? ? ? ? ? }

? ? ? ? ? ? ws.onmessage = function (ev) {

? ? ? ? ? ? ? ? var parse = JSON.parse(ev.data);

? ? ? ? ? ? ? ? console.log(parse)

? ? ? ? ? ? ? ? var code1 = parse.code;

? ? ? ? ? ? ? ? if(code1==0){

? ? ? ? ? ? ? ? ? ? ws.onclose=function () { console.log("websocket連接關閉")}

? ? ? ? ? ? ? ? ? ? window.location.;

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? ? ? var url="小程序碼接口地址";

? ? ? ? ? ? var pathStr="pages/index/index";

? ? ? ? ? ? $.post(url,{

? ? ? ? ? ? ? ? sceneStr: sceneStr,

? ? ? ? ? ? ? ? pathStr: pathStr

? ? ? ? ? ? },function(result){

? ? ? ? ? ? ? ? $("#qrcode").attr("src","data:image/png;base64,"+result);

? ? ? ? ? ? })

? ? ? ? })

? ? </script>

</head>

<body>

<img id="qrcode" src="">

</body>

</html>

小程序端demo(此demo僅做參考)



import {

? Base64,

? Crypto

} from './utils/ossUpload';

import {

? format

} from './utils/util.js';

App({

? onLaunch: function(option) {

? ? this.onLaunchArgu = option;

? ? console.log(option)

? ? this.init();

? ? this.globalData.os = this._getSystemOs()

? ? console.log(this._getSystemInfo())

? },

? requestArr:[],

? // 初始化

? init(fallback){

? ? wx.checkSession({

? ? ? success: () => {

? ? ? ? console.log('登錄沒過期了')

? ? ? ? ? // 獲取用戶信息

? ? ? ? ? wx.getSetting({

? ? ? ? ? ? success: res => {

? ? ? ? ? ? ? if (res.authSetting['scope.userInfo']) {

? ? ? ? ? ? ? ? // 已經授權,可以直接調用 getUserInfo 獲取頭像昵稱,不會彈框

? ? ? ? ? ? ? ? this.getUserInfo().then(res => {

? ? ? ? ? ? ? ? ? this.globalData.userInfo = res.userInfo

? ? ? ? ? ? ? ? ? // 由于 getUserInfo 是網絡請求,可能會在 Page.onLoad 之后才返回

? ? ? ? ? ? ? ? ? // 所以此處加入 callback 以防止這種情況

? ? ? ? ? ? ? ? ? if (this.userInfoReadyCallback) {

? ? ? ? ? ? ? ? ? ? this.userInfoReadyCallback(res)

? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? }, () => {})

? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? ? })

? ? ? },

? ? ? fail(res) {

? ? ? ? console.log('登錄過期了');

? ? ? },

? ? ? complete(res) {

? ? ? ? console.log(res);

? ? ? }

? ? })

? },

? login(callback) {

? ? wx.login({

? ? ? success: res => {

? ? ? ? const code = res.code;

? ? ? ? this.getUserInfo().then(res => {

? ? ? ? ? this.request({

? ? ? ? ? ? url: '/studyassistant/weixin/weChatAppletLogin',

? ? ? ? ? ? data: {

? ? ? ? ? ? ? #這里的參數是掃二維碼獲取到的,方便調試,直接拿取

? ? ? ? ? ? ? socketKey: this.onLaunchArgu.query.scene,?

? ? ? ? ? ? ? code,

? ? ? ? ? ? ? encryptedData: res.encryptedData,

? ? ? ? ? ? ? ivStr: res.iv,

? ? ? ? ? ? ? headurl: res.userInfo.avatarUrl,

? ? ? ? ? ? ? nickname: encodeURIComponent(res.userInfo.nickName)

? ? ? ? ? ? }

? ? ? ? ? }, false).then(res => {

? ? ? ? ? ? this.globalData.unionId = res.data.unionid;

? ? ? ? ? ? this._setStorageSync({

? ? ? ? ? ? ? unionId: res.data.unionid,

? ? ? ? ? ? })

? ? ? ? ? ? if (this.requestArr.length) {

? ? ? ? ? ? ? this.requestArr.map((item) => {

? ? ? ? ? ? ? ? this.request(item.option, item.author);

? ? ? ? ? ? ? })

? ? ? ? ? ? ? this.request()

? ? ? ? ? ? }

? ? ? ? ? ? callback();

? ? ? ? ? })

? ? ? ? }, () => {})

? ? ? }

? ? })

? },

微信小程序調試


這里的啟動參數就是頁面上小程序二維碼里面的參數scene=(你的參數)

這樣調試的時候,就不用了手機掃碼了


最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容