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

news2024/11/25 1:36:50

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

一.上一例的问题修正

在上一例中,飞书登录查看网页的界面显示是有误的,看了代码,理论上登录成功之后,应该显示用户名等信息。
最后的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)

以上,免登流程完成。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

冲击900亿美元估值!邀约路演、秘密交表的Shein上市有望

双十一的狂欢刚刚结束&#xff0c;Shein即将赴美上市的消息又在电商圈里投下一枚重磅炸弹。 继被媒体曝光其寻求900亿美金估值后&#xff0c;最新的消息称其已邀请投资人参与路演&#xff0c;且已秘密完成交表。这个神秘的中国独角兽&#xff0c;离敲钟登陆美股的日子越来越近…

爬虫----robots.txt 协议简介

文章目录 robots.txt 是一个用于指示网络爬虫(web spider或web robot)如何与网站上的内容进行交互的协议。这个文件被网站管理员放置在网站的根目录下,用于告知爬虫哪些部分的网站是可以被抓取的,哪些是不被允许的。以下是 robots.txt 协议的一些关键要点: 控制爬虫访问:…

“技能兴鲁”职业技能大赛-网络安全赛项-学生组初赛 WP

Crypto BabyRSA 共模攻击 题目附件&#xff1a; from gmpy2 import * from Crypto.Util.number import *flag flag{I\m not gonna tell you the FLAG} # 这个肯定不是FLAG了&#xff0c;不要交这个咯p getPrime(2048) q getPrime(2048) m1 bytes_to_long(bytes(flag.e…

MHA的那些事儿

什么是MHA&#xff1f; masterhight availability&#xff1a;基于主库的高可用环境下&#xff0c;主从复制和故障切换 主从的架构 MHA至少要一主两从 出现的目的&#xff1a;解决MySQL的单点故障问题。一旦主库崩溃&#xff0c;MHA可以在0-30s内自动完成故障切换 MHA使用的…

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

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

深入解析 Azure 机器学习平台:架构与组成部分

Azure机器学习平台是Microsoft Azure提供的一种云上机器学习服务&#xff0c;为开发者和数据科学家提供了一个全面且易于使用的环境来创建、训练、部署和管理机器学习模型。本文将对Azure机器学习平台的基本架构和组成部分进行深入解析&#xff0c;帮助读者全面了解该平台的工作…

10-18 请求与相应1

前后台联调 前台通过一个表单, action写的servlet绑定的url,提交表单,请求我们servlet的doGet()/ doPost()方法 问题: 1.后台怎么获取前端的提交,请求的数据?底层:TCP通信,socket的得到输入流,读取数据 2.后台处理请求之后,怎么把结果给到前端?底层:TCP通信,socket的得到输入…

VR全景:打造虚拟政务服务,打通服务群众“最后一公里”

大家对政务大厅的工作效率可能已经司空见惯&#xff0c;办事窗口少&#xff0c;而需要办理的群众和业务却很多&#xff0c;很多去政务大厅办理业务的&#xff0c;排队几个小时也是常有的。并且在传统政务服务中&#xff0c;办事流程一般都较为复杂、耗时长&#xff0c;往往需要…

TSINGSEE视频汇聚管理与AI算法视频质量检测方案

一、建设背景 随着互联网视频技术的发展&#xff0c;视频监管在辅助安全生产、管理等方面发挥了不可替代的作用。但是&#xff0c;在监管场景中&#xff0c;仍然存在视频掉线、视频人为遮挡、视频录像存储时长不足等问题&#xff0c;对企业的日常管理和运转存在较大的安全隐患…

uniapp插件开发

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

DGL如何表征一张图

有关于DGL中图的构建 DGL 将有向图表示为一个 DGL 图对象。图中的节点编号连续&#xff0c;从0开始。我们一般通过指定图中的节点数&#xff0c;以及源节点和目标节点的列表&#xff0c;来构建这么一个图。 下面的代码构造了一个图&#xff0c;这个图有五个叶子节点。中心节点…

03 # 类型基础:动态类型与静态类型

通俗定义 静态类型语言&#xff1a;在编译阶段确定所有变量的类型 编译阶段确定属性偏移量用偏移量访问代替属性名访问偏移量信息共享 动态类型语言&#xff1a;在执行阶段确定所有变量的类型 在程序运行时&#xff0c;动态计算属性偏移量需要额外的空间存储属性名所有对象的…

246:vue+openlayers 绘制多边形,drawend获取最大幅宽

第246个 点击查看专栏目录 本示例是演示如何在vue+openlayers项目中绘制多边形,drawend获取最大幅宽。这里利用turf的turf.distance和openlayers的getExtent获取坐标值。 距离赤道越近,幅宽会越大一些,这里面利用了Math.abs来做绝对值的判断处理。 直接复制下面的 vue+open…

HBase学习笔记(3)—— HBase整合Phoenix

目录 Phoenix Shell 操作 Phoenix JDBC 操作 Phoenix 二级索引 HBase整合Phoenix Phoenix 简介 Phoenix 是 HBase 的开源 SQL 皮肤。可以使用标准 JDBC API 代替 HBase 客户端 API来创建表&#xff0c;插入数据和查询 HBase 数据 使用Phoenix的优点 在 Client 和 HBase …

docker-compose 部署 MySQL 8

前言 Windows 系统通过 docker-compose 部署 MySQL8.0。 MySQL 配置文件(my.cnf) # 服务端参数配置 [mysqld] usermysql # MySQL启动用户 default-storage-engineINNODB # 创建新表时将使用的默认存储引擎 character-set-serverutf8mb4 # 设置mysql服…

科研学习|科研软件——有序多分类Logistic回归的SPSS教程!

一、问题与数据 研究者想调查人们对“本国税收过高”的赞同程度&#xff1a;Strongly Disagree——非常不同意&#xff0c;用“0”表示&#xff1b;Disagree——不同意&#xff0c;用“1”表示&#xff1b;Agree--同意&#xff0c;用“2”表示&#xff1b;Strongly Agree--非常…

从0到0.01入门 Webpack| 002.精选 Webpack面试题

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

基础课1——智能客服的定义

1.介绍 智能客服是一种采用人工智能技术的客户服务方式&#xff0c;它通过语音识别、自然语言处理、语义理解等技术&#xff0c;实现了与客户的自动交互。智能客服可以提供客户24小时不间断的服务&#xff0c;帮助客户快速解决问题&#xff0c;提高客户满意度。智能客服的应用…

oracle-buffer cache

段&#xff0c;区&#xff0c;块。 每当新建一个表&#xff0c;数据库会相应创建一个段。然后给这个段分配一个区。 一个区包含多个块。 区是oracle给段分配空间的最小单位。 块是oracle i\o的最小单位。 原则上&#xff0c;一个块包含多行数据。 dbf文件会被划分成一个一个…

学开发语言 求职互联网行业的未来发展

我喜欢回答各种各样的问题&#xff0c;自然也喜欢记录下自己的一些观点和看法。希望给朋友们多一点参考&#xff0c;也欢迎交流探讨。 提问&#xff1a; 自考本科&#xff0c;学的开发语言&#xff0c;问互联网行业求职和发展&#xff01; 作为一个资深码农&#xff0c;对这样…