【Go】十八、http 调用服务的编写
http接口框架的搭建
这个http接口框架的搭建参考之前的全量搭建,这里是快速搭建的模式:
直接对已有的http模块进行复制修改,主要修改点在于 proto部分与api、router 部分,剩余的要针对进行修改模块名称。
接口的具体编写
在 api/goods/goods.go 中编写具体的代码:
例如:goods的List接口的开发:
func List(ctx *gin.Context) {request := &proto.GoodsFilterRequest{}priceMin := ctx.DefaultQuery("pmin", "0") // 取出设定的最小价格,默认为 0priceMinInt, _ := strconv.Atoi(priceMin)request.PriceMin = int32(priceMinInt)priceMax := ctx.DefaultQuery("pmax", "0")priceMaxInt, _ := strconv.Atoi(priceMax)request.PriceMax = int32(priceMaxInt)isHot := ctx.DefaultQuery("ih", "0")if isHot == "1" {request.IsHot = true}isNew := ctx.DefaultQuery("in", "0")if isNew == "1" {request.IsNew = true}isTab := ctx.DefaultQuery("it", "0")if isTab == "1" {request.IsTab = true}categoryId := ctx.DefaultQuery("c", "0")categoryIdInt, _ := strconv.Atoi(categoryId)request.TopCategory = int32(categoryIdInt)pages := ctx.DefaultQuery("p", "0")pagesInt, _ := strconv.Atoi(pages)request.Pages = int32(pagesInt)perNums := ctx.DefaultQuery("pnum", "0")perNumsInt, _ := strconv.Atoi(perNums)request.PagePerNums = int32(perNumsInt)keywords := ctx.DefaultQuery("q", "")request.KeyWords = keywordsbrandId := ctx.DefaultQuery("b", "0")brandIdInt, _ := strconv.Atoi(brandId)request.Brand = int32(brandIdInt)r, err := global.GoodsSrvClient.GoodsList(context.Background(), request)if err != nil {zap.S().Errorw("[List] 查询 【商品列表】 失败")HandleGrpcErrorToHttp(err, ctx)return}reMap := map[string]interface{}{"total": r.Total,"data": r.Data,}ctx.JSON(http.StatusOK, reMap)
}
但是呢,这种写法存在一定的问题,就是在http接口中未存在对于 后端数据的修正,这样子不便于前端与后端的数据对接,所以我们在 http 端一般可以做一个修改确定:
reMap := map[string]interface{}{"total": r.Total,}// 这里一般会进行数据处理goodsList := make([]interface{}, 0)for _, value := range r.Data {goodsList = append(goodsList, map[string]interface{}{"id": value.Id,"name": value.Name,"goods_brief": value.GoodsBrief,"desc": value.GoodsDesc,"ship_free": value.ShipFree,"images": value.Images,"desc_images": value.DescImages,"front_image": value.GoodsFrontImage,"shop_price": value.ShopPrice,"category": map[string]interface{}{"id": value.Category.Id,"name": value.Category.Name,},"brand": map[string]interface{}{"id": value.Brand.Id,"name": value.Brand.Name,"logo": value.Brand.Logo,},"is_hot": value.IsHot,"is_new": value.IsNew,"on_sale": value.OnSale,})}reMap["data"] = goodsListctx.JSON(http.StatusOK, reMap)
注册中心内容抽取
有时候,我们的项目不是完全基于某一个注册中心,我们不希望将注册中心的逻辑集成在 main文件中,我们希望我们的项目具有快速替换性,这个时候就需要我们将注册中心的内容抽取出来
注册中心服务注册
我们在 util 包下创建一个 register 包,再在 register包下创建 consul包,再在 consul包下创建register.go:
package consulimport ("fmt""github.com/hashicorp/consul/api""mxshop-api/goods-web/global"
)// 重新配置Register,令其单独拎出来
// 注册类,这是一个类
type Registry struct {Host stringPort int
}// 这个是类的能力,能干什么
type RegistryClient interface {Register(address string, port int, name string, tags []string, id string) errorDeRegister(serviceId string) error
}func NewRegistryClient(host string, port int) RegistryClient {return &Registry{Host: host,Port: port,}
}func (r *Registry) Register(address string, port int, name string, tags []string, id string) error {cfg := api.DefaultConfig()cfg.Address = fmt.Sprintf("%s:%d", r.Host, r.Port) // consul 的地址client, err := api.NewClient(cfg)if err != nil {panic(err)}// 生成 consul 的注册对象// 配置基础信息registration := new(api.AgentServiceRegistration)registration.Name = nameregistration.ID = idregistration.Tags = tagsregistration.Port = portregistration.Address = address// 配置检查对象,也就是健康检查机制check := &api.AgentServiceCheck{HTTP: fmt.Sprintf("http://%s:%d/health", global.ServerConfig.Host, global.ServerConfig.Port), // 发送 GET 请求来进行健康检查,服务的地址Timeout: "5s", // 每次健康检查中,多久没有回复视为健康检查失败Interval: "5s", // 进行健康检查的频率DeregisterCriticalServiceAfter: "10s", // 不健康服务允许存活的时间,当一个服务被检查为不健康时,若 10s 内其没有转为健康,则将其从服务中删除}// 将检查对象配置进 consul 的注册对象 registration 中registration.Check = check// 将配置的 consul 注册进去err = client.Agent().ServiceRegister(registration)client.Agent().ServiceDeregister(name)if err != nil {panic(err)}return nil}func (r *Registry) DeRegister(serviceId string) error {return nil
}
在这个程序中,我们要注意的是程序的注册与鸭子类型的实际应用,同时理解golang的设计思想
main.go
// 动态配置 Consul 相关信息register_client := consul.NewRegistryClient(global.ServerConfig.ConsulInfo.Host, global.ServerConfig.ConsulInfo.Port)serviceId := fmt.Sprintf("%s", uuid.NewV4())err := register_client.Register(global.ServerConfig.Host, global.ServerConfig.Port, global.ServerConfig.Name, global.ServerConfig.Tags, serviceId)if err != nil {zap.S().Panic("服务注册失败", err.Error())}
注册中心服务注销
优雅的利用go 的队列机制注销服务
quit := make(chan os.Signal)// 如果接收到了 kill 或 ctrl C,则进入quitsignal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)<-quitif err = register_client.DeRegister(serviceId); err != nil {zap.S().Panic("注销失败:", err.Error())} else {zap.S().Panic("注销成功")}
新建商品接口
在router中添加路由,这个商品需要管理员权限才可以新建
func InitGoodsRouter(Router *gin.RouterGroup) {// 这样就需要 /user/list 才可以进行访问了GoodsRouter := Router.Group("goods"){// 在这里添加拦截器的作用响应位置GoodsRouter.GET("", goods.List)GoodsRouter.POST("", middlewares.JWTAuth(), middlewares.IsAdminAuth(), goods.New)}
}
同时我们在 api/goods.go 中添加新建商品的接口
func New(ctx *gin.Context) {// 由于这里使用了 类似于 Vo 的感觉,所以先构建一个用来绑定获取到的前端数据goodsForm := forms.GoodsForm{}// 绑定 ShouldBind可以同时绑定form和json,这里就写的明确一点,视为json形式if err := ctx.ShouldBindJSON(&goodsForm); err != nil {HandleValidatorError(ctx, err)return}// 利用proto 从 grpc服务中获取所需的信息goodsClient := global.GoodsSrvClientrsp, err := goodsClient.CreateGoods(context.Background(), &proto.CreateGoodsInfo{Name: goodsForm.Name,GoodsSn: goodsForm.GoodsSn,Stocks: goodsForm.Stocks,MarketPrice: goodsForm.MarketPrice,ShopPrice: goodsForm.ShopPrice,GoodsBrief: goodsForm.GoodsBrief,GoodsDesc: goodsForm.GoodsDesc,ShipFree: *goodsForm.ShipFree,Images: goodsForm.Images,DescImages: goodsForm.DescImages,GoodsFrontImage: goodsForm.FrontImage,CategoryId: goodsForm.CategoryId,BrandId: goodsForm.Brand,})if err != nil {HandleGrpcErrorToHttp(err, ctx)return}// todo 如何设置库存ctx.JSON(http.StatusOK, rsp)
}
商品详情接口
在 router 中创建 单独商品详情接口:
GoodsRouter.GET("/:id", goods.Detail) // 捕捉类似于/goods/123 的内容中的 123
func Detail(ctx *gin.Context) {// 获取拼接在 链接之后的参数id := ctx.Param("id") // 获取 /goods/123i, err := strconv.ParseInt(id, 10, 32)if err != nil {ctx.Status(http.StatusNotFound)return}// 发送 grpc 请求查询相关内容r, err := global.GoodsSrvClient.GetGoodsDetail(context.Background(), &proto.GoodInfoRequest{Id: int32(i),})if err != nil {api.HandleGrpcErrorToHttp(err, ctx)}rsp := map[string]interface{}{"id": r.Id,"name": r.Name,"goods_brief": r.GoodsBrief,"desc": r.GoodsDesc,"ship_free": r.ShipFree,"images": r.Images,"desc_images": r.DescImages,"front_image": r.GoodsFrontImage,"shop_price": r.ShopPrice,"category": map[string]interface{}{"id": r.Category.Id,"name": r.Category.Name,},"brand": map[string]interface{}{"id": r.Brand.Id,"name": r.Brand.Name,"logo": r.Brand.Logo,},"is_hot": r.IsHot,"is_new": r.IsNew,"on_sale": r.OnSale,}ctx.JSON(http.StatusOK, rsp)
}
删除商品接口
在 router 中创建 删除商品的接口:
GoodsRouter.DELETE("/:id", middlewares.JWTAuth(), middlewares.IsAdminAuth(), goods.Delete)
func Delete(ctx *gin.Context) {id := ctx.Param("id")i, err := strconv.ParseInt(id, 10, 32)if err != nil {ctx.Status(http.StatusNotFound)return}_, err = global.GoodsSrvClient.DeleteGoods(context.Background(), &proto.DeleteGoodsInfo{Id: int32(i)})if err != nil {api.HandleGrpcErrorToHttp(err, ctx)return}ctx.Status(http.StatusOK)return
}
获取商品库存接口
库存接口正在开发中
在router 中新增接口
GoodsRouter.GET("/:id/stocks", goods.Stocks) // 获取商品库存
func Stocks(ctx *gin.Context) {id := ctx.Param("id")_, err := strconv.ParseInt(id, 10, 32)if err != nil {ctx.Status(http.StatusNotFound)return}// TODO 商品库存相关信息
}
更新商品状态接口
GoodsRouter.PATCH("/:id", middlewares.JWTAuth(), middlewares.IsAdminAuth(), goods.UpdateStatus)
func UpdateStatus(ctx *gin.Context) {id := ctx.Param("id")i, err := strconv.ParseInt(id, 10, 32)if err != nil {ctx.Status(http.StatusNotFound)return}goodsStatusForm := forms.GoodsStatusForm{}if err := ctx.ShouldBindJSON(&goodsStatusForm); err != nil {api.HandleValidatorError(ctx, err)return}if _, err = global.GoodsSrvClient.UpdateGoods(context.Background(), &proto.CreateGoodsInfo{Id: int32(i),IsNew: *goodsStatusForm.IsNew,IsHot: *goodsStatusForm.IsHot,OnSale: *goodsStatusForm.OnSale,}); err != nil {api.HandleGrpcErrorToHttp(err, ctx)return}ctx.JSON(http.StatusOK, gin.H{"msg": "修改成功",})
}
更新商品接口
GoodsRouter.PUT("/:id", middlewares.JWTAuth(), middlewares.IsAdminAuth(), goods.Update)
func Update(ctx *gin.Context) {id := ctx.Param("id")i, err := strconv.ParseInt(id, 10, 32)if err != nil {ctx.Status(http.StatusNotFound)return}goodsForm := forms.GoodsForm{}if err = ctx.ShouldBindJSON(&goodsForm); err != nil {api.HandleValidatorError(ctx, err)return}if _, err = global.GoodsSrvClient.UpdateGoods(context.Background(), &proto.CreateGoodsInfo{Id: int32(i),Name: goodsForm.Name,GoodsSn: goodsForm.GoodsSn,Stocks: goodsForm.Stocks,MarketPrice: goodsForm.MarketPrice,ShopPrice: goodsForm.ShopPrice,GoodsBrief: goodsForm.GoodsBrief,GoodsDesc: goodsForm.GoodsDesc,ShipFree: *goodsForm.ShipFree,Images: goodsForm.Images,DescImages: goodsForm.DescImages,GoodsFrontImage: goodsForm.FrontImage,CategoryId: goodsForm.CategoryId,BrandId: goodsForm.Brand,}); err != nil {api.HandleGrpcErrorToHttp(err, ctx)return}ctx.JSON(http.StatusOK, gin.H{"msg": "修改成功",})}
分类的相关接口
我们在做分类接口之前,意识到有三个接口是大家都共用的,分别是:removeTopStruct、HandleGrpcErrorToHttp、HandleValidatorError ,所以我们可以把这三个接口都抽到base.go 中:
base.go(在 api 包下创建的新接口)
package apiimport ("github.com/gin-gonic/gin""github.com/go-playground/validator/v10""google.golang.org/grpc/codes""google.golang.org/grpc/status""mxshop-api/goods-web/global""net/http""strings"
)func HandleGrpcErrorToHttp(err error, c *gin.Context) {// 将 grpc 的状态码转换为 http 的状态码if err != nil {if e, ok := status.FromError(err); ok {switch e.Code() {case codes.NotFound:c.JSON(http.StatusNotFound, gin.H{"msg": e.Message(),})case codes.Internal:c.JSON(http.StatusInternalServerError, gin.H{"msg": "内部错误",})case codes.InvalidArgument:c.JSON(http.StatusBadRequest, gin.H{"msg": "参数错误",})default:c.JSON(http.StatusInternalServerError, gin.H{"msg": "其他错误:" + e.Message(),})}}}
}// 在最后返回错误时调用,用来将返回中的对象名去掉
func RemoveTopStruct(fields map[string]string) map[string]string {rsp := map[string]string{}for field, err := range fields {rsp[field[strings.Index(field, ".")+1:]] = err // 将map中的 key 中的 . 前面的信息去掉}return rsp
}func HandleValidatorError(c *gin.Context, err error) {errs, ok := err.(validator.ValidationErrors)if !ok {c.JSON(http.StatusOK, gin.H{"msg": err.Error(),})}c.JSON(http.StatusBadRequest, gin.H{"msg": RemoveTopStruct(errs.Translate(global.Trans)),})return
}
创建所需表单
forms/category.go
package formstype CategoryForm struct {Name string `form:"name" json:"name" binding:"required,min=3,max=20"`ParentCategory int32 `form:"parent" json:"parent"`Level int32 `form:"level" json:"level" binding:"required,oneof=1 2 3"`IsTab *bool `form:"is_tab" json:"is_tab" binding:"required"`
}type UpdateCategoryForm struct {Name string `form:"name" json:"name" binding:"required,min=3,max=20"`IsTab *bool `form:"is_tab" json:"is_tab"`
}
创建接口
所有接口:
package categoryimport ("context""encoding/json""github.com/gin-gonic/gin""go.uber.org/zap""google.golang.org/protobuf/types/known/emptypb""mxshop-api/goods-web/api""mxshop-api/goods-web/forms""mxshop-api/goods-web/global""mxshop-api/goods-web/proto""net/http""strconv"
)func List(ctx *gin.Context) {r, err := global.GoodsSrvClient.GetAllCategorysList(context.Background(), &emptypb.Empty{})if err != nil {api.HandleGrpcErrorToHttp(err, ctx)return}// 创建一个数组data := make([]interface{}, 0)err = json.Unmarshal([]byte(r.JsonData), &data) // 这里是逻辑写成这样了,所有的分级分类会以JSON的形式存储在r.JsonData 中,这里是对应的反解if err != nil {zap.S().Errorw("[List] 查询 【分类列表】 失败", err.Error())}ctx.JSON(http.StatusOK, data)
}func Detail(ctx *gin.Context) {id := ctx.Param("id")i, err := strconv.ParseInt(id, 10, 32)if err != nil {ctx.Status(http.StatusNotFound)return}reMap := make(map[string]interface{})subCategorys := make([]interface{}, 0)if r, err := global.GoodsSrvClient.GetSubCategory(context.Background(), &proto.CategoryListRequest{Id: int32(i),}); err != nil {api.HandleGrpcErrorToHttp(err, ctx)return} else {for _, value := range r.SubCategorys {subCategorys = append(subCategorys, map[string]interface{}{"id": value.Id,"name": value.Name,"level": value.Level,"parent_category": value.ParentCategory,"is_tab": value.IsTab,})}reMap["id"] = r.Info.IdreMap["name"] = r.Info.NamereMap["level"] = r.Info.LevelreMap["parent_category"] = r.Info.ParentCategoryreMap["sub_category"] = subCategorysctx.JSON(http.StatusOK, reMap)}return
}func New(ctx *gin.Context) {categoryForm := forms.CategoryForm{}if err := ctx.ShouldBindJSON(&categoryForm); err != nil {api.HandleValidatorError(ctx, err)return}rsp, err := global.GoodsSrvClient.CreateCategory(context.Background(), &proto.CategoryInfoRequest{Name: categoryForm.Name,IsTab: *categoryForm.IsTab,Level: categoryForm.Level,ParentCategory: categoryForm.ParentCategory,})if err != nil {api.HandleGrpcErrorToHttp(err, ctx)}request := make(map[string]interface{})request["id"] = rsp.Idrequest["name"] = rsp.Namerequest["parent"] = rsp.ParentCategoryrequest["level"] = rsp.Levelrequest["is_tab"] = rsp.IsTabctx.JSON(http.StatusOK, request)
}func Delete(ctx *gin.Context) {id := ctx.Param("id")i, err := strconv.ParseInt(id, 10, 32)if err != nil {ctx.Status(http.StatusNotFound)return}//1. 先查询出该分类写的所有子分类//2. 将所有的分类全部逻辑删除//3. 将该分类下的所有的商品逻辑删除_, err = global.GoodsSrvClient.DeleteCategory(context.Background(), &proto.DeleteCategoryRequest{Id: int32(i)})if err != nil {api.HandleGrpcErrorToHttp(err, ctx)return}ctx.Status(http.StatusOK)
}func Update(ctx *gin.Context) {categoryForm := forms.UpdateCategoryForm{}if err := ctx.ShouldBindJSON(&categoryForm); err != nil {api.HandleValidatorError(ctx, err)return}id := ctx.Param("id")i, err := strconv.ParseInt(id, 10, 32)if err != nil {ctx.Status(http.StatusNotFound)return}request := &proto.CategoryInfoRequest{Id: int32(i),Name: categoryForm.Name,}if categoryForm.IsTab != nil {request.IsTab = *categoryForm.IsTab}_, err = global.GoodsSrvClient.UpdateCategory(context.Background(), request)if err != nil {api.HandleGrpcErrorToHttp(err, ctx)return}ctx.Status(http.StatusOK)
}
添加 router
router/category.go
package routerimport ("github.com/gin-gonic/gin""mxshop-api/goods-web/api/category"
)func InitCategoryRouter(Router *gin.RouterGroup) {CategoryRouter := Router.Group("category"){CategoryRouter.GET("", category.List)CategoryRouter.DELETE("/:id", category.Delete)CategoryRouter.GET("/:id", category.Detail)CategoryRouter.POST("", category.New)CategoryRouter.PUT("/:id", category.Update)}
}
在Init 中添加category
initialize/router.go:
ApiGroup := Router.Group("/g/v1")router2.InitGoodsRouter(ApiGroup)router2.InitCategoryRouter(ApiGroup) // q添加router2.InitHealthCheckRouter(Router.Group(""))
轮播图接口
添加轮播图Form
创建文件 forms/banner.go
package formstype BannerForm struct {Image string `form:"image" json:"image" binding:"url"`Index int `form:"index" json:"index" binding:"required"`Url string `form:"url" json:"url" binding:"url"`
}
编写API
创建文件 api/banner/banner.go
package bannersimport ("context""github.com/gin-gonic/gin""google.golang.org/protobuf/types/known/emptypb""mxshop-api/goods-web/api""mxshop-api/goods-web/forms""mxshop-api/goods-web/global""mxshop-api/goods-web/proto""net/http""strconv"
)func List(ctx *gin.Context) {rsp, err := global.GoodsSrvClient.BannerList(context.Background(), &emptypb.Empty{})if err != nil {api.HandleGrpcErrorToHttp(err, ctx)return}result := make([]interface{}, 0)for _, value := range rsp.Data {reMap := make(map[string]interface{})reMap["id"] = value.IdreMap["index"] = value.IndexreMap["image"] = value.ImagereMap["url"] = value.Urlresult = append(result, reMap)}ctx.JSON(http.StatusOK, result)
}func New(ctx *gin.Context) {// 接收新增的信息bannerForm := forms.BannerForm{}if err := ctx.ShouldBindJSON(&bannerForm); err != nil {api.HandleValidatorError(ctx, err)return}rsp, err := global.GoodsSrvClient.CreateBanner(context.Background(), &proto.BannerRequest{Index: int32(bannerForm.Index),Image: bannerForm.Image,Url: bannerForm.Url,})if err != nil {api.HandleGrpcErrorToHttp(err, ctx)return}response := make(map[string]interface{})response["id"] = rsp.Idresponse["index"] = rsp.Indexresponse["url"] = rsp.Urlresponse["image"] = rsp.Imagectx.JSON(http.StatusOK, response)
}func Update(ctx *gin.Context) {bannerForm := forms.BannerForm{}if err := ctx.ShouldBindJSON(&bannerForm); err != nil {api.HandleValidatorError(ctx, err)return}id := ctx.Param("id")i, err := strconv.ParseInt(id, 10, 32)if err != nil {ctx.Status(http.StatusNotFound)return}_, err = global.GoodsSrvClient.UpdateBanner(context.Background(), &proto.BannerRequest{Id: int32(i),Index: int32(bannerForm.Index),Url: bannerForm.Url,})if err != nil {api.HandleGrpcErrorToHttp(err, ctx)return}ctx.Status(http.StatusOK)
}func Delete(ctx *gin.Context) {id := ctx.Param("id")i, err := strconv.ParseInt(id, 10, 32)if err != nil {ctx.Status(http.StatusNotFound)return}_, err = global.GoodsSrvClient.DeleteBanner(context.Background(), &proto.BannerRequest{Id: int32(i)})if err != nil {api.HandleGrpcErrorToHttp(err, ctx)return}ctx.JSON(http.StatusOK, "")
}
编写Router
创建文件 router/banner.go
package routerimport ("github.com/gin-gonic/gin""mxshop-api/goods-web/api/banners""mxshop-api/goods-web/middlewares"
)func InitBannerRouter(Router *gin.RouterGroup) {BannerRouter := Router.Group("banner"){BannerRouter.GET("", banners.List)BannerRouter.DELETE("/:id", middlewares.JWTAuth(), middlewares.IsAdminAuth(), banners.Delete)BannerRouter.POST("", middlewares.JWTAuth(), middlewares.IsAdminAuth(), banners.New)BannerRouter.PUT("/:id", middlewares.JWTAuth(), middlewares.IsAdminAuth(), banners.Update)}
}
将Router添加至InitRouter
ApiGroup := Router.Group("/g/v1")router2.InitGoodsRouter(ApiGroup)router2.InitCategoryRouter(ApiGroup)router2.InitBannerRouter(ApiGroup)router2.InitHealthCheckRouter(Router.Group(""))return Router
品牌接口
添加品牌表单
package formstype BrandForm struct {Name string `form:"name" json:"name" binding:"required,min=3,max=10"`Logo string `form:"logo" json:"logo" binding:"url"`
}type CategoryBrandForm struct {CategoryId int `form:"category_id" json:"category_id" binding:"required"`BrandId int `form:"brand_id" json:"brand_id" binding:"required"`
}
添加品牌API
注意这里的分页是不好的写法,正确的应该在service层中实现,所以这里不要参考,具体可以参考商品那里的接口
package brandsimport ("context""net/http""strconv""github.com/gin-gonic/gin""mxshop-api/goods-web/api""mxshop-api/goods-web/forms""mxshop-api/goods-web/global""mxshop-api/goods-web/proto"
)func BrandList(ctx *gin.Context) {pn := ctx.DefaultQuery("pn", "0")pnInt, _ := strconv.Atoi(pn)pSize := ctx.DefaultQuery("psize", "10")pSizeInt, _ := strconv.Atoi(pSize)rsp, err := global.GoodsSrvClient.BrandList(context.Background(), &proto.BrandFilterRequest{Pages: int32(pnInt),PagePerNums: int32(pSizeInt),})if err != nil {api.HandleGrpcErrorToHttp(err, ctx)return}result := make([]interface{}, 0)reMap := make(map[string]interface{})reMap["total"] = rsp.Totalfor _, value := range rsp.Data[pnInt : pnInt*pSizeInt+pSizeInt] {reMap := make(map[string]interface{})reMap["id"] = value.IdreMap["name"] = value.NamereMap["logo"] = value.Logoresult = append(result, reMap)}reMap["data"] = resultctx.JSON(http.StatusOK, reMap)
}func NewBrand(ctx *gin.Context) {brandForm := forms.BrandForm{}if err := ctx.ShouldBindJSON(&brandForm); err != nil {api.HandleValidatorError(ctx, err)return}rsp, err := global.GoodsSrvClient.CreateBrand(context.Background(), &proto.BrandRequest{Name: brandForm.Name,Logo: brandForm.Logo,})if err != nil {api.HandleGrpcErrorToHttp(err, ctx)return}request := make(map[string]interface{})request["id"] = rsp.Idrequest["name"] = rsp.Namerequest["logo"] = rsp.Logoctx.JSON(http.StatusOK, request)
}func DeleteBrand(ctx *gin.Context) {id := ctx.Param("id")i, err := strconv.ParseInt(id, 10, 32)if err != nil {ctx.Status(http.StatusNotFound)return}_, err = global.GoodsSrvClient.DeleteBrand(context.Background(), &proto.BrandRequest{Id: int32(i)})if err != nil {api.HandleGrpcErrorToHttp(err, ctx)return}ctx.Status(http.StatusOK)
}func UpdateBrand(ctx *gin.Context) {brandForm := forms.BrandForm{}if err := ctx.ShouldBindJSON(&brandForm); err != nil {api.HandleValidatorError(ctx, err)return}id := ctx.Param("id")i, err := strconv.ParseInt(id, 10, 32)if err != nil {ctx.Status(http.StatusNotFound)return}_, err = global.GoodsSrvClient.UpdateBrand(context.Background(), &proto.BrandRequest{Id: int32(i),Name: brandForm.Name,Logo: brandForm.Logo,})if err != nil {api.HandleGrpcErrorToHttp(err, ctx)return}ctx.Status(http.StatusOK)
}func GetCategoryBrandList(ctx *gin.Context) {id := ctx.Param("id")i, err := strconv.ParseInt(id, 10, 32)if err != nil {ctx.Status(http.StatusNotFound)return}rsp, err := global.GoodsSrvClient.GetCategoryBrandList(context.Background(), &proto.CategoryInfoRequest{Id: int32(i),})if err != nil {api.HandleGrpcErrorToHttp(err, ctx)return}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)
}func CategoryBrandList(ctx *gin.Context) {//所有的list返回的数据结构/*{"total": 100,"data":[{},{}]}*/rsp, err := global.GoodsSrvClient.CategoryBrandList(context.Background(), &proto.CategoryBrandFilterRequest{})if err != nil {api.HandleGrpcErrorToHttp(err, ctx)return}reMap := map[string]interface{}{"total": rsp.Total,}result := make([]interface{}, 0)for _, value := range rsp.Data {reMap := make(map[string]interface{})reMap["id"] = value.IdreMap["category"] = map[string]interface{}{"id": value.Category.Id,"name": value.Category.Name,}reMap["brand"] = map[string]interface{}{"id": value.Brand.Id,"name": value.Brand.Name,"logo": value.Brand.Logo,}result = append(result, reMap)}reMap["data"] = resultctx.JSON(http.StatusOK, reMap)
}func NewCategoryBrand(ctx *gin.Context) {categoryBrandForm := forms.CategoryBrandForm{}if err := ctx.ShouldBindJSON(&categoryBrandForm); err != nil {api.HandleValidatorError(ctx, err)return}rsp, err := global.GoodsSrvClient.CreateCategoryBrand(context.Background(), &proto.CategoryBrandRequest{CategoryId: int32(categoryBrandForm.CategoryId),BrandId: int32(categoryBrandForm.BrandId),})if err != nil {api.HandleGrpcErrorToHttp(err, ctx)return}response := make(map[string]interface{})response["id"] = rsp.Idctx.JSON(http.StatusOK, response)
}func UpdateCategoryBrand(ctx *gin.Context) {categoryBrandForm := forms.CategoryBrandForm{}if err := ctx.ShouldBindJSON(&categoryBrandForm); err != nil {api.HandleValidatorError(ctx, err)return}id := ctx.Param("id")i, err := strconv.ParseInt(id, 10, 32)if err != nil {ctx.Status(http.StatusNotFound)return}_, err = global.GoodsSrvClient.UpdateCategoryBrand(context.Background(), &proto.CategoryBrandRequest{Id: int32(i),CategoryId: int32(categoryBrandForm.CategoryId),BrandId: int32(categoryBrandForm.BrandId),})if err != nil {api.HandleGrpcErrorToHttp(err, ctx)return}ctx.Status(http.StatusOK)
}func DeleteCategoryBrand(ctx *gin.Context) {id := ctx.Param("id")i, err := strconv.ParseInt(id, 10, 32)if err != nil {ctx.Status(http.StatusNotFound)return}_, err = global.GoodsSrvClient.DeleteCategoryBrand(context.Background(), &proto.CategoryBrandRequest{Id: int32(i)})if err != nil {api.HandleGrpcErrorToHttp(err, ctx)return}ctx.JSON(http.StatusOK, "")
}
添加品牌Router
package routerimport ("github.com/gin-gonic/gin""mxshop-api/goods-web/api/brands"
)// 1. 商品的api接口开发完成
// 2. 图片的坑
func InitBrandRouter(Router *gin.RouterGroup) {BrandRouter := Router.Group("brands"){BrandRouter.GET("", brands.BrandList) // 品牌列表页BrandRouter.DELETE("/:id", brands.DeleteBrand) // 删除品牌BrandRouter.POST("", brands.NewBrand) //新建品牌BrandRouter.PUT("/:id", brands.UpdateBrand) //修改品牌信息}CategoryBrandRouter := Router.Group("categorybrands"){CategoryBrandRouter.GET("", brands.CategoryBrandList) // 类别品牌列表页CategoryBrandRouter.DELETE("/:id", brands.DeleteCategoryBrand) // 删除类别品牌CategoryBrandRouter.POST("", brands.NewCategoryBrand) //新建类别品牌CategoryBrandRouter.PUT("/:id", brands.UpdateCategoryBrand) //修改类别品牌CategoryBrandRouter.GET("/:id", brands.GetCategoryBrandList) //获取分类的品牌}
}
品牌路由导入路由表
ApiGroup := Router.Group("/g/v1")router2.InitGoodsRouter(ApiGroup)router2.InitCategoryRouter(ApiGroup)router2.InitBannerRouter(ApiGroup)router2.InitBrandRouter(ApiGroup)router2.InitHealthCheckRouter(Router.Group(""))
相关文章:
【Go】十八、http 调用服务的编写
http接口框架的搭建 这个http接口框架的搭建参考之前的全量搭建,这里是快速搭建的模式: 直接对已有的http模块进行复制修改,主要修改点在于 proto部分与api、router 部分,剩余的要针对进行修改模块名称。 接口的具体编写 在 a…...
提升数据洞察力:五款报表软件助力企业智能决策
概述 随着数据量的激增和企业对决策支持需求的提升,报表软件已经成为现代企业管理中不可或缺的工具。这些软件能够帮助企业高效处理数据、生成报告,并将数据可视化,从而推动更智能的决策过程。 1. 山海鲸报表 概述: 山海鲸报表…...
Materials Studio MS2020在linux系统上的安装包下载地址 支持centos Ubuntu rocky等系统
下载地址:MS2020-linux官方版下载丨最新版下载丨绿色版下载丨APP下载-123云盘 Materials Studio 2020是一款功能强大的材料科学计算模拟软件,以下是其详细介绍: 核心模块功能 CASTEP模块:采用平面波赝势方法,适用于周…...
ASP.NET MVC AJAX 文件上传
在ASP.NET MVC中实现文件上传功能,特别是在使用AJAX时,可以通过多种方式完成。以下是实现文件上传的几种常用方法,包括使用jQuery和原生AJAX。 方法1:使用jQuery的AJAX方法 1. 创建视图(View) 首先&#x…...
3.17 AI Agent 场景革命:解锁企业级应用的 15 个黄金赛道
AI Agent 场景革命:解锁企业级应用的 15 个黄金赛道 关键词:AI Agent 应用场景, 企业级智能体案例, 多模态 Agent 实现, 工具链自动化, 智能决策系统 1. 企业级 Agent 场景分类图谱 #mermaid-svg-UjUmmToEKigfdlFf {font-family:"trebuchet ms",verdana,arial,san…...
阿里云服务器宝塔终端如何创建fastadmin插件
1. 进入宝塔终端 2. cd / 进入根目录 3. FastAdmin 可以通过命令行创建一个插件,首先我们将工作目录切换到我们的项目根目录,也就是think文件所在的目录。 cd /var/www/yoursite/ 4.然后我们在命令行输入 php think addon -a mydemo -c create …...
待完成-swig将c语言程序转为python可用示例
待完成-swig将c语言程序转为python可用示例 deepseek 使用 SWIG(Simplified Wrapper and Interface Generator)可以将 C 语言程序库连接为 Python 可用的模块。以下是基本步骤: 1. 安装 SWIG 首先,确保你已经安装了 SWIG。你可以…...
【语音编解码】常用的基于神经网络的语音编解码方案对比
引言 随着实时通信与多媒体应用的爆炸式增长,传统语音编解码技术正面临带宽效率与音质保真的双重挑战。近年来,基于深度学习的神经编解码器突破性地将端到端架构、动态码率控制与可解释信号处理相结合,在3kbps以下超低码率场景仍能保持自然语…...
DeepSeek行业应用实践报告-智灵动力【112页PPT全】
DeepSeek(深度搜索)近期引发广泛关注并成为众多企业/开发者争相接入的现象,主要源于其在技术突破、市场需求适配性及生态建设等方面的综合优势。以下是关键原因分析: 一、技术核心优势 开源与低成本 DeepSeek基于开源架构…...
a_init: Unable to get log name. Retval:[-4]是什么故障
突然 接到监控告警 aix数据库内存使用超过阈值,请分析 先看内存使用吧 topas中能看到comp内存使用79%,非计算9% 看看哪个进程占用多呢 占用内存最高的20个进程(aix) ps aux |head -1 ; ps aux|sort -rn 4 |head -20看到rbal进程占用11%,比…...
利用node.js搭配express框架写后端接口(一)
Node.js 凭借其高效的非阻塞 I/O 操作、事件驱动架构以及轻量级的特点,成为了开发高性能服务器应用的热门选择。Express 框架作为 Node.js 上最流行的 Web 应用框架之一,以其简洁的 API 和丰富的中间件生态系统,极大地简化了 Web 后端开发流程…...
CentOS中shell脚本对多台机器执行下载安装
1.建立免密ssh连接 详情见这篇: CentOS建立ssh免密连接(含流程剖析)-CSDN博客 2.脚本编写 我这里只是简单写了个demo进行演示,如果服务器很多可以先暂存成文件再逐行读取host进行连接并执行命令 用node1去ssh连接node2和node…...
Go 语言内存池 (`sync.Pool`) 深度解析
Go 语言内存池 (sync.Pool) 深度解析 在高并发和性能敏感的应用中,频繁的内存分配和释放会带来显著的性能开销,并增加垃圾回收(GC)的压力。Go 语言通过 sync.Pool 提供了一种高效的对象复用机制,能够显著减少内存分配…...
深入剖析:自定义实现C语言中的atoi函数
在C语言的标准库中, atoi 函数是一个非常实用的工具,它能够将字符串形式的数字转换为对应的整数。然而,当我们深入探究其实现原理时,会发现其中蕴含着许多有趣的编程技巧和细节。本文将详细讲解如何自定义实现一个类似 atoi 功能的…...
Flutter 学习之旅 之 flutter 在 Android 端读取相册图片显示
Flutter 学习之旅 之 flutter 在 Android 端读取相册图片显示 目录 Flutter 学习之旅 之 flutter 在 Android 端读取相册图片显示 一、简单介绍 二、简单介绍 image_picker 三、安装 image_picker 四、简单案例实现 五、关键代码 代码说明: 一、简单介绍 Fl…...
数据结构秘籍(一)线性数据结构
1.数组 数组(Array)是一种很常见的数据结构。它由相同类型的元素(element)组成,并且是使用一块连续的内存来存储。 我们直接可以利用元素的索引(index)计算出该元素对应的存储地址。 数组的特…...
推荐律师事务管理系统(SpringCloud+mysql+rocketmq+deepseek)
1.深圳慧钛科技有限公司成立于2024年7月24日,官网地址:深圳慧钛律师事务管理系统(官网)-案件管理系统-律所档案管理-律所管理软件-律师办案系统-电子签章-律所印章-律师办公软件、律师办公系统、律所OA 。系统访问地址:深圳慧钛律…...
mysql怎样优化where like ‘%字符串%‘这种模糊匹配的慢sql
一 问题描述 工作中经常遇到这种模糊匹配的慢sql: select * from 表名 where 字段 like %字符串%; 由于前面有%,导致无法走该字段上的索引。 二 解决办法 ① 给该字段创建一个全文索引 CREATE FULLTEXT INDEX 索引名 ON 表名 (字段名); ② 改写sq…...
SpringSecurity基于JWT实现Token的处理
前面介绍了手写单点登录和JWT的应用,本文结合SpringSecurity来介绍下在SpringBoot项目中基于SpringSecurity作为认证授权框架的情况下如何整合JWT来实现Token的处理。 一、认证思路分析 SpringSecurity主要是通过过滤器来实现功能的!我们要找到SpringSecurity实现认证和校验…...
让AI“看见”光影变幻!华为云专利解锁动态光源渲染新境界
华为云计算技术有限公司(申请人,申请号:202311653495.3)通过一项创新专利,首次实现隐式对象模型与显式渲染管线深度融合,让动态光源下的图像渲染真实度与灵活性兼得! 一、技术深度解析 技术背景…...
Linux(centos)系统安装部署MySQL8.0数据库(GLIBC版本)
前言 MySQL 是一款开源的关系型数据库管理系统(RDBMS),主要用于结构化数据的存储、管理和检索。 一、检查环境 安装前检查服务器glibc版本,下载对应版本包 rpm -qa | grep glibc mysql安装包及依赖包已整理好,…...
Redis缓存一致性难题:如何让数据库和缓存不“打架”?
标题:Redis缓存一致性难题:如何让数据库和缓存不“打架”?(附程序员脱发指南) 导言:当数据库和缓存成了“异地恋” 想象一下:你刚在美团下单了一份麻辣小龙虾,付款后刷新页面&#…...
【R包】pathlinkR转录组数据分析和可视化利器
介绍 通常情况下,基因表达研究如微阵列和RNA-Seq会产生数百到数千个差异表达基因(deg)。理解如此庞大的数据集的生物学意义变得非常困难,尤其是在分析多个条件和比较的情况下。该软件包利用途径富集和蛋白-蛋白相互作用网络&…...
PyCharm 的使用 + PyCharm快捷键 + 切换中文界面
2025 - 02 - 27 - 第 62 篇 Author: 郑龙浩 / 仟濹 【PyCharm的使用】 文章目录 如何使用Pycharm1 新建工程,新建 .py 文件,运行2 常用快捷键3 其他快捷键 - DeepSeek 总结如下**代码编辑****导航与定位****查找与替换****运行与调试****代码重构****其…...
1.68M 免安装多格式图片批量转 webp 无广告软件推荐
软件介绍 今天要给大家分享一款超实用的图片处理工具,它能实现多格式图片向 webp 格式的转换,无论是 jpg、png、tif、gif 还是 webp 格式自身的图片,都能批量且借助多线程技术进行转换。 直接打开就能用,体积小巧,仅 …...
总结gcc与msvc在标准库实现上的不同
1. std::string::data()的返回类型区别 在C17以及之前的标准中,std::string::data()仅有一个返回类型const char *,MSVC遵守了这个规定。而GCC很早就有非标准扩展,重载了一个 char *data() noexcept;C20标准引入了这个非标准扩展。...
《Qt窗口动画实战:Qt实现呼吸灯效果》
Qt窗口动画实战:Qt实现呼吸灯效果 在嵌入式设备或桌面应用中,呼吸灯效果是一种常见且优雅的UI动画,常用于指示系统状态或吸引用户注意。本文将介绍如何使用Qt动画框架实现平滑的呼吸灯效果。 一、实现原理 利用Qt自带的动画框架来实现&…...
Rider 安装包 绿色版 Win/Mac/Linux 适合.NET和游戏开发者使用 2025全栈开发终极指南:从零配置到企业级实战
下载链接: https://pan.baidu.com/s/1cfkJf6Zgxc1XfYrVpwtHkA?pwd1234 导语:JetBrains Rider以跨平台支持率100%、深度.NET集成和智能代码分析能力,成为2025年全栈开发者的首选工具。本文涵盖环境配置、核心功能、框架集成、性能调优、团队…...
CVE-2025-1094: 通过 WebSocket 的 SQL 注入到 RCE
该存储库包含一个针对 CVE-2025-1094 的概念验证(PoC)漏洞利用,该漏洞存在于 PostgreSQL 中,允许通过 WebSocket 劫持将 SQL 注入(SQLi)攻击升级为远程代码执行(RCE)。 概述 该漏洞利用 PostgreSQL 中的 SQL 注入漏洞,注入恶意代码读取敏感文件(如 /etc/passwd),…...
详解Tomcat下载安装以及IDEA配置Tomcat(2023最新)
目录 步骤一:首先确认自己是否已经安装JDK步骤二:下载安装Tomcat步骤三:Tomcat配置环境变量步骤四:验证Tomcat配置是否成功步骤五:为IDEA配置Tomcat 步骤一:首先确认自己是否已经安装JDK jdk各版本通用安…...
