Feishu development study notes (5) - Rapid development of web applications in Python

Feishu development study notes (5) - Rapid development of web applications in Python

1. Download sample code

First enter the Feishu open platform: https://open.feishu.cn/app
On the certificate and basic information page, go to the application certificate Get the App ID and App Secret values ​​from .

Tutorial and sample code location:https://open.feishu.cn/document/home/integrating-web-apps-in-5-minutes/create-app- and-configuration
is divided into four steps, of which the sample code is at this location: Sample code
After downloading, organize Folder directory:
Insert image description here
Enter the .env file and modify the App ID and App Secret to the values ​​obtained from the developer backend.
Insert image description here
Create a virtual environment in the command line and start the virtual environment:

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

Insert image description here

2. Download sample code

2.1 Sample code structure

Sample code structure
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 Download the required environment configuration

├── requirements.txt ----- Environment configuration file
The requirements.txt file defines the required environment dependencies. Enter the command on the command line to install the dependencies:

pip install -r requirements.txt

Contents of requirements.txt

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

2.3 Start the service after installing dependencies

start server.py

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

Allow the firewall to pass
Insert image description here
Insert image description here
Enter the server address http://192.168.3.22:3000/ into the browser. The actual result is as follows:
The content is:
{ “message”: “index.html” } did not achieve the expected results. Looking at the debugging results of the command line, the status code is 500, and an internal server error has occurred



Insert image description here

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 Installation of Python dependencies

After discovering that the webpage displayed a 500 error when running, I executed the directory under the cmd command line and found that the requirements.txt installation was unsuccessful.
Then execute it again under the cmd command line

pip install -r requirements.txt

After the installation is successful, run it again to confirm that everything is successful, and then run server.py

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

Insert image description here
Found a new web page address: http://192.168.31.244:3000/ After opening it, there was a prompt:
Insert image description here
Insert image description here
It showed that the web page was opened successfully

2.5 Web application addition

In the developer background, add application capabilities and add web applications
Insert image description here
In the web application configuration, add the above debugging homepage address to the desktop segment homepage and mobile homepage< a i=2> Add the above debugging homepage address to the H5 trusted domain name Then, open and log in to Feishu, and you can see the added application in the workbench: When you open both the computer and the mobile phone, you can see this page: This means that the web application example has been successfully developed.

Insert image description here

Insert image description here

Insert image description here

3. Sample code content

3.1 auth.py server obtains jsapi_ticket, etc.

auth.py is imported in server.py, mainly authenticates through API, and obtains jsapi_ticket
authorize_tenant_access_token() uses the TENANT_ACCESS_TOKEN_URI function to obtain tenant_access_token through app_id and app_id
get_ticket() uses the JSAPI_TICKET_URI function to obtain the ticket through feishu_host and tenant_access_token
Finally, this class returns jsapi_ticket

├── auth.py ----- The server obtains jsapi_ticket, etc.

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 server uses Flask to create web pages

server.py server uses Flask to create a web page
1. Initialize Flask, the example is app
2. First instantiate the auth class and obtain feishu_host , app_id, app_secret, tenant_access_token and ticket
3. Define the route get_home(), render "index.html", and execute index.js after rendering to obtain the front-end config parameters
4. In the config parameter, ticket, random string, web page address url, and current timestamp are combined into a string
5. Use sha1 encryption to get the signature signature 7. Render index.html
6.index.js returns the parameters required for authentication to the front end

├── server.py ----- Server core business code

#!/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 front-end user information display page

│ ├── index.html ----- Front-end user information display page

<!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 front-end display style

│ ├── index.css ----- Front-end display style

* {
    
    
  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 front-end interaction code

│ ├── index.js ----- Front-end interaction code
1. Get the current address url
2. Call the page/get_config_parameters Initiate a request and provide url as parameter
3. Use window.h5sdk.config to obtain authentication parameters (appId, timestamp, nonceStr, signature)
4. Complete authentication After obtaining the rights, you can call JSAPI in window.h5sdk.ready
5. Call the getUserInfo API to obtain the basic information of the logged in user
6. Separately defined functions showUser, used to display user information on the front-end page showUser(res.userInfo);
7. Define the display code of showUser
8. Successfully display authentication User page after success

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

At this point, the rapid development of web applications in Python has been completed, but this is just a simple sample code, and more complex cases need to be practiced.

Guess you like

Origin blog.csdn.net/qq_43662503/article/details/134346096