n8n-nodes-excel-api 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +351 -231
- package/dist/nodes/ExcelApi/ExcelApi.node.d.ts +1 -0
- package/dist/nodes/ExcelApi/ExcelApi.node.js +83 -6
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -3,71 +3,73 @@
|
|
|
3
3
|
[](https://badge.fury.io/js/n8n-nodes-excel-api)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
|
|
6
|
-
n8n
|
|
6
|
+
An n8n community node for accessing Excel files via API with **concurrent safety protection**. Perfect for scenarios where multiple users simultaneously access the same Excel file through n8n workflows.
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
> 📖 **[中文文檔](README_zh-tw.md)** | **[English Documentation](README.md)**
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
直接在 n8n 中使用 Excel 檔案時:
|
|
12
|
-
- ❌ 多個工作流程同時存取同一檔案會導致檔案損毀
|
|
13
|
-
- ❌ 並行寫入時會發生資料覆蓋與遺失
|
|
14
|
-
- ❌ 缺乏檔案鎖定機制
|
|
15
|
-
- ❌ 難以處理多人同時提交的 Webhook 表單
|
|
10
|
+
## 🎯 Why This Node?
|
|
16
11
|
|
|
17
|
-
###
|
|
18
|
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
- ✅ **批次操作** - 高效的大量更新
|
|
12
|
+
### The Problem
|
|
13
|
+
When working with Excel files directly in n8n:
|
|
14
|
+
- ❌ Multiple workflows accessing the same file cause file corruption
|
|
15
|
+
- ❌ Concurrent writes lead to data overwrite and loss
|
|
16
|
+
- ❌ No file locking mechanism
|
|
17
|
+
- ❌ Difficult to handle simultaneous webhook form submissions
|
|
24
18
|
|
|
25
|
-
|
|
19
|
+
### The Solution
|
|
20
|
+
This node works with [Excel API Server](https://github.com/code4Copilot/excel-api-server) to provide:
|
|
21
|
+
- ✅ **File Locking** - Automatically queue concurrent requests
|
|
22
|
+
- ✅ **Data Integrity** - No data loss or corruption
|
|
23
|
+
- ✅ **Multi-User Support** - Perfect for multi-user HTML form submissions
|
|
24
|
+
- ✅ **Google Sheets-like Interface** - Familiar operations in n8n
|
|
25
|
+
- ✅ **Batch Operations** - Efficient bulk updates
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
## 📦 Installation
|
|
28
|
+
|
|
29
|
+
### Method 1: npm (Recommended)
|
|
28
30
|
|
|
29
31
|
```bash
|
|
30
32
|
npm install n8n-nodes-excel-api
|
|
31
33
|
```
|
|
32
34
|
|
|
33
|
-
###
|
|
35
|
+
### Method 2: Manual Installation
|
|
34
36
|
|
|
35
37
|
```bash
|
|
36
|
-
# 1.
|
|
38
|
+
# 1. Clone repository
|
|
37
39
|
git clone https://github.com/code4Copilot/n8n-nodes-excel-api.git
|
|
38
40
|
cd n8n-nodes-excel-api
|
|
39
41
|
|
|
40
|
-
# 2.
|
|
42
|
+
# 2. Install dependencies
|
|
41
43
|
npm install
|
|
42
44
|
|
|
43
|
-
# 3.
|
|
45
|
+
# 3. Build
|
|
44
46
|
npm run build
|
|
45
47
|
|
|
46
|
-
# 4.
|
|
48
|
+
# 4. Link to n8n
|
|
47
49
|
npm link
|
|
48
50
|
cd ~/.n8n
|
|
49
51
|
npm link n8n-nodes-excel-api
|
|
50
52
|
|
|
51
|
-
# 5.
|
|
53
|
+
# 5. Restart n8n
|
|
52
54
|
n8n start
|
|
53
55
|
```
|
|
54
56
|
|
|
55
|
-
###
|
|
57
|
+
### Method 3: Community Package (After Publication)
|
|
56
58
|
|
|
57
|
-
|
|
58
|
-
1.
|
|
59
|
-
2.
|
|
60
|
-
3.
|
|
61
|
-
4.
|
|
59
|
+
In n8n:
|
|
60
|
+
1. Go to **Settings** → **Community Nodes**
|
|
61
|
+
2. Click **Install**
|
|
62
|
+
3. Enter: `n8n-nodes-excel-api`
|
|
63
|
+
4. Click **Install**
|
|
62
64
|
|
|
63
|
-
## 🚀
|
|
65
|
+
## 🚀 Prerequisites
|
|
64
66
|
|
|
65
|
-
|
|
67
|
+
**You must run Excel API Server first!**
|
|
66
68
|
|
|
67
|
-
|
|
69
|
+
Install and start [Excel API Server](https://github.com/code4Copilot/excel-api-server):
|
|
68
70
|
|
|
69
71
|
```bash
|
|
70
|
-
#
|
|
72
|
+
# Quick start with Docker
|
|
71
73
|
docker run -d \
|
|
72
74
|
-p 8000:8000 \
|
|
73
75
|
-v $(pwd)/data:/app/data \
|
|
@@ -75,124 +77,153 @@ docker run -d \
|
|
|
75
77
|
yourusername/excel-api-server
|
|
76
78
|
```
|
|
77
79
|
|
|
78
|
-
|
|
80
|
+
See [Excel API Server Documentation](https://github.com/code4Copilot/excel-api-server) for details.
|
|
79
81
|
|
|
80
|
-
## 🔧
|
|
82
|
+
## 🔧 Configuration
|
|
81
83
|
|
|
82
|
-
### 1.
|
|
84
|
+
### 1. Set Up Credentials
|
|
83
85
|
|
|
84
|
-
|
|
85
|
-
1.
|
|
86
|
-
2.
|
|
87
|
-
3.
|
|
88
|
-
- **API URL
|
|
89
|
-
- **API Token
|
|
90
|
-
4.
|
|
86
|
+
In n8n:
|
|
87
|
+
1. Go to **Credentials** → **New**
|
|
88
|
+
2. Search for "Excel API"
|
|
89
|
+
3. Fill in:
|
|
90
|
+
- **API URL**: `http://localhost:8000` (Your API server address)
|
|
91
|
+
- **API Token**: `your-secret-token` (From Excel API Server)
|
|
92
|
+
4. Click **Save**
|
|
91
93
|
|
|
92
|
-
### 2.
|
|
94
|
+
### 2. Add Node to Workflow
|
|
93
95
|
|
|
94
|
-
1.
|
|
95
|
-
2.
|
|
96
|
-
3.
|
|
97
|
-
4.
|
|
98
|
-
5.
|
|
99
|
-
6.
|
|
96
|
+
1. Create or open a workflow
|
|
97
|
+
2. Click **Add Node**
|
|
98
|
+
3. Search for "Excel API"
|
|
99
|
+
4. Select the node
|
|
100
|
+
5. Choose your credential
|
|
101
|
+
6. Configure operation
|
|
100
102
|
|
|
101
|
-
## 📚
|
|
103
|
+
## 📚 Operations
|
|
102
104
|
|
|
103
|
-
### 1. Append
|
|
104
|
-
|
|
105
|
+
### 1. Append
|
|
106
|
+
Add a new row to the end of the sheet.
|
|
105
107
|
|
|
106
|
-
|
|
108
|
+
**Two Modes:**
|
|
107
109
|
|
|
108
|
-
#### Object Mode
|
|
109
|
-
|
|
110
|
+
#### Object Mode - Recommended
|
|
111
|
+
Map values by column names, safer and easier to maintain.
|
|
110
112
|
|
|
111
|
-
|
|
113
|
+
**Example:**
|
|
112
114
|
```json
|
|
113
115
|
{
|
|
114
|
-
"
|
|
115
|
-
"
|
|
116
|
-
"
|
|
117
|
-
"
|
|
118
|
-
"
|
|
116
|
+
"Employee ID": "{{ $json.body.employeeId }}",
|
|
117
|
+
"Name": "{{ $json.body.name }}",
|
|
118
|
+
"Department": "{{ $json.body.department }}",
|
|
119
|
+
"Position": "{{ $json.body.position }}",
|
|
120
|
+
"Salary": "{{ $json.body.salary }}"
|
|
119
121
|
}
|
|
120
122
|
```
|
|
121
123
|
|
|
122
|
-
|
|
123
|
-
- ✅
|
|
124
|
-
- ✅
|
|
125
|
-
- ✅
|
|
126
|
-
- ✅
|
|
127
|
-
- ✅
|
|
124
|
+
**Features:**
|
|
125
|
+
- ✅ Automatically read Excel headers (first row)
|
|
126
|
+
- ✅ Intelligently map by column names
|
|
127
|
+
- ✅ Ignore unknown columns with warnings in response
|
|
128
|
+
- ✅ Column order can be arbitrary
|
|
129
|
+
- ✅ Missing columns automatically filled with empty values
|
|
128
130
|
|
|
129
|
-
#### Array Mode
|
|
130
|
-
|
|
131
|
+
#### Array Mode
|
|
132
|
+
Specify values in exact column order.
|
|
131
133
|
|
|
132
|
-
|
|
134
|
+
**Example:**
|
|
133
135
|
```json
|
|
134
|
-
["E100", "
|
|
136
|
+
["E100", "John Doe", "HR", "Manager", "70000"]
|
|
135
137
|
```
|
|
136
138
|
|
|
137
|
-
|
|
139
|
+
**Note:** Value order must exactly match Excel column order.
|
|
138
140
|
|
|
139
|
-
### 2. Read
|
|
140
|
-
|
|
141
|
+
### 2. Read
|
|
142
|
+
Read data from Excel file.
|
|
141
143
|
|
|
142
|
-
|
|
143
|
-
- `file
|
|
144
|
-
- `sheet
|
|
145
|
-
- `range
|
|
144
|
+
**Parameters:**
|
|
145
|
+
- `file`: File name (e.g., `employees.xlsx`)
|
|
146
|
+
- `sheet`: Sheet name (default: `Sheet1`)
|
|
147
|
+
- `range`: Cell range (e.g., `A1:D10`, leave empty to read all)
|
|
146
148
|
|
|
147
|
-
|
|
148
|
-
-
|
|
149
|
-
-
|
|
150
|
-
-
|
|
149
|
+
**Output:**
|
|
150
|
+
- Auto-convert first row to column names if headers detected
|
|
151
|
+
- Return array of objects with headers as keys
|
|
152
|
+
- Return raw data array if no headers
|
|
151
153
|
|
|
152
|
-
### 3. Update
|
|
153
|
-
|
|
154
|
+
### 3. Update
|
|
155
|
+
Update existing row data.
|
|
154
156
|
|
|
155
|
-
|
|
157
|
+
**Identify Methods:**
|
|
156
158
|
|
|
157
|
-
####
|
|
158
|
-
|
|
159
|
+
#### By Row Number
|
|
160
|
+
Directly specify row number to update (starts from 2, row 1 is header).
|
|
159
161
|
|
|
160
|
-
|
|
162
|
+
**Example:**
|
|
161
163
|
```json
|
|
162
164
|
{
|
|
163
165
|
"operation": "update",
|
|
164
166
|
"identifyBy": "rowNumber",
|
|
165
167
|
"rowNumber": 5,
|
|
166
168
|
"valuesToSet": {
|
|
167
|
-
"
|
|
168
|
-
"
|
|
169
|
+
"Status": "Completed",
|
|
170
|
+
"Update Date": "2025-12-21"
|
|
169
171
|
}
|
|
170
172
|
}
|
|
171
173
|
```
|
|
172
174
|
|
|
173
|
-
####
|
|
174
|
-
|
|
175
|
+
#### By Lookup
|
|
176
|
+
Find rows to update by looking up specific column values.
|
|
177
|
+
|
|
178
|
+
**Process Modes:**
|
|
175
179
|
|
|
176
|
-
|
|
180
|
+
##### All Matching Records - Default
|
|
181
|
+
Update all matching rows, suitable for batch update scenarios.
|
|
182
|
+
|
|
183
|
+
**Example: Update all IT department employees**
|
|
177
184
|
```json
|
|
178
185
|
{
|
|
179
186
|
"operation": "update",
|
|
180
187
|
"identifyBy": "lookup",
|
|
181
|
-
"lookupColumn": "
|
|
188
|
+
"lookupColumn": "Department",
|
|
189
|
+
"lookupValue": "IT",
|
|
190
|
+
"processMode": "all",
|
|
191
|
+
"valuesToSet": {
|
|
192
|
+
"Status": "Reviewed",
|
|
193
|
+
"Review Date": "2026-01-06"
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
##### First Match Only
|
|
199
|
+
Update only the first matching record, suitable for unique identifier lookups.
|
|
200
|
+
|
|
201
|
+
**Example: Update specific employee data**
|
|
202
|
+
```json
|
|
203
|
+
{
|
|
204
|
+
"operation": "update",
|
|
205
|
+
"identifyBy": "lookup",
|
|
206
|
+
"lookupColumn": "Employee ID",
|
|
182
207
|
"lookupValue": "E100",
|
|
208
|
+
"processMode": "first",
|
|
183
209
|
"valuesToSet": {
|
|
184
|
-
"
|
|
185
|
-
"
|
|
210
|
+
"Salary": "80000",
|
|
211
|
+
"Position": "Senior Manager"
|
|
186
212
|
}
|
|
187
213
|
}
|
|
188
214
|
```
|
|
189
215
|
|
|
190
|
-
|
|
191
|
-
|
|
216
|
+
**💡 Usage Tips:**
|
|
217
|
+
- When looking up by unique identifiers (Employee ID, Email), use `processMode: "first"` for better performance
|
|
218
|
+
- Use `processMode: "all"` when batch updating multiple records
|
|
219
|
+
- Default is `"all"` to ensure no matching records are missed
|
|
220
|
+
|
|
221
|
+
### 4. Delete
|
|
222
|
+
Delete a row from the sheet.
|
|
192
223
|
|
|
193
|
-
|
|
224
|
+
**Identify Methods:**
|
|
194
225
|
|
|
195
|
-
####
|
|
226
|
+
#### By Row Number
|
|
196
227
|
```json
|
|
197
228
|
{
|
|
198
229
|
"operation": "delete",
|
|
@@ -201,31 +232,59 @@ docker run -d \
|
|
|
201
232
|
}
|
|
202
233
|
```
|
|
203
234
|
|
|
204
|
-
####
|
|
235
|
+
#### By Lookup
|
|
236
|
+
Find rows to delete by looking up specific column values.
|
|
237
|
+
|
|
238
|
+
**Process Modes:**
|
|
239
|
+
|
|
240
|
+
##### All Matching Records - Default
|
|
241
|
+
Delete all matching rows.
|
|
242
|
+
|
|
243
|
+
**Example: Delete all terminated employees**
|
|
244
|
+
```json
|
|
245
|
+
{
|
|
246
|
+
"operation": "delete",
|
|
247
|
+
"identifyBy": "lookup",
|
|
248
|
+
"lookupColumn": "Status",
|
|
249
|
+
"lookupValue": "Terminated",
|
|
250
|
+
"processMode": "all"
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
##### First Match Only
|
|
255
|
+
Delete only the first matching record.
|
|
256
|
+
|
|
257
|
+
**Example: Delete specific employee**
|
|
205
258
|
```json
|
|
206
259
|
{
|
|
207
260
|
"operation": "delete",
|
|
208
261
|
"identifyBy": "lookup",
|
|
209
|
-
"lookupColumn": "
|
|
210
|
-
"lookupValue": "E100"
|
|
262
|
+
"lookupColumn": "Employee ID",
|
|
263
|
+
"lookupValue": "E100",
|
|
264
|
+
"processMode": "first"
|
|
211
265
|
}
|
|
212
266
|
```
|
|
213
267
|
|
|
214
|
-
|
|
215
|
-
|
|
268
|
+
**⚠️ Important:**
|
|
269
|
+
- Delete operations cannot be undone, use with caution
|
|
270
|
+
- When looking up by unique identifiers, use `processMode: "first"`
|
|
271
|
+
- Verify lookup conditions carefully when batch deleting to avoid accidental data loss
|
|
272
|
+
|
|
273
|
+
### 5. Batch
|
|
274
|
+
Execute multiple operations at once (more efficient).
|
|
216
275
|
|
|
217
|
-
|
|
276
|
+
**Example:**
|
|
218
277
|
```json
|
|
219
278
|
{
|
|
220
279
|
"operations": [
|
|
221
280
|
{
|
|
222
281
|
"type": "append",
|
|
223
|
-
"values": ["E010", "Alice", "
|
|
282
|
+
"values": ["E010", "Alice", "Marketing", "Specialist", "65000"]
|
|
224
283
|
},
|
|
225
284
|
{
|
|
226
285
|
"type": "update",
|
|
227
286
|
"row": 5,
|
|
228
|
-
"values": ["E005", "Updated Name", "IT
|
|
287
|
+
"values": ["E005", "Updated Name", "IT", "Manager", "90000"]
|
|
229
288
|
},
|
|
230
289
|
{
|
|
231
290
|
"type": "delete",
|
|
@@ -235,45 +294,43 @@ docker run -d \
|
|
|
235
294
|
}
|
|
236
295
|
```
|
|
237
296
|
|
|
238
|
-
## 🎨
|
|
297
|
+
## 🎨 Usage Examples
|
|
239
298
|
|
|
240
|
-
|
|
299
|
+
### Example 1: Webhook Form to Excel
|
|
241
300
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
完美適用於多人同時提交表單的場景!
|
|
301
|
+
Perfect for scenarios with multiple simultaneous form submissions!
|
|
245
302
|
|
|
246
303
|
```
|
|
247
304
|
┌──────────────────┐
|
|
248
|
-
│ Webhook │
|
|
305
|
+
│ Webhook │ Receive form submission
|
|
249
306
|
│ POST /submit │
|
|
250
307
|
└────────┬─────────┘
|
|
251
308
|
│
|
|
252
309
|
▼
|
|
253
310
|
┌──────────────────┐
|
|
254
|
-
│ Excel API │
|
|
255
|
-
│ │
|
|
256
|
-
│ │
|
|
257
|
-
│ │ "
|
|
311
|
+
│ Excel API │ Operation: Append (Object Mode)
|
|
312
|
+
│ │ File: registrations.xlsx
|
|
313
|
+
│ │ Values: {
|
|
314
|
+
│ │ "Name": "{{ $json.body.name }}",
|
|
258
315
|
│ │ "Email": "{{ $json.body.email }}",
|
|
259
|
-
│ │ "
|
|
260
|
-
│ │ "
|
|
316
|
+
│ │ "Phone": "{{ $json.body.phone }}",
|
|
317
|
+
│ │ "Submit Time": "{{ $now }}"
|
|
261
318
|
│ │ }
|
|
262
319
|
└────────┬─────────┘
|
|
263
320
|
│
|
|
264
321
|
▼
|
|
265
322
|
┌──────────────────┐
|
|
266
|
-
│ Respond Webhook │
|
|
323
|
+
│ Respond Webhook │ Return success message
|
|
267
324
|
└──────────────────┘
|
|
268
325
|
```
|
|
269
326
|
|
|
270
|
-
**HTML
|
|
327
|
+
**HTML Form:**
|
|
271
328
|
```html
|
|
272
329
|
<form id="registrationForm">
|
|
273
|
-
<input type="text" name="name" placeholder="
|
|
330
|
+
<input type="text" name="name" placeholder="Name" required>
|
|
274
331
|
<input type="email" name="email" placeholder="Email" required>
|
|
275
|
-
<input type="tel" name="phone" placeholder="
|
|
276
|
-
<button type="submit"
|
|
332
|
+
<input type="tel" name="phone" placeholder="Phone" required>
|
|
333
|
+
<button type="submit">Submit</button>
|
|
277
334
|
</form>
|
|
278
335
|
|
|
279
336
|
<script>
|
|
@@ -285,78 +342,134 @@ document.getElementById('registrationForm').addEventListener('submit', async (e)
|
|
|
285
342
|
headers: {'Content-Type': 'application/json'},
|
|
286
343
|
body: JSON.stringify(Object.fromEntries(formData))
|
|
287
344
|
});
|
|
288
|
-
alert('
|
|
345
|
+
alert('Submitted successfully!');
|
|
289
346
|
});
|
|
290
347
|
</script>
|
|
291
348
|
```
|
|
292
349
|
|
|
293
|
-
###
|
|
350
|
+
### Example 2: Daily Report Generation
|
|
294
351
|
|
|
295
352
|
```
|
|
296
353
|
┌──────────────────┐
|
|
297
|
-
│ Schedule │
|
|
354
|
+
│ Schedule │ Every day at 9:00 AM
|
|
298
355
|
│ 0 9 * * * │
|
|
299
356
|
└────────┬─────────┘
|
|
300
357
|
│
|
|
301
358
|
▼
|
|
302
359
|
┌──────────────────┐
|
|
303
|
-
│ Excel API │
|
|
304
|
-
│ (
|
|
360
|
+
│ Excel API │ Operation: Read
|
|
361
|
+
│ (Read) │ File: sales.xlsx
|
|
305
362
|
└────────┬─────────┘
|
|
306
363
|
│
|
|
307
364
|
▼
|
|
308
365
|
┌──────────────────┐
|
|
309
|
-
│ Filter │
|
|
366
|
+
│ Filter │ Filter today's records
|
|
310
367
|
└────────┬─────────┘
|
|
311
368
|
│
|
|
312
369
|
▼
|
|
313
370
|
┌──────────────────┐
|
|
314
|
-
│ Send Email │
|
|
371
|
+
│ Send Email │ Send daily report
|
|
315
372
|
└──────────────────┘
|
|
316
373
|
```
|
|
317
374
|
|
|
318
|
-
###
|
|
375
|
+
### Example 3: Batch Updates
|
|
319
376
|
|
|
320
377
|
```
|
|
321
378
|
┌──────────────────┐
|
|
322
|
-
│ Code │
|
|
379
|
+
│ Code │ Prepare operations array
|
|
323
380
|
│ │ operations = [...]
|
|
324
381
|
└────────┬─────────┘
|
|
325
382
|
│
|
|
326
383
|
▼
|
|
327
384
|
┌──────────────────┐
|
|
328
|
-
│ Excel API │
|
|
329
|
-
│ (
|
|
330
|
-
│ │
|
|
385
|
+
│ Excel API │ Operation: Batch
|
|
386
|
+
│ (Batch) │ File: data.xlsx
|
|
387
|
+
│ │ Operations: {{ $json.operations }}
|
|
331
388
|
└──────────────────┘
|
|
332
389
|
```
|
|
333
390
|
|
|
334
|
-
###
|
|
391
|
+
### Example 4: Update Salary by Employee ID
|
|
335
392
|
|
|
336
393
|
```
|
|
337
394
|
┌──────────────────┐
|
|
338
|
-
│ Webhook │
|
|
395
|
+
│ Webhook │ Receive update request
|
|
339
396
|
│ POST /update │ { "employeeId": "E100", "salary": 85000 }
|
|
340
397
|
└────────┬─────────┘
|
|
341
398
|
│
|
|
342
399
|
▼
|
|
343
400
|
┌──────────────────┐
|
|
344
|
-
│ Excel API │
|
|
345
|
-
│ │
|
|
346
|
-
│ │
|
|
347
|
-
│ │
|
|
348
|
-
│ │
|
|
401
|
+
│ Excel API │ Operation: Update
|
|
402
|
+
│ │ Identify By: Lookup
|
|
403
|
+
│ │ Lookup Column: Employee ID
|
|
404
|
+
│ │ Lookup Value: {{ $json.body.employeeId }}
|
|
405
|
+
│ │ Process Mode: First Match Only
|
|
406
|
+
│ │ Values To Set: { "Salary": "{{ $json.body.salary }}" }
|
|
407
|
+
└────────┬─────────┘
|
|
408
|
+
│
|
|
409
|
+
▼
|
|
410
|
+
┌──────────────────┐
|
|
411
|
+
│ Respond Webhook │ Return update result
|
|
412
|
+
└──────────────────┘
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
### Example 5: Batch Department Status Update
|
|
416
|
+
|
|
417
|
+
**Use Case:** Review all employees in a department at once
|
|
418
|
+
|
|
419
|
+
```
|
|
420
|
+
┌──────────────────┐
|
|
421
|
+
│ Webhook │ Receive batch review request
|
|
422
|
+
│ POST /approve │ { "department": "IT", "status": "Reviewed" }
|
|
423
|
+
└────────┬─────────┘
|
|
424
|
+
│
|
|
425
|
+
▼
|
|
426
|
+
┌──────────────────┐
|
|
427
|
+
│ Excel API │ Operation: Update
|
|
428
|
+
│ │ Identify By: Lookup
|
|
429
|
+
│ │ Lookup Column: Department
|
|
430
|
+
│ │ Lookup Value: {{ $json.body.department }}
|
|
431
|
+
│ │ Process Mode: All Matching Records
|
|
432
|
+
│ │ Values To Set: {
|
|
433
|
+
│ │ "Status": "{{ $json.body.status }}",
|
|
434
|
+
│ │ "Review Date": "{{ $now.format('YYYY-MM-DD') }}",
|
|
435
|
+
│ │ "Reviewer": "{{ $json.body.reviewer }}"
|
|
436
|
+
│ │ }
|
|
437
|
+
└────────┬─────────┘
|
|
438
|
+
│
|
|
439
|
+
▼
|
|
440
|
+
┌──────────────────┐
|
|
441
|
+
│ Respond Webhook │ Return: Updated N records
|
|
442
|
+
└──────────────────┘
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
### Example 6: Clean Up Expired Data
|
|
446
|
+
|
|
447
|
+
**Use Case:** Periodically delete employee records terminated over a year ago
|
|
448
|
+
|
|
449
|
+
```
|
|
450
|
+
┌──────────────────┐
|
|
451
|
+
│ Schedule │ Execute on 1st of month
|
|
452
|
+
│ 0 0 1 * * │
|
|
349
453
|
└────────┬─────────┘
|
|
350
454
|
│
|
|
351
455
|
▼
|
|
352
456
|
┌──────────────────┐
|
|
353
|
-
│
|
|
457
|
+
│ Excel API │ Operation: Delete
|
|
458
|
+
│ │ Identify By: Lookup
|
|
459
|
+
│ │ Lookup Column: Status
|
|
460
|
+
│ │ Lookup Value: Terminated
|
|
461
|
+
│ │ Process Mode: All Matching Records
|
|
462
|
+
└────────┬─────────┘
|
|
463
|
+
│
|
|
464
|
+
▼
|
|
465
|
+
┌──────────────────┐
|
|
466
|
+
│ Send Email │ Notify admin: Cleaned N records
|
|
354
467
|
└──────────────────┘
|
|
355
468
|
```
|
|
356
469
|
|
|
357
|
-
## 🧪
|
|
470
|
+
## 🧪 Concurrent Testing
|
|
358
471
|
|
|
359
|
-
|
|
472
|
+
Test 10 simultaneous submissions:
|
|
360
473
|
|
|
361
474
|
```javascript
|
|
362
475
|
// concurrent_test.js
|
|
@@ -367,156 +480,163 @@ for (let i = 0; i < 10; i++) {
|
|
|
367
480
|
method: 'POST',
|
|
368
481
|
headers: {'Content-Type': 'application/json'},
|
|
369
482
|
body: JSON.stringify({
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
483
|
+
EmployeeID: `E${String(i).padStart(3, '0')}`,
|
|
484
|
+
Name: `Test User ${i}`,
|
|
485
|
+
Timestamp: new Date().toISOString()
|
|
373
486
|
})
|
|
374
487
|
})
|
|
375
488
|
);
|
|
376
489
|
}
|
|
377
490
|
|
|
378
491
|
await Promise.all(promises);
|
|
379
|
-
console.log('
|
|
492
|
+
console.log('All requests completed!');
|
|
380
493
|
```
|
|
381
494
|
|
|
382
|
-
|
|
495
|
+
**Result:** All 10 records will be safely written to Excel without data loss or corruption!
|
|
383
496
|
|
|
384
|
-
## ⚠️
|
|
497
|
+
## ⚠️ Troubleshooting
|
|
385
498
|
|
|
386
|
-
###
|
|
499
|
+
### Issue 1: Node Not Showing in n8n
|
|
387
500
|
|
|
388
|
-
|
|
501
|
+
**Solution:**
|
|
389
502
|
```bash
|
|
390
|
-
#
|
|
503
|
+
# Restart n8n
|
|
391
504
|
pkill -f n8n
|
|
392
505
|
n8n start
|
|
393
506
|
|
|
394
|
-
#
|
|
507
|
+
# Or with pm2
|
|
395
508
|
pm2 restart n8n
|
|
396
509
|
```
|
|
397
510
|
|
|
398
|
-
###
|
|
511
|
+
### Issue 2: API Connection Failed
|
|
399
512
|
|
|
400
|
-
|
|
401
|
-
-
|
|
402
|
-
-
|
|
403
|
-
-
|
|
404
|
-
-
|
|
513
|
+
**Solution:**
|
|
514
|
+
- Check if Excel API Server is running: `curl http://localhost:8000/`
|
|
515
|
+
- Verify API URL in credentials is correct
|
|
516
|
+
- Check API Token is correct
|
|
517
|
+
- Check firewall settings
|
|
405
518
|
|
|
406
|
-
###
|
|
519
|
+
### Issue 3: "Parameter Not Found" Error
|
|
407
520
|
|
|
408
|
-
|
|
521
|
+
**Cause:** Incorrect parameter name configuration
|
|
409
522
|
|
|
410
|
-
|
|
411
|
-
-
|
|
412
|
-
- Object Mode
|
|
413
|
-
- Array Mode
|
|
414
|
-
-
|
|
523
|
+
**Solution:**
|
|
524
|
+
- Confirm correct Append Mode is selected (Object or Array)
|
|
525
|
+
- Object Mode: Use `appendValuesObject` parameter
|
|
526
|
+
- Array Mode: Use `appendValuesArray` parameter
|
|
527
|
+
- Check JSON format is correct
|
|
415
528
|
|
|
416
|
-
###
|
|
529
|
+
### Issue 4: "File Lock" Error
|
|
417
530
|
|
|
418
|
-
|
|
531
|
+
**Cause:** Too many concurrent requests or API server issues
|
|
419
532
|
|
|
420
|
-
|
|
421
|
-
-
|
|
422
|
-
-
|
|
423
|
-
-
|
|
533
|
+
**Solution:**
|
|
534
|
+
- Wait a moment and retry
|
|
535
|
+
- Check API server status
|
|
536
|
+
- Restart Excel API Server if necessary
|
|
424
537
|
|
|
425
|
-
## 🔐
|
|
538
|
+
## 🔐 Security
|
|
426
539
|
|
|
427
|
-
###
|
|
540
|
+
### Best Practices
|
|
428
541
|
|
|
429
|
-
1.
|
|
542
|
+
1. **Use Strong API Token**
|
|
430
543
|
```bash
|
|
431
|
-
#
|
|
544
|
+
# Generate secure token
|
|
432
545
|
openssl rand -hex 32
|
|
433
546
|
```
|
|
434
547
|
|
|
435
|
-
2.
|
|
436
|
-
-
|
|
437
|
-
-
|
|
548
|
+
2. **Use HTTPS in Production**
|
|
549
|
+
- Set up reverse proxy (Nginx)
|
|
550
|
+
- Use SSL certificate
|
|
438
551
|
|
|
439
|
-
3.
|
|
440
|
-
-
|
|
441
|
-
-
|
|
552
|
+
3. **Restrict Access**
|
|
553
|
+
- Allow only trusted networks to access API URL
|
|
554
|
+
- Use VPN for remote access
|
|
442
555
|
|
|
443
|
-
4.
|
|
444
|
-
-
|
|
445
|
-
-
|
|
556
|
+
4. **Regular Backups**
|
|
557
|
+
- Set up automatic backups of Excel files
|
|
558
|
+
- Store backups in secure location
|
|
446
559
|
|
|
447
|
-
## 📊
|
|
560
|
+
## 📊 Performance Optimization Tips
|
|
448
561
|
|
|
449
|
-
### 1.
|
|
562
|
+
### 1. Use Batch Operations
|
|
450
563
|
```javascript
|
|
451
|
-
// ❌
|
|
564
|
+
// ❌ Bad: Multiple single operations
|
|
452
565
|
for (item of items) {
|
|
453
566
|
await appendRow(item);
|
|
454
567
|
}
|
|
455
568
|
|
|
456
|
-
// ✅
|
|
569
|
+
// ✅ Good: One batch operation
|
|
457
570
|
await batchOperations(items.map(item => ({
|
|
458
571
|
type: "append",
|
|
459
572
|
values: item.values
|
|
460
573
|
})));
|
|
461
574
|
```
|
|
462
575
|
|
|
463
|
-
### 2.
|
|
576
|
+
### 2. Specify Range When Reading
|
|
464
577
|
```javascript
|
|
465
|
-
// ❌
|
|
578
|
+
// ❌ Bad: Read entire file
|
|
466
579
|
range: ""
|
|
467
580
|
|
|
468
|
-
// ✅
|
|
581
|
+
// ✅ Good: Only read needed range
|
|
469
582
|
range: "A1:D100"
|
|
470
583
|
```
|
|
471
584
|
|
|
472
|
-
### 3.
|
|
473
|
-
-
|
|
474
|
-
-
|
|
475
|
-
-
|
|
585
|
+
### 3. Use Efficient Workflows
|
|
586
|
+
- Combine related operations in one workflow
|
|
587
|
+
- Reduce number of API calls
|
|
588
|
+
- Use caching appropriately
|
|
589
|
+
|
|
590
|
+
## 🆕 Latest Features
|
|
476
591
|
|
|
477
|
-
|
|
592
|
+
### Object Mode
|
|
593
|
+
- ✅ Uses `/api/excel/append_object` API
|
|
594
|
+
- ✅ Automatically reads Excel headers (first row)
|
|
595
|
+
- ✅ Intelligently maps by column names
|
|
596
|
+
- ✅ Ignores unknown columns with warnings in response
|
|
597
|
+
- ✅ No need to remember column order
|
|
478
598
|
|
|
479
|
-
###
|
|
480
|
-
- ✅
|
|
481
|
-
- ✅
|
|
482
|
-
- ✅
|
|
483
|
-
- ✅
|
|
484
|
-
- ✅ 不需要記住欄位順序
|
|
599
|
+
### Advanced Update and Delete
|
|
600
|
+
- ✅ Support operations by row number
|
|
601
|
+
- ✅ Support operations by column value lookup
|
|
602
|
+
- ✅ Can update specific columns without affecting others
|
|
603
|
+
- ✅ Batch processing support with process modes
|
|
485
604
|
|
|
486
|
-
###
|
|
487
|
-
- ✅
|
|
488
|
-
- ✅
|
|
489
|
-
- ✅
|
|
605
|
+
### Lookup Column Selection
|
|
606
|
+
- ✅ Dynamic dropdown selection of Excel headers
|
|
607
|
+
- ✅ Support for Chinese and special character column names
|
|
608
|
+
- ✅ Automatic URL encoding for special characters
|
|
609
|
+
- ✅ Enhanced user experience with visual column selection
|
|
490
610
|
|
|
491
|
-
## 🤝
|
|
611
|
+
## 🤝 Contributing
|
|
492
612
|
|
|
493
|
-
|
|
613
|
+
Contributions are welcome!
|
|
494
614
|
|
|
495
|
-
1. Fork
|
|
496
|
-
2.
|
|
497
|
-
3.
|
|
498
|
-
4.
|
|
499
|
-
5.
|
|
615
|
+
1. Fork this repository
|
|
616
|
+
2. Create your feature branch: `git checkout -b feature/AmazingFeature`
|
|
617
|
+
3. Commit your changes: `git commit -m 'Add some AmazingFeature'`
|
|
618
|
+
4. Push to the branch: `git push origin feature/AmazingFeature`
|
|
619
|
+
5. Open a Pull Request
|
|
500
620
|
|
|
501
|
-
## 📄
|
|
621
|
+
## 📄 License
|
|
502
622
|
|
|
503
|
-
MIT
|
|
623
|
+
MIT License - see [LICENSE](LICENSE) file
|
|
504
624
|
|
|
505
|
-
## 🔗
|
|
625
|
+
## 🔗 Related Projects
|
|
506
626
|
|
|
507
|
-
- [Excel API Server](https://github.com/code4Copilot/excel-api-server) -
|
|
508
|
-
- [n8n](https://github.com/n8n-io/n8n) -
|
|
627
|
+
- [Excel API Server](https://github.com/code4Copilot/excel-api-server) - Backend API server (Required)
|
|
628
|
+
- [n8n](https://github.com/n8n-io/n8n) - Workflow automation tool
|
|
509
629
|
|
|
510
|
-
## 📧
|
|
630
|
+
## 📧 Support
|
|
511
631
|
|
|
512
|
-
- GitHub Issues
|
|
513
|
-
- Email
|
|
514
|
-
- n8n
|
|
632
|
+
- GitHub Issues: [Report Issues](https://github.com/code4Copilot/n8n-nodes-excel-api/issues)
|
|
633
|
+
- Email: hueyan.chen@gmail.com
|
|
634
|
+
- n8n Community: [n8n Forum](https://community.n8n.io)
|
|
515
635
|
|
|
516
|
-
## ⭐ Star
|
|
636
|
+
## ⭐ Star History
|
|
517
637
|
|
|
518
|
-
|
|
638
|
+
If this project helps you, please give it a ⭐!
|
|
519
639
|
|
|
520
640
|
---
|
|
521
641
|
|
|
522
|
-
|
|
642
|
+
**Built with ❤️ for the n8n community**
|
|
@@ -5,6 +5,7 @@ export declare class ExcelApi implements INodeType {
|
|
|
5
5
|
loadOptions: {
|
|
6
6
|
getExcelFiles(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]>;
|
|
7
7
|
getExcelSheets(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]>;
|
|
8
|
+
getColumnNames(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]>;
|
|
8
9
|
};
|
|
9
10
|
};
|
|
10
11
|
execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
|
|
@@ -150,11 +150,15 @@ class ExcelApi {
|
|
|
150
150
|
description: 'Row number to update/delete (1-based, row 1 is header)',
|
|
151
151
|
hint: '⚠️ Row 1 is protected (header row). Data rows start from row 2.',
|
|
152
152
|
},
|
|
153
|
-
// Lookup Column
|
|
153
|
+
// ✨ NEW: Lookup Column - 改為下拉選單
|
|
154
154
|
{
|
|
155
155
|
displayName: 'Lookup Column',
|
|
156
156
|
name: 'lookupColumn',
|
|
157
|
-
type: '
|
|
157
|
+
type: 'options',
|
|
158
|
+
typeOptions: {
|
|
159
|
+
loadOptionsMethod: 'getColumnNames',
|
|
160
|
+
loadOptionsDependsOn: ['fileName', 'sheetName'],
|
|
161
|
+
},
|
|
158
162
|
displayOptions: {
|
|
159
163
|
show: {
|
|
160
164
|
operation: ['update', 'delete'],
|
|
@@ -163,9 +167,8 @@ class ExcelApi {
|
|
|
163
167
|
},
|
|
164
168
|
required: true,
|
|
165
169
|
default: '',
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
hint: 'The first row is treated as headers',
|
|
170
|
+
description: 'Column name to search in (automatically loaded from Excel headers)',
|
|
171
|
+
hint: '💡 The list shows all column names from the first row of your Excel file',
|
|
169
172
|
},
|
|
170
173
|
// Lookup Value (for lookup method)
|
|
171
174
|
{
|
|
@@ -184,6 +187,33 @@ class ExcelApi {
|
|
|
184
187
|
description: 'Value to search for in the lookup column',
|
|
185
188
|
hint: 'Can use expressions like {{ $json.id }}',
|
|
186
189
|
},
|
|
190
|
+
// Process Mode (for lookup method)
|
|
191
|
+
{
|
|
192
|
+
displayName: 'Process Mode',
|
|
193
|
+
name: 'processMode',
|
|
194
|
+
type: 'options',
|
|
195
|
+
displayOptions: {
|
|
196
|
+
show: {
|
|
197
|
+
operation: ['update', 'delete'],
|
|
198
|
+
identifyBy: ['lookup']
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
options: [
|
|
202
|
+
{
|
|
203
|
+
name: 'All Matching Records',
|
|
204
|
+
value: 'all',
|
|
205
|
+
description: 'Process all records that match the lookup condition'
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
name: 'First Match Only',
|
|
209
|
+
value: 'first',
|
|
210
|
+
description: 'Process only the first matching record'
|
|
211
|
+
},
|
|
212
|
+
],
|
|
213
|
+
default: 'all',
|
|
214
|
+
description: 'Choose whether to process all matching records or just the first one',
|
|
215
|
+
hint: '💡 "All Matching Records" will update/delete every row where the lookup column matches the lookup value',
|
|
216
|
+
},
|
|
187
217
|
// Update operation: Values to Set
|
|
188
218
|
{
|
|
189
219
|
displayName: 'Values to Set',
|
|
@@ -274,6 +304,38 @@ class ExcelApi {
|
|
|
274
304
|
return [];
|
|
275
305
|
}
|
|
276
306
|
},
|
|
307
|
+
// ✨ NEW: 獲取欄位名稱(表頭)
|
|
308
|
+
async getColumnNames() {
|
|
309
|
+
const fileName = this.getNodeParameter('fileName');
|
|
310
|
+
const sheetName = this.getNodeParameter('sheetName');
|
|
311
|
+
if (!fileName || !sheetName) {
|
|
312
|
+
return [];
|
|
313
|
+
}
|
|
314
|
+
const credentials = await this.getCredentials('excelApiAuth');
|
|
315
|
+
const apiUrl = credentials.url;
|
|
316
|
+
const apiToken = credentials.token;
|
|
317
|
+
try {
|
|
318
|
+
const response = await this.helpers.request({
|
|
319
|
+
method: 'GET',
|
|
320
|
+
url: `${apiUrl}/api/excel/headers?file=${encodeURIComponent(fileName)}&sheet=${encodeURIComponent(sheetName)}`,
|
|
321
|
+
headers: {
|
|
322
|
+
'Authorization': `Bearer ${apiToken}`,
|
|
323
|
+
},
|
|
324
|
+
json: true,
|
|
325
|
+
});
|
|
326
|
+
if (response.success && response.headers) {
|
|
327
|
+
return response.headers.map((header) => ({
|
|
328
|
+
name: header,
|
|
329
|
+
value: header,
|
|
330
|
+
}));
|
|
331
|
+
}
|
|
332
|
+
return [];
|
|
333
|
+
}
|
|
334
|
+
catch (error) {
|
|
335
|
+
// 如果獲取失敗,返回空列表(用戶可以手動輸入)
|
|
336
|
+
return [];
|
|
337
|
+
}
|
|
338
|
+
},
|
|
277
339
|
},
|
|
278
340
|
};
|
|
279
341
|
}
|
|
@@ -315,7 +377,6 @@ class ExcelApi {
|
|
|
315
377
|
// Use different API endpoint based on append mode
|
|
316
378
|
if (appendMode === 'object') {
|
|
317
379
|
// Object Mode: Use append_object API
|
|
318
|
-
// This API automatically reads headers and maps values by column names
|
|
319
380
|
responseData = await this.helpers.request({
|
|
320
381
|
method: 'POST',
|
|
321
382
|
url: `${apiUrl}/api/excel/append_object`,
|
|
@@ -415,8 +476,10 @@ class ExcelApi {
|
|
|
415
476
|
else if (identifyBy === 'lookup') {
|
|
416
477
|
const lookupColumn = this.getNodeParameter('lookupColumn', i);
|
|
417
478
|
const lookupValue = this.getNodeParameter('lookupValue', i);
|
|
479
|
+
const processMode = this.getNodeParameter('processMode', i);
|
|
418
480
|
requestBody.lookup_column = lookupColumn;
|
|
419
481
|
requestBody.lookup_value = lookupValue;
|
|
482
|
+
requestBody.process_all = (processMode === 'all');
|
|
420
483
|
}
|
|
421
484
|
responseData = await this.helpers.request({
|
|
422
485
|
method: 'PUT',
|
|
@@ -428,6 +491,12 @@ class ExcelApi {
|
|
|
428
491
|
body: requestBody,
|
|
429
492
|
json: true,
|
|
430
493
|
});
|
|
494
|
+
// Check if any rows were affected
|
|
495
|
+
if (responseData.success && responseData.updated_count === 0) {
|
|
496
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), identifyBy === 'lookup'
|
|
497
|
+
? `No matching rows found. Lookup column: "${requestBody.lookup_column}", Lookup value: "${requestBody.lookup_value}"`
|
|
498
|
+
: `Row ${requestBody.row} not found or is protected (header row cannot be updated)`);
|
|
499
|
+
}
|
|
431
500
|
}
|
|
432
501
|
else if (operation === 'delete') {
|
|
433
502
|
// Get identification method
|
|
@@ -444,8 +513,10 @@ class ExcelApi {
|
|
|
444
513
|
else if (identifyBy === 'lookup') {
|
|
445
514
|
const lookupColumn = this.getNodeParameter('lookupColumn', i);
|
|
446
515
|
const lookupValue = this.getNodeParameter('lookupValue', i);
|
|
516
|
+
const processMode = this.getNodeParameter('processMode', i);
|
|
447
517
|
requestBody.lookup_column = lookupColumn;
|
|
448
518
|
requestBody.lookup_value = lookupValue;
|
|
519
|
+
requestBody.process_all = (processMode === 'all');
|
|
449
520
|
}
|
|
450
521
|
responseData = await this.helpers.request({
|
|
451
522
|
method: 'DELETE',
|
|
@@ -457,6 +528,12 @@ class ExcelApi {
|
|
|
457
528
|
body: requestBody,
|
|
458
529
|
json: true,
|
|
459
530
|
});
|
|
531
|
+
// Check if any rows were affected
|
|
532
|
+
if (responseData.success && responseData.deleted_count === 0) {
|
|
533
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), identifyBy === 'lookup'
|
|
534
|
+
? `No matching rows found. Lookup column: "${requestBody.lookup_column}", Lookup value: "${requestBody.lookup_value}"`
|
|
535
|
+
: `Row ${requestBody.row} not found or is protected (header row cannot be deleted)`);
|
|
536
|
+
}
|
|
460
537
|
}
|
|
461
538
|
else if (operation === 'batch') {
|
|
462
539
|
const batchOperationsRaw = this.getNodeParameter('batchOperations', i);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "n8n-nodes-excel-api",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "n8n node for accessing Excel files via API with concurrent safety",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"n8n-community-node-package",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"@typescript-eslint/parser": "^5.0.0",
|
|
50
50
|
"eslint": "^8.57.1",
|
|
51
51
|
"eslint-plugin-n8n-nodes-base": "^1.0.0",
|
|
52
|
-
"gulp": "^
|
|
52
|
+
"gulp": "^5.0.1",
|
|
53
53
|
"jest": "^29.5.0",
|
|
54
54
|
"n8n-workflow": "*",
|
|
55
55
|
"prettier": "^2.7.1",
|
|
@@ -59,4 +59,4 @@
|
|
|
59
59
|
"peerDependencies": {
|
|
60
60
|
"n8n-workflow": "*"
|
|
61
61
|
}
|
|
62
|
-
}
|
|
62
|
+
}
|