赞
踩
首先进入飞书开放平台: 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>
示例代码结构
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等
├── requirements.txt ----- 环境配置文件
requirements.txt文件中定义了所需的环境依赖,在命令行中输入命令安装依赖:
pip install -r requirements.txt
requirements.txt的内容
Flask==2.0.2
python-dotenv
requests
pycryptodome
Werkzeug<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 -
发现网页运行显示500错误以后,将该目录在cmd命令行下执行,发现是requirements.txt安装不成功。
然后在cmd命令行下再执行一次
pip install -r requirements.txt
安装成功以后,再运行一次确认全部成功后,再运行server.py
(venv) D:\FeishuApp>python server.py
找到新的网页地址:http://192.168.31.244:3000/ 打开后先有提示:
显示网页打开成功
在开发者后台中,添加应用能力,增加网页应用
在网页应用配置中,桌面段主页和移动端主页添加上述调试主页地址
H5可信域名中添加上述调试主页地址
然后,打开并登录飞书,在工作台中可以看到增加的应用:
打开电脑端和手机端,都能看到这个页面:
说明网页应用示例开发成功了。
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__
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={}×tamp={}&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)
│ ├── 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>
│ ├── 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); }
│ ├── 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快速开发网页应用全部完成,不过这只是简单的示例代码,更复杂的案例有待练习。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。