24、订单和购物车-srv服务

一、订单表结构与接口

1 - 订单表结构与创建

  • order_srv/model/order.go
package model

import "time"

type ShoppingCart struct { // 购物车
	BaseModel
	User    int32 `gorm:"type:int;index"` // 在购物车列表中我们需要查询当前用户的购物车记录
	Goods   int32 `gorm:"type:int;index"` // 加索引:我们需要查询时候, 1. 会影响插入性能 2. 会占用磁盘
	Nums    int32 `gorm:"type:int"`       // 商品的数量
	Checked bool  // 是否选中
}

func (ShoppingCart) TableName() string {
	return "shoppingcart"
}

type OrderInfo struct {
	BaseModel

	User    int32  `gorm:"type:int;index"`
	OrderSn string `gorm:"type:varchar(30);index"` // 订单号,我们平台自己生成的订单号
	PayType string `gorm:"type:varchar(20) comment 'alipay(支付宝), wechat(微信)'"`

	// status大家可以考虑使用iota来做
	Status     string `gorm:"type:varchar(20)  comment 'PAYING(待支付), TRADE_SUCCESS(成功), TRADE_CLOSED(超时关闭), WAIT_BUYER_PAY(交易创建), TRADE_FINISHED(交易结束)'"`
	TradeNo    string `gorm:"type:varchar(100) comment '交易号'"` // 交易号就是支付宝的订单号,查账
	OrderMount float32
	PayTime    *time.Time `gorm:"type:datetime"`

	Address      string `gorm:"type:varchar(100)"`
	SignerName   string `gorm:"type:varchar(20)"`
	SingerMobile string `gorm:"type:varchar(11)"`
	Post         string `gorm:"type:varchar(20)"`
}

func (OrderInfo) TableName() string {
	return "orderinfo"
}

type OrderGoods struct {
	BaseModel

	Order int32 `gorm:"type:int;index"`
	Goods int32 `gorm:"type:int;index"`

	//把商品的信息保存下来了 , 字段冗余, 高并发系统中我们一般都不会遵循三范式, 做镜像记录
	GoodsName  string `gorm:"type:varchar(100);index"`
	GoodsImage string `gorm:"type:varchar(200)"`
	GoodsPrice float32
	Nums       int32 `gorm:"type:int"`
}

func (OrderGoods) TableName() string {
	return "ordergoods"
}

  • 数据库创建:mxshop_order_srv
    在这里插入图片描述
  • order_srv/model/main/main.go:自动创建表
package main

import (
	"fmt"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
	"gorm.io/gorm/schema"
	"log"
	"nd/order_srv/global"
	"nd/order_srv/initialize"
	"nd/order_srv/model"
	"os"
	"time"
)

func main() {
	initialize.InitConfig()
	dsn := fmt.Sprintf("root:jiushi@tcp(%s:3306)/mxshop_order_srv?charset=utf8mb4&parseTime=True&loc=Local", global.ServerConfig.MysqlInfo.Host)

	newLogger := logger.New(
		log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer
		logger.Config{
			SlowThreshold: time.Second, // 慢 SQL 阈值
			LogLevel:      logger.Info, // Log level
			Colorful:      true,        // 禁用彩色打印
		},
	)

	// 全局模式
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
		NamingStrategy: schema.NamingStrategy{
			SingularTable: true,
		},
		Logger: newLogger,
	})
	if err != nil {
		panic(err)
	}

	_ = db.AutoMigrate(&model.ShoppingCart{}, &model.OrderInfo{}, &model.OrderGoods{})
}

2 - 订单接口定义

  • order_srv/proto/order.protoprotoc --go_out=. --go_opt=paths=import --go-grpc_out=. --go-grpc_opt=paths=import *.proto
syntax = "proto3";
import "google/protobuf/empty.proto";
option go_package = ".;proto";


service Order {
  //购物车
  rpc CartItemList(UserInfo) returns(CartItemListResponse); //获取用户的购物车信息
  rpc CreateCartItem(CartItemRequest) returns(ShopCartInfoResponse); //添加商品到购物车
  rpc UpdateCartItem(CartItemRequest) returns(google.protobuf.Empty); //修改购物车信息
  rpc DeleteCartItem(CartItemRequest) returns(google.protobuf.Empty); //删除购物车条目

  //订单
  rpc CreateOrder(OrderRequest) returns (OrderInfoResponse); //创建订单
  rpc OrderList(OrderFilterRequest) returns (OrderListResponse); // 订单列表
  rpc OrderDetail(OrderRequest) returns (OrderInfoDetailResponse); // 订单详情
  rpc UpdateOrderStatus(OrderStatus) returns (google.protobuf.Empty); // 修改订单状态
}

