飞书开发学习笔记(六)-网页应用免登

飞书开发学习笔记(六)-网页应用免登

一.上一例的问题修正

在上一例中,飞书登录查看网页的界面显示是有误的,看了代码,理论上登录成功之后,应该显示用户名等信息。
最后的res.nickName是用户名,res.i18nName.en_us是英文名

function showUser(res) {
  // 展示用户信息
  // 头像
  $("#img_div").html(
    `<img src="${res.avatarUrl}" width="100%" height=""100%/>`
  );
  // 名称
  $("#hello_text_name").text(
    lang === "zh_CN" || lang === "zh-CN"
      ? `${res.nickName}`
      : `${res.i18nName.en_us}`
  );
  // 欢迎语
  $("#hello_text_welcome").text(
    lang === "zh_CN" || lang === "zh-CN" ? "欢迎使用飞书" : "welcome to Feishu"
  );
}

而这个页面结果显然是不正确的
在这里插入图片描述
仔细检查Console.log,原来是接收config参数时出现了错误,python使用了分步调试,发现问题出在

ticket = auth.get_ticket()

在这里插入图片描述
再进一步分析变量,发现是

APP_ID = os.getenv("APP_ID")
APP_SECRET = os.getenv("APP_SECRET")
FEISHU_HOST = os.getenv("FEISHU_HOST")

三个变量获取错误,最后锁定源头是.env中三个变量定义以后没有换行,应该是在查看后不小心修改了格式保存引起的,在改为标准格式,每个变量定义后换行,重新读入,成功获取用户名等信息,
这也是一个小插曲,对于学习来说却耽误了一个小时。
在这里插入图片描述
这才是登录成功的界面
在这里插入图片描述

二.应用免登

紧接着上一案例,就是应用免登的介绍
应用免登:应用免登

免登流程
网页应用免登为网页应用提供了获取当前登录用户的飞书身份的能力,网页应用免登流程如下:

步骤一:获取用户登录预授权码。
步骤二:使用预授权码获取user_access_token。
步骤三:获取用户信息并完成登录。
步骤四:刷新已过期的user_access_token。

实际应用分为四步,每个步骤与上一例基本相同。
不同点为第三步,配置网页应用访问地址时,需要同时填写
在这里插入图片描述
进入飞书开放平台: https://open.feishu.cn/app
完成重定向URL填写
在这里插入图片描述
从工作台打开应用后,同时获取用户基本信息
在这里插入图片描述
在这里插入图片描述

三.代码结构

3.1代码结构树

|── README.zh.md     ----- 说明文档
|── doc_images     ----- 说明文档的图片资源
|── public
|  |── svg     ----- 前端图形文件
|  |──index.css     ----- 前端展示样式
|  |── index.js     ----- 前端主要交互代码(免登流程函数、用户信息展示函数)
|── templates
|  |──err_info.html     ----- 前端错误信息展示页面
|  |── index.html     ----- 前端用户信息展示页面
|── auth.py     ----- 服务端免登流程类、错误信息处理类
|── server.py     ----- 服务端核心业务代码
|── requirements.txt     ----- 环境配置文件
└── .env     ----- 全局默认配置文件,主要存储 App ID 和 App Secret 等

3.2 index.html ----- 前端用户信息展示页面

在页面内部的js中,利用login_info判断是否已经登陆,如果已经登陆,就直接展示;否则走登陆程序
判断则转到index.js

<!DOCTYPE html>
<link rel="stylesheet" href="/public/index.css"/>
<html lang="en">
<head>
    <meta charset="UTF-8"/>
    <title>网页应用</title>
    <script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js">
    </script>
</head>

<!-- 在html文档中引入 JSSDK -->
<!-- JS 文件版本在升级功能时地址会变化,如有需要(比如使用新增的 API),请重新引用「网页应用开发指南」中的JSSDK链接,确保你当前使用的JSSDK版本是最新的。-->
<script type="text/javascript"
src="https://lf1-cdn-tos.bytegoofy.com/goofy/lark/op/h5-js-sdk-1.5.16.js">
</script>

<!-- 在页面上添加VConsole方便调试-->
<script src="https://unpkg.com/vconsole/dist/vconsole.min.js"></script>
<script>
// VConsole will be exported to `window.VConsole` by default.
var vConsole = new window.VConsole();
</script>

<body>
<div>
    <div class="img">
        <!-- 头像 -->
        <div id="img_div" class="img_div"> </div>
        <span class="hello_text">Hello</span>
        <!-- 名称 -->
        <div id="hello_text_name" class="hello_text_name"></div>
        <!-- 欢迎语 -->
        <div id="hello_text_welcome" class="hello_text_welcome"></div>
    </div>
    <!-- 飞书icon -->
    <div class="icon"><img src="../public/svg/icon.svg"/></div>
