用户服务注册发现、负载均衡、配置中心

三用户服务注册发现、负载均衡、配置中心

1.注册中心consual
1.1服务注册和注销
1.1.1注册接口

/agent/service/register

POSTMAN上输入以下地址(
127.0.0.1:8500/v1/agent/service/register
请求方式:PUT
{
	"Name":"mxshop-web",
	"ID":"mxshop-web",
	"Tags":["mxshop","bobby","imooc"],
	"Address":"127.0.0.1",
	"Port":50051
}
1.1.2服务注消

/agent/service/deregister/:service_id

在POSTMAN上输入以下地址(
127.0.0.1:8500/v1/agent/service/deregister/mxshop-web
1.2为grpc服务添加viper和zap
1.2.1添加MYSQL配置

在user_srv/config/config.go 添加mysql配置

package config

type MysqlConfig struct {
   Host     string `mapstructure:"host" json:"host"`
   Port     int    `mapstructure:"port" json:"port"`
   Name     string `mapstructure:"db" json:"db"`
   User     string `mapstructure:"user" json:"user"`
   Password string `mapstructure:"password" json:"password"`
}

type ServerConfig struct {
   MysqlInfo MysqlConfig `mapstructure:"mysql" json:"mysql"`
}
1.2.2建立配置文件

在user_srv根目录下建立config-debug.yaml 和config-pro.yaml

mysql:
  host: '127.0.0.1'
  port: 3306
  user: 'root'
  password: '123456'
  db: 'mxshop_user_srv'

12.3初始化数据库、viper、zap

在user_srv/initialize目录增加db.go,config.go,logger.go文件

db.go

package initialize

import (
   "fmt"
   "gorm.io/driver/mysql"
   "gorm.io/gorm"
   "gorm.io/gorm/logger"
   "gorm.io/gorm/schema"
   "log"
   "mxshop_srvs/user_srv/global"
   "os"
   "time"
)

func InitDB() {
   c := global.ServerConfig.MysqlInfo
   dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local",
      c.User, c.Password, c.Host, c.Port, c.Name)
   newLogger := logger.New(
      log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer
      logger.Config{
         SlowThreshold: time.Second,   // 慢 SQL 阈值
         LogLevel:      logger.Silent, // Log level
         Colorful:      true,          // 禁用彩色打印
      },
   )

   // 全局模式
   var err error
   global.DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{
      NamingStrategy: schema.NamingStrategy{
         SingularTable: true,
      },
      Logger: newLogger,
   })
   if err != nil {
      panic(err)
   }
}

config.go

package initialize

import (
   "fmt"
   "github.com/spf13/viper"
   "mxshop_srvs/user_srv/global"
)

func GetEnvInfo(env string) bool {
   viper.AutomaticEnv()
   return viper.GetBool(env)
   //刚才设置的环境变量 想要生效 我们必须得重启goland
}

func InitConfig() {
   //从配置文件中读取出对应的配置
   debug := GetEnvInfo("MXSHOP_DEBUG")
   configFilePrefix := "config"
   configFileName := fmt.Sprintf("user_srv/%s-pro.yaml", configFilePrefix)
   if debug {
      configFileName = fmt.Sprintf("user_srv/%s-debug.yaml", configFilePrefix)
   }

   v := viper.New()
   //文件的路径如何设置
   v.SetConfigFile(configFileName)
   if err := v.ReadInConfig(); err != nil {
      panic(err)
   }
   if err := v.Unmarshal(&global.ServerConfig); err != nil {
      panic(err)
   }
}

logger.go

package initialize

import "go.uber.org/zap"

func InitLogger() {
   logger, _ := zap.NewDevelopment()
   zap.ReplaceGlobals(logger)
}

1.2.3配置全局的serverconfig

package global

import (
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
	"gorm.io/gorm/schema"
	"log"
	"mxshop_srvs/user_srv/config"
	"os"
	"time"
)

var (
	DB           *gorm.DB
	//新增的
	ServerConfig config.ServerConfig
)

1.2.4在user_srv/main.go中初始化函数

package main

import (
   "flag"
   "fmt"
   "google.golang.org/grpc"
   "mxshop_srvs/user_srv/handler"
   "mxshop_srvs/user_srv/initialize"
   "mxshop_srvs/user_srv/proto"
   "net"
)

func main() {
   IP := flag.String("ip", "0.0.0.0", "ip地址")
   Port := flag.Int("proto", 50051, "端口")

   //初始化(新增的)
   initialize.InitLogger()
   initialize.InitConfig()
   initialize.InitDB()

   flag.Parse()
   server := grpc.NewServer()
   proto.RegisterUserServer(server, &handler.UserServer{})
   lis, err := net.Listen("tcp", fmt.Sprintf("%s:%d", *IP, *Port))
   if err != nil {
      panic("failed to listen:" + err.Error())
   }
   err = server.Serve(lis)
   if err != nil {
      panic("failed to start")
   }
}
2.grpc服务如何进行健康检查

在user_srv/main.go增加如下代码

package main

import (
   "flag"
   "fmt"
   "google.golang.org/grpc"

   "google.golang.org/grpc/health"
   "google.golang.org/grpc/health/grpc_health_v1"
   "mxshop_srvs/user_srv/handler"
   "mxshop_srvs/user_srv/initialize"
   "mxshop_srvs/user_srv/proto"
   "net"
)