message UserInfo {
  int32 id = 1;
}

message OrderStatus {
  int32 id = 1;
  string orderSn = 2;
  string status = 3;
}

message CartItemRequest {
  int32 id = 1;
  int32 userId = 2;
  int32 goodsId = 3;
  string goodsName = 4;
  string goodsImage = 5;
  float goodsPrice = 6;
  int32 nums = 7;
  bool checked = 8;
}

message OrderRequest {
  int32 id = 1;
  int32 userId = 2;
  string address = 3;
  string name = 4;
  string mobile = 5;
  string post = 6;
}

message OrderInfoResponse {
  int32 id = 1;
  int32 userId = 2;
  string orderSn = 3;
  string payType = 4;
  string status = 5;
  string post = 6;
  float total = 7;
  string address = 8;
  string name = 9;
  string mobile = 10;
  string addTime = 11;
}

message ShopCartInfoResponse {
  int32 id = 1;
  int32 userId = 2;
  int32 goodsId = 3;
  int32 nums = 4;
  bool checked = 5;
}

message OrderItemResponse {
  int32 id = 1;
  int32 orderId = 2;
  int32 goodsId = 3;
  string goodsName = 4;
  string goodsImage = 5;
  float goodsPrice = 6;
  int32 nums = 7;
}

message OrderInfoDetailResponse {
  OrderInfoResponse orderInfo = 1;
  repeated OrderItemResponse goods = 2;
}

message OrderFilterRequest {
  int32 userId = 1;
  int32 pages = 2;
  int32 pagePerNums = 3;
}

message OrderListResponse {
  int32 total = 1;
  repeated OrderInfoResponse data = 2;
}

message CartItemListResponse {
  int32 total = 1;
  repeated ShopCartInfoResponse data = 2;
}


3 - 启动订单服务

  • nacos新建命名空间orders
    在这里插入图片描述
{
  "name": "order_srv",
  "host": "192.168.124.9",
  "tags": ["imooc", "bobby", "order", "srv"],
  "mysql": {
    "host": "192.168.124.51",
    "port": 3306,
    "user": "root",
    "password": "jiushi",
    "db": "mxshop_order_srv"
  },
  "consul": {
    "host": "192.168.124.51",
    "port": 8500
  }
}
  • yaml修改:order_srv/config_zsz.yaml
host: '192.168.78.131'
port: 8848
namespace: '4fd0ed12-957c-490e-a116-99672c761433'
user: 'nacos'
password: 'nacos'
dataid: 'order_srv.json'
group: 'comp'
  • order_srv/main.goproto.RegisterOrderServer(server, &proto.UnimplementedOrderServer{})
package main

import (
	"flag"
	"fmt"
	"net"
	"os"
	"os/signal"
	"syscall"

	"github.com/satori/go.uuid"
	"go.uber.org/zap"
	"google.golang.org/grpc"
	"google.golang.org/grpc/health"
	"google.golang.org/grpc/health/grpc_health_v1"

	"nd/order_srv/global"
	"nd/order_srv/initialize"
	"nd/order_srv/proto"
	"nd/order_srv/utils"
	"nd/order_srv/utils/register/consul"
)

func main() {
	IP := flag.String("ip", "0.0.0.0", "ip地址")
	Port := flag.Int("port", 50060, "端口号") // 这个修改为0,如果我们从命令行带参数启动的话就不会为0

	//初始化
	initialize.InitLogger()
	initialize.InitConfig()
	initialize.InitDB()
	zap.S().Info(global.ServerConfig)

	flag.Parse()
	zap.S().Info("ip: ", *IP)
	if *Port == 0 {
		*Port, _ = utils.GetFreePort()
	}
	zap.S().Info("port: ", *Port)

	server := grpc.NewServer()
	proto.RegisterOrderServer(server, &proto.UnimplementedOrderServer{})
	lis, err := net.Listen("tcp", fmt.Sprintf("%s:%d", *IP, *Port))
	if err != nil {
		panic("failed to listen:" + err.Error())
	}

	//注册服务健康检查
	grpc_health_v1.RegisterHealthServer(server, health.NewServer())

	//服务注册
	register_client := consul.NewRegistryClient(global.ServerConfig.ConsulInfo.Host, global.ServerConfig.ConsulInfo.Port)
	serviceId := fmt.Sprintf("%s", uuid.NewV4())
	err = register_client.Register(global.ServerConfig.Host, *Port, global.ServerConfig.Name, global.ServerConfig.Tags, serviceId)
	if err != nil {
		zap.S().Panic("服务注册失败:", err.Error())
	}
	zap.S().Debugf("启动服务器, 端口: %d", *Port)

	go func() {
		err = server.Serve(lis)
		if err != nil {
			panic("failed to start grpc:" + err.Error())
		}
	}()

	//接收终止信号
	quit := make(chan os.Signal)
	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
	<-quit
	if err = register_client.DeRegister(serviceId); err != nil {
		zap.S().Info("注销失败:", err.Error())
	} else {
		zap.S().Info("注销成功:")
	}
}

