另类的微信登录

了个小网站,一直想要加上扫码登录。由于没有营业执照,导致我没有办法接入支付宝/微信扫码登录。

在网络上查了一些信息,看到一篇文章:微信登录

想着可以用到微信小程序(个人版即可),对接上微信扫码登录。

首先搭建在我的个人网站上加上微信登录按钮。

效果如下:

给前端两个码,一个小程序码,一个生成的二维码。进入小程序,扫完码即可完成登录。

所用技术

微信登录相关二维码
1、第一个小程序码,微信小程序上下载就行。
2、第二个二维码由Java生成。

maven

<!--生成二维码-->
        <dependency>
            <groupId>com.google.zxing</groupId>
            <artifactId>core</artifactId>
            <version>3.3.0</version>
        </dependency>
        <dependency>
            <groupId>com.google.zxing</groupId>
            <artifactId>javase</artifactId>
            <version>3.3.0</version>
        </dependency>

二维码相关工具类

生成二维码获得bufferImage,我是返回给前端base64格式的所以再调用将bufferImage转成字符串。

import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.imageio.ImageIO;
import javax.swing.filechooser.FileSystemView;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;

/**
 * QR Code 工具类
 *
 * @author AU
 * @date 2023/1/19 12:53 AM
 */
public class QRCodeUtil {
    private static final Logger log= LoggerFactory.getLogger(QRCodeUtil.class);

    //CODE_WIDTH:二维码宽度,单位像素
    private static final int CODE_WIDTH = 400;
    //CODE_HEIGHT:二维码高度,单位像素
    private static final int CODE_HEIGHT = 400;
    //FRONT_COLOR:二维码前景色,0x000000 表示黑色
    private static final int FRONT_COLOR = 0x000000;
    //BACKGROUND_COLOR:二维码背景色,0xFFFFFF 表示白色
    //演示用 16 进制表示,和前端页面 CSS 的取色是一样的,注意前后景颜色应该对比明显,如常见的黑白
    private static final int BACKGROUND_COLOR = 0xFFFFFF;

    //核心代码-生成二维码
    public static BufferedImage getBufferedImage(String content) throws WriterException {

        //com.google.zxing.EncodeHintType:编码提示类型,枚举类型
        Map<EncodeHintType, Object> hints = new HashMap();

        //EncodeHintType.CHARACTER_SET:设置字符编码类型
        hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");

        //EncodeHintType.ERROR_CORRECTION:设置误差校正
        //ErrorCorrectionLevel:误差校正等级,L = ~7% correction、M = ~15% correction、Q = ~25% correction、H = ~30% correction
        //不设置时,默认为 L 等级,等级不一样,生成的图案不同,但扫描的结果是一样的
        hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);

        //EncodeHintType.MARGIN:设置二维码边距,单位像素,值越小,二维码距离四周越近
        hints.put(EncodeHintType.MARGIN, 1);

        MultiFormatWriter multiFormatWriter = new MultiFormatWriter();
        BitMatrix bitMatrix = multiFormatWriter.encode(content, BarcodeFormat.QR_CODE, CODE_WIDTH, CODE_HEIGHT, hints);
        BufferedImage bufferedImage = new BufferedImage(CODE_WIDTH, CODE_HEIGHT, BufferedImage.TYPE_INT_BGR);
        for (int x = 0; x < CODE_WIDTH; x++) {
            for (int y = 0; y < CODE_HEIGHT; y++) {
                bufferedImage.setRGB(x, y, bitMatrix.get(x, y) ? FRONT_COLOR : BACKGROUND_COLOR);
            }
        }
        return bufferedImage;
    }
              
    //BufferedImage 转base64
    public static String GetBase64FromImage(BufferedImage img){
        ByteArrayOutputStream stream = new ByteArrayOutputStream();
        try {
            // 设置图片的格式
            ImageIO.write(img, "jpg", stream);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        byte[] bytes = Base64.encodeBase64(stream.toByteArray());
        String base64 = new String(bytes);
        return  "data:image/jpeg;base64,"+base64;
    }
}

service层-创建二维码部分

这里我的做法能实现登录,暂不确定是否存在安全问题。
【忽略WebCode】
1、首先创建了一个uuid作为二维码的信息。同时这个uuid也是即将扫码的用户的token。
2、将这个uuid存入redis,value为生成的一个空用户信息。
3、将这个uuid写入cookie。
4、生成二维码返回。