func main() {
   IP := flag.String("ip", "0.0.0.0", "ip地址")
   Port := flag.Int("proto", 50051, "端口")

   //初始化
   initialize.InitLogger()
   initialize.InitConfig()
   initialize.InitDB()

   flag.Parse()
   server := grpc.NewServer()
   proto.RegisterUserServer(server, &handler.UserServer{})
   lis, err := net.Listen("tcp", fmt.Sprintf("%s:%d", *IP, *Port))
   if err != nil {
      panic("failed to listen:" + err.Error())
   }

   //新增加的代码
   grpc_health_v1.RegisterHealthServer(server, health.NewServer())

   err = server.Serve(lis)
   if err != nil {
      panic("failed to start")
   }
}
3.将grpc服务注册到consul中
3.1增加consul配置

在user_srv/config/config.go增加

package config

type MysqlConfig struct {
   Host     string `mapstructure:"host" json:"host"`
   Port     int    `mapstructure:"port" json:"port"`
   Name     string `mapstructure:"db" json:"db"`
   User     string `mapstructure:"user" json:"user"`
   Password string `mapstructure:"password" json:"password"`
}
//新增加的
type ConsulConfig struct {
   Host string `mapstructure:"host" json:"host"`
   Port int    `mapstructure:"port" json:"port"`
}

type ServerConfig struct {
   Name       string       `mapstructure:"name" json:"name"`
   MysqlInfo  MysqlConfig  `mapstructure:"mysql" json:"mysql"`
   ConsulInfo ConsulConfig `mapstructure:"consul" json:"consul"`
}
3.2配置文件增加consul
name: 'user-srv'
mysql:
  host: '127.0.0.1'
  port: 3306
  user: 'root'
  password: '123456'
  db: 'mxshop_user_srv'

#新增加的
consul:
  host: '127.0.0.1'
  port: 8500
  password: '123456'
  db: 'mxshop_user_srv'
3.3启动注册

在user_srv/main.go增加

package main

import (
   "flag"
   "fmt"
   "github.com/hashicorp/consul/api"
   "go.uber.org/zap"
   "google.golang.org/grpc"
   "mxshop_srvs/user_srv/global"

   "github.com/satori/go.uuid"
   "google.golang.org/grpc/health"
   "google.golang.org/grpc/health/grpc_health_v1"
   "mxshop_srvs/user_srv/handler"
   "mxshop_srvs/user_srv/initialize"
   "mxshop_srvs/user_srv/proto"
   "net"
)

func main() {
   IP := flag.String("ip", "0.0.0.0", "ip地址")
   Port := flag.Int("port", 50051, "端口")

   //初始化
   initialize.InitLogger()
   initialize.InitConfig()
   initialize.InitDB()

   zap.S().Info(global.ServerConfig.ConsulInfo)

   flag.Parse()
   server := grpc.NewServer()
   proto.RegisterUserServer(server, &handler.UserServer{})
   lis, err := net.Listen("tcp", fmt.Sprintf("%s:%d", *IP, *Port))
   if err != nil {
      panic("failed to listen:" + err.Error())
   }

   grpc_health_v1.RegisterHealthServer(server, health.NewServer())
    //新增加的
    //服务注册
   cfg := api.DefaultConfig()
   cfg.Address = fmt.Sprintf("%s:%d", global.ServerConfig.ConsulInfo.Host,
      global.ServerConfig.ConsulInfo.Port)

   client, err := api.NewClient(cfg)
   if err != nil {
      panic(err)
   }
   //生成对应的检查对象
   check := &api.AgentServiceCheck{
      GRPC:                           fmt.Sprintf("192.168.0.4:%d", *Port),
      Timeout:                        "5s",
      Interval:                       "5s",
      DeregisterCriticalServiceAfter: "15s",
   }

   //生成注册对象
   registration := new(api.AgentServiceRegistration)
   registration.Name = global.ServerConfig.Name
   serviceID := fmt.Sprintf("%s", uuid.NewV4())
   registration.ID = serviceID
   registration.Port = *Port
   registration.Tags = []string{"imooc", "bobby", "user", "srv"}
   registration.Address = "192.168.0.104"
   registration.Check = check
   //1. 如何启动两个服务
   //2. 即使我能够通过终端启动两个服务,但是注册到consul中的时候也会被覆盖
   err = client.Agent().ServiceRegister(registration)
   if err != nil {
      panic(err)
   }
   err = server.Serve(lis)
   if err != nil {
      panic("failed to start")
   }
}
4.gin集成consul
4.1增加consul配置

在user-web目录下的config-debug.yaml增加配置

name: 'user-web'
port: 8021
user_srv:
  host: '127.0.0.1'
  port: 50051
  name: 'user-srv'

jwt:
  key: 'OdpJe6U9GF5*H#JUyO2qzpaz69t1dojd'

sms:
  key: 'LTAI4GHhatiznGG52T5EidAW'
  secrect: 'xOFzvwf7G8jPhL3eNpyOmjPjFRZJfx'

redis:
  host: '127.0.0.1'
  port: 6379
  expire: 3600

#增加的配置
consul:
  host: '127.0.0.1'
  port: 8500
4.2映射配置

在user-web/config/config.go目录下增加配置

package config

type JWTConfig struct {
   SigningKey string `mapstructure:"key" json:"key"`
}

