三用户服务注册发现、负载均衡、配置中心
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(®isterForm); 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)
}