登录流程(以 openid 为唯一标识): - auth.service.login 改为 upsert:openid 在库续登,不在库自动建用户签 token - 个人主体无 getPhoneNumber 权限,故不强制手机号注册;register 接口保留备未来用 - wechat.service 新增 getAccessToken(Redis 缓存)、getPluginOpenPid、getUserPhoneNumber - 新增 /api/wechat/plugin-openpid、/api/wechat/phone 接口 schema 与迁移: - User 增加 openpid(可空唯一) 兼容插件场景;openid 保持必填主键 - 新增 add_openpid_optional_openid、openid_required_primary 迁移 部署: - docker-compose.yml 改为 docker compose up -d 一键启动 postgres+redis+app - app 容器内用服务名连 db/redis,启动自动跑 prisma migrate deploy - 端口统一 3090(Dockerfile EXPOSE、compose 映射同步) - 新增 docs/wechat-api-signature-guide.md API 签名手册 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
224 lines
6.1 KiB
Plaintext
224 lines
6.1 KiB
Plaintext
// 微信小程序后端 — 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:登录判定主键。首次登录验证手机号注册,后续登录 openid 在库即放行
|
|
openid String @unique
|
|
// 插件用户标识(当前未用插件,保留可空以兼容未来)
|
|
openpid 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
|
|
}
|