type AliSmsConfig struct {
   ApiKey     string `mapstructure:"key" json:"key"`
   ApiSecrect string `mapstructure:"secrect" json:"secrect"`
}
type RedisConfig struct {
   Host   string `mapstructure:"host" json:"host"`
   Port   int    `mapstructure:"port" json:"port"`
   Expire int    `mapstructure:"expire" json:"expire"`
}
//新增的配置
type ConsulConfig struct {
   Host string `mapstructure:"host"`
   Port int    `mapstructure:"port"`
}

type UserSrvConfig struct {
   Name string `mapstructure:"name"`
   Host string `mapstructure:"host"`
   Port int    `mapstructure:"port"`
}

type ServerConfig struct {
   Name        string        `mapstructure:"name"`
   Port        int           `mapstructure:"port"`
   UserSrvInfo UserSrvConfig `mapstructure:"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"`
}

4.3服务拉取

在user-web/api/user.go增加如下代码

func GetUserList(ctx *gin.Context) {
   //从注册中心获取到用户服务的信息
    //新增加的代码开始
   cfg := api.DefaultConfig()
   consulInfo := global.ServerConfig.ConsulInfo
   cfg.Address = fmt.Sprintf("%s:%d", consulInfo.Host, consulInfo.Port)

   client, err := api.NewClient(cfg)
   if err != nil {
      panic(err)
   }
   userSrvHost := ""
   userSrvPort := 0

   fmt.Println(global.ServerConfig.UserSrvInfo.Name)
   serverList, _ := client.Agent().Services()
   for _, service := range serverList {
      if service.Service == global.ServerConfig.UserSrvInfo.Name {
         userSrvHost = service.Address
         userSrvPort = service.Port
      }
   }
     //新增加的代码结束
   userConn, err := grpc.Dial(fmt.Sprintf("%s:%d", userSrvHost,
      userSrvPort), grpc.WithInsecure())
   if err != nil {
      zap.S().Errorw("[GetUserList] 连接【用务服务失败】", "msg", err.Error())
   }
   claims, _ := ctx.Get("claims")
   currentUser := claims.(*models.CustomClaims)
   zap.S().Infof("访问用户: %d", currentUser.ID)

   //生成grpc的client并调用接口
   userSrvClient := proto.NewUserClient(userConn)

   pn := ctx.DefaultQuery("pn", "0")
   pnInt, _ := strconv.Atoi(pn)
   pSize := ctx.DefaultQuery("pSize", "0")
   pSizeInt, _ := strconv.Atoi(pSize)
   rsp, err := userSrvClient.GetUserList(context.Background(), &proto.PageInfo{
      Pn:    uint32(pnInt),
      PSize: uint32(pSizeInt),
   })
   if err != nil {
      zap.S().Errorw("[GetUserList] 查询【用户列表失败】")
      HandleGrpcErrorToHttp(err, ctx)
      return
   }
   reMap := gin.H{
      "total": rsp.Total,
   }
   result := make([]interface{}, 0)
   for _, value := range rsp.Data {
      user := response.UserResponse{
         Id:       value.Id,
         NickName: value.NickName,
         Birthday: response.JsonTime(time.Unix(int64(value.BirthDay), 0)),
         Gender:   value.Gender,
         Mobile:   value.Mobile,
      }
      result = append(result, user)
   }
   reMap["data"] = result
   ctx.JSON(http.StatusOK, reMap)
}
5.将用户的GRPC配置全局共享
5.1添加初始化RPC方法

在user-web目录initalize这个目录下增加InitSrvConn方法

func InitSrvConn() {
   //从注册中心获取到用户服务的信息
   cfg := api.DefaultConfig()
   consulInfo := global.ServerConfig.ConsulInfo
   cfg.Address = fmt.Sprintf("%s:%d", consulInfo.Host, consulInfo.Port)

   userSrvHost := ""
   userSrvPort := 0
   client, err := api.NewClient(cfg)
   if err != nil {
      panic(err)
   }

   serverList, _ := client.Agent().Services()
   for _, service := range serverList {
      if service.Service == global.ServerConfig.UserSrvInfo.Name {
         userSrvHost = service.Address
         userSrvPort = service.Port
      }
   }
   if userSrvHost == "" {
      zap.S().Fatal("[InitSrvConn] 连接 【用户服务失败】")
      return
   }

   //拨号连接用户grpc服务器 跨域的问题 - 后端解决 也可以前端来解决
   userConn, err := grpc.Dial(fmt.Sprintf("%s:%d", userSrvHost, userSrvPort), grpc.WithInsecure())
   if err != nil {
      zap.S().Errorw("[GetUserList] 连接 【用户服务失败】",
         "msg", err.Error(),
      )
   }
   //1. 后续的用户服务下线了 2. 改端口了 3. 改ip了 负载均衡来做

   //2. 已经事先创立好了连接,这样后续就不用进行再次tcp的三次握手
   //3. 一个连接多个groutine共用,性能 - 连接池
   userSrvClient := proto.NewUserClient(userConn)
   global.UserSrvClient = userSrvClient
}
5.2.在入口main.go增加初始化
package main

import (
   "fmt"
   "github.com/gin-gonic/gin/binding"
   ut "github.com/go-playground/universal-translator"
   "github.com/go-playground/validator/v10"
   "go.uber.org/zap"
   "mxshop_api/user-web/global"
   "mxshop_api/user-web/initalize"
   myvalidator "mxshop_api/user-web/validator"
)

