当前位置:网站首页>22、库存服务
22、库存服务
2022-08-11 00:59:00 【无休止符】
目录
一、库存服务的重要性

二、库存表结构与proto接口
- 数据库创建: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"` //分布式锁的乐观锁
}
- 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); // 获取库存信息
// 购买的时候,有可能是从购物车购买的,这就可能涉及到多件商品的购买库存;这里还涉及到了分布式事务
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) {
// 扣减库存,本地事务
// 数据库基本的一个应用场景:数据库事务
// 并发情况之下 可能会出现超卖 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, "库存不足")
}
// 扣减,这里会出现数据不一致的问题
inv.Stocks -= goodInfo.Num
tx.Save(&inv) // 一旦使用了事务的,保存修改数据库的操作就需要使用事务的tx,而不能使用db
}
tx.Commit() // 需要自己手动提交操作
return &emptypb.Empty{
}, nil
}
4 - 库存归还(本地事务)
func (*InventoryServer) Reback(ctx context.Context, req *proto.SellInfo) (*emptypb.Empty, error) {
//库存归还: 1:订单超时归还 2. 订单创建失败,归还之前扣减的库存 3. 手动归还
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, "端口号") // 这个修改为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("设置库存成功")
}
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. 第一件扣减成功: 第二件: 1. 没有库存信息 2. 库存不足 2. 两件都扣减成功 */
_, 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:包含了建表语句
- 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的配置导入文件
边栏推荐
- Navicat 16-数据库工具
- SystemVerilog: 验证知识点点滴滴
- Difference Between Image Recognition and Semantic Segmentation
- 91.(cesium之家)cesium火箭发射模拟
- 力扣------用栈操作构建数组
- #yyds干货盘点#【愚公系列】2022年08月 Go教学课程 008-数据类型之整型
- How to easily obtain the citation format of references?
- 成功解决raise TypeError(‘Unexpected feature_names type‘)TypeError: Unexpected feature_names type
- 【经典排序】快速排序
- ① 数据库介绍 及 关系型数据库的关系代数表达式
猜你喜欢

Word set before the title page

力扣------使用最小花费爬楼梯

Dual machine thermal for comprehensive experiment (VRRP + OSPF + + NAT + DHCP + VTP PVSTP + single-arm routing)
![[GXYCTF2019]BabySQli](/img/8a/7500c0ee275d6ef8909553f34c99cf.png)
[GXYCTF2019]BabySQli

双机热备综合实验(VRRP+OSPF+VTP+NAT+DHCP+PVSTP+单臂路由)

复制带随机指针的链表——LeetCode

ADC和DAC记录
![[ASM] The relationship between the role of the bytecode operation ClassWriter COMPUTE_FRAMES and visitMaxs](/img/28/66370d46ebeb1e16b56ea2a36fe100.jpg)
[ASM] The relationship between the role of the bytecode operation ClassWriter COMPUTE_FRAMES and visitMaxs

MSTP——多生成树(案列+配置)
![[Server data recovery] Data recovery case of lvm information and VXFS file system corruption caused by raid5 crash](/img/dc/043f861507fc16530446bf051941ef.jpg)
[Server data recovery] Data recovery case of lvm information and VXFS file system corruption caused by raid5 crash
随机推荐
Vim take on a window.
【考虫 六级英语】语法课笔记
Apache Commons Configuration Remote Code Execution Vulnerability (CVE-2022-33980) Analysis & Reproduction
WinForm(五)控件和它的成员
复制带随机指针的链表——LeetCode
力扣------值相等的最小索引
总结Qt中常用文件信息QFileInfo的获取:后缀,名称,路径,链接
Summarize the acquisition of commonly used file information QFileInfo in Qt: suffix, name, path, link
成功解决TypeError: can‘t multiply sequence by non-int of type ‘float‘
异常:try catch finally throws throw
Analysis of LENS CRA and SENSOR CRA Matching Problems
WebView2 通过 PuppeteerSharp 实现RPA获取壁纸 (案例版)
MySQL进阶查询
Kunpeng compilation and debugging and basic knowledge of native development tools
全排列思路详解
微信小程序通过URL Scheme动态的渲染数据
@Autowired注入RedisCache报错空指针
"NIO Cup" 2022 Nioke Summer Multi-School Training Camp 4 ADHK Problem Solving
【openpyxl】过滤和排序
【21天学习挑战赛】折半插入排序