/**
     * 创建微信登陆 QR
     * @return String
     */
    private String createLoginPic(String webCode, HttpServletRequest request, HttpServletResponse response) throws WriterException {
        LambdaQueryWrapper<SsoWxWeb> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(SsoWxWeb::getWebCode, webCode);
        SsoWxWeb ssoWxWeb = ssoWxWebMapper.selectOne(wrapper);
        if (Objects.isNull(ssoWxWeb)){
            throw new DefineException(RespBeanEnum.WEBCODE_ERROR);
        }
        // ↑↑↑忽略WebCode

        
        String qrId = UUID.randomUUID().toString().replaceAll("-", "");
        // 设置用户信息, 保存redis、写入cookie
        redisService.set(qrId, new SysUserVO(), 5 * 60L);
        Cookie cookie = new Cookie(SecurityConstant.TOKEN_NAME, qrId);
        cookie.setHttpOnly(false);
        cookie.setPath("/");
        response.addCookie(cookie);

        Map<String, String> qrMap = Maps.newHashMap();
        qrMap.put("qrId", qrId);
        qrMap.put("webCode", webCode);

        BufferedImage bufferedImage = QRCodeUtil.getBufferedImage(JSONObject.toJSONString(qrMap));
        return VerificationCode.GetBase64FromImage(bufferedImage);
    }

微信小程序-扫码

首先扫小程序码进入小程序。无需多言。
在进入小程序后,使用小程序扫描图片。
相关代码如下。【可直接参考微信开发者文档】

scanCode: function() {
    var that = this;
    wx.scanCode({ //扫描API
      success(res) { //扫描成功
        console.log(res) //输出回调信息
        wx.showToast({
          title: '扫码成功',
          duration: 1000
        })
        that.login(res.result) // 调用后端登录程序
      }
    })
  },
  login(code){
    wx.login({
      success (res) {
        if (res.code) {
          console.log(res)
          //发起网络请求
          wx.request({
            method: "POST",
            url: 'http://localhost:1991/sys/wxScanLogin',
            data: {
              code: res.code,
              qrInfo: code
            },
            success: (res) => {
              console.log(res);
            }
          })
        } else {
          console.log('登录失败!' + res.errMsg)
        }
      }
    })
  }

实时交互

采用WebSocket进行前后端实时交互

前端

主要是websocketOnmessage()中进行修改。

initWebSocket: function () {
            let that = this;
            // WebSocket与普通的请求所用协议有所不同,ws等同于http,wss等同于https
            console.log("调用了链接websock  ,获取的用户id为   :"+ cookies.get('TOKEN'))
            // var userId = store.getters.userInfo.id;
            var url = that.wsUrl.replace("https://","wss://").replace("http://","ws://");
            url = url + "websocket/" + cookies.get('TOKEN');
            console.log(url);
            this.websock = new WebSocket(url);
            this.websock.onopen = this.websocketOnopen;
            this.websock.onerror = this.websocketOnerror;
            this.websock.onmessage = this.websocketOnmessage;
            this.websock.onclose = this.websocketOnclose;
          },
          websocketOnopen: function () {
            console.log("WebSocket连接成功");
            //心跳检测重置
            // this.heartCheck.reset().start();
          },
          websocketOnerror: function (e) {
            console.log("WebSocket连接发生错误");
            this.reconnect();
          },


          websocketOnmessage: function (e) {
            let that = this;
            console.log('监听关闭' + e)
            console.log("-----接收消息-------",e.data);
            var data = eval("(" + e.data + ")"); //解析对象
            console.log(data)
            if(data.code == 200001){
                console.log(e)
                that.scanSuccess = true;
            }
            if(data.code == 200){
                that.$router.push({name:'about'})
            }
          },


          websocketOnclose: function (e) {
            console.log("connection closed (" + e.code + ")");
            this.reconnect();
          },
          websocketSend(text) { // 数据发送
            try {
              this.websock.send(text);
            } catch (err) {
              console.log("send failed (" + err.code + ")");
            }
          },
     
          reconnect() {
            var that = this;
            if(that.lockReconnect) return;
            that.lockReconnect = true;
            //没连接上会一直重连,设置延迟避免请求过多
            setTimeout(function () {
              console.info("尝试重连...");
              that.initWebSocket();
              that.lockReconnect = false;
            }, 5000);
        },

