飞书开发学习笔记(五)-Python快速开发网页应用

news2024/10/7 8:25:40

飞书开发学习笔记(五)-Python快速开发网页应用

一.下载示例代码

首先进入飞书开放平台: https://open.feishu.cn/app
凭证与基础信息 页面,在 应用凭证 中获取 App ID 和 App Secret 值。

教程和示例代码位置:https://open.feishu.cn/document/home/integrating-web-apps-in-5-minutes/create-app-and-configuration
共分为四步,其中示例代码在这个位置: 示例代码
下载完毕后,整理文件夹目录:
在这里插入图片描述
进入.env文件,修改 App ID 和 App Secret为开发者后台获取的值。
在这里插入图片描述
在命令行中创建虚拟环境,并启动虚拟环境:

python -m venv venv
venv\Scripts\activate
(venv) D:\FeishuApp>

在这里插入图片描述

二.下载示例代码

2.1 示例代码结构

示例代码结构
https://open.feishu.cn/document/home/integrating-web-apps-in-5-minutes/debug-and-release

.
├── README.zh.md     ----- 说明文档
├── public
│   ├── svg     ----- 前端图形文件
│   ├── index.css     ----- 前端展示样式
│   ├── index.js     ----- 前端交互代码
├── templates
│   ├── index.html     ----- 前端用户信息展示页面
├── auth.py     ----- 服务端获取jsapi_ticket等
├── server.py     ----- 服务端核心业务代码
├── requirements.txt     ----- 环境配置文件
└── .env     ----- 全局默认配置文件,主要存储App ID和App Secret等

2.2 下载所需的环境配置

├── requirements.txt ----- 环境配置文件
requirements.txt文件中定义了所需的环境依赖,在命令行中输入命令安装依赖:

pip install -r requirements.txt

requirements.txt的内容

Flask==2.0.2
python-dotenv
requests
pycryptodome
Werkzeug<3

2.3 安装依赖后启动服务

启动server.py

(venv) D:\FeishuApp>python server.py

允许防火墙通过
在这里插入图片描述
在这里插入图片描述
将服务器地址 http://192.168.3.22:3000/填入到浏览器中,现实结果如下:
内容为:
{
“message”: “index.html”
}
没有达到预期的效果。
在这里插入图片描述
看命令行的调试结果,显示状态码为500,出现了内部服务器错误

192.168.3.22 - - [12/Nov/2023 12:47:42] "GET / HTTP/1.1" 500 -
192.168.3.22 - - [12/Nov/2023 12:47:42] "GET /favicon.ico HTTP/1.1" 500 -

2.4 Python依赖的安装

发现网页运行显示500错误以后,将该目录在cmd命令行下执行,发现是requirements.txt安装不成功。
然后在cmd命令行下再执行一次

pip install -r requirements.txt

安装成功以后,再运行一次确认全部成功后,再运行server.py

(venv) D:\FeishuApp>python server.py

在这里插入图片描述
找到新的网页地址:http://192.168.31.244:3000/ 打开后先有提示:
在这里插入图片描述
在这里插入图片描述
显示网页打开成功

2.5 网页应用添加

在开发者后台中,添加应用能力,增加网页应用
在这里插入图片描述
在网页应用配置中,桌面段主页和移动端主页添加上述调试主页地址
H5可信域名中添加上述调试主页地址
在这里插入图片描述
然后,打开并登录飞书,在工作台中可以看到增加的应用:
在这里插入图片描述
打开电脑端和手机端,都能看到这个页面:
在这里插入图片描述
说明网页应用示例开发成功了。

三.示例代码内容

3.1 auth.py服务端获取jsapi_ticket等

auth.py在server.py中导入,主要通过API进行鉴权,获取jsapi_ticket
authorize_tenant_access_token()利用TENANT_ACCESS_TOKEN_URI 函数,通过app_id和app_id获取tenant_access_token
get_ticket()利用JSAPI_TICKET_URI 函数,通过feishu_host和tenant_access_token获取ticket
最终,该类返回jsapi_ticket

├── auth.py ----- 服务端获取jsapi_ticket等

import requests
import logging

# const
# 开放接口 URI
TENANT_ACCESS_TOKEN_URI = "/open-apis/auth/v3/tenant_access_token/internal"
JSAPI_TICKET_URI = "/open-apis/jssdk/ticket/get"

