DIFY获取飞书表格获取数据生成附件并上传到jira

部署运行你感兴趣的模型镜像

最近公司要读取飞书表格信息 并上传到jira自动生成任务 所以有了这个

一 创建dify

在这里插入图片描述

二 调用飞书(有三个变量需要自己定义 )

app_id,app_secret,url

import os
import json
import requests
import sys
import urllib.parse
from typing import Dict, Any, Tuple, List, Optional
import time

def get_tenant_access_token(app_id: str, app_secret: str) -> Tuple[str, Exception]:
    """获取 tenant_access_token

    Args:
        app_id: 应用ID
        app_secret: 应用密钥

    Returns:
        Tuple[str, Exception]: (access_token, error)
    """
    url = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal"
    payload = {
        "app_id": app_id,
        "app_secret": app_secret
    }
    headers = {
        "Content-Type": "application/json; charset=utf-8"
    }
    try:
        print(f"POST: {url}")
        print(f"Request body: {json.dumps(payload)}")
        response = requests.post(url, json=payload, headers=headers)
        response.raise_for_status()

        result = response.json()
        print(f"Response: {json.dumps(result)}")

        if result.get("code", 0) != 0:
            print(f"ERROR: failed to get tenant_access_token: {result.get('msg', 'unknown error')}", file=sys.stderr)
            return "", Exception(f"failed to get tenant_access_token: {response.text}")

        return result["tenant_access_token"], None

    except Exception as e:
        print(f"ERROR: getting tenant_access_token: {e}", file=sys.stderr)
        if hasattr(e, 'response') and e.response is not None:
            print(f"ERROR: Response: {e.response.text}", file=sys.stderr)
        return "", e

def get_wiki_node_info(tenant_access_token: str, node_token: str) -> Dict[str, Any]:
    """获取知识空间节点信息

    Args:
        tenant_access_token: 租户访问令牌
        node_token: 节点令牌

    Returns:
        Dict[str, Any]: 节点信息对象
    """
    url = f"https://open.feishu.cn/open-apis/wiki/v2/spaces/get_node?token={urllib.parse.quote(node_token)}"
    headers = {
        "Authorization": f"Bearer {tenant_access_token}",
        "Content-Type": "application/json; charset=utf-8"
    }

    try:
        print(f"GET: {url}")
        response = requests.get(url, headers=headers)
        response.raise_for_status()

        result = response.json()
        print(f"Response: {json.dumps(result)}")
        
        if result.get("code", 0) != 0:
            print(f"ERROR: 获取知识空间节点信息失败 {result}", file=sys.stderr)
            raise Exception(f"failed to get wiki node info: {result.get('msg', 'unknown error')}")

        if not result.get("data") or not result["data"].get("node"):
            raise Exception("未获取到节点信息")

        node_info = result["data"]["node"]
        print("节点信息获取成功:", {
            "node_token": node_info.get("node_token"),
            "obj_type": node_info.get("obj_type"),
            "obj_token": node_info.get("obj_token"),
            "title": node_info.get("title")
        })
        return node_info

    except Exception as e:
        print(f"ERROR: getting wiki node info: {e}", file=sys.stderr)
        raise

def get_spreadsheet_info(tenant_access_token: str, spreadsheet_token: str) -> Dict[str, Any]:
    """获取电子表格信息

    Args:
        tenant_access_token: 租户访问令牌
        spreadsheet_token: 电子表格token

    Returns:
        Dict[str, Any]: 电子表格信息
    """
    url = f"https://open.feishu.cn/open-apis/sheets/v3/spreadsheets/{spreadsheet_token}"
    headers = {
        "Authorization": f"Bearer {tenant_access_token}",
        "Content-Type": "application/json; charset=utf-8"
    }

    try:
        print(f"GET: {url}")
        response = requests.get(url, headers=headers)
        response.raise_for_status()

        result = response.json()
        print(f"Response: {json.dumps(result)}")
        
        if result.get("code", 0) != 0:
            print(f"ERROR: 获取电子表格信息失败 {result}", file=sys.stderr)
            raise Exception(f"failed to get spreadsheet info: {result.get('msg', 'unknown error')}")

        if not result.get("data") or not result["data"].get("spreadsheet"):
            raise Exception("未获取到电子表格信息")

        spreadsheet_info = result["data"]["spreadsheet"]
        print("电子表格信息获取成功:", {
            "title": spreadsheet_info.get("title"),
            "owner_id": spreadsheet_info.get("owner_id"),
            "token": spreadsheet_info.get("token"),
            "url": spreadsheet_info.get("url")
        })
        return spreadsheet_info

    except Exception as e:
        print(f"ERROR: getting spreadsheet info: {e}", file=sys.stderr)
        raise

