基于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=(你的參數)

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


最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,556評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,463評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,778評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,218評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,436評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,969評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,795評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,993評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,229評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,687評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,990評論 2 374

推薦閱讀更多精彩內容