当前位置:网站首页>22. Inventory service
22. Inventory service
2022-08-11 01:10:00 【There is no rest】
目录
一、Importance of inventory service
二、Inventory table structure withproto接口
- 数据库创建:mxshop_inventory_srv
- inventory_srv/model/inventory.go:表结构
package model
type Inventory struct {
BaseModel
Goods int32 `gorm:"type:int;index"` // 商品id
Stocks int32 `gorm:"type:int"` // 库存
Version int32 `gorm:"type:int"` //Optimistic locking for distributed locks
}
- 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:[email protected](%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), // 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.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); // 获取库存信息
// 购买的时候,Possibly purchased from a shopping cart,This may involve buying inventory for multiple items;Distributed transactions are also involved here
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'
四、Inventory service interface implementation
1 - Set up the inventory interface
- inventory_srv/handler/inventory.go
func (*InventoryServer) SetInv(ctx context.Context, req *proto.GoodsInvInfo) (*emptypb.Empty, error) {
//设置库存, If I want to update inventory
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 - Get inventory interface
- 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) {
// 扣减库存,本地事务
// A basic application scenario of the database:数据库事务
// under concurrent conditions Oversold may occur 1
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, "库存不足")
}
// 扣减,There will be data inconsistencies here
inv.Stocks -= goodInfo.Num
tx.Save(&inv) // Once transactional is used,The operation of saving and modifying the database requires the use of transactionstx,而不能使用db
}
tx.Commit() // You need to manually submit the operation yourself
return &emptypb.Empty{
}, nil
}
4 - 库存归还(本地事务)
func (*InventoryServer) Reback(ctx context.Context, req *proto.SellInfo) (*emptypb.Empty, error) {
//库存归还: 1:Order returned over time 2. 订单创建失败,Return previously deducted inventory 3. manual return
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() // You need to manually submit the operation yourself
return &emptypb.Empty{
}, nil
}
5 - 接口测试
- inventory_srv/main.go:修改端口为50059;protoThe registered object is modified to&handler.InventoryServer{}
func main() {
IP := flag.String("ip", "0.0.0.0", "ip地址")
Port := flag.Int("port", 50059, "端口号") // 这个修改为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.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("Set up inventory successfully")
}
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() {
/* 1. The first deduction was successful: 第二件: 1. 没有库存信息 2. 库存不足 2. Both were successfully deducted */
_, 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()
//var i int32
//for i = 1; i <= 9; i++ {
// TestSetInv(i, 90)
//}
//TestInvDetail(2)
//TestSell()
TestReback()
conn.Close()
}
五、完整源码
- 完整源码下载:mxshop_srvsV8.6.rar
- 源码说明:(nacos的ip配置自行修改,全局变量DEV_CONFIG设置:1=zsz,2=comp,3=home)
- goods_srv/model/sql/mxshop_goods.sql:Contains the create table statement
- other_import/api.json:YApi的导入文件
- other_import/nacos_config_export_user.zip:nacos的userConfiguration set import file
- other_import/nacos_config_export_goods.zip:nacos的goodsConfiguration set import file
- other_import/nacos_config_export_inventory.zip:nacos的inventoryconfiguration import file
边栏推荐
- C#-委托的详细用法
- 如何做到构建的提速,再提速
- 【ASM】字节码操作 ClassWriter COMPUTE_FRAMES 的作用 与 visitMaxs 的关系
- Mysql数据库安装配置详细教程
- vim simple save window id
- 关于编程本质那些事
- @Autowired注入RedisCache报错空指针
- BEVDepth: Acquisition of Reliable Depth for Multi-view 3D Object Detection Paper Notes
- SQL statement--get database table information, table name, column name, description comment, etc.
- Data Filters in ABP
猜你喜欢
异常和异常处理机制
【考虫 六级英语】语法课笔记
Dual machine thermal for comprehensive experiment (VRRP + OSPF + + NAT + DHCP + VTP PVSTP + single-arm routing)
Some Experiences of Embedded Software Logging
leetcode 前K个高频单词
WinForm(五)控件和它的成员
数据分析面试手册《统计篇》
如何破坏Excel文件,让其显示文件已损坏方法
HW-蓝队工作流程(1)
Kunpeng compilation and debugging and basic knowledge of native development tools
随机推荐
【redis】发布和订阅消息
How to check if the online query suddenly slows down
力扣------使用最小花费爬楼梯
Update chromedriver driver programming skills │ selenium
Pico 4更多参数曝光:Pancake+彩色透视,还有Pro版本
C# using timer
[ASM] The relationship between the role of the bytecode operation ClassWriter COMPUTE_FRAMES and visitMaxs
16. 最接近的三数之和
MySQL indexes and transactions
SQL statement--get database table information, table name, column name, description comment, etc.
Analysis of LENS CRA and SENSOR CRA Matching Problems
BEVDepth: Acquisition of Reliable Depth for Multi-view 3D Object Detection Paper Notes
Successfully resolved raise TypeError('Unexpected feature_names type')TypeError: Unexpected feature_names type
22/8/9 贪心问题合集
C# JObject解析JSON数据
Some Experiences of Embedded Software Logging
WinForm (5) control and its members
EPro-PnP: Generalized End-to-End Probabilistic Perspective-n-Points for Monocular Object Pose Est...
More parameter exposure of Pico 4: Pancake + color perspective, and Pro version
Mysql数据库安装配置详细教程