java springboot+redis+WebSocket实现微信扫码登录

基于websocket实现扫描小程序码登录web页面和微信小程序(双端登录) SpringBoot+WebSocket+Redis控制二维码失效时间 这里运用了github上基于微信SDK的更易用的SDK weixin-java-miniapp 第一步:导入需要的maven依赖 <!--springboot整合了websocket--> <dependency> <groupId>org.spr... 阅读详情

代码篇

在这里插入图片描述

maven

		<!--XML解析-->
        <dependency>
            <groupId>org.dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>2.1.1</version>
        </dependency>
        <!--websocket-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
            <version>2.3.3.RELEASE</version>
        </dependency>

application.yml

#微信配置
wechat:
  appId: wx5**********756
  appSecret: 21ca23************70403
  token: 1******a
  sceneId: 1****
  accessTokenUrl: https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s
  ticketUrl: https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=%s
  qrCodeUrl: https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=%s
  shortUrl: https://api.weixin.qq.com/cgi-bin/shorturl?access_token=%s
  userInfoUrl: https://api.weixin.qq.com/cgi-bin/user/info?access_token=%s&openid=%s&lang=zh_CN

WeChatProperties.java

package com.qcn.common.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 * 读取微信配置文件
 */
@Component
@ConfigurationProperties("wechat")
public class WeChatProperties {
    /**
     * 开发者id
     */
    private String appId;

    /**
     * 开发者密码
     */
    private String appSecret;

    /**
     * 微信公众号校验自定义设置的token
     */
    private String token;

    /**
     * 临时二维码场景值id,32位非0整型
     */
    private Integer sceneId;

    /**
     * 获取accessToken的url
     */
    private String accessTokenUrl;

    /**
     * 获取ticket的url
     */
    private String ticketUrl;

    /**
     * 获取二维码url
     */
    private String qrCodeUrl;

    /**
     * 长链接转短连接url
     */
    private String shortUrl;

    /**
     * 获取用户信息
     */
    private String userInfoUrl;

    public String getAppId() {
        return appId;
    }

    public void setAppId(String appId) {
        this.appId = appId;
    }

    public String getAppSecret() {
        return appSecret;
    }

    public void setAppSecret(String appSecret) {
        this.appSecret = appSecret;
    }

    public String getToken() {
        return token;
    }

    public void setToken(String token) {
        this.token = token;
    }

    public String getAccessTokenUrl() {
        return accessTokenUrl;
    }

    public void setAccessTokenUrl(String accessTokenUrl) {
        this.accessTokenUrl = accessTokenUrl;
    }

    public String getTicketUrl() {
        return ticketUrl;
    }

    public void setTicketUrl(String ticketUrl) {
        this.ticketUrl = ticketUrl;
    }

    public String getQrCodeUrl() {
        return qrCodeUrl;
    }

    public void setQrCodeUrl(String qrCodeUrl) {
        this.qrCodeUrl = qrCodeUrl;
    }

    public String getShortUrl() {
        return shortUrl;
    }

    public void setShortUrl(String shortUrl) {
        this.shortUrl = shortUrl;
    }

    public String getUserInfoUrl() {
        return userInfoUrl;
    }

    public void setUserInfoUrl(String userInfoUrl) {
        this.userInfoUrl = userInfoUrl;
    }

    public Integer getSceneId() {
        return sceneId;
    }

    public void setSceneId(Integer sceneId) {
        this.sceneId = sceneId;
    }
}

SHA1.java

package com.qcn.framework.api.wechat;

import java.security.MessageDigest;

/**
 * 加密
 */
public class SHA1 {

