电商API实战 #
本节将使用Gin框架构建一个电商系统的后端API,包括商品管理、购物车、订单系统、支付集成等核心功能。
项目结构 #
text
ecommerce-api/
├── main.go
├── config/
│ └── config.go
├── models/
│ ├── user.go
│ ├── product.go
│ ├── category.go
│ ├── cart.go
│ ├── order.go
│ └── payment.go
├── handlers/
│ ├── product_handler.go
│ ├── cart_handler.go
│ ├── order_handler.go
│ └── payment_handler.go
├── services/
│ ├── product_service.go
│ ├── cart_service.go
│ ├── order_service.go
│ └── payment_service.go
├── middleware/
│ ├── auth.go
│ └── response.go
└── router/
└── router.go
数据模型 #
商品模型 #
go
// models/product.go
package models
import (
"time"
"gorm.io/gorm"
)
type Product struct {
ID uint `json:"id" gorm:"primaryKey"`
Name string `json:"name" gorm:"size:200;not null;index"`
Description string `json:"description" gorm:"type:text"`
Price float64 `json:"price" gorm:"type:decimal(10,2);not null"`
OriginalPrice float64 `json:"original_price" gorm:"type:decimal(10,2)"`
Stock int `json:"stock" gorm:"default:0"`
Sales int `json:"sales" gorm:"default:0"`
CategoryID uint `json:"category_id" gorm:"index"`
BrandID uint `json:"brand_id" gorm:"index"`
MainImage string `json:"main_image" gorm:"size:255"`
Images string `json:"images" gorm:"type:text"`
Status int `json:"status" gorm:"default:1"` // 0: off, 1: on
SortOrder int `json:"sort_order" gorm:"default:0"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `json:"-" gorm:"index"`
Category *Category `json:"category,omitempty" gorm:"foreignKey:CategoryID"`
Skus []ProductSku `json:"skus,omitempty" gorm:"foreignKey:ProductID"`
}
type ProductSku struct {
ID uint `json:"id" gorm:"primaryKey"`
ProductID uint `json:"product_id" gorm:"index;not null"`
SkuCode string `json:"sku_code" gorm:"uniqueIndex;size:50;not null"`
Name string `json:"name" gorm:"size:100;not null"`
Price float64 `json:"price" gorm:"type:decimal(10,2);not null"`
Stock int `json:"stock" gorm:"default:0"`
Image string `json:"image" gorm:"size:255"`
Specs string `json:"specs" gorm:"type:json"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `json:"-" gorm:"index"`
}
func (Product) TableName() string {
return "products"
}
func (ProductSku) TableName() string {
return "product_skus"
}
分类模型 #
go
// models/category.go
package models
import (
"time"
"gorm.io/gorm"
)
type Category struct {
ID uint `json:"id" gorm:"primaryKey"`
Name string `json:"name" gorm:"size:50;not null"`
ParentID uint `json:"parent_id" gorm:"default:0;index"`
Icon string `json:"icon" gorm:"size:255"`
SortOrder int `json:"sort_order" gorm:"default:0"`
Status int `json:"status" gorm:"default:1"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `json:"-" gorm:"index"`
Children []Category `json:"children,omitempty" gorm:"foreignKey:ParentID"`
Products []Product `json:"products,omitempty" gorm:"foreignKey:CategoryID"`
}
func (Category) TableName() string {
return "categories"
}
购物车模型 #
go
// models/cart.go
package models
import (
"time"
"gorm.io/gorm"
)
type CartItem struct {
ID uint `json:"id" gorm:"primaryKey"`
UserID uint `json:"user_id" gorm:"index;not null"`
ProductID uint `json:"product_id" gorm:"index;not null"`
SkuID uint `json:"sku_id" gorm:"index"`
Quantity int `json:"quantity" gorm:"not null"`
Selected bool `json:"selected" gorm:"default:true"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `json:"-" gorm:"index"`
Product *Product `json:"product,omitempty" gorm:"foreignKey:ProductID"`
Sku *ProductSku `json:"sku,omitempty" gorm:"foreignKey:SkuID"`
}
func (CartItem) TableName() string {
return "cart_items"
}
type CartResponse struct {
Items []CartItemResponse `json:"items"`
TotalCount int `json:"total_count"`
TotalPrice float64 `json:"total_price"`
SelectedCount int `json:"selected_count"`
SelectedPrice float64 `json:"selected_price"`
}
type CartItemResponse struct {
ID uint `json:"id"`
ProductID uint `json:"product_id"`
ProductName string `json:"product_name"`
ProductImage string `json:"product_image"`
SkuID uint `json:"sku_id"`
SkuName string `json:"sku_name"`
Price float64 `json:"price"`
Quantity int `json:"quantity"`
Selected bool `json:"selected"`
Subtotal float64 `json:"subtotal"`
}
func (c *CartItem) ToResponse() *CartItemResponse {
resp := &CartItemResponse{
ID: c.ID,
ProductID: c.ProductID,
SkuID: c.SkuID,
Quantity: c.Quantity,
Selected: c.Selected,
}
if c.Product != nil {
resp.ProductName = c.Product.Name
resp.ProductImage = c.Product.MainImage
}
if c.Sku != nil {
resp.SkuName = c.Sku.Name
resp.Price = c.Sku.Price
} else if c.Product != nil {
resp.Price = c.Product.Price
}
resp.Subtotal = resp.Price * float64(resp.Quantity)
return resp
}
订单模型 #
go
// models/order.go
package models
import (
"time"
"gorm.io/gorm"
)
type Order struct {
ID uint `json:"id" gorm:"primaryKey"`
OrderNo string `json:"order_no" gorm:"uniqueIndex;size:32;not null"`
UserID uint `json:"user_id" gorm:"index;not null"`
Status int `json:"status" gorm:"default:0"`
TotalAmount float64 `json:"total_amount" gorm:"type:decimal(10,2);not null"`
PayAmount float64 `json:"pay_amount" gorm:"type:decimal(10,2);not null"`
FreightAmount float64 `json:"freight_amount" gorm:"type:decimal(10,2);default:0"`
DiscountAmount float64 `json:"discount_amount" gorm:"type:decimal(10,2);default:0"`
ReceiverName string `json:"receiver_name" gorm:"size:50;not null"`
ReceiverPhone string `json:"receiver_phone" gorm:"size:20;not null"`
ReceiverProvince string `json:"receiver_province" gorm:"size:50"`
ReceiverCity string `json:"receiver_city" gorm:"size:50"`
ReceiverDistrict string `json:"receiver_district" gorm:"size:50"`
ReceiverAddress string `json:"receiver_address" gorm:"size:200;not null"`
PaymentMethod string `json:"payment_method" gorm:"size:20"`
PaymentTime *time.Time `json:"payment_time"`
DeliveryTime *time.Time `json:"delivery_time"`
ReceiveTime *time.Time `json:"receive_time"`
Remark string `json:"remark" gorm:"size:500"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `json:"-" gorm:"index"`
Items []OrderItem `json:"items,omitempty" gorm:"foreignKey:OrderID"`
User *User `json:"user,omitempty" gorm:"foreignKey:UserID"`
}
type OrderItem struct {
ID uint `json:"id" gorm:"primaryKey"`
OrderID uint `json:"order_id" gorm:"index;not null"`
ProductID uint `json:"product_id" gorm:"index;not null"`
SkuID uint `json:"sku_id" gorm:"index"`
ProductName string `json:"product_name" gorm:"size:200;not null"`
SkuName string `json:"sku_name" gorm:"size:100"`
Image string `json:"image" gorm:"size:255"`
Price float64 `json:"price" gorm:"type:decimal(10,2);not null"`
Quantity int `json:"quantity" gorm:"not null"`
TotalAmount float64 `json:"total_amount" gorm:"type:decimal(10,2);not null"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `json:"-" gorm:"index"`
}
func (Order) TableName() string {
return "orders"
}
func (OrderItem) TableName() string {
return "order_items"
}
const (
OrderStatusPending = 0
OrderStatusPaid = 1
OrderStatusShipped = 2
OrderStatusDelivered = 3
OrderStatusCancelled = 4
OrderStatusRefunded = 5
)
func (o *Order) CanCancel() bool {
return o.Status == OrderStatusPending
}
func (o *Order) CanPay() bool {
return o.Status == OrderStatusPending
}
func (o *Order) CanShip() bool {
return o.Status == OrderStatusPaid
}
func (o *Order) CanReceive() bool {
return o.Status == OrderStatusShipped
}
支付模型 #
go
// models/payment.go
package models
import (
"time"
"gorm.io/gorm"
)
type Payment struct {
ID uint `json:"id" gorm:"primaryKey"`
PaymentNo string `json:"payment_no" gorm:"uniqueIndex;size:32;not null"`
OrderID uint `json:"order_id" gorm:"index;not null"`
OrderNo string `json:"order_no" gorm:"size:32;not null"`
UserID uint `json:"user_id" gorm:"index;not null"`
Amount float64 `json:"amount" gorm:"type:decimal(10,2);not null"`
Method string `json:"method" gorm:"size:20;not null"`
Status int `json:"status" gorm:"default:0"`
TradeNo string `json:"trade_no" gorm:"size:64"`
PaidAt *time.Time `json:"paid_at"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `json:"-" gorm:"index"`
Order *Order `json:"order,omitempty" gorm:"foreignKey:OrderID"`
}
func (Payment) TableName() string {
return "payments"
}
const (
PaymentStatusPending = 0
PaymentStatusSuccess = 1
PaymentStatusFailed = 2
PaymentStatusRefunded = 3
)
商品服务 #
go
// services/product_service.go
package services
import (
"ecommerce-api/models"
"ecommerce-api/utils"
"errors"
"gorm.io/gorm"
)
type ProductService struct {
db *gorm.DB
}
func NewProductService(db *gorm.DB) *ProductService {
return &ProductService{db: db}
}
type CreateProductRequest struct {
Name string `json:"name" binding:"required,max=200"`
Description string `json:"description"`
Price float64 `json:"price" binding:"required,gt=0"`
OriginalPrice float64 `json:"original_price"`
Stock int `json:"stock" binding:"gte=0"`
CategoryID uint `json:"category_id" binding:"required"`
MainImage string `json:"main_image"`
Images string `json:"images"`
Status int `json:"status"`
Skus []CreateSkuRequest `json:"skus"`
}
type CreateSkuRequest struct {
SkuCode string `json:"sku_code" binding:"required"`
Name string `json:"name" binding:"required"`
Price float64 `json:"price" binding:"required,gt=0"`
Stock int `json:"stock" binding:"gte=0"`
Image string `json:"image"`
Specs string `json:"specs"`
}
type ProductListQuery struct {
utils.Pagination
Keyword string `form:"keyword"`
CategoryID uint `form:"category_id"`
Status *int `form:"status"`
MinPrice float64 `form:"min_price"`
MaxPrice float64 `form:"max_price"`
SortBy string `form:"sort_by"`
SortDesc bool `form:"sort_desc"`
}
func (s *ProductService) Create(req *CreateProductRequest) (*models.Product, error) {
product := &models.Product{
Name: req.Name,
Description: req.Description,
Price: req.Price,
OriginalPrice: req.OriginalPrice,
Stock: req.Stock,
CategoryID: req.CategoryID,
MainImage: req.MainImage,
Images: req.Images,
Status: req.Status,
}
err := s.db.Transaction(func(tx *gorm.DB) error {
if err := tx.Create(product).Error; err != nil {
return err
}
if len(req.Skus) > 0 {
for i := range req.Skus {
req.Skus[i].ProductID = product.ID
}
var skus []models.ProductSku
for _, skuReq := range req.Skus {
skus = append(skus, models.ProductSku{
ProductID: product.ID,
SkuCode: skuReq.SkuCode,
Name: skuReq.Name,
Price: skuReq.Price,
Stock: skuReq.Stock,
Image: skuReq.Image,
Specs: skuReq.Specs,
})
}
if err := tx.Create(&skus).Error; err != nil {
return err
}
}
return nil
})
if err != nil {
return nil, err
}
return s.GetByID(product.ID)
}
func (s *ProductService) GetByID(id uint) (*models.Product, error) {
var product models.Product
err := s.db.Preload("Category").Preload("Skus").First(&product, id).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errors.New("商品不存在")
}
return nil, err
}
return &product, nil
}
func (s *ProductService) List(query *ProductListQuery) ([]models.Product, int64, error) {
var products []models.Product
var total int64
q := s.db.Model(&models.Product{}).Preload("Category")
if query.Keyword != "" {
q = q.Where("name LIKE ?", "%"+query.Keyword+"%")
}
if query.CategoryID > 0 {
q = q.Where("category_id = ?", query.CategoryID)
}
if query.Status != nil {
q = q.Where("status = ?", *query.Status)
}
if query.MinPrice > 0 {
q = q.Where("price >= ?", query.MinPrice)
}
if query.MaxPrice > 0 {
q = q.Where("price <= ?", query.MaxPrice)
}
q.Count(&total)
orderBy := "created_at DESC"
if query.SortBy != "" {
order := "ASC"
if query.SortDesc {
order = "DESC"
}
orderBy = query.SortBy + " " + order
}
q = q.Order(orderBy)
q = q.Offset(query.GetOffset()).Limit(query.GetLimit())
if err := q.Find(&products).Error; err != nil {
return nil, 0, err
}
return products, total, nil
}
func (s *ProductService) UpdateStock(id uint, quantity int) error {
return s.db.Model(&models.Product{}).Where("id = ? AND stock >= ?", id, quantity).
Update("stock", gorm.Expr("stock - ?", quantity)).Error
}
购物车服务 #
go
// services/cart_service.go
package services
import (
"ecommerce-api/models"
"errors"
"gorm.io/gorm"
)
type CartService struct {
db *gorm.DB
}
func NewCartService(db *gorm.DB) *CartService {
return &CartService{db: db}
}
type AddToCartRequest struct {
ProductID uint `json:"product_id" binding:"required"`
SkuID uint `json:"sku_id"`
Quantity int `json:"quantity" binding:"required,gt=0"`
}
func (s *CartService) Add(userID uint, req *AddToCartRequest) (*models.CartItem, error) {
var product models.Product
if err := s.db.First(&product, req.ProductID).Error; err != nil {
return nil, errors.New("商品不存在")
}
if product.Status != 1 {
return nil, errors.New("商品已下架")
}
if req.SkuID > 0 {
var sku models.ProductSku
if err := s.db.First(&sku, req.SkuID).Error; err != nil {
return nil, errors.New("SKU不存在")
}
if sku.Stock < req.Quantity {
return nil, errors.New("库存不足")
}
} else {
if product.Stock < req.Quantity {
return nil, errors.New("库存不足")
}
}
var cartItem models.CartItem
err := s.db.Where("user_id = ? AND product_id = ? AND sku_id = ?",
userID, req.ProductID, req.SkuID).First(&cartItem).Error
if err == nil {
cartItem.Quantity += req.Quantity
if err := s.db.Save(&cartItem).Error; err != nil {
return nil, err
}
} else if errors.Is(err, gorm.ErrRecordNotFound) {
cartItem = models.CartItem{
UserID: userID,
ProductID: req.ProductID,
SkuID: req.SkuID,
Quantity: req.Quantity,
Selected: true,
}
if err := s.db.Create(&cartItem).Error; err != nil {
return nil, err
}
} else {
return nil, err
}
return s.GetByID(cartItem.ID)
}
func (s *CartService) GetByID(id uint) (*models.CartItem, error) {
var item models.CartItem
err := s.db.Preload("Product").Preload("Sku").First(&item, id).Error
if err != nil {
return nil, err
}
return &item, nil
}
func (s *CartService) GetByUserID(userID uint) (*models.CartResponse, error) {
var items []models.CartItem
err := s.db.Preload("Product").Preload("Sku").
Where("user_id = ?", userID).
Order("created_at DESC").
Find(&items).Error
if err != nil {
return nil, err
}
resp := &models.CartResponse{
Items: make([]models.CartItemResponse, len(items)),
}
for i, item := range items {
resp.Items[i] = *item.ToResponse()
resp.TotalCount += item.Quantity
resp.TotalPrice += resp.Items[i].Subtotal
if item.Selected {
resp.SelectedCount += item.Quantity
resp.SelectedPrice += resp.Items[i].Subtotal
}
}
return resp, nil
}
func (s *CartService) UpdateQuantity(userID, itemID uint, quantity int) error {
var item models.CartItem
if err := s.db.Where("id = ? AND user_id = ?", itemID, userID).First(&item).Error; err != nil {
return errors.New("购物车项不存在")
}
if quantity <= 0 {
return s.db.Delete(&item).Error
}
return s.db.Model(&item).Update("quantity", quantity).Error
}
func (s *CartService) UpdateSelected(userID, itemID uint, selected bool) error {
return s.db.Model(&models.CartItem{}).
Where("id = ? AND user_id = ?", itemID, userID).
Update("selected", selected).Error
}
func (s *CartService) SelectAll(userID uint, selected bool) error {
return s.db.Model(&models.CartItem{}).
Where("user_id = ?", userID).
Update("selected", selected).Error
}
func (s *CartService) Delete(userID, itemID uint) error {
return s.db.Where("id = ? AND user_id = ?", itemID, userID).Delete(&models.CartItem{}).Error
}
func (s *CartService) Clear(userID uint) error {
return s.db.Where("user_id = ?", userID).Delete(&models.CartItem{}).Error
}
func (s *CartService) GetSelectedItems(userID uint) ([]models.CartItem, error) {
var items []models.CartItem
err := s.db.Preload("Product").Preload("Sku").
Where("user_id = ? AND selected = ?", userID, true).
Find(&items).Error
return items, err
}
订单服务 #
go
// services/order_service.go
package services
import (
"ecommerce-api/models"
"errors"
"fmt"
"time"
"gorm.io/gorm"
)
type OrderService struct {
db *gorm.DB
cartService *CartService
}
func NewOrderService(db *gorm.DB, cartService *CartService) *OrderService {
return &OrderService{db: db, cartService: cartService}
}
type CreateOrderRequest struct {
AddressID uint `json:"address_id" binding:"required"`
Remark string `json:"remark"`
CouponID uint `json:"coupon_id"`
}
type Address struct {
Name string `json:"name" binding:"required"`
Phone string `json:"phone" binding:"required"`
Province string `json:"province" binding:"required"`
City string `json:"city" binding:"required"`
District string `json:"district" binding:"required"`
Address string `json:"address" binding:"required"`
}
func (s *OrderService) Create(userID uint, req *CreateOrderRequest, addr *Address) (*models.Order, error) {
cartItems, err := s.cartService.GetSelectedItems(userID)
if err != nil {
return nil, err
}
if len(cartItems) == 0 {
return nil, errors.New("请选择要购买的商品")
}
var totalAmount float64
var orderItems []models.OrderItem
for _, item := range cartItems {
var price float64
var skuName string
var image string
if item.Sku != nil {
price = item.Sku.Price
skuName = item.Sku.Name
image = item.Sku.Image
if item.Sku.Stock < item.Quantity {
return nil, fmt.Errorf("商品 %s 库存不足", item.Sku.Name)
}
} else if item.Product != nil {
price = item.Product.Price
image = item.Product.MainImage
if item.Product.Stock < item.Quantity {
return nil, fmt.Errorf("商品 %s 库存不足", item.Product.Name)
}
}
subtotal := price * float64(item.Quantity)
totalAmount += subtotal
orderItems = append(orderItems, models.OrderItem{
ProductID: item.ProductID,
SkuID: item.SkuID,
ProductName: item.Product.Name,
SkuName: skuName,
Image: image,
Price: price,
Quantity: item.Quantity,
TotalAmount: subtotal,
})
}
orderNo := generateOrderNo()
order := &models.Order{
OrderNo: orderNo,
UserID: userID,
Status: models.OrderStatusPending,
TotalAmount: totalAmount,
PayAmount: totalAmount,
FreightAmount: 0,
DiscountAmount: 0,
ReceiverName: addr.Name,
ReceiverPhone: addr.Phone,
ReceiverProvince: addr.Province,
ReceiverCity: addr.City,
ReceiverDistrict: addr.District,
ReceiverAddress: addr.Address,
Remark: req.Remark,
}
err = s.db.Transaction(func(tx *gorm.DB) error {
if err := tx.Create(order).Error; err != nil {
return err
}
for i := range orderItems {
orderItems[i].OrderID = order.ID
}
if err := tx.Create(&orderItems).Error; err != nil {
return err
}
for _, item := range cartItems {
if item.SkuID > 0 {
if err := tx.Model(&models.ProductSku{}).
Where("id = ?", item.SkuID).
Update("stock", gorm.Expr("stock - ?", item.Quantity)).Error; err != nil {
return err
}
} else {
if err := tx.Model(&models.Product{}).
Where("id = ?", item.ProductID).
Update("stock", gorm.Expr("stock - ?", item.Quantity)).Error; err != nil {
return err
}
}
}
if err := tx.Where("user_id = ? AND selected = ?", userID, true).
Delete(&models.CartItem{}).Error; err != nil {
return err
}
return nil
})
if err != nil {
return nil, err
}
return s.GetByID(order.ID)
}
func (s *OrderService) GetByID(id uint) (*models.Order, error) {
var order models.Order
err := s.db.Preload("Items").First(&order, id).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errors.New("订单不存在")
}
return nil, err
}
return &order, nil
}
func (s *OrderService) GetByOrderNo(orderNo string) (*models.Order, error) {
var order models.Order
err := s.db.Preload("Items").Where("order_no = ?", orderNo).First(&order).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errors.New("订单不存在")
}
return nil, err
}
return &order, nil
}
func (s *OrderService) List(userID uint, status *int, page, pageSize int) ([]models.Order, int64, error) {
var orders []models.Order
var total int64
q := s.db.Model(&models.Order{}).Where("user_id = ?", userID)
if status != nil {
q = q.Where("status = ?", *status)
}
q.Count(&total)
offset := (page - 1) * pageSize
if err := q.Preload("Items").Order("created_at DESC").
Offset(offset).Limit(pageSize).Find(&orders).Error; err != nil {
return nil, 0, err
}
return orders, total, nil
}
func (s *OrderService) Cancel(userID, orderID uint) error {
order, err := s.GetByID(orderID)
if err != nil {
return err
}
if order.UserID != userID {
return errors.New("无权操作此订单")
}
if !order.CanCancel() {
return errors.New("订单状态不允许取消")
}
return s.db.Transaction(func(tx *gorm.DB) error {
if err := tx.Model(order).Update("status", models.OrderStatusCancelled).Error; err != nil {
return err
}
for _, item := range order.Items {
if item.SkuID > 0 {
if err := tx.Model(&models.ProductSku{}).
Where("id = ?", item.SkuID).
Update("stock", gorm.Expr("stock + ?", item.Quantity)).Error; err != nil {
return err
}
} else {
if err := tx.Model(&models.Product{}).
Where("id = ?", item.ProductID).
Update("stock", gorm.Expr("stock + ?", item.Quantity)).Error; err != nil {
return err
}
}
}
return nil
})
}
func (s *OrderService) PaySuccess(orderNo string, tradeNo string) error {
order, err := s.GetByOrderNo(orderNo)
if err != nil {
return err
}
now := time.Now()
return s.db.Model(order).Updates(map[string]interface{}{
"status": models.OrderStatusPaid,
"payment_time": &now,
"payment_method": "alipay",
}).Error
}
func generateOrderNo() string {
return fmt.Sprintf("%d%06d", time.Now().UnixNano()/1e6, time.Now().Nanosecond()/1000)
}
支付服务 #
go
// services/payment_service.go
package services
import (
"ecommerce-api/models"
"errors"
"fmt"
"time"
"gorm.io/gorm"
)
type PaymentService struct {
db *gorm.DB
orderService *OrderService
}
func NewPaymentService(db *gorm.DB, orderService *OrderService) *PaymentService {
return &PaymentService{db: db, orderService: orderService}
}
type PaymentRequest struct {
OrderID uint `json:"order_id" binding:"required"`
Method string `json:"method" binding:"required,oneof=alipay wechat"`
}
type PaymentResponse struct {
PaymentNo string `json:"payment_no"`
PayURL string `json:"pay_url"`
}
func (s *PaymentService) Create(userID uint, req *PaymentRequest) (*PaymentResponse, error) {
order, err := s.orderService.GetByID(req.OrderID)
if err != nil {
return nil, err
}
if order.UserID != userID {
return nil, errors.New("无权操作此订单")
}
if !order.CanPay() {
return nil, errors.New("订单状态不允许支付")
}
paymentNo := generatePaymentNo()
payment := &models.Payment{
PaymentNo: paymentNo,
OrderID: order.ID,
OrderNo: order.OrderNo,
UserID: userID,
Amount: order.PayAmount,
Method: req.Method,
Status: models.PaymentStatusPending,
}
if err := s.db.Create(payment).Error; err != nil {
return nil, err
}
payURL := s.generatePayURL(payment, req.Method)
return &PaymentResponse{
PaymentNo: paymentNo,
PayURL: payURL,
}, nil
}
func (s *PaymentService) generatePayURL(payment *models.Payment, method string) string {
switch method {
case "alipay":
return fmt.Sprintf("https://openapi.alipay.com/gateway.do?payment_no=%s&amount=%.2f",
payment.PaymentNo, payment.Amount)
case "wechat":
return fmt.Sprintf("weixin://wxpay/bizpayurl?pr=%s", payment.PaymentNo)
default:
return ""
}
}
func (s *PaymentService) Callback(paymentNo string, tradeNo string) error {
var payment models.Payment
if err := s.db.Where("payment_no = ?", paymentNo).First(&payment).Error; err != nil {
return errors.New("支付记录不存在")
}
if payment.Status != models.PaymentStatusPending {
return errors.New("支付状态异常")
}
now := time.Now()
return s.db.Transaction(func(tx *gorm.DB) error {
if err := tx.Model(&payment).Updates(map[string]interface{}{
"status": models.PaymentStatusSuccess,
"trade_no": tradeNo,
"paid_at": &now,
}).Error; err != nil {
return err
}
if err := s.orderService.PaySuccess(payment.OrderNo, tradeNo); err != nil {
return err
}
return nil
})
}
func generatePaymentNo() string {
return fmt.Sprintf("P%d%06d", time.Now().UnixNano()/1e6, time.Now().Nanosecond()/1000)
}
订单处理器 #
go
// handlers/order_handler.go
package handlers
import (
"ecommerce-api/middleware"
"ecommerce-api/services"
"ecommerce-api/utils"
"strconv"
"github.com/gin-gonic/gin"
)
type OrderHandler struct {
service *services.OrderService
}
func NewOrderHandler(service *services.OrderService) *OrderHandler {
return &OrderHandler{service: service}
}
func (h *OrderHandler) Create(c *gin.Context) {
var req services.CreateOrderRequest
if err := c.ShouldBindJSON(&req); err != nil {
utils.BadRequest(c, "请求参数错误")
return
}
var addr services.Address
if err := c.ShouldBindJSON(&struct {
services.CreateOrderRequest
services.Address `json:"address"`
}{}); err != nil {
utils.BadRequest(c, "地址信息错误")
return
}
userID := middleware.GetUserID(c)
order, err := h.service.Create(userID, &req, &addr)
if err != nil {
utils.BadRequest(c, err.Error())
return
}
utils.Created(c, order)
}
func (h *OrderHandler) GetByID(c *gin.Context) {
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
utils.BadRequest(c, "无效的订单ID")
return
}
userID := middleware.GetUserID(c)
order, err := h.service.GetByID(uint(id))
if err != nil {
utils.NotFound(c, err.Error())
return
}
if order.UserID != userID {
utils.Forbidden(c, "无权查看此订单")
return
}
utils.Success(c, order)
}
func (h *OrderHandler) List(c *gin.Context) {
userID := middleware.GetUserID(c)
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "10"))
var status *int
if s := c.Query("status"); s != "" {
st, _ := strconv.Atoi(s)
status = &st
}
orders, total, err := h.service.List(userID, status, page, pageSize)
if err != nil {
utils.InternalError(c, "获取订单列表失败")
return
}
utils.SuccessWithPage(c, orders, total, page, pageSize)
}
func (h *OrderHandler) Cancel(c *gin.Context) {
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
utils.BadRequest(c, "无效的订单ID")
return
}
userID := middleware.GetUserID(c)
if err := h.service.Cancel(userID, uint(id)); err != nil {
utils.BadRequest(c, err.Error())
return
}
utils.Success(c, gin.H{"message": "订单已取消"})
}
路由配置 #
go
// router/router.go
package router
import (
"ecommerce-api/handlers"
"ecommerce-api/middleware"
"ecommerce-api/models"
"ecommerce-api/services"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
func Setup(db *gorm.DB) *gin.Engine {
r := gin.Default()
db.AutoMigrate(
&models.User{},
&models.Product{},
&models.ProductSku{},
&models.Category{},
&models.CartItem{},
&models.Order{},
&models.OrderItem{},
&models.Payment{},
)
productService := services.NewProductService(db)
cartService := services.NewCartService(db)
orderService := services.NewOrderService(db, cartService)
paymentService := services.NewPaymentService(db, orderService)
productHandler := handlers.NewProductHandler(productService)
cartHandler := handlers.NewCartHandler(cartService)
orderHandler := handlers.NewOrderHandler(orderService)
paymentHandler := handlers.NewPaymentHandler(paymentService)
api := r.Group("/api/v1")
{
products := api.Group("/products")
{
products.GET("", productHandler.List)
products.GET("/:id", productHandler.GetByID)
admin := products.Use(middleware.JWTAuth(), middleware.RequireAdmin())
{
admin.POST("", productHandler.Create)
admin.PUT("/:id", productHandler.Update)
admin.DELETE("/:id", productHandler.Delete)
}
}
cart := api.Group("/cart")
cart.Use(middleware.JWTAuth())
{
cart.GET("", cartHandler.List)
cart.POST("", cartHandler.Add)
cart.PUT("/:id", cartHandler.UpdateQuantity)
cart.PUT("/:id/selected", cartHandler.UpdateSelected)
cart.DELETE("/:id", cartHandler.Delete)
cart.DELETE("", cartHandler.Clear)
}
orders := api.Group("/orders")
orders.Use(middleware.JWTAuth())
{
orders.GET("", orderHandler.List)
orders.POST("", orderHandler.Create)
orders.GET("/:id", orderHandler.GetByID)
orders.PUT("/:id/cancel", orderHandler.Cancel)
}
payment := api.Group("/payment")
payment.Use(middleware.JWTAuth())
{
payment.POST("/create", paymentHandler.Create)
payment.POST("/callback", paymentHandler.Callback)
}
}
return r
}
小结 #
本节构建了一个电商系统的后端API:
- 商品管理:商品、SKU、分类管理
- 购物车:添加、修改、删除、选择
- 订单系统:创建、取消、状态流转
- 支付集成:支付创建、回调处理
- 库存管理:下单扣减、取消恢复
这个电商API具备完整的电商核心功能,可以根据需求继续扩展优惠券、物流、评价等功能。
最后更新:2026-03-28