面试官:如何实现微信/支付宝扫码登录?(以bbs-go开源社区项目为例)

311 阅读5分钟

ChatGPT Image 2026年1月5日 09_27_44.png

简述

通过本篇教程,你可以了解到如何把微信登录功能接入到系统中,除了微信登录之外,其他的第三方登录方式也能以同样的方式接入到系统。

本篇教程以开源项目 bbs-go 为例,为读者介绍如何实现系统的微信登录功能。bbs-go 是一款基于 Golang 的开源社区系统。简洁对话,高效互动,社区新体验!

bbs-go 项目地址:gitee.com/mlogclub/bb…

文档地址:bbs-go.com/

准备环境

后端环境

首先,我们需要将代码下载到本地,然后使用 Goland 打开。此处不需要 git clone,直接下载 master 分支的就行。

image.png

启动一个 Go 程序项目之前需要安装一下依赖项,输入 go mod tidy 命令安装依赖。

在 server 目录下,找到 bbs-go-example.yaml 文件。这是项目的配置文件,也是提供好的示例,我们需要拷贝一份且名称需要命名为 bbs-go.dev.yaml。

在框起来的地方修改你的数据库的用户名和密码,其他的中间件暂时用不到,所以不需要配置。

image.png

数据库环境准备完成之后,直接点击 main.go 文件的 main 函数启动。启动之后日志如下:

image.png

前端环境

首先你需要安装 Node.js,版本选择 V20,这个不做赘述。还有依赖安装也不做介绍,自行解决。唯一需要注意的是 site 模块使用 .env 文件进行配置。源代码中提供了一个示例配置文件 .env.example,你需要根据实际环境创建对应的配置文件:

# 复制示例配置文件
# 开发环境
cp .env.example .env.local

# 生产环境
cp .env.example .env.production

目前配置文件主要包含以下参数:

SERVER_URL 是 site 模块访问 server 模块的 API 地址,这是必须配置的参数:

  • 开发环境通常设置为:http://localhost:8082
  • 生产环境设置为您的实际 API 地址,例如:https://api.yourdomain.com

开发环境:

SERVER_URL=http://localhost:8082

生产环境:

SERVER_URL=https://api.yourdomain.com

最后启动一下前端工程,点击 site 目录下的 package.json 的 dev 按钮启动工程:

image.png

启动之后登录界面如下:

image.png

你可以进去随便创建一个账号然后登录进去。

了解第三方登录

我们现在要实现的效果是正在上图的账号登录右侧多出第三方登录的页签,目前仅支持微信登录。

image.png

我们现在需要了解使用微信扫码登录的流程,如下图所示:

image.png

  1. 用户在选择微信登录选项之后,使用手机扫码之后会跳转到微信登录的确认界面,点击确认之后微信服务器会调用我们设置好的回调接口。
  2. 然后我们的程序会拿到微信服务器返回的 code 参数,我们可以使用这个参数再去调用的微信的 oauth2 接口获取 access_tokenopenid 信息。
  3. 上一步会返回 access_tokenopenid 参数,我们再根据这两个参数调用微信的用户信息接口获取用户信息。
  4. 最后校验我们的数据库是否存在该用户,没有则创建一个,有那么直接登录成功。

我们需要在微信开放平台创建网站应用获得 AppId 和 AppSecret,参考教程如下:

image.png

链接:open.weixin.qq.com/cgi-bin/app…

内网穿透

想要实现外部服务调用己方服务接口的功能,那么还需要一个内网穿透工具,这里推荐 NATAPP,自行查询教程。

官网:natapp.cn/

image.png

开始编码

前端工程

先找到登录页面对应的文件,路径如下:src/pages/user/signin/index.vue

在账号登录下面可以新增一项:第三方登录。

image.png

然后根据页签序号的值判断样式 is-active 是否使用:

<li :class="{'is-active': tabIndex === 1}">
  <a @click="switchLoginMethod('third-party')">第三方登录</a>
</li>

编写修改页签序号的函数:

const tabIndex = ref<number>(0)

function switchLoginMethod(way: string) {
  switch (way) {
    case 'password':
      tabIndex.value = 0
      break
    case 'third-party':
      tabIndex.value = 1
      break
  }
}

最后模仿账号登录组件,开发一个第三方登录,命名为 thirdparty。因为这是在 Nuxt 工程内部,所以不需要显式地声明路由,Nuxt 会自动生成路由。