class Auth(object):
    def __init__(self, feishu_host, app_id, app_secret):
        self.feishu_host = feishu_host
        self.app_id = app_id
        self.app_secret = app_secret
        self.tenant_access_token = ""

    def get_ticket(self):
        # 获取jsapi_ticket,具体参考文档:https://open.feishu.cn/document/ukTMukTMukTM/uYTM5UjL2ETO14iNxkTN/h5_js_sdk/authorization
        self.authorize_tenant_access_token()
        url = "{}{}".format(self.feishu_host, JSAPI_TICKET_URI)
        headers = {
            "Authorization": "Bearer " + self.tenant_access_token,
            "Content-Type": "application/json",
        }
        resp = requests.post(url=url, headers=headers)
        Auth._check_error_response(resp)
        return resp.json().get("data").get("ticket", "")

    def authorize_tenant_access_token(self):
        # 获取tenant_access_token,基于开放平台能力实现,具体参考文档:https://open.feishu.cn/document/ukTMukTMukTM/ukDNz4SO0MjL5QzM/auth-v3/auth/tenant_access_token_internal
        url = "{}{}".format(self.feishu_host, TENANT_ACCESS_TOKEN_URI)
        req_body = {"app_id": self.app_id, "app_secret": self.app_secret}
        response = requests.post(url, req_body)
        Auth._check_error_response(response)
        self.tenant_access_token = response.json().get("tenant_access_token")

    @staticmethod
    def _check_error_response(resp):
        # 检查响应体是否包含错误信息
        if resp.status_code != 200:
            raise resp.raise_for_status()
        response_dict = resp.json()
        code = response_dict.get("code", -1)
        if code != 0:
            logging.error(response_dict)
            raise FeishuException(code=code, msg=response_dict.get("msg"))


class FeishuException(Exception):
    # 处理并展示飞书侧返回的错误码和错误信息
    def __init__(self, code=0, msg=None):
        self.code = code
        self.msg = msg

    def __str__(self) -> str:
        return "{}:{}".format(self.code, self.msg)

    __repr__ = __str__

3.2 server.py服务端利用Flask建立网页

server.py服务端利用Flask建立网页
1.初始化Flask,示例为app
2.先实例化auth类,获取feishu_host, app_id, app_secret,tenant_access_token和ticket
3.定义路由get_home(),渲染"index.html",渲染后执行index.js获取前端的config参数
4.config参数中,通过ticket,随机字符串,网页地址url,以及当前时间戳组合成一个字符串
5.使用sha1加密,得到签名signature
6.index.js将鉴权所需参数返回前端
7.渲染index.html

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

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
import os
import time
import hashlib
import requests
from auth import Auth
from dotenv import load_dotenv, find_dotenv
from flask import Flask, request, jsonify, render_template

# const
# 随机字符串,用于签名生成加密使用
NONCE_STR = "13oEviLbrTo458A3NjrOwS70oTOXVOAm"

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

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

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

# 应用出现错误时,实用flask的errorhandler装饰器实现应用错误处理
@app.errorhandler(Exception)
def auth_error_handler(ex):
    response = jsonify(message=str(ex))
    response.status_code = (
        ex.response.status_code if isinstance(ex, requests.HTTPError) else 500
    )
    return response


# 用获取的环境变量初始化Auth类,由APP ID和APP SECRET获取access token,进而获取jsapi_ticket
auth = Auth(FEISHU_HOST, APP_ID, APP_SECRET)

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

# 获取并返回接入方前端将要调用的config接口所需的参数
@app.route("/get_config_parameters", methods=["GET"])
def get_config_parameters():    
    # 接入方前端传来的需要鉴权的网页url
    url = request.args.get("url")
    # 初始化Auth类时获取的jsapi_ticket
    ticket = auth.get_ticket()
    # 当前时间戳,毫秒级
    timestamp = int(time.time()) * 1000
    # 拼接成字符串 
    verify_str = "jsapi_ticket={}&noncestr={}&timestamp={}&url={}".format(
        ticket, NONCE_STR, timestamp, url
    )
    # 对字符串做sha1加密,得到签名signature
    signature = hashlib.sha1(verify_str.encode("utf-8")).hexdigest()
    # 将鉴权所需参数返回给前端
    return jsonify(
        {
            "appid": APP_ID,
            "signature": signature,
            "noncestr": NONCE_STR,
            "timestamp": timestamp,
        }
    )
    