def get_spreadsheet_sheets(tenant_access_token: str, spreadsheet_token: str) -> List[Dict[str, Any]]:
    """获取电子表格中的所有工作表

    Args:
        tenant_access_token: 租户访问令牌
        spreadsheet_token: 电子表格token

    Returns:
        List[Dict[str, Any]]: 工作表列表
    """
    url = f"https://open.feishu.cn/open-apis/sheets/v3/spreadsheets/{spreadsheet_token}/sheets/query"
    headers = {
        "Authorization": f"Bearer {tenant_access_token}",
        "Content-Type": "application/json; charset=utf-8"
    }

    try:
        print(f"GET: {url}")
        response = requests.get(url, headers=headers)
        response.raise_for_status()

        result = response.json()
        print(f"Response: {json.dumps(result)}")
        
        if result.get("code", 0) != 0:
            print(f"ERROR: 获取工作表列表失败 {result}", file=sys.stderr)
            raise Exception(f"failed to get sheets: {result.get('msg', 'unknown error')}")

        if not result.get("data") or not result["data"].get("sheets"):
            raise Exception("未获取到工作表列表")

        sheets = result["data"]["sheets"]
        print(f"获取到 {len(sheets)} 个工作表:")
        for i, sheet in enumerate(sheets):
            print(f"  {i+1}. 标题: {sheet.get('title')}, ID: {sheet.get('sheet_id')}, 索引: {sheet.get('index')}, 隐藏: {sheet.get('hidden')}")

        return sheets

    except Exception as e:
        print(f"ERROR: getting sheets: {e}", file=sys.stderr)
        raise

def read_sheet_range(tenant_access_token: str, spreadsheet_token: str, sheet_id: str, range_str: str) -> Dict[str, Any]:
    """读取电子表格中单个范围的数据

    Args:
        tenant_access_token: 租户访问令牌
        spreadsheet_token: 电子表格token
        sheet_id: 工作表ID
        range_str: 查询范围

    Returns:
        Dict[str, Any]: 读取的数据
    """
    full_range = f"{sheet_id}!{range_str}"
    url = f"https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/{spreadsheet_token}/values/{urllib.parse.quote(full_range)}"
    url += "?valueRenderOption=ToString&dateTimeRenderOption=FormattedString&user_id_type=open_id"
    
    headers = {
        "Authorization": f"Bearer {tenant_access_token}",
        "Content-Type": "application/json; charset=utf-8"
    }

    try:
        print(f"GET: {url}")
        response = requests.get(url, headers=headers)
        response.raise_for_status()

        result = response.json()
        print(f"Response: {json.dumps(result)}")
        
        if result.get("code", 0) != 0:
            print(f"ERROR: 读取表格范围失败 {result}", file=sys.stderr)
            raise Exception(f"failed to read sheet range: {result.get('msg', 'unknown error')}")

        if not result.get("data") or not result["data"].get("valueRange"):
            raise Exception("未获取到表格数据")

        value_range = result["data"]["valueRange"]
        print(f"读取范围 {full_range} 成功,数据行数: {len(value_range.get('values', []))}")
        
        # 打印前5行数据作为示例
        values = value_range.get("values", [])
        for i, row in enumerate(values[:5]):
            print(f"  行{i+1}: {row}")
        if len(values) > 5:
            print(f"  ... 还有 {len(values) - 5} 行数据")

        return value_range

    except Exception as e:
        print(f"ERROR: reading sheet range: {e}", file=sys.stderr)
        raise