func main() {
   //1.初始化logger
   initalize.InitLogger()

   //2. 初始化配置文件
   initalize.InitConfig()

   //3.初始化routers
   Router := initalize.Routers()
   zap.S().Debugf("启动服务器,端口:%d", global.ServerConfig.Port)

   //4. 初始化翻译
   if err := initalize.InitTrans("zh"); err != nil {
      panic(err)
   }

   //5.初始化SRV的连接(这个是新增加的)
   initalize.InitSrvConn()

   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
      })
   }

   if err := Router.Run(fmt.Sprintf(":%d", global.ServerConfig.Port)); err != nil {
      zap.S().Panic("启动失败:", err.Error())
   }
}
5.3调整user-api

在user-web/api/user.go调整,调整后的代码如下

package api

import (
   "context"
   "fmt"
   "github.com/dgrijalva/jwt-go"
   "github.com/gin-gonic/gin"
   "github.com/go-playground/validator/v10"
   "github.com/go-redis/redis/v8"
   "go.uber.org/zap"
   "google.golang.org/grpc"
   "google.golang.org/grpc/codes"
   "google.golang.org/grpc/status"
   "mxshop_api/user-web/forms"
   "mxshop_api/user-web/global"
   "mxshop_api/user-web/global/response"
   "mxshop_api/user-web/middlewares"
   "mxshop_api/user-web/models"
   "mxshop_api/user-web/proto"
   "net/http"
   "strconv"
   "strings"
   "time"
)

func removeTopStruct(fileds map[string]string) map[string]string {
   rsp := map[string]string{}
   for field, err := range fileds {
      rsp[field[strings.Index(field, ".")+1:]] = err
   }
   return rsp
}

func HandleGrpcErrorToHttp(err error, c *gin.Context) {
   //将grpc的code转换成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": "参数错误",
            })
         case codes.Unavailable:
            c.JSON(http.StatusInternalServerError, gin.H{
               "msg": "用户服务不可用",
            })
         default:
            c.JSON(http.StatusInternalServerError, gin.H{
               "msg": e.Code(),
            })
         }
         return
      }
   }
}

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{
      "error": removeTopStruct(errs.Translate(global.Trans)),
   })
   return
}

func GetUserList(ctx *gin.Context) {
   pn := ctx.DefaultQuery("pn", "0")
   pnInt, _ := strconv.Atoi(pn)
   pSize := ctx.DefaultQuery("pSize", "0")
   pSizeInt, _ := strconv.Atoi(pSize)
   rsp, err := global.UserSrvClient.GetUserList(context.Background(), &proto.PageInfo{
      Pn:    uint32(pnInt),
      PSize: uint32(pSizeInt),
   })
   if err != nil {
      zap.S().Errorw("[GetUserList] 查询【用户列表失败】")
      HandleGrpcErrorToHttp(err, ctx)
      return
   }
   reMap := gin.H{
      "total": rsp.Total,
   }
   result := make([]interface{}, 0)
   for _, value := range rsp.Data {
      user := response.UserResponse{
         Id:       value.Id,
         NickName: value.NickName,
         Birthday: response.JsonTime(time.Unix(int64(value.BirthDay), 0)),
         Gender:   value.Gender,
         Mobile:   value.Mobile,
      }
      result = append(result, user)
   }
   reMap["data"] = result
   ctx.JSON(http.StatusOK, reMap)
}

func PassWordLogin(c *gin.Context) {
   //表单验证
   passwordLoginForm := forms.PassWordLoginForm{}
   if err := c.ShouldBindJSON(&passwordLoginForm); err != nil {
      //如何返回错误信息
      HandleValidatorError(c, err)
      return
   }

   if !store.Verify(passwordLoginForm.CaptchaId, passwordLoginForm.Captcha, false) {
      c.JSON(http.StatusBadRequest, gin.H{
         "captcha": "验证码错误",
      })
      return
   }
   //登录的逻辑
   if rsp, err := global.UserSrvClient.GetUserByMobile(context.Background(), &proto.MobileRequest{
      Mobile: passwordLoginForm.Mobile,
   }); err != nil {
      if e, ok := status.FromError(err); ok {
         switch e.Code() {
         case codes.NotFound:
            c.JSON(http.StatusBadRequest, map[string]string{
               "mobile": "用户不存在",
            })
         default:
            c.JSON(http.StatusInternalServerError, map[string]string{
               "mobile": "登录失败",
            })
         }
         return
      }
   } else {
      //只是查询到了用户而已,并没有检查密码
      if passRsp, pasERR := global.UserSrvClient.CheckPassWord(context.Background(), &proto.PasswordCheckInfo{
         Password:          passwordLoginForm.PassWord,
         EncryptedPassword: rsp.PassWord,
      }); pasERR != nil {
         c.JSON(http.StatusInternalServerError, map[string]string{
            "password": "登录失败",
         })
      } else {
         if passRsp.Success {
            fmt.Println("rsp", rsp.Id)
            //生成token
            j := middlewares.NewJWT()
            claims := models.CustomClaims{
               ID:          uint(rsp.Id),
               NickName:    rsp.NickName,
               AuthorityId: uint(rsp.Role),
               StandardClaims: jwt.StandardClaims{
                  NotBefore: time.Now().Unix(),               //签名的生效时间
                  ExpiresAt: time.Now().Unix() + 60*60*14*30, //30天过期
                  Issuer:    "lisus",
               },
            }
            token, err := j.CreateToken(claims)
            if err != nil {
               c.JSON(http.StatusInternalServerError, gin.H{
                  "msg": "生成token失败",
               })
               return
            }
            c.JSON(http.StatusOK, gin.H{
               "id":         rsp.Id,
               "nick_name":  rsp.NickName,
               "token":      token,
               "expired_at": (time.Now().Unix() + 60*60*14*30) * 1000,
            })
         } else {
            c.JSON(http.StatusBadRequest, map[string]string{
               "msg": "登录失败",
            })
         }

      }
   }
}

