SpringBoot微信扫码登录(小程序版)

一、需求描述

用户在PC端用微信扫描二维码实现后台登录
图示:
SpringBoot微信扫码登录(小程序版)_第1张图片
SpringBoot微信扫码登录(小程序版)_第2张图片
SpringBoot微信扫码登录(小程序版)_第3张图片

二、实现原理

SpringBoot微信扫码登录(小程序版)_第4张图片
此处采用socket实现,当然也可以通过轮询去监测微信扫码状态

三、实现步骤

1. 微信公众平台小程序后台申请跳转链接

开发管理->开发设置->扫普通链接二维码打开小程序

SpringBoot微信扫码登录(小程序版)_第5张图片

2. PC请求websocket连接获取二维码key

客户端:




    
    Title



扫码


服务端:

@Configuration
public class WebSocketConfig {

    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}
public class JsonWebSocketEncoder implements Encoder.Text {

    @Override
    public String encode(Result object) throws EncodeException {
        try {
            return JSONUtil.toJsonStr(object);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public void init(EndpointConfig endpointConfig) {

    }

    @Override
    public void destroy() {

    }
}
@Slf4j
@Component
@ServerEndpoint(value = "/socket/wx/qrLogin", encoders = JsonWebSocketEncoder.class)
public class WxQrLoginWebSocket {

    public static final ConcurrentMap sockets = new ConcurrentHashMap<>();

    @OnOpen
    public void onOpen(Session session) {
        log.info("客户端连接成功:{}", session.getId());
        //将连接结果告诉客户端用于携带进入小程序(此处直接用的雪花算法,可使用其他方式生成key)
        String key = IdUtil.getSnowflake().nextIdStr();
        //存放session
        sockets.put(key, session);
        log.info("当前在线客户端:{}个", sockets.size());
        //通知客户端
        sendMessage(key, Result.result("0", null, key));
    }

    /**
     * 链接关闭调用的方法
     */
    @OnClose
    public void onClose(Session session) {
        try {
            sockets.entrySet().removeIf(entry -> session.getId().equals(entry.getValue().getId()));
            log.info("【websocket消息】连接断开,sessionId:" + session.getId());
            log.info("当前在线客户端:{}个", sockets.size());
        } catch (Exception e) {
            e.printStackTrace();
            log.error(e.getMessage());
        }
    }

    /**
     * 收到客户端消息后调用的方法
     *
     * @param message
     * @param session
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        log.info("【websocket消息】收到客户端心跳:{},{}", message, session.getId());
        log.info("当前在线客户端:{}个", sockets.size());
    }

    /**
     * 发送错误时的处理
     *
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {
        try {
            //打印错误
            log.error("用户错误,原因:" + error.getMessage());
            error.printStackTrace();
            //关闭链接
            session.close();
            sockets.entrySet().removeIf(entry -> session.getId().equals(entry.getValue().getId()));
            log.info("当前在线客户端:{}个", sockets.size());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 消息
     *
     * @param key  key
     * @param data 数据
     **/
    public static void sendMessage(String key, Object data) {
        sockets.get(key).getAsyncRemote().sendObject(data);
    }
}

3. 小程序获取key并授权登录

客户端:

  1. 获取key

     onLoad(option) {
      if(option.q){
          //微信扫描扫描二维码进来的
          let url = decodeURIComponent(option.q)
          let params = this.getUrlParam(url)
          console.log('从微信扫码过来params')
          //此处需要存储这个pcKey后面会用到
          console.log(params.pcKey)
      }
     }
     getUrlParam(url){
        let params = url.split("?")[1].split("&");
        let obj = {};
        params.map(v => (obj[v.split("=")[0]] = v.split("=")[1]));
        return obj
     }
  2. 请求服务端登录并通知PC端

    wx.request({
     url: 'https://ip:port/...', //仅为示例,并非真实的接口地址
     method: 'POST',
     header: {
         'content-type': 'application/x-www-form-urlencoded'
     },
     data: {
         username: 'username',
         password: 'password'
     },
     success: function (res) {
         //获取token
         var token = res.data.accessToken;
         //通知服务端告诉PC登录
         wx.request({
             url: 'https://ip:port/auth/notifyPcLogin', //仅为示例,并非真实的接口地址
             method: 'GET',
             header: {
                 'Authorization': token
             },
             data: {
                 pcKey: '之前存储的key'
             },
             success: function (res) {
                 console.log(res.data)
             }
         })
     }
    })

    服务端:

    @RestController
    @RequestMapping("/auth")
    @RequiredArgsConstructor
    public class LoginController {
    
     private final WxLoginService wxLoginService;
    
     @ApiOperation(value = "通知PC登录接口")
     @GetMapping("/notifyPcLogin")
     public Result notifyPcLogin(
             @ApiParam(name = "pcKey", value = "PC端后端给的唯一标识", required = true)
             @NotBlank(message = "唯一标识不能为空") @RequestParam(value = "pcKey") String pcKey
     ) {
         return Result.success(wxLoginService.notifyPcLogin(pcKey));
     }
    }
    @Service
    @RequiredArgsConstructor
    public class WxLoginService {
    
     private final ISysUserService sysUserService;
    
     /**
      * 唤醒PC登录
      *
      * @param pcKey pc唯一key
      * @return java.lang.Boolean
      * @author Guo Shuai
      * @since 1.0
      **/
     public Boolean notifyPcLogin(String pcKey) {
         //获取socket session
         Session session = WxQrLoginWebSocket.sockets.get(pcKey);
         //判断是否存在
         if (ObjectUtil.isNull(session)) {
             return Boolean.FALSE;
         }
         //获取当前登录用户手机号
         String phone = sessionService.getLoginUser().getPhone();
         //通过手机号查找用户
         SysUser sysUser = sysUserService.lambdaQuery().eq(SysUser::getMobile, phone).one();
         //获取用户pc端token
         LoginUserVO loginUserVO = sysUserService.getUserToken(sysUser, sysUser.getMobile(), sessionService.getTenantId(), "10");
         //通知pc登录
         WxQrLoginWebSocket.sendMessage(pcKey, Result.result("1", null, loginUserVO));
         //返回
         return Boolean.FALSE;
     }
    }

    至此整个流程结束

    四、注意事项

    小程序申请的跳转链接只有正式版可以携带自定义的动态参数,否则只能用配置的测试地址,测试地址可以配置最多5个

你可能感兴趣的:(SpringBoot微信扫码登录(小程序版))