def read_sheet_ranges(tenant_access_token: str, spreadsheet_token: str, ranges: List[str]) -> Dict[str, Any]:
    """读取电子表格中多个范围的数据

    Args:
        tenant_access_token: 租户访问令牌
        spreadsheet_token: 电子表格token
        ranges: 查询范围列表

    Returns:
        Dict[str, Any]: 读取的数据
    """
    ranges_param = ",".join([urllib.parse.quote(r) for r in ranges])
    url = f"https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/{spreadsheet_token}/values_batch_get?ranges={ranges_param}"
    url += "&valueRenderOption=ToString&dateTimeRenderOption=FormattedString&user_id_type=open_id"
    
    headers = {
        "Authorization": f"Bearer {tenant_access_token}",
        "Content-Type": "application/json; charset=utf-8"
    }

    try:
        print(f"GET: {url}")
        response = requests.get(url, headers=headers)
        response.raise_for_status()

        result = response.json()
        print(f"Response: {json.dumps(result)}")
        
        if result.get("code", 0) != 0:
            print(f"ERROR: 读取多个表格范围失败 {result}", file=sys.stderr)
            raise Exception(f"failed to read sheet ranges: {result.get('msg', 'unknown error')}")

        if not result.get("data") or not result["data"].get("valueRanges"):
            raise Exception("未获取到表格数据")

        value_ranges = result["data"]["valueRanges"]
        print(f"读取 {len(value_ranges)} 个范围成功:")
        for i, vr in enumerate(value_ranges):
            range_name = vr.get("range", "")
            values = vr.get("values", [])
            print(f"  范围{i+1}: {range_name}, 数据行数: {len(values)}")
            # 打印前3行数据作为示例
            for j, row in enumerate(values[:3]):
                print(f"    行{j+1}: {row}")
            if len(values) > 3:
                print(f"    ... 还有 {len(values) - 3} 行数据")

        return result["data"]

    except Exception as e:
        print(f"ERROR: reading sheet ranges: {e}", file=sys.stderr)
        raise

def extract_node_token_from_url(url: str) -> str:
    """从知识库URL中提取节点token

    Args:
        url: 知识库URL

    Returns:
        str: 节点token
    """
    try:
        parsed_url = urllib.parse.urlparse(url)
        path_parts = parsed_url.path.split("/")
        for part in path_parts:
            if part.startswith("wiki/"):
                return part.replace("wiki/", "")
        # 如果没有找到wiki/,尝试直接从路径中获取
        if len(path_parts) >= 3:
            return path_parts[2].split("?")[0]
        raise Exception("无法从URL中提取节点token")
    except Exception as e:
        print(f"ERROR: extracting node token from URL: {e}", file=sys.stderr)
        raise

三 因为我们公司的dify包太多 而且dify调用python生成文件没权限 所以调用第三方服务器api 下面是第三方api生成的jira代码

from jira import JIRA
import os

class JiraTaskManager:
    def __init__(self, server_url, username, api_token):
        """
        初始化JIRA连接
        :param server_url: JIRA服务器地址
        :param username: 用户名或邮箱
        :param api_token: API令牌
        """
        self.server_url = server_url
        self.username = username
        self.api_token = api_token
        self.jira = None
        self._connect()
    
    def _connect(self):
        """建立JIRA连接"""
        try:
            self.jira = JIRA(
                server=self.server_url,
                basic_auth=(self.username, self.api_token)
            )
            print("✅ JIRA连接成功")
            #print(self.jira.projects())

        except Exception as e:
            print(f"❌ JIRA连接失败: {e}")
            raise
    
    def create_issue(self, project_key, summary, description, issue_type='Task', 
                     priority='Medium', assignee=None, parent_key=None, attachments=None):
        """
        创建JIRA任务并上传附件
        :param project_key: 项目键
        :param summary: 任务标题
        :param description: 任务描述
        :param issue_type: 任务类型
        :param priority: 优先级
        :param assignee: 负责人用户名
        :param parent_key: 父任务键
        :param attachments: 附件文件路径列表
        :return: 创建的任务对象
        """
        issue_dict = {
            'project': {'key': project_key},
            'issuetype': {'name': issue_type},
            'summary': summary,
            'description': description,
            'priority': {'name': priority},
            'customfield_10405': {'value': 'Coding'},
            'components': [{'name': 'test'}]
        }
        
        if assignee:
            issue_dict['assignee'] = {'name': assignee}
        
        if issue_type == 'Sub-task' and parent_key:
            issue_dict['parent'] = {'key': parent_key}
        
        try:
            issue = self.jira.create_issue(fields=issue_dict)
            print(f"✅ 任务创建成功: {issue.key}")
            
            # 上传附件
            if attachments:
                self.upload_attachments(issue.key, attachments)
            
            return issue.key
        except Exception as e:
            print(f"❌ 任务创建失败: {e}")
            return None
    
    def upload_attachments(self, issue_key, file_paths):
        """
        为指定任务上传多个附件
        :param issue_key: 任务键
        :param file_paths: 文件路径列表
        """
        try:
            issue = self.jira.issue(issue_key)
            uploaded_files = []
            
            for file_path in file_paths:
                if os.path.exists(file_path):
                    self.jira.add_attachment(issue=issue, attachment=file_path)
                    file_name = os.path.basename(file_path)
                    uploaded_files.append(file_name)
                    print(f"📎 附件上传成功: {file_name}")
                else:
                    print(f"❌ 文件不存在: {file_path}")
            
            if uploaded_files:
                print(f"✅ 共上传 {len(uploaded_files)} 个附件到任务 {issue_key}")
            return uploaded_files
        except Exception as e:
            print(f"❌ 附件上传失败: {e}")
            return []
    
    def upload_single_attachment(self, issue_key, file_path):
        """
        为指定任务上传单个附件
        :param issue_key: 任务键
        :param file_path: 文件路径
        """
        return self.upload_attachments(issue_key, [file_path])
    
    def create_issue_with_attachment(self, project_key, summary, description, 
                                    attachment_path, issue_type='Task', priority='Medium'):
        """
        创建任务并上传单个附件的便捷方法
        """
        attachments = [attachment_path] if attachment_path else None
        return self.create_issue(
            project_key=project_key,
            summary=summary,
            description=description,
            issue_type=issue_type,
            priority=priority,
            attachments=attachments
        )
    
    def list_attachments(self, issue_key):
        """
        列出任务的所有附件
        :param issue_key: 任务键
        :return: 附件列表
        """
        try:
            issue = self.jira.issue(issue_key)
            attachments = issue.fields.attachment
            if attachments:
                print(f"📎 任务 {issue_key} 的附件列表:")
                for attachment in attachments:
                    print(f"  - {attachment.filename} ({attachment.size} bytes)")
            else:
                print(f"ℹ️ 任务 {issue_key} 没有附件")
            return attachments
        except Exception as e:
            print(f"❌ 获取附件列表失败: {e}")
            return []
    
    def download_attachment(self, attachment, download_dir='./downloads'):
        """
        下载附件到指定目录
        :param attachment: 附件对象
        :param download_dir: 下载目录
        """
        try:
            if not os.path.exists(download_dir):
                os.makedirs(download_dir)
            
            download_path = os.path.join(download_dir, attachment.filename)
            
            with open(download_path, 'wb') as f:
                f.write(attachment.get())
            
            print(f"📥 附件下载成功: {download_path}")
            return download_path
        except Exception as e:
            print(f"❌ 附件下载失败: {e}")
            return None
