Syncing Feishu Freight Ledgers into MySQL: A Single-Strategy Walkthrough
What this strategy solves (scenario and value)
A retail client records monthly courier consumable costs, JD self-operated TC freight, and JD self-operated inbound freight for each regional warehouse in a Feishu spreadsheet. When finance closes the month, those figures need to land in a MySQL business database so that BI and cost-accounting systems can consume them. This strategy does one thing only: it syncs one Feishu sheet of freight entries into one MySQL table on a schedule. It looks simple, but a poorly designed code mapping will leave the two sides out of sync after three months — a real-world failure we have seen. We use the Qeasy data integration platform to chain extract, transform, and load into one observable and re-runnable pipeline.
Data flow and field mapping (source → staging → target)
The source is a Feishu spreadsheet pulled via the open API by range. The staging layer is the field-mapping zone inside Qeasy. The target is a business table in MySQL, written through batchexecute.
Key field mapping (desensitized, original field names retained):
| Source (Feishu row) | Staging (Qeasy variable) | Target (MySQL column) | Type |
|---|---|---|---|
| id | id | id | int |
| 月份 | 月份 | 月份 | string |
| 长春仓快递耗材费 | 长春仓快递耗材费 | 长春仓快递耗材费 | float |
| 京东自营TC运费 | 京东自营TC运费 | 京东自营TC运费 | float |
| 京东自营入仓运费 | 京东自营入仓运费 | 京东自营入仓运费 | float |
| 日期 | 日期 | 日期 | date |
The target uses id as primary key with idCheck=true. When the same id already exists, the row is overwritten by primary key, which prevents duplicate inserts.
How to configure it in Qeasy
For the source, pick WebAPI and fill in the Feishu open API spreadsheets/{token}/values/{range}. Fix valueRenderOption to ToString and dateTimeRenderOption to FormattedString, and set the header row to 1. Every numeric column arrives as a string and is converted later.
For the target, pick the MySQL SQL executor with API batchexecute, and reference every request field with the {{variable}} syntax.
The mapping zone is where most of the configuration effort goes. A common pattern we see across Qeasy customers is to centralize code mapping in the field-mapping panel rather than scattering it across scripts — when a field is added or removed later, only one place needs editing. Configure type-conversion rules (string→float, string→date) for date and amount fields once in the mapping zone so that dirty data never reaches downstream.
Implementation steps (phased scheduling)
Step 1 — Inspect the source. Trigger the source pull manually in Qeasy, then eyeball the column names, null values, and special characters. Pay special attention to stray commas or spaces inside fields such as 京东自营TC运费. This step cannot be skipped.
Step 2 — Create the target table. In MySQL, create the table with id as the primary key and column types matching the staging variables.
Step 3 — Configure the incremental starting point. Run a full sync the first time, then switch to incremental. Pick id as the incremental key. Qeasy remembers the last maximum id and only pulls rows with a larger id. For a new project, the safe approach is to run full sync once, confirm no data is missing, then switch to incremental.
Step 4 — Configure the schedule. Set the source cron to 0 1 * * * (pull at 01:00 every day) and the target cron to 30 1 * * * (write at 01:30 every day). Stagger the two sides by 30 minutes so that the target is not loading while the source is still pulling. This is an extension of the "header and body in phases" approach — do not stack different actions into the same second.
Step 5 — Trial run and monitoring. Run in a test environment for three days and reconcile the MySQL row count against the Feishu sheet. Then promote to production and turn on alerting.
Pitfalls from real projects
- Type mismatch. Feishu cells return strings by default, and MySQL float columns will reject them. Convert types explicitly in the mapping zone; do not rely on implicit database conversion.
idCheckleft off. WithoutidCheck=true, a source replay will create duplicate rows in the target and inflate month-end numbers.- No incremental starting point. Switching directly to incremental will drop historical data when the source already has large ids. Always run full sync first, then switch to incremental.
- Cron collision. When source and target share the same minute, the target may load an empty batch. Stagger by 15–30 minutes — that is the rule of thumb.
- Wrong header row. Forgetting or misconfiguring
headers_lineturns field names intoA, B, Cand breaks the entire mapping. Always eyeball the header row the first time you configure it.
When to use and when not to use
Use it when the source is a lightweight spreadsheet such as Feishu or Excel, the target is a structured database, the data volume is under tens of thousands of rows, and T+1 sync is acceptable. Do not use it when real-time second-level sync is required, when source fields change frequently, or when data volume exceeds one hundred thousand rows and calls for batch file exchange.