在这里插入图片描述


二、购物车接口

1 - 获取用户购物车

func (*OrderServer) CartItemList(ctx context.Context, req *proto.UserInfo) (*proto.CartItemListResponse, error) {
	//获取用户的购物车列表
	var shopCarts []model.ShoppingCart
	var rsp proto.CartItemListResponse

	if result := global.DB.Where(&model.ShoppingCart{User: req.Id}).Find(&shopCarts); result.Error != nil {
		return nil, result.Error
	} else {
		rsp.Total = int32(result.RowsAffected)
	}

	for _, shopCart := range shopCarts {
		rsp.Data = append(rsp.Data, &proto.ShopCartInfoResponse{
			Id:      shopCart.ID,
			UserId:  shopCart.User,
			GoodsId: shopCart.Goods,
			Nums:    shopCart.Nums,
			Checked: shopCart.Checked,
		})
	}
	return &rsp, nil
}

2 - 添加商品到购物车

func (*OrderServer) CreateCartItem(ctx context.Context, req *proto.CartItemRequest) (*proto.ShopCartInfoResponse, error) {
	//将商品添加到购物车 1. 购物车中原本没有这件商品 - 新建一个记录 2. 这个商品之前添加到了购物车- 合并
	var shopCart model.ShoppingCart

	if result := global.DB.Where(&model.ShoppingCart{Goods: req.GoodsId, User: req.UserId}).First(&shopCart); result.RowsAffected == 1 {
		//如果记录已经存在,则合并购物车记录, 更新操作
		shopCart.Nums += req.Nums
	} else {
		//插入操作
		shopCart.User = req.UserId
		shopCart.Goods = req.GoodsId
		shopCart.Nums = req.Nums
		shopCart.Checked = false
	}

	global.DB.Save(&shopCart)
	return &proto.ShopCartInfoResponse{Id: shopCart.ID}, nil
}

3 - 更新购物车

func (*OrderServer) UpdateCartItem(ctx context.Context, req *proto.CartItemRequest) (*emptypb.Empty, error) {
	//更新购物车记录,更新数量和选中状态
	var shopCart model.ShoppingCart

	if result := global.DB.First(&shopCart, req.Id); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.NotFound, "购物车记录不存在")
	}

	shopCart.Checked = req.Checked
	if req.Nums > 0 {
		shopCart.Nums = req.Nums
	}
	global.DB.Save(&shopCart)

	return &emptypb.Empty{}, nil
}

4 - 删除购物车

func (*OrderServer) DeleteCartItem(ctx context.Context, req *proto.CartItemRequest) (*emptypb.Empty, error) {
	if result := global.DB.Where("goods=? and user=?", req.GoodsId, req.UserId).Delete(&model.ShoppingCart{}); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.NotFound, "购物车记录不存在")
	}
	return &emptypb.Empty{}, nil
}

三、查询订单与订单详情

1 - 查询订单列表

func (*OrderServer) OrderList(ctx context.Context, req *proto.OrderFilterRequest) (*proto.OrderListResponse, error) {
	var orders []model.OrderInfo
	var rsp proto.OrderListResponse

	var total int64
	global.DB.Where(&model.OrderInfo{User: req.UserId}).Count(&total)
	rsp.Total = int32(total)

	//分页
	global.DB.Scopes(Paginate(int(req.Pages), int(req.PagePerNums))).Where(&model.OrderInfo{User: req.UserId}).Find(&orders)
	for _, order := range orders {
		rsp.Data = append(rsp.Data, &proto.OrderInfoResponse{
			Id:      order.ID,
			UserId:  order.User,
			OrderSn: order.OrderSn,
			PayType: order.PayType,
			Status:  order.Status,
			Post:    order.Post,
			Total:   order.OrderMount,
			Address: order.Address,
			Name:    order.SignerName,
			Mobile:  order.SingerMobile,
			AddTime: order.CreatedAt.Format("2006-01-02 15:04:05"),
		})
	}
	return &rsp, nil
}

