修改上传
This commit is contained in:
parent
f2b9918408
commit
9fc98a6fd5
@ -1,5 +1,14 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": ["Bash", "ReadFiles", "WriteFiles", "EditFiles"]
|
||||
"allow": [
|
||||
"Bash",
|
||||
"ReadFiles",
|
||||
"WriteFiles",
|
||||
"EditFiles",
|
||||
"WebFetch(domain:3d.hunyuan.tencent.com)",
|
||||
"WebSearch",
|
||||
"WebFetch(domain:cloud.tencent.com)",
|
||||
"WebFetch(domain:cloud.tencent.com.cn)"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
207
.claude/skills/my-coding-standard/SKLII.md
Normal file
207
.claude/skills/my-coding-standard/SKLII.md
Normal file
@ -0,0 +1,207 @@
|
||||
需求确认
|
||||
|
||||
- AI服务:先用 Mock 数据开发,后期接入真实API(腾讯混元3D/Meshy)
|
||||
- 功能入口:独立页面
|
||||
- 生成历史:需要保存
|
||||
|
||||
---
|
||||
技术架构
|
||||
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ 前端 Vue 3 │
|
||||
│ ┌─────────────────────────────────────────────────┐ │
|
||||
│ │ AI 3D生成页面 (/ai-3d) │ │
|
||||
│ │ - 文字输入 / 图片上传 │ │
|
||||
│ │ - 生成进度展示 │ │
|
||||
│ │ - 历史记录列表 │ │
|
||||
│ │ - 3D预览(复用 ModelViewer) │ │
|
||||
│ └─────────────────────────────────────────────────┘ │
|
||||
└───────────────────────────┬─────────────────────────────┘
|
||||
│
|
||||
┌───────────────────────────▼─────────────────────────────┐
|
||||
│ 后端 NestJS │
|
||||
│ ┌─────────────────────────────────────────────────┐ │
|
||||
│ │ AI 3D生成模块 │ │
|
||||
│ │ - 提交生成任务 │ │
|
||||
│ │ - 查询任务状态 │ │
|
||||
│ │ - 获取历史记录 │ │
|
||||
│ └──────────────────────┬──────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌──────────────────────▼──────────────────────────┐ │
|
||||
│ │ Mock Provider(开发阶段) │ │
|
||||
│ │ - 模拟生成延迟(5-10秒) │ │
|
||||
│ │ - 返回示例3D模型URL │ │
|
||||
│ └─────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
|
||||
---
|
||||
实现步骤
|
||||
|
||||
第一步:后端 - 创建数据模型
|
||||
|
||||
文件: backend/prisma/schema.prisma
|
||||
|
||||
新增 AI3DTask 表:
|
||||
model AI3DTask {
|
||||
id Int @id @default(autoincrement())
|
||||
tenantId Int @map("tenant_id")
|
||||
userId Int @map("user_id")
|
||||
inputType String @map("input_type") // text | image
|
||||
inputContent String @db.Text // 文字描述或图片URL
|
||||
status String @default("pending") // pending|processing|completed|failed
|
||||
resultUrl String? @map("result_url") // 生成的3D模型URL
|
||||
errorMessage String? @map("error_message")
|
||||
createTime DateTime @default(now()) @map("create_time")
|
||||
completeTime DateTime? @map("complete_time")
|
||||
|
||||
tenant Tenant @relation(fields: [tenantId], references: [id])
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
|
||||
@@map("t_ai_3d_task")
|
||||
}
|
||||
|
||||
第二步:后端 - 创建 AI 3D 模块
|
||||
|
||||
新建文件结构:
|
||||
backend/src/ai-3d/
|
||||
├── ai-3d.module.ts
|
||||
├── ai-3d.controller.ts
|
||||
├── ai-3d.service.ts
|
||||
├── dto/
|
||||
│ ├── create-task.dto.ts
|
||||
│ └── query-task.dto.ts
|
||||
└── providers/
|
||||
└── mock.provider.ts # Mock实现
|
||||
|
||||
API 端点:
|
||||
POST /api/ai-3d/generate # 提交生成任务
|
||||
GET /api/ai-3d/tasks # 获取历史记录列表
|
||||
GET /api/ai-3d/tasks/:id # 获取任务详情/状态
|
||||
DELETE /api/ai-3d/tasks/:id # 删除任务
|
||||
|
||||
第三步:后端 - Mock Provider 实现
|
||||
|
||||
文件: backend/src/ai-3d/providers/mock.provider.ts
|
||||
|
||||
// Mock实现:模拟5-10秒生成延迟,返回示例模型
|
||||
async generate(input: { type: 'text' | 'image', content: string }) {
|
||||
// 模拟处理时间
|
||||
await sleep(random(5000, 10000));
|
||||
|
||||
// 返回示例模型URL(使用公开的GLB示例)
|
||||
return {
|
||||
status: 'completed',
|
||||
resultUrl: 'https://example.com/sample-model.glb'
|
||||
};
|
||||
}
|
||||
|
||||
第四步:前端 - 创建页面和API
|
||||
|
||||
新建文件结构:
|
||||
frontend/src/
|
||||
├── api/
|
||||
│ └── ai-3d.ts # API接口
|
||||
├── views/
|
||||
│ └── ai-3d/
|
||||
│ ├── index.vue # 主页面
|
||||
│ └── components/
|
||||
│ ├── GenerateForm.vue # 生成表单
|
||||
│ ├── TaskList.vue # 历史列表
|
||||
│ └── TaskCard.vue # 任务卡片
|
||||
|
||||
页面布局:
|
||||
┌────────────────────────────────────────────────┐
|
||||
│ AI 3D模型生成 │
|
||||
├────────────────────────────────────────────────┤
|
||||
│ ┌──────────────────┐ ┌────────────────────┐ │
|
||||
│ │ 生成方式 │ │ 历史记录 │ │
|
||||
│ │ ○ 文字描述 │ │ ┌────────────────┐ │ │
|
||||
│ │ ○ 上传图片 │ │ │ 任务1 完成 ✓ │ │ │
|
||||
│ │ │ │ └────────────────┘ │ │
|
||||
│ │ [输入区域] │ │ ┌────────────────┐ │ │
|
||||
│ │ │ │ │ 任务2 生成中...│ │ │
|
||||
│ │ [生成] 按钮 │ │ └────────────────┘ │ │
|
||||
│ └──────────────────┘ └────────────────────┘ │
|
||||
│ │
|
||||
│ ┌──────────────────────────────────────────┐ │
|
||||
│ │ 3D预览区域(选中任务后显示) │ │
|
||||
│ │ │ │
|
||||
│ └──────────────────────────────────────────┘ │
|
||||
└────────────────────────────────────────────────┘
|
||||
|
||||
第五步:前端 - 添加路由和菜单
|
||||
|
||||
修改文件: frontend/src/router/index.ts
|
||||
|
||||
{
|
||||
path: "ai-3d",
|
||||
name: "AI3DGenerate",
|
||||
component: () => import("@/views/ai-3d/index.vue"),
|
||||
meta: {
|
||||
title: "AI 3D生成",
|
||||
requiresAuth: true,
|
||||
},
|
||||
}
|
||||
|
||||
---
|
||||
关键文件清单
|
||||
┌──────┬──────────────────────────────────────────────────────┐
|
||||
│ 操作 │ 文件路径 │
|
||||
├──────┼──────────────────────────────────────────────────────┤
|
||||
│ 修改 │ backend/prisma/schema.prisma - 添加 AI3DTask 表 │
|
||||
├──────┼──────────────────────────────────────────────────────┤
|
||||
│ 新建 │ backend/src/ai-3d/ai-3d.module.ts │
|
||||
├──────┼──────────────────────────────────────────────────────┤
|
||||
│ 新建 │ backend/src/ai-3d/ai-3d.controller.ts │
|
||||
├──────┼──────────────────────────────────────────────────────┤
|
||||
│ 新建 │ backend/src/ai-3d/ai-3d.service.ts │
|
||||
├──────┼──────────────────────────────────────────────────────┤
|
||||
│ 新建 │ backend/src/ai-3d/dto/create-task.dto.ts │
|
||||
├──────┼──────────────────────────────────────────────────────┤
|
||||
│ 新建 │ backend/src/ai-3d/dto/query-task.dto.ts │
|
||||
├──────┼──────────────────────────────────────────────────────┤
|
||||
│ 新建 │ backend/src/ai-3d/providers/mock.provider.ts │
|
||||
├──────┼──────────────────────────────────────────────────────┤
|
||||
│ 修改 │ backend/src/app.module.ts - 注册 AI3D 模块 │
|
||||
├──────┼──────────────────────────────────────────────────────┤
|
||||
│ 新建 │ frontend/src/api/ai-3d.ts │
|
||||
├──────┼──────────────────────────────────────────────────────┤
|
||||
│ 新建 │ frontend/src/views/ai-3d/index.vue │
|
||||
├──────┼──────────────────────────────────────────────────────┤
|
||||
│ 新建 │ frontend/src/views/ai-3d/components/GenerateForm.vue │
|
||||
├──────┼──────────────────────────────────────────────────────┤
|
||||
│ 新建 │ frontend/src/views/ai-3d/components/TaskList.vue │
|
||||
├──────┼──────────────────────────────────────────────────────┤
|
||||
│ 新建 │ frontend/src/views/ai-3d/components/TaskCard.vue │
|
||||
├──────┼──────────────────────────────────────────────────────┤
|
||||
│ 修改 │ frontend/src/router/index.ts - 添加路由 │
|
||||
└──────┴──────────────────────────────────────────────────────┘
|
||||
---
|
||||
验证方式
|
||||
|
||||
1. 数据库迁移
|
||||
cd backend
|
||||
npx prisma migrate dev --name add-ai-3d-task
|
||||
2. 后端测试
|
||||
cd backend && npm run start:dev
|
||||
# 测试API
|
||||
curl -X POST http://localhost:3001/api/ai-3d/generate \
|
||||
-H "Authorization: Bearer <token>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"type":"text","content":"一个红色的椅子"}'
|
||||
3. 前端测试
|
||||
cd frontend && npm run dev
|
||||
# 访问 http://localhost:3000/<tenantCode>/ai-3d
|
||||
# 测试文字生成、图片上传、历史记录功能
|
||||
4. 完整流程测试
|
||||
- 输入文字描述 → 点击生成 → 等待完成 → 预览3D模型
|
||||
- 上传图片 → 点击生成 → 等待完成 → 预览3D模型
|
||||
- 查看历史记录 → 点击历史任务 → 预览3D模型
|
||||
|
||||
---
|
||||
后期扩展
|
||||
|
||||
Mock 开发完成后,接入真实 API 只需:
|
||||
1. 新建 hunyuan-3d.provider.ts 或 meshy.provider.ts
|
||||
2. 配置环境变量
|
||||
3. 在 Service 中切换 Provider
|
||||
689
backend/package-lock.json
generated
689
backend/package-lock.json
generated
@ -20,6 +20,7 @@
|
||||
"bcrypt": "^6.0.0",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.1",
|
||||
"cos-nodejs-sdk-v5": "^2.15.4",
|
||||
"passport": "^0.7.0",
|
||||
"passport-jwt": "^4.0.1",
|
||||
"passport-local": "^1.0.0",
|
||||
@ -3160,6 +3161,54 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/asn1": {
|
||||
"version": "0.2.6",
|
||||
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz",
|
||||
"integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safer-buffer": "~2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/assert-plus": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
|
||||
"integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/atomically": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/atomically/-/atomically-1.7.0.tgz",
|
||||
"integrity": "sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/aws-sign2": {
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
|
||||
"integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/aws4": {
|
||||
"version": "1.13.2",
|
||||
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz",
|
||||
"integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/babel-jest": {
|
||||
"version": "29.7.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz",
|
||||
@ -3338,6 +3387,15 @@
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/bcrypt-pbkdf": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
|
||||
"integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"tweetnacl": "^0.14.3"
|
||||
}
|
||||
},
|
||||
"node_modules/binary-extensions": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
|
||||
@ -3700,6 +3758,12 @@
|
||||
],
|
||||
"license": "CC-BY-4.0"
|
||||
},
|
||||
"node_modules/caseless": {
|
||||
"version": "0.12.0",
|
||||
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
|
||||
"integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
@ -3959,6 +4023,18 @@
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/commander": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
|
||||
@ -4008,6 +4084,88 @@
|
||||
"typedarray": "^0.0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/conf": {
|
||||
"version": "9.0.2",
|
||||
"resolved": "https://registry.npmjs.org/conf/-/conf-9.0.2.tgz",
|
||||
"integrity": "sha512-rLSiilO85qHgaTBIIHQpsv8z+NnVfZq3cKuYNCXN1AOqPzced0GWZEe/A517VldRLyQYXUMyV+vszavE2jSAqw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ajv": "^7.0.3",
|
||||
"ajv-formats": "^1.5.1",
|
||||
"atomically": "^1.7.0",
|
||||
"debounce-fn": "^4.0.0",
|
||||
"dot-prop": "^6.0.1",
|
||||
"env-paths": "^2.2.0",
|
||||
"json-schema-typed": "^7.0.3",
|
||||
"make-dir": "^3.1.0",
|
||||
"onetime": "^5.1.2",
|
||||
"pkg-up": "^3.1.0",
|
||||
"semver": "^7.3.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/conf/node_modules/ajv": {
|
||||
"version": "7.2.4",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-7.2.4.tgz",
|
||||
"integrity": "sha512-nBeQgg/ZZA3u3SYxyaDvpvDtgZ/EZPF547ARgZBrG9Bhu1vKDwAIjtIf+sDtJUKa2zOcEbmRLBRSyMraS/Oy1A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"json-schema-traverse": "^1.0.0",
|
||||
"require-from-string": "^2.0.2",
|
||||
"uri-js": "^4.2.2"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/epoberezkin"
|
||||
}
|
||||
},
|
||||
"node_modules/conf/node_modules/ajv-formats": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-1.6.1.tgz",
|
||||
"integrity": "sha512-4CjkH20If1lhR5CGtqkrVg3bbOtFEG80X9v6jDOIUhbzzbB+UzPBGy8GQhUNVZ0yvMHdMpawCOcy5ydGMsagGQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ajv": "^7.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"ajv": "^7.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"ajv": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/conf/node_modules/make-dir": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
|
||||
"integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"semver": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/conf/node_modules/make-dir/node_modules/semver": {
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
},
|
||||
"node_modules/confbox": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz",
|
||||
@ -4084,6 +4242,21 @@
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/cos-nodejs-sdk-v5": {
|
||||
"version": "2.15.4",
|
||||
"resolved": "https://registry.npmjs.org/cos-nodejs-sdk-v5/-/cos-nodejs-sdk-v5-2.15.4.tgz",
|
||||
"integrity": "sha512-TP/iYTvKKKhRK89on9SRfSMGEw/9SFAAU8EC1kdT5Fmpx7dAwaCNM2+R2H1TSYoQt+03rwOs8QEfNkX8GOHjHQ==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"conf": "^9.0.0",
|
||||
"fast-xml-parser": "4.2.5",
|
||||
"mime-types": "^2.1.24",
|
||||
"request": "^2.88.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/cosmiconfig": {
|
||||
"version": "8.3.6",
|
||||
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz",
|
||||
@ -4155,6 +4328,42 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/dashdash": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
|
||||
"integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"assert-plus": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/debounce-fn": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/debounce-fn/-/debounce-fn-4.0.0.tgz",
|
||||
"integrity": "sha512-8pYCQiL9Xdcg0UPSD3d+0KMlOjp+KGU5EPwYddgzQ7DATsg4fuUDjQtsYLmWjnk2obnNHgV3vE2Y4jejSOJVBQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mimic-fn": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/debounce-fn/node_modules/mimic-fn": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-3.1.0.tgz",
|
||||
"integrity": "sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.4.3",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
||||
@ -4252,6 +4461,15 @@
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/depd": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||
@ -4334,6 +4552,21 @@
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dot-prop": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz",
|
||||
"integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-obj": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/dotenv": {
|
||||
"version": "17.2.3",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz",
|
||||
@ -4422,6 +4655,16 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ecc-jsbn": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
|
||||
"integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"jsbn": "~0.1.0",
|
||||
"safer-buffer": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ecdsa-sig-formatter": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
|
||||
@ -4508,6 +4751,15 @@
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/env-paths": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
|
||||
"integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/error-ex": {
|
||||
"version": "1.3.4",
|
||||
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz",
|
||||
@ -5005,6 +5257,12 @@
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/extend": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
|
||||
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/external-editor": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
|
||||
@ -5020,6 +5278,15 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/extsprintf": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
|
||||
"integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==",
|
||||
"engines": [
|
||||
"node >=0.6.0"
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-check": {
|
||||
"version": "3.23.2",
|
||||
"resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.23.2.tgz",
|
||||
@ -5047,7 +5314,6 @@
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-diff": {
|
||||
@ -5078,7 +5344,6 @@
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
|
||||
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-levenshtein": {
|
||||
@ -5094,6 +5359,28 @@
|
||||
"integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-xml-parser": {
|
||||
"version": "4.2.5",
|
||||
"resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.2.5.tgz",
|
||||
"integrity": "sha512-B9/wizE4WngqQftFPmdaMYlXoJlJOYxGQOanC77fq9k8+Z0v5dDSVh+3glErdIROP//s/jgb7ZuxKfB8nVyo0g==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "paypal",
|
||||
"url": "https://paypal.me/naturalintelligence"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/NaturalIntelligence"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"strnum": "^1.0.5"
|
||||
},
|
||||
"bin": {
|
||||
"fxparser": "src/cli/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/fastq": {
|
||||
"version": "1.20.1",
|
||||
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz",
|
||||
@ -5279,6 +5566,15 @@
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/forever-agent": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
|
||||
"integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/fork-ts-checker-webpack-plugin": {
|
||||
"version": "9.0.2",
|
||||
"resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-9.0.2.tgz",
|
||||
@ -5332,6 +5628,20 @@
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
|
||||
"integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.6",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.12"
|
||||
}
|
||||
},
|
||||
"node_modules/forwarded": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
||||
@ -5483,6 +5793,15 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/getpass": {
|
||||
"version": "0.1.7",
|
||||
"resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
|
||||
"integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"assert-plus": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/giget": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/giget/-/giget-2.0.0.tgz",
|
||||
@ -5663,6 +5982,51 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/har-schema": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
|
||||
"integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/har-validator": {
|
||||
"version": "5.1.5",
|
||||
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz",
|
||||
"integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==",
|
||||
"deprecated": "this library is no longer supported",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ajv": "^6.12.3",
|
||||
"har-schema": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/har-validator/node_modules/ajv": {
|
||||
"version": "6.12.6",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
"json-schema-traverse": "^0.4.1",
|
||||
"uri-js": "^4.2.2"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/epoberezkin"
|
||||
}
|
||||
},
|
||||
"node_modules/har-validator/node_modules/json-schema-traverse": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
@ -5742,6 +6106,21 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/http-signature": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
|
||||
"integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"assert-plus": "^1.0.0",
|
||||
"jsprim": "^1.2.2",
|
||||
"sshpk": "^1.7.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8",
|
||||
"npm": ">=1.3.7"
|
||||
}
|
||||
},
|
||||
"node_modules/human-signals": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
|
||||
@ -5994,6 +6373,15 @@
|
||||
"node": ">=0.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-obj": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz",
|
||||
"integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/is-path-inside": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
|
||||
@ -6017,6 +6405,12 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/is-typedarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
|
||||
"integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/is-unicode-supported": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz",
|
||||
@ -6037,6 +6431,12 @@
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/isstream": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
|
||||
"integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/istanbul-lib-coverage": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
|
||||
@ -6882,6 +7282,12 @@
|
||||
"js-yaml": "bin/js-yaml.js"
|
||||
}
|
||||
},
|
||||
"node_modules/jsbn": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
|
||||
"integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/jsesc": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
|
||||
@ -6909,13 +7315,24 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/json-schema": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz",
|
||||
"integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==",
|
||||
"license": "(AFL-2.1 OR BSD-3-Clause)"
|
||||
},
|
||||
"node_modules/json-schema-traverse": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
||||
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/json-schema-typed": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-7.0.3.tgz",
|
||||
"integrity": "sha512-7DE8mpG+/fVw+dTpjbxnx47TaMnDfOI1jwft9g1VybltZCduyRQPJPvc+zzKY9WPHxhPWczyFuYa6I8Mw4iU5A==",
|
||||
"license": "BSD-2-Clause"
|
||||
},
|
||||
"node_modules/json-stable-stringify-without-jsonify": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
|
||||
@ -6923,6 +7340,12 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/json-stringify-safe": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
|
||||
"integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/json5": {
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
|
||||
@ -6978,6 +7401,21 @@
|
||||
"npm": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/jsprim": {
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz",
|
||||
"integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"assert-plus": "1.0.0",
|
||||
"extsprintf": "1.3.0",
|
||||
"json-schema": "0.4.0",
|
||||
"verror": "1.10.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jwa": {
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz",
|
||||
@ -7351,7 +7789,6 @@
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
|
||||
"integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
@ -7589,6 +8026,15 @@
|
||||
"node": "^14.18.0 || >=16.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/oauth-sign": {
|
||||
"version": "0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
|
||||
"integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
@ -7643,7 +8089,6 @@
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
|
||||
"integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mimic-fn": "^2.1.0"
|
||||
@ -7743,7 +8188,6 @@
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
|
||||
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
@ -7940,6 +8384,12 @@
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/performance-now": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
|
||||
"integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
@ -8051,6 +8501,79 @@
|
||||
"pathe": "^2.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/pkg-up": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz",
|
||||
"integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"find-up": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/pkg-up/node_modules/find-up": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
|
||||
"integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"locate-path": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/pkg-up/node_modules/locate-path": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
|
||||
"integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"p-locate": "^3.0.0",
|
||||
"path-exists": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/pkg-up/node_modules/p-limit": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
|
||||
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"p-try": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/pkg-up/node_modules/p-locate": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
|
||||
"integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"p-limit": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/pkg-up/node_modules/path-exists": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
|
||||
"integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/pluralize": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz",
|
||||
@ -8181,11 +8704,22 @@
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/psl": {
|
||||
"version": "1.15.0",
|
||||
"resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz",
|
||||
"integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"punycode": "^2.3.1"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/lupomontero"
|
||||
}
|
||||
},
|
||||
"node_modules/punycode": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
@ -8352,6 +8886,47 @@
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/request": {
|
||||
"version": "2.88.2",
|
||||
"resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz",
|
||||
"integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==",
|
||||
"deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"aws-sign2": "~0.7.0",
|
||||
"aws4": "^1.8.0",
|
||||
"caseless": "~0.12.0",
|
||||
"combined-stream": "~1.0.6",
|
||||
"extend": "~3.0.2",
|
||||
"forever-agent": "~0.6.1",
|
||||
"form-data": "~2.3.2",
|
||||
"har-validator": "~5.1.3",
|
||||
"http-signature": "~1.2.0",
|
||||
"is-typedarray": "~1.0.0",
|
||||
"isstream": "~0.1.2",
|
||||
"json-stringify-safe": "~5.0.1",
|
||||
"mime-types": "~2.1.19",
|
||||
"oauth-sign": "~0.9.0",
|
||||
"performance-now": "^2.1.0",
|
||||
"qs": "~6.5.2",
|
||||
"safe-buffer": "^5.1.2",
|
||||
"tough-cookie": "~2.5.0",
|
||||
"tunnel-agent": "^0.6.0",
|
||||
"uuid": "^3.3.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/request/node_modules/qs": {
|
||||
"version": "6.5.3",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz",
|
||||
"integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==",
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/require-directory": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||
@ -8366,7 +8941,6 @@
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
|
||||
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
@ -8925,6 +9499,31 @@
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/sshpk": {
|
||||
"version": "1.18.0",
|
||||
"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz",
|
||||
"integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"asn1": "~0.2.3",
|
||||
"assert-plus": "^1.0.0",
|
||||
"bcrypt-pbkdf": "^1.0.0",
|
||||
"dashdash": "^1.12.0",
|
||||
"ecc-jsbn": "~0.1.1",
|
||||
"getpass": "^0.1.1",
|
||||
"jsbn": "~0.1.0",
|
||||
"safer-buffer": "^2.0.2",
|
||||
"tweetnacl": "~0.14.0"
|
||||
},
|
||||
"bin": {
|
||||
"sshpk-conv": "bin/sshpk-conv",
|
||||
"sshpk-sign": "bin/sshpk-sign",
|
||||
"sshpk-verify": "bin/sshpk-verify"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/stack-utils": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz",
|
||||
@ -9079,6 +9678,18 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/strnum": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/strnum/-/strnum-1.1.2.tgz",
|
||||
"integrity": "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/NaturalIntelligence"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/strtok3": {
|
||||
"version": "10.3.4",
|
||||
"resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.4.tgz",
|
||||
@ -9417,6 +10028,19 @@
|
||||
"url": "https://github.com/sponsors/Borewit"
|
||||
}
|
||||
},
|
||||
"node_modules/tough-cookie": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
|
||||
"integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"psl": "^1.1.28",
|
||||
"punycode": "^2.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/tr46": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||
@ -9624,6 +10248,24 @@
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/tunnel-agent": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
||||
"integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"safe-buffer": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/tweetnacl": {
|
||||
"version": "0.14.5",
|
||||
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
|
||||
"integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==",
|
||||
"license": "Unlicense"
|
||||
},
|
||||
"node_modules/type-check": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
||||
@ -9791,7 +10433,6 @@
|
||||
"version": "4.4.1",
|
||||
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
|
||||
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
"punycode": "^2.1.0"
|
||||
@ -9812,6 +10453,16 @@
|
||||
"node": ">= 0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/uuid": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
|
||||
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
|
||||
"deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"uuid": "bin/uuid"
|
||||
}
|
||||
},
|
||||
"node_modules/v8-compile-cache-lib": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
|
||||
@ -9852,6 +10503,26 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/verror": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
|
||||
"integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==",
|
||||
"engines": [
|
||||
"node >=0.6.0"
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"assert-plus": "^1.0.0",
|
||||
"core-util-is": "1.0.2",
|
||||
"extsprintf": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/verror/node_modules/core-util-is": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||
"integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/walker": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz",
|
||||
|
||||
@ -56,6 +56,7 @@
|
||||
"bcrypt": "^6.0.0",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.1",
|
||||
"cos-nodejs-sdk-v5": "^2.15.4",
|
||||
"passport": "^0.7.0",
|
||||
"passport-jwt": "^4.0.1",
|
||||
"passport-local": "^1.0.0",
|
||||
|
||||
@ -16,6 +16,7 @@ import { ContestsModule } from './contests/contests.module';
|
||||
import { JudgesManagementModule } from './judges-management/judges-management.module';
|
||||
import { UploadModule } from './upload/upload.module';
|
||||
import { HomeworkModule } from './homework/homework.module';
|
||||
import { OssModule } from './oss/oss.module';
|
||||
import { JwtAuthGuard } from './auth/guards/jwt-auth.guard';
|
||||
import { RolesGuard } from './auth/guards/roles.guard';
|
||||
import { TransformInterceptor } from './common/interceptors/transform.interceptor';
|
||||
@ -48,6 +49,7 @@ import { HttpExceptionFilter } from './common/filters/http-exception.filter';
|
||||
JudgesManagementModule,
|
||||
UploadModule,
|
||||
HomeworkModule,
|
||||
OssModule,
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
|
||||
@ -2,8 +2,6 @@ import { NestFactory } from '@nestjs/core';
|
||||
import { ValidationPipe } from '@nestjs/common';
|
||||
import { AppModule } from './app.module';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { NestExpressApplication } from '@nestjs/platform-express';
|
||||
import { join } from 'path';
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
@ -14,12 +12,8 @@ async function bootstrap() {
|
||||
credentials: true,
|
||||
});
|
||||
|
||||
// 配置静态文件服务
|
||||
const expressApp = app as NestExpressApplication;
|
||||
const uploadsPath = join(process.cwd(), 'uploads');
|
||||
expressApp.useStaticAssets(uploadsPath, {
|
||||
prefix: '/api/uploads',
|
||||
});
|
||||
// Global prefix
|
||||
app.setGlobalPrefix('api');
|
||||
|
||||
// Global validation pipe
|
||||
app.useGlobalPipes(
|
||||
@ -30,9 +24,6 @@ async function bootstrap() {
|
||||
}),
|
||||
);
|
||||
|
||||
// Global prefix
|
||||
app.setGlobalPrefix('api');
|
||||
|
||||
// 验证环境配置加载
|
||||
const configService = app.get(ConfigService);
|
||||
|
||||
|
||||
9
backend/src/oss/oss.module.ts
Normal file
9
backend/src/oss/oss.module.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { Module, Global } from '@nestjs/common';
|
||||
import { OssService } from './oss.service';
|
||||
|
||||
@Global() // 全局模块,其他模块无需导入即可使用
|
||||
@Module({
|
||||
providers: [OssService],
|
||||
exports: [OssService],
|
||||
})
|
||||
export class OssModule {}
|
||||
202
backend/src/oss/oss.service.ts
Normal file
202
backend/src/oss/oss.service.ts
Normal file
@ -0,0 +1,202 @@
|
||||
import { Injectable, BadRequestException } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import * as COS from 'cos-nodejs-sdk-v5';
|
||||
import * as path from 'path';
|
||||
import { randomBytes } from 'crypto';
|
||||
|
||||
@Injectable()
|
||||
export class OssService {
|
||||
private client: COS | null = null;
|
||||
private readonly bucket: string;
|
||||
private readonly region: string;
|
||||
private readonly enabled: boolean;
|
||||
|
||||
constructor(private configService: ConfigService) {
|
||||
const secretId = this.configService.get<string>('COS_SECRET_ID');
|
||||
const secretKey = this.configService.get<string>('COS_SECRET_KEY');
|
||||
this.bucket = this.configService.get<string>('COS_BUCKET') || '';
|
||||
this.region = this.configService.get<string>('COS_REGION') || 'ap-guangzhou';
|
||||
|
||||
// 检查是否配置了 COS
|
||||
this.enabled = !!(secretId && secretKey && this.bucket);
|
||||
|
||||
if (this.enabled) {
|
||||
this.client = new COS({
|
||||
SecretId: secretId,
|
||||
SecretKey: secretKey,
|
||||
});
|
||||
console.log('腾讯云 COS 已启用,Bucket:', this.bucket, 'Region:', this.region);
|
||||
} else {
|
||||
console.log('腾讯云 COS 未配置,将使用本地存储');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查 COS 是否启用
|
||||
*/
|
||||
isEnabled(): boolean {
|
||||
return this.enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传文件到 COS
|
||||
* @param file 文件 Buffer 或 Stream
|
||||
* @param originalName 原始文件名
|
||||
* @param tenantId 租户ID(可选,用于目录隔离)
|
||||
* @param userId 用户ID(可选,用于目录隔离)
|
||||
* @returns 文件访问URL
|
||||
*/
|
||||
async uploadFile(
|
||||
file: Buffer | NodeJS.ReadableStream,
|
||||
originalName: string,
|
||||
tenantId?: number,
|
||||
userId?: number,
|
||||
): Promise<{ url: string; fileName: string; ossPath: string }> {
|
||||
if (!this.enabled || !this.client) {
|
||||
throw new BadRequestException('COS 服务未启用');
|
||||
}
|
||||
|
||||
// 生成唯一文件名
|
||||
const fileExt = path.extname(originalName);
|
||||
const uniqueId = randomBytes(16).toString('hex');
|
||||
const fileName = `${uniqueId}${fileExt}`;
|
||||
|
||||
// 构建 COS 存储路径:uploads/tenant_X/user_Y/filename
|
||||
let cosPath = 'uploads';
|
||||
if (tenantId) {
|
||||
cosPath += `/tenant_${tenantId}`;
|
||||
if (userId) {
|
||||
cosPath += `/user_${userId}`;
|
||||
}
|
||||
}
|
||||
cosPath += `/${fileName}`;
|
||||
|
||||
try {
|
||||
// 上传到 COS
|
||||
await new Promise<COS.PutObjectResult>((resolve, reject) => {
|
||||
this.client!.putObject(
|
||||
{
|
||||
Bucket: this.bucket,
|
||||
Region: this.region,
|
||||
Key: cosPath,
|
||||
Body: file as Buffer,
|
||||
},
|
||||
(err, data) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(data);
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
// 构建访问URL
|
||||
const url = `https://${this.bucket}.cos.${this.region}.myqcloud.com/${cosPath}`;
|
||||
|
||||
// 返回文件信息
|
||||
return {
|
||||
url,
|
||||
fileName: originalName,
|
||||
ossPath: cosPath,
|
||||
};
|
||||
} catch (error: any) {
|
||||
console.error('COS 上传失败:', error);
|
||||
throw new BadRequestException(`文件上传失败: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除 COS 文件
|
||||
* @param ossPath COS 文件路径
|
||||
*/
|
||||
async deleteFile(ossPath: string): Promise<void> {
|
||||
if (!this.enabled || !this.client) {
|
||||
throw new BadRequestException('COS 服务未启用');
|
||||
}
|
||||
|
||||
try {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
this.client!.deleteObject(
|
||||
{
|
||||
Bucket: this.bucket,
|
||||
Region: this.region,
|
||||
Key: ossPath,
|
||||
},
|
||||
(err) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.error('COS 删除失败:', error);
|
||||
throw new BadRequestException(`文件删除失败: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件的签名 URL(用于私有 Bucket)
|
||||
* @param ossPath COS 文件路径
|
||||
* @param expires 过期时间(秒),默认 3600
|
||||
*/
|
||||
async getSignedUrl(ossPath: string, expires: number = 3600): Promise<string> {
|
||||
if (!this.enabled || !this.client) {
|
||||
throw new BadRequestException('COS 服务未启用');
|
||||
}
|
||||
|
||||
try {
|
||||
const url = this.client.getObjectUrl(
|
||||
{
|
||||
Bucket: this.bucket,
|
||||
Region: this.region,
|
||||
Key: ossPath,
|
||||
Sign: true,
|
||||
Expires: expires,
|
||||
},
|
||||
);
|
||||
return url;
|
||||
} catch (error: any) {
|
||||
console.error('获取签名 URL 失败:', error);
|
||||
throw new BadRequestException(`获取文件链接失败: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查文件是否存在
|
||||
* @param ossPath COS 文件路径
|
||||
*/
|
||||
async exists(ossPath: string): Promise<boolean> {
|
||||
if (!this.enabled || !this.client) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
this.client!.headObject(
|
||||
{
|
||||
Bucket: this.bucket,
|
||||
Region: this.region,
|
||||
Key: ossPath,
|
||||
},
|
||||
(err) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
return true;
|
||||
} catch (error: any) {
|
||||
if (error.statusCode === 404) {
|
||||
return false;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,22 +1,29 @@
|
||||
import {
|
||||
Controller,
|
||||
Post,
|
||||
Get,
|
||||
Param,
|
||||
Res,
|
||||
UseInterceptors,
|
||||
UploadedFile,
|
||||
UseGuards,
|
||||
Request,
|
||||
BadRequestException,
|
||||
NotFoundException,
|
||||
} from '@nestjs/common';
|
||||
import { FileInterceptor } from '@nestjs/platform-express';
|
||||
import { Response } from 'express';
|
||||
import { UploadService } from './upload.service';
|
||||
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
|
||||
@Controller('upload')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
export class UploadController {
|
||||
constructor(private readonly uploadService: UploadService) {}
|
||||
|
||||
@Post()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@UseInterceptors(FileInterceptor('file'))
|
||||
async uploadFile(
|
||||
@UploadedFile() file: Express.Multer.File,
|
||||
@ -39,3 +46,61 @@ export class UploadController {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 专门用于静态文件服务的控制器
|
||||
* 处理 /api/uploads/* 路径的文件请求
|
||||
*/
|
||||
@Controller('uploads')
|
||||
export class UploadsController {
|
||||
private readonly uploadDir: string;
|
||||
|
||||
constructor() {
|
||||
this.uploadDir = path.join(process.cwd(), 'uploads');
|
||||
}
|
||||
|
||||
@Get('*')
|
||||
async serveFile(@Param() params: any, @Res() res: Response) {
|
||||
// 获取文件路径(从通配符参数中)
|
||||
const filePath = params[0] || '';
|
||||
const fullPath = path.join(this.uploadDir, filePath);
|
||||
|
||||
// 安全检查:防止路径遍历攻击
|
||||
const normalizedPath = path.normalize(fullPath);
|
||||
if (!normalizedPath.startsWith(this.uploadDir)) {
|
||||
throw new BadRequestException('无效的文件路径');
|
||||
}
|
||||
|
||||
// 检查文件是否存在
|
||||
if (!fs.existsSync(normalizedPath)) {
|
||||
console.error('文件不存在:', normalizedPath);
|
||||
throw new NotFoundException('文件不存在');
|
||||
}
|
||||
|
||||
// 获取文件扩展名以设置正确的 Content-Type
|
||||
const ext = path.extname(normalizedPath).toLowerCase();
|
||||
const mimeTypes: Record<string, string> = {
|
||||
'.glb': 'model/gltf-binary',
|
||||
'.gltf': 'model/gltf+json',
|
||||
'.obj': 'text/plain',
|
||||
'.fbx': 'application/octet-stream',
|
||||
'.stl': 'model/stl',
|
||||
'.png': 'image/png',
|
||||
'.jpg': 'image/jpeg',
|
||||
'.jpeg': 'image/jpeg',
|
||||
'.gif': 'image/gif',
|
||||
'.webp': 'image/webp',
|
||||
'.pdf': 'application/pdf',
|
||||
'.mp4': 'video/mp4',
|
||||
'.webm': 'video/webm',
|
||||
};
|
||||
|
||||
const contentType = mimeTypes[ext] || 'application/octet-stream';
|
||||
|
||||
res.setHeader('Content-Type', contentType);
|
||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
|
||||
// 发送文件
|
||||
res.sendFile(normalizedPath);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { UploadController } from './upload.controller';
|
||||
import { UploadController, UploadsController } from './upload.controller';
|
||||
import { UploadService } from './upload.service';
|
||||
|
||||
@Module({
|
||||
controllers: [UploadController],
|
||||
controllers: [UploadController, UploadsController],
|
||||
providers: [UploadService],
|
||||
exports: [UploadService],
|
||||
})
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { Injectable, BadRequestException } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { OssService } from '../oss/oss.service';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { randomBytes } from 'crypto';
|
||||
@ -7,15 +8,28 @@ import { randomBytes } from 'crypto';
|
||||
@Injectable()
|
||||
export class UploadService {
|
||||
private readonly uploadDir: string;
|
||||
private readonly useOss: boolean;
|
||||
|
||||
constructor(private configService: ConfigService) {
|
||||
// 上传文件存储目录
|
||||
constructor(
|
||||
private configService: ConfigService,
|
||||
private ossService: OssService,
|
||||
) {
|
||||
// 本地上传文件存储目录(作为 COS 的备用方案)
|
||||
this.uploadDir = path.join(process.cwd(), 'uploads');
|
||||
|
||||
// 确保上传目录存在
|
||||
|
||||
// 确保本地上传目录存在
|
||||
if (!fs.existsSync(this.uploadDir)) {
|
||||
fs.mkdirSync(this.uploadDir, { recursive: true });
|
||||
}
|
||||
|
||||
// 检查是否使用 COS
|
||||
this.useOss = this.ossService.isEnabled();
|
||||
|
||||
if (this.useOss) {
|
||||
console.log('文件上传将使用腾讯云 COS');
|
||||
} else {
|
||||
console.log('文件上传将使用本地存储,目录:', this.uploadDir);
|
||||
}
|
||||
}
|
||||
|
||||
async uploadFile(
|
||||
@ -27,11 +41,56 @@ export class UploadService {
|
||||
throw new BadRequestException('文件不存在');
|
||||
}
|
||||
|
||||
// 优先使用 COS
|
||||
if (this.useOss) {
|
||||
return this.uploadToOss(file, tenantId, userId);
|
||||
}
|
||||
|
||||
// 备用方案:本地存储
|
||||
return this.uploadToLocal(file, tenantId, userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传到腾讯云 COS
|
||||
*/
|
||||
private async uploadToOss(
|
||||
file: Express.Multer.File,
|
||||
tenantId?: number,
|
||||
userId?: number,
|
||||
): Promise<{ url: string; fileName: string; size: number }> {
|
||||
try {
|
||||
const result = await this.ossService.uploadFile(
|
||||
file.buffer,
|
||||
file.originalname,
|
||||
tenantId,
|
||||
userId,
|
||||
);
|
||||
|
||||
return {
|
||||
url: result.url,
|
||||
fileName: result.fileName,
|
||||
size: file.size,
|
||||
};
|
||||
} catch (error: any) {
|
||||
console.error('COS 上传失败,尝试本地存储:', error.message);
|
||||
// COS 失败时回退到本地存储
|
||||
return this.uploadToLocal(file, tenantId, userId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传到本地存储
|
||||
*/
|
||||
private async uploadToLocal(
|
||||
file: Express.Multer.File,
|
||||
tenantId?: number,
|
||||
userId?: number,
|
||||
): Promise<{ url: string; fileName: string; size: number }> {
|
||||
// 生成唯一文件名
|
||||
const fileExt = path.extname(file.originalname);
|
||||
const uniqueId = randomBytes(16).toString('hex');
|
||||
const fileName = `${uniqueId}${fileExt}`;
|
||||
|
||||
|
||||
// 根据租户ID和用户ID创建目录结构:uploads/tenantId/userId/
|
||||
let targetDir = this.uploadDir;
|
||||
if (tenantId) {
|
||||
@ -39,7 +98,7 @@ export class UploadService {
|
||||
if (!fs.existsSync(targetDir)) {
|
||||
fs.mkdirSync(targetDir, { recursive: true });
|
||||
}
|
||||
|
||||
|
||||
if (userId) {
|
||||
targetDir = path.join(targetDir, `user_${userId}`);
|
||||
if (!fs.existsSync(targetDir)) {
|
||||
@ -72,4 +131,3 @@ export class UploadService {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
1044
docs/ai-3d-generation.md
Normal file
1044
docs/ai-3d-generation.md
Normal file
File diff suppressed because it is too large
Load Diff
@ -375,7 +375,10 @@ watch(
|
||||
watch(
|
||||
() => searchParams.organization,
|
||||
() => {
|
||||
if (searchParams.organization === undefined || searchParams.organization === "") {
|
||||
if (
|
||||
searchParams.organization === undefined ||
|
||||
searchParams.organization === ""
|
||||
) {
|
||||
handleSearch()
|
||||
}
|
||||
}
|
||||
|
||||
@ -107,11 +107,18 @@ let dracoLoader: DRACOLoader | null = null
|
||||
let initialCameraPosition: THREE.Vector3 | null = null
|
||||
|
||||
// 获取模型 URL
|
||||
// Vue Router 会自动解码 query 参数,所以直接使用即可
|
||||
const modelUrl = ref((route.query.url as string) || "")
|
||||
console.log("模型查看器 - URL:", modelUrl.value)
|
||||
|
||||
// 返回上一页
|
||||
const handleBack = () => {
|
||||
router.back()
|
||||
// 如果是新标签页打开(没有历史记录),则关闭窗口
|
||||
if (window.history.length <= 1) {
|
||||
window.close()
|
||||
} else {
|
||||
router.back()
|
||||
}
|
||||
}
|
||||
|
||||
// 重置相机视角
|
||||
@ -224,6 +231,18 @@ const loadModel = async () => {
|
||||
if (!scene || !modelUrl.value) {
|
||||
error.value = "模型 URL 不存在"
|
||||
loading.value = false
|
||||
console.error("模型加载失败: URL为空", { scene: !!scene, url: modelUrl.value })
|
||||
return
|
||||
}
|
||||
|
||||
// 检查文件扩展名
|
||||
const supportedExtensions = ['.glb', '.gltf']
|
||||
const urlLower = modelUrl.value.toLowerCase()
|
||||
const isSupported = supportedExtensions.some(ext => urlLower.includes(ext))
|
||||
if (!isSupported) {
|
||||
error.value = `不支持的文件格式,目前仅支持 GLB/GLTF 格式`
|
||||
loading.value = false
|
||||
console.error("不支持的文件格式:", modelUrl.value)
|
||||
return
|
||||
}
|
||||
|
||||
@ -233,10 +252,16 @@ const loadModel = async () => {
|
||||
try {
|
||||
console.log("开始加载模型,URL:", modelUrl.value)
|
||||
|
||||
// 验证 URL
|
||||
const response = await fetch(modelUrl.value, { method: "HEAD" })
|
||||
if (!response.ok) {
|
||||
throw new Error(`文件不存在或无法访问 (${response.status})`)
|
||||
// 验证 URL 是否可访问
|
||||
try {
|
||||
const response = await fetch(modelUrl.value, { method: "HEAD" })
|
||||
if (!response.ok) {
|
||||
throw new Error(`文件不存在或无法访问 (HTTP ${response.status})`)
|
||||
}
|
||||
console.log("文件验证通过,开始加载...")
|
||||
} catch (fetchErr: any) {
|
||||
console.error("文件访问验证失败:", fetchErr)
|
||||
throw new Error(`无法访问文件: ${fetchErr.message}`)
|
||||
}
|
||||
|
||||
const loader = new GLTFLoader()
|
||||
|
||||
Loading…
Reference in New Issue
Block a user