第三方登录功能的实现之 QQ登录 - 有账号

940 阅读8分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第26天,点击查看活动详情

目录

登录流程梳理

用户操作

 前置工作准备 - 了解

电脑环境设置

目的

核心步骤

验证效果 

DNS 解析概念 - 了解

QQ授权登录实现

改进一下:不要跳转到新页面

 三方登录之后的判断逻辑

1.在登录页面, QQ登录按钮处,赋予其打开QQ登录页面功能

2. 回跳的页面得到QQ给的唯一标识 openId,此时表示QQ已经认可TA: TA是QQ家的用户,但是,是不是我们系统的用户呢? 根据openId去后台查询是否已经绑定过账户

3.登录成功后,跳转首页,或者来源页面

QQ登录-基本结构和切换交互

QQ登录 - 创建组件

 思路

 组件1 - 绑定手机

组件2 - 完善信息

QQ登录 - 已绑定账号

思路

补充类型声明

QQ登录 - 未绑定 - 有账号

QQ登录 - 未绑定 - 无账号


现在越来越流行第三方登录,它在我们生活中是到处可见的,就比如QQ、微信扫码登录等等

下面呐,就借用QQ登录来讲解是第三方登录是怎样实现的

        因为其他第三方登录的思路跟QQ登录思路相同

登录流程梳理

目标:掌握第三方登录的实现流程

用户操作

点击QQ登录  ---> 小兔仙跳转到QQ登录页--->扫码/用户名密码登录 --->登录成功 ---> 回跳目标页(是提前与QQ约定的)

​编辑

 前置工作准备 - 了解

1)参考文档

  1. 准备工作(opens new window)

     2.QQ互联JS_SDK(opens new window)

2)大致步骤

  1. 准备一个已经备案的网站需要有QQ登录的逻辑(登录页面,回跳页面)
  2. 然后在QQ互联上进行身份认证,并且审核通过
  3. 在QQ互联上创建应用,应用需要域名,备案号,回调地址等
  4. 等待人工审核,审核通过会得到应用ID 应用key 回调地址
  5. 帮大家申请的结果如下:
# 测试用 appid 
# 100556005
# 测试用 redirect_uri
# http://www.corho.com:8080/#/login/callback

那问题来了

1.这个申请工作一般由谁去做?

        公司的运维(负责管理公司账号的人)

2.申请下来的id,应用key,回调地址url能改吗?

        a.都不能修改,负责无效

        b.回调地址 uri 的包含四部分: 1. 域名,2. 端口号 3. 哈希路由模式 4. 路由地址 都必须完全一致,否则不能展示。

3.回调url打开看不到内容?

www.corho.com:8080/#/login/cal…

        a.修改vite.config.ts 配置。

        b.修改电脑的 host 文件,访问本地服务器。

        c.配置VueRouter路由 和 vue 组件。

电脑环境设置

目的

就是让浏览器访问www.corho.com:8080/#/login/cal… 地址,能打开正在开发的 Vue 项目。

核心步骤

  1. 项目中准备页面及路由。配置 VueRoutervue 组件。
  2. 设置项目的访问地址为指定域名。修改 vite.config.ts 配置。
  3. 修改电脑的 host 文件。

(1)修改vite.config.ts 配置。

export default defineConfig({
  // 配置开发服务器
  server: {
    // QQ三方登录的回调uri为:http://www.corho.com:8080/#/login/callback
    // vite 中配置: www.corho.com:8080
    host: "www.corho.com",
    port: 8080,
    // 其他有价值的配置项
    open: true, // 帮我们打开浏览器
    cors: true, // 允许开发时 ajax 跨域
  },
  ...
});

(2)修改host文件

Windows系统

提醒:修改电脑配置,需要先退出 360 或 各种管家 各种 杀毒软件

提醒:如果修改 hosts 文件有弹窗警告,点击信任(因为这是我们自己进行的安全操作)

  1. 找到 C:\Windows\System32\drivers\etc 下hosts文件
  2. 在文件中加入  127.0.0.1       www.corho.com
  3. 保存即可

如果提示没有权限

  1. 将hosts文件移到桌面,然后进行修改,确认保存。
  2. 将桌面hosts文件替换c盘文件

 mac OS 系统

  1. 打开命令行窗口
  2. 输入:sudo vim /etc/hosts
  3. 按下:i 键                             (进入编辑状态 => 利用光标挪位置)
  4. 输入:127.0.0.1       www.corho.com     (改完如何保存? 看下一步)
  5. 按下:esc
  6. 按下:shift + :
  7. 输入:wq 回车即可