后端

maven

<!-- 实现对 WebSocket 相关依赖的引入,方便~ -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

config

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

    @Configuration
    @EnableWebSocket
    public class WebSocketConfig {

        /**
* 注入一个ServerEndpointExporter,该Bean会自动注册使用@ServerEndpoint注解申明的websocket endpoint
*/
        @Bean
        public ServerEndpointExporter serverEndpointExporter() {
            return new ServerEndpointExporter();
        }

    }

server

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

@ServerEndpoint("/websocket/{uniqueId}")
@Component(value = "websocket")
public class WebSocketServer {

    private static final Logger log = LoggerFactory.getLogger(WebSocketServer.class);

    /**
     * 当前在线连接数
     */
    private static AtomicInteger onlineCount = new AtomicInteger(0);

    /**
     * 用来存放每个客户端对应的 WebSocketServer 对象
     */
    private static ConcurrentHashMap<String, WebSocketServer> webSocketMap = new ConcurrentHashMap<>();

    /**
     * 与某个客户端的连接会话,需要通过它来给客户端发送数据
     */
    private Session session;

    /**
     * 接收 uniqueId
     */
    private String uniqueId = "";

    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("uniqueId") String userId) {
        this.session = session;
        this.uniqueId = userId;
        if (webSocketMap.containsKey(userId)) {
            webSocketMap.remove(userId);
            webSocketMap.put(userId, this);
        } else {
            webSocketMap.put(userId, this);
            addOnlineCount();
        }
        log.info("用户连接:" + userId + ",当前在线人数为:" + getOnlineCount());
        try {
            sendMessage("连接成功!");
        } catch (IOException e) {
            log.error("用户:" + userId + ",网络异常!!!!!!");
        }
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose() {
        if (webSocketMap.containsKey(uniqueId)) {
            webSocketMap.remove(uniqueId);
            subOnlineCount();
        }
        log.info("用户退出:" + uniqueId + ",当前在线人数为:" + getOnlineCount());
    }

    /**
     * 收到客户端消息后调用的方法
     *
     * @param message 客户端发送过来的消息
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        log.info("用户消息:" + uniqueId + ",报文:" + message);
        if (!StringUtils.isEmpty(message)) {
            try {
                JSONObject jsonObject = JSON.parseObject(message);
                jsonObject.put("fromUserId", this.uniqueId);
                String toUserId = jsonObject.getString("toUserId");
                if (!StringUtils.isEmpty(toUserId) && webSocketMap.containsKey(toUserId)) {
                    webSocketMap.get(toUserId).sendMessage(jsonObject.toJSONString());
                } else {
                    log.error("请求的 uniqueId:" + toUserId + "不在该服务器上");
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 发生错误时调用
     *
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {
        log.error("用户错误:" + this.uniqueId + ",原因:" + error.getMessage());
        error.printStackTrace();
    }

    /**
     * 实现服务器主动推送
     */
    public void sendMessage(String message) throws IOException {
        this.session.getBasicRemote().sendText(message);
    }


    /**
     * 使用web-socket 发送信息
     *
     * @param msg      味精
     * @param uniqueId 惟一id
     */
    public static void sendUseSocket(String msg, String uniqueId){
        if (webSocketMap.containsKey(uniqueId)){
            WebSocketServer webSocketServer = webSocketMap.get(uniqueId);
            try {
                webSocketServer.sendMessage(msg);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 关闭连接
     *
     * @param uniqueId 惟一id
     */
    public static void closeUnique(String uniqueId){
        if (webSocketMap.containsKey(uniqueId)){
            WebSocketServer webSocketServer = webSocketMap.get(uniqueId);
            webSocketServer.onClose();
        }
    }


    public static synchronized AtomicInteger getOnlineCount() {
        return onlineCount;
    }

    public static synchronized void addOnlineCount() {
        WebSocketServer.onlineCount.getAndIncrement();
    }

    public static synchronized void subOnlineCount() {
        WebSocketServer.onlineCount.getAndDecrement();
    }


}

service

image.png

image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容