</div>

<!-- 在html文档中引入本demo免登流程函数apiAuth()和用户信息展示函数showUser(res) -->
<script src="/public/index.js"></script>
<!-- 根据服务端传来的参数login_info判断走免登流程还是直接展示用户信息 -->
<script>
    const login_info = '{{ login_info }}';
    console.log("login info: ", login_info);
    if (login_info == "alreadyLogin") {
        const user_info = JSON.parse('{{ user_info | tojson | safe }}');
        console.log("user: ", user_info.name);
        $('document').ready(showUser(user_info))
    } else {
        $('document').ready(apiAuth())
    }
</script>

</body>
</html>

3.3 index.js ----- 前端主要交互代码(免登流程函数、用户信息展示函数)

服务器端Route:server.py
1.服务器端执行get_appid获取app_id
2.调用JSAPI tt.requestAuthCode 获取 authorization code,参数为app_id,存储在 res.code
3.服务器端fetch把code传递给接入方服务端Route: callback,并获得user_info
4.成功后showUser(res)

let lang = window.navigator.language;
console.log(lang);

function apiAuth() {
    if (!window.h5sdk) {
        console.log('invalid h5sdk')
        alert('please open in feishu')
        return
    }

    // 通过服务端的Route: get_appid获取app_id
    // 服务端Route: get_appid的具体内容请参阅服务端模块server.py的get_appid()函数
    // 为了安全,app_id不应对外泄露,尤其不应在前端明文书写,因此此处从服务端获取
    fetch(`/get_appid`).then(response1 => response1.json().then(res1 => {
        console.log("get appid succeed: ", res1.appid);
        // 通过error接口处理API验证失败后的回调
        window.h5sdk.error(err => {
            throw('h5sdk error:', JSON.stringify(err));
        });
        // 通过ready接口确认环境准备就绪后才能调用API
        window.h5sdk.ready(() => {
            console.log("window.h5sdk.ready");
            console.log("url:", window.location.href);
            // 调用JSAPI tt.requestAuthCode 获取 authorization code
            tt.requestAuthCode({
                appId: res1.appid,
                // 获取成功后的回调
                success(res) {
                    console.log("getAuthCode succeed");
                    //authorization code 存储在 res.code
                    // 此处通过fetch把code传递给接入方服务端Route: callback,并获得user_info
                    // 服务端Route: callback的具体内容请参阅服务端模块server.py的callback()函数
                    fetch(`/callback?code=${res.code}`).then(response2 => response2.json().then(res2 => {
                        console.log("getUserInfo succeed");
                        // 示例Demo中单独定义的函数showUser,用于将用户信息展示在前端页面上
                        showUser(res2);}
                        )
                    ).catch(function (e) {console.error(e)})
                },
                // 获取失败后的回调
                fail(err) {
                    console.log(`getAuthCode failed, err:`, JSON.stringify(err));
                }
            })
        }
        )
    })).catch(function (e) { // 从服务端获取app_id失败后的处理
        console.error(e)
        })
}

function showUser(res) {
    // 展示用户信息
    // 头像
    $('#img_div').html(`<img src="${res.avatar_url}" width="100%" height=""100%/>`);
    // 名称
    $('#hello_text_name').text(lang === "zh_CN" || lang === "zh-CN" ? `${res.name}` : `${res.en_name}`);
    // 欢迎语
    $('#hello_text_welcome').text(lang === "zh_CN" || lang === "zh-CN" ? "欢迎使用飞书" : "welcome to Feishu");
}

3.4 auth.py ----- 服务端免登流程类、错误信息处理类

1.获取authorize_app_access_token,参数为 “app_id” 和 “app_secret”,获取到USER_ACCESS_TOKEN_URI
2.获取authorize_user_access_token,url 通过self._gen_url(USER_ACCESS_TOKEN_URI)获得
3.authorize_user_access_token的headers中 :“app_access_token” 位于HTTP请求的请求头
4.response.json().get(“data”)获得user_access_token


def authorize_app_access_token(self):
    # 获取 app_access_token, 依托于飞书开放能力实现. 
    # 文档链接: https://open.feishu.cn/document/ukTMukTMukTM/ukDNz4SO0MjL5QzM/auth-v3/auth/app_access_token_internal
    url = self._gen_url(APP_ACCESS_TOKEN_URI)
    # "app_id" 和 "app_secret" 位于HTTP请求的请求体
    req_body = {"app_id": self.app_id, "app_secret": self.app_secret}
    response = requests.post(url, req_body)
    Auth._check_error_response(response)
    self._app_access_token = response.json().get("app_access_token")