代码如下:

<template>
  <div class="login-method">微信登录</div>
  <div style="text-align: center">
    <qrcode-vue :value="wechatLoginUrl" :size="220" />
  </div>
</template>

<script setup lang="ts">
import QrcodeVue from 'qrcode.vue'

const appid = '你的appId'
const redirectUri = encodeURIComponent('http://域名/wechat-callback')
const state = Math.random().toString(36).substring(2)
const wechatLoginUrl = `https://open.weixin.qq.com/connect/qrconnect?appid=${appid}&redirect_uri=${redirectUri}&response_type=code&scope=snsapi_login&state=${state}#wechat_redirect`
</script>

<style scoped lang="scss">
.login-method {
  text-align: center;
  font-size: 20px;
}
</style>

这里使用了一个第三方的二维码生成组件,使用如下命令安装:

pnpm install qrcode.vue

最后效果如下:

image.png

后端工程

在 dto 包下声明 TokenResp 结构体,用于接收微信的响应体:

type TokenResp struct {  
    AccessToken string `json:"access_token"`  
    OpenID string `json:"openid"`  
    ErrMsg string `json:"errmsg"`  
}

还有微信的用户详情结构体 UserInfoResp

type UserInfoResp struct {
    OpenID    string `json:"openid"`
    Nickname  string `json:"nickname"`
    Sex       int    `json:"sex"`
    HeadImg   string `json:"headimgurl"`
    ErrMsg    string `json:"errmsg"`
}

在 login_controller.go 文件中添加 GetWechatCallBack 方法,用于作为微信回调接口:

// 微信登录的回调接口
func (c *LoginController) GetWechatCallBack() {
    code := c.Ctx.URLParam("code")
    state := c.Ctx.URLParam("state")

    if state != "state123" {
        c.Ctx.JSON(iris.Map{"error": "无效state"})
        return
    }

    tokenURL := fmt.Sprintf("https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code",
            "你的AppID", "你的AppSecret", code)

    data, err := common.HttpGet(tokenURL)
    if err != nil {
        c.Ctx.JSON(iris.Map{"error": "access_token 获取失败"})
        return
    }

    var token dto.TokenResp
    if err := json.Unmarshal(data, &token); err != nil {
        c.Ctx.JSON(iris.Map{"error": "解析失败"})
        return
    }

    // 查询是否已存在
    if user := repositories.UserRepository.Take(sqls.DB(), "open_id = ?", token.OpenID); user == nil {
        c.Ctx.JSON(iris.Map{
            "msg":      "登录成功(已有用户)",
            "openid":   user.OpenId,
            "nickname": user.Nickname,
        })
        return
    }

    // 获取用户信息
    userInfoURL := fmt.Sprintf("https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s", token.AccessToken, token.OpenID)
    userData, err := common.HttpGet(userInfoURL)
    if err != nil {
        c.Ctx.JSON(iris.Map{"error": "获取用户信息失败"})
        return
    }

    var userInfo dto.UserInfoResp
    if err := json.Unmarshal(userData, &userInfo); err != nil || userInfo.OpenID == "" {
        c.Ctx.JSON(iris.Map{"error": "解析用户信息失败"})
        return
    }

    // 写入数据库
    var gender constants.Gender
    if userInfo.Sex == 1 {
        gender = constants.GenderMale
    } else {
        gender = constants.GenderFemale
    }
    newUser := models.User{
        OpenId:   userInfo.OpenID,
        Nickname: userInfo.Nickname,
        Avatar:   userInfo.HeadImg,
        Gender:   gender,
    }

    if err := repositories.UserRepository.Create(sqls.DB(), &newUser); err != nil {
        c.Ctx.JSON(iris.Map{"error": "写入用户失败"})
        return
    }

    c.Ctx.JSON(iris.Map{
        "msg":      "注册成功",
        "openid":   newUser.OpenId,
        "nickname": newUser.Nickname,
    })
}

这个接口作为微信回调接口,用于接口微信服务器的请求。微信会提供两个参数分别是 statecode,这是临时票据,我们可以将它们再加上 AppIdAppSecret 用于请求微信的 oAuth2 接口,然后拿到 OpenIdAccessToken 参数,最后再通过这几个参数获取用户信息,根据 openId 判断用户的信息在数据库是否存在,不存在就插入数据库。如果存在那么就正常登录。