func Register(c *gin.Context) {
   //用户注册
   registerForm := forms.RegisterForm{}
   if err := c.ShouldBind(&registerForm); err != nil {
      HandleValidatorError(c, err)
      return
   }

   //验证码
   rdb := redis.NewClient(&redis.Options{
      Addr: fmt.Sprintf("%s:%d", global.ServerConfig.RedisInfo.Host, global.ServerConfig.RedisInfo.Port),
   })
   value, err := rdb.Get(context.Background(), registerForm.Mobile).Result()
   if err == redis.Nil {
      c.JSON(http.StatusBadRequest, gin.H{
         "code": "验证码错误",
      })
      return
   } else {
      if value != registerForm.Code {
         c.JSON(http.StatusBadRequest, gin.H{
            "code": "验证码错误",
         })
         return
      }
   }

   userConn, err := grpc.Dial(fmt.Sprintf("%s:%d", global.ServerConfig.UserSrvInfo.Host,
      global.ServerConfig.UserSrvInfo.Port), grpc.WithInsecure())
   if err != nil {
      zap.S().Errorw("[Register] 连接【用务服务失败】", "msg", err.Error())
   }
   //调用接口

   //生成grpc的client并调用接口
   userSrvClient := proto.NewUserClient(userConn)
   user, err := userSrvClient.CreateUser(context.Background(), &proto.CreateUserInfo{
      NickName: registerForm.Mobile,
      PassWord: registerForm.PassWord,
      Mobile:   registerForm.Mobile,
   })

   if err != nil {
      zap.S().Errorf("[Register] 查询 【新建用户失败】失败: %s", err.Error())
      HandleGrpcErrorToHttp(err, c)
      return
   }
   j := middlewares.NewJWT()
   claims := models.CustomClaims{
      ID:          uint(user.Id),
      NickName:    user.NickName,
      AuthorityId: uint(user.Role),
      StandardClaims: jwt.StandardClaims{
         NotBefore: time.Now().Unix(),               //签名的生效时间
         ExpiresAt: time.Now().Unix() + 60*60*24*30, //30天过期
         Issuer:    "imooc",
      },
   }
   token, err := j.CreateToken(claims)
   if err != nil {
      c.JSON(http.StatusInternalServerError, gin.H{
         "msg": "生成token失败",
      })
      return
   }

   c.JSON(http.StatusOK, gin.H{
      "id":         user.Id,
      "nick_name":  user.NickName,
      "token":      token,
      "expired_at": (time.Now().Unix() + 60*60*24*30) * 1000,
   })
}
6.动态配置端口
6.1在user-web/utils目录下添加addr.go
package utils

import "net"

func GetFreePort() (int, error) {
   addr, err := net.ResolveTCPAddr("tcp", "localhost:0")
   if err != nil {
      return 0, err
   }

   l, err := net.ListenTCP("tcp", addr)
   if err != nil {
      return 0, err
   }
   defer l.Close()
   return l.Addr().(*net.TCPAddr).Port, nil
}
6.2修改user-web/main.go
package main

import (
   "fmt"
   "github.com/gin-gonic/gin/binding"
   ut "github.com/go-playground/universal-translator"
   "github.com/go-playground/validator/v10"
   "github.com/spf13/viper"
   "go.uber.org/zap"
   "mxshop_api/user-web/global"
   "mxshop_api/user-web/initalize"
   "mxshop_api/user-web/utils"
   myvalidator "mxshop_api/user-web/validator"
)

