feat(storage): integrate Alibaba Cloud OSS for file persistence - Add OSSAdapter and LocalAdapter with StorageFactory pattern - Integrate PKB module with OSS upload - Rename difyDocumentId to storageKey - Create 4 OSS buckets and development specification
This commit is contained in:
@@ -53,6 +53,9 @@ Status: Day 1 complete (11/11 tasks), ready for Day 2
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -283,6 +283,9 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -229,6 +229,9 @@ https://iit.xunzhengyixue.com/api/v1/iit/health
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
4
backend/.gitignore
vendored
4
backend/.gitignore
vendored
@@ -1,5 +1,3 @@
|
||||
node_modules
|
||||
node_modules
|
||||
# Keep environment variables out of version control
|
||||
.env
|
||||
|
||||
/src/generated/prisma
|
||||
|
||||
@@ -158,6 +158,9 @@ https://iit.xunzhengyixue.com/api/v1/iit/health
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -59,6 +59,9 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -319,6 +319,9 @@ npx tsx src/modules/iit-manager/test-patient-wechat-url-verify.ts
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -181,6 +181,9 @@ npm run dev
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -62,3 +62,6 @@ main()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -56,3 +56,6 @@ main()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -51,3 +51,6 @@ main()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -83,3 +83,6 @@ main()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -46,3 +46,6 @@ main()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -87,3 +87,6 @@ main()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -34,3 +34,6 @@ main()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -122,3 +122,6 @@ main()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -93,3 +93,6 @@ main()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -79,3 +79,6 @@ main()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -121,3 +121,6 @@ main()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -32,3 +32,6 @@ ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -64,3 +64,6 @@ ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
79
backend/env.example.md
Normal file
79
backend/env.example.md
Normal file
@@ -0,0 +1,79 @@
|
||||
# 环境变量配置示例
|
||||
|
||||
复制以下内容到 `.env` 文件中:
|
||||
|
||||
```bash
|
||||
# ===========================================
|
||||
# AI Clinical Research Platform - 环境变量配置
|
||||
# ===========================================
|
||||
|
||||
# ==================== 应用配置 ====================
|
||||
NODE_ENV=development
|
||||
PORT=3001
|
||||
HOST=0.0.0.0
|
||||
LOG_LEVEL=debug
|
||||
SERVICE_NAME=aiclinical-backend
|
||||
|
||||
# ==================== 数据库配置 ====================
|
||||
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/ai_clinical
|
||||
|
||||
# ==================== 存储配置 ====================
|
||||
# 存储类型:local | oss
|
||||
STORAGE_TYPE=local
|
||||
|
||||
# --- 本地存储配置(STORAGE_TYPE=local)---
|
||||
LOCAL_STORAGE_DIR=uploads
|
||||
LOCAL_STORAGE_URL=http://localhost:3001/uploads
|
||||
|
||||
# --- 阿里云OSS配置(STORAGE_TYPE=oss)---
|
||||
# OSS_REGION=oss-cn-beijing
|
||||
# OSS_BUCKET=ai-clinical-data-dev
|
||||
# OSS_BUCKET_STATIC=ai-clinical-static-dev
|
||||
# OSS_ACCESS_KEY_ID=your-access-key-id
|
||||
# OSS_ACCESS_KEY_SECRET=your-access-key-secret
|
||||
# OSS_INTERNAL=false
|
||||
# OSS_SIGNED_URL_EXPIRES=3600
|
||||
|
||||
# ==================== 安全配置 ====================
|
||||
JWT_SECRET=your-secret-key-change-in-production
|
||||
JWT_EXPIRES_IN=7d
|
||||
CORS_ORIGIN=http://localhost:5173
|
||||
|
||||
# ==================== LLM API配置 ====================
|
||||
DEEPSEEK_API_KEY=
|
||||
DEEPSEEK_BASE_URL=https://api.deepseek.com
|
||||
DASHSCOPE_API_KEY=
|
||||
GEMINI_API_KEY=
|
||||
|
||||
# ==================== 文件上传配置 ====================
|
||||
UPLOAD_MAX_SIZE=31457280
|
||||
```
|
||||
|
||||
## OSS 开发环境配置
|
||||
|
||||
如果要测试 OSS,将 `STORAGE_TYPE` 改为 `oss` 并填写以下配置:
|
||||
|
||||
```bash
|
||||
STORAGE_TYPE=oss
|
||||
OSS_REGION=oss-cn-beijing
|
||||
OSS_BUCKET=ai-clinical-data-dev
|
||||
OSS_BUCKET_STATIC=ai-clinical-static-dev
|
||||
OSS_ACCESS_KEY_ID=LTAI5tBHkL39GjdLfcr77Y3f
|
||||
OSS_ACCESS_KEY_SECRET=<从安全渠道获取>
|
||||
OSS_INTERNAL=false
|
||||
OSS_SIGNED_URL_EXPIRES=3600
|
||||
```
|
||||
|
||||
## OSS 生产环境配置(SAE)
|
||||
|
||||
```bash
|
||||
STORAGE_TYPE=oss
|
||||
OSS_REGION=oss-cn-beijing
|
||||
OSS_BUCKET=ai-clinical-data
|
||||
OSS_BUCKET_STATIC=ai-clinical-static
|
||||
OSS_ACCESS_KEY_ID=LTAI5tBHkL39GjdLfcr77Y3f
|
||||
OSS_ACCESS_KEY_SECRET=<从安全渠道获取>
|
||||
OSS_INTERNAL=true # 🔴 生产必须用内网
|
||||
OSS_SIGNED_URL_EXPIRES=3600
|
||||
```
|
||||
|
||||
@@ -78,6 +78,9 @@ WHERE table_schema = 'dc_schema'
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
659
backend/package-lock.json
generated
659
backend/package-lock.json
generated
@@ -16,6 +16,7 @@
|
||||
"@types/form-data": "^2.2.1",
|
||||
"@wecom/crypto": "^1.0.1",
|
||||
"ajv": "^8.17.1",
|
||||
"ali-oss": "^6.23.0",
|
||||
"axios": "^1.12.2",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"bullmq": "^5.65.0",
|
||||
@@ -41,6 +42,7 @@
|
||||
"zod": "^4.1.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/ali-oss": "^6.23.1",
|
||||
"@types/bcryptjs": "^2.4.6",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"@types/jsonwebtoken": "^9.0.7",
|
||||
@@ -1024,6 +1026,13 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/ali-oss": {
|
||||
"version": "6.23.1",
|
||||
"resolved": "https://registry.npmmirror.com/@types/ali-oss/-/ali-oss-6.23.1.tgz",
|
||||
"integrity": "sha512-4mmCq2gUPBaPo6UlLIS/wMc7LxzDCzEUlRJAbLEk68Ntw7KWuF4RUwrQz3gtJkAeIYQ/R6PN3IvPTqk8daVVKQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/bcryptjs": {
|
||||
"version": "2.4.6",
|
||||
"resolved": "https://registry.npmmirror.com/@types/bcryptjs/-/bcryptjs-2.4.6.tgz",
|
||||
@@ -1166,6 +1175,15 @@
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/address": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmmirror.com/address/-/address-1.2.2.tgz",
|
||||
"integrity": "sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/adler-32": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmmirror.com/adler-32/-/adler-32-1.3.1.tgz",
|
||||
@@ -1175,6 +1193,18 @@
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/agentkeepalive": {
|
||||
"version": "3.5.3",
|
||||
"resolved": "https://registry.npmmirror.com/agentkeepalive/-/agentkeepalive-3.5.3.tgz",
|
||||
"integrity": "sha512-yqXL+k5rr8+ZRpOAntkaaRgWgE5o8ESAj5DyRmVTCSoZxXmqemb9Dd7T4i5UzwuERdLAJUy6XzR9zFVuf0kzkw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"humanize-ms": "^1.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ajv": {
|
||||
"version": "8.17.1",
|
||||
"resolved": "https://registry.npmmirror.com/ajv/-/ajv-8.17.1.tgz",
|
||||
@@ -1208,6 +1238,57 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/ali-oss": {
|
||||
"version": "6.23.0",
|
||||
"resolved": "https://registry.npmmirror.com/ali-oss/-/ali-oss-6.23.0.tgz",
|
||||
"integrity": "sha512-FipRmyd16Pr/tEey/YaaQ/24Pc3HEpLM9S1DRakEuXlSLXNIJnu1oJtHM53eVYpvW3dXapSjrip3xylZUTIZVQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"address": "^1.2.2",
|
||||
"agentkeepalive": "^3.4.1",
|
||||
"bowser": "^1.6.0",
|
||||
"copy-to": "^2.0.1",
|
||||
"dateformat": "^2.0.0",
|
||||
"debug": "^4.3.4",
|
||||
"destroy": "^1.0.4",
|
||||
"end-or-error": "^1.0.1",
|
||||
"get-ready": "^1.0.0",
|
||||
"humanize-ms": "^1.2.0",
|
||||
"is-type-of": "^1.4.0",
|
||||
"js-base64": "^2.5.2",
|
||||
"jstoxml": "^2.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"merge-descriptors": "^1.0.1",
|
||||
"mime": "^2.4.5",
|
||||
"platform": "^1.3.1",
|
||||
"pump": "^3.0.0",
|
||||
"qs": "^6.4.0",
|
||||
"sdk-base": "^2.0.1",
|
||||
"stream-http": "2.8.2",
|
||||
"stream-wormhole": "^1.0.4",
|
||||
"urllib": "^2.44.0",
|
||||
"utility": "^1.18.0",
|
||||
"xml2js": "^0.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/ali-oss/node_modules/dateformat": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmmirror.com/dateformat/-/dateformat-2.2.0.tgz",
|
||||
"integrity": "sha512-GODcnWq3YGoTnygPfi02ygEiRxqUxpJwuRHjdhJYuxpcZmDq4rjBiXYmbCCzStxo176ixfLT6i4NPwQooRySnw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/any-promise": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/any-promise/-/any-promise-1.3.0.tgz",
|
||||
"integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/anymatch": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz",
|
||||
@@ -1482,6 +1563,12 @@
|
||||
"integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/bowser": {
|
||||
"version": "1.9.4",
|
||||
"resolved": "https://registry.npmmirror.com/bowser/-/bowser-1.9.4.tgz",
|
||||
"integrity": "sha512-9IdMmj2KjigRq6oWhmwv1W36pDuA4STQZ8q6YO9um+x07xgYNCD3Oou+WP/3L1HNz7iqythGet3/p4wvc8AAwQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.12",
|
||||
"resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||
@@ -1561,6 +1648,12 @@
|
||||
"node": ">=0.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/builtin-status-codes": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz",
|
||||
"integrity": "sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/bullmq": {
|
||||
"version": "5.65.0",
|
||||
"resolved": "https://registry.npmmirror.com/bullmq/-/bullmq-5.65.0.tgz",
|
||||
@@ -1642,6 +1735,22 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bound": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmmirror.com/call-bound/-/call-bound-1.0.4.tgz",
|
||||
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
"get-intrinsic": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/canvg": {
|
||||
"version": "3.0.11",
|
||||
"resolved": "https://registry.npmmirror.com/canvg/-/canvg-3.0.11.tgz",
|
||||
@@ -1837,6 +1946,15 @@
|
||||
"node": "^14.18.0 || >=16.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/content-type": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmmirror.com/content-type/-/content-type-1.0.5.tgz",
|
||||
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/cookie": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/cookie/-/cookie-1.0.2.tgz",
|
||||
@@ -1846,6 +1964,12 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/copy-to": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/copy-to/-/copy-to-2.0.1.tgz",
|
||||
"integrity": "sha512-3DdaFaU/Zf1AnpLiFDeNCD4TOWe3Zl2RZaTzUvWiIk5ERzcCodOE20Vqq4fzCbNoHURFHT4/us/Lfq+S2zyY4w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/core-js": {
|
||||
"version": "3.46.0",
|
||||
"resolved": "https://registry.npmmirror.com/core-js/-/core-js-3.46.0.tgz",
|
||||
@@ -1985,6 +2109,18 @@
|
||||
"node": ">=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/default-user-agent": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/default-user-agent/-/default-user-agent-1.0.0.tgz",
|
||||
"integrity": "sha512-bDF7bg6OSNcSwFWPu4zYKpVkJZQYVrAANMYB8bc9Szem1D0yKdm4sa/rOCs2aC9+2GMqQ7KnwtZRvDhmLF0dXw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"os-name": "~1.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/defu": {
|
||||
"version": "6.1.4",
|
||||
"resolved": "https://registry.npmmirror.com/defu/-/defu-6.1.4.tgz",
|
||||
@@ -2024,6 +2160,16 @@
|
||||
"integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/destroy": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmmirror.com/destroy/-/destroy-1.2.0.tgz",
|
||||
"integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8",
|
||||
"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",
|
||||
@@ -2050,6 +2196,15 @@
|
||||
"integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/digest-header": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/digest-header/-/digest-header-1.1.0.tgz",
|
||||
"integrity": "sha512-glXVh42vz40yZb9Cq2oMOt70FIoWiv+vxNvdKdU8CwjLad25qHM3trLxhl9bVjdr6WaslIXhWpn0NO8T/67Qjg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dompurify": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/dompurify/-/dompurify-3.3.0.tgz",
|
||||
@@ -2134,6 +2289,12 @@
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/ee-first": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz",
|
||||
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/effect": {
|
||||
"version": "3.16.12",
|
||||
"resolved": "https://registry.npmmirror.com/effect/-/effect-3.16.12.tgz",
|
||||
@@ -2168,6 +2329,15 @@
|
||||
"once": "^1.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/end-or-error": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/end-or-error/-/end-or-error-1.0.1.tgz",
|
||||
"integrity": "sha512-OclLMSug+k2A0JKuf494im25ANRBVW8qsjmwbgX7lQ8P82H21PQ1PWkoYwb9y5yMBS69BPlwtzdIFClo3+7kOQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.11.14"
|
||||
}
|
||||
},
|
||||
"node_modules/es-define-property": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||
@@ -2255,6 +2425,12 @@
|
||||
"@esbuild/win32-x64": "0.25.10"
|
||||
}
|
||||
},
|
||||
"node_modules/escape-html": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz",
|
||||
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/eventemitter3": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-5.0.1.tgz",
|
||||
@@ -2297,6 +2473,18 @@
|
||||
"integrity": "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/extend-shallow": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/extend-shallow/-/extend-shallow-2.0.1.tgz",
|
||||
"integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-extendable": "^0.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-check": {
|
||||
"version": "3.23.2",
|
||||
"resolved": "https://registry.npmmirror.com/fast-check/-/fast-check-3.23.2.tgz",
|
||||
@@ -2611,6 +2799,18 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/formstream": {
|
||||
"version": "1.5.2",
|
||||
"resolved": "https://registry.npmmirror.com/formstream/-/formstream-1.5.2.tgz",
|
||||
"integrity": "sha512-NASf0lgxC1AyKNXQIrXTEYkiX99LhCEXTkiGObXAkpBui86a4u8FjH1o2bGb3PpqI3kafC+yw4zWeK6l6VHTgg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"destroy": "^1.0.4",
|
||||
"mime": "^2.5.2",
|
||||
"node-hex": "^1.0.1",
|
||||
"pause-stream": "~0.0.11"
|
||||
}
|
||||
},
|
||||
"node_modules/frac": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/frac/-/frac-1.1.2.tgz",
|
||||
@@ -2709,6 +2909,12 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/get-ready": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/get-ready/-/get-ready-1.0.0.tgz",
|
||||
"integrity": "sha512-mFXCZPJIlcYcth+N8267+mghfYN9h3EhsDa6JSnbA3Wrhh/XFpuowviFcsDeYZtKspQyWyJqfs4O6P8CHeTwzw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/get-tsconfig": {
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmmirror.com/get-tsconfig/-/get-tsconfig-4.12.0.tgz",
|
||||
@@ -2888,6 +3094,27 @@
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/humanize-ms": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmmirror.com/humanize-ms/-/humanize-ms-1.2.1.tgz",
|
||||
"integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ieee754": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmmirror.com/ieee754/-/ieee754-1.2.1.tgz",
|
||||
@@ -2997,6 +3224,21 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/is-class-hotfix": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmmirror.com/is-class-hotfix/-/is-class-hotfix-0.0.6.tgz",
|
||||
"integrity": "sha512-0n+pzCC6ICtVr/WXnN2f03TK/3BfXY7me4cjCAqT8TYXEl0+JBRoqBo94JJHXcyDSLUeWbNX8Fvy5g5RJdAstQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/is-extendable": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmmirror.com/is-extendable/-/is-extendable-0.1.1.tgz",
|
||||
"integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-extglob": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||
@@ -3042,12 +3284,29 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/is-type-of": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmmirror.com/is-type-of/-/is-type-of-1.4.0.tgz",
|
||||
"integrity": "sha512-EddYllaovi5ysMLMEN7yzHEKh8A850cZ7pykrY1aNRQGn/CDjRDE9qEWbIdt7xGEVJmjBXzU/fNnC4ABTm8tEQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"core-util-is": "^1.0.2",
|
||||
"is-class-hotfix": "~0.0.6",
|
||||
"isstream": "~0.1.2"
|
||||
}
|
||||
},
|
||||
"node_modules/isarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/isarray/-/isarray-1.0.0.tgz",
|
||||
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/isstream": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/isstream/-/isstream-0.1.2.tgz",
|
||||
"integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/jiti": {
|
||||
"version": "2.6.1",
|
||||
"resolved": "https://registry.npmmirror.com/jiti/-/jiti-2.6.1.tgz",
|
||||
@@ -3067,6 +3326,12 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/js-base64": {
|
||||
"version": "2.6.4",
|
||||
"resolved": "https://registry.npmmirror.com/js-base64/-/js-base64-2.6.4.tgz",
|
||||
"integrity": "sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||
@@ -3152,6 +3417,12 @@
|
||||
"html2canvas": "^1.0.0-rc.5"
|
||||
}
|
||||
},
|
||||
"node_modules/jstoxml": {
|
||||
"version": "2.2.9",
|
||||
"resolved": "https://registry.npmmirror.com/jstoxml/-/jstoxml-2.2.9.tgz",
|
||||
"integrity": "sha512-OYWlK0j+roh+eyaMROlNbS5cd5R25Y+IUpdl7cNdB8HNrkgwQzIS7L9MegxOiWNBj9dQhA/yAxiMwCC5mwNoBw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/jszip": {
|
||||
"version": "3.10.1",
|
||||
"resolved": "https://registry.npmmirror.com/jszip/-/jszip-3.10.1.tgz",
|
||||
@@ -3321,6 +3592,12 @@
|
||||
"integrity": "sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
"version": "4.17.23",
|
||||
"resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.23.tgz",
|
||||
"integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.defaults": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmmirror.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
|
||||
@@ -3478,6 +3755,27 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/merge-descriptors": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
|
||||
"integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/mime": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmmirror.com/mime/-/mime-2.6.0.tgz",
|
||||
"integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"mime": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz",
|
||||
@@ -3604,6 +3902,17 @@
|
||||
"@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/mz": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmmirror.com/mz/-/mz-2.7.0.tgz",
|
||||
"integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"any-promise": "^1.0.0",
|
||||
"object-assign": "^4.0.1",
|
||||
"thenify-all": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/napi-build-utils": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/napi-build-utils/-/napi-build-utils-2.0.0.tgz",
|
||||
@@ -3657,6 +3966,15 @@
|
||||
"node-gyp-build-optional-packages-test": "build-test.js"
|
||||
}
|
||||
},
|
||||
"node_modules/node-hex": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/node-hex/-/node-hex-1.0.1.tgz",
|
||||
"integrity": "sha512-iwpZdvW6Umz12ICmu9IYPRxg0tOLGmU3Tq2tKetejCj3oZd7b2nUXwP3a7QA5M9glWy8wlPS1G3RwM/CdsUbdQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/nodemon": {
|
||||
"version": "3.1.10",
|
||||
"resolved": "https://registry.npmmirror.com/nodemon/-/nodemon-3.1.10.tgz",
|
||||
@@ -3752,6 +4070,27 @@
|
||||
"node": "^14.16.0 || >=16.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/object-inspect": {
|
||||
"version": "1.13.4",
|
||||
"resolved": "https://registry.npmmirror.com/object-inspect/-/object-inspect-1.13.4.tgz",
|
||||
"integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/obliterator": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmmirror.com/obliterator/-/obliterator-2.0.5.tgz",
|
||||
@@ -3812,6 +4151,37 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/os-name": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/os-name/-/os-name-1.0.3.tgz",
|
||||
"integrity": "sha512-f5estLO2KN8vgtTRaILIgEGBoBrMnZ3JQ7W9TMZCnOIGwHe8TRGSpcagnWDo+Dfhd/z08k9Xe75hvciJJ8Qaew==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"osx-release": "^1.0.0",
|
||||
"win-release": "^1.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"os-name": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/osx-release": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/osx-release/-/osx-release-1.1.0.tgz",
|
||||
"integrity": "sha512-ixCMMwnVxyHFQLQnINhmIpWqXIfS2YOXchwQrk+OFzmo6nDjQ0E4KXAyyUh0T0MZgV4bUhkRrAbVqlE4yLVq4A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"minimist": "^1.1.0"
|
||||
},
|
||||
"bin": {
|
||||
"osx-release": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/p-queue": {
|
||||
"version": "9.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/p-queue/-/p-queue-9.0.1.tgz",
|
||||
@@ -3861,6 +4231,18 @@
|
||||
"integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pause-stream": {
|
||||
"version": "0.0.11",
|
||||
"resolved": "https://registry.npmmirror.com/pause-stream/-/pause-stream-0.0.11.tgz",
|
||||
"integrity": "sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==",
|
||||
"license": [
|
||||
"MIT",
|
||||
"Apache2"
|
||||
],
|
||||
"dependencies": {
|
||||
"through": "~2.3"
|
||||
}
|
||||
},
|
||||
"node_modules/perfect-debounce": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/perfect-debounce/-/perfect-debounce-1.0.0.tgz",
|
||||
@@ -4075,6 +4457,12 @@
|
||||
"pathe": "^2.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/platform": {
|
||||
"version": "1.3.6",
|
||||
"resolved": "https://registry.npmmirror.com/platform/-/platform-1.3.6.tgz",
|
||||
"integrity": "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/postgres-array": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/postgres-array/-/postgres-array-2.0.0.tgz",
|
||||
@@ -4205,7 +4593,6 @@
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/pump/-/pump-3.0.3.tgz",
|
||||
"integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"end-of-stream": "^1.1.0",
|
||||
@@ -4228,6 +4615,21 @@
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.14.1",
|
||||
"resolved": "https://registry.npmmirror.com/qs/-/qs-6.14.1.tgz",
|
||||
"integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"side-channel": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/quick-format-unescaped": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmmirror.com/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz",
|
||||
@@ -4513,6 +4915,15 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/sdk-base": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/sdk-base/-/sdk-base-2.0.1.tgz",
|
||||
"integrity": "sha512-eeG26wRwhtwYuKGCDM3LixCaxY27Pa/5lK4rLKhQa7HBjJ3U3Y+f81MMZQRsDw/8SC2Dao/83yJTXJ8aULuN8Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"get-ready": "~1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/secure-json-parse": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/secure-json-parse/-/secure-json-parse-4.1.0.tgz",
|
||||
@@ -4568,6 +4979,78 @@
|
||||
"integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/side-channel": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/side-channel/-/side-channel-1.1.0.tgz",
|
||||
"integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"object-inspect": "^1.13.3",
|
||||
"side-channel-list": "^1.0.0",
|
||||
"side-channel-map": "^1.0.1",
|
||||
"side-channel-weakmap": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/side-channel-list": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/side-channel-list/-/side-channel-list-1.0.0.tgz",
|
||||
"integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"object-inspect": "^1.13.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/side-channel-map": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/side-channel-map/-/side-channel-map-1.0.1.tgz",
|
||||
"integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bound": "^1.0.2",
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.5",
|
||||
"object-inspect": "^1.13.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/side-channel-weakmap": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
|
||||
"integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bound": "^1.0.2",
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.5",
|
||||
"object-inspect": "^1.13.3",
|
||||
"side-channel-map": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"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",
|
||||
@@ -4698,6 +5181,15 @@
|
||||
"integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/statuses": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmmirror.com/statuses/-/statuses-1.5.0.tgz",
|
||||
"integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/steed": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmmirror.com/steed/-/steed-1.1.3.tgz",
|
||||
@@ -4711,6 +5203,58 @@
|
||||
"reusify": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/stream-http": {
|
||||
"version": "2.8.2",
|
||||
"resolved": "https://registry.npmmirror.com/stream-http/-/stream-http-2.8.2.tgz",
|
||||
"integrity": "sha512-QllfrBhqF1DPcz46WxKTs6Mz1Bpc+8Qm6vbqOpVav5odAXwbyzwnEczoWqtxrsmlO+cJqtPrp/8gWKWjaKLLlA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"builtin-status-codes": "^3.0.0",
|
||||
"inherits": "^2.0.1",
|
||||
"readable-stream": "^2.3.6",
|
||||
"to-arraybuffer": "^1.0.0",
|
||||
"xtend": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/stream-http/node_modules/readable-stream": {
|
||||
"version": "2.3.8",
|
||||
"resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.8.tgz",
|
||||
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.3",
|
||||
"isarray": "~1.0.0",
|
||||
"process-nextick-args": "~2.0.0",
|
||||
"safe-buffer": "~5.1.1",
|
||||
"string_decoder": "~1.1.1",
|
||||
"util-deprecate": "~1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/stream-http/node_modules/safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/stream-http/node_modules/string_decoder": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safe-buffer": "~5.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/stream-wormhole": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/stream-wormhole/-/stream-wormhole-1.1.0.tgz",
|
||||
"integrity": "sha512-gHFfL3px0Kctd6Po0M8TzEvt3De/xu6cnRrjlfYNhwbhLPLwigI2t1nc6jrzNuaYg5C4YF78PPFuQPzRiqn9ew==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/string_decoder": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||
@@ -4800,6 +5344,27 @@
|
||||
"utrie": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/thenify": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmmirror.com/thenify/-/thenify-3.3.1.tgz",
|
||||
"integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"any-promise": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/thenify-all": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmmirror.com/thenify-all/-/thenify-all-1.6.0.tgz",
|
||||
"integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"thenify": ">= 3.1.0 < 4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/thread-stream": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/thread-stream/-/thread-stream-3.1.0.tgz",
|
||||
@@ -4809,6 +5374,12 @@
|
||||
"real-require": "^0.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/through": {
|
||||
"version": "2.3.8",
|
||||
"resolved": "https://registry.npmmirror.com/through/-/through-2.3.8.tgz",
|
||||
"integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tiktoken": {
|
||||
"version": "1.0.22",
|
||||
"resolved": "https://registry.npmmirror.com/tiktoken/-/tiktoken-1.0.22.tgz",
|
||||
@@ -4830,6 +5401,12 @@
|
||||
"node": ">=14.14"
|
||||
}
|
||||
},
|
||||
"node_modules/to-arraybuffer": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz",
|
||||
"integrity": "sha512-okFlQcoGTi4LQBG/PgSYblw9VOyptsz2KJZqc6qtgGdes8VktzUQkj4BI2blit072iS8VODNcMA+tvnS9dnuMA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
@@ -5015,6 +5592,18 @@
|
||||
"integrity": "sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/unescape": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/unescape/-/unescape-1.0.1.tgz",
|
||||
"integrity": "sha512-O0+af1Gs50lyH1nUu3ZyYS1cRh01Q/kUKatTOkSs7jukXE6/NebucDVxyiDsA9AQ4JC1V1jUH9EO8JX2nMDgGQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"extend-shallow": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/unzipper": {
|
||||
"version": "0.10.14",
|
||||
"resolved": "https://registry.npmmirror.com/unzipper/-/unzipper-0.10.14.tgz",
|
||||
@@ -5063,12 +5652,59 @@
|
||||
"safe-buffer": "~5.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/urllib": {
|
||||
"version": "2.44.0",
|
||||
"resolved": "https://registry.npmmirror.com/urllib/-/urllib-2.44.0.tgz",
|
||||
"integrity": "sha512-zRCJqdfYllRDA9bXUtx+vccyRqtJPKsw85f44zH7zPD28PIvjMqIgw9VwoTLV7xTBWZsbebUFVHU5ghQcWku2A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"any-promise": "^1.3.0",
|
||||
"content-type": "^1.0.2",
|
||||
"default-user-agent": "^1.0.0",
|
||||
"digest-header": "^1.0.0",
|
||||
"ee-first": "~1.1.1",
|
||||
"formstream": "^1.1.0",
|
||||
"humanize-ms": "^1.2.0",
|
||||
"iconv-lite": "^0.6.3",
|
||||
"pump": "^3.0.0",
|
||||
"qs": "^6.4.0",
|
||||
"statuses": "^1.3.1",
|
||||
"utility": "^1.16.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.10.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"proxy-agent": "^5.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"proxy-agent": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/utility": {
|
||||
"version": "1.18.0",
|
||||
"resolved": "https://registry.npmmirror.com/utility/-/utility-1.18.0.tgz",
|
||||
"integrity": "sha512-PYxZDA+6QtvRvm//++aGdmKG/cI07jNwbROz0Ql+VzFV1+Z0Dy55NI4zZ7RHc9KKpBePNFwoErqIuqQv/cjiTA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"copy-to": "^2.0.1",
|
||||
"escape-html": "^1.0.3",
|
||||
"mkdirp": "^0.5.1",
|
||||
"mz": "^2.7.0",
|
||||
"unescape": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/utrie": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/utrie/-/utrie-1.0.2.tgz",
|
||||
@@ -5094,6 +5730,27 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/win-release": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmmirror.com/win-release/-/win-release-1.1.1.tgz",
|
||||
"integrity": "sha512-iCRnKVvGxOQdsKhcQId2PXV1vV3J/sDPXKA4Oe9+Eti2nb2ESEsYHRYls/UjoUW3bIc5ZDO8dTH50A/5iVN+bw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"semver": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/win-release/node_modules/semver": {
|
||||
"version": "5.7.2",
|
||||
"resolved": "https://registry.npmmirror.com/semver/-/semver-5.7.2.tgz",
|
||||
"integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver"
|
||||
}
|
||||
},
|
||||
"node_modules/winston": {
|
||||
"version": "3.18.3",
|
||||
"resolved": "https://registry.npmmirror.com/winston/-/winston-3.18.3.tgz",
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
"@types/form-data": "^2.2.1",
|
||||
"@wecom/crypto": "^1.0.1",
|
||||
"ajv": "^8.17.1",
|
||||
"ali-oss": "^6.23.0",
|
||||
"axios": "^1.12.2",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"bullmq": "^5.65.0",
|
||||
@@ -58,6 +59,7 @@
|
||||
"zod": "^4.1.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/ali-oss": "^6.23.1",
|
||||
"@types/bcryptjs": "^2.4.6",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"@types/jsonwebtoken": "^9.0.7",
|
||||
|
||||
@@ -116,6 +116,9 @@ ORDER BY ordinal_position;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -129,6 +129,9 @@ runMigration()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -63,6 +63,9 @@ COMMENT ON COLUMN "dc_schema"."dc_tool_c_sessions"."column_mapping" IS '列名
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -90,6 +90,9 @@ COMMENT ON COLUMN dc_schema.dc_tool_c_sessions.expires_at IS '过期时间(创
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -62,3 +62,6 @@ USING gin (metadata jsonb_path_ops);
|
||||
-- SELECT indexname, indexdef FROM pg_indexes WHERE schemaname = 'ekb_schema';
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -29,3 +29,6 @@ ON "ekb_schema"."ekb_document"
|
||||
USING gin (tags);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -152,7 +152,7 @@ model Document {
|
||||
fileType String @map("file_type")
|
||||
fileSizeBytes BigInt @map("file_size_bytes")
|
||||
fileUrl String @map("file_url")
|
||||
difyDocumentId String @map("dify_document_id")
|
||||
storageKey String @map("dify_document_id") // 原 difyDocumentId,现存储 OSS 路径
|
||||
status String @default("uploading")
|
||||
progress Int @default(0)
|
||||
errorMessage String? @map("error_message")
|
||||
@@ -168,7 +168,7 @@ model Document {
|
||||
batchResults BatchResult[] @relation("DocumentBatchResults")
|
||||
knowledgeBase KnowledgeBase @relation("KnowledgeBaseDocuments", fields: [kbId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([difyDocumentId], map: "idx_pkb_documents_dify_document_id")
|
||||
@@index([storageKey], map: "idx_pkb_documents_dify_document_id") // 保留原索引名
|
||||
@@index([extractionMethod], map: "idx_pkb_documents_extraction_method")
|
||||
@@index([kbId], map: "idx_pkb_documents_kb_id")
|
||||
@@index([status], map: "idx_pkb_documents_status")
|
||||
|
||||
@@ -130,6 +130,9 @@ Write-Host ""
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -240,6 +240,9 @@ function extractCodeBlocks(obj, blocks = []) {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -41,3 +41,6 @@ CREATE TABLE IF NOT EXISTS platform_schema.job_common (
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -115,3 +115,6 @@ CREATE OR REPLACE FUNCTION platform_schema.delete_queue(queue_name text) RETURNS
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -259,6 +259,9 @@ checkDCTables();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -16,3 +16,6 @@ CREATE SCHEMA IF NOT EXISTS capability_schema;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -211,6 +211,9 @@ createAiHistoryTable()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -198,6 +198,9 @@ createToolCTable()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -195,6 +195,9 @@ createToolCTable()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -319,3 +319,6 @@ main()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -126,3 +126,6 @@ main()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
163
backend/scripts/test-oss.ts
Normal file
163
backend/scripts/test-oss.ts
Normal file
@@ -0,0 +1,163 @@
|
||||
/**
|
||||
* OSS存储适配器测试脚本
|
||||
*
|
||||
* 使用方法:
|
||||
* 1. 配置环境变量(.env 文件)
|
||||
* 2. 运行:npx tsx scripts/test-oss.ts
|
||||
*
|
||||
* 测试项:
|
||||
* - 上传文件(按 MVP 目录结构)
|
||||
* - 下载文件
|
||||
* - 检查文件存在
|
||||
* - 获取签名URL
|
||||
* - 【可选】删除文件
|
||||
*/
|
||||
|
||||
import dotenv from 'dotenv'
|
||||
import path from 'path'
|
||||
import fs from 'fs'
|
||||
import { fileURLToPath } from 'url'
|
||||
import { randomUUID } from 'crypto'
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = path.dirname(__filename)
|
||||
|
||||
// 加载环境变量
|
||||
dotenv.config({ path: path.join(__dirname, '../.env') })
|
||||
|
||||
import { StorageFactory } from '../src/common/storage/StorageFactory.js'
|
||||
|
||||
/**
|
||||
* 生成存储 Key(按 MVP 目录结构)
|
||||
*
|
||||
* 格式:tenants/{tenantId}/users/{userId}/{module}/{uuid}.{ext}
|
||||
*/
|
||||
function generateStorageKey(
|
||||
tenantId: string,
|
||||
userId: string | null,
|
||||
module: string,
|
||||
filename: string
|
||||
): string {
|
||||
const uuid = randomUUID().replace(/-/g, '').substring(0, 16)
|
||||
const ext = path.extname(filename)
|
||||
|
||||
if (userId) {
|
||||
// 用户私有数据
|
||||
return `tenants/${tenantId}/users/${userId}/${module}/${uuid}${ext}`
|
||||
} else {
|
||||
// 租户共享数据
|
||||
return `tenants/${tenantId}/shared/${module}/${uuid}${ext}`
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log('='.repeat(60))
|
||||
console.log('OSS 存储适配器测试 - MVP 目录结构验证')
|
||||
console.log('='.repeat(60))
|
||||
|
||||
// 检查环境变量
|
||||
const storageType = process.env.STORAGE_TYPE || 'local'
|
||||
console.log(`\n📦 存储类型: ${storageType}`)
|
||||
|
||||
if (storageType === 'oss') {
|
||||
console.log(` Region: ${process.env.OSS_REGION}`)
|
||||
console.log(` Bucket: ${process.env.OSS_BUCKET}`)
|
||||
console.log(` Internal: ${process.env.OSS_INTERNAL}`)
|
||||
}
|
||||
|
||||
// 获取存储实例
|
||||
const storage = StorageFactory.getInstance()
|
||||
console.log(`\n✅ 存储实例创建成功`)
|
||||
|
||||
// ============================================================
|
||||
// 测试文件:真实 PDF 文献
|
||||
// ============================================================
|
||||
const testPdfPath = path.join(__dirname, '../../docs/06-测试文档/Ihl 2011.pdf')
|
||||
|
||||
if (!fs.existsSync(testPdfPath)) {
|
||||
console.error(`\n❌ 测试文件不存在: ${testPdfPath}`)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const pdfBuffer = fs.readFileSync(testPdfPath)
|
||||
console.log(`\n📄 测试文件: Ihl 2011.pdf`)
|
||||
console.log(` 大小: ${(pdfBuffer.length / 1024).toFixed(2)} KB`)
|
||||
|
||||
// ============================================================
|
||||
// 按 MVP 目录结构生成 Key
|
||||
// ============================================================
|
||||
// 模拟:租户 yizhengxun,用户 test-user-001,模块 pkb
|
||||
const tenantId = 'yizhengxun'
|
||||
const userId = 'test-user-001'
|
||||
const module = 'pkb'
|
||||
|
||||
const storageKey = generateStorageKey(tenantId, userId, module, 'Ihl 2011.pdf')
|
||||
|
||||
console.log(`\n📁 目录结构验证:`)
|
||||
console.log(` 租户ID: ${tenantId}`)
|
||||
console.log(` 用户ID: ${userId}`)
|
||||
console.log(` 模块: ${module}`)
|
||||
console.log(` 生成Key: ${storageKey}`)
|
||||
|
||||
try {
|
||||
// 1. 上传测试
|
||||
console.log(`\n📤 上传文件到 OSS...`)
|
||||
const uploadUrl = await storage.upload(storageKey, pdfBuffer)
|
||||
console.log(` ✅ 上传成功!`)
|
||||
console.log(` 签名URL: ${uploadUrl.substring(0, 80)}...`)
|
||||
|
||||
// 2. 检查存在
|
||||
console.log(`\n🔍 验证文件存在...`)
|
||||
const exists = await storage.exists(storageKey)
|
||||
console.log(` 存在: ${exists ? '✅ 是' : '❌ 否'}`)
|
||||
|
||||
// 3. 下载验证
|
||||
console.log(`\n📥 下载验证...`)
|
||||
const downloadBuffer = await storage.download(storageKey)
|
||||
const sizeMatch = downloadBuffer.length === pdfBuffer.length
|
||||
console.log(` 下载大小: ${(downloadBuffer.length / 1024).toFixed(2)} KB`)
|
||||
console.log(` 大小匹配: ${sizeMatch ? '✅ 是' : '❌ 否'}`)
|
||||
|
||||
// 4. 获取URL(不带原始文件名)
|
||||
console.log(`\n🔗 获取访问URL...`)
|
||||
const url = storage.getUrl(storageKey)
|
||||
console.log(` URL: ${url.substring(0, 80)}...`)
|
||||
|
||||
// 5. 获取URL(带原始文件名 - 下载时恢复文件名)
|
||||
console.log(`\n📎 获取带原始文件名的URL...`)
|
||||
// 类型断言访问 OSSAdapter 的 getSignedUrl 方法
|
||||
const ossAdapter = storage as any
|
||||
if (typeof ossAdapter.getSignedUrl === 'function') {
|
||||
const urlWithFilename = ossAdapter.getSignedUrl(storageKey, 3600, 'Ihl 2011.pdf')
|
||||
console.log(` 原始文件名: Ihl 2011.pdf`)
|
||||
console.log(` URL: ${urlWithFilename.substring(0, 80)}...`)
|
||||
console.log(` ✅ 下载此URL时,浏览器会保存为 "Ihl 2011.pdf"`)
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 🔴 不删除文件!保留在 OSS 中供验证
|
||||
// ============================================================
|
||||
console.log(`\n⚠️ 文件已保留在 OSS 中,不删除!`)
|
||||
console.log(` 请登录阿里云 OSS 控制台查看:`)
|
||||
console.log(` https://oss.console.aliyun.com/bucket/oss-cn-beijing/ai-clinical-data-dev/object`)
|
||||
console.log(`\n 文件路径: ${storageKey}`)
|
||||
|
||||
// 测试完成
|
||||
console.log('\n' + '='.repeat(60))
|
||||
console.log('🎉 测试完成!请到 OSS 控制台验证目录结构')
|
||||
console.log('='.repeat(60))
|
||||
|
||||
// 输出完整信息供验证
|
||||
console.log(`\n📋 验证信息:`)
|
||||
console.log(` Bucket: ai-clinical-data-dev`)
|
||||
console.log(` Key: ${storageKey}`)
|
||||
console.log(` 预期目录: tenants/yizhengxun/users/test-user-001/pkb/`)
|
||||
|
||||
} catch (error) {
|
||||
console.error('\n❌ 测试失败:', error)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// 运行测试
|
||||
main().catch(console.error)
|
||||
@@ -342,6 +342,9 @@ runTests().catch(error => {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -92,3 +92,6 @@ testAPI().catch(console.error);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -122,3 +122,6 @@ testDeepSearch().catch(console.error);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -307,6 +307,9 @@ verifySchemas()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -199,3 +199,6 @@ export const jwtService = new JWTService();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -327,6 +327,9 @@ export function getBatchItems<T>(
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -82,3 +82,6 @@ export interface VariableValidation {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -352,3 +352,6 @@ export function chunkMarkdown(markdown: string, config?: ChunkConfig): TextChunk
|
||||
export default ChunkService;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -48,3 +48,6 @@ class DeprecatedDifyClient {
|
||||
export const difyClient = new DeprecatedDifyClient();
|
||||
export const DifyClient = DeprecatedDifyClient;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,135 +1,372 @@
|
||||
import { StorageAdapter } from './StorageAdapter.js'
|
||||
// import OSS from 'ali-oss' // ⚠️ 需要安装:npm install ali-oss
|
||||
import OSS from 'ali-oss'
|
||||
|
||||
/**
|
||||
* 阿里云OSS适配器
|
||||
* 阿里云OSS存储适配器
|
||||
*
|
||||
* 适用场景:
|
||||
* - 云端SaaS部署(阿里云Serverless)
|
||||
* - 高可用、高并发场景
|
||||
* - 需要CDN加速
|
||||
* 支持功能:
|
||||
* - 文件上传/下载/删除
|
||||
* - 私有Bucket签名URL
|
||||
* - 内网Endpoint(SAE部署零流量费)
|
||||
* - 静态Bucket公开访问
|
||||
*
|
||||
* 配置要求:
|
||||
* - OSS_REGION: OSS地域(如:oss-cn-hangzhou)
|
||||
* - OSS_BUCKET: OSS Bucket名称
|
||||
* - OSS_ACCESS_KEY_ID: AccessKey ID
|
||||
* - OSS_ACCESS_KEY_SECRET: AccessKey Secret
|
||||
* Bucket策略:
|
||||
* - ai-clinical-data: 私有,核心数据(文献、病历、报告)
|
||||
* - ai-clinical-static: 公共读,静态资源(头像、Logo、图片)
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const adapter = new OSSAdapter({
|
||||
* region: 'oss-cn-hangzhou',
|
||||
* bucket: 'aiclinical-prod',
|
||||
* region: 'oss-cn-beijing',
|
||||
* bucket: 'ai-clinical-data',
|
||||
* accessKeyId: process.env.OSS_ACCESS_KEY_ID!,
|
||||
* accessKeySecret: process.env.OSS_ACCESS_KEY_SECRET!
|
||||
* accessKeySecret: process.env.OSS_ACCESS_KEY_SECRET!,
|
||||
* internal: true // SAE内网
|
||||
* })
|
||||
* await adapter.upload('literature/123.pdf', buffer)
|
||||
* ```
|
||||
*
|
||||
* ⚠️ 当前为预留实现,待云端部署时完善
|
||||
* // 上传文件
|
||||
* const url = await adapter.upload('pkb/tenant123/user456/doc.pdf', buffer)
|
||||
*
|
||||
* // 获取签名URL(私有Bucket)
|
||||
* const signedUrl = adapter.getSignedUrl('pkb/tenant123/user456/doc.pdf')
|
||||
* ```
|
||||
*/
|
||||
|
||||
export interface OSSAdapterConfig {
|
||||
/** OSS地域,如 oss-cn-beijing */
|
||||
region: string
|
||||
/** Bucket名称 */
|
||||
bucket: string
|
||||
/** AccessKey ID */
|
||||
accessKeyId: string
|
||||
/** AccessKey Secret */
|
||||
accessKeySecret: string
|
||||
/** 是否使用内网Endpoint(SAE部署必须为true) */
|
||||
internal?: boolean
|
||||
/** 签名URL过期时间(秒),默认3600 */
|
||||
signedUrlExpires?: number
|
||||
}
|
||||
|
||||
export class OSSAdapter implements StorageAdapter {
|
||||
// private readonly client: OSS
|
||||
private readonly client: OSS
|
||||
private readonly bucket: string
|
||||
private readonly region: string
|
||||
private readonly internal: boolean
|
||||
private readonly signedUrlExpires: number
|
||||
|
||||
constructor(config: {
|
||||
region: string
|
||||
bucket: string
|
||||
accessKeyId: string
|
||||
accessKeySecret: string
|
||||
}) {
|
||||
constructor(config: OSSAdapterConfig) {
|
||||
this.region = config.region
|
||||
this.bucket = config.bucket
|
||||
this.internal = config.internal ?? false
|
||||
this.signedUrlExpires = config.signedUrlExpires ?? 3600
|
||||
|
||||
// ⚠️ TODO: 待安装 ali-oss 后取消注释
|
||||
// this.client = new OSS({
|
||||
// region: config.region,
|
||||
// bucket: config.bucket,
|
||||
// accessKeyId: config.accessKeyId,
|
||||
// accessKeySecret: config.accessKeySecret
|
||||
// })
|
||||
// 构建Endpoint
|
||||
// 内网:oss-cn-beijing-internal.aliyuncs.com(SAE零流量费)
|
||||
// 公网:oss-cn-beijing.aliyuncs.com(本地开发)
|
||||
const endpoint = this.internal
|
||||
? `${config.region}-internal.aliyuncs.com`
|
||||
: `${config.region}.aliyuncs.com`
|
||||
|
||||
this.client = new OSS({
|
||||
region: config.region,
|
||||
bucket: config.bucket,
|
||||
accessKeyId: config.accessKeyId,
|
||||
accessKeySecret: config.accessKeySecret,
|
||||
endpoint,
|
||||
// 禁用自动URL编码,避免中文文件名问题
|
||||
secure: true,
|
||||
})
|
||||
|
||||
console.log(`[OSSAdapter] Initialized: bucket=${config.bucket}, region=${config.region}, internal=${this.internal}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传文件到OSS
|
||||
*
|
||||
* @param key 存储路径,如 pkb/tenant123/user456/doc.pdf
|
||||
* @param buffer 文件内容
|
||||
* @returns 文件访问URL(私有Bucket返回签名URL)
|
||||
*/
|
||||
async upload(_key: string, _buffer: Buffer): Promise<string> {
|
||||
// ⚠️ TODO: 待实现
|
||||
// const result = await this.client.put(key, buffer)
|
||||
// return result.url
|
||||
async upload(key: string, buffer: Buffer): Promise<string> {
|
||||
try {
|
||||
const normalizedKey = this.normalizeKey(key)
|
||||
|
||||
// 使用 put 方法上传 Buffer(适合 < 30MB 文件)
|
||||
const result = await this.client.put(normalizedKey, buffer)
|
||||
|
||||
console.log(`[OSSAdapter] Upload success: ${normalizedKey}, size=${buffer.length}`)
|
||||
|
||||
// 返回签名URL(假设是私有Bucket)
|
||||
return this.getSignedUrl(normalizedKey)
|
||||
} catch (error) {
|
||||
console.error(`[OSSAdapter] Upload failed: ${key}`, error)
|
||||
throw new Error(`OSS上传失败: ${key}`)
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('[OSSAdapter] Not implemented yet. Please install ali-oss and configure OSS.')
|
||||
/**
|
||||
* 流式上传(适合大文件 > 30MB)
|
||||
*
|
||||
* @param key 存储路径
|
||||
* @param stream 可读流
|
||||
* @returns 文件访问URL
|
||||
*/
|
||||
async uploadStream(key: string, stream: NodeJS.ReadableStream): Promise<string> {
|
||||
try {
|
||||
const normalizedKey = this.normalizeKey(key)
|
||||
|
||||
// 使用 putStream 方法上传流
|
||||
const result = await this.client.putStream(normalizedKey, stream)
|
||||
|
||||
console.log(`[OSSAdapter] Stream upload success: ${normalizedKey}`)
|
||||
|
||||
return this.getSignedUrl(normalizedKey)
|
||||
} catch (error) {
|
||||
console.error(`[OSSAdapter] Stream upload failed: ${key}`, error)
|
||||
throw new Error(`OSS流式上传失败: ${key}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从OSS下载文件
|
||||
*
|
||||
* @param key 存储路径
|
||||
* @returns 文件内容 Buffer
|
||||
*/
|
||||
async download(_key: string): Promise<Buffer> {
|
||||
// ⚠️ TODO: 待实现
|
||||
// const result = await this.client.get(key)
|
||||
// return result.content as Buffer
|
||||
|
||||
throw new Error('[OSSAdapter] Not implemented yet. Please install ali-oss and configure OSS.')
|
||||
async download(key: string): Promise<Buffer> {
|
||||
try {
|
||||
const normalizedKey = this.normalizeKey(key)
|
||||
|
||||
const result = await this.client.get(normalizedKey)
|
||||
|
||||
// result.content 可能是 Buffer 或 string
|
||||
if (Buffer.isBuffer(result.content)) {
|
||||
return result.content
|
||||
}
|
||||
|
||||
// 如果是字符串,转换为 Buffer
|
||||
return Buffer.from(result.content as string)
|
||||
} catch (error: any) {
|
||||
// 处理文件不存在的情况
|
||||
if (error.code === 'NoSuchKey') {
|
||||
throw new Error(`文件不存在: ${key}`)
|
||||
}
|
||||
console.error(`[OSSAdapter] Download failed: ${key}`, error)
|
||||
throw new Error(`OSS下载失败: ${key}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从OSS删除文件
|
||||
*
|
||||
* @param key 存储路径
|
||||
*/
|
||||
async delete(_key: string): Promise<void> {
|
||||
// ⚠️ TODO: 待实现
|
||||
// await this.client.delete(key)
|
||||
|
||||
throw new Error('[OSSAdapter] Not implemented yet. Please install ali-oss and configure OSS.')
|
||||
async delete(key: string): Promise<void> {
|
||||
try {
|
||||
const normalizedKey = this.normalizeKey(key)
|
||||
|
||||
await this.client.delete(normalizedKey)
|
||||
|
||||
console.log(`[OSSAdapter] Delete success: ${normalizedKey}`)
|
||||
} catch (error: any) {
|
||||
// 删除不存在的文件不报错(幂等)
|
||||
if (error.code === 'NoSuchKey') {
|
||||
console.log(`[OSSAdapter] Delete skipped (not exists): ${key}`)
|
||||
return
|
||||
}
|
||||
console.error(`[OSSAdapter] Delete failed: ${key}`, error)
|
||||
throw new Error(`OSS删除失败: ${key}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件访问URL
|
||||
*
|
||||
* 私有Bucket:返回签名URL
|
||||
* 公共Bucket:返回直接URL
|
||||
*
|
||||
* @param key 存储路径
|
||||
* @returns 文件访问URL
|
||||
*/
|
||||
getUrl(key: string): string {
|
||||
// 返回OSS公开访问URL
|
||||
// 格式:https://{bucket}.{region}.aliyuncs.com/{key}
|
||||
return `https://${this.bucket}.${this.region}.aliyuncs.com/${key}`
|
||||
// 默认返回签名URL(适合私有Bucket)
|
||||
return this.getSignedUrl(key)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取签名URL(私有Bucket访问)
|
||||
*
|
||||
* @param key 存储路径
|
||||
* @param expires 过期时间(秒),默认使用配置值
|
||||
* @param originalFilename 原始文件名(可选,用于下载时恢复文件名)
|
||||
* @returns 签名URL
|
||||
*/
|
||||
getSignedUrl(key: string, expires?: number, originalFilename?: string): string {
|
||||
const normalizedKey = this.normalizeKey(key)
|
||||
const expireTime = expires ?? this.signedUrlExpires
|
||||
|
||||
// 生成签名URL选项
|
||||
const options: any = {
|
||||
expires: expireTime,
|
||||
}
|
||||
|
||||
// 如果提供了原始文件名,添加 Content-Disposition 头
|
||||
// 下载时浏览器会使用这个文件名
|
||||
if (originalFilename) {
|
||||
// RFC 5987 编码,支持中文文件名
|
||||
const encodedFilename = encodeURIComponent(originalFilename)
|
||||
options.response = {
|
||||
'content-disposition': `attachment; filename*=UTF-8''${encodedFilename}`,
|
||||
}
|
||||
}
|
||||
|
||||
// 生成签名URL
|
||||
const url = this.client.signatureUrl(normalizedKey, options)
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取公开访问URL(适合静态资源Bucket)
|
||||
*
|
||||
* 格式:https://{bucket}.{region}.aliyuncs.com/{key}
|
||||
*
|
||||
* @param key 存储路径
|
||||
* @returns 公开URL
|
||||
*/
|
||||
getPublicUrl(key: string): string {
|
||||
const normalizedKey = this.normalizeKey(key)
|
||||
return `https://${this.bucket}.${this.region}.aliyuncs.com/${normalizedKey}`
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查文件是否存在
|
||||
*
|
||||
* @param key 存储路径
|
||||
* @returns 是否存在
|
||||
*/
|
||||
async exists(_key: string): Promise<boolean> {
|
||||
// ⚠️ TODO: 待实现
|
||||
// try {
|
||||
// await this.client.head(key)
|
||||
// return true
|
||||
// } catch (error) {
|
||||
// return false
|
||||
// }
|
||||
async exists(key: string): Promise<boolean> {
|
||||
try {
|
||||
const normalizedKey = this.normalizeKey(key)
|
||||
|
||||
await this.client.head(normalizedKey)
|
||||
return true
|
||||
} catch (error: any) {
|
||||
if (error.code === 'NoSuchKey') {
|
||||
return false
|
||||
}
|
||||
// 其他错误抛出
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('[OSSAdapter] Not implemented yet. Please install ali-oss and configure OSS.')
|
||||
/**
|
||||
* 获取文件元信息
|
||||
*
|
||||
* @param key 存储路径
|
||||
* @returns 文件元信息(大小、类型、最后修改时间等)
|
||||
*/
|
||||
async getMetadata(key: string): Promise<{
|
||||
size: number
|
||||
contentType: string
|
||||
lastModified: Date
|
||||
} | null> {
|
||||
try {
|
||||
const normalizedKey = this.normalizeKey(key)
|
||||
|
||||
const result = await this.client.head(normalizedKey)
|
||||
|
||||
return {
|
||||
size: parseInt(result.res.headers['content-length'] as string, 10),
|
||||
contentType: result.res.headers['content-type'] as string,
|
||||
lastModified: new Date(result.res.headers['last-modified'] as string),
|
||||
}
|
||||
} catch (error: any) {
|
||||
if (error.code === 'NoSuchKey') {
|
||||
return null
|
||||
}
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除文件
|
||||
*
|
||||
* @param keys 存储路径数组
|
||||
*/
|
||||
async deleteMany(keys: string[]): Promise<void> {
|
||||
if (keys.length === 0) return
|
||||
|
||||
try {
|
||||
const normalizedKeys = keys.map(k => this.normalizeKey(k))
|
||||
|
||||
// OSS支持批量删除,最多1000个
|
||||
await this.client.deleteMulti(normalizedKeys, {
|
||||
quiet: true, // 静默模式,不返回删除结果
|
||||
})
|
||||
|
||||
console.log(`[OSSAdapter] Batch delete success: ${keys.length} files`)
|
||||
} catch (error) {
|
||||
console.error(`[OSSAdapter] Batch delete failed`, error)
|
||||
throw new Error(`OSS批量删除失败`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 列出目录下的文件
|
||||
*
|
||||
* @param prefix 目录前缀,如 pkb/tenant123/
|
||||
* @param maxKeys 最大返回数量
|
||||
* @returns 文件列表
|
||||
*/
|
||||
async list(prefix: string, maxKeys: number = 1000): Promise<{
|
||||
key: string
|
||||
size: number
|
||||
lastModified: Date
|
||||
}[]> {
|
||||
try {
|
||||
const normalizedPrefix = this.normalizeKey(prefix)
|
||||
|
||||
const result = await this.client.list({
|
||||
prefix: normalizedPrefix,
|
||||
'max-keys': maxKeys,
|
||||
}, {})
|
||||
|
||||
return (result.objects || []).map(obj => ({
|
||||
key: obj.name,
|
||||
size: obj.size,
|
||||
lastModified: new Date(obj.lastModified),
|
||||
}))
|
||||
} catch (error) {
|
||||
console.error(`[OSSAdapter] List failed: ${prefix}`, error)
|
||||
throw new Error(`OSS列表失败: ${prefix}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制文件
|
||||
*
|
||||
* @param sourceKey 源路径
|
||||
* @param targetKey 目标路径
|
||||
*/
|
||||
async copy(sourceKey: string, targetKey: string): Promise<void> {
|
||||
try {
|
||||
const normalizedSource = this.normalizeKey(sourceKey)
|
||||
const normalizedTarget = this.normalizeKey(targetKey)
|
||||
|
||||
await this.client.copy(normalizedTarget, normalizedSource)
|
||||
|
||||
console.log(`[OSSAdapter] Copy success: ${sourceKey} -> ${targetKey}`)
|
||||
} catch (error) {
|
||||
console.error(`[OSSAdapter] Copy failed: ${sourceKey} -> ${targetKey}`, error)
|
||||
throw new Error(`OSS复制失败`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 规范化Key(移除开头的斜杠)
|
||||
*/
|
||||
private normalizeKey(key: string): string {
|
||||
return key.replace(/^\/+/, '')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ⚠️ 实施说明:
|
||||
*
|
||||
* 1. 安装依赖:
|
||||
* npm install ali-oss
|
||||
* npm install -D @types/ali-oss
|
||||
*
|
||||
* 2. 取消注释代码:
|
||||
* - import OSS from 'ali-oss'
|
||||
* - new OSS({ ... })
|
||||
* - 所有方法的实现代码
|
||||
*
|
||||
* 3. 配置环境变量:
|
||||
* OSS_REGION=oss-cn-hangzhou
|
||||
* OSS_BUCKET=aiclinical-prod
|
||||
* OSS_ACCESS_KEY_ID=your-access-key-id
|
||||
* OSS_ACCESS_KEY_SECRET=your-access-key-secret
|
||||
*
|
||||
* 4. 测试:
|
||||
* - 上传小文件
|
||||
* - 下载文件
|
||||
* - 删除文件
|
||||
* - 检查文件是否存在
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { StorageAdapter } from './StorageAdapter.js'
|
||||
import { LocalAdapter } from './LocalAdapter.js'
|
||||
import { OSSAdapter } from './OSSAdapter.js'
|
||||
import { OSSAdapter, OSSAdapterConfig } from './OSSAdapter.js'
|
||||
|
||||
/**
|
||||
* 存储工厂类
|
||||
@@ -9,23 +9,39 @@ import { OSSAdapter } from './OSSAdapter.js'
|
||||
* - STORAGE_TYPE=local: 使用LocalAdapter(本地文件系统)
|
||||
* - STORAGE_TYPE=oss: 使用OSSAdapter(阿里云OSS)
|
||||
*
|
||||
* 支持双Bucket策略:
|
||||
* - Data Bucket(私有): 核心数据,文献、病历、报告
|
||||
* - Static Bucket(公共读): 静态资源,头像、Logo、图片
|
||||
*
|
||||
* 零代码切换:
|
||||
* - 本地开发:不配置STORAGE_TYPE,默认使用local
|
||||
* - 云端部署:配置STORAGE_TYPE=oss,自动切换到OSS
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { storage } from '@/common/storage'
|
||||
* import { storage, staticStorage } from '@/common/storage'
|
||||
*
|
||||
* // 业务代码不关心是local还是oss
|
||||
* const url = await storage.upload('literature/123.pdf', buffer)
|
||||
* // 上传私有文件(自动使用签名URL)
|
||||
* const url = await storage.upload('pkb/tenant/user/doc.pdf', buffer)
|
||||
*
|
||||
* // 上传静态资源(公开访问)
|
||||
* const avatarUrl = await staticStorage.upload('avatars/user123.jpg', buffer)
|
||||
* ```
|
||||
*/
|
||||
export class StorageFactory {
|
||||
/** 主存储实例(私有数据) */
|
||||
private static instance: StorageAdapter | null = null
|
||||
|
||||
/** 静态存储实例(公共资源) */
|
||||
private static staticInstance: StorageAdapter | null = null
|
||||
|
||||
/**
|
||||
* 获取存储适配器实例(单例模式)
|
||||
* 获取主存储适配器实例(单例模式)
|
||||
*
|
||||
* 用于存储私有数据:
|
||||
* - PKB文献文件
|
||||
* - 审稿PDF报告
|
||||
* - 临床数据文件
|
||||
*/
|
||||
static getInstance(): StorageAdapter {
|
||||
if (!this.instance) {
|
||||
@@ -35,7 +51,22 @@ export class StorageFactory {
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建存储适配器
|
||||
* 获取静态资源存储适配器实例(单例模式)
|
||||
*
|
||||
* 用于存储公共资源:
|
||||
* - 用户头像
|
||||
* - 系统Logo
|
||||
* - RAG引用的图片
|
||||
*/
|
||||
static getStaticInstance(): StorageAdapter {
|
||||
if (!this.staticInstance) {
|
||||
this.staticInstance = this.createStaticAdapter()
|
||||
}
|
||||
return this.staticInstance
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建主存储适配器(私有数据)
|
||||
*/
|
||||
private static createAdapter(): StorageAdapter {
|
||||
const storageType = process.env.STORAGE_TYPE || 'local'
|
||||
@@ -54,41 +85,99 @@ export class StorageFactory {
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建本地适配器
|
||||
* 创建静态资源存储适配器
|
||||
*/
|
||||
private static createLocalAdapter(): LocalAdapter {
|
||||
const baseDir = process.env.LOCAL_STORAGE_DIR || 'uploads'
|
||||
const baseUrl = process.env.LOCAL_STORAGE_URL || 'http://localhost:3001/uploads'
|
||||
|
||||
console.log(`[StorageFactory] Using LocalAdapter (baseDir: ${baseDir})`)
|
||||
|
||||
return new LocalAdapter(baseDir, baseUrl)
|
||||
private static createStaticAdapter(): StorageAdapter {
|
||||
const storageType = process.env.STORAGE_TYPE || 'local'
|
||||
|
||||
switch (storageType) {
|
||||
case 'local':
|
||||
// 本地模式下,静态资源也用同一个目录
|
||||
return this.createLocalAdapter('uploads/static', 'http://localhost:3001/uploads/static')
|
||||
|
||||
case 'oss':
|
||||
return this.createOSSStaticAdapter()
|
||||
|
||||
default:
|
||||
return this.createLocalAdapter('uploads/static', 'http://localhost:3001/uploads/static')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建OSS适配器
|
||||
* 创建本地适配器
|
||||
*/
|
||||
private static createLocalAdapter(
|
||||
baseDir?: string,
|
||||
baseUrl?: string
|
||||
): LocalAdapter {
|
||||
const dir = baseDir || process.env.LOCAL_STORAGE_DIR || 'uploads'
|
||||
const url = baseUrl || process.env.LOCAL_STORAGE_URL || 'http://localhost:3001/uploads'
|
||||
|
||||
console.log(`[StorageFactory] Using LocalAdapter (baseDir: ${dir})`)
|
||||
|
||||
return new LocalAdapter(dir, url)
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建OSS适配器(私有数据Bucket)
|
||||
*/
|
||||
private static createOSSAdapter(): OSSAdapter {
|
||||
const config = this.getOSSConfig()
|
||||
|
||||
console.log(`[StorageFactory] Using OSSAdapter (region: ${config.region}, bucket: ${config.bucket}, internal: ${config.internal})`)
|
||||
|
||||
return new OSSAdapter(config)
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建OSS静态资源适配器(公共Bucket)
|
||||
*/
|
||||
private static createOSSStaticAdapter(): OSSAdapter {
|
||||
const baseConfig = this.getOSSConfig()
|
||||
|
||||
// 静态资源使用独立的Bucket
|
||||
const staticBucket = process.env.OSS_BUCKET_STATIC || `${baseConfig.bucket}-static`
|
||||
|
||||
console.log(`[StorageFactory] Using OSSAdapter for static (bucket: ${staticBucket})`)
|
||||
|
||||
return new OSSAdapter({
|
||||
...baseConfig,
|
||||
bucket: staticBucket,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取OSS配置(带验证)
|
||||
*/
|
||||
private static getOSSConfig(): OSSAdapterConfig {
|
||||
const region = process.env.OSS_REGION
|
||||
const bucket = process.env.OSS_BUCKET
|
||||
const accessKeyId = process.env.OSS_ACCESS_KEY_ID
|
||||
const accessKeySecret = process.env.OSS_ACCESS_KEY_SECRET
|
||||
const internal = process.env.OSS_INTERNAL === 'true'
|
||||
const signedUrlExpires = parseInt(process.env.OSS_SIGNED_URL_EXPIRES || '3600', 10)
|
||||
|
||||
// 验证必需的环境变量
|
||||
if (!region || !bucket || !accessKeyId || !accessKeySecret) {
|
||||
const missing = []
|
||||
if (!region) missing.push('OSS_REGION')
|
||||
if (!bucket) missing.push('OSS_BUCKET')
|
||||
if (!accessKeyId) missing.push('OSS_ACCESS_KEY_ID')
|
||||
if (!accessKeySecret) missing.push('OSS_ACCESS_KEY_SECRET')
|
||||
|
||||
throw new Error(
|
||||
'[StorageFactory] OSS configuration incomplete. Required: OSS_REGION, OSS_BUCKET, OSS_ACCESS_KEY_ID, OSS_ACCESS_KEY_SECRET'
|
||||
`[StorageFactory] OSS configuration incomplete. Missing: ${missing.join(', ')}`
|
||||
)
|
||||
}
|
||||
|
||||
console.log(`[StorageFactory] Using OSSAdapter (region: ${region}, bucket: ${bucket})`)
|
||||
|
||||
return new OSSAdapter({
|
||||
return {
|
||||
region,
|
||||
bucket,
|
||||
accessKeyId,
|
||||
accessKeySecret
|
||||
})
|
||||
accessKeySecret,
|
||||
internal,
|
||||
signedUrlExpires,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -96,6 +185,6 @@ export class StorageFactory {
|
||||
*/
|
||||
static reset(): void {
|
||||
this.instance = null
|
||||
this.staticInstance = null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,40 +3,63 @@
|
||||
*
|
||||
* 提供平台级的文件存储能力,支持本地和云端无缝切换。
|
||||
*
|
||||
* 双Bucket策略:
|
||||
* - storage: 私有数据(文献、病历、报告),使用签名URL
|
||||
* - staticStorage: 静态资源(头像、Logo、图片),公开访问
|
||||
*
|
||||
* @module storage
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // 方式1:使用单例(推荐)
|
||||
* import { storage } from '@/common/storage'
|
||||
* const url = await storage.upload('literature/123.pdf', buffer)
|
||||
* import { storage, staticStorage } from '@/common/storage'
|
||||
*
|
||||
* // 方式2:直接使用适配器
|
||||
* import { LocalAdapter } from '@/common/storage'
|
||||
* const adapter = new LocalAdapter()
|
||||
* const url = await adapter.upload('literature/123.pdf', buffer)
|
||||
* // 上传私有文件
|
||||
* const url = await storage.upload('pkb/tenant/user/doc.pdf', buffer)
|
||||
*
|
||||
* // 方式3:使用工厂
|
||||
* // 上传静态资源
|
||||
* const avatarUrl = await staticStorage.upload('avatars/user123.jpg', buffer)
|
||||
*
|
||||
* // 方式2:使用工厂
|
||||
* import { StorageFactory } from '@/common/storage'
|
||||
* const storage = StorageFactory.getInstance()
|
||||
* const url = await storage.upload('literature/123.pdf', buffer)
|
||||
* const dataStorage = StorageFactory.getInstance()
|
||||
* const staticStorage = StorageFactory.getStaticInstance()
|
||||
* ```
|
||||
*/
|
||||
|
||||
export type { StorageAdapter } from './StorageAdapter.js'
|
||||
export { LocalAdapter } from './LocalAdapter.js'
|
||||
export { OSSAdapter } from './OSSAdapter.js'
|
||||
export type { OSSAdapterConfig } from './OSSAdapter.js'
|
||||
export { StorageFactory } from './StorageFactory.js'
|
||||
|
||||
// Import for usage below
|
||||
import { StorageFactory } from './StorageFactory.js'
|
||||
|
||||
/**
|
||||
* 全局存储实例(推荐使用)
|
||||
* 全局主存储实例(私有数据)
|
||||
*
|
||||
* 自动根据环境变量选择存储实现:
|
||||
* - STORAGE_TYPE=local: 本地文件系统
|
||||
* - STORAGE_TYPE=oss: 阿里云OSS
|
||||
* - STORAGE_TYPE=oss: 阿里云OSS(私有Bucket,签名URL)
|
||||
*
|
||||
* 使用场景:
|
||||
* - PKB文献文件
|
||||
* - 审稿PDF报告
|
||||
* - 临床数据文件
|
||||
*/
|
||||
export const storage = StorageFactory.getInstance()
|
||||
|
||||
/**
|
||||
* 全局静态资源存储实例
|
||||
*
|
||||
* 自动根据环境变量选择存储实现:
|
||||
* - STORAGE_TYPE=local: 本地文件系统
|
||||
* - STORAGE_TYPE=oss: 阿里云OSS(公共读Bucket)
|
||||
*
|
||||
* 使用场景:
|
||||
* - 用户头像
|
||||
* - 系统Logo
|
||||
* - RAG引用的图片
|
||||
*/
|
||||
export const staticStorage = StorageFactory.getStaticInstance()
|
||||
|
||||
@@ -203,3 +203,6 @@ export function createOpenAIStreamAdapter(
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -209,3 +209,6 @@ export async function streamChat(
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -27,3 +27,6 @@ export { THINKING_TAGS } from './types';
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -102,3 +102,6 @@ export type SSEEventType =
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -69,14 +69,23 @@ export const config = {
|
||||
/** 阿里云OSS地域 */
|
||||
ossRegion: process.env.OSS_REGION,
|
||||
|
||||
/** 阿里云OSS Bucket名称 */
|
||||
/** 阿里云OSS Bucket名称(私有数据) */
|
||||
ossBucket: process.env.OSS_BUCKET,
|
||||
|
||||
/** 阿里云OSS 静态资源Bucket名称(公共读) */
|
||||
ossBucketStatic: process.env.OSS_BUCKET_STATIC,
|
||||
|
||||
/** 阿里云OSS AccessKey ID */
|
||||
ossAccessKeyId: process.env.OSS_ACCESS_KEY_ID,
|
||||
|
||||
/** 阿里云OSS AccessKey Secret */
|
||||
ossAccessKeySecret: process.env.OSS_ACCESS_KEY_SECRET,
|
||||
|
||||
/** 是否使用OSS内网Endpoint(SAE部署必须为true) */
|
||||
ossInternal: process.env.OSS_INTERNAL === 'true',
|
||||
|
||||
/** OSS签名URL过期时间(秒) */
|
||||
ossSignedUrlExpires: parseInt(process.env.OSS_SIGNED_URL_EXPIRES || '3600', 10),
|
||||
|
||||
// ==================== 缓存配置(平台基础设施)====================
|
||||
|
||||
@@ -150,8 +159,8 @@ export const config = {
|
||||
|
||||
// ==================== 文件上传配置(Legacy兼容)====================
|
||||
|
||||
/** 文件上传大小限制 */
|
||||
uploadMaxSize: parseInt(process.env.UPLOAD_MAX_SIZE || '10485760', 10), // 10MB
|
||||
/** 文件上传大小限制(30MB) */
|
||||
uploadMaxSize: parseInt(process.env.UPLOAD_MAX_SIZE || '31457280', 10), // 30MB
|
||||
|
||||
/** 文件上传目录(Legacy兼容,新模块使用storage) */
|
||||
uploadDir: process.env.UPLOAD_DIR || './uploads',
|
||||
@@ -185,6 +194,16 @@ export function validateEnv(): void {
|
||||
if (!config.ossBucket) errors.push('OSS_BUCKET is required when STORAGE_TYPE=oss')
|
||||
if (!config.ossAccessKeyId) errors.push('OSS_ACCESS_KEY_ID is required when STORAGE_TYPE=oss')
|
||||
if (!config.ossAccessKeySecret) errors.push('OSS_ACCESS_KEY_SECRET is required when STORAGE_TYPE=oss')
|
||||
|
||||
// 可选配置警告
|
||||
if (!config.ossBucketStatic) {
|
||||
warnings.push('OSS_BUCKET_STATIC not set, static resources will use main bucket')
|
||||
}
|
||||
|
||||
// 生产环境必须使用内网
|
||||
if (config.nodeEnv === 'production' && !config.ossInternal) {
|
||||
warnings.push('OSS_INTERNAL should be true in production (SAE内网访问免流量费)')
|
||||
}
|
||||
}
|
||||
|
||||
// 如果使用Redis,验证Redis配置
|
||||
|
||||
@@ -275,11 +275,12 @@ export class ChatController {
|
||||
select: {
|
||||
id: true,
|
||||
filename: true,
|
||||
difyDocumentId: true,
|
||||
storageKey: true, // 原 difyDocumentId,现为 OSS 路径
|
||||
},
|
||||
});
|
||||
|
||||
const difyDocIds = documents.map(d => d.difyDocumentId).filter(Boolean);
|
||||
// Legacy: 此代码已废弃,Dify 已移除
|
||||
const difyDocIds = documents.map(d => d.storageKey).filter(Boolean);
|
||||
console.log(`📄 [ChatController] 目标Dify文档ID:`, difyDocIds);
|
||||
|
||||
// 过滤结果
|
||||
|
||||
@@ -48,7 +48,7 @@ export async function uploadDocument(
|
||||
fileType,
|
||||
fileSizeBytes,
|
||||
fileUrl,
|
||||
difyDocumentId: '', // 暂时为空,稍后更新
|
||||
storageKey: '', // 原 difyDocumentId,现为 OSS 路径(Legacy 代码已废弃)
|
||||
status: 'uploading',
|
||||
progress: 0,
|
||||
},
|
||||
@@ -88,11 +88,11 @@ export async function uploadDocument(
|
||||
filename
|
||||
);
|
||||
|
||||
// 6. 更新文档记录(更新difyDocumentId、状态和Phase 2字段)
|
||||
// 6. 更新文档记录(Legacy:此代码已废弃,Dify 已移除)
|
||||
const updatedDocument = await prisma.document.update({
|
||||
where: { id: document.id },
|
||||
data: {
|
||||
difyDocumentId: difyResult.document.id,
|
||||
storageKey: difyResult.document.id, // Legacy: 原为 difyDocumentId
|
||||
status: difyResult.document.indexing_status,
|
||||
progress: 50,
|
||||
// Phase 2新增字段
|
||||
@@ -138,7 +138,7 @@ async function pollDocumentStatus(
|
||||
userId: string,
|
||||
kbId: string,
|
||||
documentId: string,
|
||||
difyDocumentId: string,
|
||||
legacyDifyDocId: string, // Legacy: 原为 difyDocumentId
|
||||
maxAttempts: number = 30
|
||||
) {
|
||||
const knowledgeBase = await prisma.knowledgeBase.findFirst({
|
||||
@@ -156,7 +156,7 @@ async function pollDocumentStatus(
|
||||
// 查询Dify中的文档状态
|
||||
const difyDocument = await difyClient.getDocument(
|
||||
knowledgeBase.difyDatasetId,
|
||||
difyDocumentId
|
||||
legacyDifyDocId
|
||||
);
|
||||
|
||||
// 更新数据库中的状态
|
||||
@@ -264,12 +264,12 @@ export async function deleteDocument(userId: string, documentId: string) {
|
||||
throw new Error('Document not found or access denied');
|
||||
}
|
||||
|
||||
// 2. 删除Dify中的文档
|
||||
if (document.difyDocumentId) {
|
||||
// 2. 删除Dify中的文档(Legacy:此代码已废弃,Dify 已移除)
|
||||
if (document.storageKey) {
|
||||
try {
|
||||
await difyClient.deleteDocument(
|
||||
document.knowledgeBase.difyDatasetId,
|
||||
document.difyDocumentId
|
||||
document.storageKey // Legacy: 原为 difyDocumentId
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Failed to delete Dify document:', error);
|
||||
@@ -305,12 +305,12 @@ export async function reprocessDocument(userId: string, documentId: string) {
|
||||
throw new Error('Document not found or access denied');
|
||||
}
|
||||
|
||||
// 2. 触发Dify重新索引
|
||||
if (document.difyDocumentId) {
|
||||
// 2. 触发Dify重新索引(Legacy:此代码已废弃,Dify 已移除)
|
||||
if (document.storageKey) {
|
||||
try {
|
||||
await difyClient.updateDocument(
|
||||
document.knowledgeBase.difyDatasetId,
|
||||
document.difyDocumentId
|
||||
document.storageKey // Legacy: 原为 difyDocumentId
|
||||
);
|
||||
|
||||
// 3. 更新状态为processing
|
||||
@@ -328,7 +328,7 @@ export async function reprocessDocument(userId: string, documentId: string) {
|
||||
userId,
|
||||
document.kbId,
|
||||
documentId,
|
||||
document.difyDocumentId
|
||||
document.storageKey // Legacy: 原为 difyDocumentId
|
||||
).catch(error => {
|
||||
console.error('Failed to poll document status:', error);
|
||||
});
|
||||
|
||||
@@ -88,3 +88,6 @@ export async function moduleRoutes(fastify: FastifyInstance) {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -118,3 +118,6 @@ export interface PaginatedResponse<T> {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -165,3 +165,6 @@ export const ROLE_DISPLAY_NAMES: Record<UserRole, string> = {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -240,3 +240,6 @@ async function matchIntent(query: string): Promise<{
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -94,3 +94,6 @@ export async function uploadAttachment(
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -23,3 +23,6 @@ export { aiaRoutes };
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -363,6 +363,9 @@ runTests().catch((error) => {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -304,6 +304,9 @@ runTest()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -342,6 +342,9 @@ Content-Type: application/json
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -278,6 +278,9 @@ export const conflictDetectionService = new ConflictDetectionService();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -228,6 +228,9 @@ curl -X POST http://localhost:3000/api/v1/dc/tool-c/test/execute \
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -282,6 +282,9 @@ export const streamAIController = new StreamAIController();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -191,6 +191,9 @@ logger.info('[SessionMemory] 会话记忆管理器已启动', {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -125,6 +125,9 @@ checkTableStructure();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -112,6 +112,9 @@ checkProjectConfig().catch(console.error);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -94,6 +94,9 @@ main();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -551,6 +551,9 @@ URL: https://iit.xunzhengyixue.com/api/v1/iit/patient-wechat/callback
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -186,6 +186,9 @@ console.log('');
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -503,6 +503,9 @@ export const patientWechatService = new PatientWechatService();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -148,6 +148,9 @@ testDifyIntegration().catch(error => {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -177,6 +177,9 @@ testIitDatabase()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -163,6 +163,9 @@ if (hasError) {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -189,6 +189,9 @@ async function testUrlVerification() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -270,6 +270,9 @@ main().catch((error) => {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -154,6 +154,9 @@ Write-Host ""
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -247,6 +247,9 @@ export interface CachedProtocolRules {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import type { FastifyRequest, FastifyReply } from 'fastify';
|
||||
import * as documentService from '../services/documentService.js';
|
||||
import { storage } from '../../../common/storage/index.js';
|
||||
import { randomUUID } from 'crypto';
|
||||
import path from 'path';
|
||||
|
||||
/**
|
||||
* 获取用户ID(从JWT Token中获取)
|
||||
@@ -12,6 +15,30 @@ function getUserId(request: FastifyRequest): string {
|
||||
return userId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取租户ID(从JWT Token中获取)
|
||||
*/
|
||||
function getTenantId(request: FastifyRequest): string {
|
||||
const tenantId = (request as any).user?.tenantId;
|
||||
// 如果没有租户ID,使用默认值
|
||||
return tenantId || 'default';
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成 PKB 文档存储 Key
|
||||
* 格式:tenants/{tenantId}/users/{userId}/pkb/{kbId}/{uuid}.{ext}
|
||||
*/
|
||||
function generatePkbStorageKey(
|
||||
tenantId: string,
|
||||
userId: string,
|
||||
kbId: string,
|
||||
filename: string
|
||||
): string {
|
||||
const uuid = randomUUID().replace(/-/g, '').substring(0, 16);
|
||||
const ext = path.extname(filename).toLowerCase();
|
||||
return `tenants/${tenantId}/users/${userId}/pkb/${kbId}/${uuid}${ext}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传文档
|
||||
*/
|
||||
@@ -45,15 +72,15 @@ export async function uploadDocument(
|
||||
const fileType = data.mimetype;
|
||||
const fileSizeBytes = file.length;
|
||||
|
||||
// 文件大小限制(10MB)
|
||||
const maxSize = 10 * 1024 * 1024;
|
||||
console.log(`📊 文件大小: ${(fileSizeBytes / 1024 / 1024).toFixed(2)}MB (限制: 10MB)`);
|
||||
// 文件大小限制(30MB - 按 OSS 规范)
|
||||
const maxSize = 30 * 1024 * 1024;
|
||||
console.log(`📊 文件大小: ${(fileSizeBytes / 1024 / 1024).toFixed(2)}MB (限制: 30MB)`);
|
||||
|
||||
if (fileSizeBytes > maxSize) {
|
||||
console.error(`❌ 文件太大: ${(fileSizeBytes / 1024 / 1024).toFixed(2)}MB`);
|
||||
return reply.status(400).send({
|
||||
success: false,
|
||||
message: 'File size exceeds 10MB limit',
|
||||
message: 'File size exceeds 30MB limit',
|
||||
});
|
||||
}
|
||||
|
||||
@@ -75,9 +102,30 @@ export async function uploadDocument(
|
||||
});
|
||||
}
|
||||
|
||||
// 上传文档(这里fileUrl暂时为空,实际应该上传到对象存储)
|
||||
console.log(`⚙️ 调用文档服务上传文件...`);
|
||||
// 获取用户信息
|
||||
const userId = getUserId(request);
|
||||
const tenantId = getTenantId(request);
|
||||
|
||||
// 生成 OSS 存储 Key(包含 kbId)
|
||||
const storageKey = generatePkbStorageKey(tenantId, userId, kbId, filename);
|
||||
console.log(`📦 OSS 存储路径: ${storageKey}`);
|
||||
|
||||
// 上传到 OSS
|
||||
console.log(`☁️ 上传文件到存储服务...`);
|
||||
let fileUrl = '';
|
||||
try {
|
||||
fileUrl = await storage.upload(storageKey, file);
|
||||
console.log(`✅ 文件已上传到存储服务`);
|
||||
} catch (storageError) {
|
||||
console.error(`❌ 存储服务上传失败:`, storageError);
|
||||
return reply.status(500).send({
|
||||
success: false,
|
||||
message: 'Failed to upload file to storage',
|
||||
});
|
||||
}
|
||||
|
||||
// 调用文档服务处理(传入 storageKey)
|
||||
console.log(`⚙️ 调用文档服务处理文件...`);
|
||||
const document = await documentService.uploadDocument(
|
||||
userId,
|
||||
kbId,
|
||||
@@ -85,7 +133,8 @@ export async function uploadDocument(
|
||||
filename,
|
||||
fileType,
|
||||
fileSizeBytes,
|
||||
'' // fileUrl - 可以上传到OSS后填入
|
||||
fileUrl,
|
||||
storageKey // 新增:存储路径
|
||||
);
|
||||
|
||||
console.log(`✅ 文档上传成功: ${document.id}`);
|
||||
|
||||
@@ -60,6 +60,9 @@ export default async function healthRoutes(fastify: FastifyInstance) {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { prisma } from '../../../config/database.js';
|
||||
import { logger } from '../../../common/logging/index.js';
|
||||
import { extractionClient } from '../../../common/document/ExtractionClient.js';
|
||||
import { ingestDocument as ragIngestDocument } from './ragService.js';
|
||||
import { storage } from '../../../common/storage/index.js';
|
||||
|
||||
/**
|
||||
* 文档服务
|
||||
@@ -11,6 +12,15 @@ import { ingestDocument as ragIngestDocument } from './ragService.js';
|
||||
|
||||
/**
|
||||
* 上传文档到知识库
|
||||
*
|
||||
* @param userId - 用户ID
|
||||
* @param kbId - 知识库ID
|
||||
* @param file - 文件内容 Buffer
|
||||
* @param filename - 原始文件名
|
||||
* @param fileType - MIME 类型
|
||||
* @param fileSizeBytes - 文件大小(字节)
|
||||
* @param fileUrl - 文件访问 URL(签名URL)
|
||||
* @param storageKey - OSS 存储路径(新增)
|
||||
*/
|
||||
export async function uploadDocument(
|
||||
userId: string,
|
||||
@@ -19,7 +29,8 @@ export async function uploadDocument(
|
||||
filename: string,
|
||||
fileType: string,
|
||||
fileSizeBytes: number,
|
||||
fileUrl: string
|
||||
fileUrl: string,
|
||||
storageKey?: string // 新增:OSS 存储路径
|
||||
) {
|
||||
// 1. 验证知识库权限
|
||||
const knowledgeBase = await prisma.knowledgeBase.findFirst({
|
||||
@@ -65,7 +76,7 @@ export async function uploadDocument(
|
||||
fileType,
|
||||
fileSizeBytes,
|
||||
fileUrl,
|
||||
difyDocumentId: '', // 不再使用
|
||||
storageKey: storageKey || '', // OSS 存储路径
|
||||
status: 'uploading',
|
||||
progress: 0,
|
||||
},
|
||||
@@ -107,10 +118,10 @@ export async function uploadDocument(
|
||||
});
|
||||
|
||||
// 7. 更新文档记录 - pgvector 模式立即完成
|
||||
// 注意:storageKey 已在创建时设置,这里不需要更新
|
||||
const updatedDocument = await prisma.document.update({
|
||||
where: { id: document.id },
|
||||
data: {
|
||||
difyDocumentId: ingestResult.documentId || '',
|
||||
status: 'completed',
|
||||
progress: 100,
|
||||
// 提取信息
|
||||
@@ -234,13 +245,23 @@ export async function deleteDocument(userId: string, documentId: string) {
|
||||
|
||||
logger.info(`[PKB] 删除文档: documentId=${documentId}`);
|
||||
|
||||
// 1.5 删除 OSS 文件
|
||||
if (document.storageKey) {
|
||||
try {
|
||||
await storage.delete(document.storageKey);
|
||||
logger.info(`[PKB] OSS 文件已删除: ${document.storageKey}`);
|
||||
} catch (storageError) {
|
||||
logger.warn(`[PKB] OSS 文件删除失败,继续删除数据库记录`, { storageError });
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 删除 EKB 中的文档和 Chunks
|
||||
try {
|
||||
// 查找 EKB 文档(通过 filename 和 kbId 匹配)
|
||||
const ekbDoc = await prisma.ekbDocument.findFirst({
|
||||
where: {
|
||||
filename: document.filename,
|
||||
kb: {
|
||||
knowledgeBase: {
|
||||
ownerId: userId,
|
||||
name: document.knowledgeBase.name,
|
||||
},
|
||||
@@ -311,7 +332,7 @@ export async function reprocessDocument(userId: string, documentId: string) {
|
||||
const ekbDoc = await prisma.ekbDocument.findFirst({
|
||||
where: {
|
||||
filename: document.filename,
|
||||
kb: {
|
||||
knowledgeBase: {
|
||||
ownerId: userId,
|
||||
name: document.knowledgeBase.name,
|
||||
},
|
||||
|
||||
@@ -140,5 +140,8 @@ Content-Type: application/json
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -125,5 +125,8 @@ Write-Host " - 删除任务: DELETE $BaseUrl/api/v1/rvw/tasks/{taskId}" -Foregr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -39,5 +39,8 @@ export * from './services/utils.js';
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -130,5 +130,8 @@ export function validateAgentSelection(agents: string[]): void {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -428,6 +428,9 @@ SET session_replication_role = 'origin';
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -110,3 +110,6 @@ async function testCrossLanguageSearch() {
|
||||
testCrossLanguageSearch();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -172,3 +172,6 @@ async function testQueryRewrite() {
|
||||
testQueryRewrite();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -118,3 +118,6 @@ async function testRerank() {
|
||||
testRerank();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user