# 这里也可以拿到user_info
# 但是考虑到服务端许多API需要user_access_token,如文档:https://open.feishu.cn/document/ukTMukTMukTM/uUDN04SN0QjL1QDN/document-docx/docx-overview
# 建议的最佳实践为先获取user_access_token,再获得user_info
# user_access_token后续刷新可以参阅文档:https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/authen-v1/authen/refresh_access_token
def authorize_user_access_token(self, code):
    # 获取 user_access_token, 依托于飞书开放能力实现. 
    # 文档链接: https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/authen-v1/authen/access_token
    self.authorize_app_access_token()
    url = self._gen_url(USER_ACCESS_TOKEN_URI)
    # “app_access_token” 位于HTTP请求的请求头
    headers = {
        "Content-Type": "application/json",
        "Authorization": "Bearer " + self.app_access_token,
    }
    # 临时授权码 code 位于HTTP请求的请求体
    req_body = {"grant_type": "authorization_code", "code": code}
    response = requests.post(url=url, headers=headers, json=req_body)
    Auth._check_error_response(response)
    self._user_access_token = response.json().get("data").get("access_token")

def get_user_info(self):
    # 获取 user info, 依托于飞书开放能力实现.  
    # 文档链接: https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/authen-v1/authen/user_info
    url = self._gen_url(USER_INFO_URI)
    # “user_access_token” 位于HTTP请求的请求头
    headers = {
        "Authorization": "Bearer " + self.user_access_token,
        "Content-Type": "application/json",
    }
    response = requests.get(url=url, headers=headers)
    Auth._check_error_response(response)
    # 如需了解响应体字段说明与示例,请查询开放平台文档: 
    # https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/authen-v1/authen/access_token
    return response.json().get("data")

3.5 server.py ----- 服务端核心业务代码

1.关键函数callback():
2.通过传递的code先获取 user_access_token
3. 再获取 user info
4. 将 user info 存入 session
5. get_home()查询USER_INFO_KEY 是否在session,在则免登,否则就登陆程序

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
import os
import logging

from auth import Auth
from dotenv import load_dotenv, find_dotenv
from flask import Flask, render_template, session, jsonify, request

# 日志格式设置
LOG_FORMAT = "%(asctime)s - %(message)s"
DATE_FORMAT = "%m/%d/%Y %H:%M:%S"
logging.basicConfig(level=logging.DEBUG, format=LOG_FORMAT, datefmt=DATE_FORMAT)

# const
# 在session中存储用户信息 user info 所需要的对应 session key
USER_INFO_KEY = "UserInfo"
# secret_key 是使用 flask session 所必须有的
SECRET_KEY = "ThisIsSecretKey"

# 从 .env 文件加载环境变量参数
load_dotenv(find_dotenv())

# 初始化 flask 网页应用
app = Flask(__name__, static_url_path="/public", static_folder="./public")
app.secret_key = SECRET_KEY
app.debug = True

# 获取环境变量值
APP_ID = os.getenv("APP_ID")
APP_SECRET = os.getenv("APP_SECRET")
FEISHU_HOST = os.getenv("FEISHU_HOST")

# 用获取的环境变量初始化免登流程类Auth
auth = Auth(FEISHU_HOST, APP_ID, APP_SECRET)

# 业务逻辑类
class Biz(object):
    @staticmethod
    def home_handler():
        # 主页加载流程
        return Biz._show_user_info()

    @staticmethod
    def login_handler():
        # 需要走免登流程
        return render_template("index.html", user_info={"name": "unknown"}, login_info="needLogin")

    @staticmethod
    def login_failed_handler(err_info):
        # 出错后的页面加载流程
        return Biz._show_err_info(err_info)

    # Session in Flask has a concept very similar to that of a cookie, 
    # i.e. data containing identifier to recognize the computer on the network, 
    # except the fact that session data is stored in a server.
    @staticmethod
    def _show_user_info():
        # 直接展示session中存储的用户信息
        return render_template("index.html", user_info=session[USER_INFO_KEY], login_info="alreadyLogin")

    @staticmethod
    def _show_err_info(err_info):
        # 将错误信息展示在页面上
        return render_template("err_info.html", err_info=err_info)

# 出错时走错误页面加载流程Biz.login_failed_handler(err_info)
@app.errorhandler(Exception)
def auth_error_handler(ex):
    return Biz.login_failed_handler(ex)