2 - 查询订单详情

func (*OrderServer) OrderDetail(ctx context.Context, req *proto.OrderRequest) (*proto.OrderInfoDetailResponse, error) {
	var order model.OrderInfo
	var rsp proto.OrderInfoDetailResponse
	//这个订单的id是否是当前用户的订单, 如果在web层用户传递过来一个id的订单, web层应该先查询一下订单id是否是当前用户的
	//在个人中心可以这样做,但是如果是后台管理系统,web层如果是后台管理系统 那么只传递order的id,如果是电商系统还需要一个用户的id
	if result := global.DB.Where(&model.OrderInfo{BaseModel: model.BaseModel{ID: req.Id}, User: req.UserId}).First(&order); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.NotFound, "订单不存在")
	}
	orderInfo := proto.OrderInfoResponse{}
	orderInfo.Id = order.ID
	orderInfo.UserId = order.User
	orderInfo.OrderSn = order.OrderSn
	orderInfo.PayType = order.PayType
	orderInfo.Status = order.Status
	orderInfo.Post = order.Post
	orderInfo.Total = order.OrderMount
	orderInfo.Address = order.Address
	orderInfo.Name = order.SignerName
	orderInfo.Mobile = order.SingerMobile
	rsp.OrderInfo = &orderInfo
	var orderGoods []model.OrderGoods
	if result := global.DB.Where(&model.OrderGoods{Order: order.ID}).Find(&orderGoods); result.Error != nil {
		return nil, result.Error
	}
	for _, orderGood := range orderGoods {
		rsp.Goods = append(rsp.Goods, &proto.OrderItemResponse{
			GoodsId:    orderGood.Goods,
			GoodsName:  orderGood.GoodsName,
			GoodsPrice: orderGood.GoodsPrice,
			GoodsImage: orderGood.GoodsImage,
			Nums:       orderGood.Nums,
		})
	}
	return &rsp, nil
}

四、创建订单

1 - 创建订单流程分析

  • 创建订单流程
    • ①.从购物车中获取到选中的商品
    • ②.商品的价格自己查询 - 访问商品服务 (跨微服务)
    • ③.库存的扣减 - 访问库存服务 (跨微服务)
    • ④.订单的基本信息表 - 订单的商品信息表
    • ⑤.从购物车中删除已购买的记录

2 - 订单的跨服务实现

  • 跨微服务的proto:因为这里需要用到商品服务和库存服务,所以需要把商品的proto和库存的proto文件拷贝到订单服务的proto中;路径 —— order_srv/proto
    在这里插入图片描述
  • order_srv/global/global.go
    • 添加两个rpc的client:GoodsSrvClient,InventorySrvClient
package global

import (
	"gorm.io/gorm"
	"nd/order_srv/config"
	"nd/order_srv/proto"
)

var (
	DB           *gorm.DB
	ServerConfig config.ServerConfig
	NacosConfig  config.NacosConfig

	GoodsSrvClient     proto.GoodsClient
	InventorySrvClient proto.InventoryClient
)
  • order_srv/config/config.go:添加商品和库存的微服务配置
package config

type MysqlConfig struct {
	Host     string `mapstructure:"host" json:"host"`
	Port     int    `mapstructure:"port" json:"port"`
	Name     string `mapstructure:"db" json:"db"`
	User     string `mapstructure:"user" json:"user"`
	Password string `mapstructure:"password" json:"password"`
}

type ConsulConfig struct {
	Host string `mapstructure:"host" json:"host"`
	Port int    `mapstructure:"port" json:"port"`
}

type SrvConfig struct {
	Name string `mapstructure:"name" json:"name"`
}

