8
n8n 中文网amn8n.com

使用Redis项目状态跟踪防止重复处理

高级

这是一个Engineering领域的自动化工作流,包含 21 个节点。主要使用 Set, Code, Redis, Switch, GoogleSheets 等节点。 使用Redis项目状态跟踪防止重复处理

前置要求
  • Redis 服务器连接信息
  • Google Sheets API 凭证

分类

工作流预览
可视化展示节点连接关系,支持缩放和平移
导出工作流
复制以下 JSON 配置到 n8n 导入,即可使用此工作流
{
  "meta": {
    "instanceId": "a8c64a6cf7dcfa4f9b9bae070111f579dc10d8d5f08bd580db0503f385c3ed87",
    "templateCredsSetupCompleted": true
  },
  "nodes": [
    {
      "id": "041eb17b-fff8-4372-bbc7-b61d447ad7dd",
      "name": "Handle Invalid Action",
      "type": "n8n-nodes-base.stopAndError",
      "position": [
        -240,
        800
      ],
      "parameters": {
        "errorMessage": "Invalid 'action' provided. Expected 'add' or 'check'."
      },
      "typeVersion": 1
    },
    {
      "id": "017dfe79-569a-4e7b-9c32-7a75e67c2194",
      "name": "Set CHECK Output",
      "type": "n8n-nodes-base.set",
      "position": [
        -20,
        0
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "eac27f12-6ac3-412e-ba13-170a1e928a51",
              "name": "success",
              "type": "boolean",
              "value": true
            },
            {
              "id": "e3610868-e785-4ac7-b247-e1a9b7be9624",
              "name": "isMember",
              "type": "string",
              "value": "={{ $json.retrievedValue !== null && $json.retrievedValue !== '' }}"
            },
            {
              "id": "a693fc18-5d2d-4be4-a434-0cb87b1ce5c8",
              "name": "itemId",
              "type": "string",
              "value": "={{ $('Switch Action').item.json.itemId }}"
            }
          ]
        },
        "includeOtherFields": true
      },
      "typeVersion": 3.4
    },
    {
      "id": "70d42586-70b3-46dc-ae9b-4e53daa0928d",
      "name": "Set ADD Output",
      "type": "n8n-nodes-base.set",
      "position": [
        -20,
        200
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "bed29ea4-0a13-4b64-ac75-dea65cd24fa0",
              "name": "success",
              "type": "boolean",
              "value": true
            }
          ]
        },
        "includeOtherFields": true
      },
      "typeVersion": 3.4
    },
    {
      "id": "4ecfa499-ac8a-42f7-9587-492f559ced09",
      "name": "Redis: GET (Check Processed)",
      "type": "n8n-nodes-base.redis",
      "position": [
        -240,
        0
      ],
      "parameters": {
        "key": "={{ $json.fullKey }}",
        "options": {},
        "operation": "get",
        "propertyName": "retrievedValue"
      },
      "credentials": {
        "redis": {
          "id": "DTdIQIBsjIgfBKFZ",
          "name": "Redis_"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "caa7bbc9-32e7-4206-a316-22a16fba9d15",
      "name": "Redis: SET (Add to Processed)",
      "type": "n8n-nodes-base.redis",
      "position": [
        -240,
        200
      ],
      "parameters": {
        "key": "={{ $json.fullKey }}",
        "ttl": "={{ $json.TTL }}",
        "value": "={{ JSON.stringify({ status: 'in_progress', startedAt: new Date().toISOString(), parentExecution: $json.parentExecution }) }}\"",
        "expire": true,
        "operation": "set"
      },
      "credentials": {
        "redis": {
          "id": "DTdIQIBsjIgfBKFZ",
          "name": "Redis_"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "905064c5-1698-467f-99ca-c5385a95aab6",
      "name": "Switch Action",
      "type": "n8n-nodes-base.switch",
      "position": [
        -480,
        360
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "outputKey": "check",
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "0e924b2c-0a59-42ff-961f-3c590a874020",
                    "operator": {
                      "name": "filter.operator.equals",
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.action }}",
                    "rightValue": "check"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "add",
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "05cd7073-69db-4b6a-864c-7b118e811964",
                    "operator": {
                      "name": "filter.operator.equals",
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.action }}",
                    "rightValue": "add"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "delete",
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "0cb8065c-90bf-402e-a6c0-52f4a705b53e",
                    "operator": {
                      "name": "filter.operator.equals",
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.action }}",
                    "rightValue": "delete"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "update",
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "3e99796c-4445-406c-ac81-79d019e76108",
                    "operator": {
                      "name": "filter.operator.equals",
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.action }}",
                    "rightValue": "update"
                  }
                ]
              },
              "renameOutput": true
            }
          ]
        },
        "options": {
          "fallbackOutput": "extra",
          "renameFallbackOutput": "invalid_action"
        }
      },
      "typeVersion": 3.2
    },
    {
      "id": "dc7eb8e9-5c19-482b-aaa4-737a5ab3a272",
      "name": "Trigger (from other workflows)",
      "type": "n8n-nodes-base.executeWorkflowTrigger",
      "position": [
        -900,
        400
      ],
      "parameters": {
        "workflowInputs": {
          "values": [
            {
              "name": "keyPrefix"
            },
            {
              "name": "itemId"
            },
            {
              "name": "action"
            },
            {
              "name": "TTL",
              "type": "number"
            },
            {
              "name": "parentExecution",
              "type": "object"
            },
            {
              "name": "keySuffix"
            },
            {
              "name": "time",
              "type": "array"
            },
            {
              "name": "updateValue",
              "type": "any"
            }
          ]
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "e7d86a22-5474-452d-8990-04d322487ff4",
      "name": "Redis: DELETE (Untrack Item)",
      "type": "n8n-nodes-base.redis",
      "position": [
        -240,
        400
      ],
      "parameters": {
        "key": "={{ $json.fullKey }}",
        "operation": "delete"
      },
      "credentials": {
        "redis": {
          "id": "DTdIQIBsjIgfBKFZ",
          "name": "Redis_"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "1044073e-1493-4bc8-b2fa-afdd2cae7d2d",
      "name": "To_redis-audit-log",
      "type": "n8n-nodes-base.redis",
      "position": [
        720,
        300
      ],
      "parameters": {
        "channel": "redis-audit-log",
        "operation": "publish",
        "messageData": "={{ $json.logMessage }}"
      },
      "credentials": {
        "redis": {
          "id": "DTdIQIBsjIgfBKFZ",
          "name": "Redis_"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "01bd9ded-d831-4d51-ae31-bd2ae15706c3",
      "name": "Set DELETE Output",
      "type": "n8n-nodes-base.set",
      "position": [
        -20,
        400
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "b0d29ea4-0a13-4b64-ac75-dea65cd24fa0",
              "name": "success",
              "type": "boolean",
              "value": true
            }
          ]
        },
        "includeOtherFields": true
      },
      "typeVersion": 3.4
    },
    {
      "id": "0e94717d-656d-4ccc-820c-96b43305116b",
      "name": "Code: Prepare Audit Log & Response",
      "type": "n8n-nodes-base.code",
      "position": [
        320,
        300
      ],
      "parameters": {
        "jsCode": "// Get the consistent context from the start of the workflow.\nconst context = $('Set: Context for Response').item.json;\n\n// Get the result data from the node that just ran (e.g., Set CHECK Output, Set ADD Output, Set UPDATE Output, Set DELETE Output)\nconst resultData = $input.item.json;\n\n// Build the base log payload\nconst logData = {\n  timestamp: new Date().toISOString(),\n  utilityName: 'ItemTracker',\n  action: context.action,\n  keyPrefix: context.keyPrefix,\n  itemId: context.itemId,\n  fullKey: context.fullKey,\n  ttl_seconds: context.TTL || null,\n  status: 'Success',\n  parentExecution: context.parentExecution || null // Keep the full parentExecution object as is for comprehensive logging\n};\n\n// Add the specific result of the 'check' action to the log\nif (logData.action === 'check') {\n  logData.result = {\n    isMember: resultData.isMember\n  };\n}\n\n// Determine status and add action-specific results for logging\nlet status = 'Success'; // Default status for logging\n\nif (context.action === 'check') {\n  status = resultData.isMember ? 'Item_Found' : 'Item_Not_Found';\n  // Result for 'check' is already set above\n} else if (context.action === 'add') {\n  status = 'In_Progress_Marked';\n} else if (context.action === 'update') {\n  status = resultData.status; // Get status from the updated value (e.g., 'completed_success', 'failed')\n  if (resultData.errorMessage) {\n    logData.error = { message: resultData.errorMessage, details: resultData.errorDetails };\n  }\n} else if (context.action === 'delete') {\n  status = 'Tracking_Removed';\n}\n\nlogData.status = status; // Set the determined status for the log\n\n// --- START: FINAL Parent Workflow Execution URL Construction (Troubleshoot Link) ---\nconst parentExecContext = context.parentExecution || {};\nlet troubleshootExecutionUrl = null;\n\n// The most direct link to the execution details page\nif (parentExecContext.workflowId && parentExecContext.id) {\n    troubleshootExecutionUrl = `https://centralise.app.n8n.cloud/workflow/${parentExecContext.workflowId}/executions/${parentExecContext.id}`;\n} else if (parentExecContext.url && parentExecContext.id) {\n    // Fallback if workflowId isn't separated, try to construct from base URL and ID\n    const baseUrl = parentExecContext.url.split('/workflow/')[0]; // Extract base URL if 'url' contains workflow path\n    troubleshootExecutionUrl = `${baseUrl}/workflow/${parentExecContext.workflowId || 'UNKNOWN_WF_ID'}/executions/${parentExecContext.id}`;\n}\n// Note: parentExecContext.resumeUrl is explicitly NOT used here as the primary \"troubleshoot\" link\n// as it's for resuming, not for viewing execution details.\n\nif (troubleshootExecutionUrl) {\n    logData.troubleshootExecutionUrl = troubleshootExecutionUrl; // Add to the log message\n}\n// --- END: FINAL Parent Workflow Execution URL Construction ---\n\n\n// Explicitly construct the final output object to ensure correctness.\nconst finalOutput = {\n  success: true, // Always true if the utility workflow completes without crashing\n  actionPerformed: context.action,\n  itemId: context.itemId,\n  isMember: resultData.isMember, // Only relevant for 'check' action\n  \n  // For 'update' action, pass relevant status/error back:\n  status: context.action === 'update' ? resultData.status : undefined,\n  errorMessage: context.action === 'update' ? resultData.errorMessage : undefined,\n  errorDetails: context.action === 'update' ? resultData.errorDetails : undefined,\n\n  // Add the direct troubleshoot URL to the final output\n  troubleshootExecutionUrl: troubleshootExecutionUrl // <-- FINAL, CORRECT FIELD FOR OUTPUT\n};\n\n// Add the log message to the final output object\nfinalOutput.logMessage = JSON.stringify(logData);\n\n// Return the final, complete object\nreturn [{\n  json: finalOutput\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "89efb8f5-1b74-4511-8f60-c9e6e9bdf6f3",
      "name": "Set: Context for Response",
      "type": "n8n-nodes-base.set",
      "position": [
        -680,
        400
      ],
      "parameters": {
        "options": {
          "ignoreConversionErrors": true
        },
        "assignments": {
          "assignments": [
            {
              "id": "d31bb33b-aa9c-4da3-95a1-265aff674c6d",
              "name": "fullKey",
              "type": "string",
              "value": "={{ $json.keyPrefix }}:{{ $json.itemId }}:{{ $json.keySuffix }}"
            },
            {
              "id": "dc6f0043-65d6-4072-805c-05cdc95cd80b",
              "name": "itemId",
              "type": "string",
              "value": "={{ $json.itemId }}"
            },
            {
              "id": "2109dab8-d745-4b7f-8326-7ab9d468270a",
              "name": "TTL",
              "type": "string",
              "value": "={{ $json.TTL || 1250000 }}"
            },
            {
              "id": "fc022e1b-54cc-4f69-b3ea-5c517a3bba78",
              "name": "time",
              "type": "array",
              "value": "={{ $json.time || null }}"
            }
          ]
        },
        "includeOtherFields": true
      },
      "typeVersion": 3.4
    },
    {
      "id": "ba9d5387-4868-4c36-8be5-2598c62fe1bc",
      "name": "Redis: SET (Update Result)2",
      "type": "n8n-nodes-base.redis",
      "position": [
        -240,
        600
      ],
      "parameters": {
        "key": "={{ $json.fullKey }}",
        "ttl": "={{ $json.TTL }}",
        "value": "={{ JSON.stringify($json.updateValue) }}",
        "expire": true,
        "operation": "set"
      },
      "credentials": {
        "redis": {
          "id": "DTdIQIBsjIgfBKFZ",
          "name": "Redis_"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "c8718a75-c54a-4712-81a8-5150d67ef1a5",
      "name": "Set UPDATE Output2",
      "type": "n8n-nodes-base.set",
      "position": [
        -20,
        600
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "update_success",
              "name": "success",
              "type": "boolean",
              "value": true
            },
            {
              "id": "update_status",
              "name": "status",
              "type": "string",
              "value": "={{ $json.updateValue.status }}"
            },
            {
              "id": "update_errorMessage",
              "name": "errorMessage",
              "type": "string",
              "value": "={{ $json.updateValue.errorMessage || null }}"
            },
            {
              "id": "update_errorDetails",
              "name": "errorDetails",
              "type": "json",
              "value": "={{ $json.updateValue.errorDetails || null }}"
            }
          ]
        },
        "includeOtherFields": true
      },
      "typeVersion": 3.4
    },
    {
      "id": "269f2897-2919-462f-9eca-562a16f9cf9b",
      "name": "Parse Log Message",
      "type": "n8n-nodes-base.code",
      "position": [
        860,
        1700
      ],
      "parameters": {
        "jsCode": "// The message from Redis Pub/Sub is a string.\n// This parses it back into a usable JSON object.\n\nconst logData = JSON.parse($json.message);\n\nreturn logData;"
      },
      "typeVersion": 2
    },
    {
      "id": "d93cee23-3274-4105-be43-d7972c79da87",
      "name": "redis-audit-log",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1100,
        1700
      ],
      "parameters": {
        "columns": {
          "value": {},
          "schema": [
            {
              "id": "timestamp",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "timestamp",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "utilityName",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "utilityName",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "action",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "action",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "keyPrefix",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "keyPrefix",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "itemId",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "itemId",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "fullKey",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "fullKey",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "ttl_seconds",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "ttl_seconds",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "status",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "parentExecution",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "parentExecution",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "result",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "result",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "workflowName",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "workflowName",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "WorkflowId",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "WorkflowId",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "url",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "url",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "autoMapInputData",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/10wfQiHgxabjELTgaTKYN3iTInam2ipJG7ZdheRvD2lk/edit#gid=0",
          "cachedResultName": "Sheet1"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "10wfQiHgxabjELTgaTKYN3iTInam2ipJG7ZdheRvD2lk",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/10wfQiHgxabjELTgaTKYN3iTInam2ipJG7ZdheRvD2lk/edit?usp=drivesdk",
          "cachedResultName": "redis-audit-log"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "hflsBesfa3NNf1aS",
          "name": "Google Sheets account"
        }
      },
      "typeVersion": 4.1
    },
    {
      "id": "e58189c4-13f2-4423-ad63-42afb16e1d98",
      "name": "On new Redis event",
      "type": "n8n-nodes-base.redisTrigger",
      "position": [
        620,
        1700
      ],
      "parameters": {
        "options": {},
        "channels": "redis-audit-log"
      },
      "credentials": {
        "redis": {
          "id": "DTdIQIBsjIgfBKFZ",
          "name": "Redis_"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "dadf7eab-024e-4a3a-aa5b-18867ff72eac",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1680,
        -480
      ],
      "parameters": {
        "width": 540,
        "height": 2280,
        "content": "## No more messy loops with the Redis Item Tracker Utility \n\nThis workflow serves as a robust, centralized utility to prevent duplicate processing of items and manage their state in Redis. It's designed to be called by other workflows, providing a reliable mechanism to **check if an item has been handled, mark it as in-progress, or update its final status.** A key new feature is its ability to automatically generate a direct link to the parent workflow's execution for rapid troubleshooting.\n\n## How it works\n\n* **Receives Requests:** The workflow is triggered by an API call from another workflow, receiving an `itemId`, the desired `action` (`check`, `add`, `delete`, `update`), and an optional parent execution context.\n* **Enforces Idempotency:** It uses a Switch node to route the request to the correct Redis operation. A `check` action prevents double processing, while `add` marks an item as in-progress.\n* **Generates Audit Logs:** It meticulously prepares a detailed audit log entry for each operation. This log is then published to a Redis channel for centralized monitoring.\n* **Provides Traceability:** A direct, clickable URL to the parent workflow's execution is automatically generated and included in the output, drastically reducing the time it takes to trace an event back to its source for troubleshooting.\n\n\n## Setup Steps\n\nDeploy this workflow as a callable sub-workflow within your n8n instance.\n1.  **Import Workflow:** Import the provided JSON into your n8n instance.\n2.  **Configure Redis Credentials:** Ensure your Redis credentials are set up and linked to the Redis nodes in the workflow.\n3.  **Integrate with Parent Workflows:** Call this workflow from your main processes, passing the required `action`, `itemId`, `keyPrefix`, and, crucially, the `parentExecution` object to enable the traceability feature.\n\n\n## Requirements\n\n* **n8n Instance:** A running n8n instance with a direct URL that is accessible by your workflows.\n* **Redis Database:** Access to a Redis database for storing item states and audit logs.\n* **Redis Credentials:** Configured Redis credentials within n8n.\n\n---\n\n## How to Use\n\nThis workflow is designed to be called by an \"Execute Workflow\" node in your primary workflows. A typical, robust pattern involves a three-step process:\n\n* **Initial Check:** Before starting a resource-intensive process, call this utility with `action: 'check'` to see if the item is already being processed. Use the output's `isMember` property to conditionally proceed.\n* **Set an In-Progress Lock:** If the item is not a member, immediately call the utility with `action: 'add'` and a short TTL (e.g., 360 seconds). This creates a temporary lock that prevents other instances from processing it. The key should follow the pattern `n8n:{{workflow_name}}:{{item_id}}:in_progress`.\n* **Final Status Update:** After the main processing is complete, call the utility with `action: 'update'`. Set the `keySuffix` to a final state like `completed` and a longer TTL (e.g., 7200 seconds) or `error` with a very long TTL. This removes the `in_progress` lock and flags the item's final status, preventing the workflow from re-processing it in the future.\n\n\n### Standard Key Patterns\n\nYour keys should follow a consistent format:\n`{{keyPrefix}}:{{itemId}}:{{keySuffix}}`\n\n* **`keyPrefix`**: The type of data or the n8n workflow that owns the key (e.g., `n8n:hubspot_engagements`).\n* **`itemId`**: The unique identifier of the business object (e.g., a HubSpot object ID, `198482226407`).\n* **`keySuffix`**: The status or flag of the item (e.g., `in_progress`, `completed`).\n\nBased on a standard implementation, here are the patterns and their purpose:\n\n1.  **In-Progress Lock:**\n    * **Full Key Pattern:** `n8n:hubspot_engagements:{{item_id}}:in_progress`\n    * **Purpose:** A short-lived lock to prevent duplicate processing.\n    * **TTL:** A short duration (e.g., 5-10 minutes) that's long enough for the process to complete but short enough to clear automatically if the workflow fails.\n2.  **Completed Flag:**\n    * **Full Key Pattern:** `n8n:hubspot_engagements:{{item_id}}:completed`\n    * **Purpose:** A flag to track successful processing for a defined period.\n    * **TTL:** A moderate duration (e.g., 2 hours as used in your implementation) that gives time for downstream systems to catch up or for manual checks before the key expires. This is a crucial setting to customize for your specific needs.\n\n3.  **Error Flag:**\n    * **Full Key Pattern:** `n8n:hubspot_engagements:{{item_id}}:error`\n    * **Purpose:** A long-lived flag to track permanent failures for troubleshooting.\n    * **TTL:** Very long (e.g., 180 days)."
      },
      "typeVersion": 1
    },
    {
      "id": "c3d1ea3e-b37c-47a3-801b-20e5c9ef43b4",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        600,
        740
      ],
      "parameters": {
        "color": 6,
        "width": 520,
        "height": 860,
        "content": "## Audit Log Listener & Recorder\n---\n\n\nThis workflow acts as the central monitoring system for your Item Tracker Utility. Its sole purpose is to create a permanent, easy-to-read record of every single action performed by the utility. While the main Item Tracker is busy doing its job of preventing duplicates, this workflow is silently listening for each event and writing it down.\n\n\n### **How it works**\n\nThis is a background process that runs automatically and continuously.\n* **It Listens for Events:** The workflow is triggered every time the Item Tracker utility publishes a new log message to a specific channel in Redis (named `redis-audit-log`).\n* **It Processes the Message:** The incoming message is a simple text string. The workflow's first step is to instantly convert this string back into a structured data format, making it ready to be saved.\n* **It Records Everything:** Finally, it takes the processed data and adds it as a new row in a designated Google Sheet. Each action, along with its timestamp and item ID, gets its own record, creating a full historical log of your system's behavior. This turns the invisible work of your automation into a visible, searchable history.\n\n\n### **Requirements & Dependencies**\n\nThis workflow is a companion to the main Item Tracker Utility and needs a few things to function correctly.\n* **Redis Connection:** The workflow needs access to the same Redis database as your Item Tracker.\n* **Google Sheets Account:** You must have a Google Sheets account and a pre-configured spreadsheet with column headers that match the Item Tracker's log output (e.g., `timestamp`, `action`, `itemId`, etc.).\n* **Google Sheets Credentials:** Your n8n instance must have Google Sheets credentials configured and linked to the node in this workflow."
      },
      "typeVersion": 1
    },
    {
      "id": "dd89e7c3-cc60-4c39-9d7c-97100f03b501",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        580,
        180
      ],
      "parameters": {
        "color": 6,
        "width": 420,
        "height": 320,
        "content": "### Audit Log Listener & Recorder\n\nuse this optional node to log workflow executions  \n\n\n"
      },
      "typeVersion": 1
    },
    {
      "id": "e60564db-1c52-4e2f-a1bf-ea381611f580",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1040,
        1140
      ],
      "parameters": {
        "color": 7,
        "width": 500,
        "height": 660,
        "content": "### The Item Tracker Playbook: Key Concepts at a Glance\n\nThis guide provides a quick reference for the core components and actions of the Redis Item Tracker.\n\n**Key:** `keyPrefix:itemId:keySuffix`\n\n**Part: `keyPrefix`**\n* **Role:** Group related items\n* **Example:** `n8n:hubspot_engagements`\n\n**Part: `itemId`**\n* **Role:** Unique ID per item\n* **Example:** `hs_object_id`\n\n**Part: `keySuffix`**\n* **Role:** State of the item\n* **Example:** `in_progress`\n\n**Action: `check`**\n* **Role:** Read: \"Is this state active?\"\n* **Example:** Use before processing a new item.\n\n**Action: `add`**\n* **Role:** Write: \"Set a temporary lock\"\n* **Example:** Use at the start of a task.\n\n**Action: `update`**\n* **Role:** Write: \"Record final state\"\n* **Example:** Use at the end of a process."
      },
      "typeVersion": 1
    }
  ],
  "pinData": {},
  "connections": {
    "Switch Action": {
      "main": [
        [
          {
            "node": "Redis: GET (Check Processed)",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Redis: SET (Add to Processed)",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Redis: DELETE (Untrack Item)",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Redis: SET (Update Result)2",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Handle Invalid Action",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set ADD Output": {
      "main": [
        [
          {
            "node": "Code: Prepare Audit Log & Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set CHECK Output": {
      "main": [
        [
          {
            "node": "Code: Prepare Audit Log & Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Log Message": {
      "main": [
        [
          {
            "node": "redis-audit-log",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set DELETE Output": {
      "main": [
        [
          {
            "node": "Code: Prepare Audit Log & Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "On new Redis event": {
      "main": [
        [
          {
            "node": "Parse Log Message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set UPDATE Output2": {
      "main": [
        [
          {
            "node": "Code: Prepare Audit Log & Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set: Context for Response": {
      "main": [
        [
          {
            "node": "Switch Action",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Redis: SET (Update Result)2": {
      "main": [
        [
          {
            "node": "Set UPDATE Output2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Redis: DELETE (Untrack Item)": {
      "main": [
        [
          {
            "node": "Set DELETE Output",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Redis: GET (Check Processed)": {
      "main": [
        [
          {
            "node": "Set CHECK Output",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Redis: SET (Add to Processed)": {
      "main": [
        [
          {
            "node": "Set ADD Output",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Trigger (from other workflows)": {
      "main": [
        [
          {
            "node": "Set: Context for Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code: Prepare Audit Log & Response": {
      "main": [
        [
          {
            "node": "To_redis-audit-log",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
常见问题

如何使用这个工作流?

复制上方的 JSON 配置代码,在您的 n8n 实例中创建新工作流并选择「从 JSON 导入」,粘贴配置后根据需要修改凭证设置即可。

这个工作流适合什么场景?

高级 - 工程

需要付费吗?

本工作流完全免费,您可以直接导入使用。但请注意,工作流中使用的第三方服务(如 OpenAI API)可能需要您自行付费。

工作流信息
难度等级
高级
节点数量21
分类1
节点类型9
难度说明

适合高级用户,包含 16+ 个节点的复杂工作流

作者
Stephan Koning

Stephan Koning

@reklaim

Account Executive by day , Noco builder for fun at night and always a proud dad of Togo the Samoyed.

外部链接
在 n8n.io 查看

分享此工作流