# 默认的主页路径
@app.route("/", methods=["GET"])
def get_home():
    # 打开本网页应用会执行的第一个函数

    # 如果session当中没有存储user info,则走免登业务流程Biz.login_handler()
    if USER_INFO_KEY not in session:
        logging.info("need to get user information")
        return Biz.login_handler()
    else:
        # 如果session中已经有user info,则直接走主页加载流程Biz.home_handler()
        logging.info("already have user information")
        return Biz.home_handler()

@app.route("/callback", methods=["GET"])
def callback():
    # 获取 user info

    # 拿到前端传来的临时授权码 Code
    code = request.args.get("code")
    # 先获取 user_access_token
    auth.authorize_user_access_token(code)
    # 再获取 user info
    user_info = auth.get_user_info()
    # 将 user info 存入 session
    session[USER_INFO_KEY] = user_info
    return jsonify(user_info)

@app.route("/get_appid", methods=["GET"])
def get_appid():
    # 获取 appid
    # 为了安全,app_id不应对外泄露,尤其不应在前端明文书写,因此此处从服务端传递过去
    return jsonify(
        {
            "appid": APP_ID
        }
    )


if __name__ == "__main__":
    # 以debug模式运行本网页应用
    # debug模式能检测服务端模块的代码变化,如果有修改会自动重启服务
    app.run(host="0.0.0.0", port=3000, debug=True)

以上,免登流程完成。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/146637.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

leetcode二分查找算法题

