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
+58
View File
@@ -0,0 +1,58 @@
import { Injectable } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { PrismaService } from '../prisma/prisma.service';
import { CreateOrderDto } from './dto/create-order.dto';
// 订单号生成:日期 + 随机后缀(无 Math.random 依赖要求仅限 workflow 脚本,普通运行时可用)
function generateOrderNo(): string {
const now = new Date();
const ymd =
now.getUTCFullYear().toString() +
String(now.getUTCMonth() + 1).padStart(2, '0') +
String(now.getUTCDate()).padStart(2, '0');
const rand = Math.random().toString(36).slice(2, 8).toUpperCase();
return `${ymd}${rand}`;
}
@Injectable()
export class OrdersService {
constructor(private readonly prisma: PrismaService) {}
async listByUser(userId: string) {
return this.prisma.order.findMany({
where: { userId },
include: { items: true, payment: true },
orderBy: { createdAt: 'desc' },
});
}
async findOne(id: string) {
// TODO: 权限校验(仅本人)
return this.prisma.order.findUnique({
where: { id },
include: { items: true, payment: true },
});
}
async create(userId: string, dto: CreateOrderDto) {
// TODO: 商品库存/价格校验、事务原子性、金额防篡改(服务端重算 totalAmount
const totalAmount = dto.items.reduce((sum, it) => sum + it.price * it.quantity, 0);
const data: Prisma.OrderCreateInput = {
orderNo: generateOrderNo(),
user: { connect: { id: userId } },
totalAmount,
addressSnapshot: dto.addressSnapshot as Prisma.InputJsonValue,
items: {
create: dto.items.map((it) => ({
productId: it.productId,
name: it.name,
price: it.price,
quantity: it.quantity,
})),
},
};
return this.prisma.order.create({ data, include: { items: true } });
}
}