整理文档及修改配置
This commit is contained in:
@@ -1,8 +1,6 @@
|
||||
{
|
||||
"SUPA_URL": "http://192.168.1.62:18000",
|
||||
"SUPA_KEY": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoic2VydmljZV9yb2xlIiwiaXNzIjoic3VwYWJhc2UtMSIsImlhdCI6MTc2OTY3NjQ5OCwiZXhwIjoxOTI3MzU2NDk4fQ.ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890",
|
||||
"SUPA_USE_BEARER": "false",
|
||||
"NOTIFY_WORKER_SUPA_USE_BEARER": "true",
|
||||
"SUPA_KEY": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoic2VydmljZV9yb2xlIiwiaXNzIjoic3VwYWJhc2UiLCJpYXQiOjE3NzM2MjExNDMsImV4cCI6MTkzMTMwMTE0M30.zahVzrUCUkB436SHYyM2muGL3Lg_aocJJgWv1t6PpKg",
|
||||
"CLOUD_FUNC_URL": "https://env-00jy5x5oy9zd.dev-hz.cloudbasefunction.cn/test",
|
||||
"ENABLE_CONSUMER": "true",
|
||||
"CONSUMER_POLL_MS": "2000",
|
||||
|
||||
@@ -51,6 +51,12 @@ function supaFetch(restPath, opts = {}, useBearer = false) {
|
||||
Accept: 'application/json'
|
||||
})
|
||||
if (useBearer) headers.Authorization = `Bearer ${SUPA_KEY}`
|
||||
|
||||
if (process.env.DEBUG_SUPA) {
|
||||
console.log(`[DEBUG] supaFetch: ${opts.method || 'GET'} ${url}`)
|
||||
console.log(`[DEBUG] Headers: apikey=${SUPA_KEY ? SUPA_KEY.substring(0, 10) : 'MISSING'}... Authorization=${headers.Authorization ? 'PRESENT' : 'NONE'}`)
|
||||
}
|
||||
|
||||
return fetchImpl(url, Object.assign({}, opts, { headers }))
|
||||
}
|
||||
|
||||
|
||||
491
server/package-lock.json
generated
491
server/package-lock.json
generated
@@ -8,12 +8,16 @@
|
||||
"name": "mall-push-server",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@supabase/supabase-js": "^2.0.0",
|
||||
"archiver": "^7.0.1",
|
||||
"body-parser": "^1.20.2",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.18.2",
|
||||
"form-data": "^4.0.5",
|
||||
"node-fetch": "^2.7.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@isaacs/cliui": {
|
||||
@@ -43,6 +47,110 @@
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@supabase/auth-js": {
|
||||
"version": "2.99.1",
|
||||
"resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.99.1.tgz",
|
||||
"integrity": "sha512-x7lKKTvKjABJt/FYcRSPiTT01Xhm2FF8RhfL8+RHMkmlwmRQ88/lREupIHKwFPW0W6pTCJqkZb7Yhpw/EZ+fNw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "2.8.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@supabase/functions-js": {
|
||||
"version": "2.99.1",
|
||||
"resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.99.1.tgz",
|
||||
"integrity": "sha512-WQE62W5geYImCO4jzFxCk/avnK7JmOdtqu2eiPz3zOaNiIJajNRSAwMMDgEGd2EMs+sUVYj1LfBjfmW3EzHgIA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "2.8.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@supabase/postgrest-js": {
|
||||
"version": "2.99.1",
|
||||
"resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.99.1.tgz",
|
||||
"integrity": "sha512-gtw2ibJrADvfqrpUWXGNlrYUvxttF4WVWfPpTFKOb2IRj7B6YRWMDgcrYqIuD4ZEabK4m6YKQCCGy6clgf1lPA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "2.8.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@supabase/realtime-js": {
|
||||
"version": "2.99.1",
|
||||
"resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.99.1.tgz",
|
||||
"integrity": "sha512-9EDdy/5wOseGFqxW88ShV9JMRhm7f+9JGY5x+LqT8c7R0X1CTLwg5qie8FiBWcXTZ+68yYxVWunI+7W4FhkWOg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/phoenix": "^1.6.6",
|
||||
"@types/ws": "^8.18.1",
|
||||
"tslib": "2.8.1",
|
||||
"ws": "^8.18.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@supabase/storage-js": {
|
||||
"version": "2.99.1",
|
||||
"resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.99.1.tgz",
|
||||
"integrity": "sha512-mf7zPfqofI62SOoyQJeNUVxe72E4rQsbWim6lTDPeLu3lHija/cP5utlQADGrjeTgOUN6znx/rWn7SjrETP1dw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"iceberg-js": "^0.8.1",
|
||||
"tslib": "2.8.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@supabase/supabase-js": {
|
||||
"version": "2.99.1",
|
||||
"resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.99.1.tgz",
|
||||
"integrity": "sha512-5MRoYD9ffXq8F6a036dm65YoSHisC3by/d22mauKE99Vrwf792KxYIIr/iqCX7E4hkuugbPZ5EGYHTB7MKy6Vg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@supabase/auth-js": "2.99.1",
|
||||
"@supabase/functions-js": "2.99.1",
|
||||
"@supabase/postgrest-js": "2.99.1",
|
||||
"@supabase/realtime-js": "2.99.1",
|
||||
"@supabase/storage-js": "2.99.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "25.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz",
|
||||
"integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~7.18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/phoenix": {
|
||||
"version": "1.6.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.7.tgz",
|
||||
"integrity": "sha512-oN9ive//QSBkf19rfDv45M7eZPi0eEXylht2OLEXicu5b4KoQ1OzXIw+xDSGWxSxe1JmepRR/ZH283vsu518/Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/ws": {
|
||||
"version": "8.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
|
||||
"integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/abort-controller": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
|
||||
@@ -92,6 +200,20 @@
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/anymatch": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
||||
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"normalize-path": "^3.0.0",
|
||||
"picomatch": "^2.0.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/archiver": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz",
|
||||
@@ -203,6 +325,19 @@
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/binary-extensions": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
|
||||
"integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/body-parser": {
|
||||
"version": "1.20.4",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz",
|
||||
@@ -239,6 +374,19 @@
|
||||
"node": "18 || 20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/braces": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
||||
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fill-range": "^7.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
|
||||
@@ -310,6 +458,31 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/chokidar": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
||||
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"anymatch": "~3.1.2",
|
||||
"braces": "~3.0.2",
|
||||
"glob-parent": "~5.1.2",
|
||||
"is-binary-path": "~2.1.0",
|
||||
"is-glob": "~4.0.1",
|
||||
"normalize-path": "~3.0.0",
|
||||
"readdirp": "~3.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8.10.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
@@ -671,6 +844,19 @@
|
||||
"integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"to-regex-range": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/finalhandler": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz",
|
||||
@@ -739,6 +925,21 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
@@ -806,6 +1007,19 @@
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/glob-parent": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
||||
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"is-glob": "^4.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||
@@ -824,6 +1038,16 @@
|
||||
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/has-flag": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
@@ -883,6 +1107,15 @@
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/iceberg-js": {
|
||||
"version": "0.8.1",
|
||||
"resolved": "https://registry.npmjs.org/iceberg-js/-/iceberg-js-0.8.1.tgz",
|
||||
"integrity": "sha512-1dhVQZXhcHje7798IVM+xoo/1ZdVfzOMIc8/rgVSijRK38EDqOJoGula9N/8ZI5RD8QTxNQtK/Gozpr+qUqRRA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.4.24",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||
@@ -915,6 +1148,13 @@
|
||||
],
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/ignore-by-default": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
|
||||
"integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
@@ -930,6 +1170,29 @@
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/is-binary-path": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
||||
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"binary-extensions": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/is-extglob": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-fullwidth-code-point": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||
@@ -939,6 +1202,29 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/is-glob": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
||||
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-extglob": "^2.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-number": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-stream": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
|
||||
@@ -1160,6 +1446,76 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/nodemon": {
|
||||
"version": "3.1.14",
|
||||
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.14.tgz",
|
||||
"integrity": "sha512-jakjZi93UtB3jHMWsXL68FXSAosbLfY0In5gtKq3niLSkrWznrVBzXFNOEMJUfc9+Ke7SHWoAZsiMkNP3vq6Jw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"chokidar": "^3.5.2",
|
||||
"debug": "^4",
|
||||
"ignore-by-default": "^1.0.1",
|
||||
"minimatch": "^10.2.1",
|
||||
"pstree.remy": "^1.1.8",
|
||||
"semver": "^7.5.3",
|
||||
"simple-update-notifier": "^2.0.0",
|
||||
"supports-color": "^5.5.0",
|
||||
"touch": "^3.1.0",
|
||||
"undefsafe": "^2.0.5"
|
||||
},
|
||||
"bin": {
|
||||
"nodemon": "bin/nodemon.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/nodemon"
|
||||
}
|
||||
},
|
||||
"node_modules/nodemon/node_modules/debug": {
|
||||
"version": "4.4.3",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
||||
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/nodemon/node_modules/minimatch": {
|
||||
"version": "10.2.4",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz",
|
||||
"integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==",
|
||||
"dev": true,
|
||||
"license": "BlueOak-1.0.0",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^5.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "18 || 20 || >=22"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/nodemon/node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/normalize-path": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
||||
@@ -1248,6 +1604,19 @@
|
||||
"integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/process": {
|
||||
"version": "0.11.10",
|
||||
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
|
||||
@@ -1276,6 +1645,13 @@
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/pstree.remy": {
|
||||
"version": "1.1.8",
|
||||
"resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
|
||||
"integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.14.2",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz",
|
||||
@@ -1367,6 +1743,19 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/readdirp": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
||||
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"picomatch": "^2.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
@@ -1393,6 +1782,19 @@
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.7.4",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
|
||||
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/send": {
|
||||
"version": "0.19.2",
|
||||
"resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz",
|
||||
@@ -1549,6 +1951,19 @@
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/simple-update-notifier": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
|
||||
"integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"semver": "^7.5.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/statuses": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
|
||||
@@ -1674,6 +2089,19 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-flag": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/tar-stream": {
|
||||
"version": "3.1.7",
|
||||
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz",
|
||||
@@ -1694,6 +2122,19 @@
|
||||
"b4a": "^1.6.4"
|
||||
}
|
||||
},
|
||||
"node_modules/to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-number": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/toidentifier": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
|
||||
@@ -1703,12 +2144,28 @@
|
||||
"node": ">=0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/touch": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz",
|
||||
"integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"nodetouch": "bin/nodetouch.js"
|
||||
}
|
||||
},
|
||||
"node_modules/tr46": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/type-is": {
|
||||
"version": "1.6.18",
|
||||
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
|
||||
@@ -1722,6 +2179,19 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/undefsafe": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
|
||||
"integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "7.18.2",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
|
||||
"integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/unpipe": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||
@@ -1877,6 +2347,27 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.19.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
|
||||
"integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"utf-8-validate": ">=5.0.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bufferutil": {
|
||||
"optional": true
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/zip-stream": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz",
|
||||
|
||||
@@ -5,12 +5,19 @@
|
||||
"main": "push-server.js",
|
||||
"scripts": {
|
||||
"start": "node push-server.js",
|
||||
"worker": "node notify-worker.js"
|
||||
"worker": "node notify-worker.js",
|
||||
"dev": "nodemon --watch . --exec \"node --inspect=0.0.0.0:9229 push-server.js\"",
|
||||
"dev:worker": "nodemon --watch . --exec \"node --inspect=0.0.0.0:9230 notify-worker.js\"",
|
||||
"dev:pnpm": "pnpm -w --parallel --filter ./server --filter ./server -- \"node --inspect=0.0.0.0:9229 push-server.js\""
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^3.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"archiver": "^7.0.1",
|
||||
"body-parser": "^1.20.2",
|
||||
"cors": "^2.8.5",
|
||||
"@supabase/supabase-js": "^2.0.0",
|
||||
"express": "^4.18.2",
|
||||
"form-data": "^4.0.5",
|
||||
"node-fetch": "^2.7.0"
|
||||
|
||||
184
server/消息推送文档/DELIVERY_NOTIFICATION_DATA_FLOW.md
Normal file
184
server/消息推送文档/DELIVERY_NOTIFICATION_DATA_FLOW.md
Normal file
@@ -0,0 +1,184 @@
|
||||
# 订单发货与物流通知:全链路数据流转分析文档
|
||||
|
||||
本文档详细描述了本商城系统中,用户订单在发货阶段,从底层物流提供商(快递100)触发Webhook回掉,一直到最终向用户设备下发App内推送消息的全量数据流转过程。
|
||||
|
||||
---
|
||||
|
||||
## 整体数据流图谱
|
||||
|
||||
整个数据运转被精心设计为异步且解耦的数个阶段,以确保在高并发回调或网络异常时,数据不丢失、不阻塞。
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Kuaidi100 as 快递100 (Webhook)
|
||||
participant Receiver as Webhook Receiver
|
||||
participant Supabase as Supabase (PostgreSQL)
|
||||
participant DBTrigger as 数据库触发器
|
||||
participant NotifyWorker as Notify Worker
|
||||
participant PushServer as Push Server
|
||||
participant UniPush as 云函数/UniPush
|
||||
participant UserApp as 用户App
|
||||
|
||||
%% 第一阶段:外部数据接入与存储
|
||||
Kuaidi100->>Receiver: HTTP POST 物流状态更新
|
||||
Note over Kuaidi100,Receiver: 验证签名(sign),解析参数
|
||||
Receiver->>Supabase: 插入/更新 platform_express_tracking_events
|
||||
Supabase-->>Receiver: 存入成功返回
|
||||
Receiver-->>Kuaidi100: 200 OK 返回给快递平台
|
||||
|
||||
%% 第二阶段:事件触发与入队
|
||||
Supabase->>DBTrigger: 触发器: event_to_queue_trigger
|
||||
Note over DBTrigger: 监听到 tracking_events 写入/更新<br>(条件检查:特定物流状态等)
|
||||
DBTrigger->>Supabase: 插入新任务至 notify_queue 表
|
||||
|
||||
%% 第三阶段:队列消费与通知生成
|
||||
loop 轮询读取队列 (10秒/次)
|
||||
NotifyWorker->>Supabase: 捞取 status='pending' 的任务
|
||||
NotifyWorker->>Supabase: 分析业务数据 (查ml_orders等)
|
||||
Note over NotifyWorker: 数据转换、格式化模板内容
|
||||
NotifyWorker->>Supabase: 将生成的推送记录写入 express_notifications 表
|
||||
NotifyWorker->>Supabase: 更新 notify_queue.status 为 'processed'
|
||||
end
|
||||
|
||||
%% 第四阶段:消息分发与远端推送
|
||||
loop 轮询待推送通知 (10秒/次)
|
||||
PushServer->>Supabase: 捞取 push_status='pending' 的记录
|
||||
PushServer->>Supabase: 结合 push_devices 表查找用户cid
|
||||
PushServer->>UniPush: HTTP POST (附带云函数URL & 推送体)
|
||||
Note over PushServer,UniPush: 触发 unicloud 上的云函数
|
||||
UniPush->>UserApp: 发放手机系统推送通知 (通道下发)
|
||||
UniPush-->>PushServer: 返回推送结果 (成功/失败)
|
||||
PushServer->>Supabase: 更新 express_notifications 推送状态 (delivered/failed)
|
||||
end
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 阶段一:外部回调接入与原始数据落库
|
||||
|
||||
### 1. Webhook 请求接收
|
||||
- **服务组件**:`server/webhook-receiver.js` ( 基于 Node.js + Express )
|
||||
- **触发源头**:快递100平台(订阅回调业务)。
|
||||
- **行为过程**:
|
||||
- 接收 HTTP POST 请求(默认端口 3001,路径 `POST /webhook/kuaidi100`)。
|
||||
- **参数验证**:解析 `param` 数据和 `sign` 签名,比对系统中配置的 secret 确保数据安全,非假冒请求。
|
||||
- **源头日志留痕**:原始的 JSON 报文会被原封不动地记录在 **`platform_express_event_raw`** 表中。这是追踪客诉(如“没收到通知”时排查是否丢件)的**第一查证现场**。
|
||||
- **数据提取**:解包得到快递单号、最新物流轨迹节点、物流状态编码(如:在途、派件、签收等)。
|
||||
|
||||
### 2. 落入业务核心库
|
||||
- 表目标:主要涉及 `platform_express_tracking_events`(物流事件记录)或 `platform_express_waybills`(运单主表)。
|
||||
- **处理要求**:将接收到的非结构化或外部结构化数据,转译为本系统认可的数据类型,并 Upsert(更新或插入)入库。至此,外部调用阶段结束,返回 `200 { "result": true, "returnCode": "200", "message": "成功" }` 予以确认,这确保了外部平台不再重复推送同一条消息。
|
||||
|
||||
---
|
||||
|
||||
## 阶段二:数据库内部事件流转(触发器驱动)
|
||||
|
||||
为了让核心写入逻辑与后续繁琐的通知逻辑解耦,系统利用了 PostgreSQL 强大的触发器(Trigger)能力。
|
||||
|
||||
### 1. 触发器监听
|
||||
- **关联脚本**:`20260309_add_notify_queue_and_trigger.sql` (位于 `pages/mall/delivery/doc/需求文档/`)
|
||||
- 当 `platform_express_tracking_events` 发生 `INSERT` (或特定 `UPDATE`) 时,触发底层 SQL 逻辑。
|
||||
|
||||
### 2. 队列填充 (`notify_queue`)
|
||||
- 触发器脚本 `event_to_queue_trigger` 负责初步筛选。它可能会排除一些毫无业务通知价值的冗余轨迹节点,只抓取关键节点(例如:“正在派发”、“已签收”)。
|
||||
- 把关联的业务主键(如:运单号、关联订单 ID)、事件类型,作为 JSON payload,以极快的速度 `INSERT` 进入 `notify_queue` 表,设定初始状态为 `pending`。
|
||||
- **优点**:Webhook API 的响应时间不再受后续通知逻辑(查询用户组、查找文案模板)的拖累。
|
||||
|
||||
---
|
||||
|
||||
## 阶段三:通知生成流水线(Notify Worker 消费任务)
|
||||
|
||||
这是业务逻辑最密集的部分,将纯粹的“发生了一件事”转化为具体的“应该发给谁、发什么内容”的推送任务数据。
|
||||
|
||||
### 1. 任务抓取与消费
|
||||
- **服务组件**:`server/notify-worker.js` ( 独立的 Node.js 守护进程 )
|
||||
- Worker 在后台持续运行,定期(例如每 10 秒)扫描 `notify_queue` 中处于 `pending` 状态的任务项。
|
||||
|
||||
### 2. 业务溯源与数据拼接
|
||||
- Worker 锁定任务后,从 Payload 提取运单 ID / 订单 ID。
|
||||
- **关联查询**:向数据库发起查询,结合 `ml_orders` (订单表)、可能的用户属性表、商品主数据表,查询出:买家的 `user_id`,商品名称摘要等内容。
|
||||
- **模版渲染**:例如,将“订单 + 运单号处于签收状态”渲染为中文文本:“您购买的商品【xxx等】已由用户签收”。
|
||||
|
||||
### 3. 持久化至推送队代表(加工日志留存)
|
||||
- 组装完毕后,Worker 将成型的通知指令,插入到真实的通知表 `express_notifications` 中,其包括目标 `user_id`、具体的 `title`、`body`。
|
||||
- 把该条新建的记录标记为 `push_status = 'pending'`。
|
||||
- 最后,将 `notify_queue` 中的原始任务记录状态更新为 `processed`。如果中间报错(例如解析运单或匹配订单失败),则会标记为 **`failed`** 并带有 **`error_msg`**,此表充当了**加工阶段的完整状态日志**。
|
||||
|
||||
---
|
||||
|
||||
## 阶段四:物理推送与结果反馈(Push Server 分发)
|
||||
|
||||
该阶段将上阶段准备好的具体通知内容,通过第三方通道真正发送到用户设备。
|
||||
|
||||
### 1. 推送目标检索
|
||||
- **服务组件**:`server/push-server.js` ( 基于 Node.js + Express )
|
||||
- Push Server 定期轮询 `express_notifications` 中 `push_status = 'pending'` 的新通知项。
|
||||
|
||||
### 2. 补齐通道标识(CID)
|
||||
- 系统根据需通知的目标 `user_id`,去 `push_devices`(Client ID 表) 中查找此用户最后活跃设备的绑定的 `cid`。如果没有找到合适的 `cid`,可能直接将该通知记为 `failed` 或转为站内信处理。
|
||||
|
||||
### 3. 请求云函数下发 (DCloud / uniCloud 侧)
|
||||
- **关联配置**:`server/config.json` 的 `CLOUD_FUNC_URL`。
|
||||
- Push Server 封装目标 `cid` 列表、文章标题 (title)、摘要主体 (body)、可能的附加跳转数据(payload 数据),构建一个 HTTP POST 载荷。
|
||||
- 通过网络请求抛给特定的 **DCloud 阿里云/腾讯云空间** 上部署的 **uniCloud 云函数服务**。
|
||||
- **核心下发通道技术栈**:因为本商城前端使用的是 uni-app x 架构,该云函数服务内包装了 DCloud 官方提供的 **UniPush 2.0 管理端 SDK**。由其直接对接个推(Getui)和包括华米OV(华为、小米、OPPO、vivo)以及苹果(APNs)在内的各大手机硬件厂商原生的**离线下发通道**,以保证 App 无论是否在后台驻留都能收到消息。
|
||||
|
||||
### 4. 结果更新与下发日志跟踪
|
||||
- 网络请求完成后,Push Server 会接受到云函数关于各客户端接收/下发的汇报。
|
||||
- 该结果会作为**下发日志**回写至对应的 `express_notifications` 记录中:
|
||||
- 如果成功,`push_status` 变为 **`delivered`**。
|
||||
- 如果失败,`push_status` 变为 **`failed`**,且将具体的错误文本(例如 `"invalid cid"`,无效的通道ID)存入 **`provider_response`** 字段中。这直接揭示了**为什么下发失败**。
|
||||
- **至此,一条由快递公司产生的外部轨迹更新,完美结束了内部的长尾运转,闭环成了用户手机屏幕上的一次震动提醒。**
|
||||
|
||||
---
|
||||
|
||||
## 业务溯源与客诉排查指南
|
||||
|
||||
本架构天然具备完整的数据追溯能力,所有生命周期均在数据库中永久留痕。在处理诸如“用户投诉没有收到签收推送”的问题时,运维人员可按照以下步骤依序溯源:
|
||||
|
||||
1. **查源头是否丢件 (`platform_express_event_raw`)**:根据快递单号检索此表,确认底层物流商是否真的推送了回调数据。如果没有记录,说明是快递公司没发/漏发。
|
||||
2. **查系统是否消费失败 (`notify_queue`)**:根据单号检索,看是否存在对应记录。如果在,看 `status` 字段。如果是 `pending`,说明 `notify-worker` 服务挂了没消费;如果是 `failed`,请查看 `error_msg`。
|
||||
3. **查终端是否下发失败 (`express_notifications`)**:如果前面都正常,看此表的 `push_status`。如果是 `pending` 说明云函数未执行/Push Server离线;如果是 `failed`,直接查看 `provider_response` 确认厂商阻断或CID失效的具体原因。
|
||||
|
||||
---
|
||||
|
||||
## 架构演进与为何不使用 Redis 的说明
|
||||
|
||||
针对很多大厂常见的推送后台中大量使用 Redis 的现状,本系统出于以下考量采取了**“去中间件化”的轻量级 PostgreSQL 强依赖架构**:
|
||||
|
||||
1. **队列平替**:大厂常将 `notify_queue` 的待推消息放在 Redis(如 List/Stream)。我们直接利用 Supabase PostgreSQL 的实体表 `notify_queue` 结合 10 秒级定时轮询 (Poll) 实现了队列。
|
||||
- **优势**:数据强一致性,发生断电/宕机等灾难时消息绝对不丢;极大地节约了项目运维成本,不需要单独起 Redis 实例或关心 RDB/AOF 策略。
|
||||
- **容限**:在单日处理数万至十万级流转的状态下,这类轮询对 PostgreSQL 的 IO 压力微乎其微。只有当出现“秒杀级别数十万并发群发”时,才需演进到 Redis 消息队列。
|
||||
2. **设备缓存查找平替**:查找 `userId -> cid` 虽然是典型的 KV 场景,但通过 Supabase 的索引表直接查询依然能保证极低的延迟响应。
|
||||
|
||||
这套设计对于我们十万级规模商城是性价比极高、免维护且极其稳壮的 Enterprise-Ready (企业级可用)的轻量化方案。
|
||||
|
||||
---
|
||||
|
||||
## 系统容量预期与未来性能优化指南
|
||||
|
||||
随着业务长期运行和单量增长,本套无中间件架构在某些极限边界上可能会暴露出其物理瓶颈。为了防患于未然,现将已知的极限边缘与预案说明如下,供未来的后端维护者参考:
|
||||
|
||||
### 1. 历史数据爆炸与日志表清理(DB 膨胀约束)
|
||||
由于本系统利用 `platform_express_event_raw`、`notify_queue` 和 `express_notifications` 等实体表承担了主要的业务流转与日志溯源功能,它们是“只增不减”的。
|
||||
- **隐患**:随着每天单量的累积,单表数据可轻易超过百万条,导致查询缓慢、索引膨胀或耗尽 Supabase 数据库的物理容量。
|
||||
- **未来演进四大方案(由简到强建议)**:
|
||||
1. **方案一:轻量级定时硬清理(起步期适用)**:在 Supabase 面板开启 `pg_cron`,每晚跑定时 SQL 脚本,直接 `DELETE` 掉 60 天前的历史记录。优点是零门槛实施,缺点是追溯期短且引发表碎片。
|
||||
2. **方案二:冷热数据分离归档(小型商城标配)**:创建同构归档表(如 `notify_queue_archive`),每晚将旧数据从热表迁移至归档表再删热表。确保生产环境极快的同时,运维能查陈年老账。
|
||||
3. **方案三:声明式表分区 Table Partitioning(高配优雅解)**:将主表按月改造成分区表层级(如 `queue_2026_01`)。业务代码零修改,删数据只需直接 `DROP TABLE` 具体月表,毫秒级释放巨大闲置空间,完全无磁盘碎片。
|
||||
4. **方案四:日志流外部卸载(大厂海量终极解)**:挂载监听 Supabase Realtime Webhook 或者 PostgreSQL 的逻辑复制流(Logical Replication),将所有的日志插入和变化发往第三方 Elasticsearch 或云厂商极其廉价的 OSS 对象存储桶里长存。主事务数据库里发完件立马删空,只留运行中的活跃数据。
|
||||
|
||||
### 2. 避免队列发生“毒药死循环” (Poison Message)
|
||||
- **隐患**:本纯 DB 驱动的队列体系未完全建立如同专业死信队列(Dead Letter Queue)那般的重试计数和隔离。如果在运行 `notify-worker` 的拼接数据逻辑中,有一条“破损订单数据”触发了未能 Catch 住的致命错(Fatal Error) 使得 Node.js 进程崩塌,进程通过守护脚本重启后,这个任务依然是 `pending`。Worker 重新抓取再次崩溃,就会形成堵死同批其他订单消息的“毒药效应”。
|
||||
- **未来预案/开发原则**:在 `notify-worker.js` 中新增、修改关于订单及商品表的复杂关联解析代码时,**必须将对单条业务的独立 Try-Catch 包裹进最内层的批次 `map` 中**,失败了直接给它标记为 `failed` 并写入 `error_msg` 抛弃,**绝不能**向外层抛出导致整个批次断裂。
|
||||
|
||||
### 3. 排队性能上限设置 (Scale-up)
|
||||
- **隐患**:当前我们提供的起服动作 (`start-delivery-backend.ps1`) 均为单线程执行(一个 Node 进程)。一分钟只能通过轮询处理规定上限条数的推送,若双十一爆发发货,可能会导致发信严重滞后(排队几小时后才收到)。
|
||||
- **未来预案**:
|
||||
1. 第一个手段是修改配置:适当调短轮询间隙时间 (`POLL_MS`),或加大每次查询限制 (`BATCH_SIZE`)。
|
||||
2. 第二个手段是多实例并发:如果要开多个 Worker 同时跑以消化消息波峰。在做这个扩展前,必须保证通过修改 SQL 将获取队列的查询改造为 `SELECT ... FOR UPDATE SKIP LOCKED`。以此实现单条消息在数据库级别的悲观跨行抢占,防止多个 Worker 获取并重复发送同一个通知。
|
||||
|
||||
本设计在每个阶段都支持“断层续接”:
|
||||
1. 若 Webhook receiver 崩溃,外部快递100会按照自己的退避算法发起重试,保证事件能抵达网络库。
|
||||
2. 若 Notify worker 崩溃,数据库里的 `notify_queue` 会静静堆积 pending 数据,一旦恢复,Worker 会把积压数据逐步消化。
|
||||
3. 若 Push server 网络故障或 云函数URL失效,`express_notifications` 依然保留 `pending`,待网络或配置恢复(如更新 `CLOUD_FUNC_URL`)后自动补偿发送。
|
||||
@@ -441,6 +441,7 @@ node .\pages\mall\delivery\webhook-server\test-send.js
|
||||
|
||||
## 6. 进一步阅读(从“总览”到“可落地”)
|
||||
|
||||
- 数据流详细流转图谱与分析:`server/消息推送文档/DELIVERY_NOTIFICATION_DATA_FLOW.md`
|
||||
- webhook-receiver:`pages/mall/delivery/webhook-server/README.md`
|
||||
- push-server(运行与变更记录):`server/PUSH_SERVER_README.md`
|
||||
- notify-worker(消息生成入队):`server/NOTIFY_WORKER_README.md`
|
||||
|
||||
@@ -71,8 +71,12 @@ curl -X POST http://localhost:7301/api/v1/notifications -H "Content-Type: applic
|
||||
|
||||
重要链接
|
||||
- push-server 源码: [server/push-server.js](../push-server.js)
|
||||
- notify-worker: [server/notify-worker.js](../notify-worker.js)
|
||||
- webhook-receiver: [pages/mall/delivery/webhook-server/README.md](../../pages/mall/delivery/webhook-server/README.md)
|
||||
- 完整全链路数据流转分析:`DELIVERY_NOTIFICATION_DATA_FLOW.md`
|
||||
- 运行与变更记录:`PUSH_SERVER_README.md`
|
||||
- 消息生成Worker服务:`NOTIFY_WORKER_README.md`
|
||||
- 云函数集成指引:`UNI_PUSH2_CLOUD_FUNCTION.md`
|
||||
- 排错指南:`DELIVERY_E2E_TROUBLESHOOTING_20260310.md`
|
||||
- webhook-receiver:`../../pages/mall/delivery/webhook-server/README.md`
|
||||
- 部署脚本: [server/scripts/start-delivery-backend.ps1](../scripts/start-delivery-backend.ps1)
|
||||
- 迁移脚本与 SQL: pages/mall/delivery/doc/需求文档/
|
||||
|
||||
|
||||
Reference in New Issue
Block a user