一、库存服务的重要性
二、库存表结构与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的配置导入文件