if __name__ == "__main__":
    # 以debug模式运行本网页应用
    # debug模式能检测服务端模块的代码变化,如果有修改会自动重启服务
    app.run(host="0.0.0.0", port=3000, debug=True)

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

│ ├── index.html ----- 前端用户信息展示页面

<!DOCTYPE html>

<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>网页应用鉴权</title>
    <link rel="stylesheet" href="/public/index.css" />
    <script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script>
    <!-- 引入 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>
      var vConsole = new window.VConsole();
    </script>
  </head>

  <body>
    <div>
      <div class="img">
        <!-- 头像 -->
        <div id="img_div" class="img_div"></div>
        <span class="text_hello">Hello</span>
        <!-- 名称 -->
        <div id="hello_text_name" class="text_hello_name"></div>
        <!-- 欢迎语 -->
        <div id="hello_text_welcome" class="text_hello_welcome"></div>
      </div>
      <!-- 飞书icon -->
      <div class="icon"><img src="../public/svg/icon.svg" /></div>
    </div>
    <script src="/public/index.js"></script>
  </body>
</html>

3.4 index.css 前端展示样式

│ ├── index.css ----- 前端展示样式

* {
  margin: 0;
  padding: 0;
}

body {
  background-color: #ebf1fd;
}

.header {
  display: flex;
  flex-direction: column;
  background-color: white;
}

.header .time-message {
  display: flex;
  height: 44px;
  align-items: center;
  padding: 0 33.5px;
  justify-content: space-between;
}

.header .title {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 44px;
}

.header .title span {
  font-weight: 500;
  font-size: 17px;
}

.img {
  width: 120px;
  height: 239px;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  display: flex;
  align-items: center;
  flex-direction: column;
}

.img_div {
  border-radius: 50%;
  overflow: hidden;
  width: 88px;
  height: 88px;
  border: 3px white solid;
  display: flex;
  justify-content: center;
  align-items: center;
}

.text_hello {
  font-size: 26px;
  font-weight: 600;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

.text_hello_name {
  font-size: 20px;
  color: #3370ff;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, 50%);
  text-align: center;
}

.text_hello_welcome {
  position: absolute;
  bottom: 0;
  size: 20px;
  font-weight: 500;
  text-align: center;
  white-space: nowrap;
}

.icon {
  position: absolute;
  bottom: 44px;
  left: 50%;
  transform: translate(-50%, 0);
}

3.5 index.js 前端交互代码

│ ├── index.js ----- 前端交互代码
1.获得当前地址url
2.调用页面/get_config_parameters发起请求,提供url作为参数
3.利用window.h5sdk.config获取鉴权参数(appId、timestamp、nonceStr、signature)
4.完成鉴权后,便可在 window.h5sdk.ready 里调用 JSAPI
5.调用 getUserInfo API 获取已登录用户的基本信息
6.单独定义的函数showUser,用于将用户信息展示在前端页面上showUser(res.userInfo);
7.定义showUser的显示代码
8.成功显示鉴权成功后用户页面

let lang = window.navigator.language;

$("document").ready(apiAuth());

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

  // 调用config接口的当前网页url
  const url = encodeURIComponent(location.href.split("#")[0]);
  console.log("接入方前端将需要鉴权的url发给接入方服务端,url为:", url);
  // 向接入方服务端发起请求,获取鉴权参数(appId、timestamp、nonceStr、signature)
  fetch(`/get_config_parameters?url=${url}`)
    .then((response) =>
      response.json().then((res) => {
        console.log(
          "接入方服务端返回给接入方前端的结果(前端调用config接口的所需参数):", res
        );
        // 通过error接口处理API验证失败后的回调
        window.h5sdk.error((err) => {
          throw ("h5sdk error:", JSON.stringify(err));
        });
        // 调用config接口进行鉴权
        window.h5sdk.config({
          appId: res.appid,
          timestamp: res.timestamp,
          nonceStr: res.noncestr,
          signature: res.signature,
          jsApiList: [],
          //鉴权成功回调
          onSuccess: (res) => {
            console.log(`config success: ${JSON.stringify(res)}`);
          },
          //鉴权失败回调
          onFail: (err) => {
            throw `config failed: ${JSON.stringify(err)}`;
          },
        });
        // 完成鉴权后,便可在 window.h5sdk.ready 里调用 JSAPI
        window.h5sdk.ready(() => {
          // window.h5sdk.ready回调函数在环境准备就绪时触发
          // 调用 getUserInfo API 获取已登录用户的基本信息,详细文档参见https://open.feishu.cn/document/uYjL24iN/ucjMx4yNyEjL3ITM
          tt.getUserInfo({
            // getUserInfo API 调用成功回调
            success(res) {
              console.log(`getUserInfo success: ${JSON.stringify(res)}`);
              // 单独定义的函数showUser,用于将用户信息展示在前端页面上
              showUser(res.userInfo);
            },
            // getUserInfo API 调用失败回调
            fail(err) {
              console.log(`getUserInfo failed:`, JSON.stringify(err));
            },
          });
          // 调用 showToast API 弹出全局提示框,详细文档参见https://open.feishu.cn/document/uAjLw4CM/uYjL24iN/block/api/showtoast
          tt.showToast({
            title: "鉴权成功",
            icon: "success",
            duration: 3000,
            success(res) {
              console.log("showToast 调用成功", res.errMsg);
            },
            fail(res) {
              console.log("showToast 调用失败", res.errMsg);
            },
            complete(res) {
              console.log("showToast 调用结束", res.errMsg);
            },
          });
        });
      })
    )
    .catch(function (e) {
      console.error(e);
    });
}

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"
  );
}