def main():
    """主函数示例"""
    # 配置信息 - 请替换为实际的值
    JIRA_SERVER = ''
    USERNAME = ''
    API_TOKEN = ''
    
    # 创建JIRA连接实例
    manager = JiraTaskManager(JIRA_SERVER, USERNAME, API_TOKEN)
    #exit()
    # 示例1: 创建任务并上传多个附件
    print("\n--- 创建任务并上传多个附件 ---")
    attachments = [
        './public/log.txt',
    ]
    
    issue = manager.create_issue(
        project_key='LIN',
        summary='congcong-dify带附件的测试任务-请忽略',
        description='这个任务包含了多个相关附件文件',
        issue_type='Task',
        priority='Low',
        attachments=attachments
    )
    print(issue)
    #exit()
    # if issue:
    #     # 示例2: 为现有任务上传新附件
    #     print("\n--- 为现有任务上传新附件 ---")
    #     new_attachment = './public/1.txt'
    #     manager.upload_single_attachment(issue.key, new_attachment)
        
    #     # 示例3: 列出任务的所有附件
    #     print("\n--- 列出任务附件 ---")
    #     manager.list_attachments(issue.key)

if __name__ == "__main__":
    main()

大功告成 本文档只为记录dify开发中的实际应用

您可能感兴趣的与本文相关的镜像

Python3.9

Python3.9

Conda
Python

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