type ServerConfig struct {
	Name       string       `mapstructure:"name" json:"name"`
	Host       string       `mapstructure:"host" json:"host"`
	Tags       []string     `mapstructure:"tags" json:"tags"`
	MysqlInfo  MysqlConfig  `mapstructure:"mysql" json:"mysql"`
	ConsulInfo ConsulConfig `mapstructure:"consul" json:"consul"`

	//商品微服务的配置
	GoodsSrvInfo SrvConfig `mapstructure:"goods_srv" json:"goods_srv"`
	//库存微服务的配置
	InventorySrvInfo SrvConfig `mapstructure:"inventory_srv" json:"inventory_srv"`
}

type NacosConfig struct {
	Host      string `mapstructure:"host"`
	Port      uint64 `mapstructure:"port"`
	Namespace string `mapstructure:"namespace"`
	User      string `mapstructure:"user"`
	Password  string `mapstructure:"password"`
	DataId    string `mapstructure:"dataid"`
	Group     string `mapstructure:"group"`
}

  • nacos中添加商品和库存的配置
{
  "name": "order_srv",
  "host": "192.168.124.9",
  "tags": ["imooc", "bobby", "order", "srv"],
  "mysql": {
    "host": "192.168.124.51",
    "port": 3306,
    "user": "root",
    "password": "jiushi",
    "db": "mxshop_order_srv"
  },
  "goods_srv":{
    "name":"goods_srv"
  },
  "inventory_srv":{
    "name":"inventory_srv"
  },
  "consul": {
    "host": "192.168.124.51",
    "port": 8500
  }
}
  • order_srv/initialize/init_srv_conn.go:初始化连接商品和库存的微服务
package initialize

import (
	"fmt"
	"google.golang.org/grpc/credentials/insecure"

	_ "github.com/mbobakov/grpc-consul-resolver" // It's important
	"go.uber.org/zap"
	"google.golang.org/grpc"

	"nd/order_srv/global"
	"nd/order_srv/proto"
)

func InitSrvConn() {
	consulInfo := global.ServerConfig.ConsulInfo
	goodsConn, err := grpc.Dial(
		fmt.Sprintf("consul://%s:%d/%s?wait=14s", consulInfo.Host, consulInfo.Port, global.ServerConfig.GoodsSrvInfo.Name),
		grpc.WithTransportCredentials(insecure.NewCredentials()),
		grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy": "round_robin"}`),
		//grpc.WithUnaryInterceptor(otgrpc.OpenTracingClientInterceptor(opentracing.GlobalTracer())),
	)
	if err != nil {
		zap.S().Fatal("[InitSrvConn] 连接 【商品服务失败】")
	}

	global.GoodsSrvClient = proto.NewGoodsClient(goodsConn)

	//初始化库存服务连接
	invConn, err := grpc.Dial(
		fmt.Sprintf("consul://%s:%d/%s?wait=14s", consulInfo.Host, consulInfo.Port, global.ServerConfig.InventorySrvInfo.Name),
		grpc.WithTransportCredentials(insecure.NewCredentials()),
		grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy": "round_robin"}`),
		//grpc.WithUnaryInterceptor(otgrpc.OpenTracingClientInterceptor(opentracing.GlobalTracer())),
	)
	if err != nil {
		zap.S().Fatal("[InitSrvConn] 连接 【库存服务失败】")
	}

	global.InventorySrvClient = proto.NewInventoryClient(invConn)
}

  • order_srv/main.go:添加商品和库存微服务的初始化
