• 16、注册中心-consul


    一、服务注册与发现

    • 什么是服务注册和发现:假如这个产品已经在线上运行,有一天运营想搞一场促销活动,那么我们相对应的【用户服务】可能就要新开启三个微服务实例来支撑这场促销活动。而与此同时,作为苦逼程序员的你就只有手动去 API gateway 中添加新增的这三个微服务实例的 ip 与port ,一个真正在线的微服务系统可能有成百上千微服务,难道也要一个一个去手动添加吗?
    • 解决方案:当我们新添加一个微服务实例的时候,微服务就会将自己的 ip 与 port 发送到注册中心,在注册中心里面记录起来。当 API gateway 需要访问某些微服务的时候,就会去注册中心取到相应的 ip 与 port。从而实现自动化操作

    在这里插入图片描述

    • 技术选型:Consul 与其他常见服务发现框架对比

    在这里插入图片描述


    二、consul安装与配置


    三、consul服务注册与注销

    1 - 注册服务

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

    2 - 注销服务

    3 - 健康检查

    4 - 获取服务


    四、go中使用consul

    • Register:注册服务
    • AllServices:获取所有服务
    • FilterService:过滤服务
    package main
    
    import (
    	"fmt"
    	"github.com/hashicorp/consul/api"
    )
    
    // Register 注册服务
    func Register(address string, port int, name string, tags []string, id string) error {
    	cfg := api.DefaultConfig()
    	cfg.Address = "192.168.124.51:8500"
    
    	client, err := api.NewClient(cfg)
    	if err != nil {
    		panic(err)
    	}
    	//生成对应的检查对象
    	check := &api.AgentServiceCheck{
    		HTTP:                           "http://192.168.124.9:8081/health",
    		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
    }
    
    // AllServices 获取所有服务
    func AllServices() {
    	cfg := api.DefaultConfig()
    	cfg.Address = "192.168.124.51:8500"
    
    	client, err := api.NewClient(cfg)
    	if err != nil {
    		panic(err)
    	}
    
    	data, err := client.Agent().Services()
    	if err != nil {
    		panic(err)
    	}
    	for key, _ := range data {
    		fmt.Println(key)
    	}
    }
    
    // FilterService 服务过滤
    func FilterService() {
    	cfg := api.DefaultConfig()
    	cfg.Address = "192.168.124.51:8500"
    
    	client, err := api.NewClient(cfg)
    	if err != nil {
    		panic(err)
    	}
    
    	data, err := client.Agent().ServicesWithFilter(`Service == "user-web"`)
    	if err != nil {
    		panic(err)
    	}
    	for key, _ := range data {
    		fmt.Println(key)
    	}
    }
    
    func main() {
    	_ = Register("http://192.168.124.9", 8081, "user-web", []string{"mxshop", "bobby"}, "user-web")
    	AllServices()
    	FilterService()
    }
    
    
    • 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

    五、user_srv集成viper和zap

    • 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 ServerConfig struct {
    	Name      string      `mapstructure:"name" json:"name"`
    	MysqlInfo MysqlConfig `mapstructure:"mysql" json:"mysql"`
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • user_srv/global/global.go:添加全局对象ServerConfig
    package global
    
    import (
    	"gorm.io/gorm"
    	"nd/user_srv/config"
    )
    
    var (
    	DB           *gorm.DB
    	ServerConfig config.ServerConfig
    )
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • user_srv/initialize/init_config.go:初始化config配置
    package initialize
    
    import (
    	"fmt"
    	"github.com/spf13/viper"
    	"go.uber.org/zap"
    	"nd/user_srv/global"
    )
    
    func GetEnvInfo(env string) bool {
    	viper.AutomaticEnv()
    	return viper.GetBool(env)
    	//刚才设置的环境变量 想要生效 我们必须得重启goland
    }
    
    func InitConfig() {
    	//从配置文件中读取出对应的配置
    	debug := GetEnvInfo("DEV_CONFIG")
    	configFilePrefix := "config"
    	configFileName := fmt.Sprintf("%s_pro.yaml", configFilePrefix)
    	if debug {
    		configFileName = fmt.Sprintf("%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)
    	}
    	zap.S().Infof("配置信息: %v", global.ServerConfig)
    }
    
    
    • 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
    • user_srv/initialize/init_db.go:初始化db
    package initialize
    
    import (
    	"fmt"
    	"log"
    	"os"
    	"time"
    
    	"gorm.io/driver/mysql"
    	"gorm.io/gorm"
    	"gorm.io/gorm/logger"
    	"gorm.io/gorm/schema"
    
    	"nd/user_srv/global"
    )
    
    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)
    	}
    }
    
    
    • 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
    • user_srv\initialize\init_logger.go:zap初始化
    package initialize
    
    import "go.uber.org/zap"
    
    func InitLogger() {
    	logger, _ := zap.NewDevelopment()
    	zap.ReplaceGlobals(logger)
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • yaml
    //user_srv/config_pro.yaml
    mysql:
      host: '192.168.124.51'
      port: '3306'
      user: 'root'
      password: 'jiushi'
      db: 'mxshop_user_srv'
    //user_srv/config_pro.yaml
    mysql:
      host: '192.168.124.51'
      port: '3306'
      user: 'root'
      password: 'jiushi'
      db: 'mxshop_user_srv'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • user_srv/main.go:主逻辑修改
    package main
    
    import (
    	"flag"
    	"fmt"
    	"go.uber.org/zap"
    	"nd/user_srv/global"
    	"nd/user_srv/handler"
    	"nd/user_srv/initialize"
    	"nd/user_srv/proto"
    	"net"
    
    	"google.golang.org/grpc"
    )
    
    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)
    
    	flag.Parse()
    	zap.S().Info("ip: ", *IP)
    	zap.S().Info("port: ", *Port)
    
    	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 grpc:" + err.Error())
    	}
    }
    
    
    • 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

    六、grpc健康检查

    grpc服务注册到consul

    • user_srv/config/config.go:添加ConsulConfig配置对象
    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"`
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • yaml
    //user_srv/config_pro.yaml
    mysql:
      host: '192.168.124.51'
      port: '3306'
      user: 'root'
      password: 'jiushi'
      db: 'mxshop_user_srv'
    
    name: 'user_srv'
    
    consul:
      host: '192.168.124.51'
      port: '8500'
    //user_srv/config_pro.yaml
    mysql:
      host: '192.168.124.51'
      port: '3306'
      user: 'root'
      password: 'jiushi'
      db: 'mxshop_user_srv'
    
    name: 'user_srv'
    
    consul:
      host: '192.168.124.51'
      port: '8500'
    
    • 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
    • user_srv/main.go
      • ①.注册健康检查
      • ②.注册服务
    package main
    
    import (
    	"flag"
    	"fmt"
    	"github.com/hashicorp/consul/api"
    	"go.uber.org/zap"
    	"google.golang.org/grpc/health"
    	"google.golang.org/grpc/health/grpc_health_v1"
    	"nd/user_srv/global"
    	"nd/user_srv/handler"
    	"nd/user_srv/initialize"
    	"nd/user_srv/proto"
    	"net"
    
    	"google.golang.org/grpc"
    )
    
    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)
    
    	flag.Parse()
    	zap.S().Info("ip: ", *IP)
    	zap.S().Info("port: ", *Port)
    
    	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.124.9:%d", *Port),
    		Timeout:                        "5s",
    		Interval:                       "5s",
    		DeregisterCriticalServiceAfter: "15s",
    	}
    
    	//生成注册对象
    	registration := new(api.AgentServiceRegistration)
    	registration.Name = global.ServerConfig.Name
    	registration.ID = global.ServerConfig.Name
    	registration.Port = *Port
    	registration.Tags = []string{"imooc", "bobby", "user", "srv"}
    	registration.Address = "192.168.124.9"
    	registration.Check = check
    
    	err = client.Agent().ServiceRegister(registration)
    	if err != nil {
    		panic(err)
    	}
    
    	err = server.Serve(lis)
    	if err != nil {
    		panic("failed to start grpc:" + err.Error())
    	}
    }
    
    
    • 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

    在这里插入图片描述


    七、gin集成consul

    user_web层需既要完成服务发现、也需要完成服务注册

    1 - UserSrvClient优化

    • UserSrvClient优化:每个接口都需要UserSrvClient,每次如果都重新获取都需要tcp三次握手,优化成统一初始化全局的UserSrvClient;当然这里还会存在一个问题,就是多个连接都使用了同一个UserSrvClient,这个后续再优化
    • user_web/global/global.go:添加全局UserSrvClient对象
    package global
    
    import (
    	ut "github.com/go-playground/universal-translator"
    
    	"web_api/user_web/config"
    	"web_api/user_web/proto"
    )
    
    var (
    	Trans         ut.Translator
    	ServerConfig  *config.ServerConfig = &config.ServerConfig{}
    	UserSrvClient proto.UserClient
    )
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • user_web/initialize/init_srv_conn.go:添加初始化的业务逻辑
    package initialize
    
    import (
    	"fmt"
    	"github.com/hashicorp/consul/api"
    	"go.uber.org/zap"
    	"google.golang.org/grpc"
    	"google.golang.org/grpc/credentials/insecure"
    	"web_api/user_web/global"
    	"web_api/user_web/proto"
    )
    
    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)
    	}
    
    	data, err := client.Agent().ServicesWithFilter(fmt.Sprintf("Service == \"%s\"", global.ServerConfig.UserSrvInfo.Name))
    	if err != nil {
    		panic(err)
    	}
    
    	// 只要获取一个就可以了
    	for _, value := range data {
    		userSrvHost = value.Address
    		userSrvPort = value.Port
    		break
    	}
    
    	if userSrvHost == "" {
    		zap.S().Fatal("[InitSrvConn] 连接 【用户服务失败】")
    		return
    	}
    
    	//拨号连接用户grpc服务器
    	userConn, err := grpc.Dial(fmt.Sprintf("%s:%d", userSrvHost, userSrvPort),
    		grpc.WithTransportCredentials(insecure.NewCredentials()))
    	if err != nil {
    		zap.S().Errorw("[GetUserList] 连接 【用户服务失败】", "msg", err.Error())
    	}
    	// 1. 后续用户服务下线了如何处理  2. 该端口了 3. 改ip了
    	// 已经事先建立好了链接,这样后续就不用再进行tcp的三次握手了
    	// 一个连接多个groutine公用,性能问题 - 连接池
    	userClient := proto.NewUserClient(userConn)
    	global.UserSrvClient = userClient
    }
    
    
    • 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
    • user_web/main.go:添加初始化srv的连接
    package main
    
    import (
    	"fmt"
    	"web_api/user_web/global"
    	"web_api/user_web/initialize"
    
    	"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()
    
    	//注册验证器
    	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
    		})
    	}
    
    	/*
    		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())
    	}
    }
    
    
    • 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

    2 - gin集成consul

    • yaml配置:添加user_srv的name,consul的host和ip
    //user_web/config_debug.yaml
    name: 'user-web'
    port: '8081'
    user_srv:
      host: '127.0.0.1'
      port: '50051'
      name: 'user_srv'
    jwt:
      key: 'VYLDYq3&hGWjWqF$K1ih'
    sms:
      key: ''
      secrect: ''
      expire: 300
    redis:
      host: '192.168.124.51'
      port: '6379'
    consul:
      host: '192.168.124.51'
      port: '8500'
    
    //user_web/config_pro.yaml
    name: 'user-web'
    port: '8081'
    user_srv:
      host: '127.0.0.1'
      port: '50051'
      name: 'user_srv'
    jwt:
      key: 'VYLDYq3&hGWjWqF$K1ih'
    sms:
      key: ''
      secrect: ''
      expire: 300
    redis:
      host: '192.168.124.51'
      port: '6379'
    consul:
      host: '192.168.124.51'
      port: '8500'
    
    
    • 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
    • user_web/api/api_user.go:删除掉初始化UserSrvClient的操作,将对应的UserSrvClient都替换为global.UserSrvClient
    package api
    
    import (
    	"context"
    	"fmt"
    	"net/http"
    	"strconv"
    	"strings"
    	"time"
    
    	"github.com/dgrijalva/jwt-go"
    	"github.com/gin-gonic/gin"
    	"github.com/go-playground/validator"
    	"github.com/go-redis/redis"
    	"go.uber.org/zap"
    	"google.golang.org/grpc/codes"
    	"google.golang.org/grpc/status"
    
    	"web_api/user_web/forms"
    	"web_api/user_web/global"
    	"web_api/user_web/global/response"
    	"web_api/user_web/middlewares"
    	"web_api/user_web/models"
    	"web_api/user_web/proto"
    )
    
    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)),
    	})
    }
    
    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
    	}
    	return rsp
    }
    
    func GetUserList(ctx *gin.Context) {
    	//拨号连接用户grpc服务器 跨域的问题 - 后端解决 也可以前端来解决
    	//claims, _ := ctx.Get("claims")
    	//currentUser := claims.(*models.CustomClaims)
    	//zap.S().Infof("访问用户: %d", currentUser.ID)
    
    	pn := ctx.DefaultQuery("pn", "0")
    	pnInt, _ := strconv.Atoi(pn)
    	pSize := ctx.DefaultQuery("psize", "10")
    	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
    	}
    
    	result := make([]interface{}, 0)
    	for _, value := range rsp.Data {
    		user := response.UserResponse{
    			Id:       value.Id,
    			NickName: value.NickName,
    			//Birthday: time.Time(time.Unix(int64(value.BirthDay), 0)).Format("2006-01-02"),
    			Birthday: response.JsonTime(time.Unix(int64(value.BirthDay), 0)),
    			Gender:   value.Gender,
    			Mobile:   value.Mobile,
    		}
    		result = append(result, user)
    	}
    
    	ctx.JSON(http.StatusOK, result)
    }
    
    func PassWordLogin(c *gin.Context) {
    	//表单验证
    	passwordLoginForm := forms.PassWordLoginForm{}
    	if err := c.ShouldBind(&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 {
    				//生成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*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":         rsp.Id,
    					"nick_name":  rsp.NickName,
    					"token":      token,
    					"expired_at": (time.Now().Unix() + 60*60*24*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(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
    		}
    	}
    
    	user, err := global.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,
    	})
    }
    
    
    • 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
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258

    在这里插入图片描述


    八、完整源码

    mxshop_srvsV6.0.rar

  • 相关阅读:
    推荐一款新的自动化测试框架:DrissionPage!
    NC20242 [SCOI2005]最大子矩阵
    【单目标优化求解】基于matlab平衡算法求解单目标优化问题【含Matlab源码 2114期】
    DAY26:GetShell专题
    【多目标进化优化】 Pareto 最优解集的构造方法
    免费小程序商城搭建之b2b2c o2o 多商家入驻商城 直播带货商城 电子商务b2b2c o2o 多商家入驻商城 直播带货商城 电子商务
    MyBatis的< resultMap >标签的简析
    DTDX991A 61430001-UW 自由IOT引入人工智能功能
    【头歌】——Ethernet包分析(计算机网络)
    Angular React Vue 比较 - 前言
  • 原文地址:https://blog.csdn.net/qq23001186/article/details/126025480