func main() {
   //1.初始化logger
   initalize.InitLogger()

   //2. 初始化配置文件
   initalize.InitConfig()

   //3.初始化routers
   Router := initalize.Routers()
   zap.S().Debugf("启动服务器,端口:%d", global.ServerConfig.Port)

   //4. 初始化翻译
   if err := initalize.InitTrans("zh"); err != nil {
      panic(err)
   }

   //5.初始化SRV的连接
   initalize.InitSrvConn()

   viper.AutomaticEnv()
   debug := viper.GetBool("MXSHOP_DEBUG")
   if !debug {
      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
      })
   }

   if err := Router.Run(fmt.Sprintf(":%d", global.ServerConfig.Port)); err != nil {
      zap.S().Panic("启动失败:", err.Error())
   }
}
7.gin集成grpc的负载均衡[
7.1引入grpc-consul-resolver
go get github.com/mbobakov/grpc-consul-resolver
7.2.在user-web/initalize 目录修改srv-conn文件
package initalize

import (
   "fmt"
   "github.com/hashicorp/consul/api"
   _ "github.com/mbobakov/grpc-consul-resolver"
   "go.uber.org/zap"
   "google.golang.org/grpc"
   "mxshop_api/user-web/global"
   "mxshop_api/user-web/proto"
)

// 新增加的方法
func InitSrvConn() {
   consulInfo := global.ServerConfig.ConsulInfo
   userConn, err := grpc.Dial(
      fmt.Sprintf("consul://%s:%d/%s?wait=14s", consulInfo.Host, consulInfo.Port, global.ServerConfig.UserSrvInfo.Name),
      grpc.WithInsecure(),
      grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy": "round_robin"}`),
   )
   if err != nil {
      zap.S().Fatal("[InitSrvConn] 连接 【用户服务失败】")
   }

   userSrvClient := proto.NewUserClient(userConn)
   global.UserSrvClient = userSrvClient
}

func InitSrvConn2() {
   //从注册中心获取到用户服务的信息
   cfg := api.DefaultConfig()
   consulInfo := global.ServerConfig.ConsulInfo
   cfg.Address = fmt.Sprintf("%s:%d", consulInfo.Host, consulInfo.Port)

   userSrvHost := ""
   userSrvPort := 0
   client, err := api.NewClient(cfg)
   if err != nil {
      panic(err)
   }

   serverList, _ := client.Agent().Services()
   for _, service := range serverList {
      if service.Service == global.ServerConfig.UserSrvInfo.Name {
         userSrvHost = service.Address
         userSrvPort = service.Port
      }
   }
   if userSrvHost == "" {
      zap.S().Fatal("[InitSrvConn] 连接 【用户服务失败】")
      return
   }

   //拨号连接用户grpc服务器 跨域的问题 - 后端解决 也可以前端来解决
   userConn, err := grpc.Dial(fmt.Sprintf("%s:%d", userSrvHost, userSrvPort), grpc.WithInsecure())
   if err != nil {
      zap.S().Errorw("[GetUserList] 连接 【用户服务失败】",
         "msg", err.Error(),
      )
   }
   //1. 后续的用户服务下线了 2. 改端口了 3. 改ip了 负载均衡来做

   //2. 已经事先创立好了连接,这样后续就不用进行再次tcp的三次握手
   //3. 一个连接多个groutine共用,性能 - 连接池
   userSrvClient := proto.NewUserClient(userConn)
   global.UserSrvClient = userSrvClient
}
8.gin集成nacos( user-web)
8.1引入nacos安装包
go get github.com/nacos-group/nacos-sdk-go
8.2在nacos上建立配置

在nacos上建立如下配置

在这里插入图片描述

8.3建立nacos配置和映射文件

在user-web/config/config.go中进行修改

package config

type UserSrvConfig struct {
   Host string `mapstructure:"host" json:"host"`
   Port int    `mapstructure:"port" json:"port"`
   Name string `mapstructure:"name" json:"name"`
}

type JWTConfig struct {
   SigningKey string `mapstructure:"key" json:"key"`
}

type AliSmsConfig struct {
   ApiKey     string `mapstructure:"key" json:"key"`
   ApiSecrect string `mapstructure:"secrect" json:"secrect"`
}

type ConsulConfig struct {
   Host string `mapstructure:"host" json:"host"`
   Port int    `mapstructure:"port" json:"port"`
}

type RedisConfig struct {
   Host   string `mapstructure:"host" json:"host"`
   Port   int    `mapstructure:"port" json:"port"`
   Expire int    `mapstructure:"expire" json:"expire"`
}

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"`
}
//新增加的
type NacosConfig struct {
   Host      string `mapstructure:"host"`
   Port      uint64 `mapstructure:"port"`
   Namespace string `mapstructure:"namespace"`
   User      string `mapstructure:"user"`
   Password  string `mapstructure:"password"`
   DataId    string `mapstructure:"dataid"`
   Group     string `mapstructure:"group"`
}
8.4建立全局的nacos配置

在user-web/global/global.go进行修改

package global

import (
   ut "github.com/go-playground/universal-translator"
   "mxshop_api/user-web/config"
   "mxshop_api/user-web/proto"
)

var (
   Trans         ut.Translator
   ServerConfig  *config.ServerConfig = &config.ServerConfig{}
   UserSrvClient proto.UserClient
    //新增的
   NacosConfig   *config.NacosConfig = &config.NacosConfig{}
)
8.5修改初始化配置

在user-web/initalize/config.go中进行修改,下面是修改后的代码

package initalize

import (
   "encoding/json"
   "fmt"
   "github.com/nacos-group/nacos-sdk-go/clients"
   "github.com/nacos-group/nacos-sdk-go/common/constant"
   "github.com/nacos-group/nacos-sdk-go/vo"
   "github.com/spf13/viper"
   "go.uber.org/zap"
   "mxshop_api/user-web/global"
)

func GetEnvInfo(env string) bool {
   viper.AutomaticEnv()
   return viper.GetBool(env)
}

