fix: 更改数据库包
This commit is contained in:
384
package-lock.json
generated
384
package-lock.json
generated
@@ -8,7 +8,6 @@
|
|||||||
"name": "us-iran-military-dashboard",
|
"name": "us-iran-military-dashboard",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"better-sqlite3": "^11.6.0",
|
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"echarts": "^5.5.0",
|
"echarts": "^5.5.0",
|
||||||
"echarts-for-react": "^3.0.2",
|
"echarts-for-react": "^3.0.2",
|
||||||
@@ -19,6 +18,7 @@
|
|||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-map-gl": "^7.1.7",
|
"react-map-gl": "^7.1.7",
|
||||||
"react-router-dom": "^7.13.1",
|
"react-router-dom": "^7.13.1",
|
||||||
|
"sql.js": "^1.11.0",
|
||||||
"swagger-ui-express": "^5.0.1",
|
"swagger-ui-express": "^5.0.1",
|
||||||
"ws": "^8.19.0",
|
"ws": "^8.19.0",
|
||||||
"zustand": "^5.0.0"
|
"zustand": "^5.0.0"
|
||||||
@@ -1929,25 +1929,6 @@
|
|||||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/base64-js": {
|
|
||||||
"version": "1.5.1",
|
|
||||||
"resolved": "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz",
|
|
||||||
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/feross"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "patreon",
|
|
||||||
"url": "https://www.patreon.com/feross"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "consulting",
|
|
||||||
"url": "https://feross.org/support"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"node_modules/baseline-browser-mapping": {
|
"node_modules/baseline-browser-mapping": {
|
||||||
"version": "2.10.0",
|
"version": "2.10.0",
|
||||||
"resolved": "https://registry.npmmirror.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz",
|
"resolved": "https://registry.npmmirror.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz",
|
||||||
@@ -1960,16 +1941,6 @@
|
|||||||
"node": ">=6.0.0"
|
"node": ">=6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/better-sqlite3": {
|
|
||||||
"version": "11.10.0",
|
|
||||||
"resolved": "https://registry.npmmirror.com/better-sqlite3/-/better-sqlite3-11.10.0.tgz",
|
|
||||||
"integrity": "sha512-EwhOpyXiOEL/lKzHz9AW1msWFNzGc/z+LzeB3/jnFJpxu+th2yqvzsSWas1v9jgs9+xiXJcD5A8CJxAG2TaghQ==",
|
|
||||||
"hasInstallScript": true,
|
|
||||||
"dependencies": {
|
|
||||||
"bindings": "^1.5.0",
|
|
||||||
"prebuild-install": "^7.1.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/binary-extensions": {
|
"node_modules/binary-extensions": {
|
||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.3.0.tgz",
|
"resolved": "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.3.0.tgz",
|
||||||
@@ -1982,24 +1953,6 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/bindings": {
|
|
||||||
"version": "1.5.0",
|
|
||||||
"resolved": "https://registry.npmmirror.com/bindings/-/bindings-1.5.0.tgz",
|
|
||||||
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"file-uri-to-path": "1.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/bl": {
|
|
||||||
"version": "4.1.0",
|
|
||||||
"resolved": "https://registry.npmmirror.com/bl/-/bl-4.1.0.tgz",
|
|
||||||
"integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
|
|
||||||
"dependencies": {
|
|
||||||
"buffer": "^5.5.0",
|
|
||||||
"inherits": "^2.0.4",
|
|
||||||
"readable-stream": "^3.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/body-parser": {
|
"node_modules/body-parser": {
|
||||||
"version": "1.20.4",
|
"version": "1.20.4",
|
||||||
"resolved": "https://registry.npmmirror.com/body-parser/-/body-parser-1.20.4.tgz",
|
"resolved": "https://registry.npmmirror.com/body-parser/-/body-parser-1.20.4.tgz",
|
||||||
@@ -2091,29 +2044,6 @@
|
|||||||
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
|
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/buffer": {
|
|
||||||
"version": "5.7.1",
|
|
||||||
"resolved": "https://registry.npmmirror.com/buffer/-/buffer-5.7.1.tgz",
|
|
||||||
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/feross"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "patreon",
|
|
||||||
"url": "https://www.patreon.com/feross"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "consulting",
|
|
||||||
"url": "https://feross.org/support"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"dependencies": {
|
|
||||||
"base64-js": "^1.3.1",
|
|
||||||
"ieee754": "^1.1.13"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/bytes": {
|
"node_modules/bytes": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmmirror.com/bytes/-/bytes-3.1.2.tgz",
|
"resolved": "https://registry.npmmirror.com/bytes/-/bytes-3.1.2.tgz",
|
||||||
@@ -2261,11 +2191,6 @@
|
|||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/chownr": {
|
|
||||||
"version": "1.1.4",
|
|
||||||
"resolved": "https://registry.npmmirror.com/chownr/-/chownr-1.1.4.tgz",
|
|
||||||
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="
|
|
||||||
},
|
|
||||||
"node_modules/color-convert": {
|
"node_modules/color-convert": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz",
|
"resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz",
|
||||||
@@ -2407,28 +2332,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/decompress-response": {
|
|
||||||
"version": "6.0.0",
|
|
||||||
"resolved": "https://registry.npmmirror.com/decompress-response/-/decompress-response-6.0.0.tgz",
|
|
||||||
"integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"mimic-response": "^3.1.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/deep-extend": {
|
|
||||||
"version": "0.6.0",
|
|
||||||
"resolved": "https://registry.npmmirror.com/deep-extend/-/deep-extend-0.6.0.tgz",
|
|
||||||
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=4.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/deep-is": {
|
"node_modules/deep-is": {
|
||||||
"version": "0.1.4",
|
"version": "0.1.4",
|
||||||
"resolved": "https://registry.npmmirror.com/deep-is/-/deep-is-0.1.4.tgz",
|
"resolved": "https://registry.npmmirror.com/deep-is/-/deep-is-0.1.4.tgz",
|
||||||
@@ -2452,14 +2355,6 @@
|
|||||||
"npm": "1.2.8000 || >= 1.4.16"
|
"npm": "1.2.8000 || >= 1.4.16"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/detect-libc": {
|
|
||||||
"version": "2.1.2",
|
|
||||||
"resolved": "https://registry.npmmirror.com/detect-libc/-/detect-libc-2.1.2.tgz",
|
|
||||||
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/didyoumean": {
|
"node_modules/didyoumean": {
|
||||||
"version": "1.2.2",
|
"version": "1.2.2",
|
||||||
"resolved": "https://registry.npmmirror.com/didyoumean/-/didyoumean-1.2.2.tgz",
|
"resolved": "https://registry.npmmirror.com/didyoumean/-/didyoumean-1.2.2.tgz",
|
||||||
@@ -2531,14 +2426,6 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/end-of-stream": {
|
|
||||||
"version": "1.4.5",
|
|
||||||
"resolved": "https://registry.npmmirror.com/end-of-stream/-/end-of-stream-1.4.5.tgz",
|
|
||||||
"integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
|
|
||||||
"dependencies": {
|
|
||||||
"once": "^1.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/es-define-property": {
|
"node_modules/es-define-property": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz",
|
"resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||||
@@ -2805,14 +2692,6 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/expand-template": {
|
|
||||||
"version": "2.0.3",
|
|
||||||
"resolved": "https://registry.npmmirror.com/expand-template/-/expand-template-2.0.3.tgz",
|
|
||||||
"integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/express": {
|
"node_modules/express": {
|
||||||
"version": "4.22.1",
|
"version": "4.22.1",
|
||||||
"resolved": "https://registry.npmmirror.com/express/-/express-4.22.1.tgz",
|
"resolved": "https://registry.npmmirror.com/express/-/express-4.22.1.tgz",
|
||||||
@@ -2948,11 +2827,6 @@
|
|||||||
"node": ">=16.0.0"
|
"node": ">=16.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/file-uri-to-path": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmmirror.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
|
||||||
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="
|
|
||||||
},
|
|
||||||
"node_modules/fill-range": {
|
"node_modules/fill-range": {
|
||||||
"version": "7.1.1",
|
"version": "7.1.1",
|
||||||
"resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz",
|
"resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz",
|
||||||
@@ -3059,11 +2933,6 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/fs-constants": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmmirror.com/fs-constants/-/fs-constants-1.0.0.tgz",
|
|
||||||
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="
|
|
||||||
},
|
|
||||||
"node_modules/fsevents": {
|
"node_modules/fsevents": {
|
||||||
"version": "2.3.3",
|
"version": "2.3.3",
|
||||||
"resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz",
|
"resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz",
|
||||||
@@ -3143,11 +3012,6 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/github-from-package": {
|
|
||||||
"version": "0.0.0",
|
|
||||||
"resolved": "https://registry.npmmirror.com/github-from-package/-/github-from-package-0.0.0.tgz",
|
|
||||||
"integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="
|
|
||||||
},
|
|
||||||
"node_modules/gl-matrix": {
|
"node_modules/gl-matrix": {
|
||||||
"version": "3.4.4",
|
"version": "3.4.4",
|
||||||
"resolved": "https://registry.npmmirror.com/gl-matrix/-/gl-matrix-3.4.4.tgz",
|
"resolved": "https://registry.npmmirror.com/gl-matrix/-/gl-matrix-3.4.4.tgz",
|
||||||
@@ -3254,25 +3118,6 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ieee754": {
|
|
||||||
"version": "1.2.1",
|
|
||||||
"resolved": "https://registry.npmmirror.com/ieee754/-/ieee754-1.2.1.tgz",
|
|
||||||
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/feross"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "patreon",
|
|
||||||
"url": "https://www.patreon.com/feross"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "consulting",
|
|
||||||
"url": "https://feross.org/support"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"node_modules/ignore": {
|
"node_modules/ignore": {
|
||||||
"version": "5.3.2",
|
"version": "5.3.2",
|
||||||
"resolved": "https://registry.npmmirror.com/ignore/-/ignore-5.3.2.tgz",
|
"resolved": "https://registry.npmmirror.com/ignore/-/ignore-5.3.2.tgz",
|
||||||
@@ -3312,11 +3157,6 @@
|
|||||||
"resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz",
|
"resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz",
|
||||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||||
},
|
},
|
||||||
"node_modules/ini": {
|
|
||||||
"version": "1.3.8",
|
|
||||||
"resolved": "https://registry.npmmirror.com/ini/-/ini-1.3.8.tgz",
|
|
||||||
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="
|
|
||||||
},
|
|
||||||
"node_modules/ipaddr.js": {
|
"node_modules/ipaddr.js": {
|
||||||
"version": "1.9.1",
|
"version": "1.9.1",
|
||||||
"resolved": "https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
"resolved": "https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
||||||
@@ -3714,17 +3554,6 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/mimic-response": {
|
|
||||||
"version": "3.1.0",
|
|
||||||
"resolved": "https://registry.npmmirror.com/mimic-response/-/mimic-response-3.1.0.tgz",
|
|
||||||
"integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/minimatch": {
|
"node_modules/minimatch": {
|
||||||
"version": "3.1.5",
|
"version": "3.1.5",
|
||||||
"resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.5.tgz",
|
"resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.5.tgz",
|
||||||
@@ -3745,11 +3574,6 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/mkdirp-classic": {
|
|
||||||
"version": "0.5.3",
|
|
||||||
"resolved": "https://registry.npmmirror.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
|
|
||||||
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="
|
|
||||||
},
|
|
||||||
"node_modules/ms": {
|
"node_modules/ms": {
|
||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz",
|
"resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz",
|
||||||
@@ -3789,11 +3613,6 @@
|
|||||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/napi-build-utils": {
|
|
||||||
"version": "2.0.0",
|
|
||||||
"resolved": "https://registry.npmmirror.com/napi-build-utils/-/napi-build-utils-2.0.0.tgz",
|
|
||||||
"integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA=="
|
|
||||||
},
|
|
||||||
"node_modules/natural-compare": {
|
"node_modules/natural-compare": {
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmmirror.com/natural-compare/-/natural-compare-1.4.0.tgz",
|
"resolved": "https://registry.npmmirror.com/natural-compare/-/natural-compare-1.4.0.tgz",
|
||||||
@@ -3808,28 +3627,6 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/node-abi": {
|
|
||||||
"version": "3.87.0",
|
|
||||||
"resolved": "https://registry.npmmirror.com/node-abi/-/node-abi-3.87.0.tgz",
|
|
||||||
"integrity": "sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"semver": "^7.3.5"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/node-abi/node_modules/semver": {
|
|
||||||
"version": "7.7.4",
|
|
||||||
"resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.4.tgz",
|
|
||||||
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
|
|
||||||
"bin": {
|
|
||||||
"semver": "bin/semver.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/node-releases": {
|
"node_modules/node-releases": {
|
||||||
"version": "2.0.27",
|
"version": "2.0.27",
|
||||||
"resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.27.tgz",
|
"resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.27.tgz",
|
||||||
@@ -3884,14 +3681,6 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/once": {
|
|
||||||
"version": "1.4.0",
|
|
||||||
"resolved": "https://registry.npmmirror.com/once/-/once-1.4.0.tgz",
|
|
||||||
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
|
||||||
"dependencies": {
|
|
||||||
"wrappy": "1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/optionator": {
|
"node_modules/optionator": {
|
||||||
"version": "0.9.4",
|
"version": "0.9.4",
|
||||||
"resolved": "https://registry.npmmirror.com/optionator/-/optionator-0.9.4.tgz",
|
"resolved": "https://registry.npmmirror.com/optionator/-/optionator-0.9.4.tgz",
|
||||||
@@ -4196,32 +3985,6 @@
|
|||||||
"resolved": "https://registry.npmmirror.com/potpack/-/potpack-2.1.0.tgz",
|
"resolved": "https://registry.npmmirror.com/potpack/-/potpack-2.1.0.tgz",
|
||||||
"integrity": "sha512-pcaShQc1Shq0y+E7GqJqvZj8DTthWV1KeHGdi0Z6IAin2Oi3JnLCOfwnCo84qc+HAp52wT9nK9H7FAJp5a44GQ=="
|
"integrity": "sha512-pcaShQc1Shq0y+E7GqJqvZj8DTthWV1KeHGdi0Z6IAin2Oi3JnLCOfwnCo84qc+HAp52wT9nK9H7FAJp5a44GQ=="
|
||||||
},
|
},
|
||||||
"node_modules/prebuild-install": {
|
|
||||||
"version": "7.1.3",
|
|
||||||
"resolved": "https://registry.npmmirror.com/prebuild-install/-/prebuild-install-7.1.3.tgz",
|
|
||||||
"integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==",
|
|
||||||
"deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.",
|
|
||||||
"dependencies": {
|
|
||||||
"detect-libc": "^2.0.0",
|
|
||||||
"expand-template": "^2.0.3",
|
|
||||||
"github-from-package": "0.0.0",
|
|
||||||
"minimist": "^1.2.3",
|
|
||||||
"mkdirp-classic": "^0.5.3",
|
|
||||||
"napi-build-utils": "^2.0.0",
|
|
||||||
"node-abi": "^3.3.0",
|
|
||||||
"pump": "^3.0.0",
|
|
||||||
"rc": "^1.2.7",
|
|
||||||
"simple-get": "^4.0.0",
|
|
||||||
"tar-fs": "^2.0.0",
|
|
||||||
"tunnel-agent": "^0.6.0"
|
|
||||||
},
|
|
||||||
"bin": {
|
|
||||||
"prebuild-install": "bin.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/prelude-ls": {
|
"node_modules/prelude-ls": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmmirror.com/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
"resolved": "https://registry.npmmirror.com/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
||||||
@@ -4248,15 +4011,6 @@
|
|||||||
"node": ">= 0.10"
|
"node": ">= 0.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/pump": {
|
|
||||||
"version": "3.0.4",
|
|
||||||
"resolved": "https://registry.npmmirror.com/pump/-/pump-3.0.4.tgz",
|
|
||||||
"integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==",
|
|
||||||
"dependencies": {
|
|
||||||
"end-of-stream": "^1.1.0",
|
|
||||||
"once": "^1.3.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/punycode": {
|
"node_modules/punycode": {
|
||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
"resolved": "https://registry.npmmirror.com/punycode/-/punycode-2.3.1.tgz",
|
"resolved": "https://registry.npmmirror.com/punycode/-/punycode-2.3.1.tgz",
|
||||||
@@ -4327,28 +4081,6 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/rc": {
|
|
||||||
"version": "1.2.8",
|
|
||||||
"resolved": "https://registry.npmmirror.com/rc/-/rc-1.2.8.tgz",
|
|
||||||
"integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
|
|
||||||
"dependencies": {
|
|
||||||
"deep-extend": "^0.6.0",
|
|
||||||
"ini": "~1.3.0",
|
|
||||||
"minimist": "^1.2.0",
|
|
||||||
"strip-json-comments": "~2.0.1"
|
|
||||||
},
|
|
||||||
"bin": {
|
|
||||||
"rc": "cli.js"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/rc/node_modules/strip-json-comments": {
|
|
||||||
"version": "2.0.1",
|
|
||||||
"resolved": "https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
|
|
||||||
"integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.10.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/react": {
|
"node_modules/react": {
|
||||||
"version": "18.3.1",
|
"version": "18.3.1",
|
||||||
"resolved": "https://registry.npmmirror.com/react/-/react-18.3.1.tgz",
|
"resolved": "https://registry.npmmirror.com/react/-/react-18.3.1.tgz",
|
||||||
@@ -4461,19 +4193,6 @@
|
|||||||
"pify": "^2.3.0"
|
"pify": "^2.3.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/readable-stream": {
|
|
||||||
"version": "3.6.2",
|
|
||||||
"resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-3.6.2.tgz",
|
|
||||||
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
|
|
||||||
"dependencies": {
|
|
||||||
"inherits": "^2.0.3",
|
|
||||||
"string_decoder": "^1.1.1",
|
|
||||||
"util-deprecate": "^1.0.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/readdirp": {
|
"node_modules/readdirp": {
|
||||||
"version": "3.6.0",
|
"version": "3.6.0",
|
||||||
"resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz",
|
"resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz",
|
||||||
@@ -4814,49 +4533,6 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/simple-concat": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmmirror.com/simple-concat/-/simple-concat-1.0.1.tgz",
|
|
||||||
"integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/feross"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "patreon",
|
|
||||||
"url": "https://www.patreon.com/feross"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "consulting",
|
|
||||||
"url": "https://feross.org/support"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"node_modules/simple-get": {
|
|
||||||
"version": "4.0.1",
|
|
||||||
"resolved": "https://registry.npmmirror.com/simple-get/-/simple-get-4.0.1.tgz",
|
|
||||||
"integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==",
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/feross"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "patreon",
|
|
||||||
"url": "https://www.patreon.com/feross"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "consulting",
|
|
||||||
"url": "https://feross.org/support"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"dependencies": {
|
|
||||||
"decompress-response": "^6.0.0",
|
|
||||||
"once": "^1.3.1",
|
|
||||||
"simple-concat": "^1.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/size-sensor": {
|
"node_modules/size-sensor": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmmirror.com/size-sensor/-/size-sensor-1.0.3.tgz",
|
"resolved": "https://registry.npmmirror.com/size-sensor/-/size-sensor-1.0.3.tgz",
|
||||||
@@ -4942,6 +4618,11 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/sql.js": {
|
||||||
|
"version": "1.14.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/sql.js/-/sql.js-1.14.0.tgz",
|
||||||
|
"integrity": "sha512-NXYh+kFqLiYRCNAaHD0PcbjFgXyjuolEKLMk5vRt2DgPENtF1kkNzzMlg42dUk5wIsH8MhUzsRhaUxIisoSlZQ=="
|
||||||
|
},
|
||||||
"node_modules/statuses": {
|
"node_modules/statuses": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmmirror.com/statuses/-/statuses-2.0.2.tgz",
|
"resolved": "https://registry.npmmirror.com/statuses/-/statuses-2.0.2.tgz",
|
||||||
@@ -4950,14 +4631,6 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/string_decoder": {
|
|
||||||
"version": "1.3.0",
|
|
||||||
"resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.3.0.tgz",
|
|
||||||
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
|
|
||||||
"dependencies": {
|
|
||||||
"safe-buffer": "~5.2.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/strip-json-comments": {
|
"node_modules/strip-json-comments": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
|
"resolved": "https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
|
||||||
@@ -5083,32 +4756,6 @@
|
|||||||
"node": ">=14.0.0"
|
"node": ">=14.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tar-fs": {
|
|
||||||
"version": "2.1.4",
|
|
||||||
"resolved": "https://registry.npmmirror.com/tar-fs/-/tar-fs-2.1.4.tgz",
|
|
||||||
"integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"chownr": "^1.1.1",
|
|
||||||
"mkdirp-classic": "^0.5.2",
|
|
||||||
"pump": "^3.0.0",
|
|
||||||
"tar-stream": "^2.1.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/tar-stream": {
|
|
||||||
"version": "2.2.0",
|
|
||||||
"resolved": "https://registry.npmmirror.com/tar-stream/-/tar-stream-2.2.0.tgz",
|
|
||||||
"integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"bl": "^4.0.3",
|
|
||||||
"end-of-stream": "^1.4.1",
|
|
||||||
"fs-constants": "^1.0.0",
|
|
||||||
"inherits": "^2.0.3",
|
|
||||||
"readable-stream": "^3.1.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/thenify": {
|
"node_modules/thenify": {
|
||||||
"version": "3.3.1",
|
"version": "3.3.1",
|
||||||
"resolved": "https://registry.npmmirror.com/thenify/-/thenify-3.3.1.tgz",
|
"resolved": "https://registry.npmmirror.com/thenify/-/thenify-3.3.1.tgz",
|
||||||
@@ -5223,17 +4870,6 @@
|
|||||||
"resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz",
|
"resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz",
|
||||||
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="
|
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="
|
||||||
},
|
},
|
||||||
"node_modules/tunnel-agent": {
|
|
||||||
"version": "0.6.0",
|
|
||||||
"resolved": "https://registry.npmmirror.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
|
||||||
"integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
|
|
||||||
"dependencies": {
|
|
||||||
"safe-buffer": "^5.0.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/type-check": {
|
"node_modules/type-check": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
"resolved": "https://registry.npmmirror.com/type-check/-/type-check-0.4.0.tgz",
|
"resolved": "https://registry.npmmirror.com/type-check/-/type-check-0.4.0.tgz",
|
||||||
@@ -5371,7 +5007,8 @@
|
|||||||
"node_modules/util-deprecate": {
|
"node_modules/util-deprecate": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
"resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
|
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/utils-merge": {
|
"node_modules/utils-merge": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
@@ -5472,11 +5109,6 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/wrappy": {
|
|
||||||
"version": "1.0.2",
|
|
||||||
"resolved": "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz",
|
|
||||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
|
|
||||||
},
|
|
||||||
"node_modules/ws": {
|
"node_modules/ws": {
|
||||||
"version": "8.19.0",
|
"version": "8.19.0",
|
||||||
"resolved": "https://registry.npmmirror.com/ws/-/ws-8.19.0.tgz",
|
"resolved": "https://registry.npmmirror.com/ws/-/ws-8.19.0.tgz",
|
||||||
|
|||||||
@@ -20,7 +20,6 @@
|
|||||||
"verify:full": "./scripts/verify-pipeline.sh --start-crawler"
|
"verify:full": "./scripts/verify-pipeline.sh --start-crawler"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"better-sqlite3": "^11.6.0",
|
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"echarts": "^5.5.0",
|
"echarts": "^5.5.0",
|
||||||
"echarts-for-react": "^3.0.2",
|
"echarts-for-react": "^3.0.2",
|
||||||
@@ -31,6 +30,7 @@
|
|||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-map-gl": "^7.1.7",
|
"react-map-gl": "^7.1.7",
|
||||||
"react-router-dom": "^7.13.1",
|
"react-router-dom": "^7.13.1",
|
||||||
|
"sql.js": "^1.11.0",
|
||||||
"swagger-ui-express": "^5.0.1",
|
"swagger-ui-express": "^5.0.1",
|
||||||
"ws": "^8.19.0",
|
"ws": "^8.19.0",
|
||||||
"zustand": "^5.0.0"
|
"zustand": "^5.0.0"
|
||||||
|
|||||||
170
server/README.md
Normal file
170
server/README.md
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
# 后端运行逻辑
|
||||||
|
|
||||||
|
后端是 **Node.js Express + SQLite + WebSocket**,与 Python 爬虫共用同一数据库文件,负责提供「态势数据」API、实时推送和简单统计。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 一、启动方式
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run api # 启动 server/index.js,默认端口 3001
|
||||||
|
```
|
||||||
|
|
||||||
|
- 端口:`process.env.API_PORT || 3001`
|
||||||
|
- 数据库:`process.env.DB_PATH` 或 `server/data.db`(与爬虫共用)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、整体架构
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────┐
|
||||||
|
│ server/index.js │
|
||||||
|
│ (HTTP Server + WebSocket Server) │
|
||||||
|
└─────────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
┌───────────────────────────────┼───────────────────────────────┐
|
||||||
|
│ │ │
|
||||||
|
▼ ▼ ▼
|
||||||
|
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
|
||||||
|
│ /api/* │ │ /ws │ │ 静态 dist │
|
||||||
|
│ routes.js │ │ WebSocket │ │ (生产) │
|
||||||
|
└──────┬──────┘ └──────┬──────┘ └─────────────┘
|
||||||
|
│ │
|
||||||
|
│ 读/写 │ 广播 situation + stats
|
||||||
|
▼ │
|
||||||
|
┌─────────────┐ │
|
||||||
|
│ db.js │◄─────────────────────┘
|
||||||
|
│ (SQLite) │ getSituation() / getStats()
|
||||||
|
└──────┬──────┘
|
||||||
|
│
|
||||||
|
│ 同文件 data.db
|
||||||
|
▼
|
||||||
|
┌─────────────┐
|
||||||
|
│ Python 爬虫 │ 抓取 → 去重 → AI 清洗 → 映射到库字段 → 写表 → POST /api/crawler/notify
|
||||||
|
│ situation_ │ (main.py 或 gdelt 服务;写 situation_update / news_content / combat_losses 等)
|
||||||
|
│ update 等 │
|
||||||
|
└─────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、核心模块
|
||||||
|
|
||||||
|
| 文件 | 作用 |
|
||||||
|
|------|------|
|
||||||
|
| **index.js** | 创建 HTTP + WebSocket 服务,挂载路由、静态资源、定时广播、爬虫通知回调 |
|
||||||
|
| **routes.js** | 所有 `/api/*` 接口:situation、db/dashboard、visit、feedback、share、stats、events、news 等 |
|
||||||
|
| **situationData.js** | `getSituation()`:从多张表聚合为前端所需的「态势」JSON(军力、基地、战损、事件脉络、GDELT 等) |
|
||||||
|
| **db.js** | SQLite 连接、建表、迁移(better-sqlite3,WAL 模式) |
|
||||||
|
| **stats.js** | `getStats()`:在看人数、累计访问、留言数、分享数 |
|
||||||
|
| **openapi.js** | Swagger/OpenAPI 文档定义 |
|
||||||
|
| **seed.js** | 初始化/重置种子数据(可单独运行 `npm run api:seed`) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、数据流(读)
|
||||||
|
|
||||||
|
1. **前端要「整页态势」**
|
||||||
|
- 请求 `GET /api/situation` → `routes.js` 调用 `getSituation()`
|
||||||
|
- `situationData.js` 从 db 读:`force_summary`、`power_index`、`force_asset`、`key_location`、`combat_losses`、`wall_street_trend`、`retaliation_*`、`situation_update`(最近 50 条)、`gdelt_events`、`conflict_stats` 等
|
||||||
|
- 组装成 `{ lastUpdated, usForces, iranForces, recentUpdates, conflictEvents, conflictStats, civilianCasualtiesTotal }` 返回。
|
||||||
|
|
||||||
|
2. **前端要「事件列表」**
|
||||||
|
- `GET /api/events` 返回 `conflictEvents` + `conflict_stats` + `updated_at`(同样来自 getSituation 的数据)。
|
||||||
|
|
||||||
|
3. **前端要「原始表数据」**
|
||||||
|
- `GET /api/db/dashboard` 返回多张表的 `SELECT *` 结果(含 `situation_update`),供 `/db` 调试页使用。
|
||||||
|
|
||||||
|
4. **WebSocket**
|
||||||
|
- 连接 `ws://host/ws` 时立即收到一条 `{ type: 'situation', data: getSituation(), stats: getStats() }`。
|
||||||
|
- 之后每 3 秒服务端主动广播同结构数据,前端可据此做实时刷新。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 五、数据流(写)
|
||||||
|
|
||||||
|
### 5.1 爬虫侧写库链路(推荐理解顺序)
|
||||||
|
|
||||||
|
爬虫写入前端库的完整链路如下,**不是**「抓完直接写表」,而是经过去重、AI 清洗、字段映射后再落库:
|
||||||
|
|
||||||
|
1. **爬虫抓取实时数据**
|
||||||
|
- RSS 等源抓取(`scrapers/rss_scraper.fetch_all`),得到原始条目列表。
|
||||||
|
|
||||||
|
2. **数据去重**
|
||||||
|
- 抓取阶段:RSS 内按 (title, url) 去重。
|
||||||
|
- 落库前:按 `content_hash(title, summary, url)` 在 `news_content` 表中去重,仅**未出现过**的条目进入后续流程(`news_storage.save_and_dedup`)。
|
||||||
|
|
||||||
|
3. **去重后按批次推送给 AI 清洗**
|
||||||
|
- 对通过去重的每条/每批数据:
|
||||||
|
- **展示用清洗**:标题/摘要翻译、`clean_news_for_panel` 提炼为符合面板的纯文本与长度(如 summary ≤120 字),`ensure_category` / `ensure_severity` 规范为前端枚举(`cleaner_ai`)。
|
||||||
|
- **结构化提取**(可选):`extractor_ai` / `extractor_dashscope` / `extractor_rules` 从新闻文本中抽取战损、基地状态等,输出符合 `panel_schema` 的结构。
|
||||||
|
- 得到「有效数据」:既有人读的 summary/category/severity,也有可落库的 combat_losses_delta、key_location 等。
|
||||||
|
|
||||||
|
4. **有效数据映射回前端数据库字段**
|
||||||
|
- 事件脉络:清洗后的条目写入 `situation_update`(`db_writer.write_updates`)。
|
||||||
|
- 资讯存档:去重后的新数据写入 `news_content`(已在步骤 2 完成)。
|
||||||
|
- 结构化数据:AI 提取结果通过 `db_merge.merge` 映射到前端表结构,更新 `combat_losses`、`key_location`、`retaliation_*`、`wall_street_trend` 等(与 `situationData.getSituation` 所用字段一致)。
|
||||||
|
|
||||||
|
5. **更新数据库表并通知后端**
|
||||||
|
- 上述表更新完成后,爬虫请求 **POST /api/crawler/notify**。
|
||||||
|
- 后端(index.js)更新 `situation.updated_at` 并调用 `broadcastSituation()`,前端通过 WebSocket 拿到最新态势。
|
||||||
|
|
||||||
|
实现上,**gdelt 服务**(`realtime_conflict_service`)里:先对抓取结果做翻译与清洗,再 `save_and_dedup` 去重落库 `news_content`,用去重后的新项写 `situation_update`,再按批次对这批新项做 AI 提取并 `db_merge.merge` 写战损/基地等表。
|
||||||
|
|
||||||
|
### 5.2 用户行为写入
|
||||||
|
|
||||||
|
- **POST /api/visit**:记 IP 到 `visits`,`visitor_count.total` +1,并触发一次广播。
|
||||||
|
- **POST /api/feedback**:插入 `feedback`。
|
||||||
|
- **POST /api/share**:`share_count.total` +1。
|
||||||
|
|
||||||
|
这些写操作在 `routes.js` 中通过 `db.prepare().run()` 完成。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六、API 一览
|
||||||
|
|
||||||
|
| 方法 | 路径 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| GET | /api/health | 健康检查 |
|
||||||
|
| GET | /api/situation | 完整态势(供主面板) |
|
||||||
|
| GET | /api/events | 冲突事件 + 统计 |
|
||||||
|
| GET | /api/db/dashboard | 各表原始数据(供 /db 页) |
|
||||||
|
| GET | /api/news | 资讯列表(news_content 表) |
|
||||||
|
| GET | /api/stats | 在看/累计/留言/分享数 |
|
||||||
|
| POST | /api/visit | 记录访问并返回 stats |
|
||||||
|
| POST | /api/feedback | 提交留言 |
|
||||||
|
| POST | /api/share | 分享计数 +1 |
|
||||||
|
| POST | /api/crawler/notify | 爬虫通知:更新 updated_at 并广播(内部用) |
|
||||||
|
|
||||||
|
- **Swagger**:`http://localhost:3001/api-docs`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 七、WebSocket 行为
|
||||||
|
|
||||||
|
- **路径**:`/ws`(与 HTTP 同端口)。
|
||||||
|
- **连接时**:服务端发送一条 `{ type: 'situation', data, stats }`。
|
||||||
|
- **定时广播**:`setInterval(broadcastSituation, 3000)` 每 3 秒向所有已连接客户端推送最新 `getSituation()` + `getStats()`。
|
||||||
|
- **爬虫通知**:POST `/api/crawler/notify` 会立即执行一次 `broadcastSituation()`,不必等 3 秒。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 八、与爬虫的协作
|
||||||
|
|
||||||
|
- **共享 DB**:后端与爬虫都使用同一 `DB_PATH`(默认 `server/data.db`)。
|
||||||
|
- **爬虫写库链路**:爬虫抓取 → 去重 → AI 清洗出有效数据 → 映射到前端库字段 → 更新 `situation_update`、`news_content`、`combat_losses`、`key_location`、`gdelt_events` 等表 → 调用 POST `/api/crawler/notify` 通知后端。
|
||||||
|
- **后端角色**:只读这些表(`getSituation()` 等)并推送;不参与抓取、去重或 AI 清洗,不调度爬虫。
|
||||||
|
|
||||||
|
整体上,后端是「读库 + 聚合 + 推送」的服务;写库来自**爬虫(经过去重与 AI 清洗、字段映射后)**以及**用户行为**(访问/留言/分享)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 九、本地验证链路
|
||||||
|
|
||||||
|
1. **启动后端**:`npm run api`(默认 3001)。
|
||||||
|
2. **检查读库**:`curl -s http://localhost:3001/api/situation` 应返回含 `lastUpdated`、`recentUpdates` 的 JSON。
|
||||||
|
3. **检查写库与通知**:爬虫跑完流水线后会 POST `/api/crawler/notify`,后端会更新 `situation.updated_at` 并广播;可再请求 `/api/situation` 看 `lastUpdated` 是否更新。
|
||||||
|
4. **查原始表**:浏览器打开 `http://localhost:3001/api/db/dashboard` 或前端 `/db` 页,查看 `situation_update`、`news_content` 等表。
|
||||||
|
|
||||||
|
爬虫侧完整验证步骤见 **crawler/README.md** 的「本地验证链路」;项目根目录可执行 `./scripts/verify-pipeline.sh` 做一键检查。
|
||||||
BIN
server/data.db
BIN
server/data.db
Binary file not shown.
Binary file not shown.
Binary file not shown.
214
server/db.js
214
server/db.js
@@ -1,20 +1,67 @@
|
|||||||
const Database = require('better-sqlite3')
|
/**
|
||||||
|
* SQLite 封装:使用 sql.js(纯 JS/WebAssembly,无需 node-gyp)
|
||||||
|
* 对外接口与 better-sqlite3 兼容:db.prepare().get/all/run、db.exec
|
||||||
|
*/
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
|
const fs = require('fs')
|
||||||
|
|
||||||
const dbPath = process.env.DB_PATH || path.join(__dirname, 'data.db')
|
const dbPath = process.env.DB_PATH || path.join(__dirname, 'data.db')
|
||||||
const db = new Database(dbPath)
|
let _db = null
|
||||||
|
|
||||||
// 启用外键
|
function getDb() {
|
||||||
db.pragma('journal_mode = WAL')
|
if (!_db) throw new Error('DB not initialized. Call initDb() first.')
|
||||||
|
return _db
|
||||||
|
}
|
||||||
|
|
||||||
// 建表
|
function wrapDatabase(nativeDb, persist) {
|
||||||
db.exec(`
|
return {
|
||||||
|
prepare(sql) {
|
||||||
|
return {
|
||||||
|
get(...args) {
|
||||||
|
const stmt = nativeDb.prepare(sql)
|
||||||
|
stmt.bind(args.length ? args : null)
|
||||||
|
const row = stmt.step() ? stmt.getAsObject() : undefined
|
||||||
|
stmt.free()
|
||||||
|
return row
|
||||||
|
},
|
||||||
|
all(...args) {
|
||||||
|
const stmt = nativeDb.prepare(sql)
|
||||||
|
stmt.bind(args.length ? args : null)
|
||||||
|
const rows = []
|
||||||
|
while (stmt.step()) rows.push(stmt.getAsObject())
|
||||||
|
stmt.free()
|
||||||
|
return rows
|
||||||
|
},
|
||||||
|
run(...args) {
|
||||||
|
const stmt = nativeDb.prepare(sql)
|
||||||
|
stmt.bind(args.length ? args : null)
|
||||||
|
while (stmt.step());
|
||||||
|
stmt.free()
|
||||||
|
persist()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
exec(sql) {
|
||||||
|
const statements = sql.split(';').map((s) => s.trim()).filter(Boolean)
|
||||||
|
statements.forEach((s) => nativeDb.run(s))
|
||||||
|
persist()
|
||||||
|
},
|
||||||
|
pragma(str) {
|
||||||
|
nativeDb.run('PRAGMA ' + str)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function runMigrations(db) {
|
||||||
|
const exec = (sql) => db.exec(sql)
|
||||||
|
const prepare = (sql) => db.prepare(sql)
|
||||||
|
|
||||||
|
exec(`
|
||||||
CREATE TABLE IF NOT EXISTS situation (
|
CREATE TABLE IF NOT EXISTS situation (
|
||||||
id INTEGER PRIMARY KEY CHECK (id = 1),
|
id INTEGER PRIMARY KEY CHECK (id = 1),
|
||||||
data TEXT NOT NULL,
|
data TEXT NOT NULL,
|
||||||
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS force_summary (
|
CREATE TABLE IF NOT EXISTS force_summary (
|
||||||
side TEXT PRIMARY KEY CHECK (side IN ('us', 'iran')),
|
side TEXT PRIMARY KEY CHECK (side IN ('us', 'iran')),
|
||||||
total_assets INTEGER NOT NULL,
|
total_assets INTEGER NOT NULL,
|
||||||
@@ -26,7 +73,6 @@ db.exec(`
|
|||||||
missile_consumed INTEGER NOT NULL,
|
missile_consumed INTEGER NOT NULL,
|
||||||
missile_stock INTEGER NOT NULL
|
missile_stock INTEGER NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS power_index (
|
CREATE TABLE IF NOT EXISTS power_index (
|
||||||
side TEXT PRIMARY KEY CHECK (side IN ('us', 'iran')),
|
side TEXT PRIMARY KEY CHECK (side IN ('us', 'iran')),
|
||||||
overall INTEGER NOT NULL,
|
overall INTEGER NOT NULL,
|
||||||
@@ -34,7 +80,6 @@ db.exec(`
|
|||||||
economic_power INTEGER NOT NULL,
|
economic_power INTEGER NOT NULL,
|
||||||
geopolitical_influence INTEGER NOT NULL
|
geopolitical_influence INTEGER NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS force_asset (
|
CREATE TABLE IF NOT EXISTS force_asset (
|
||||||
id TEXT PRIMARY KEY,
|
id TEXT PRIMARY KEY,
|
||||||
side TEXT NOT NULL CHECK (side IN ('us', 'iran')),
|
side TEXT NOT NULL CHECK (side IN ('us', 'iran')),
|
||||||
@@ -45,7 +90,6 @@ db.exec(`
|
|||||||
lat REAL,
|
lat REAL,
|
||||||
lng REAL
|
lng REAL
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS key_location (
|
CREATE TABLE IF NOT EXISTS key_location (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
side TEXT NOT NULL CHECK (side IN ('us', 'iran')),
|
side TEXT NOT NULL CHECK (side IN ('us', 'iran')),
|
||||||
@@ -55,7 +99,6 @@ db.exec(`
|
|||||||
type TEXT,
|
type TEXT,
|
||||||
region TEXT
|
region TEXT
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS combat_losses (
|
CREATE TABLE IF NOT EXISTS combat_losses (
|
||||||
side TEXT PRIMARY KEY CHECK (side IN ('us', 'iran')),
|
side TEXT PRIMARY KEY CHECK (side IN ('us', 'iran')),
|
||||||
bases_destroyed INTEGER NOT NULL,
|
bases_destroyed INTEGER NOT NULL,
|
||||||
@@ -67,24 +110,20 @@ db.exec(`
|
|||||||
armor INTEGER NOT NULL,
|
armor INTEGER NOT NULL,
|
||||||
vehicles INTEGER NOT NULL
|
vehicles INTEGER NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS wall_street_trend (
|
CREATE TABLE IF NOT EXISTS wall_street_trend (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
time TEXT NOT NULL,
|
time TEXT NOT NULL,
|
||||||
value INTEGER NOT NULL
|
value INTEGER NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS retaliation_current (
|
CREATE TABLE IF NOT EXISTS retaliation_current (
|
||||||
id INTEGER PRIMARY KEY CHECK (id = 1),
|
id INTEGER PRIMARY KEY CHECK (id = 1),
|
||||||
value INTEGER NOT NULL
|
value INTEGER NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS retaliation_history (
|
CREATE TABLE IF NOT EXISTS retaliation_history (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
time TEXT NOT NULL,
|
time TEXT NOT NULL,
|
||||||
value INTEGER NOT NULL
|
value INTEGER NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS situation_update (
|
CREATE TABLE IF NOT EXISTS situation_update (
|
||||||
id TEXT PRIMARY KEY,
|
id TEXT PRIMARY KEY,
|
||||||
timestamp TEXT NOT NULL,
|
timestamp TEXT NOT NULL,
|
||||||
@@ -92,7 +131,6 @@ db.exec(`
|
|||||||
summary TEXT NOT NULL,
|
summary TEXT NOT NULL,
|
||||||
severity TEXT NOT NULL
|
severity TEXT NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS gdelt_events (
|
CREATE TABLE IF NOT EXISTS gdelt_events (
|
||||||
event_id TEXT PRIMARY KEY,
|
event_id TEXT PRIMARY KEY,
|
||||||
event_time TEXT NOT NULL,
|
event_time TEXT NOT NULL,
|
||||||
@@ -103,7 +141,6 @@ db.exec(`
|
|||||||
url TEXT,
|
url TEXT,
|
||||||
created_at TEXT DEFAULT (datetime('now'))
|
created_at TEXT DEFAULT (datetime('now'))
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS conflict_stats (
|
CREATE TABLE IF NOT EXISTS conflict_stats (
|
||||||
id INTEGER PRIMARY KEY CHECK (id = 1),
|
id INTEGER PRIMARY KEY CHECK (id = 1),
|
||||||
total_events INTEGER NOT NULL DEFAULT 0,
|
total_events INTEGER NOT NULL DEFAULT 0,
|
||||||
@@ -112,7 +149,6 @@ db.exec(`
|
|||||||
estimated_strike_count INTEGER NOT NULL DEFAULT 0,
|
estimated_strike_count INTEGER NOT NULL DEFAULT 0,
|
||||||
updated_at TEXT NOT NULL
|
updated_at TEXT NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS news_content (
|
CREATE TABLE IF NOT EXISTS news_content (
|
||||||
id TEXT PRIMARY KEY,
|
id TEXT PRIMARY KEY,
|
||||||
content_hash TEXT NOT NULL UNIQUE,
|
content_hash TEXT NOT NULL UNIQUE,
|
||||||
@@ -125,57 +161,49 @@ db.exec(`
|
|||||||
severity TEXT NOT NULL DEFAULT 'medium',
|
severity TEXT NOT NULL DEFAULT 'medium',
|
||||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||||
);
|
);
|
||||||
CREATE INDEX IF NOT EXISTS idx_news_content_hash ON news_content(content_hash);
|
`)
|
||||||
CREATE INDEX IF NOT EXISTS idx_news_content_published ON news_content(published_at DESC);
|
try { exec('CREATE INDEX IF NOT EXISTS idx_news_content_hash ON news_content(content_hash)') } catch (_) {}
|
||||||
`)
|
try { exec('CREATE INDEX IF NOT EXISTS idx_news_content_published ON news_content(published_at DESC)') } catch (_) {}
|
||||||
|
|
||||||
// 迁移:为已有 key_location 表添加 type、region、status、damage_level 列
|
|
||||||
try {
|
|
||||||
const cols = db.prepare('PRAGMA table_info(key_location)').all()
|
|
||||||
const names = cols.map((c) => c.name)
|
|
||||||
if (!names.includes('type')) db.exec('ALTER TABLE key_location ADD COLUMN type TEXT')
|
|
||||||
if (!names.includes('region')) db.exec('ALTER TABLE key_location ADD COLUMN region TEXT')
|
|
||||||
if (!names.includes('status')) db.exec('ALTER TABLE key_location ADD COLUMN status TEXT DEFAULT "operational"')
|
|
||||||
if (!names.includes('damage_level')) db.exec('ALTER TABLE key_location ADD COLUMN damage_level INTEGER')
|
|
||||||
} catch (_) {}
|
|
||||||
// 迁移:combat_losses 添加平民伤亡、updated_at
|
|
||||||
try {
|
|
||||||
const lossCols = db.prepare('PRAGMA table_info(combat_losses)').all()
|
|
||||||
const lossNames = lossCols.map((c) => c.name)
|
|
||||||
if (!lossNames.includes('civilian_killed')) db.exec('ALTER TABLE combat_losses ADD COLUMN civilian_killed INTEGER NOT NULL DEFAULT 0')
|
|
||||||
if (!lossNames.includes('civilian_wounded')) db.exec('ALTER TABLE combat_losses ADD COLUMN civilian_wounded INTEGER NOT NULL DEFAULT 0')
|
|
||||||
if (!lossNames.includes('updated_at')) db.exec('ALTER TABLE combat_losses ADD COLUMN updated_at TEXT DEFAULT (datetime("now"))')
|
|
||||||
if (!lossNames.includes('drones')) db.exec('ALTER TABLE combat_losses ADD COLUMN drones INTEGER NOT NULL DEFAULT 0')
|
|
||||||
if (!lossNames.includes('missiles')) db.exec('ALTER TABLE combat_losses ADD COLUMN missiles INTEGER NOT NULL DEFAULT 0')
|
|
||||||
if (!lossNames.includes('helicopters')) db.exec('ALTER TABLE combat_losses ADD COLUMN helicopters INTEGER NOT NULL DEFAULT 0')
|
|
||||||
if (!lossNames.includes('submarines')) db.exec('ALTER TABLE combat_losses ADD COLUMN submarines INTEGER NOT NULL DEFAULT 0')
|
|
||||||
if (!lossNames.includes('tanks')) db.exec('ALTER TABLE combat_losses ADD COLUMN tanks INTEGER NOT NULL DEFAULT 0')
|
|
||||||
if (!lossNames.includes('carriers')) {
|
|
||||||
db.exec('ALTER TABLE combat_losses ADD COLUMN carriers INTEGER NOT NULL DEFAULT 0')
|
|
||||||
db.exec('UPDATE combat_losses SET carriers = tanks')
|
|
||||||
}
|
|
||||||
if (!lossNames.includes('civilian_ships')) db.exec('ALTER TABLE combat_losses ADD COLUMN civilian_ships INTEGER NOT NULL DEFAULT 0')
|
|
||||||
if (!lossNames.includes('airport_port')) db.exec('ALTER TABLE combat_losses ADD COLUMN airport_port INTEGER NOT NULL DEFAULT 0')
|
|
||||||
} catch (_) {}
|
|
||||||
|
|
||||||
// 迁移:所有表添加 updated_at 用于数据回放
|
|
||||||
const addUpdatedAt = (table) => {
|
|
||||||
try {
|
try {
|
||||||
const cols = db.prepare(`PRAGMA table_info(${table})`).all()
|
const cols = prepare('PRAGMA table_info(key_location)').all()
|
||||||
|
const names = cols.map((c) => c.name)
|
||||||
|
if (!names.includes('type')) exec('ALTER TABLE key_location ADD COLUMN type TEXT')
|
||||||
|
if (!names.includes('region')) exec('ALTER TABLE key_location ADD COLUMN region TEXT')
|
||||||
|
if (!names.includes('status')) exec('ALTER TABLE key_location ADD COLUMN status TEXT DEFAULT "operational"')
|
||||||
|
if (!names.includes('damage_level')) exec('ALTER TABLE key_location ADD COLUMN damage_level INTEGER')
|
||||||
|
} catch (_) {}
|
||||||
|
try {
|
||||||
|
const lossCols = prepare('PRAGMA table_info(combat_losses)').all()
|
||||||
|
const lossNames = lossCols.map((c) => c.name)
|
||||||
|
if (!lossNames.includes('civilian_killed')) exec('ALTER TABLE combat_losses ADD COLUMN civilian_killed INTEGER NOT NULL DEFAULT 0')
|
||||||
|
if (!lossNames.includes('civilian_wounded')) exec('ALTER TABLE combat_losses ADD COLUMN civilian_wounded INTEGER NOT NULL DEFAULT 0')
|
||||||
|
if (!lossNames.includes('updated_at')) exec('ALTER TABLE combat_losses ADD COLUMN updated_at TEXT DEFAULT (datetime("now"))')
|
||||||
|
if (!lossNames.includes('drones')) exec('ALTER TABLE combat_losses ADD COLUMN drones INTEGER NOT NULL DEFAULT 0')
|
||||||
|
if (!lossNames.includes('missiles')) exec('ALTER TABLE combat_losses ADD COLUMN missiles INTEGER NOT NULL DEFAULT 0')
|
||||||
|
if (!lossNames.includes('helicopters')) exec('ALTER TABLE combat_losses ADD COLUMN helicopters INTEGER NOT NULL DEFAULT 0')
|
||||||
|
if (!lossNames.includes('submarines')) exec('ALTER TABLE combat_losses ADD COLUMN submarines INTEGER NOT NULL DEFAULT 0')
|
||||||
|
if (!lossNames.includes('tanks')) exec('ALTER TABLE combat_losses ADD COLUMN tanks INTEGER NOT NULL DEFAULT 0')
|
||||||
|
if (!lossNames.includes('carriers')) {
|
||||||
|
exec('ALTER TABLE combat_losses ADD COLUMN carriers INTEGER NOT NULL DEFAULT 0')
|
||||||
|
exec('UPDATE combat_losses SET carriers = tanks')
|
||||||
|
}
|
||||||
|
if (!lossNames.includes('civilian_ships')) exec('ALTER TABLE combat_losses ADD COLUMN civilian_ships INTEGER NOT NULL DEFAULT 0')
|
||||||
|
if (!lossNames.includes('airport_port')) exec('ALTER TABLE combat_losses ADD COLUMN airport_port INTEGER NOT NULL DEFAULT 0')
|
||||||
|
} catch (_) {}
|
||||||
|
|
||||||
|
const addUpdatedAt = (table) => {
|
||||||
|
try {
|
||||||
|
const cols = prepare(`PRAGMA table_info(${table})`).all()
|
||||||
if (!cols.some((c) => c.name === 'updated_at')) {
|
if (!cols.some((c) => c.name === 'updated_at')) {
|
||||||
db.exec(`ALTER TABLE ${table} ADD COLUMN updated_at TEXT DEFAULT (datetime("now"))`)
|
exec(`ALTER TABLE ${table} ADD COLUMN updated_at TEXT DEFAULT (datetime("now"))`)
|
||||||
}
|
}
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
}
|
}
|
||||||
addUpdatedAt('force_summary')
|
;['force_summary', 'power_index', 'force_asset', 'key_location', 'retaliation_current'].forEach(addUpdatedAt)
|
||||||
addUpdatedAt('power_index')
|
|
||||||
addUpdatedAt('force_asset')
|
|
||||||
addUpdatedAt('key_location')
|
|
||||||
addUpdatedAt('retaliation_current')
|
|
||||||
|
|
||||||
// 来访统计:visits 用于在看(近期活跃 IP),visitor_count 用于累积人次(每次接入 +1)
|
try {
|
||||||
try {
|
exec(`
|
||||||
db.exec(`
|
|
||||||
CREATE TABLE IF NOT EXISTS visits (
|
CREATE TABLE IF NOT EXISTS visits (
|
||||||
ip TEXT PRIMARY KEY,
|
ip TEXT PRIMARY KEY,
|
||||||
last_seen TEXT NOT NULL DEFAULT (datetime('now'))
|
last_seen TEXT NOT NULL DEFAULT (datetime('now'))
|
||||||
@@ -186,11 +214,9 @@ try {
|
|||||||
);
|
);
|
||||||
INSERT OR IGNORE INTO visitor_count (id, total) VALUES (1, 0);
|
INSERT OR IGNORE INTO visitor_count (id, total) VALUES (1, 0);
|
||||||
`)
|
`)
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
|
try {
|
||||||
// 后台留言:供开发者收集用户反馈
|
exec(`
|
||||||
try {
|
|
||||||
db.exec(`
|
|
||||||
CREATE TABLE IF NOT EXISTS feedback (
|
CREATE TABLE IF NOT EXISTS feedback (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
content TEXT NOT NULL,
|
content TEXT NOT NULL,
|
||||||
@@ -198,17 +224,55 @@ try {
|
|||||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||||
)
|
)
|
||||||
`)
|
`)
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
|
try {
|
||||||
// 分享次数:累计分享次数
|
exec(`
|
||||||
try {
|
|
||||||
db.exec(`
|
|
||||||
CREATE TABLE IF NOT EXISTS share_count (
|
CREATE TABLE IF NOT EXISTS share_count (
|
||||||
id INTEGER PRIMARY KEY CHECK (id = 1),
|
id INTEGER PRIMARY KEY CHECK (id = 1),
|
||||||
total INTEGER NOT NULL DEFAULT 0
|
total INTEGER NOT NULL DEFAULT 0
|
||||||
);
|
);
|
||||||
INSERT OR IGNORE INTO share_count (id, total) VALUES (1, 0);
|
INSERT OR IGNORE INTO share_count (id, total) VALUES (1, 0);
|
||||||
`)
|
`)
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = db
|
async function initDb() {
|
||||||
|
const initSqlJs = require('sql.js')
|
||||||
|
const SQL = await initSqlJs()
|
||||||
|
let data = new Uint8Array(0)
|
||||||
|
if (fs.existsSync(dbPath)) {
|
||||||
|
data = new Uint8Array(fs.readFileSync(dbPath))
|
||||||
|
}
|
||||||
|
const nativeDb = new SQL.Database(data)
|
||||||
|
|
||||||
|
function persist() {
|
||||||
|
try {
|
||||||
|
const buf = nativeDb.export()
|
||||||
|
fs.writeFileSync(dbPath, Buffer.from(buf))
|
||||||
|
} catch (e) {
|
||||||
|
console.error('[db] persist error:', e.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nativeDb.run('PRAGMA journal_mode = WAL')
|
||||||
|
const wrapped = wrapDatabase(nativeDb, persist)
|
||||||
|
runMigrations(wrapped)
|
||||||
|
_db = wrapped
|
||||||
|
return _db
|
||||||
|
}
|
||||||
|
|
||||||
|
const proxy = {
|
||||||
|
prepare(sql) {
|
||||||
|
return getDb().prepare(sql)
|
||||||
|
},
|
||||||
|
exec(sql) {
|
||||||
|
return getDb().exec(sql)
|
||||||
|
},
|
||||||
|
pragma(str) {
|
||||||
|
getDb().pragma(str)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = proxy
|
||||||
|
module.exports.initDb = initDb
|
||||||
|
module.exports.getDb = getDb
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ const fs = require('fs')
|
|||||||
const express = require('express')
|
const express = require('express')
|
||||||
const cors = require('cors')
|
const cors = require('cors')
|
||||||
const { WebSocketServer } = require('ws')
|
const { WebSocketServer } = require('ws')
|
||||||
|
const db = require('./db')
|
||||||
const routes = require('./routes')
|
const routes = require('./routes')
|
||||||
const { getSituation } = require('./situationData')
|
const { getSituation } = require('./situationData')
|
||||||
|
|
||||||
@@ -67,7 +68,12 @@ function notifyCrawlerUpdate() {
|
|||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
server.listen(PORT, () => {
|
db.initDb().then(() => {
|
||||||
|
server.listen(PORT, () => {
|
||||||
console.log(`API + WebSocket running at http://localhost:${PORT}`)
|
console.log(`API + WebSocket running at http://localhost:${PORT}`)
|
||||||
console.log(`Swagger docs at http://localhost:${PORT}/api-docs`)
|
console.log(`Swagger docs at http://localhost:${PORT}/api-docs`)
|
||||||
|
})
|
||||||
|
}).catch((err) => {
|
||||||
|
console.error('DB init failed:', err)
|
||||||
|
process.exit(1)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -186,4 +186,7 @@ function seed() {
|
|||||||
console.log('Seed completed.')
|
console.log('Seed completed.')
|
||||||
}
|
}
|
||||||
|
|
||||||
seed()
|
require('./db').initDb().then(() => seed()).catch((err) => {
|
||||||
|
console.error('Seed failed:', err)
|
||||||
|
process.exit(1)
|
||||||
|
})
|
||||||
|
|||||||
@@ -155,6 +155,8 @@ function getSituation() {
|
|||||||
})),
|
})),
|
||||||
conflictStats,
|
conflictStats,
|
||||||
civilianCasualtiesTotal,
|
civilianCasualtiesTotal,
|
||||||
|
// 顶层聚合,便于 sit.combatLosses.us / sit.combatLosses.iran 与 usForces/iranForces 内保持一致
|
||||||
|
combatLosses: { us: usLosses, iran: irLosses },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user