一、库存服务的重要性

二、库存表结构与proto接口
- 数据库创建:mxshop_inventory_srv

- inventory_srv/model/inventory.go:表结构
package model
type Inventory struct {
BaseModel
Goods int32 `gorm:"type:int;index"`
Stocks int32 `gorm:"type:int"`
Version int32 `gorm:"type:int"`
}
- inventory_srv/model/main/main.go:gorm建表
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"gorm.io/gorm/schema"
"log"
"nd/inventory_srv/global"
"nd/inventory_srv/initialize"
"nd/inventory_srv/model"
"os"
"time"
)
func main() {
initialize.InitConfig()
dsn := fmt.Sprintf("root:jiushi@tcp(%s:3306)/mxshop_inventory_srv?charset=utf8mb4&parseTime=True&loc=Local", global.ServerConfig.MysqlInfo.Host)
newLogger := logger.New(
log.New(os.Stdout, "\r\n", log.LstdFlags),
logger.Config{
SlowThreshold: time.Second,
LogLevel: logger.Info,
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.Inventory{})
}
- inventory_srv/proto/inventory.proto:
protoc --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 Inventory {
rpc SetInv(GoodsInvInfo) returns(google.protobuf.Empty);
rpc InvDetail(GoodsInvInfo) returns (GoodsInvInfo);
rpc Sell(SellInfo) returns (google.protobuf.Empty);
rpc Reback(SellInfo) returns(google.protobuf.Empty);
}
message GoodsInvInfo {
int32 goodsId = 1;
int32 num = 2;
}
message SellInfo {
repeated GoodsInvInfo goodsInfo = 1;
string orderSn = 2;
}
三、快速拉起inventory服务
- inventory_srv/main.go:proto注册
server := grpc.NewServer()
proto.RegisterInventoryServer(server, &proto.UnimplementedInventoryServer{})
lis, err := net.Listen("tcp", fmt.Sprintf("%s:%d", *IP, *Port))
if err != nil {
panic("failed to listen:" + err.Error())
}
- nacos新建命名空间


{
"name": "inventory_srv",
"host": "192.168.78.1",
"tags": ["imooc", "bobby", "inventory", "srv"],
"mysql": {
"host": "192.168.78.131",
"port": 3306,
"user": "root",
"password": "jiushi",
"db": "mxshop_inventory_srv"
},
"consul": {
"host": "192.168.78.131",
"port": 8500
}
}
- 修改yaml的命名空间为inventory的命名空间id
host: '192.168.78.131'
port: 8848
namespace: '2a8c0128-127b-4356-8670-811eb688f7bd'
user: 'nacos'
password: 'nacos'
dataid: 'inventory_srv.json'
group: 'comp'

四、库存服务接口实现
1 - 设置库存接口
- inventory_srv/handler/inventory.go
func (*InventoryServer) SetInv(ctx context.Context, req *proto.GoodsInvInfo) (*emptypb.Empty, error) {
var inv model.Inventory
global.DB.Where(&model.Inventory{Goods: req.GoodsId}).First(&inv)
inv.Goods = req.GoodsId
inv.Stocks = req.Num
global.DB.Save(&inv)
return &emptypb.Empty{}, nil
}
2 - 获取库存接口
- inventory_srv/handler/inventory.go
func (*InventoryServer) InvDetail(ctx context.Context, req *proto.GoodsInvInfo) (*proto.GoodsInvInfo, error) {
var inv model.Inventory
if result := global.DB.Where(&model.Inventory{Goods: req.GoodsId}).First(&inv); result.RowsAffected == 0 {
return nil, status.Errorf(codes.NotFound, "没有库存信息")
}
return &proto.GoodsInvInfo{
GoodsId: inv.Goods,
Num: inv.Stocks,
}, nil
}
3 - 扣减库存(本地事务)
func (*InventoryServer) Sell(ctx context.Context, req *proto.SellInfo) (*emptypb.Empty, error) {
tx := global.DB.Begin()
for _, goodInfo := range req.GoodsInfo {
var inv model.Inventory
if result := global.DB.Where(&model.Inventory{Goods: goodInfo.GoodsId}).First(&inv); result.RowsAffected == 0 {
tx.Rollback()
return nil, status.Errorf(codes.InvalidArgument, "没有库存信息")
}
if inv.Stocks < goodInfo.Num {
tx.Rollback()
return nil, status.Errorf(codes.ResourceExhausted, "库存不足")
}
inv.Stocks -= goodInfo.Num
tx.Save(&inv)
}
tx.Commit()
return &emptypb.Empty{}, nil
}
4 - 库存归还(本地事务)
func (*InventoryServer) Reback(ctx context.Context, req *proto.SellInfo) (*emptypb.Empty, error) {
tx := global.DB.Begin()
for _, goodInfo := range req.GoodsInfo {
var inv model.Inventory
if result := global.DB.Where(&model.Inventory{Goods: goodInfo.GoodsId}).First(&inv); result.RowsAffected == 0 {
tx.Rollback()
return nil, status.Errorf(codes.InvalidArgument, "没有库存信息")
}
inv.Stocks += goodInfo.Num
tx.Save(&inv)
}
tx.Commit()
return &emptypb.Empty{}, nil
}
5 - 接口测试
- inventory_srv/main.go:修改端口为50059;proto注册对象修改为&handler.InventoryServer{}
func main() {
IP := flag.String("ip", "0.0.0.0", "ip地址")
Port := flag.Int("port", 50059, "端口号")
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.RegisterInventoryServer(server, &handler.InventoryServer{})
- inventory_srv/tests/test_config.go
package tests
var (
TargetAddr = "127.0.0.1:50059"
)
- 测试前提
- goods_srv服务启动:端口50058
- inventory_srv服务启动:端口50059
- 运行 inventory_srv/tests/inventory/main.go
package main
import (
"context"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"nd/inventory_srv/proto"
"nd/inventory_srv/tests"
)
var invClient proto.InventoryClient
var conn *grpc.ClientConn
func Init() {
var err error
conn, err = grpc.Dial(tests.TargetAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
panic(err)
}
invClient = proto.NewInventoryClient(conn)
}
func TestSetInv(goodsId, Num int32) {
_, err := invClient.SetInv(context.Background(), &proto.GoodsInvInfo{
GoodsId: goodsId,
Num: Num,
})
if err != nil {
panic(err)
}
fmt.Println("设置库存成功")
}
func TestInvDetail(goodsId int32) {
rsp, err := invClient.InvDetail(context.Background(), &proto.GoodsInvInfo{
GoodsId: goodsId,
})
if err != nil {
panic(err)
}
fmt.Println(rsp.Num)
}
func TestSell() {
_, err := invClient.Sell(context.Background(), &proto.SellInfo{
GoodsInfo: []*proto.GoodsInvInfo{
{GoodsId: 1, Num: 1},
{GoodsId: 2, Num: 70},
},
})
if err != nil {
panic(err)
}
fmt.Println("库存扣减成功")
}
func TestReback() {
_, err := invClient.Reback(context.Background(), &proto.SellInfo{
GoodsInfo: []*proto.GoodsInvInfo{
{GoodsId: 1, Num: 10},
{GoodsId: 100, Num: 30},
},
})
if err != nil {
panic(err)
}
fmt.Println("归还成功")
}
func main() {
Init()
TestReback()
conn.Close()
}
五、完整源码
- 完整源码下载:mxshop_srvsV8.6.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的配置导入文件