func main() {
	IP := flag.String("ip", "0.0.0.0", "ip地址")
	Port := flag.Int("port", 50060, "端口号") // 这个修改为0,如果我们从命令行带参数启动的话就不会为0

	//初始化
	initialize.InitLogger()
	initialize.InitConfig()
	initialize.InitDB()
	initialize.InitSrvConn()
	zap.S().Info(global.ServerConfig)

	//省略。。。

3 - 创建订单接口(本地mysql事务)

因为涉及到了库存的扣减,需要使用分布式的事务;
我们这里先实现本地mysql事务,后续再优化成分布式的事务

  • order_srv/handler/handle_order.go
func (*OrderServer) CreateOrder(ctx context.Context, req *proto.OrderRequest) (*proto.OrderInfoResponse, error) {
	/*
		新建订单
			1. 从购物车中获取到选中的商品
			2. 商品的价格自己查询 - 访问商品服务 (跨微服务)
			3. 库存的扣减 - 访问库存服务 (跨微服务)
			4. 订单的基本信息表 - 订单的商品信息表
			5. 从购物车中删除已购买的记录
	*/

	var goodsIds []int32
	var shopCarts []model.ShoppingCart
	goodsNumsMap := make(map[int32]int32)
	if result := global.DB.Where(&model.ShoppingCart{User: req.UserId, Checked: true}).Find(&shopCarts); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.InvalidArgument, "没有选中结算的商品")
	}

	for _, shopCarts := range shopCarts {
		goodsIds = append(goodsIds, shopCarts.Goods)
		goodsNumsMap[shopCarts.Goods] = shopCarts.Nums
	}

	//跨服务调用 - 商品微服务 —— 批量查询商品与价格
	goods, err := global.GoodsSrvClient.BatchGetGoods(context.Background(), &proto.BatchGoodsIdInfo{Id: goodsIds})
	if err != nil {
		return nil, status.Errorf(codes.Internal, "批量查询商品信息失败")
	}

	var orderAmount float32
	var orderGoods []*model.OrderGoods
	var goodsInvInfo []*proto.GoodsInvInfo
	for _, good := range goods.Data {
		orderAmount += good.ShopPrice * float32(goodsNumsMap[good.Id])
		orderGoods = append(orderGoods, &model.OrderGoods{
			Goods:      good.Id,
			GoodsName:  good.Name,
			GoodsImage: good.GoodsFrontImage,
			GoodsPrice: good.ShopPrice,
			Nums:       goodsNumsMap[good.Id],
		})
		goodsInvInfo = append(goodsInvInfo, &proto.GoodsInvInfo{
			GoodsId: good.Id,
			Num:     goodsNumsMap[good.Id],
		})
	}
	//跨服务调用 - 库存微服务 —— 扣减库存
	if _, err = global.InventorySrvClient.Sell(context.Background(), &proto.SellInfo{GoodsInfo: goodsInvInfo}); err != nil {
		return nil, status.Errorf(codes.ResourceExhausted, "扣减库存失败")
	}

	tx := global.DB.Begin()
	//生成订单表
	order := model.OrderInfo{
		OrderSn:      GenerateOrderSn(req.UserId),
		OrderMount:   orderAmount,
		Address:      req.Address,
		SignerName:   req.Name,
		SingerMobile: req.Mobile,
		Post:         req.Post,
		User:         req.UserId,
	}
	if result := tx.Save(&order); result.RowsAffected == 0 {
		tx.Rollback()
		return nil, status.Errorf(codes.Internal, "创建订单失败")
	}

	for _, orderGood := range orderGoods {
		orderGood.Order = order.ID
	}

	// 批量插入orderGoods
	if result := tx.CreateInBatches(orderGoods, 100); result.RowsAffected == 0 {
		tx.Rollback()
		return nil, status.Errorf(codes.Internal, "创建订单失败")
	}

	// 删除购物车的记录
	if result := tx.Where(&model.ShoppingCart{User: req.UserId, Checked: true}).Delete(&model.ShoppingCart{}); result.RowsAffected == 0 {
		tx.Rollback()
		return nil, status.Errorf(codes.Internal, "创建订单失败")
	}

	//提交事务
	tx.Commit()
	return &proto.OrderInfoResponse{Id: order.ID, OrderSn: order.OrderSn, Total: order.OrderMount}, nil
}

4 - 更新订单状态接口

  • order_srv/handler/handle_order.go
func (*OrderServer) UpdateOrderStatus(ctx context.Context, req *proto.OrderStatus) (*emptypb.Empty, error) {
	//先查询,再更新 实际上有两条sql执行, select 和 update语句
	if result := global.DB.Model(&model.OrderInfo{}).Where("order_sn = ?", req.OrderSn).Update("status", req.Status); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.NotFound, "订单不存在")
	}
	return &emptypb.Empty{}, nil
}

五、订单服务接口调试

测试接口前,要注意 order_srv/main.go 中 proto.RegisterOrderServer(server, &handler.OrderServer{}) ,这个注册的实现
否则会一直报空实现错误

1 - 通用测试初始化

  • order_srv/tests/test_config.go
package tests

import (
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
	"nd/order_srv/proto"
)

var (
	TargetAddr  = "127.0.0.1:50060"
	OrderClient proto.OrderClient
	Conn        *grpc.ClientConn
)

func Init() {
	var err error
	Conn, err = grpc.Dial(TargetAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		panic(err)
	}
	OrderClient = proto.NewOrderClient(Conn)
}

2 - 购物车相关接口测试

  • order_srv/tests/order/test_shop_cart.go
package main

