NestJS电子商务API开发实战教程

学习目标

  • 掌握使用NestJS开发电子商务API的方法
  • 理解电子商务系统的核心功能模块设计
  • 学会实现产品管理、购物车、订单处理等功能
  • 掌握支付集成和物流管理
  • 理解电子商务系统的安全和性能优化

核心概念

电子商务系统架构设计

电子商务系统通常包含以下核心模块:

  1. 用户模块:用户注册、登录、个人资料管理
  2. 产品模块:产品管理、分类、库存管理
  3. 购物车模块:添加商品、修改数量、删除商品
  4. 订单模块:创建订单、订单状态管理、订单历史
  5. 支付模块:支付集成、支付状态管理
  6. 物流模块:物流信息管理、配送状态更新
  7. 评价模块:产品评价、用户反馈
  8. 促销模块:优惠券、折扣活动

技术选型

  • 后端框架: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:dev

API文档

使用Postman或其他API测试工具测试以下API:

  1. 用户接口

    • POST /users/register - 注册
    • POST /users/login - 登录
    • GET /users/profile - 获取个人资料
    • PATCH /users/profile - 更新个人资料
  2. 产品接口

    • POST /products - 创建产品
    • GET /products - 获取产品列表
    • GET /products/:id - 获取单个产品
    • PUT /products/:id - 更新产品
    • DELETE /products/:id - 删除产品
  3. 购物车接口

    • POST /carts - 添加商品到购物车
    • GET /carts - 获取购物车
    • PUT /carts/:id - 更新购物车商品数量
    • DELETE /carts/:id - 删除购物车商品
    • DELETE /carts - 清空购物车
  4. 订单接口

    • POST /orders - 创建订单
    • GET /orders - 获取用户订单列表
    • GET /orders/:id - 获取订单详情
    • PUT /orders/:id/status - 更新订单状态
    • PUT /orders/:id/tracking - 更新物流信息
  5. 支付接口

    • 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

部署到服务器

  1. 使用PM2管理进程
# 安装PM2
npm i -g pm2

# 启动应用
npm run start:prod

# 或者使用PM2
npm run build
pm run start:prod
  1. 使用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

性能优化

数据库优化

  1. 添加索引
// 在实体中添加索引
@Entity('products')
export class Product {
  @Index()
  @Column()
  name: string;

  @Index()
  @Column()
  price: number;

  @Index()
  @Column()
  status: ProductStatus;
}
  1. 使用缓存
// 使用缓存
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;
  }
}

代码优化

  1. 使用DTO验证
# 安装验证模块
npm install class-validator class-transformer
  1. 使用事务
// 使用事务
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;
  });
}

安全措施

  1. 输入验证:使用class-validator验证输入数据
  2. SQL注入防护:使用TypeORM的参数化查询
  3. XSS防护:对用户输入进行转义
  4. CSRF防护:实现CSRF令牌验证
  5. 密码安全:使用bcrypt加密存储密码
  6. API限流:实现请求频率限制
  7. HTTPS:在生产环境使用HTTPS
  8. 支付安全:使用安全的支付集成方案
  9. 数据加密:对敏感数据进行加密存储
  10. 定期安全审计:定期检查系统安全漏洞

总结

本教程详细讲解了如何使用NestJS框架开发一个完整的电子商务API系统,包括用户认证、产品管理、购物车、订单处理、支付集成等核心功能。通过本教程的学习,你应该能够:

  1. 理解电子商务系统的整体架构设计
  2. 掌握NestJS框架的核心功能和使用方法
  3. 学会使用TypeORM操作数据库
  4. 实现基于JWT的认证系统
  5. 开发RESTful API接口
  6. 了解支付集成和物流管理
  7. 掌握项目部署和性能优化技巧

互动问答

问题1:如何实现产品分类和筛选功能?

答案:在产品实体中添加分类关联,通过查询参数实现分类筛选、价格范围筛选、关键词搜索等功能。

问题2:如何实现购物车功能?

答案:创建购物车实体,实现添加商品、更新数量、删除商品、清空购物车等功能,并在创建订单时更新产品库存。

问题3:如何实现支付集成?

答案:使用Stripe、PayPal等支付服务的SDK,实现支付意图创建、支付状态管理、退款等功能。

问题4:如何优化电子商务系统的性能?

答案:可以通过添加数据库索引、使用Redis缓存、优化查询语句、实现分页加载、使用CDN加速等方式提高系统性能。

问题5:如何确保电子商务系统的安全性?

答案:可以通过输入验证、SQL注入防护、XSS防护、CSRF防护、密码安全、API限流、HTTPS、支付安全、数据加密、定期安全审计等措施确保系统安全。

实践作业

  1. 实现评价模块

    • 创建产品评价功能
    • 实现评价的增删改查
    • 添加评价星级系统
  2. 实现促销模块

    • 创建优惠券功能
    • 实现折扣活动管理
    • 添加促销代码验证
  3. 实现物流模块

    • 集成物流API
    • 实现物流信息查询
    • 添加配送状态更新
  4. 实现搜索功能

    • 集成Elasticsearch
    • 实现产品全文搜索
    • 添加搜索建议功能
  5. 实现后台管理系统

    • 创建管理员控制台
    • 实现用户管理、产品管理、订单管理
    • 添加数据统计和分析功能

通过完成这些作业,你将能够进一步巩固所学知识,开发出功能更加完善的电子商务系统。

« 上一篇 NestJS博客系统开发实战教程 下一篇 » NestJS实时聊天应用开发实战教程