至此,python快速开发网页应用全部完成,不过这只是简单的示例代码,更复杂的案例有待练习。

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

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

相关文章

YOLOv5算法进阶改进(2)— 引入可变形卷积模块 | 涨点杀器

前言:Hello大家好,我是小哥谈。可变形卷积模块是一种改进的卷积操作,它可以更好地适应物体的形状和尺寸,提高模型的鲁棒性。可变形卷积模块的实现方式是在标准卷积操作中增加一个偏移量offset,使卷积核能够在训练过程中扩展到更大的范围,从而实现对尺度、长宽比和旋转等各…

Linux_磁盘管理_df命令

1、df命令是用来干什么的 df的全称是disk free&#xff0c;意为“磁盘空间”。 使用df命令可以查看系统中磁盘的占用情况&#xff0c;有哪些文件系统&#xff0c;在什么位置&#xff08;挂载点&#xff09;&#xff0c;总空间&#xff0c;已使用空间&#xff0c;剩余空间等。…

2023.11.13【读书笔记】丨生物信息学与功能基因组学(第六章 多重序列比对 下)

目录 6.4 多重序列比对数据库6.5 基因组区域的多重序列比对6.6 展望6.7 常见问题总结 6.4 多重序列比对数据库 Pfam&#xff1a;基于谱隐马尔可夫模型构建的蛋白质家族数据库 SMART&#xff1a;简易分子构型研究工具&#xff0c;与细胞信号传导、细胞外结构域以及染色质功能…

Jmeter+ant+Jenkins持续集成

&#x1f4e2;专注于分享软件测试干货内容&#xff0c;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01;&#x1f4e2;交流讨论&#xff1a;欢迎加入我们一起学习&#xff01;&#x1f4e2;资源分享&#xff1a;耗时200小时精选的「软件测试」资…

【C++入门篇】保姆级教程篇【下】

目录 一、运算符重载 1&#xff09;比较、赋值运算符重载 2&#xff09; 流插入留提取运算符重载 二、剩下的默认成员函数 1&#xff09;赋值运算符重载 2&#xff09;const成员函数 3&#xff09;取地址及const取地址操作符重载 三、再谈构造函数 1&#xff09;初始化列表 …

2023年数维杯国际大学生数学建模挑战赛A题

当大家面临着复杂的数学建模问题时&#xff0c;你是否曾经感到茫然无措&#xff1f;作为2022年美国大学生数学建模比赛的O奖得主&#xff0c;我为大家提供了一套优秀的解题思路&#xff0c;让你轻松应对各种难题。 cs数模团队在数维杯前为大家提供了许多资料的内容呀&#xff0…

java项目之公廉租房维保系统(ssm框架)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于ssm的公廉租房维保系统。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 项目简介&#xff1a; 一、业主管理功能 该部分内容提…

单电源供电的运放如何增加直流偏置

在一些单电源供电的运放电路中&#xff0c;输入信号可能是交流信号&#xff0c;有正也有负&#xff0c;如果输入信号直接接到运算放大电路&#xff0c;则运放不会输出负电压&#xff0c;只有正电压&#xff0c;从而不能实现信号的调理&#xff1b; 这时我们就需要给运放添加直流…

