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:
@@ -0,0 +1,40 @@
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { IsArray, IsNotEmpty, IsObject, IsOptional, IsString, ValidateNested } from 'class-validator';
|
||||
import { Type } from 'class-transformer';
|
||||
|
||||
export class OrderItemDto {
|
||||
@ApiPropertyOptional({ description: '商品 id(定制类可空)' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
productId?: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
name!: string;
|
||||
|
||||
@ApiProperty({ description: '单价(元)' })
|
||||
@Type(() => Number)
|
||||
price!: number;
|
||||
|
||||
@ApiProperty({ description: '数量' })
|
||||
@Type(() => Number)
|
||||
quantity!: number;
|
||||
}
|
||||
|
||||
export class CreateOrderDto {
|
||||
@ApiProperty({ description: '收货地址快照(JSON)' })
|
||||
@IsObject()
|
||||
addressSnapshot!: Record<string, unknown>;
|
||||
|
||||
@ApiProperty({ type: [OrderItemDto] })
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => OrderItemDto)
|
||||
items!: OrderItemDto[];
|
||||
|
||||
@ApiPropertyOptional({ description: '关联设计清单 id(定制订单)' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
designListId?: string;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { Body, Controller, Get, Param, Post } from '@nestjs/common';
|
||||
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
import { CurrentUser, JwtPayload } from '../common/decorators/current-user.decorator';
|
||||
import { OrdersService } from './orders.service';
|
||||
import { CreateOrderDto } from './dto/create-order.dto';
|
||||
|
||||
@ApiTags('订单')
|
||||
@ApiBearerAuth()
|
||||
@Controller('orders')
|
||||
export class OrdersController {
|
||||
constructor(private readonly ordersService: OrdersService) {}
|
||||
|
||||
@Get()
|
||||
@ApiOperation({ summary: '我的订单列表' })
|
||||
list(@CurrentUser() user: JwtPayload) {
|
||||
return this.ordersService.listByUser(user.sub);
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@ApiOperation({ summary: '订单详情' })
|
||||
findOne(@Param('id') id: string) {
|
||||
return this.ordersService.findOne(id);
|
||||
}
|
||||
|
||||
@Post()
|
||||
@ApiOperation({ summary: '创建订单' })
|
||||
create(@CurrentUser() user: JwtPayload, @Body() dto: CreateOrderDto) {
|
||||
return this.ordersService.create(user.sub, dto);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { OrdersController } from './orders.controller';
|
||||
import { OrdersService } from './orders.service';
|
||||
|
||||
@Module({
|
||||
controllers: [OrdersController],
|
||||
providers: [OrdersService],
|
||||
exports: [OrdersService],
|
||||
})
|
||||
export class OrdersModule {}
|
||||
@@ -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 } });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user