feat: 初始化微信小程序后端骨架

- NestJS + TypeScript + Prisma + PostgreSQL 工程骨架
- 微信登录安全流程:服务端 code2Session 换 openid 后签发 JWT,
  session_key 缓存于 Redis,不信任前端 openid
- 统一响应/异常处理、JWT 全局鉴权(@Public 豁免)、Swagger 文档
- Prisma 全量核心 schema(用户/分类/商品/设计清单/地址/订单/支付/上传/定制任务)+ seed
- 业务模块空壳(商品/分类/设计清单/地址/订单/支付/上传/BullMQ 队列)
- Docker 多阶段镜像 + 本地/生产 docker-compose
- docs:密钥获取指南、COS SDK 移除记录

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-05 17:33:50 +08:00
co-authored by Claude Fable 5
commit deaa0c9ce4
73 changed files with 9533 additions and 0 deletions
+220
View File
@@ -0,0 +1,220 @@
// 微信小程序后端 — Prisma schema
// 数据库:PostgreSQL
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
// ── 用户 ──────────────────────────────────────────────
model User {
id String @id @default(cuid())
openid String @unique
unionid String?
nickname String?
avatar String?
phone String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
orders Order[]
addresses Address[]
designLists DesignList[]
uploads Upload[]
customizations CustomizationTask[]
@@index([phone])
}
// ── 商品分类 ──────────────────────────────────────────
model Category {
id String @id @default(cuid())
name String
parentId String?
sort Int @default(0)
parent Category? @relation("CategoryTree", fields: [parentId], references: [id])
children Category[] @relation("CategoryTree")
products Product[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([parentId])
}
// ── 商品 ──────────────────────────────────────────────
model Product {
id String @id @default(cuid())
name String
categoryId String?
price Decimal @db.Decimal(10, 2)
images String[]
description String?
status ProductStatus @default(DRAFT)
category Category? @relation(fields: [categoryId], references: [id])
orderItems OrderItem[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([categoryId])
@@index([status])
}
enum ProductStatus {
DRAFT
ON_SALE
OFF_SHELF
}
// ── 设计清单 / 定制需求 ───────────────────────────────
model DesignList {
id String @id @default(cuid())
userId String
title String
items Json
status DesignListStatus @default(DRAFT)
user User @relation(fields: [userId], references: [id])
customizations CustomizationTask[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([userId])
}
enum DesignListStatus {
DRAFT
SUBMITTED
PROCESSING
DONE
}
// ── 收货地址 ──────────────────────────────────────────
model Address {
id String @id @default(cuid())
userId String
name String
phone String
province String
city String
district String
detail String
isDefault Boolean @default(false)
user User @relation(fields: [userId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([userId])
}
// ── 订单 ──────────────────────────────────────────────
model Order {
id String @id @default(cuid())
orderNo String @unique
userId String
status OrderStatus @default(PENDING)
totalAmount Decimal @db.Decimal(10, 2)
addressSnapshot Json
user User @relation(fields: [userId], references: [id])
items OrderItem[]
payment Payment?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([userId])
@@index([status])
}
enum OrderStatus {
PENDING
PAID
PROCESSING
SHIPPED
COMPLETED
CANCELLED
}
model OrderItem {
id String @id @default(cuid())
orderId String
productId String?
name String
price Decimal @db.Decimal(10, 2)
quantity Int
order Order @relation(fields: [orderId], references: [id], onDelete: Cascade)
product Product? @relation(fields: [productId], references: [id])
@@index([orderId])
}
// ── 支付 ──────────────────────────────────────────────
model Payment {
id String @id @default(cuid())
orderId String @unique
transactionId String?
provider PaymentProvider @default(WECHAT)
status PaymentStatus @default(PENDING)
paidAt DateTime?
order Order @relation(fields: [orderId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
enum PaymentProvider {
WECHAT
}
enum PaymentStatus {
PENDING
SUCCESS
REFUNDED
}
// ── 文件上传 ──────────────────────────────────────────
model Upload {
id String @id @default(cuid())
userId String
cosKey String
url String
mimetype String?
size Int?
user User @relation(fields: [userId], references: [id])
createdAt DateTime @default(now())
@@index([userId])
}
// ── 异步定制任务 ──────────────────────────────────────
model CustomizationTask {
id String @id @default(cuid())
designListId String?
userId String?
status CustomizationTaskStatus @default(PENDING)
resultUrl String?
queueJobId String?
designList DesignList? @relation(fields: [designListId], references: [id])
user User? @relation(fields: [userId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([designListId])
@@index([status])
}
enum CustomizationTaskStatus {
PENDING
RUNNING
SUCCESS
FAILED
}