当前位置:网站首页>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
边栏推荐
- Analysis of LENS CRA and SENSOR CRA Matching Problems
- C# using timer
- 【redis】发布和订阅消息
- 使用 BeanUtils 做属性拷贝,性能有点拉胯!
- More parameter exposure of Pico 4: Pancake + color perspective, and Pro version
- 数据分析面试手册《统计篇》
- 复制带随机指针的链表——LeetCode
- [ASM] The relationship between the role of the bytecode operation ClassWriter COMPUTE_FRAMES and visitMaxs
- Go项目配置管理神器之viper使用详解
- Only lazy and hungry. You still don't understand the singleton pattern!
猜你喜欢
20张图,全面掌握MVCC原理!
容器技术真的是环境管理的救星吗?
【考虫 六级英语】语法课笔记
如何破坏Excel文件,让其显示文件已损坏方法
91.(cesium之家)cesium火箭发射模拟
WebView2 通过 PuppeteerSharp 实现RPA获取壁纸 (案例版)
[ASM] The relationship between the role of the bytecode operation ClassWriter COMPUTE_FRAMES and visitMaxs
两个链表的第一个公共节点——LeetCode
How engineers treat open source
BEVDepth: Acquisition of Reliable Depth for Multi-view 3D Object Detection 论文笔记
随机推荐
成功解决TypeError: can‘t multiply sequence by non-int of type ‘float‘
Jvm. Profiling tools (jconsole, jvisualvm, arthas, jprofiler, mat)
How to check if the online query suddenly slows down
Apache Commons Configuration远程代码执行漏洞(CVE-2022-33980)分析&复现
小程序onPageNotFound的坑
Mysql database installation and configuration detailed tutorial
MySQL indexes and transactions
[21 Days Learning Challenge] Half Insertion Sort
EN 12467纤维水泥平板产品—CE认证
什么是“门”电路(电子硬件)
More parameter exposure of Pico 4: Pancake + color perspective, and Pro version
Linux安装redis数据库
R language multiple linear regression, ARIMA analysis of the impact of different candidates in the United States on the economic GDP time series
#yyds Dry Goods Inventory#[Yugong Series] August 2022 Go Teaching Course 008-Integer of Data Types
全排列思路详解
rhel7.0解决yum无法使用(system is not registered to Red Hat Subscription Management)
什么是数组
Shell编程三剑客之sed
Successfully resolved raise TypeError('Unexpected feature_names type')TypeError: Unexpected feature_names type
【pypdf2】合并PDF、旋转、缩放、裁剪、加密解密、添加水印