NestJS电子商务API开发实战教程
学习目标
- 掌握使用NestJS开发电子商务API的方法
- 理解电子商务系统的核心功能模块设计
- 学会实现产品管理、购物车、订单处理等功能
- 掌握支付集成和物流管理
- 理解电子商务系统的安全和性能优化
核心概念
电子商务系统架构设计
电子商务系统通常包含以下核心模块:
- 用户模块:用户注册、登录、个人资料管理
- 产品模块:产品管理、分类、库存管理
- 购物车模块:添加商品、修改数量、删除商品
- 订单模块:创建订单、订单状态管理、订单历史
- 支付模块:支付集成、支付状态管理
- 物流模块:物流信息管理、配送状态更新
- 评价模块:产品评价、用户反馈
- 促销模块:优惠券、折扣活动
技术选型
- 后端框架:NestJS
- 数据库:PostgreSQL
- ORM:TypeORM
- 认证:JWT
- 支付集成:Stripe/PayPal/支付宝
- 文件存储:本地存储/云存储
- API风格:RESTful
- 缓存:Redis
项目初始化
创建项目
# 创建NestJS项目
npm i -g @nestjs/cli
nest new ecommerce-api
# 进入项目目录
cd ecommerce-api
# 安装依赖
npm install @nestjs/typeorm typeorm pg @nestjs/jwt @nestjs/passport passport passport-jwt bcrypt @nestjs/config multer redis @nestjs/cache-manager cache-manager配置数据库
创建 .env 文件:
# 数据库配置
DATABASE_HOST=localhost
DATABASE_PORT=5432
DATABASE_USERNAME=postgres
DATABASE_PASSWORD=postgres
DATABASE_NAME=ecommerce_api
# JWT配置
JWT_SECRET=your-secret-key
JWT_EXPIRES_IN=3600
# 文件上传配置
UPLOAD_DIR=./uploads
# 支付配置
STRIPE_SECRET_KEY=your-stripe-secret-key
PAYPAL_CLIENT_ID=your-paypal-client-id
PAYPAL_CLIENT_SECRET=your-paypal-client-secret
# Redis配置
REDIS_HOST=localhost
REDIS_PORT=6379核心模块实现
用户模块
用户实体
创建 src/users/entities/user.entity.ts:
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
OneToMany,
} from 'typeorm';
import { Order } from '../../orders/entities/order.entity';
import { Review } from '../../reviews/entities/review.entity';
import { Cart } from '../../carts/entities/cart.entity';
export enum UserRole {
ADMIN = 'admin',
USER = 'user',
}
@Entity('users')
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column({ unique: true })
username: string;
@Column({ unique: true })
email: string;
@Column()
password: string;
@Column({ default: UserRole.USER })
role: UserRole;
@Column({ nullable: true })
avatar: string;
@Column({ nullable: true })
phone: string;
@Column({ nullable: true })
address: string;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
@OneToMany(() => Order, (order) => order.user)
orders: Order[];
@OneToMany(() => Review, (review) => review.user)
reviews: Review[];
@OneToMany(() => Cart, (cart) => cart.user)
carts: Cart[];
}用户服务
创建 src/users/users.service.ts:
import {
Injectable,
ConflictException,
UnauthorizedException,
} from '@nestjs/common';
import {
InjectRepository,
} from '@nestjs/typeorm';
import {
Repository,
} from 'typeorm';
import { User } from './entities/user.entity';
import * as bcrypt from 'bcrypt';
import { JwtService } from '@nestjs/jwt';
@Injectable()
export class UsersService {
constructor(
@InjectRepository(User)
private usersRepository: Repository<User>,
private jwtService: JwtService,
) {}
async register(username: string, email: string, password: string) {
// 检查用户是否存在
const existingUser = await this.usersRepository.findOne({
where: [{ username }, { email }],
});
if (existingUser) {
throw new ConflictException('用户名或邮箱已存在');
}
// 密码加密
const hashedPassword = await bcrypt.hash(password, 10);
// 创建用户
const user = this.usersRepository.create({
username,
email,
password: hashedPassword,
});
return await this.usersRepository.save(user);
}
async login(email: string, password: string) {
// 查找用户
const user = await this.usersRepository.findOne({
where: { email },
});
if (!user) {
throw new UnauthorizedException('邮箱或密码错误');
}
// 验证密码
const isPasswordValid = await bcrypt.compare(password, user.password);
if (!isPasswordValid) {
throw new UnauthorizedException('邮箱或密码错误');
}
// 生成JWT令牌
const payload = { userId: user.id, role: user.role };
const token = this.jwtService.sign(payload);
return {
access_token: token,
user: {
id: user.id,
username: user.username,
email: user.email,
role: user.role,
avatar: user.avatar,
phone: user.phone,
address: user.address,
},
};
}
async findOne(id: number) {
return await this.usersRepository.findOne({
where: { id },
});
}
async updateProfile(id: number, updateData: Partial<User>) {
return await this.usersRepository.update(id, updateData);
}
}产品模块
产品实体
创建 src/products/entities/product.entity.ts:
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
ManyToOne,
OneToMany,
} from 'typeorm';
import { Category } from '../../categories/entities/category.entity';
import { Review } from '../../reviews/entities/review.entity';
import { Cart } from '../../carts/entities/cart.entity';
import { OrderItem } from '../../orders/entities/order-item.entity';
export enum ProductStatus {
ACTIVE = 'active',
INACTIVE = 'inactive',
OUT_OF_STOCK = 'out_of_stock',
}
@Entity('products')
export class Product {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@Column()
description: string;
@Column('decimal', { precision: 10, scale: 2 })
price: number;
@Column()
stock: number;
@Column({ default: ProductStatus.ACTIVE })
status: ProductStatus;
@Column('simple-array', { nullable: true })
images: string[];
@Column('decimal', { precision: 5, scale: 2, nullable: true })
discount: number;
@Column('decimal', { precision: 10, scale: 2, nullable: true })
discountedPrice: number;
@Column({ default: 0 })
viewCount: number;
@Column({ default: 0 })
soldCount: number;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
@ManyToOne(() => Category, (category) => category.products)
category: Category;
@OneToMany(() => Review, (review) => review.product)
reviews: Review[];
@OneToMany(() => Cart, (cart) => cart.product)
carts: Cart[];
@OneToMany(() => OrderItem, (orderItem) => orderItem.product)
orderItems: OrderItem[];
}产品服务
创建 src/products/products.service.ts:
import {
Injectable,
NotFoundException,
} from '@nestjs/common';
import {
InjectRepository,
} from '@nestjs/typeorm';
import {
Repository,
Like,
Between,
} from 'typeorm';
import { Product, ProductStatus } from './entities/product.entity';
import { Category } from '../../categories/entities/category.entity';
@Injectable()
export class ProductsService {
constructor(
@InjectRepository(Product)
private productsRepository: Repository<Product>,
@InjectRepository(Category)
private categoriesRepository: Repository<Category>,
) {}
// 创建产品
async create(createProductDto: any) {
const { categoryId, ...productData } = createProductDto;
// 计算折扣价格
if (productData.discount) {
productData.discountedPrice = productData.price * (1 - productData.discount / 100);
}
const product = this.productsRepository.create({
...productData,
category: categoryId ? { id: categoryId } : null,
});
return await this.productsRepository.save(product);
}
// 获取产品列表
async findAll(query: any) {
const {
page = 1,
limit = 10,
category,
minPrice,
maxPrice,
search,
sort = 'createdAt',
order = 'DESC',
} = query;
const skip = (page - 1) * limit;
const where: any = {
status: ProductStatus.ACTIVE,
};
if (category) {
where.category = { id: category };
}
if (minPrice && maxPrice) {
where.price = Between(minPrice, maxPrice);
} else if (minPrice) {
where.price = Between(minPrice, 999999);
} else if (maxPrice) {
where.price = Between(0, maxPrice);
}
if (search) {
where.name = Like(`%${search}%`);
}
const [products, total] = await this.productsRepository.findAndCount({
where,
skip,
take: limit,
relations: ['category'],
order: { [sort]: order },
});
return {
products,
total,
page,
limit,
pages: Math.ceil(total / limit),
};
}
// 获取单个产品
async findOne(id: number) {
const product = await this.productsRepository.findOne({
where: { id },
relations: ['category', 'reviews', 'reviews.user'],
});
if (!product) {
throw new NotFoundException('产品不存在');
}
// 增加浏览量
product.viewCount++;
await this.productsRepository.save(product);
return product;
}
// 更新产品
async update(id: number, updateProductDto: any) {
const { categoryId, ...productData } = updateProductDto;
// 计算折扣价格
if (productData.discount) {
productData.discountedPrice = productData.price * (1 - productData.discount / 100);
}
const updateData: any = {
...productData,
};
if (categoryId) {
updateData.category = { id: categoryId };
}
return await this.productsRepository.update(id, updateData);
}
// 删除产品
async remove(id: number) {
return await this.productsRepository.delete(id);
}
// 批量更新库存
async updateStock(productId: number, quantity: number) {
const product = await this.productsRepository.findOne({
where: { id: productId },
});
if (!product) {
throw new NotFoundException('产品不存在');
}
if (product.stock < quantity) {
throw new NotFoundException('产品库存不足');
}
product.stock -= quantity;
product.soldCount += quantity;
if (product.stock === 0) {
product.status = ProductStatus.OUT_OF_STOCK;
}
return await this.productsRepository.save(product);
}
}购物车模块
购物车实体
创建 src/carts/entities/cart.entity.ts:
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
ManyToOne,
} from 'typeorm';
import { User } from '../../users/entities/user.entity';
import { Product } from '../../products/entities/product.entity';
@Entity('carts')
export class Cart {
@PrimaryGeneratedColumn()
id: number;
@Column()
quantity: number;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
@ManyToOne(() => User, (user) => user.carts)
user: User;
@ManyToOne(() => Product, (product) => product.carts)
product: Product;
}购物车服务
创建 src/carts/carts.service.ts:
import {
Injectable,
NotFoundException,
} from '@nestjs/common';
import {
InjectRepository,
} from '@nestjs/typeorm';
import {
Repository,
} from 'typeorm';
import { Cart } from './entities/cart.entity';
import { ProductsService } from '../../products/products.service';
@Injectable()
export class CartsService {
constructor(
@InjectRepository(Cart)
private cartsRepository: Repository<Cart>,
private productsService: ProductsService,
) {}
// 添加商品到购物车
async addToCart(userId: number, productId: number, quantity: number) {
// 检查商品是否存在
const product = await this.productsService.findOne(productId);
if (!product) {
throw new NotFoundException('商品不存在');
}
// 检查购物车中是否已存在该商品
const existingCart = await this.cartsRepository.findOne({
where: {
user: { id: userId },
product: { id: productId },
},
});
if (existingCart) {
// 更新数量
existingCart.quantity += quantity;
return await this.cartsRepository.save(existingCart);
} else {
// 创建新购物车项
const cart = this.cartsRepository.create({
user: { id: userId },
product: { id: productId },
quantity,
});
return await this.cartsRepository.save(cart);
}
}
// 获取用户购物车
async getCart(userId: number) {
const carts = await this.cartsRepository.find({
where: { user: { id: userId } },
relations: ['product'],
});
// 计算总价
const total = carts.reduce((sum, cart) => {
const price = cart.product.discountedPrice || cart.product.price;
return sum + price * cart.quantity;
}, 0);
return {
items: carts,
total,
count: carts.length,
};
}
// 更新购物车商品数量
async updateQuantity(userId: number, cartId: number, quantity: number) {
const cart = await this.cartsRepository.findOne({
where: {
id: cartId,
user: { id: userId },
},
});
if (!cart) {
throw new NotFoundException('购物车项不存在');
}
cart.quantity = quantity;
return await this.cartsRepository.save(cart);
}
// 删除购物车商品
async removeFromCart(userId: number, cartId: number) {
return await this.cartsRepository.delete({
id: cartId,
user: { id: userId },
});
}
// 清空购物车
async clearCart(userId: number) {
return await this.cartsRepository.delete({
user: { id: userId },
});
}
}订单模块
订单实体
创建 src/orders/entities/order.entity.ts:
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
ManyToOne,
OneToMany,
} from 'typeorm';
import { User } from '../../users/entities/user.entity';
import { OrderItem } from './order-item.entity';
export enum OrderStatus {
PENDING = 'pending',
PAID = 'paid',
PROCESSING = 'processing',
SHIPPED = 'shipped',
DELIVERED = 'delivered',
CANCELLED = 'cancelled',
}
@Entity('orders')
export class Order {
@PrimaryGeneratedColumn()
id: number;
@Column()
orderNumber: string;
@Column('decimal', { precision: 10, scale: 2 })
totalAmount: number;
@Column({ default: OrderStatus.PENDING })
status: OrderStatus;
@Column({ nullable: true })
paymentMethod: string;
@Column({ nullable: true })
paymentId: string;
@Column({ nullable: true })
shippingAddress: string;
@Column({ nullable: true })
trackingNumber: string;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
@ManyToOne(() => User, (user) => user.orders)
user: User;
@OneToMany(() => OrderItem, (orderItem) => orderItem.order)
items: OrderItem[];
}订单项实体
创建 src/orders/entities/order-item.entity.ts:
import {
Entity,
PrimaryGeneratedColumn,
Column,
ManyToOne,
} from 'typeorm';
import { Order } from './order.entity';
import { Product } from '../../products/entities/product.entity';
@Entity('order_items')
export class OrderItem {
@PrimaryGeneratedColumn()
id: number;
@Column()
quantity: number;
@Column('decimal', { precision: 10, scale: 2 })
price: number;
@Column('decimal', { precision: 10, scale: 2 })
total: number;
@ManyToOne(() => Order, (order) => order.items)
order: Order;
@ManyToOne(() => Product)
product: Product;
}订单服务
创建 src/orders/orders.service.ts:
import {
Injectable,
NotFoundException,
} from '@nestjs/common';
import {
InjectRepository,
} from '@nestjs/typeorm';
import {
Repository,
} from 'typeorm';
import { Order, OrderStatus } from './entities/order.entity';
import { OrderItem } from './entities/order-item.entity';
import { CartsService } from '../../carts/carts.service';
import { ProductsService } from '../../products/products.service';
@Injectable()
export class OrdersService {
constructor(
@InjectRepository(Order)
private ordersRepository: Repository<Order>,
@InjectRepository(OrderItem)
private orderItemsRepository: Repository<OrderItem>,
private cartsService: CartsService,
private productsService: ProductsService,
) {}
// 创建订单
async createOrder(userId: number, shippingAddress: string, paymentMethod: string) {
// 获取购物车
const cart = await this.cartsService.getCart(userId);
if (cart.items.length === 0) {
throw new NotFoundException('购物车为空');
}
// 生成订单号
const orderNumber = `ORDER-${Date.now()}-${Math.floor(Math.random() * 1000)}`;
// 创建订单
const order = this.ordersRepository.create({
user: { id: userId },
orderNumber,
totalAmount: cart.total,
shippingAddress,
paymentMethod,
});
const savedOrder = await this.ordersRepository.save(order);
// 创建订单项
for (const item of cart.items) {
const orderItem = this.orderItemsRepository.create({
order: { id: savedOrder.id },
product: { id: item.product.id },
quantity: item.quantity,
price: item.product.discountedPrice || item.product.price,
total: (item.product.discountedPrice || item.product.price) * item.quantity,
});
await this.orderItemsRepository.save(orderItem);
// 更新产品库存
await this.productsService.updateStock(item.product.id, item.quantity);
}
// 清空购物车
await this.cartsService.clearCart(userId);
return savedOrder;
}
// 获取用户订单列表
async getUserOrders(userId: number, query: any) {
const {
page = 1,
limit = 10,
status,
} = query;
const skip = (page - 1) * limit;
const where: any = {
user: { id: userId },
};
if (status) {
where.status = status;
}
const [orders, total] = await this.ordersRepository.findAndCount({
where,
skip,
take: limit,
relations: ['items', 'items.product'],
order: { createdAt: 'DESC' },
});
return {
orders,
total,
page,
limit,
pages: Math.ceil(total / limit),
};
}
// 获取订单详情
async getOrderById(userId: number, orderId: number) {
const order = await this.ordersRepository.findOne({
where: {
id: orderId,
user: { id: userId },
},
relations: ['items', 'items.product'],
});
if (!order) {
throw new NotFoundException('订单不存在');
}
return order;
}
// 更新订单状态
async updateOrderStatus(orderId: number, status: OrderStatus) {
return await this.ordersRepository.update(orderId, { status });
}
// 更新物流信息
async updateTrackingNumber(orderId: number, trackingNumber: string) {
return await this.ordersRepository.update(orderId, {
trackingNumber,
status: OrderStatus.SHIPPED,
});
}
}支付模块
支付服务
创建 src/payments/payments.service.ts:
import {
Injectable,
BadRequestException,
} from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import Stripe from 'stripe';
import { OrdersService } from '../../orders/orders.service';
import { OrderStatus } from '../../orders/entities/order.entity';
@Injectable()
export class PaymentsService {
private stripe: Stripe;
constructor(
private configService: ConfigService,
private ordersService: OrdersService,
) {
this.stripe = new Stripe(configService.get('STRIPE_SECRET_KEY'), {
apiVersion: '2024-06-20',
});
}
// 创建支付意图
async createPaymentIntent(orderId: number, amount: number) {
try {
const paymentIntent = await this.stripe.paymentIntents.create({
amount: Math.round(amount * 100), // 转换为分
currency: 'cny',
metadata: { orderId: orderId.toString() },
});
return {
clientSecret: paymentIntent.client_secret,
paymentIntentId: paymentIntent.id,
};
} catch (error) {
throw new BadRequestException('创建支付意图失败');
}
}
// 处理支付回调
async handlePaymentCallback(paymentId: string, status: string) {
if (status === 'succeeded') {
// 查找订单
// 更新订单状态
await this.ordersService.updateOrderStatus(
parseInt(paymentId.split('_')[1]),
OrderStatus.PAID,
);
}
return { status };
}
// 退款
async refund(orderId: number, amount: number) {
try {
const refund = await this.stripe.refunds.create({
amount: Math.round(amount * 100),
payment_intent: orderId.toString(),
});
return refund;
} catch (error) {
throw new BadRequestException('退款失败');
}
}
}API控制器
用户控制器
创建 src/users/users.controller.ts:
import {
Controller,
Post,
Get,
Body,
UseGuards,
Request,
Patch,
} from '@nestjs/common';
import { UsersService } from './users.service';
import { AuthGuard } from '@nestjs/passport';
@Controller('users')
export class UsersController {
constructor(private usersService: UsersService) {}
// 注册
@Post('register')
async register(@Body() body: { username: string; email: string; password: string }) {
return await this.usersService.register(body.username, body.email, body.password);
}
// 登录
@Post('login')
async login(@Body() body: { email: string; password: string }) {
return await this.usersService.login(body.email, body.password);
}
// 获取个人资料
@UseGuards(AuthGuard('jwt'))
@Get('profile')
async getProfile(@Request() req) {
return await this.usersService.findOne(req.user.userId);
}
// 更新个人资料
@UseGuards(AuthGuard('jwt'))
@Patch('profile')
async updateProfile(@Request() req, @Body() body) {
return await this.usersService.updateProfile(req.user.userId, body);
}
}产品控制器
创建 src/products/products.controller.ts:
import {
Controller,
Post,
Get,
Put,
Delete,
Body,
Param,
Query,
UseGuards,
Request,
} from '@nestjs/common';
import { ProductsService } from './products.service';
import { AuthGuard } from '@nestjs/passport';
@Controller('products')
export class ProductsController {
constructor(private productsService: ProductsService) {}
// 创建产品
@UseGuards(AuthGuard('jwt'))
@Post()
async create(@Request() req, @Body() body) {
return await this.productsService.create(body);
}
// 获取产品列表
@Get()
async findAll(@Query() query) {
return await this.productsService.findAll(query);
}
// 获取单个产品
@Get(':id')
async findOne(@Param('id') id: number) {
return await this.productsService.findOne(id);
}
// 更新产品
@UseGuards(AuthGuard('jwt'))
@Put(':id')
async update(@Param('id') id: number, @Body() body) {
return await this.productsService.update(id, body);
}
// 删除产品
@UseGuards(AuthGuard('jwt'))
@Delete(':id')
async remove(@Param('id') id: number) {
return await this.productsService.remove(id);
}
}购物车控制器
创建 src/carts/carts.controller.ts:
import {
Controller,
Post,
Get,
Put,
Delete,
Body,
Param,
UseGuards,
Request,
} from '@nestjs/common';
import { CartsService } from './carts.service';
import { AuthGuard } from '@nestjs/passport';
@Controller('carts')
export class CartsController {
constructor(private cartsService: CartsService) {}
// 添加商品到购物车
@UseGuards(AuthGuard('jwt'))
@Post()
async addToCart(@Request() req, @Body() body: { productId: number; quantity: number }) {
return await this.cartsService.addToCart(req.user.userId, body.productId, body.quantity);
}
// 获取购物车
@UseGuards(AuthGuard('jwt'))
@Get()
async getCart(@Request() req) {
return await this.cartsService.getCart(req.user.userId);
}
// 更新购物车商品数量
@UseGuards(AuthGuard('jwt'))
@Put(':id')
async updateQuantity(@Request() req, @Param('id') id: number, @Body() body: { quantity: number }) {
return await this.cartsService.updateQuantity(req.user.userId, id, body.quantity);
}
// 删除购物车商品
@UseGuards(AuthGuard('jwt'))
@Delete(':id')
async removeFromCart(@Request() req, @Param('id') id: number) {
return await this.cartsService.removeFromCart(req.user.userId, id);
}
// 清空购物车
@UseGuards(AuthGuard('jwt'))
@Delete()
async clearCart(@Request() req) {
return await this.cartsService.clearCart(req.user.userId);
}
}订单控制器
创建 src/orders/orders.controller.ts:
import {
Controller,
Post,
Get,
Put,
Body,
Param,
Query,
UseGuards,
Request,
} from '@nestjs/common';
import { OrdersService } from './orders/orders.service';
import { AuthGuard } from '@nestjs/passport';
@Controller('orders')
export class OrdersController {
constructor(private ordersService: OrdersService) {}
// 创建订单
@UseGuards(AuthGuard('jwt'))
@Post()
async createOrder(@Request() req, @Body() body: { shippingAddress: string; paymentMethod: string }) {
return await this.ordersService.createOrder(req.user.userId, body.shippingAddress, body.paymentMethod);
}
// 获取用户订单列表
@UseGuards(AuthGuard('jwt'))
@Get()
async getUserOrders(@Request() req, @Query() query) {
return await this.ordersService.getUserOrders(req.user.userId, query);
}
// 获取订单详情
@UseGuards(AuthGuard('jwt'))
@Get(':id')
async getOrderById(@Request() req, @Param('id') id: number) {
return await this.ordersService.getOrderById(req.user.userId, id);
}
// 更新订单状态
@UseGuards(AuthGuard('jwt'))
@Put(':id/status')
async updateOrderStatus(@Param('id') id: number, @Body() body: { status: string }) {
return await this.ordersService.updateOrderStatus(id, body.status);
}
// 更新物流信息
@UseGuards(AuthGuard('jwt'))
@Put(':id/tracking')
async updateTrackingNumber(@Param('id') id: number, @Body() body: { trackingNumber: string }) {
return await this.ordersService.updateTrackingNumber(id, body.trackingNumber);
}
}支付控制器
创建 src/payments/payments.controller.ts:
import {
Controller,
Post,
Get,
Body,
Param,
UseGuards,
Request,
} from '@nestjs/common';
import { PaymentsService } from './payments.service';
import { AuthGuard } from '@nestjs/passport';
@Controller('payments')
export class PaymentsController {
constructor(private paymentsService: PaymentsService) {}
// 创建支付意图
@UseGuards(AuthGuard('jwt'))
@Post('intent')
async createPaymentIntent(@Body() body: { orderId: number; amount: number }) {
return await this.paymentsService.createPaymentIntent(body.orderId, body.amount);
}
// 处理支付回调
@Post('webhook')
async handlePaymentCallback(@Body() body) {
return await this.paymentsService.handlePaymentCallback(body.paymentId, body.status);
}
// 退款
@UseGuards(AuthGuard('jwt'))
@Post('refund')
async refund(@Body() body: { orderId: number; amount: number }) {
return await this.paymentsService.refund(body.orderId, body.amount);
}
}主模块配置
创建 src/app.module.ts:
import {
Module,
} from '@nestjs/common';
import {
TypeOrmModule,
} from '@nestjs/typeorm';
import {
ConfigModule,
ConfigService,
} from '@nestjs/config';
import { UsersModule } from './users/users.module';
import { ProductsModule } from './products/products.module';
import { CartsModule } from './carts/carts.module';
import { OrdersModule } from './orders/orders.module';
import { PaymentsModule } from './payments/payments.module';
import { CategoriesModule } from './categories/categories.module';
import { ReviewsModule } from './reviews/reviews.module';
import { AuthModule } from './auth/auth.module';
import { CacheModule } from '@nestjs/cache-manager';
import * as redisStore from 'cache-manager-redis-store';
import { User } from './users/entities/user.entity';
import { Product } from './products/entities/product.entity';
import { Category } from './categories/entities/category.entity';
import { Cart } from './carts/entities/cart.entity';
import { Order } from './orders/entities/order.entity';
import { OrderItem } from './orders/entities/order-item.entity';
import { Review } from './reviews/entities/review.entity';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
}),
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
type: 'postgres',
host: configService.get('DATABASE_HOST'),
port: parseInt(configService.get('DATABASE_PORT')),
username: configService.get('DATABASE_USERNAME'),
password: configService.get('DATABASE_PASSWORD'),
database: configService.get('DATABASE_NAME'),
entities: [User, Product, Category, Cart, Order, OrderItem, Review],
synchronize: true,
}),
inject: [ConfigService],
}),
CacheModule.registerAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
store: redisStore,
host: configService.get('REDIS_HOST'),
port: parseInt(configService.get('REDIS_PORT')),
ttl: 60 * 60, // 1小时
}),
inject: [ConfigService],
}),
UsersModule,
ProductsModule,
CartsModule,
OrdersModule,
PaymentsModule,
CategoriesModule,
ReviewsModule,
AuthModule,
],
})
export class AppModule {}运行项目
启动开发服务器
# 启动开发服务器
npm run start:devAPI文档
使用Postman或其他API测试工具测试以下API:
用户接口:
POST /users/register- 注册POST /users/login- 登录GET /users/profile- 获取个人资料PATCH /users/profile- 更新个人资料
产品接口:
POST /products- 创建产品GET /products- 获取产品列表GET /products/:id- 获取单个产品PUT /products/:id- 更新产品DELETE /products/:id- 删除产品
购物车接口:
POST /carts- 添加商品到购物车GET /carts- 获取购物车PUT /carts/:id- 更新购物车商品数量DELETE /carts/:id- 删除购物车商品DELETE /carts- 清空购物车
订单接口:
POST /orders- 创建订单GET /orders- 获取用户订单列表GET /orders/:id- 获取订单详情PUT /orders/:id/status- 更新订单状态PUT /orders/:id/tracking- 更新物流信息
支付接口:
POST /payments/intent- 创建支付意图POST /payments/webhook- 处理支付回调POST /payments/refund- 退款
前端集成
前端技术选型
- 框架:React/Vue/Angular
- 状态管理:Redux/Vuex/Pinia
- HTTP客户端:Axios
- UI组件库:Ant Design/Element Plus
- 支付集成:Stripe.js/PayPal SDK
- 路由:React Router/Vue Router
API调用示例
// 使用Axios调用API
import axios from 'axios';
// 创建axios实例
const api = axios.create({
baseURL: 'http://localhost:3000',
timeout: 10000,
});
// 请求拦截器
api.interceptors.request.use(
(config) => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
// 获取产品列表
async function getProducts(params) {
const response = await api.get('/products', { params });
return response.data;
}
// 添加商品到购物车
async function addToCart(productId, quantity) {
const response = await api.post('/carts', { productId, quantity });
return response.data;
}
// 创建订单
async function createOrder(shippingAddress, paymentMethod) {
const response = await api.post('/orders', { shippingAddress, paymentMethod });
return response.data;
}
// 创建支付意图
async function createPaymentIntent(orderId, amount) {
const response = await api.post('/payments/intent', { orderId, amount });
return response.data;
}部署上线
构建项目
# 构建生产版本
npm run build部署到服务器
- 使用PM2管理进程:
# 安装PM2
npm i -g pm2
# 启动应用
npm run start:prod
# 或者使用PM2
npm run build
pm run start:prod- 使用Docker部署:
创建 Dockerfile:
FROM node:16-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
EXPOSE 3000
CMD ["npm", "run", "start:prod"]创建 docker-compose.yml:
version: '3'
services:
app:
build: .
ports:
- '3000:3000'
depends_on:
- db
- redis
environment:
- DATABASE_HOST=db
- DATABASE_PORT=5432
- DATABASE_USERNAME=postgres
- DATABASE_PASSWORD=postgres
- DATABASE_NAME=ecommerce_api
- REDIS_HOST=redis
- REDIS_PORT=6379
db:
image: postgres:13
ports:
- '5432:5432'
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
- POSTGRES_DB=ecommerce_api
volumes:
- postgres-data:/var/lib/postgresql/data
redis:
image: redis:6
ports:
- '6379:6379'
volumes:
- redis-data:/data
volumes:
postgres-data:
redis-data:启动Docker容器:
docker-compose up -d性能优化
数据库优化
- 添加索引:
// 在实体中添加索引
@Entity('products')
export class Product {
@Index()
@Column()
name: string;
@Index()
@Column()
price: number;
@Index()
@Column()
status: ProductStatus;
}- 使用缓存:
// 使用缓存
import { Cache } from 'cache-manager';
import { Inject, CACHE_MANAGER } from '@nestjs/common';
@Injectable()
export class ProductsService {
constructor(
@Inject(CACHE_MANAGER)
private cacheManager: Cache,
) {}
async findAll(query: any) {
const cacheKey = `products_${JSON.stringify(query)}`;
const cachedData = await this.cacheManager.get(cacheKey);
if (cachedData) {
return cachedData;
}
// 获取数据
const data = await this.productsRepository.findAndCount(...);
// 设置缓存
await this.cacheManager.set(cacheKey, data, 3600);
return data;
}
}代码优化
- 使用DTO验证:
# 安装验证模块
npm install class-validator class-transformer- 使用事务:
// 使用事务
async createOrder(userId: number, shippingAddress: string, paymentMethod: string) {
return await this.orm.transaction(async (manager) => {
// 创建订单
const order = manager.create(Order, {
user: { id: userId },
orderNumber: `ORDER-${Date.now()}`,
totalAmount: cart.total,
shippingAddress,
paymentMethod,
});
const savedOrder = await manager.save(order);
// 创建订单项
for (const item of cart.items) {
const orderItem = manager.create(OrderItem, {
order: { id: savedOrder.id },
product: { id: item.product.id },
quantity: item.quantity,
price: item.product.discountedPrice || item.product.price,
total: (item.product.discountedPrice || item.product.price) * item.quantity,
});
await manager.save(orderItem);
}
return savedOrder;
});
}安全措施
- 输入验证:使用class-validator验证输入数据
- SQL注入防护:使用TypeORM的参数化查询
- XSS防护:对用户输入进行转义
- CSRF防护:实现CSRF令牌验证
- 密码安全:使用bcrypt加密存储密码
- API限流:实现请求频率限制
- HTTPS:在生产环境使用HTTPS
- 支付安全:使用安全的支付集成方案
- 数据加密:对敏感数据进行加密存储
- 定期安全审计:定期检查系统安全漏洞
总结
本教程详细讲解了如何使用NestJS框架开发一个完整的电子商务API系统,包括用户认证、产品管理、购物车、订单处理、支付集成等核心功能。通过本教程的学习,你应该能够:
- 理解电子商务系统的整体架构设计
- 掌握NestJS框架的核心功能和使用方法
- 学会使用TypeORM操作数据库
- 实现基于JWT的认证系统
- 开发RESTful API接口
- 了解支付集成和物流管理
- 掌握项目部署和性能优化技巧
互动问答
问题1:如何实现产品分类和筛选功能?
答案:在产品实体中添加分类关联,通过查询参数实现分类筛选、价格范围筛选、关键词搜索等功能。
问题2:如何实现购物车功能?
答案:创建购物车实体,实现添加商品、更新数量、删除商品、清空购物车等功能,并在创建订单时更新产品库存。
问题3:如何实现支付集成?
答案:使用Stripe、PayPal等支付服务的SDK,实现支付意图创建、支付状态管理、退款等功能。
问题4:如何优化电子商务系统的性能?
答案:可以通过添加数据库索引、使用Redis缓存、优化查询语句、实现分页加载、使用CDN加速等方式提高系统性能。
问题5:如何确保电子商务系统的安全性?
答案:可以通过输入验证、SQL注入防护、XSS防护、CSRF防护、密码安全、API限流、HTTPS、支付安全、数据加密、定期安全审计等措施确保系统安全。
实践作业
实现评价模块:
- 创建产品评价功能
- 实现评价的增删改查
- 添加评价星级系统
实现促销模块:
- 创建优惠券功能
- 实现折扣活动管理
- 添加促销代码验证
实现物流模块:
- 集成物流API
- 实现物流信息查询
- 添加配送状态更新
实现搜索功能:
- 集成Elasticsearch
- 实现产品全文搜索
- 添加搜索建议功能
实现后台管理系统:
- 创建管理员控制台
- 实现用户管理、产品管理、订单管理
- 添加数据统计和分析功能
通过完成这些作业,你将能够进一步巩固所学知识,开发出功能更加完善的电子商务系统。