目录 1.二分查找2.在排序数组中查找元素的第一个和最后一个位置3.x的平方根4.搜索插入位置5.山脉数组的峰顶索引6. 寻找峰值7.寻找旋转排序数组中的最小值8.8.0~n-1中缺失的数字 1.二分查找 二分查找 class Solution { public:int search(vector<int>& nums, int …

Ubuntu 17.10 “Artful Aardvark” 发布首个 Beta

Ubuntu 17.10 “Artful Aardvark” 首个 Beta 版已发布。 按照 Ubuntu 17.10 的发布日程 &#xff0c;Ubuntu 17.10 首个 beta 版按时发布了。不过参与本次测试版的没有 Ubuntu 官方风味版本&#xff08;要尝试的话可以考虑每日构建 ISO&#xff09;&#xff0c;包括了 Kubunt…

uniapp插件开发

安装android studio&#xff1a;安装目录下bin下的此文件&#xff0c;是用来修改分配给android studio的占用内存。 Android 11足够用。 创建新项目&#xff1a; 目录结构介绍&#xff1a; UI组件介绍&#xff1a;在设计程序界面时可以使用可视化拖拽的方式&#xff0c;没有必要…

RT-Thread STM32F407 五步完成OLED移植

这里使用RT-Thread Studio提供的IIC API驱动函数进行移植 第一步&#xff0c;进入RT-Thread Settings配置IIC驱动 第二步&#xff0c;进入board.h&#xff0c;定义IIC宏 第三步&#xff0c;进入STM32CubeMX&#xff0c;配置时钟及IIC 第四步&#xff0c;添加oled.c及oled…

Mozilla 面向基于 Debian 的 Linux 发行版

导读Mozilla 公司今天发布新闻稿&#xff0c;表示面向 Debian、Ubuntu 和 Linux Mint 等基于 Debian 的发行版&#xff0c;推出了.deb 格式的 Firefox Nightly 浏览器安装包&#xff0c;便于用户在上述发行版中更轻松地安装。 本次更新的亮点之一在于采用 APT 存储库&#xff0…

毫米波雷达模块的目标检测与跟踪

毫米波雷达技术在目标检测与跟踪方面具有独特的优势&#xff0c;其高精度、不受光照影响等特点使其在汽车、军事、工业等领域广泛应用。本文深入探讨毫米波雷达模块在目标检测与跟踪方面的研究现状、关键技术以及未来发展方向。 随着科技的不断进步&#xff0c;毫米波雷达技术在…

Zabbix 5.0部署(centos7+server+MySQL+Apache)

环境 系统IPZABBIX版本主机名centos7192.168.231.2195.0zabbix-server 安装zabbix 我选择版本是zabbix-5.0 zabbix的官网是Zabbix :: The Enterprise-Class Open Source Network Monitoring Solution 安装Zabbix软件源 rpm -Uvh https://repo.zabbix.com/zabbix/5.0/rhel/7/…

【开发工具】gitee还不用会?我直接拿捏 >_>

&#x1f308;键盘敲烂&#xff0c;年薪30万&#x1f308; 目录 git的一些前置操作 如何获取本地仓库 本地仓库的操作 远程仓库操作 合并两个仓库&#xff08;通用方法&#xff09; 从远程仓库拉取文件报错 fatal:refusing to merge unrelated histories 分支操作 注意&…

人工智能基础_机器学习033_多项式回归升维_多项式回归代码实现_非线性数据预测_升维后的数据对非线性数据预测---人工智能工作笔记0073

然后我们来实际的操作一下看看,多项式升维的作用,其实就是为了,来对,非线性的数据进行拟合. 我们直接看代码 import numpy as np import matplotlib.pyplot as plt from sklearn.linear_model import LinearRegression X=np.linspace(-1,11,num=100) 从-1到11中获取100个数…

MVC使用的设计模式

MVC使用的设计模式 一、背景 MVC模式是"Model-View-Controller"的缩写&#xff0c;中文翻译为"模式-视图-控制器"。MVC应用程序总是由这三个部分组成。Event(事件)导致Controller改变Model或View&#xff0c;或者同时改变两者。只要Controller改变了Model…

No210.精选前端面试题,享受每天的挑战和学习

🤍 前端开发工程师(主业)、技术博主(副业)、已过CET6 🍨 阿珊和她的猫_CSDN个人主页 🕠 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 🍚 蓝桥云课签约作者、已在蓝桥云课上架的前后端实战课程《Vue.js 和 Egg.js 开发企业级健康管理项目》、《带你从入…

JVM虚拟机:垃圾回收之三色标记

本文重点 在前面的课程中我们已经学习了垃圾回收器CMS和G1,其中CMS和G1中的mixedGC都存在四个过程,这四个过程中有一个过程叫做并发标记,也就是说程序一边运行,一边标记垃圾。这个过程最困难的是:如果在标记垃圾的时候,如果对象的引用关系发生了改变,此时应该如何处理?…

Spark通过三种方式创建DataFrame

通过toDF方法创建DataFrame 通过toDF的方法创建 集合rdd中元素类型是样例类的时候&#xff0c;转成DataFrame之后列名默认是属性名集合rdd中元素类型是元组的时候&#xff0c;转成DataFrame之后列名默认就是_N集合rdd中元素类型是元组/样例类的时候&#xff0c;转成DataFrame…

【Python】一文带你掌握数据容器之集合,字典

目录&#xff1a; 一、集合 思考&#xff1a;我们目前接触到了列表、元组、字符串三个数据容器了。基本满足大多数的使用场景为何又需要学习新的集合类型呢? 通过特性来分析: &#xff08;1&#xff09;列表可修改、支持重复元素且有序 &#xff08;2&#xff09;元组、字符…

数据结构第四课 -----线性表之栈

作者前言 &#x1f382; ✨✨✨✨✨✨&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f382; ​&#x1f382; 作者介绍&#xff1a; &#x1f382;&#x1f382; &#x1f382; &#x1f389;&#x1f389;&#x1f389…

常见排序算法实现

&#x1f495;"每一天都是值得被热爱的"&#x1f495; 作者&#xff1a;Mylvzi 文章主要内容&#xff1a;常见排序算法实现 1.排序的概念 所谓排序&#xff0c;就是按照特定顺序重新排列序列的操作 排序的稳定性&#xff1a; 当一个序列中存在相同的元素时 排序过…

1、NPC 三电平SVPWM simulink仿真

1、SVPWM时间计算函数&#xff0c;是从matlab的SVPWM3L_TimingCalculation.p文件中反汇编出来的函数&#xff1a; function [TgABC_On ,TgABC_Off ,Sn ]SVPWM3L_TimingCalculation_frompfile (Vref ,DeltaVdc ,Fsw ) %#codegen %coder .allowpcode (plain ); TgABC_On [0 ,0 ,…

Genio 700安卓核心板-MT8390安卓核心板规格参数

Genio 700(MT8390)安卓核心板是一款专门针对智能家居、互动零售、工业和商业应用的高性能边缘人工智能物联网平台。它集成了高度响应的边缘处理、先进的多媒体功能、各种传感器和连接选项&#xff0c;并支持多任务操作系统。 )安卓核心板采用高效的芯片内人工智能多处理器(APU)…

计算机毕业设计 基于SpringBoot的销售项目流程化管理系统的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

Python+Qt多点最短路径(最优路径)算法实现

程序示例精选 PythonQt多点最短路径(最优路径)算法实现 如需安装运行环境或远程调试&#xff0c;见文章底部个人QQ名片&#xff0c;由专业技术人员远程协助&#xff01; 前言 这篇博客针对《PythonQt多点最短路径(最优路径)算法实现》编写代码&#xff0c;代码整洁&#xff0…