ApiGroup := Router.Group("/g/v1")
ctx.DefaultQuery()
,按照 proto 的定义拼装好向 srv 的请求参数type Registry struct {Host stringPort int
}func NewRegistryClient(host string, port int) RegistryClient {return &Registry{Host: host,Port: port,}
}
type RegistryClient interface {Register(address string, port int, name string, tags []string, id string) errorDeRegister(serviceId string) error
}
type GoodsClient interface {//商品接口GoodsList(ctx context.Context, in *GoodsFilterRequest, opts ...grpc.CallOption) (*GoodsListResponse, error)// ....
}type goodsClient struct {cc grpc.ClientConnInterface
}func NewGoodsClient(cc grpc.ClientConnInterface) GoodsClient {return &goodsClient{cc}
}
CategoryRouter.GET("", category.List) // 商品类别列表页
CategoryRouter.DELETE("/:id", category.Delete) // 删除分类
CategoryRouter.GET("/:id", category.Detail) // 获取分类详情
CategoryRouter.POST("", category.New) //新建分类
CategoryRouter.PUT("/:id", category.Update) //修改分类信息
// 根据某个分类找到所有品牌
func GetCategoryBrandList(ctx *gin.Context) {// 1. 获取请求参数id := ctx.Param("id")i, err := strconv.ParseInt(id, 10, 32)if err != nil {ctx.Status(http.StatusNotFound)return}// 2. 组装请求参数,请求后端接口rsp, err := global.GoodsSrvClient.GetCategoryBrandList(context.Background(), &proto.CategoryInfoRequest{Id: int32(i),})if err != nil {api.HandleGrpcErrorToHttp(err, ctx)return}// 3. 解析响应数据,返回给前端// 组成 json 格式result := make([]interface{}, 0) // 切片for _, value := range rsp.Data {reMap := make(map[string]interface{})reMap["id"] = value.IdreMap["name"] = value.NamereMap["logo"] = value.Logoresult = append(result, reMap)}ctx.JSON(http.StatusOK, result)
}
package mainimport ("fmt""github.com/aliyun/aliyun-oss-go-sdk/oss""os"
)func handleError(err error) {fmt.Println("Error:", err)os.Exit(-1)
}
func main() {// yourEndpoint填写Bucket对应的Endpoint,以华东1(杭州)为例,填写为https://oss-cn-hangzhou.aliyuncs.com。其它Region请按实际情况填写。endpoint := "https://oss-cn-beijing.aliyuncs.com"// 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。accessKeyId := "LTAI5tAnJCMgDdYKNPKK5AUP"accessKeySecret := "xxx"// yourBucketName填写存储空间名称。bucketName := "bucket-fileupload-test"// yourObjectName填写Object完整路径,完整路径不包含Bucket名称。objectName := "files/test.png"// yourLocalFileName填写本地文件的完整路径。localFileName := "D:\\GoLand\\workspaces\\shop-api\\temp\\oss\\yun.png"// 创建OSSClient实例。client, err := oss.New(endpoint, accessKeyId, accessKeySecret)if err != nil {handleError(err)}// 获取存储空间。bucket, err := client.Bucket(bucketName)if err != nil {handleError(err)}// 上传文件。err = bucket.PutObjectFromFile(objectName, localFileName)if err != nil {handleError(err)}
}
go run appserver.go 127.0.0.1 8888
{
accessid: "LTAI5tAnJCMgDdYKNPKK5AUP",
host: "http://bucket-fileupload-test.oss-cn-hangzhou.aliyuncs.com",
expire: 1677897001,
signature: "P/C8MKC9vxoJcAOzzKDJVWv8Mf4=",
policy: "eyJleHBpcmF0aW9uIjoiMjAyMy0wMy0wNFQwMjozMDowMVoiLCJjb25kaXRpb25zIjpbWyJzdGFydHMtd2l0aCIsIiRrZ",
dir: "webfiles/",
callback: "eyJjYWxsYmFja1VybCI6Imh0dHA6Ly8xMjcuMC4wLjE6ODg4OCIsImNhbGxiYWNrQm9keSI6ImZpbGVuYW1lPSR7b2JqZWN0fVx1MDAyNnNpemU9JHtzaXplfVx1MDAyNm1pbWVUeXBlPSR7bWltZVR5cGV9XHUwMDI2aGVpZ2h0PSR7aW1hZ2VJbmZvLmhlaWdodH1cdTAwMjZ3aWR0aD0ke2ltYWdlSW5mby53aWR0aH0iLCJjYWxsYmFja0JvZHlUeXBlIjoiYXBwbGljYXRpb24veC13d3ctZm9ybS11cmxlbmNvZGVkIn0="
}
serverUrl
new_multipart_params = {'key' : g_object_name,'policy': policyBase64,'OSSAccessKeyId': accessid, 'success_action_status' : '200', //让服务端返回200,不然,默认会返回204//'callback' : callbackbody,'signature': signature,
};
responseSuccess/responseFailed
,就需要再经过我们的服务器callbackUrl
需要公网 IP,我们的服务器都在局域网上 type Inventory struct{BaseModelGoods int32 `gorm:"type:int;index"` // 这个其实是商品id,这里直接用 goodsStocks int32 `gorm:"type:int"`Version int32 `gorm:"type:int"` //分布式锁的乐观锁
}
SetInv
,给商品服务用InvDetail
Sell
,给订单服务用,也可以批量,因为用户一般会直接从购物车下单多件Reback
,要针对订单 ID 新设计表(归还这个订单下的所有商品),方便支持事务UnimplementedInventoryServer
让我们在编写微服务时具有向前兼容的能力 func main() {Init()var i int32// 商品表 id 从 421 到 840 for i = 421; i<=840; i++ {TestSetInv(i, 100)}
}
func main() {Init()var wg sync.WaitGroupwg.Add(20)for i := 0; i < 20; i++ {go TestSell(&wg)}wg.Wait()conn.Close()
}
sync.Mutex
里面的 Lock
和 unLock
方法for update
(InnoDB引擎) // 直接用 SQL 语句是这样的
// select * from table where xxx for update
// mysql 默认自动提交,提交了会释放锁,所以事务在这里起到了两个作用,还有一个就是相当于关闭autocommit
// 换句话说就是:在begin与commit之间才生效
tx := global.DB.Begin()
tx.Clauses(clause.Locking{Strength: "UPDATE"}) // 后面再跟 Where 即可
version
,更新条件中带上 version,如果和一开始查询到的 version 不一致,证明之前查到的数据已经别人被更新了,需要重新查询;如果更新成功则需把 version+1for {// 这里有个坑,就是一定要用 Select,不然有零值问题,gorm 会忽略掉不更新,就是说不让你设置为0,库存虽然只剩一件,但是version没问题,全部都能扣减成功!库存始终是1if result := tx.Model(&model.Inventory{}).Select("Stocks", "Version").Where("goods = ? and version= ?", goodInfo.GoodsId, inv.Version).Updates(model.Inventory{Stocks: inv.Stocks, Version: inv.Version+1}); result.RowsAffected == 0 {zap.S().Info("库存扣减失败")}else{break}
}
client := goredislib.NewClient(&goredislib.Options{Addr: fmt.Sprintf("%s:%d", global.ServerConfig.RedisInfo.Host, global.ServerConfig.RedisInfo.Port),
})
pool := goredis.NewPool(client) // or, pool := redigo.NewPool(...)
rs := redsync.New(pool)
// ...
mutex := rs.NewMutex(fmt.Sprintf("goods_%d", goodInfo.GoodsId))
// 剩下的就和go自带的mutex类似
mutex.Unlock()