• 20、商品微服务-web层实现


    一、快速新建goods_web服务

    1 - 新建项目goods_web

    • 参考之前的user_web服务
      • 将项目拷贝过来,然后将不需要的删除,也可以直接跳过,下载之后的完整源码;以下介绍一些关键的修改
    • 使用目录的方式替换import:将user_web替换为goods_web
    • goods_web/api/goods/api_goods.go:新增一个List实现,不需要其他逻辑
      在这里插入图片描述
    • goods_web/initialize/init_router.go

    在这里插入图片描述

    • nacos的namespace修改:这里以goods_web/config_zsz.yaml为例,另外2个自行修改
      在这里插入图片描述
    host: '192.168.124.51'
    port: 8848
    namespace: 'b2f00e3b-4e22-4e87-8129-9c02b5f1220d'
    user: 'nacos'
    password: 'nacos'
    dataid: 'goods_web.json'
    group: 'zsz'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 克隆nacos配置:克隆user_web.json配置为goods_web.json,注意修改目标空间、DataId、Group
      在这里插入图片描述
    {
      "name": "goods_web",
      "port": 8082,
      "goods_srv": {
        "name": "goods_srv"
      },
      "jwt": {
        "key": "VYLDYq3&hGWjWqF$K1ih"
      },
      "consul": {
        "host": "192.168.124.51",
        "port": 8500
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    2 - 快速测试

    • 启动商品web服务:YApi测试200成功
      在这里插入图片描述
    • YApi新增商品测试接口
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述

    二、商品列表接口

    • goods_web/api/goods/api_goods.go:实现List
    func List(ctx *gin.Context) {
    	fmt.Println("商品列表")
    	//商品的列表
    	 根据前端的请求参数构造request对象
    	request := &proto.GoodsFilterRequest{}
    	priceMin := ctx.DefaultQuery("pmin", "0")
    	priceMinInt, _ := 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 = keywords
    	brandId := ctx.DefaultQuery("b", "0")
    	brandIdInt, _ := strconv.Atoi(brandId)
    	request.Brand = int32(brandIdInt)
    
    	请求商品的service服务
    	r, err := global.GoodsSrvClient.GoodsList(context.WithValue(context.Background(), "ginContext", ctx), request)
    	if err != nil {
    		zap.S().Errorw("[List] 查询 【商品列表】失败")
    		HandleGrpcErrorToHttp(err, ctx)
    		return
    	}
    
    	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"] = goodsList
    
    	ctx.JSON(http.StatusOK, reMap)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • YApi设置
      在这里插入图片描述

    在这里插入图片描述
    在这里插入图片描述


    三、goods_web服务注册与注销

    • 需求分析:提高注册中心的扩展性,比如之后不想用consul,想换nacos、etcd等
    • nacos配置
      在这里插入图片描述
    {
      "host": "192.168.78.1",
      "name": "goods_web",
      "port": 8082,
      "tags": ["mxshop","imooc","bobby","goods","web"],
      "goods_srv": {
        "name": "goods_srv"
      },
      "jwt": {
        "key": "VYLDYq3&hGWjWqF$K1ih"
      },
      "consul": {
        "host": "192.168.78.131",
        "port": 8500
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • goods_web/config/config.go:ServerConfig添加本机的host和Tags
    type ServerConfig struct {
    	Name         string         `mapstructure:"name" json:"name"`
    	Host         string         `mapstructure:"host" json:"host"`
    	Tags         []string       `mapstructure:"tags" json:"tags"`
    	Port         int            `mapstructure:"port" json:"port"`
    	GoodsSrvInfo GoodsSrvConfig `mapstructure:"goods_srv" json:"goods_srv"`
    	JWTInfo      JWTConfig      `mapstructure:"jwt" json:"jwt"`
    	ConsulInfo   ConsulConfig   `mapstructure:"consul" json:"consul"`
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • goods_web/utils/register/consul/reg_consul.go:封装consul的注册与注销
    package consul
    
    import (
    	"fmt"
    
    	"github.com/hashicorp/consul/api"
    )
    
    type Registry struct {
    	Host string
    	Port int
    }
    
    type RegistryClient interface {
    	Register(address string, port int, name string, tags []string, id string) error
    	DeRegister(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)
    
    	client, err := api.NewClient(cfg)
    	if err != nil {
    		panic(err)
    	}
    	//生成对应的检查对象
    	check := &api.AgentServiceCheck{
    		HTTP:                           fmt.Sprintf("http://%s:%d/health", address, port),
    		Timeout:                        "5s",
    		Interval:                       "5s",
    		DeregisterCriticalServiceAfter: "10s",
    	}
    
    	//生成注册对象
    	registration := new(api.AgentServiceRegistration)
    	registration.Name = name
    	registration.ID = id
    	registration.Port = port
    	registration.Tags = tags
    	registration.Address = address
    	registration.Check = check
    
    	err = client.Agent().ServiceRegister(registration)
    	if err != nil {
    		panic(err)
    	}
    	return nil
    }
    
    func (r *Registry) DeRegister(serviceId string) error {
    	cfg := api.DefaultConfig()
    	cfg.Address = fmt.Sprintf("%s:%d", r.Host, r.Port)
    
    	client, err := api.NewClient(cfg)
    	if err != nil {
    		return err
    	}
    	err = client.Agent().ServiceDeregister(serviceId)
    	return err
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • goods_web/main.go
    package main
    
    import (
    	"fmt"
    	"github.com/satori/go.uuid"
    	"github.com/spf13/viper"
    	"go.uber.org/zap"
    	"os"
    	"os/signal"
    	"syscall"
    
    	"web_api/goods_web/global"
    	"web_api/goods_web/initialize"
    	"web_api/goods_web/utils"
    	"web_api/goods_web/utils/register/consul"
    )
    
    func main() {
    	//1. 初始化logger
    	initialize.InitLogger()
    	//2. 初始化配置文件
    	initialize.InitConfig()
    	//3. 初始化routers
    	Router := initialize.Routers()
    	//4. 初始化翻译
    	if err := initialize.InitTrans("zh"); err != nil {
    		panic(err)
    	}
    
    	//5. 初始化srv的连接
    	initialize.InitSrvConn()
    
    	viper.AutomaticEnv()
    	//如果是本地开发环境端口号固定,线上环境启动获取端口号
    	flag := viper.GetInt("DEV_CONFIG")
    	if flag > 4 { // 1=zsz  2=comp  3=home
    		port, err := utils.GetFreePort()
    		if err == nil {
    			global.ServerConfig.Port = port
    		}
    	}
    
    	registerClient := consul.NewRegistryClient(global.ServerConfig.ConsulInfo.Host, global.ServerConfig.ConsulInfo.Port)
    	serviceId := fmt.Sprintf("%s", uuid.NewV4())
    	err := registerClient.Register(global.ServerConfig.Host, global.ServerConfig.Port, global.ServerConfig.Name, global.ServerConfig.Tags, serviceId)
    	if err != nil {
    		zap.S().Panic("服务注册失败:", err.Error())
    	}
    	zap.S().Debugf("启动服务器, 端口: %d", global.ServerConfig.Port)
    	go func() {
    		if err := Router.Run(fmt.Sprintf(":%d", global.ServerConfig.Port)); err != nil {
    			zap.S().Panic("启动失败:", err.Error())
    		}
    	}()
    	//接收终止信号
    	quit := make(chan os.Signal)
    	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
    	<-quit
    	if err = registerClient.DeRegister(serviceId); err != nil {
    		zap.S().Info("注销失败:", err.Error())
    	} else {
    		zap.S().Info("注销成功:")
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65

    四、user_web服务注册与注销

    • user_web/config/config.go
    type ServerConfig struct {
    	Name        string        `mapstructure:"name" json:"name"`
    	Host        string        `mapstructure:"host" json:"host"`
    	Tags        []string      `mapstructure:"tags" json:"tags"`
    	Port        int           `mapstructure:"port" json:"port"`
    	UserSrvInfo UserSrvConfig `mapstructure:"user_srv" json:"user_srv"`
    	JWTInfo     JWTConfig     `mapstructure:"jwt" json:"jwt"`
    	AliSmsInfo  AliSmsConfig  `mapstructure:"sms" json:"sms"`
    	RedisInfo   RedisConfig   `mapstructure:"redis" json:"redis"`
    	ConsulInfo  ConsulConfig  `mapstructure:"consul" json:"consul"`
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • user_web/utils/register/consul/reg_consul.go
    package consul
    
    import (
    	"fmt"
    
    	"github.com/hashicorp/consul/api"
    )
    
    type Registry struct {
    	Host string
    	Port int
    }
    
    type RegistryClient interface {
    	Register(address string, port int, name string, tags []string, id string) error
    	DeRegister(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)
    
    	client, err := api.NewClient(cfg)
    	if err != nil {
    		panic(err)
    	}
    	//生成对应的检查对象
    	check := &api.AgentServiceCheck{
    		HTTP:                           fmt.Sprintf("http://%s:%d/health", address, port),
    		Timeout:                        "5s",
    		Interval:                       "5s",
    		DeregisterCriticalServiceAfter: "10s",
    	}
    
    	//生成注册对象
    	registration := new(api.AgentServiceRegistration)
    	registration.Name = name
    	registration.ID = id
    	registration.Port = port
    	registration.Tags = tags
    	registration.Address = address
    	registration.Check = check
    
    	err = client.Agent().ServiceRegister(registration)
    	if err != nil {
    		panic(err)
    	}
    	return nil
    }
    
    func (r *Registry) DeRegister(serviceId string) error {
    	cfg := api.DefaultConfig()
    	cfg.Address = fmt.Sprintf("%s:%d", r.Host, r.Port)
    
    	client, err := api.NewClient(cfg)
    	if err != nil {
    		return err
    	}
    	err = client.Agent().ServiceDeregister(serviceId)
    	return err
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • user_web/main.go
    package main
    
    import (
    	"fmt"
    	uuid "github.com/satori/go.uuid"
    	"github.com/spf13/viper"
    	"os"
    	"os/signal"
    	"syscall"
    	"web_api/user_web/global"
    	"web_api/user_web/initialize"
    	"web_api/user_web/utils"
    	"web_api/user_web/utils/register/consul"
    
    	"github.com/gin-gonic/gin/binding"
    	ut "github.com/go-playground/universal-translator"
    	"github.com/go-playground/validator/v10"
    	"go.uber.org/zap"
    
    	myvalidator "web_api/user_web/validator"
    )
    
    func main() {
    	//1. 初始化logger
    	initialize.InitLogger()
    	//2. 初始化配置文件
    	initialize.InitConfig()
    	//3. 初始化routers
    	Router := initialize.Routers()
    	//4. 初始化翻译
    	if err := initialize.InitTrans("zh"); err != nil {
    		panic(err)
    	}
    
    	//5. 初始化srv的连接
    	initialize.InitSrvConn()
    
    	viper.AutomaticEnv()
    	//如果是本地开发环境端口号固定,线上环境启动获取端口号
    	flag := viper.GetInt("DEV_CONFIG")
    	if flag > 4 { // 1=zsz  2=comp  3=home
    		port, err := utils.GetFreePort()
    		if err == nil {
    			global.ServerConfig.Port = port
    		}
    	}
    
    	//注册验证器
    	if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
    		_ = v.RegisterValidation("mobile", myvalidator.ValidateMobile)
    		_ = v.RegisterTranslation("mobile", global.Trans, func(ut ut.Translator) error {
    			return ut.Add("mobile", "{0} 非法的手机号码!", true) // see universal-translator for details
    		}, func(ut ut.Translator, fe validator.FieldError) string {
    			t, _ := ut.T("mobile", fe.Field())
    			return t
    		})
    	}
    
    	//服务注册
    	registerClient := consul.NewRegistryClient(global.ServerConfig.ConsulInfo.Host, global.ServerConfig.ConsulInfo.Port)
    	serviceId := fmt.Sprintf("%s", uuid.NewV4())
    	err := registerClient.Register(global.ServerConfig.Host, global.ServerConfig.Port, global.ServerConfig.Name, global.ServerConfig.Tags, serviceId)
    	if err != nil {
    		zap.S().Panic("服务注册失败:", err.Error())
    	}
    
    	/*
    		1. S()可以获取一个全局的sugar,可以让我们自己设置一个全局的logger
    		2. 日志是分级别的,debug, info , warn, error, fetal
    			debug最低,fetal最高,如果配置成info,所有比info低的都不会输出
    			NewProduction默认日志级别为info
    			NewDevelopment默认日志级别为debug
    		3. S函数和L函数很有用, 提供了一个全局的安全访问logger的途径
    	*/
    	zap.S().Debugf("启动服务器, 端口: %d", global.ServerConfig.Port)
    	if err := Router.Run(fmt.Sprintf(":%d", global.ServerConfig.Port)); err != nil {
    		zap.S().Panic("启动失败:", err.Error())
    	}
    
    	//接收终止信号
    	quit := make(chan os.Signal)
    	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
    	<-quit
    	if err = registerClient.DeRegister(serviceId); err != nil {
    		zap.S().Info("注销失败:", err.Error())
    	} else {
    		zap.S().Info("注销成功:")
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90

    五、goods_api接口实现

    1 - 添加商品接口

    • goods_web/forms/form_goods.go
    type GoodsForm struct {
    	Name        string   `form:"name" json:"name" binding:"required,min=2,max=100"`
    	GoodsSn     string   `form:"goods_sn" json:"goods_sn" binding:"required,min=2,lt=20"`
    	Stocks      int32    `form:"stocks" json:"stocks" binding:"required,min=1"`
    	CategoryId  int32    `form:"category" json:"category" binding:"required"`
    	MarketPrice float32  `form:"market_price" json:"market_price" binding:"required,min=0"`
    	ShopPrice   float32  `form:"shop_price" json:"shop_price" binding:"required,min=0"`
    	GoodsBrief  string   `form:"goods_brief" json:"goods_brief" binding:"required,min=3"`
    	Images      []string `form:"images" json:"images" binding:"required,min=1"`
    	DescImages  []string `form:"desc_images" json:"desc_images" binding:"required,min=1"`
    	ShipFree    *bool    `form:"ship_free" json:"ship_free" binding:"required"`
    	FrontImage  string   `form:"front_image" json:"front_image" binding:"required,url"`
    	Brand       int32    `form:"brand" json:"brand" binding:"required"`
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • goods_web/router/router_goods.go
    package router
    
    import (
    	"github.com/gin-gonic/gin"
    	"web_api/goods_web/api/goods"
    )
    
    func InitGoodsRouter(Router *gin.RouterGroup) {
    	GoodsRouter := Router.Group("goods")
    	{
    		GoodsRouter.GET("", goods.List) //商品列表
    		//GoodsRouter.POST("", middlewares.JWTAuth(), middlewares.IsAdminAuth(), goods.New) //该接口需要管理员权限
    		GoodsRouter.POST("", goods.New) // 我们测试先将这2个接口权限关闭掉
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • goods_web/api/goods/api_goods.go
    func New(ctx *gin.Context) {
    	goodsForm := forms.GoodsForm{}
    	if err := ctx.ShouldBindJSON(&goodsForm); err != nil {
    		HandleValidatorError(ctx, err)
    		return
    	}
    	goodsClient := global.GoodsSrvClient
    	rsp, 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,
    		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)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • YApi
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述

    2 - 获取商品详情

    • goods_web/router/router_goods.go:添加路由
    package router
    
    import (
    	"github.com/gin-gonic/gin"
    	"web_api/goods_web/api/goods"
    )
    
    func InitGoodsRouter(Router *gin.RouterGroup) {
    	GoodsRouter := Router.Group("goods")
    	{
    		GoodsRouter.GET("", goods.List) //商品列表
    		//GoodsRouter.POST("", middlewares.JWTAuth(), middlewares.IsAdminAuth(), goods.New) //该接口需要管理员权限
    		GoodsRouter.POST("", goods.New)       // 我们测试先将这2个接口权限关闭掉
    		GoodsRouter.GET("/:id", goods.Detail) //获取商品的详情
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • goods_web/api/goods/api_goods.go
    func Detail(ctx *gin.Context) {
    	id := ctx.Param("id")
    	i, err := strconv.ParseInt(id, 10, 32)
    	if err != nil {
    		ctx.Status(http.StatusNotFound)
    		return
    	}
    
    	r, err := global.GoodsSrvClient.GetGoodsDetail(context.WithValue(context.Background(), "ginContext", ctx), &proto.GoodInfoRequest{
    		Id: int32(i),
    	})
    	if err != nil {
    		HandleGrpcErrorToHttp(err, ctx)
    		return
    	}
    
    	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,
    		"ctegory": 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)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    3 - 商品删除、更新

    • goods_web/router/router_goods.go:添加路由
    package router
    
    import (
    	"github.com/gin-gonic/gin"
    	"web_api/goods_web/api/goods"
    )
    
    func InitGoodsRouter(Router *gin.RouterGroup) {
    	GoodsRouter := Router.Group("goods")
    	{
    		GoodsRouter.GET("", goods.List) //商品列表
    		//GoodsRouter.POST("", middlewares.JWTAuth(), middlewares.IsAdminAuth(), goods.New) //该接口需要管理员权限
    		GoodsRouter.POST("", goods.New)       // 我们测试先将这2个接口权限关闭掉
    		GoodsRouter.GET("/:id", goods.Detail) //获取商品的详情
    		//GoodsRouter.DELETE("/:id",middlewares.JWTAuth(), middlewares.IsAdminAuth(), goods.Delete) //删除商品
    		GoodsRouter.DELETE("/:id", goods.Delete)     // 我们测试先将这2个接口权限关闭掉
    		GoodsRouter.GET("/:id/stocks", goods.Stocks) //获取商品的库存
    		//GoodsRouter.PATCH("/:id",middlewares.JWTAuth(), middlewares.IsAdminAuth(), goods.UpdateStatus)
    		GoodsRouter.PATCH("/:id", goods.UpdateStatus)
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • goods_web/forms/form_goods.go
    package router
    
    import (
    	"github.com/gin-gonic/gin"
    	"web_api/goods_web/api/goods"
    )
    
    func InitGoodsRouter(Router *gin.RouterGroup) {
    	GoodsRouter := Router.Group("goods")
    	{
    		GoodsRouter.GET("", goods.List) //商品列表
    		//GoodsRouter.POST("", middlewares.JWTAuth(), middlewares.IsAdminAuth(), goods.New) //该接口需要管理员权限
    		GoodsRouter.POST("", goods.New)       // 我们测试先将这2个接口权限关闭掉
    		GoodsRouter.GET("/:id", goods.Detail) //获取商品的详情
    		//GoodsRouter.DELETE("/:id",middlewares.JWTAuth(), middlewares.IsAdminAuth(), goods.Delete) //删除商品
    		GoodsRouter.DELETE("/:id", goods.Delete)     // 我们测试先将这2个接口权限关闭掉
    		GoodsRouter.GET("/:id/stocks", goods.Stocks) //获取商品的库存
    		//GoodsRouter.PUT("/:id",middlewares.JWTAuth(), middlewares.IsAdminAuth(), goods.Update)
    		GoodsRouter.PUT("/:id", goods.Update)
    		//GoodsRouter.PATCH("/:id",middlewares.JWTAuth(), middlewares.IsAdminAuth(), goods.UpdateStatus)
    		GoodsRouter.PATCH("/:id", goods.UpdateStatus)
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • goods_web/api/goods/api_goods.go
    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 {
    		HandleGrpcErrorToHttp(err, ctx)
    		return
    	}
    	ctx.Status(http.StatusOK)
    	return
    }
    
    func Stocks(ctx *gin.Context) {
    	id := ctx.Param("id")
    	_, err := strconv.ParseInt(id, 10, 32)
    	if err != nil {
    		ctx.Status(http.StatusNotFound)
    		return
    	}
    
    	//TODO 商品的库存
    	return
    }
    
    func UpdateStatus(ctx *gin.Context) {
    	goodsStatusForm := forms.GoodsStatusForm{}
    	if err := ctx.ShouldBindJSON(&goodsStatusForm); err != nil {
    		HandleValidatorError(ctx, err)
    		return
    	}
    
    	id := ctx.Param("id")
    	i, err := strconv.ParseInt(id, 10, 32)
    	if _, err = global.GoodsSrvClient.UpdateGoods(context.Background(), &proto.CreateGoodsInfo{
    		Id:     int32(i),
    		IsHot:  *goodsStatusForm.IsHot,
    		IsNew:  *goodsStatusForm.IsNew,
    		OnSale: *goodsStatusForm.OnSale,
    	}); err != nil {
    		HandleGrpcErrorToHttp(err, ctx)
    		return
    	}
    	ctx.JSON(http.StatusOK, gin.H{
    		"msg": "修改成功",
    	})
    }
    
    func Update(ctx *gin.Context) {
    	goodsForm := forms.GoodsForm{}
    	if err := ctx.ShouldBindJSON(&goodsForm); err != nil {
    		HandleValidatorError(ctx, err)
    		return
    	}
    
    	id := ctx.Param("id")
    	i, err := strconv.ParseInt(id, 10, 32)
    	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,
    		ShipFree:        *goodsForm.ShipFree,
    		Images:          goodsForm.Images,
    		DescImages:      goodsForm.DescImages,
    		GoodsFrontImage: goodsForm.FrontImage,
    		CategoryId:      goodsForm.CategoryId,
    		BrandId:         goodsForm.Brand,
    	}); err != nil {
    		HandleGrpcErrorToHttp(err, ctx)
    		return
    	}
    	ctx.JSON(http.StatusOK, gin.H{
    		"msg": "更新成功",
    	})
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82

    4 - 商品分类接口

    • goods_web/router/router_category.go
    package router
    
    import (
    	"github.com/gin-gonic/gin"
    	"web_api/goods_web/api/category"
    )
    
    func InitCategoryRouter(Router *gin.RouterGroup) {
    	CategoryRouter := Router.Group("categorys")
    	{
    		CategoryRouter.GET("", category.List)          // 商品类别列表页
    		CategoryRouter.DELETE("/:id", category.Delete) // 删除分类
    		CategoryRouter.GET("/:id", category.Detail)    // 获取分类详情
    		CategoryRouter.POST("", category.New)          //新建分类
    		CategoryRouter.PUT("/:id", category.Update)    //修改分类信息
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • goods_web/forms/form_category.go
    package forms
    
    type 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"`
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • goods_web/api/category/category.go:代码太多,就贴下方法图片,具体查看完整源码
      在这里插入图片描述

    这里总结下套路:
    1、先添加form表单的struct
    2、实现API方法
    3、添加router

    5 - 轮播图接口

    • goods_web/forms/form_banner.go
    package forms
    
    type 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"`
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • goods_web/api/banners/api_banner.go
      在这里插入图片描述
    • goods_web/router/router_banner.go
    package router
    
    import (
    	"github.com/gin-gonic/gin"
    	"web_api/goods_web/api/banners"
    )
    
    func InitBannerRouter(Router *gin.RouterGroup) {
    	BannerRouter := Router.Group("banners")
    	{
    		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)    //修改轮播图信息
    		BannerRouter.DELETE("/:id", banners.Delete) // 删除轮播图
    		BannerRouter.POST("", banners.New)          //新建轮播图
    		BannerRouter.PUT("/:id", banners.Update)    //修改轮播图信息
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    6 - 品牌接口

    • goods_web/api/brands/api_brand.go

    在这里插入图片描述

    • goods_web/forms/form_brand.go
    package forms
    
    type 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"`
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • goods_web/router/router_brand.go
    package router
    
    import (
    	"github.com/gin-gonic/gin"
    	"web_api/goods_web/api/brands"
    )
    
    // InitBrandRouter
    //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)   //获取分类的品牌
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    六、完整源码

    • 完整源码下载mxshop_srvsV8.3.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配置集导入文件

    附:YApi快速添加接口方法

    • 这里以轮播图举例
      • 一开始我们还没有轮播图的数据,这里有4个接口
        在这里插入图片描述
    • 我们可以先实现添加轮播图的接口

    在这里插入图片描述

    在这里插入图片描述

    • 反复编辑 -> 运行几次:数据库数据就有了
      在这里插入图片描述
    • 这时候我们再查询轮播图列表数据就有了
      在这里插入图片描述
    • 将返回的body中的json数据复制下,编辑接口-轮播图列表
      • 在返回数据设置这选择导入,粘贴复制的json数据,就会自动帮我们生成返回数据的json
      • 而我们需要做的仅仅就是修改下备注就可以了
        在这里插入图片描述
  • 相关阅读:
    python 基本概念整理
    SQLite常用语句
    Python 爬虫使用 Selenium 如何在 WebElement 获得属性
    ps如何换背景颜色,自学ps软件photoshop2022,3种不同的方式笔记记录
    uniapp<button>按钮去掉边框
    python+nodejs+php+springboot+vue 法律知识分享科普系统平台
    Flutter配置Android SDK路径
    从零开始探索C语言(十一)----共用体和位域
    TiDB 工具下载
    宠物行业如何进行软文营销
  • 原文地址:https://blog.csdn.net/qq23001186/article/details/126111045