func InitConfig() {
   debug := GetEnvInfo("MXSHOP_DEBUG")
   configFilePrefix := "config"
   configFileName := fmt.Sprintf("user-web/%s-pro.yaml", configFilePrefix)
   if debug {
      configFileName = fmt.Sprintf("user-web/%s-debug.yaml", configFilePrefix)
   }
   v := viper.New()
   v.SetConfigFile(configFileName)
   if err := v.ReadInConfig(); err != nil {
      panic(err)
   }
   if err := v.Unmarshal(global.NacosConfig); err != nil {
      panic(err)
   }
   zap.S().Infof("配置信息: &v", global.NacosConfig)
   //viper功能,动态监控变化
   //v.WatchConfig()
   //v.OnConfigChange(func(e fsnotify.Event) {
   // zap.S().Infof("配置信息产生变化:%v", e.Name)
   // _ = v.ReadInConfig()
   // _ = v.Unmarshal(global.ServerConfig)
   // fmt.Println(global.ServerConfig)
   //})
   //从nacos读取信息
   sc := []constant.ServerConfig{
      {
         IpAddr: global.NacosConfig.Host,
         Port:   global.NacosConfig.Port,
      },
   }

   cc := constant.ClientConfig{
      NamespaceId:         global.NacosConfig.Namespace, // 如果需要支持多namespace,我们可以场景多个client,它们有不同的NamespaceId
      TimeoutMs:           5000,
      NotLoadCacheAtStart: true,
      LogDir:              "tmp/nacos/log",
      CacheDir:            "tmp/nacos/cache",
      LogLevel:            "debug",
   }

   configClient, err := clients.CreateConfigClient(map[string]interface{}{
      "serverConfigs": sc,
      "clientConfig":  cc,
   })
   if err != nil {
      panic(err)
   }

   content, err := configClient.GetConfig(vo.ConfigParam{
      DataId: global.NacosConfig.DataId,
      Group:  global.NacosConfig.Group})

   if err != nil {
      panic(err)
   }
   //fmt.Println(content) //字符串 - yaml
   //想要将一个json字符串转换成struct,需要去设置这个struct的tag
   err = json.Unmarshal([]byte(content), &global.ServerConfig)
   if err != nil {
      zap.S().Fatalf("读取nacos配置失败: %s", err.Error())
   }
   fmt.Println(&global.ServerConfig)
}
9.用户服务集成nacos
9.1在nacos上配置user-srv

在这里插入图片描述

9.2建立nacos配置

在user-srv/ config/config.go中修改

package config

type MysqlConfig struct {
   Host     string `mapstructure:"host" json:"host"`
   Port     int    `mapstructure:"port" json:"port"`
   Name     string `mapstructure:"db" json:"db"`
   User     string `mapstructure:"user" json:"user"`
   Password string `mapstructure:"password" json:"password"`
}

type ConsulConfig struct {
   Host string `mapstructure:"host" json:"host"`
   Port int    `mapstructure:"port" json:"port"`
}

type ServerConfig struct {
   Name       string       `mapstructure:"name" json:"name"`
   MysqlInfo  MysqlConfig  `mapstructure:"mysql" json:"mysql"`
   ConsulInfo ConsulConfig `mapstructure:"consul" json:"consul"`
}
//新增的
type NacosConfig struct {
   Host      string `mapstructure:"host"`
   Port      uint64 `mapstructure:"port"`
   Namespace string `mapstructure:"namespace"`
   User      string `mapstructure:"user"`
   Password  string `mapstructure:"password"`
   DataId    string `mapstructure:"dataid"`
   Group     string `mapstructure:"group"`
}
9.4建立全局的配置

在user-srv/global/globa.go中修改

package global

import (
   "gorm.io/driver/mysql"
   "gorm.io/gorm"
   "gorm.io/gorm/logger"
   "gorm.io/gorm/schema"
   "log"
   "mxshop_srvs/user_srv/config"
   "os"
   "time"
)

var (
   DB           *gorm.DB
   ServerConfig config.ServerConfig
   //新增的
   NacosConfig  config.NacosConfig
)
9.5修改config-debug.yaml
host: '192.168.0.4'
port: 8848
namespace: 'aed3d75d-cee1-4d35-b001-5da8ed17298f'
user: 'nacos'
password: 'nacos'
dataid: 'user-srv.json'
group: 'dev'
9.6修改初始化配置

在user-srv/initialize/config.go中修改

package initialize

import (
   "encoding/json"
   "fmt"
   "github.com/nacos-group/nacos-sdk-go/clients"
   "github.com/nacos-group/nacos-sdk-go/common/constant"
   "github.com/nacos-group/nacos-sdk-go/vo"
   "github.com/spf13/viper"
   "go.uber.org/zap"
   "mxshop_srvs/user_srv/global"
)

func GetEnvInfo(env string) bool {
   viper.AutomaticEnv()
   return viper.GetBool(env)
   //刚才设置的环境变量 想要生效 我们必须得重启goland
}

func InitConfig() {
   //从配置文件中读取出对应的配置
   debug := GetEnvInfo("MXSHOP_DEBUG")
   configFilePrefix := "config"
   configFileName := fmt.Sprintf("user_srv/%s-pro.yaml", configFilePrefix)
   if debug {
      configFileName = fmt.Sprintf("user_srv/%s-debug.yaml", configFilePrefix)
   }

   v := viper.New()
   //文件的路径如何设置
   v.SetConfigFile(configFileName)
   if err := v.ReadInConfig(); err != nil {
      panic(err)
   }
   //这个对象如何在其他文件中使用 - 全局变量
   if err := v.Unmarshal(&global.NacosConfig); err != nil {
      panic(err)
   }
   zap.S().Infof("配置信息: %v", global.NacosConfig)

   //从nacos中读取配置信息
   sc := []constant.ServerConfig{
      {
         IpAddr: global.NacosConfig.Host,
         Port:   global.NacosConfig.Port,
      },
   }

   cc := constant.ClientConfig{
      NamespaceId:         global.NacosConfig.Namespace, // 如果需要支持多namespace,我们可以场景多个client,它们有不同的NamespaceId
      TimeoutMs:           5000,
      NotLoadCacheAtStart: true,
      LogDir:              "tmp/nacos/log",
      CacheDir:            "tmp/nacos/cache",
      LogLevel:            "debug",
   }

   configClient, err := clients.CreateConfigClient(map[string]interface{}{
      "serverConfigs": sc,
      "clientConfig":  cc,
   })
   if err != nil {
      panic(err)
   }

   content, err := configClient.GetConfig(vo.ConfigParam{
      DataId: global.NacosConfig.DataId,
      Group:  global.NacosConfig.Group})

   if err != nil {
      panic(err)
   }
   //fmt.Println(content) //字符串 - yaml
   //想要将一个json字符串转换成struct,需要去设置这个struct的tag
   err = json.Unmarshal([]byte(content), &global.ServerConfig)
   if err != nil {
      zap.S().Fatalf("读取nacos配置失败: %s", err.Error())
   }
   fmt.Println(&global.ServerConfig)
}