<think> 我们正在讨论Dify如何实现飞书表格的实时数据同步。根据引用[1]的内容,Dify可以通过工作流与飞书表格集成,实现数据同步。但是,引用[1]主要描述的是AI绘画与飞书表格、企业微信的联动,没有直接说明实时数据同步的机制。不过,我们可以根据一般的集成原理和Dify的功能特性来推断实现方法。 实时数据同步通常有两种方式: 1. **轮询(Polling)**:定期检查数据源是否有更新。 2. **事件驱动(Event-driven)**:当数据源发生变化时主动通知。 由于飞书表格本身支持事件订阅(通过飞书开放平台),因此Dify更可能采用事件驱动的方式实现实时同步。 ### 实现步骤 根据引用[1]中提到的配置,以下是实现飞书表格实时数据同步的可能方法: 1. **配置飞书事件订阅** - 在飞书开放平台为自建应用开启事件订阅权限,订阅表格变更事件(如`sheet_updated`)[^1]。 - 设置事件回调URL(指向Dify工作流的Webhook入口)。 2. **在Dify中创建Webhook触发器** - 在Dify工作流中添加一个Webhook节点,用于接收飞书的事件通知。 - 配置该节点验证飞书事件签名(确保安全性)。 3. **设计数据同步逻辑** - 当接收到飞书表格变更事件时,触发工作流执行。 - 使用`FeishuSheetReader`节点读取变更的单元格范围(事件中会包含变更的sheetId、range等信息)。 - 将读取的数据同步到目标系统(如数据库、企业微信等)。 4. **处理同步数据** - 根据业务需求,可能需要对数据进行转换、过滤或存储。 - 引用[1]中提到结果输出处理,可以配置输出到企业微信、飞书文档或邮件。 ### 关键配置点 - **飞书事件订阅配置**:需要订阅`drive_file.edit_v2`事件(对应表格编辑)[官方文档参考](https://open.feishu.cn/document/server-docs/docs/event-subscription-guide/event-listening)。 - **Dify工作流设计**:工作流应包含以下节点: 1. Webhook触发器(接收事件) 2. 条件判断(验证事件类型) 3. FeishuSheetReader(读取变更数据) 4. 数据处理节点(可选) 5. 输出节点(同步到目标系统) ### 注意事项 1. **权限配置**:飞书应用需要拥有该表格的读写权限。 2. **事件去重**:飞书事件可能重复推送,需在Dify中设计去重机制(如记录已处理的事件ID)。 3. **性能优化**:实时同步对延迟敏感,需确保工作流执行效率。 > 示例:某企业通过事件订阅实现销售数据变更后5秒内同步至CRM系统[^1]。 §§相关问题§§ 1. Dify工作流处理飞书事件时如何保证数据安全性? 2. 飞书表格事件订阅支持哪些具体的事件类型? 3. 如何调试Dify中的飞书事件响应工作流? </think> ### Dify实现飞书表格实时数据同步的方法 根据引用[1]的集成方案,实现飞书表格实时同步需通过事件驱动架构完成,核心步骤如下: #### 1. **配置飞书事件订阅** ```mermaid sequenceDiagram 飞书服务器->>Dify Webhook: 表格变更事件推送 Dify Webhook->>数据解析引擎: 解码JSON事件 数据解析引擎->>同步执行器: 触发同步指令 ``` - 在飞书开放平台创建订阅:选择`drive_file.edit_v2`事件类型 - 设置回调URL指向Dify的Webhook端点 - 配置权限范围:`sheets:sheet:readonly`[^1] #### 2. **Dify工作流设计** ```python # 实时同步工作流逻辑 def sync_handler(event): if event['type'] == 'file_edit': # 检测编辑事件 changed_range = event['data']['range'] # 获取变更区域 sheet_data = feishu_api.get_range_data( sheet_id=event['sheet_id'], range=changed_range ) # 执行增量更新 database.upsert(sheet_data) return {'status': 'synced', 'rows': len(sheet_data)} ``` #### 3. **同步模式选择** | 同步模式 | 延迟 | 适用场景 | 配置要点 | |---------|------|----------|----------| | **即时推送** | <3秒 | 财务/库存系统 | 启用飞书事件强校验 | | **定时轮询** | 5-10分钟 | 报表系统 | 设置$cron(0/5 * * * *)$ | | **混合模式** | 动态调整 | 多数业务场景 | 空闲期降频策略 | #### 4. **异常处理机制** $$ \text{重试策略} = \begin{cases} \text{立即重试} & \textit{if } error \in [408,429] \\ \text{指数退避} & \textit{if } error \in [500,503] \\ \text{人工介入} & \textit{if } error = 403 \end{cases} $$ - 配置死信队列存储失败请求 - 设置企业微信告警规则:`错误率 > 5%`时触发通知 #### 5. **性能优化方案** 1. **增量同步**:通过`modified_time`字段过滤变更 2. **字段投影**:仅同步必要列(如`SELECT order_id, status`) 3. **缓存层**:使用Redis存储最近10次变更摘要 ```sql -- 示例增量查询 SELECT * FROM orders WHERE update_time > {{last_sync_time}} ``` > 某电商平台使用此方案实现订单状态实时同步,日均处理20万次变更事件[^1]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值