Imported from xiajingan/mai-mktmedia (
AGENTS.md). Install upstream withnpx skills add xiajingan/mai-mktmedia. Copyright stays with the author.
项目配置与技术规范
MKTMedia — 海外客户智能产品提案生成与分发系统 系统架构见 ARCHITECTURE.md,用户故事见 USER_STORIES.md
项目概述
- 名称: MKTMedia
- 仓库: mai-mktmedia
- 类型: pnpm Monorepo
- 语言: TypeScript(全栈)
技术栈
后端
| 技术 | 版本 | 用途 |
|---|---|---|
| Node.js | 20.x LTS | 运行时 |
| Fastify | 5.x | Web 框架 |
| TypeScript | 5.x | 开发语言(strict mode) |
| Prisma | 6.x | ORM + 数据库迁移 |
| PostgreSQL | 16.x | 主数据库 |
| Redis | 7.x | 缓存 / 队列 |
| Zod | 3.x | 输入校验 |
| BullMQ | 5.x | 任务队列(PDF/邮件/WhatsApp) |
| Puppeteer | 23.x | PDF 渲染 |
| Nodemailer | 6.x | 邮件发送 |
| LLM SDK | GPT-5.1 / Claude Opus 4.6 | LLM 翻译 + 智能选品 |
| NanoBanana SDK | latest | AI 产品展示图生成 |
| Vitest | 3.x | 单元测试 |
| Supertest | 7.x | API 集成测试 |
前端
| 技术 | 版本 | 用途 |
|---|---|---|
| Vue | 3.5+ | UI 框架(Composition API) |
| Vite | 6.x | 构建工具 |
| TypeScript | 5.x | 开发语言 |
| Vue Router | 4.x | 路由 |
| Pinia | 2.x | 状态管理 |
| Tailwind CSS | 4.x | 原子化样式 |
| Headless UI | 1.x | 无样式组件库 |
| Vitest | 3.x | 单元测试 |
| Playwright | 1.x | E2E 测试 |
基础设施
| 技术 | 版本 | 用途 |
|---|---|---|
| pnpm | 9.x | 包管理 |
| Docker | 27.x | 容器化 |
| Docker Compose | 2.x | 本地开发编排 |
| GitHub Actions | — | CI/CD |
| ESLint | 9.x | 代码检查 |
| Prettier | 3.x | 代码格式化 |
目录结构
mai-mktmedia/
├── .github/
│ ├── copilot-instructions.md # Harness 全局指令
│ └── workflows/ # GitHub Actions
│ ├── ci.yml # CI 流水线
│ └── deploy.yml # 部署流水线
├── packages/
│ ├── api/ # 后端 API
│ │ ├── src/
│ │ │ ├── modules/ # 业务模块
│ │ │ │ ├── auth/ # 认证
│ │ │ │ │ ├── controller.ts
│ │ │ │ │ ├── service.ts
│ │ │ │ │ ├── repository.ts
│ │ │ │ │ └── dto.ts
│ │ │ │ ├── product/ # 产品管理
│ │ │ │ ├── proposal/ # 产品提案生成
│ │ │ │ ├── ai/ # AI 服务(翻译/图片生成/选品)
│ │ │ │ ├── template/ # 模板管理
│ │ │ │ ├── distribution/ # 分发(Email/WhatsApp)
│ │ │ │ ├── collection/ # 交互式 Web 提案
│ │ │ │ ├── feedback/ # 客户反馈
│ │ │ │ └── tag/ # 客户标签
│ │ │ ├── services/ # 跨模块服务
│ │ │ │ ├── logger.ts
│ │ │ │ ├── cache.ts
│ │ │ │ ├── queue.ts
│ │ │ │ ├── pdf.ts # PDF 生成服务
│ │ │ │ ├── email.ts # 邮件发送服务
│ │ │ │ ├── whatsapp.ts # WhatsApp 发送服务
│ │ │ │ ├── llm.ts # LLM 翻译/内容生成服务
│ │ │ │ └── image-gen.ts # AI 图片生成服务
│ │ │ ├── database/
│ │ │ │ └── schema.prisma
│ │ │ ├── config/
│ │ │ │ └── env.ts
│ │ │ ├── plugins/ # Fastify 插件
│ │ │ │ ├── auth.ts
│ │ │ │ ├── cors.ts
│ │ │ │ └── rate-limit.ts
│ │ │ ├── app.ts # Fastify 实例
│ │ │ └── main.ts # 入口
│ │ ├── tests/
│ │ │ ├── unit/
│ │ │ └── integration/
│ │ ├── package.json
│ │ └── tsconfig.json
│ ├── web/ # 前端 Web
│ │ ├── src/
│ │ │ ├── pages/ # 页面组件
│ │ │ │ ├── login/
│ │ │ │ ├── dashboard/
│ │ │ │ ├── products/
│ │ │ │ ├── proposals/
│ │ │ │ ├── feedbacks/
│ │ │ │ ├── templates/
│ │ │ │ ├── settings/
│ │ │ │ └── collection/ # 公开收集表页面
│ │ │ ├── components/ # 可复用组件
│ │ │ │ ├── common/ # 通用(Button/Input/Modal)
│ │ │ │ ├── ui/ # UI 组件
│ │ │ │ ├── product/ # 产品相关组件
│ │ │ │ └── proposal/ # 产品提案相关组件
│ │ │ ├── composables/ # Vue Composables
│ │ │ ├── services/ # API 请求层
│ │ │ │ ├── api.ts # 请求封装
│ │ │ │ └── modules/ # 按模块拆分
│ │ │ ├── stores/ # Pinia stores
│ │ │ ├── router/
│ │ │ │ └── index.ts
│ │ │ ├── types/ # TypeScript 类型
│ │ │ ├── utils/
│ │ │ ├── assets/
│ │ │ ├── App.vue
│ │ │ └── main.ts
│ │ ├── tests/
│ │ │ ├── unit/
│ │ │ └── e2e/
│ │ ├── index.html
│ │ ├── package.json
│ │ ├── tsconfig.json
│ │ ├── vite.config.ts
│ │ └── tailwind.config.ts
│ └── shared/ # 共享代码
│ ├── src/
│ │ ├── types/ # 共享类型定义
│ │ │ ├── product.ts
│ │ │ ├── proposal.ts
│ │ │ ├── ai.ts
│ │ │ └── common.ts
│ │ ├── constants/ # 共享常量
│ │ └── utils/ # 共享工具函数
│ ├── package.json
│ └── tsconfig.json
├── docker/
│ ├── Dockerfile.api
│ ├── Dockerfile.web
│ └── docker-compose.yml # 本地开发(PostgreSQL + Redis)
├── docs/ # Harness 文档(symlinks + 产出目录)
├── USER_STORIES.md
├── ARCHITECTURE.md
├── AGENTS.md
├── .env.example
├── .nvmrc
├── .gitignore
├── .prettierrc
├── eslint.config.mjs
├── pnpm-workspace.yaml
├── package.json # Root package.json
└── turbo.json # Turborepo 配置(可选)
构建与部署命令
开发
# 安装依赖
pnpm install
# 启动本地基础设施(PostgreSQL + Redis)
docker compose -f docker/docker-compose.yml up -d
# 数据库迁移
pnpm db:migrate
# 并行启动 API + Web 开发服务器
pnpm dev
# 仅启动 API
pnpm dev:api
# 仅启动 Web
pnpm dev:web
测试
# 类型检查
pnpm typecheck
# 代码检查
pnpm lint
# 代码格式化
pnpm format
# 单元测试(带覆盖率)
pnpm test:unit -- --coverage
# 集成测试
pnpm test:integration
# E2E 测试(Playwright)
pnpm test:e2e
# 性能测试
pnpm test:perf
# 全量测试
pnpm test
构建
# 构建 API
pnpm build:api
# 构建 Web
pnpm build:web
# 构建全部
pnpm build
数据库
# 运行迁移
pnpm db:migrate
# 创建迁移
pnpm db:migrate:create
# 数据库 seed
pnpm db:seed
# 重置数据库(仅开发环境)
pnpm db:reset
# 打开 Prisma Studio
pnpm db:studio
Docker
# 构建镜像
docker build -f docker/Dockerfile.api -t mktmedia-api:latest .
docker build -f docker/Dockerfile.web -t mktmedia-web:latest .
# 本地完整环境
docker compose -f docker/docker-compose.yml up
环境变量
.env.example
# ===== API =====
API_PORT=3000
API_HOST=0.0.0.0
NODE_ENV=development
# Database
DATABASE_URL=postgresql://mktmedia:mktmedia@localhost:5432/mktmedia
# Redis
REDIS_URL=redis://localhost:6379
# JWT
JWT_SECRET=change-me-in-production
JWT_ACCESS_EXPIRES=15m
JWT_REFRESH_EXPIRES=7d
# SMTP(邮件发送)
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USER=
SMTP_PASS=
SMTP_FROM=noreply@example.com
# WhatsApp Business API
WHATSAPP_API_URL=https://graph.facebook.com/v18.0
WHATSAPP_TOKEN=
WHATSAPP_PHONE_ID=
# ERP Integration
ERP_API_URL=
ERP_API_KEY=
# AI Services — LLM
LLM_PROVIDER=gpt-5.1 # gpt-5.1 | claude-opus-4.6
LLM_API_KEY=
# AI Services — NanoBanana Image Generation
NANOBANANA_API_URL=
NANOBANANA_API_KEY=
# ===== Web =====
VITE_API_URL=http://localhost:3000
VITE_PROPOSAL_BASE_URL=http://localhost:5173/p
编码规范
通用
- 语言: TypeScript(strict mode),禁止
any - 格式化: Prettier(自动,tab-width: 2,single quote,trailing comma)
- Lint: ESLint flat config + TypeScript rules
- 命名: camelCase(变量/函数),PascalCase(类/组件/类型),UPPER_SNAKE_CASE(常量)
- 导入: 使用路径别名
@/代替相对路径 - 导出: 优先 named export,页面组件可用 default export
后端
- 分层: Controller → Service → Repository(禁止跨层调用)
- DTO: 每个 API 端点用 Zod schema 定义请求/响应,同时导出 TypeScript 类型
- 错误处理: 统一错误格式
{ error: { code, message, requestId } } - 日志: JSON 结构化,每个请求携带 requestId,禁止
console.log - 异步: async/await,禁止裸 Promise
- 函数长度: 单函数 ≤ 50 行
- 圈复杂度: ≤ 10
前端
- 组件 API:
<script setup lang="ts">(仅 Composition API) - Props:
defineProps<T>()(类型声明式) - Emits:
defineEmits<T>() - 状态: 组件内
ref/reactive,跨组件用 Pinia store - 文件结构:
<script setup>→<template>→<style scoped> - 路由: 统一在
router/index.ts中定义,页面懒加载 - 样式: Tailwind CSS 原子类优先,复杂样式用
<style scoped> - 组件粒度: 单组件 template ≤ 100 行,超出须拆分
Pre-commit 检查清单
-
pnpm typecheck通过(0 错误) -
pnpm lint通过(0 错误) -
pnpm test:unit通过(100% 通过) - 无
console.log()出现在生产代码中 - 无硬编码密钥/密码
- 新增 API 端点有对应 Zod schema 校验
- 新增页面/组件有基础单元测试
- 数据库变更有对应 migration 文件
性能基线
| 场景 | 指标 | 基线 |
|---|---|---|
| API 一般请求 P50 | 延迟 | < 200ms |
| API 一般请求 P99 | 延迟 | < 1000ms |
| 产品提案生成 P99 | 延迟 | < 10s |
| AI 产品图生成 P99 | 延迟 | < 15s |
| AI 翻译 P99 | 延迟 | < 5s |
| PDF 生成 P99 | 延迟 | < 5s |
| Web LCP | 首屏加载 | < 2.5s |
| Web FCP | 首次内容绘制 | < 1.8s |
| Web CLS | 布局稳定性 | < 0.1 |
| 收集表 LCP(3G) | 首屏加载 | < 2s |
| 交互反馈 | 响应时间 | < 100ms |
安全基线
| 维度 | 要求 |
|---|---|
| 认证 | JWT Bearer Token,短有效期(15min access + 7d refresh) |
| 密码 | bcrypt hash(salt rounds: 12),禁止明文存储 |
| 输入校验 | Zod schema 校验所有 API 输入,拒绝未定义字段 |
| SQL 注入 | Prisma 参数化查询,禁止原始 SQL 拼接 |
| XSS | Vue 自动转义 + CSP 头 |
| CORS | 白名单域名,禁止 * |
| Rate Limiting | API 全局限流 + 敏感端点独立限流 |
| HTTPS | TLS 1.2+,全站 HTTPS |
| 密钥管理 | 环境变量,禁止硬编码,.env 文件加入 .gitignore |
可靠性基线
| 维度 | 要求 |
|---|---|
| 错误处理 | 统一 try-catch,结构化错误响应(code + message + requestId) |
| 日志追踪 | 每个请求生成 requestId,贯穿所有日志 |
| 重试策略 | 外部服务调用指数退避重试(max 3 次) |
| 幂等性 | 关键操作(分发/支付)支持幂等检测 |
| 优雅降级 | Redis 不可用时降级为内存缓存;ERP 不可用时提示手动录入 |
| 健康检查 | /health + /ready 端点,2min 内无响应视为异常 |
| 超时 | 外部服务调用统一超时(默认 10s),PDF 生成超时 30s |