    private static final char[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5',
            '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};

    private static String getFormattedText(byte[] bytes) {
        int len = bytes.length;
        StringBuilder buf = new StringBuilder(len * 2);
        for (int j = 0; j < len; j++) {
            buf.append(HEX_DIGITS[(bytes[j] >> 4) & 0x0f]);
            buf.append(HEX_DIGITS[bytes[j] & 0x0f]);
        }
        return buf.toString();
    }

    public static String encode(String str) {
        if (str == null) {
            return null;
        }
        try {
            MessageDigest messageDigest = MessageDigest.getInstance("SHA1");
            messageDigest.update(str.getBytes());
            return getFormattedText(messageDigest.digest());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

WeChatUtils.java

package com.qcn.framework.api.wechat;

import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.net.ConnectException;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.URLConnection;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class WeChatUtils {
    private static final Logger log = LoggerFactory.getLogger(WeChatUtils.class);
    /**
     * 向指定 URL 发送GET方法的请求
     * @param url 发送请求的完整URL,应该是 http://127.0.0.1?name1=value1&name2=value2 的形式
     * @return
     */
    public static String sendGet(String url)
    {
        StringBuilder result = new StringBuilder();
        BufferedReader in = null;
        try
        {
            log.info("开始get请求: {}", url);
            URL realUrl = new URL(url);
            URLConnection connection = realUrl.openConnection();
            connection.setRequestProperty("accept", "*/*");
            connection.setRequestProperty("connection", "Keep-Alive");
            connection.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
            connection.connect();
            in = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8"));
            String line;
            while ((line = in.readLine()) != null)
            {
                result.append(line);
            }
            log.info("get请求成功 : {}", result);
        }
        catch (ConnectException e)
        {
            log.error("调用WeChatUtils.sendGet ConnectException, url=" + url, e);
        }
        catch (SocketTimeoutException e)
        {
            log.error("调用WeChatUtils.sendGet SocketTimeoutException, url=" + url, e);
        }
        catch (IOException e)
        {
            log.error("调用WeChatUtils.sendGet IOException, url=" + url, e);
        }
        catch (Exception e)
        {
            log.error("调用WeChatUtils.sendGet Exception, url=" + url, e);
        }
        finally
        {
            try
            {
                if (in != null)
                {
                    in.close();
                }
            }
            catch (Exception ex)
            {
                log.error("调用in.close Exception, url=" + url, ex);
            }
        }
        return result.toString();
    }

    /**
     * 向指定 URL 发送POST方法的请求
     *
     * @param url 发送请求的 URL
     * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
     * @return 所代表远程资源的响应结果
     */
    public static String sendPost(String url, String param)
    {
        PrintWriter out = null;
        BufferedReader in = null;
        StringBuilder result = new StringBuilder();
        try
        {
            String urlNameString = url;
            log.info("开始post请求: {}", urlNameString);
            URL realUrl = new URL(urlNameString);
            URLConnection conn = realUrl.openConnection();
            conn.setRequestProperty("accept", "*/*");
            conn.setRequestProperty("connection", "Keep-Alive");
            conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
            conn.setRequestProperty("Accept-Charset", "utf-8");
            conn.setRequestProperty("contentType", "utf-8");
            conn.setDoOutput(true);
            conn.setDoInput(true);
            out = new PrintWriter(conn.getOutputStream());
            out.print(param);
            out.flush();
            in = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8"));
            String line;
            while ((line = in.readLine()) != null)
            {
                result.append(line);
            }
            log.info("post请求成功 : {}", result);
        }
        catch (ConnectException e)
        {
            log.error("调用WeChatUtils.sendPost ConnectException, url=" + url + ",param=" + param, e);
        }
        catch (SocketTimeoutException e)
        {
            log.error("调用WeChatUtils.sendPost SocketTimeoutException, url=" + url + ",param=" + param, e);
        }
        catch (IOException e)
        {
            log.error("调用WeChatUtils.sendPost IOException, url=" + url + ",param=" + param, e);
        }
        catch (Exception e)
        {
            log.error("调用WeChatUtils.sendPost Exception, url=" + url + ",param=" + param, e);
        }
        finally
        {
            try
            {
                if (out != null)
                {
                    out.close();
                }
                if (in != null)
                {
                    in.close();
                }
            }
            catch (IOException ex)
            {
                log.error("调用in.close Exception, url=" + url + ",param=" + param, ex);
            }
        }
        return result.toString();
    }

    /**
     * xml解析
     * @param request
     * @return
     * @throws Exception
     */
    public static Map<String, String> parseXml(HttpServletRequest request) throws Exception {
        Map<String, String> map = new HashMap<>();
        InputStream inputStream = request.getInputStream();
        SAXReader reader = new SAXReader();
        Document document = reader.read(inputStream);
        Element root = document.getRootElement();
        List<Element> elementList = root.elements();
        for (Element e : elementList)
            map.put(e.getName(), e.getText());
        inputStream.close();
        return map;
    }

    /**
     * 校验签名
     * @param signature
     * @param timestamp
     * @param nonce
     * @param token
     * @return
     */
    public static boolean checkSignature(String signature, String timestamp, String nonce, String token) {
        String[] str = new String[]{token, timestamp, nonce};
        //排序
        Arrays.sort(str);
        //拼接字符串
        StringBuffer buffer = new StringBuffer();
        for (int i = 0; i < str.length; i++) {
            buffer.append(str[i]);
        }
        //进行sha1加密
        String temp = SHA1.encode(buffer.toString());
        //与微信提供的signature进行匹对
        return signature.equals(temp);
    }
}

WebSocketConfig.java

package com.qcn.framework.api.websocket;

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

/**
 * 配置websocket
 */
@Configuration
public class WebSocketConfig {
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

WebSocketService.java

package com.qcn.framework.api.websocket;

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.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

/**
 * webSocket服务
 */
@Component
@ServerEndpoint(value = "/webSocket/{socketKey}")
public class WebSocketService {
    private static Logger log = LoggerFactory.getLogger(WebSocketService.class);


    private static int onlineCount = 0;
    private static Map<String, WebSocketService> clients = new ConcurrentHashMap<>();
    private Session session;
    private String socketKey;

    @OnOpen
    public void onOpen(@PathParam("socketKey") String socketKey, Session session) {
        this.socketKey = socketKey;
        this.session = session;
        WebSocketService.onlineCount++;
        clients.put(socketKey, this);
        log.info("【websocket有新的连接】, 总数:{}", onlineCount);
    }

    @OnClose
    public void onClose() {
        clients.remove(socketKey);
        WebSocketService.onlineCount--;
        log.info("【websocket连接断开】总数:{}", onlineCount);
    }

    @OnMessage
    public void onMessage(String message) {
        log.info("【websocket收到来自 {} 消息】: {}",socketKey, message);
    }

    @OnError
    public void onError(Session session, Throwable throwable) {
        log.error("【websocket发生错误】: {}",throwable.getMessage());
    }

    public void sendMessage(String message,String socketKey) {
        String prefix=socketKey.split("_")[0];
        if("admin".equals(prefix)){
            //向admin端所有客户端推送消息
            for (WebSocketService item : clients.values()) {
                if(Objects.equals(item.socketKey.split("_")[0],"admin")){
                    item.session.getAsyncRemote().sendText(message);
                }
            }
        }else{
            //只向pc端扫码登录的客户端推送消息
            for (WebSocketService item : clients.values()) {
                if(Objects.equals(item.socketKey,socketKey)){
                    item.session.getAsyncRemote().sendText(message);
                }
            }
        }
    }
}

WeChatService.java

package com.qcn.framework.api.wechat;

import com.alibaba.fastjson.JSONObject;
import com.qcn.common.config.WeChatProperties;
import com.qcn.common.constant.CardConstants;
import com.qcn.common.constant.Constants;
import com.qcn.common.core.redis.RedisCache;
import com.qcn.common.utils.StringUtils;
import com.qcn.common.utils.sign.Md5Utils;
import com.qcn.framework.api.websocket.WebSocketService;
import com.qcn.framework.web.service.CardLoginService;
import com.qcn.system.domain.card.CardUser;
import com.qcn.system.service.api.ICardUserInfoService;
import com.qcn.system.service.api.ICardUserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * 微信服务处理
 */
@Component
public class WeChatService {
    private static final Logger log = LoggerFactory.getLogger(WeChatService.class);
    @Autowired
    private RedisCache redisCache;
    @Autowired
    private WeChatProperties weChatProperties;
    @Autowired
    private WebSocketService webSocketService;
    @Autowired
    private ICardUserService userService;
    @Autowired
    private ICardUserInfoService iCardUserInfoService;
    @Autowired
    private CardLoginService cardLoginService;

    /**
     * 获取AccessToken,写入redis,比官方失效时间expires_in提前10分钟
     *
     * @return
     */
    public String getAccessToken() {
        String accessToken = redisCache.getCacheObject(CardConstants.ACCESS_TOKEN_KEY);
        if (StringUtils.isBlank(accessToken)) {
            String url = String.format(weChatProperties.getAccessTokenUrl(), weChatProperties.getAppId(), weChatProperties.getAppSecret());
            String result = WeChatUtils.sendGet(url);
            JSONObject jsonObject = JSONObject.parseObject(result);
            accessToken = jsonObject.getString("access_token");
            Integer expiresIn = jsonObject.getInteger("expires_in");
            redisCache.setCacheObject(CardConstants.ACCESS_TOKEN_KEY, accessToken, expiresIn - 600, TimeUnit.SECONDS);
        }
        return accessToken;
    }

    /**
     * 获取二维码
     *
     * @return 二维码url,二维码失效时间,webSocket的socketKey
     */
    public Map<String, Object> getQrCodeUrl() {
        Map<String, Object> map = new HashMap<>();
        String qrCodeUrl = "";
        String encodeTicket = "";
        //----获取的二维码ticket----
        String accessToken = getAccessToken();
        String url = String.format(weChatProperties.getTicketUrl(), accessToken);
        //{"expire_seconds": 604800, "action_name": "QR_SCENE", "action_info": {"scene": {"scene_id": 123}}}
        JSONObject params = new JSONObject();
        params.put("expire_seconds", 300);//二维码有效时间(秒),最大不能超过30天
        params.put("action_name", "QR_SCENE");//二维码类型,QR_SCENE为临时的整型参数值
        JSONObject action_info = new JSONObject();//二维码详细信息
        JSONObject scene = new JSONObject();
        scene.put("scene_id", weChatProperties.getSceneId());//场景值ID
        action_info.put("scene", scene);
        params.put("action_info", action_info);
        String result = WeChatUtils.sendPost(url, params.toJSONString());
        JSONObject jsonObject = JSONObject.parseObject(result);
        String ticket = jsonObject.getString("ticket");
        //---凭借此ticket可以在有效时间内换取二维码, https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=TICKET,TICKET记得进行UrlEncode
        try {
            encodeTicket = URLEncoder.encode(ticket, "UTF-8");
            String longUrl = String.format(weChatProperties.getQrCodeUrl(), encodeTicket);
            qrCodeUrl = getShortUrl(accessToken, longUrl);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        map.put("qrCodeUrl", qrCodeUrl);
        map.put("socketKey", Md5Utils.hash(encodeTicket));
        map.put("expireSeconds", jsonObject.getInteger("expire_seconds"));//该二维码有效时间,以秒为单位
        return map;
    }

    /**
     * 长链接转短连接,提升扫码速度
     *
     * @param accessToken
     * @param longUrl
     * @return
     */
    private String getShortUrl(String accessToken, String longUrl) {
        if (StringUtils.isNotBlank(accessToken) && StringUtils.isNotBlank(longUrl)) {
            String url = String.format(weChatProperties.getShortUrl(), accessToken);
            JSONObject params = new JSONObject();
            params.put("action", "long2short");//固定值
            params.put("long_url", longUrl);
            String result = WeChatUtils.sendPost(url, params.toJSONString());
            JSONObject jsonObject = JSONObject.parseObject(result);
            if (jsonObject.getInteger("errcode") == 0) {
                return jsonObject.getString("short_url");
            }
        }
        return longUrl;
    }

    /**
     * 扫码登录处理
     *
     * @param openId
     */
    public void login(String openId, String socketKey) {
        if (StringUtils.isNotBlank(openId)) {
            String url = String.format(weChatProperties.getUserInfoUrl(), getAccessToken(), openId);
            String result = WeChatUtils.sendGet(url);
            JSONObject jsonObject = JSONObject.parseObject(result);
            log.info("【获取微信用户信息】 {}", jsonObject);
            if (jsonObject.getInteger("subscribe").compareTo(1) == 0) {
                String subscribe_scene = jsonObject.getString("subscribe_scene");
                //关注的渠道来源,ADD_SCENE_SEARCH 公众号搜索(微信扫码登录用这个)
                if ("ADD_SCENE_QR_CODE".equals(subscribe_scene) || "ADD_SCENE_SEARCH".equals(subscribe_scene)) {
                    Map<String, Object> map = new HashMap<>();
                    String weChatNickname = jsonObject.getString("nickname");
                    CardUser user = userService.loginByOpenId(openId);
                    String msg;
                    String code;
                    if (Objects.isNull(user)) {
                        log.info("【微信扫码登录】系统新用户");
                        code = "204";
                        msg = "授权成功,请绑定用户信息,以便下次直接扫码登录";
                    } else {
                        if ("0".equals(user.getStatus())) {
                            log.info("【微信扫码登录成功】系统老用户");
                            code = "200";
                            msg = "登录成功";
                            map.put(Constants.TOKEN, cardLoginService.login(user.getMobile(), "CUSTOM_LOGIN_OPENID"));
                            map.put("data", iCardUserInfoService.findAllInfo(user.getId()));
                            if (!weChatNickname.equals(user.getNickName())) {
                                //更新微信昵称
                                iCardUserInfoService.updateWeChatInfo(user.getId(), null, weChatNickname);
                            }
                        } else {
                            log.info("【微信扫码登录失败】系统老用户账号异常");
                            code = "201";
                            msg = "登录失败,请阅读【用户协议】24小时后提供手机号联系客服";
                        }
                    }
                    map.put("code", code);
                    map.put("msg", msg);
                    map.put("openId", openId);
                    map.put("weChatNickname", weChatNickname);
                    webSocketService.sendMessage(JSONObject.toJSONString(map), socketKey);
                }
            }
        }
    }
}

ApiWeChatController.java

package com.qcn.web.controller.api;

import com.qcn.common.config.WeChatProperties;
import com.qcn.common.core.domain.AjaxResult;
import com.qcn.common.utils.sign.Md5Utils;
import com.qcn.framework.api.wechat.WeChatService;
import com.qcn.framework.api.wechat.WeChatUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.util.Map;

/**
 * 微信相关接口
 */
@RestController
@RequestMapping("/weChat")
public class ApiWeChatController {

    private static final Logger log = LoggerFactory.getLogger(ApiWeChatController.class);
    @Autowired
    private WeChatProperties weChatProperties;
    @Autowired
    private WeChatService weChatService;

    /**
     * token校验
     *
     * @param request
     * @param response
     * @throws UnsupportedEncodingException
     */
    @GetMapping("/msgCallback")
    public void login(HttpServletRequest request, HttpServletResponse response) throws UnsupportedEncodingException {
        log.debug("开始校验token……");
        request.setCharacterEncoding("UTF-8");
        String signature = request.getParameter("signature");//微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。
        String timestamp = request.getParameter("timestamp");//时间戳
        String nonce = request.getParameter("nonce");//随机数
        String echoStr = request.getParameter("echostr");//随机字符串
        PrintWriter out = null;
        try {
            out = response.getWriter();
            if (WeChatUtils.checkSignature(signature, timestamp, nonce, weChatProperties.getToken())) {
                out.write(echoStr);//校验成功原数据返回
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            out.close();
        }
    }

    /**
     * 消息回调
     *
     * @param request
     * @throws Exception
     */
    @PostMapping("/msgCallback")
    public void scanCodeCallback(HttpServletRequest request) throws Exception {
        request.setCharacterEncoding("UTF-8");
        Map<String, String> map = WeChatUtils.parseXml(request);
        String openId = map.get("FromUserName");//发送方帐号(一个OpenID)
        String event = map.get("Event");//事件类型,未关注:subscribe;已关注:SCAN
        String eventKey = map.get("EventKey");//未关注:事件KEY值,qrscene_为前缀,后面为二维码的参数值;已关注:事件KEY值,是一个32位无符号整数,即创建二维码时的二维码scene_id
        String ticket = map.get("Ticket");//二维码的ticket,可用来换取二维码图片
        //不管新关注还是已关注进入,都处理微信授权登录
        log.info("【扫码成功】 ticket:{}",ticket);
        if ("subscribe".equals(event)) {
            eventKey=eventKey.split("_")[1];
        }
        if(weChatProperties.getSceneId().compareTo(Integer.valueOf(eventKey))==0){
            log.info("【微信消息】开始处理微信授权登录……");
            weChatService.login(openId, Md5Utils.hash(ticket));
        }
    }

    /**
     * 获取二维码
     * @return
     */
    @GetMapping("/getQrCodeUrl")
    public AjaxResult getQrCodeUrl() {
        log.debug("开始获取二维码url……");
        Map<String,Object> map = weChatService.getQrCodeUrl();
        log.debug("获取二维码结果:" + map);
        return AjaxResult.success(map);
    }
}

案例-本地&生产环境部署测试

这里使用了内网穿透做本地测试

1.把内网穿透域名地址配置在微信公众号【开发】-【基本设置】,其它配置与yml文件配置一直即可
2.把HTML页面的ip地址也改为这个内网穿透域名
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
1.新建一个静态页面

<!DOCTYPE html>
<html xmlns:v-if="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <!-- import CSS -->
    <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
    <style>
        .body{
            background-image: url("backpic.jpg");
            background-repeat: no-repeat;
            background-size: cover;
        }
        .qrcodeimg {
            width: 200px;
            height: 200px;
        }
        .maindiv {
            width: 400px;
            height: 400px;
            margin-top: 200px;
            float: right;
        }
        .infoinput {
            width: 200px;
            display: block;
        }
        .infoBox {
            width: 220px;
            height: 220px;
            max-height: 220px;
        }
    </style>
</head>
<body class="body">
<div id="app" class="maindiv">
    <div v-if="qrcodeLogin" class="infoBox">
        <div class="block" v-if="qrcodeLogin">
            <el-image :src="src" class="qrcodeimg">
                <div slot="placeholder" class="image-slot">
                    加载中<span class="dot">...</span>
                </div>
            </el-image>
        </div>
       
    </div>


</div>
</body>
<!-- import Vue before Element -->
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<!-- import JavaScript -->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>

<script src="https://cdn.bootcss.com/vue-resource/1.5.1/vue-resource.js"></script>
<script>
    new Vue({
        el: '#app',
        data: function () {
            return {
                qrcodeLogin: true,
                src: "",
                ipaddr: "25*******e.cc",
                enableSSL: ""
            }
        },
        mounted: function () {
            console.log("页面加载完成!");
            this.getQrcode();
        },
        methods: {
            getQrcode(){//获取带参二维码,并设置到页面
                console.log("设置二维码");
                //请求二维码
                const that = this;
                this.$http.get("http" + that.enableSSL + "://" + that.ipaddr + "/weChat/getQrCodeUrl").then(function(data){
                    console.log("function1:返回结果");
                    console.log(data.body.data);
                    //二维码图片地址
                    that.src = data.body.data.qrCodeUrl;
                    //ticket
                    that.websocketLink(data.body.data.socketKey);
                },function(res){
                    console.log("function2");
                    console.log(res);
                });
            },
            websocketLink(userId) {
                var that = this;
                if ("WebSocket" in window) {
                    console.log("您的浏览器支持 WebSocket!");
                    // 打开一个 web socket
                    var ws = new WebSocket("ws" + that.enableSSL + "://" + that.ipaddr + "/webSocket/" + userId);
                    ws.onopen = function () {
                        // Web Socket 已连接上,使用 send() 方法发送数据
                        ws.send("pc端请求扫码登录,socketKey:" + userId);
						console.log("数据发送中...");
                    };
                    ws.onmessage = function (evt) {
                        console.log(evt.data);
                    var received_msg = JSON.parse(evt.data);
                    if (received_msg.code == 200) {
                    	alert(received_msg.message);
                    	console.log(evt.data);
                    } else if (received_msg.code == 201) {
                    	console.log(received_msg.message);
                    } else {
                        console.log("socket通道校验");
                    }
 
                    };
                    ws.onclose = function () {
                        // 关闭 websocket
                        console.log("连接已关闭...");
                    };
                } else {
                    // 浏览器不支持 WebSocket
                    console("您的浏览器不支持 WebSocket!");
                }
            }
        }
    })
</script>
</html>

2.修改yml配置文件

spring:
  resources:
    static-locations: classpath:static/

3.本地项目端口改为80,运行项目
在这里插入图片描述
4.访问本地,扫码测试
在这里插入图片描述

线上部署

nginx
在这里插入图片描述

location /prod-api/{
			proxy_pass http://localhost:7002/;
			proxy_set_header Host $http_host;
			proxy_set_header X-Real-IP $remote_addr;
			proxy_set_header REMOTE-HOST $remote_addr;
			proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
			proxy_set_header Upgrade $http_upgrade;
			proxy_set_header Connection "upgrade";
		}

如果是域名是https请求,就一定要将login_vue.html的WebSocket连接改为wss:
在这里插入图片描述
部署后访问测试即可

详细讲解篇

官方文档

先吐槽一下,做过微信对接的人,看微信文档应都是口吐芬芳吧…………………………

获取access_token

https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_access_token.html

生成带参数的二维码

https://developers.weixin.qq.com/doc/offiaccount/Account_Management/Generating_a_Parametric_QR_Code.html

长链接转短连接

https://developers.weixin.qq.com/doc/offiaccount/Account_Management/URL_Shortener.html

接收事件推送-扫描带参数二维码事件

https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_event_pushes.html

微信公众平台接口调试工具

http://mp.weixin.qq.com/debug/

微信公众号配置

微信扫码登录有两种方式:
1.微信扫一扫直接登录(这里是通过微信开放平台实现,请看另一篇博文)
2.微信扫一扫关注公众号登录(这里通过微信公众号实现,就是这篇博文的实现方式)

正确配置最终图:

在这里插入图片描述
一步一步操作:
在这里插入图片描述
服务器配置:

  1. 启用
  2. 修改配置
  1. URL:项目的部署地址+controller的token校验方法路由,也就是token校验的完整访问路径
  2. Token:自定义字符,例如mytoken,一定要与项目yml配置保持一致即可
  3. EncodingAESKey:用默认的随机生成即可
  4. 加解密方式:明文(在熟练之后再改为加密吧)

在这里插入图片描述

测试

由于买的域名已被上线项目占用,所以买了内网穿透natapp 官网,可以直接在本地测试

获取二维码:http://你的域名/weChat/getQrCodeUrl

在这里插入图片描述

展示二维码,微信客户端扫一扫,关注公众号微信回调

在这里插入图片描述

至此,微信扫描微信公众号二维码实现登录已全部完成

实现微信公众号扫码登录全过程(最详细) 这篇文章介绍了使用微信公众号如何做到从web端提供二维码扫描登录功能,主要分认证的公众号和未认证的普通公众号,两个方向实现扫描登录。这篇文章介绍了使用微信公众号如何做到从web端提供二维码扫描登录功能,主要分认证的公众号和未认证的普通公众号,两个方向实现扫描登录。TOC(真心顶不住微信公众号开发文档的乱并且刚接触的开发者绝对一头雾水,决心编写,防止更多人跳坑! 阅读详情

相关推荐

SpringBoot实现二维码扫码登录:从原理到工程实践

在Web应用开发中,用户身份认证是保障系统安全的核心环节。传统的账号密码登录方式在跨设备场景下存在体验割裂的问题,而基于凭证交换的认证机制则提供了一种更流畅的解决方案。其技术原理是通过生成一个临时的、唯一的凭证(如二维码),在可信的已认证设备(如手机)与待登录设备(如电脑)之间建立安全通道,完成身份信息的传递与绑定。这种方案的技术价值在于提升了登录流程的安全性与用户体验,特别适用于移动端与桌面端协同、内部管理系统等需要快速便捷登录的场景。本文聚焦于利用SpringBoot、Redis和ZXing库,构建一个

weixin_34406086的博客 343

webSocket实现扫码登录

2019独角兽企业重金招聘Python工程师标准>>> ...

weixin_34090643的博客 2578

SpringBoot实现二维码登录功能完整指南

本文介绍了一个基于SpringBoot的二维码登录系统实现方案。系统采用分层架构设计,使用SpringBoot作为基础框架,结合WebSocket实现实时通信,Redis存储状态信息,Google ZXing生成二维码。核心业务流程包括二维码生成、状态管理、移动端扫描确认等步骤,通过状态机设计(WAITING→SCANNED→CONFIRMED)确保

蔡定努 168

Spring Boot+WebSocket实现扫码登录

Spring Boot + Web Socket 实现扫码登录

落的博客 1477

schrodinger 薛定谔

链接: https://pan.baidu.com/s/1Duh4GsoytIklvZp7TdHpWg 密码: wmr1 Linux系统版 进入 Schrodinger_Suites_2021-2_Linux-x86_64 sudo ./INSTALL 中间按默认的来即可,也可以自己调整安装目录。安装目录一定要记住。 安装完,把patcher 拷贝到安装目录 sudo ./patcher 点击Patch即可。 启动程序,进入安装目录 ./maestro ...

周迪新的博客 1万+

Aruco标签定位、测距、三维位置估算 opencv应用

摄像头要一开始对准aruco标签才开始运行。+ 相机标定文件(参考我的上篇文章)+ aruco标签6*6。

dgut_guangdian的博客 4221

SpringMVC微信公众服务号接入

之前用的柳大神写的Servlet+jdbc写的微信服务号,别的项目全是Stringmvc,就把项目更改了,做个记录 项目是maven管理,需要的包配置在pom文件,在最底下,以后写避免找不到 一些基础类就不贴了,很多网站都有 Controller类: import java.io.PrintWriter; import javax.servlet.http.HttpServlet

老街冷巷旧少年的专栏 3431

MD01和MD01N比较

摘要:MD01(传统MRP)与MD01N(MRP Live)是SAP系统不同版本的物料需求计划事务码。核心差异包括:MD01N取消MRP列表、简化计划规则、合并MPS/MRP操作、采用BAdI技术等,体现S/4HANA平台简化、标准化和智能化的演进方向。MD01N通过优化参数配置、增强筛选功能、利用HANA实时计算能力,实现了更高效的物料计划流程,代表了新一代计划引擎的技术革新。

SAP_LES_COM的博客 891

SpringBoot+webSocket 实现扫码登录功能

最近单位又有一个新Java项目。涉及到扫码登录。之前项目使用的是 ajax轮询的方式。感觉太low了。所以这次用webSocket的方式进行实现好。废话不多说!咱们开始!!一、首先咱们需要...

民工哥的博客 934

光猫改桥接后,一根网线搞定IPTV和上网的保姆级教程(附VLAN配置避坑点)

本文详细介绍了光猫改桥接后如何通过单线复用技术实现IPTV和上网功能共存。从VLAN配置原理到具体操作步骤,包括光猫超级管理员权限获取、VLAN绑定设置及路由器IPTV功能配置,提供了全面的保姆级教程。特别针对常见问题如IPTV信号丢失、网速下降等给出了实用解决方案,帮助用户轻松避坑。

weixin_29927965的博客 1744

YOLOv8 WIOU

本文介绍了如何在YOLOv8中实现WIOU(Weighted Intersection over Union)的修改方法。主要修改了ultralytics/utils/metrics.py文件中的bbox_iou函数,新增了WIoU_Scale类,支持单调性FM v2、非单调性FM v3两种模式,并提供了动态调整的动量参数。修改后的函数支持多种IoU变体(GIoU、DIoU、CIoU等),通过数学公式实现了更精确的边界框回归损失计算,特别针对小目标检测场景进行了优化。该实现使用了PyTorch框架,包含完整

FJN110的博客 2039

springboot实现微信公众号扫码登录

首先后端可以给前端发送绑定唯一code的二维码了,并且绑定了扫码处理器,用户扫码会调用到扫码处理器的内容,处理器内会判断用户是否之前扫描过,若扫描过根据openid查询用户数据,直接异步发送登陆成功的事件,正常登录。方法利用redis生成一个唯一的自增的编码,后端给前端发送二维码时,会绑定一个这个code,根据这个唯一code来判断是哪一个channel扫的码,这样知道了是哪个channel才可以向前端发送对应的通知。5、后端接收到扫码事件,向MQ发送用户扫码成功,MQ异步通知前端扫码成功等待授权。

a1308003218的博客 1308

手把手教你在openclaw中白嫖英伟达羊毛调用GLM5/Minimax2.1/Kimi K 2.5等模型

本文介绍如何在OpenClaw中免费调用英伟达平台的GLM5、Minimax2.1和Kimi K2.5等模型。通过在openclaw.json配置文件中添加NVIDIA的API信息,设置免费模型参数(如contextWindow和maxTokens),并执行简单命令即可切换默认模型。该方法可节省token消耗成本,最后需重启gateway使配置生效。文中还提供了使用其他API平台时的配置模板。

Science prince的博客 5103

SpringBoot 实现扫码登录

路人发表于上海收录于合集#java充电社451个Java充电社,专注分享Java技术干货,包括多线程、JVM、SpringBoot、SpringCloud、Dubbo、Zookeeper、Redis、架构设计、微服务、消息队列、Git、面试题、程序员攻略、最新动态等。28篇原创内容公众号最近有个项目涉及到 websocket 实现扫码登录,看到一篇不错的技术文,分享一下。这表是干啥的呢?就是记录一下谁扫码了。谁登录了。

竹林幽深 266

Open3D 机载点云电力线提取【2025最新版】

传统方法实现的机载点云电力线分割提取。博客长期更新,本文最新更新时间为:2025年3月23日。

点云侠的博客 4468

Fiori Elements 的 Adapting the UI: Analytical List Page 全解析与实战

摘要:本文详细介绍了如何通过SAP Fiori elements的UI Adaptation功能对Analytical List Page(ALP)进行细粒度适配。重点讲解了页头过滤区、图表区域、表格区域等关键组件的可配置属性,包括过滤区折叠/固定、图表类型限制、表格滚动阈值等实用功能。文章强调这些适配均可通过可视化工具完成,无需修改业务代码,并提供了典型场景的实践清单。适用于SAP Fiori elements for OData V2项目,建议配合SAP Fiori tools的Adaptation E

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 432

springboot+vue基于web的扫码解锁共享单车管理系统设计与实现

对于本系统,我们提供全方位的支持,包括修改时间和标题,以及完整的安装、部署、运行和调试服务,确保系统能在你的电脑上顺利运行。单车信息录入(编号、型号、GPS定位)、状态监控(可用/故障/骑行中)、维修记录管理、单车分布热力图展示。记录用户骑行轨迹、时长、距离、卡路里消耗等数据,支持历史记录查询与数据可视化展示。用户注册、登录、个人信息管理、实名认证、押金缴纳与退还、信用积分管理。通过FICO信用分模型,结合用户骑行行为(违规停车、报修响应速度)动态调整信用等级,影响押金额度和用车权限。

QQ402205496的博客 332

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

springboot图书馆座位预约微信小程序系统设计实现

图书馆座位资源有限,尤其在考试周或高峰期,座位供不应求,传统的人工占座或现场排队方式效率低下,易引发纠纷。核心表包括用户表、座位表、预约记录表。预约记录表关联用户和座位,包含预约时间段、状态等字段。使用Redis Bitmap记录座位实时状态(0/1表示可用/占用),优化查询性能。用户发起预约时,通过Redis分布式锁(Redisson)防止重复预约。MySQL 8.0作为关系型数据库,存储用户信息、座位数据和预约记录。处理预约请求时需要检查座位状态和冲突预约,采用乐观锁防止超卖。

QQ358101148的博客 743

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

java+vue基于springboot的图书馆座位预约微信小程序系统

基于SpringBoot和Vue的图书馆座位预约微信小程序系统,采用前后端分离架构,实现图书馆座位的在线预约、管理及实时状态查询。后端使用SpringBoot提供RESTful API,前端通过Vue构建微信小程序界面,结合微信生态实现便捷的用户交互。

ztopmind的博客 288

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

springboot+vue3+mysql+websocket实现的即时通讯软件

本项目为即时聊天服务的后端,由Springboot+vue3+mysql+websocket实现,功能参考微信等聊天软件设计 技术栈 后端 选用编程语言 Java web框架SpringBoot db MySQL 持久存储 nosql 缓存 Redis 全双工通信框架 WebSocket 前端 前端框架Vue3 Typescript UI样式 Css、ElementPlus 网页路由 vue-router 全双工通信框架Websocket

pedestrian_h的博客 1854

基于SpringBoot的图书馆座位预约微信小程序系统毕设源码+文档+讲解视频

本课题针对图书馆传统座位管理中资源分配不均、预约流程繁琐、占座现象频发等问题,基于 SpringBoot 框架开发图书馆座位预约微信小程序系统,整合 MyBatis-Plus、Redis、WebSocket 等技术,采用 “后端服务 + 微信小程序前端” 架构,覆盖座位查询(实时显示座位占用状态、位置分布图)、预约管理(时段选择、预约取消、临时改签)、签到签退(扫码签到、超时自动释放)、违规管理(占座警告、黑名单记录)及数据统计(座位使用率、高峰时段分析)等功能,支持读者微信授权登录、消息推送(预约成功、即

全网粉丝10W+、全栈领域优质创作者、掘金、阿里云等社区博客专家、专注于全栈领域和毕业项目实战 1018

SpringBoot+微信小程序 微信课堂点名系统平台完整项目源码+SQL脚本+接口文档【Java Web毕设】

直接拿走,意外获得200多套代码,需要的滴我SpringBoot+微信小程序 微信课堂点名系统平台完整项目源码+SQL脚本+接口文档【Java Web毕设】(可提供说明文档(通过*AIGC*)

QQ1273148749的博客 732

BS1019-基于Java+Websocket+Vue.js实现仿微信即时通讯聊天APP系统

基于Java+Websocket+Vue.js实现仿微信即时通讯聊天APP系统,采用面向对象思想,选用Vue.js技术实现APP端聊天界面,后台选用JavaSSM轻量级开发框架,采用websocket实现客户端与服务端的及时通信。系统主要界面功能仿照微信实现,具备聊天通讯录,我的朋友圈,个人信息等模块。

qq_2934918046的博客 1万+

SpringBoot+微信小程序 课堂考勤微信平台完整项目源码+SQL脚本+接口文档【Java Web毕设】

直接拿走,意外获得200多套代码,需要的滴我SpringBoot+微信小程序 课堂考勤微信平台完整项目源码+SQL脚本+接口文档【Java Web毕设】(可提供说明文档(通过*AIGC*)

die_wang的博客 735
上一篇: javamail 发送邮件报错:Could not connect to SMTP host: smtp.163.com, port: 25 ,问题解决
下一篇: List深层复制
夕颜以北
博客等级 码龄9年 30粉丝 46原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值