import (
	"context"
	"fmt"
	"nd/order_srv/proto"
	"nd/order_srv/tests"
)

func TestCartItemList(userId int32) {
	rsp, err := tests.OrderClient.CartItemList(context.Background(), &proto.UserInfo{
		Id: userId,
	})
	if err != nil {
		panic(err)
	}
	for _, item := range rsp.Data {
		fmt.Println(item.Id, item.GoodsId, item.Nums)
	}
}

func TestCreateCartItem(userId, nums, goodsId int32) {
	rsp, err := tests.OrderClient.CreateCartItem(context.Background(), &proto.CartItemRequest{
		UserId:  userId,
		Nums:    nums,
		GoodsId: goodsId,
	})
	if err != nil {
		panic(err)
	}
	fmt.Println(rsp.Id)
}

func TestUpdateCartItem(id int32) {
	_, err := tests.OrderClient.UpdateCartItem(context.Background(), &proto.CartItemRequest{
		Id:      id,
		Checked: true,
	})
	if err != nil {
		panic(err)
	}
}

func main() {
	tests.Init()
	TestCreateCartItem(1, 1, 26)
	TestCreateCartItem(1, 1, 27)
	TestCartItemList(1)
	TestUpdateCartItem(1)
	tests.Conn.Close()
}

在这里插入图片描述

3 - 创建订单接口测试

  • order_srv/tests/order/test_order.go:创建订单接口测试
package main

import (
	"context"
	"nd/order_srv/proto"
	"nd/order_srv/tests"
)

func TestCreateOrder() {
	_, err := tests.OrderClient.CreateOrder(context.Background(), &proto.OrderRequest{
		UserId:  1,
		Address: "北京市",
		Name:    "bobby",
		Mobile:  "18787878787",
		Post:    "请尽快发货",
	})
	if err != nil {
		panic(err)
	}
}

func main() {
	tests.Init()

	TestCreateOrder()

	tests.Conn.Close()
}

  • 先测试购物车都没选中的情况

在这里插入图片描述

  • 接下来测试有购物车的情况:这里需要注意,我们需要开启商品微服务、库存微服务、订单微服务3个微服务才可以测试;并且要保证端口不冲突

在这里插入图片描述

  • 这里还需要注意修改库存微服务中的扣减库存方法:之前的redis地址我们是写死的,如果你换了机器后需要进行修改;因为我的笔记是在不同的三台机器上切换来切换去,所以调试这个bug花了好久(泪崩),这也是微服务的痛点
  • inventory_srv/handler/inventory.go

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

4 - 订单详情接口测试

  • order_srv/tests/order/test_order.go
func TestGetOrderDetail(orderId int32) {
	rsp, err := tests.OrderClient.OrderDetail(context.Background(), &proto.OrderRequest{
		Id: orderId,
	})
	if err != nil {
		panic(err)
	}
	fmt.Println(rsp.OrderInfo.OrderSn)
	for _, good := range rsp.Goods {
		fmt.Println(good.GoodsName)
	}
}

在这里插入图片描述

5 - 获取订单列表接口测试

  • order_srv/tests/order/test_order.go
func TestOrderList() {
	rsp, err := tests.OrderClient.OrderList(context.Background(), &proto.OrderFilterRequest{})
	//rsp, err := tests.OrderClient.OrderList(context.Background(), &proto.OrderFilterRequest{
	//	UserId: 1,
	//})
	if err != nil {
		panic(err)
	}
	for _, order := range rsp.Data {
		fmt.Println(order.OrderSn)
	}
}

在这里插入图片描述

  • 这里我们再测试下gorm忽略零值的情况
  • 不传递userid的情况
    在这里插入图片描述
  • 传递userid的情况

在这里插入图片描述

在这里插入图片描述


六、完整源码

  • 完整源码下载mxshop_srvsV8.8.rar
  • 源码说明:(nacos的ip配置自行修改,全局变量DEV_CONFIG设置:1=zsz,2=comp,3=home)
    • goods_srv/model/sql/mxshop_goods.sql:包含了建表语句
    • other_import/api.json:YApi的导入文件
    • other_import/nacos_config_export_user.zip:nacos的user配置集导入文件
    • other_import/nacos_config_export_goods.zip:nacos的goods配置集导入文件
    • other_import/nacos_config_export_inventory.zip:nacos的inventory的配置导入文件
    • other_import/nacos_config_export_orders.zip:nacos的orders的配置导入文件
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

无休止符

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值