附录

http://www.mf2.cn/img2base64/
https://www.json2yaml.com/convert-yaml-to-json

name: 'user-web'
port: 8021
user_srv:
  host: '127.0.0.1'
  port: 50051
  name: 'user-srv'

jwt:
  key: 'OdpJe6U9GF5*H#JUyO2qzpaz69t1dojd'

sms:
  key: 'LTAI4GHhatiznGG52T5EidAW'
  secrect: 'xOFzvwf7G8jPhL3eNpyOmjPjFRZJfx'

redis:
  host: '127.0.0.1'
  port: 6379
  expire: 3600

consul:
  host: '127.0.0.1'
  port: 8500
e NacosConfig struct {
   Host      string `mapstructure:"host"`
   Port      uint64 `mapstructure:"port"`
   Namespace string `mapstructure:"namespace"`
   User      string `mapstructure:"user"`
   Password  string `mapstructure:"password"`
   DataId    string `mapstructure:"dataid"`
   Group     string `mapstructure:"group"`
}
9.4建立全局的配置

在user-srv/global/globa.go中修改

package global

import (
   "gorm.io/driver/mysql"
   "gorm.io/gorm"
   "gorm.io/gorm/logger"
   "gorm.io/gorm/schema"
   "log"
   "mxshop_srvs/user_srv/config"
   "os"
   "time"
)

var (
   DB           *gorm.DB
   ServerConfig config.ServerConfig
   //新增的
   NacosConfig  config.NacosConfig
)
9.5修改config-debug.yaml
host: '192.168.0.4'
port: 8848
namespace: 'aed3d75d-cee1-4d35-b001-5da8ed17298f'
user: 'nacos'
password: 'nacos'
dataid: 'user-srv.json'
group: 'dev'
9.6修改初始化配置

在user-srv/initialize/config.go中修改

package initialize

import (
   "encoding/json"
   "fmt"
   "github.com/nacos-group/nacos-sdk-go/clients"
   "github.com/nacos-group/nacos-sdk-go/common/constant"
   "github.com/nacos-group/nacos-sdk-go/vo"
   "github.com/spf13/viper"
   "go.uber.org/zap"
   "mxshop_srvs/user_srv/global"
)

func GetEnvInfo(env string) bool {
   viper.AutomaticEnv()
   return viper.GetBool(env)
   //刚才设置的环境变量 想要生效 我们必须得重启goland
}

func InitConfig() {
   //从配置文件中读取出对应的配置
   debug := GetEnvInfo("MXSHOP_DEBUG")
   configFilePrefix := "config"
   configFileName := fmt.Sprintf("user_srv/%s-pro.yaml", configFilePrefix)
   if debug {
      configFileName = fmt.Sprintf("user_srv/%s-debug.yaml", configFilePrefix)
   }

   v := viper.New()
   //文件的路径如何设置
   v.SetConfigFile(configFileName)
   if err := v.ReadInConfig(); err != nil {
      panic(err)
   }
   //这个对象如何在其他文件中使用 - 全局变量
   if err := v.Unmarshal(&global.NacosConfig); err != nil {
      panic(err)
   }
   zap.S().Infof("配置信息: %v", global.NacosConfig)

   //从nacos中读取配置信息
   sc := []constant.ServerConfig{
      {
         IpAddr: global.NacosConfig.Host,
         Port:   global.NacosConfig.Port,
      },
   }

   cc := constant.ClientConfig{
      NamespaceId:         global.NacosConfig.Namespace, // 如果需要支持多namespace,我们可以场景多个client,它们有不同的NamespaceId
      TimeoutMs:           5000,
      NotLoadCacheAtStart: true,
      LogDir:              "tmp/nacos/log",
      CacheDir:            "tmp/nacos/cache",
      LogLevel:            "debug",
   }

   configClient, err := clients.CreateConfigClient(map[string]interface{}{
      "serverConfigs": sc,
      "clientConfig":  cc,
   })
   if err != nil {
      panic(err)
   }

   content, err := configClient.GetConfig(vo.ConfigParam{
      DataId: global.NacosConfig.DataId,
      Group:  global.NacosConfig.Group})

   if err != nil {
      panic(err)
   }
   //fmt.Println(content) //字符串 - yaml
   //想要将一个json字符串转换成struct,需要去设置这个struct的tag
   err = json.Unmarshal([]byte(content), &global.ServerConfig)
   if err != nil {
      zap.S().Fatalf("读取nacos配置失败: %s", err.Error())
   }
   fmt.Println(&global.ServerConfig)
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值