五分钟利用Vite创建Vue项目

1.准备工具 Vite是尤雨溪团队开发的&#xff0c;官方称是下一代新型前端构建工具&#xff0c;能够显著提升前端开发体验。 上面称是下一代&#xff0c;当前一代当然是我们熟悉的webpack Vite 优势 开发环境中&#xff0c;无需打包操作&#xff0c;可快速的冷启动。轻量快速…

Python 如何实现 Command(命令)模式?什么是 Command(命令)设计模式?

什么是命令设计模式&#xff1f; 命令模式&#xff08;Command Design Pattern&#xff09;是一种行为设计模式&#xff0c;它将请求封装成一个对象&#xff0c;从而允许参数化客户端对象&#xff0c;排队请求&#xff0c;或者对请求进行操作。命令模式支持撤销操作&#xff0…

ros1 基础学习07 - 模拟客户端生成小乌龟服务请求生成小乌龟

模拟客户端生成小乌龟服务请求生成小乌龟 一、话题模型二、创建功能包三 创建客户端Client代码四 配置CMakeLists.txt编译规则&#xff1a;五 测试启动ros 主服务启动小乌龟的服务启动模型客户端服务 一、话题模型 Sever端是海龟仿真器/turtlesim&#xff0c;Client端是待实现…

基于鸡群算法优化概率神经网络PNN的分类预测 - 附代码

基于鸡群算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于鸡群算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于鸡群优化的PNN网络5.测试结果6.参考文献7.Matlab代码 摘要&#xff1a;针对PNN神经网络的光滑…

SPSS时间序列分析:序列图

前言&#xff1a; 本专栏参考教材为《SPSS22.0从入门到精通》&#xff0c;由于软件版本原因&#xff0c;部分内容有所改变&#xff0c;为适应软件版本的变化&#xff0c;特此创作此专栏便于大家学习。本专栏使用软件为&#xff1a;SPSS25.0 本专栏所有的数据文件请点击此链接下…

【Java 进阶篇】JQuery DOM操作:通用属性操作的绝妙魔法

在前端的舞台上&#xff0c;JQuery犹如一位魔法师&#xff0c;为我们展现了操纵HTML元素的奇妙技巧。而在这个技巧的精妙组成中&#xff0c;通用属性操作是一门绝妙的魔法。在本篇博客中&#xff0c;我们将深入研究JQuery DOM操作中的通用属性操作&#xff0c;揭示这段魔法的神…

基于springboot+vue的学生毕业离校信息网站

项目介绍 该学生毕业离校系统包括管理员、学生和教师。其主要功能包括管理员&#xff1a;首页、个人中心、学生管理、教师管理、离校信息管理、费用结算管理、论文审核管理、管理员管理、留言板管理、系统管理等&#xff0c;前台首页&#xff1b;首页、离校信息、网站公告、留…

海上船舶交通事故VR模拟体验低成本高效率-深圳华锐视点

在海上运输行业&#xff0c;安全事故的防范和应对能力是企业安全教育的重中之重。针对这一问题&#xff0c;海上运输事故VR模拟逃生演练成为了一种创新且高效的教育手段。通过这种演练&#xff0c;企业能够在提升员工安全意识和技能方面获得多方面的帮助。 在VR船舶搜救演练中&…

python 根据经纬度绘制点图 极投影

参考了python cartopy手动导入地图数据绘制底图/python地图上绘制散点图&#xff1a;Downloading:warnings/散点图添加图里标签_python add_feature-CSDN博客 点的颜色按照时间显示 # -*- coding: utf-8 -*- """ Created on Mon Nov 13 11:32:48 2023"&quo…

Python数据容器(序列操作)

序列 1.什么是序列 序列是指&#xff1a;内容连续、有序。可以使用下标索引的一类数据容器 列表、元组、字符串。均可以视为序列 2.序列的常用操作 - 切片 语法&#xff1a;序列[起始下标:结束下标:步长]起始下标表示从何处开始&#xff0c;可以留空&#xff0c;留空视作从…

11.13 牛客刷题8/10

11.13 信号完整性 指针地址 的加减&#xff0c;注意 最后转为16进制

毕业设计项目:基于java+springboot的共享单车信息网站

运行环境 开发语言&#xff1a;Java 框架&#xff1a;springboot JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09; 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea Ma…