Dify可自动完成银行流水与财务凭证的双文件对账,精准识别未达账项、金额不一致、日期跨期三类差异,并逐条标注中文问题类型及原始行号。
☞☞☞AI 智能聊天, 问答助手, AI 智能搜索, 多模态理解力帮你轻松跨越从0到1的创作门槛☜☜☜

你需要让Dify自动比对银行流水Excel和财务系统导出的记账凭证Excel,识别未达账项、金额不一致、日期跨期等差异,并用中文逐条标出问题类型和原始行号——这个工作流必须支持双文件上传、结构化解析、逐行比对逻辑与自然语言差异说明,全程不依赖外部数据库或人工干预。
准备对账数据源与格式规范
将银行流水文件命名为“bank_statement_202510.xlsx”,财务凭证文件命名为“accounting_voucher_202510.xlsx”,两份文件均需为标准.xlsx格式,且【第一行必须为表头,且不得含合并单元格】;若存在合并单元格,Dify表格解析器会跳过整行,导致比对漏项。
银行流水表头至少包含:交易日期、对方户名、摘要、收入金额、支出金额、余额;财务凭证表头至少包含:记账日期、对方单位、摘要、借方金额、贷方金额、凭证号。字段名称不必完全一致,但语义需可映射。
文件名中禁止出现空格、“&”“#”“%”等特殊符号,Dify上传时对编码敏感,命名违规会导致文件解析失败并静默跳过。
创建双文件输入节点并配置校验逻辑
登录Dify控制台→左侧导航栏点击「Workflow」→右上角「+ New Workflow」→选择「Blank Workflow」。
在画布空白处双击,添加第一个「File Upload」节点;设置中勾选「Allow multiple files」为关闭状态,「File type restriction」设为「xlsx」;点击右下角「⋯」→「Rename」,改名为「银行流水文件」。
同理,再添加第二个「File Upload」节点,命名为「财务凭证文件」;【两个文件节点必须独立配置,不可共用同一上传组件】,否则后续无法分别调用其解析结果。
在两个节点下方各添加一个「Condition」节点:分别判断文件是否为空。条件表达式填「{{bank_statement_file}} != null」和「{{accounting_voucher_file}} != null」;任一为空则直接输出“请上传完整对账文件”,终止流程。
结构化解析与字段映射
从左侧组件栏拖入两个「Table Extract」节点,分别连接至「银行流水文件」和「财务凭证文件」的输出端口。
使用AIsa生成图像与视频。仅需一个API密钥即可调用Gemini 3 Pro Image(图像)和Qwen Wan 2.6(视频)。
配置「银行流水文件」对应的Table Extract节点:「Sheet name」留空(读取首sheet);「Header row index」填0;在「Column mapping」中手动映射:第1列→“trade_date”,第2列→“counterparty”,第3列→“summary”,第4列→“income_amount”,第5列→“expense_amount”,第6列→“balance”。
配置「财务凭证文件」对应的Table Extract节点:同样Header填0;映射第1列→“posting_date”,第2列→“counterparty”,第3列→“summary”,第4列→“debit_amount”,第5列→“credit_amount”,第6列→“voucher_no”。
注意:金额字段必须映射为数字类型,不能映射为字符串;否则后续比对会因类型不匹配全部返回False。
执行逐行差异比对与分类标记
拖入一个「Code」节点(Python),连接两个Table Extract节点的输出;在代码框中粘贴以下逻辑:
```python
import pandas as pd
import numpy as np
# 获取两个DataFrame
bank_df = pd.DataFrame(inputs['bank_statement_file'])
acct_df = pd.DataFrame(inputs['accounting_voucher_file'])
# 标准化日期格式
bank_df['trade_date'] = pd.to_datetime(bank_df['trade_date'], errors='coerce')
acct_df['posting_date'] = pd.to_datetime(acct_df['posting_date'], errors='coerce')
# 构建比对键:对方单位+摘要前20字+金额绝对值(四舍五入到分)
bank_df['match_key'] = (
bank_df['counterparty'].fillna('').str[:20] + '_' +
bank_df['summary'].fillna('').str[:20] + '_' +
bank_df[['income_amount', 'expense_amount']].sum(axis=1).round(2).astype(str)
)
acct_df['match_key'] = (
acct_df['counterparty'].fillna('').str[:20] + '_' +
acct_df['summary'].fillna('').str[:20] + '_' +
acct_df[['debit_amount', 'credit_amount']].sum(axis=1).round(2).astype(str)
)
# 找出仅存在于银行流水的行(未达账-银行已付企业未记)
only_in_bank = bank_df[~bank_df['match_key'].isin(acct_df['match_key'])].copy()
only_in_bank['issue_type'] = '银行有、凭证无(未达账)'
# 找出仅存在于凭证的行(未达账-企业已付银行未收)
only_in_acct = acct_df[~acct_df['match_key'].isin(bank_df['match_key'])].copy()
only_in_acct['issue_type'] = '凭证有、银行无(未达账)'
# 找出match_key相同但金额/日期不一致的行
merged = pd.merge(bank_df, acct_df, on='match_key', how='inner', suffixes=('_bank', '_acct'))
amount_mismatch = merged[
~np.isclose(
merged[['income_amount_bank', 'expense_amount_bank']].sum(axis=1),
merged[['debit_amount_acct', 'credit_amount_acct']].sum(axis=1),
atol=0.01
)
].copy()
amount_mismatch['issue_type'] = '金额不一致'
date_mismatch = merged[
(merged['trade_date_bank'].dt.date != merged['posting_date_acct'].dt.date)
].copy()
date_mismatch['issue_type'] = '日期跨期'
# 合并所有问题行,保留原始索引(+2因表头占两行)
issues = []
for _, r in only_in_bank.iterrows():
issues.append({'row_number': int(r.name + 2), 'source': '银行流水', 'issue_type': r['issue_type'], 'detail': f'对方:{r["counterparty"][:15]} 摘要:{r["summary"][:15]} 金额:{r[["income_amount","expense_amount"]].sum()}'})
for _, r in only_in_acct.iterrows():
issues.append({'row_number': int(r.name + 2), 'source': '财务凭证', 'issue_type': r['issue_type'], 'detail': f'对方:{r["counterparty"][:15]} 摘要:{r["summary"][:15]} 金额:{r[["debit_amount","credit_amount"]].sum()}'})
for _, r in amount_mismatch.iterrows():
issues.append({'row_number': int(r.name_bank + 2), 'source': '银行流水', 'issue_type': r['issue_type'], 'detail': f'匹配键:{r["match_key"][:20]} 银行金额:{r[["income_amount_bank","expense_amount_bank"]].sum():.2f} 凭证金额:{r[["debit_amount_acct","credit_amount_acct"]].sum():.2f}'})
for _, r in date_mismatch.iterrows():
issues.append({'row_number': int(r.name_bank + 2), 'source': '银行流水', 'issue_type': r['issue_type'], 'detail': f'匹配键:{r["match_key"][:20]} 银行日期:{r["trade_date_bank"].date()} 凭证日期:{r["posting_date_acct"].date()}'})
outputs['issues'] = issues
```
生成结构化中文差异报告
拖入一个「LLM」节点,连接Code节点的输出;在Prompt中填写:
你是一名有8年经验的财务对账专员。请根据以下差异列表,逐条生成中文说明,每条包含:①原始文件位置(如“银行流水第142行”);②问题类型;③简明原因(不超过20字);④建议动作(如“补做凭证”“核查银行回单”)。不要编号,不要加粗,不要使用项目符号,用纯中文自然段落分隔。差异列表:{{issues}}。
模型选择Qwen2.5-7B(本地部署)或GPT-4-turbo(API可用时);温度设为0.3,确保输出稳定不发散。
这一步操作起来很简单,直接把上面写好的Prompt粘贴进去就行。注意不要删掉{{issues}}这个变量占位符,否则LLM收不到数据。

