验证效果 

 浏览器访问 www.corho.com:8080/#/ 能看到自己开发的 Vue3 项目表示第二步配置成功。

(3)配置路由和组件

         a。新建组件: views/Login/Callback.vue

<script setup lang="ts" name="LoginCallback">
//
</script>

<template>
  <h1>callback-QQ登录回跳页面测试</h1>
</template>

        b。配置路由:

 绑定路由(一级路由)

{
path: '/login/callback',
component: () => import('@/views/login/callback.vue')
}

 步骤验证:www.corho.com:8080/#/login/cal…

DNS 解析概念 - 了解

由于本地我们的网站是访问http://localhost:8080   =>  http://127.0.0.1:8080

而回调地址的域名是http://www.corho.com:8080,俩个地址不一致是无法进行跳转的,

需要我们修改本地的 hosts 文件,让域名访问时解析到我们本地的ip上

DNS 解析:(网络中,服务器不认域名的,认的是 ipwww.jd.com

  1. 作用是将域名地址解析成ip地址
  2. 优先级 先以本地的 hosts 文件为主 然后才走线上的dns服务器

DSN解析说明

DNS解析: 将域名解析成ip地址的过程。

想看一个网站 www.jd.com => 电脑不知道什么是 www.jd.com,需要询问的

1.先问本地hosts文件(一般不改) 如果本地配置了 域名 和 地址的映射关系,优先使用 hosts 中的映射

127.0.0.1           www.jd.com

 2.如果本地hosts文件里面没配(默认一般都没配)比如:找www.baidu.com

会找线上的 dns 服务器, dns 服务器就像一个字典, 
字典中记录大量的 网站域名 和 网站ip 的对应关系

dns 服务器
112.80.248.75      www.baidu.com
xxx.xx.xxx.xx      www.xxx.com 

QQ授权登录实现

 官网流程:QQ互联JS_SDK(opens new window)

(1)在index.html中添加

<script
  type="text/javascript"
  charset="utf-8"
  src="http://connect.qq.com/qc_jssdk.js"
  data-appid="100556005"
  data-redirecturi="http://www.corho.com:8080/#/login/callback"
></script>
<script type="text/javascript">
    QC.Login({
       btnId:"qqLoginBtn"//插入按钮的节点id
});
</script>
<span id="qqLoginBtn"></span>

编辑

 上面的代码的作用是生成 登录按钮, 之后就可以删除这段代码!!

现在就可以实现QQ的登录啦。

改进一下:不要跳转到新页面

编辑

 window.open可以打开一个新的窗口 ===》 我们不需要!

 改进: 可以直接通过a标签指向这个地址

具体代码

在组件src/views/Login/components/LoginForm.vue中添加登录图标

<a
  href="https://graph.qq.com/oauth2.0/authorize?client_id=100556005&amp;response_type=token&amp;scope=all&amp;redirect_uri=http%3A%2F%2Fwww.corho.com%3A8080%2F%23%2Flogin%2Fcallback"
  ><img
    src="https://qzonestyle.gtimg.cn/qzone/vas/opensns/res/img/Connect_logo_7.png"
    alt="QQ登录"
    border="0"
/></a>

 三方登录之后的判断逻辑

1.在登录页面, QQ登录按钮处,赋予其打开QQ登录页面功能

2. 回跳的页面得到QQ给的唯一标识 openId,此时表示QQ已经认可TA: TA是QQ家的用户,但是,是不是我们系统的用户呢? 根据openId去后台查询是否已经绑定过账户

    如果绑定过,完成登录

    没有绑定过

         · 有账号的,绑定手机号,即为登录

        ·  没账号的, 完善账户信息,即为登录

3.登录成功后,跳转首页,或者来源页面

QQ登录-基本结构和切换交互

(1)准备结构

<script lang="ts" setup name="LoginCallback">
import LoginHeader from './components/LoginHeader.vue'
import LoginFooter from './components/LoginFooter.vue'
</script>

<template>
  <LoginHeader>QQ登录</LoginHeader>
  <section class="container">
    <nav class="tab">
      <a href="javascript:;">
        <i class="iconfont icon-bind" />
        <span>已有小兔鲜账号,请绑定手机</span>
      </a>
      <a href="javascript:;">
        <i class="iconfont icon-edit" />
        <span>没有小兔鲜账号,请完善资料</span>
      </a>
    </nav>
    <div class="tab-content" v-if="true">绑定手机</div>
    <div class="tab-content" v-else>完善资料</div>
  </section>
  <LoginFooter></LoginFooter>
</template>

<style scoped lang="less">
.container {
  padding: 25px 0;
}
.tab {
  background: #fff;
  height: 80px;
  padding-top: 40px;
  font-size: 18px;
  text-align: center;
  a {
    color: #666;
    display: inline-block;
    width: 350px;
    line-height: 40px;
    border-bottom: 2px solid #e4e4e4;
    i {
      font-size: 22px;
      vertical-align: middle;
    }
    span {
      vertical-align: middle;
      margin-left: 4px;
    }
    &.active {
      color: @xtxColor;
      border-color: @xtxColor;
    }
  }
}
.tab-content {
  min-height: 600px;
  background: #fff;
}
</style>

(2)切换效果

<script lang="ts" setup name="LoginCallback">
import LoginHeader from './components/login-header.vue'
import LoginFooter from './components/login-footer.vue'
+ import { ref } from 'vue'
+ const hasAccount = ref(true)
</script>

<template>
  <LoginHeader></LoginHeader>
  <section class="container">
    <nav class="tab">
      <a
+        :class="{ active: hasAccount }"
        href="javascript:;"
+        @click="hasAccount = true"
      >
        <i class="iconfont icon-bind" />
        <span>已有小兔鲜账号,请绑定手机</span>
      </a>
      <a
+        :class="{ active: !hasAccount }"
        href="javascript:;"
+        @click="hasAccount = false"
      >
        <i class="iconfont icon-edit" />
        <span>没有小兔鲜账号,请完善资料</span>
      </a>
    </nav>
+    <div class="tab-content" v-if="hasAccount">绑定手机</div>
+    <div class="tab-content" v-else>完善资料</div>
  </section>
  <LoginFooter></LoginFooter>
</template>

QQ登录 - 创建组件

 思路

这两种情况对应不同的表单,所以这里分别封装成两个小的组件

编辑

编辑

 组件1 - 绑定手机

src/views/Login/components/CallbackBind.vue 绑定手机

<script name="CallbackBind" lang="ts" setup></script>
<template>
  <div class="xtx-form">
    <div class="user-info">
      <img
        src="http://qzapp.qlogo.cn/qzapp/101941968/57C7969540F9D3532451374AA127EE5B/50"
        alt=""
      />
      <p>Hi,Tom 欢迎来小兔鲜,完成绑定后可以QQ账号一键登录哦~</p>
    </div>
    <div class="xtx-form-item">
      <div class="field">
        <i class="icon iconfont icon-phone"></i>
        <input class="input" type="text" placeholder="绑定的手机号" />
      </div>
      <div class="error"></div>
    </div>
    <div class="xtx-form-item">
      <div class="field">
        <i class="icon iconfont icon-code"></i>
        <input class="input" type="text" placeholder="短信验证码" />
        <span class="code">发送验证码</span>
      </div>
      <div class="error"></div>
    </div>
    <a href="javascript:;" class="submit">立即绑定</a>
  </div>
</template>

<style scoped lang="less">
.user-info {
  width: 320px;
  height: 70px;
  margin: 0 auto;
  display: flex;
  background: #f2f2f2;
  align-items: center;
  padding: 0 10px;
  margin-bottom: 25px;
  img {
    background: #f2f2f2;
    width: 50px;
    height: 50px;
  }
  p {
    padding-left: 10px;
  }
}
.code {
  position: absolute;
  right: 0;
  top: 0;
  line-height: 50px;
  width: 80px;
  color: #999;
  &:hover {
    cursor: pointer;
  }
}
</style>

组件2 - 完善信息

 src/views/Login/components/CallbackPatch.vue 完善信息

<script lang="ts" setup name="CallbackPatch"></script>
<template>
  <div class="xtx-form">
    <div class="xtx-form-item">
      <div class="field">
        <i class="icon iconfont icon-user"></i>
        <input class="input" type="text" placeholder="请输入用户名" />
      </div>
      <div class="error"></div>
    </div>
    <div class="xtx-form-item">
      <div class="field">
        <i class="icon iconfont icon-phone"></i>
        <input class="input" type="text" placeholder="请输入手机号" />
      </div>
      <div class="error"></div>
    </div>
    <div class="xtx-form-item">
      <div class="field">
        <i class="icon iconfont icon-code"></i>
        <input class="input" type="text" placeholder="请输入验证码" />
        <span class="code">发送验证码</span>
      </div>
      <div class="error"></div>
    </div>
    <div class="xtx-form-item">
      <div class="field">
        <i class="icon iconfont icon-lock"></i>
        <input class="input" type="password" placeholder="请输入密码" />
      </div>
      <div class="error"></div>
    </div>
    <div class="xtx-form-item">
      <div class="field">
        <i class="icon iconfont icon-lock"></i>
        <input class="input" type="password" placeholder="请确认密码" />
      </div>
      <div class="error"></div>
    </div>
    <a href="javascript:;" class="submit">立即提交</a>
  </div>
</template>

<style scoped lang="less">
.code {
  position: absolute;
  right: 0;
  top: 0;
  line-height: 50px;
  width: 80px;
  color: #999;
  &:hover {
    cursor: pointer;
  }
}
</style>

渲染组件

<script lang="ts" setup name="LoginCallback">
import LoginHeader from './components/LoginHeader.vue'
import LoginFooter from './components/LoginFooter.vue'
+import CallbackBind from './components/CallbackBind.vue'
+import CallbackPatch from './components/CallbackPatch.vue'
import { ref } from 'vue'
const hasAccount = ref(true)
</script>

<template>
  <LoginHeader></LoginHeader>
  <section class="container">
    <nav class="tab">
      <a
        :class="{ active: hasAccount }"
        href="javascript:;"
        @click="hasAccount = true"
      >
        <i class="iconfont icon-bind" />
        <span>已有小兔鲜账号,请绑定手机</span>
      </a>
      <a
        :class="{ active: !hasAccount }"
        href="javascript:;"
        @click="hasAccount = false"
      >
        <i class="iconfont icon-edit" />
        <span>没有小兔鲜账号,请完善资料</span>
      </a>
    </nav>
    <div class="tab-content" v-if="hasAccount">
+      <CallbackBind></CallbackBind>
    </div>
    <div class="tab-content" v-else>
+      <CallbackPatch></CallbackPatch>
    </div>
  </section>
  <LoginFooter></LoginFooter>
</template>

QQ登录 - 已绑定账号

QQ登录之后会有三种情况需要处理

  1. 该QQ账号已经绑定过小兔鲜账号,直接跳转到首页
  2. 该QQ账号没有绑定有账号

                    a:有账号,直接绑定QQ号

                    b:没有账号,完善注册信息并绑定QQ号

思路

  1. 回跳组件初始化的时候根据QQ的接口获取openId
  2. 根据openId去自己后台尝试进行登录
  3. 如果成功,就代表已注册已绑定,记录返回的用户信息,跳转到首页或者来源页面

js dk公开方法

OpenAPI调用说明_OAuth2.0 — QQ互联WIKI

  1. QC.Login.check() 检查此时QQ是否已经登录
  2. QC.Login.getMe() 获取用户标识openId 将来传给userQQLogin作为第一个参数
  3. QC.api("get_user_info") 获取用户资料

补充类型声明

新增QC的类型声明 在文件中 env.d.ts

直接使用QC在TS中会报错,因为TS无法识别QC,且QQ第三方登录默认是不支持ts的,需要自定义命名空间来实现该效果。

declare namespace QC {
  const Login: {
    /**
     * 检查QQ是否登录成功
     */
    check: () => boolean
    /**
     * 获取openId
     */
    getMe: (callback: (openId: string) => void) => void
  }
  const api: (api: 'get_user_info') => { success: (res: any) => void }
}

(2)确认当前QQ是否登录成功

// 获取用户信息
const isLogin = QC.Login.check()
console.log(isLogin)

(3)获取openid

// 1. 判断用户是否登录
const isLogin = QC.Login.check()
// console.log(isLogin)
if (isLogin) {
  // 2. 获取openId
  QC.Login.getMe((openId) => {
    console.log(openId)
  })
}

(4)封装方法,使用QQ登录

 source: 1为pc,2为webapp,3为微信小程序, 4为Android, 5为ios, 6为qq, 7为微信

//  source: 1为pc,2为webapp,3为微信小程序, 4为Android, 5为ios, 6为qq, 7为微信
async qqLogin(openId: string) {
  const res = await request.post<IApiRes<Profile>>('/login/social', {
    unionId: openId,
    source: 6
  })
  // 1. 保存用户信息到 state 中
  this.profile = res.data.result
  setProfile(res.data.result)
},

(5)登录成功

// 1. 判断用户是否登录
const isLogin = QC.Login.check()
// console.log(isLogin)
if (isLogin) {
  // 2. 获取openId
  QC.Login.getMe(async (openId) => {
    // 3. 发送请求进行QQ登录
    await user.qqLogin(openId)
    Message.success('登录成功')
    router.push('/')
  })
}

QQ登录 - 未绑定 - 有账号

QQ登录 - 未绑定 - 无账号