在微服务架构中,Web 层作为系统的入口门面,承担着请求路由、权限校验和服务聚合等核心功能。本文将围绕 Gin 框架与 Consul 注册中心的集成展开,详细讲解 Web 服务如何实现服务注册与发现,帮助你构建可扩展的微服务前端架构。
承接微服务注册中心详解
我在前面文章中也写了gRPC服务层集成consul,需要的可以回去看一下。
一、Web 层服务集成 Consul 概述
1.1 Web 层在微服务中的定位
Web 层(如 Gin 服务)通常扮演以下多重角色:
- API 网关:作为系统统一入口,处理跨域请求、身份认证、流量限流等非业务逻辑,避免底层服务重复实现通用功能
- 服务聚合器:将多个底层微服务的能力组合成复合 API,减少客户端与后端的交互次数(如同时获取用户信息和订单列表)
- 请求路由枢纽:根据业务逻辑将请求转发到对应服务,实现前端与后端服务的逻辑解耦
为什么 Web 层需要集成 Consul?
在单体架构中,服务地址通常是硬编码的;但在微服务场景下:
- 底层服务可能部署多个实例(如用户服务有 3 个副本),需要动态获取可用地址
- 服务可能因扩缩容、故障转移而变更 IP 端口,手动维护地址列表不现实
- 负载均衡需要从注册中心获取实时服务列表,实现流量的动态分配
1.2 服务注册与发现的双重需求
1.2.1 服务注册的必要性
即使作为前端服务,Web 层仍需注册到 Consul 的核心原因:
- 反向调用场景:后台管理系统、数据同步服务可能需要调用 Web 层 API,需通过注册中心获取地址
- 负载均衡需求:Nginx 等反向代理可从 Consul 动态获取 Web 服务地址,避免手动配置上游服务器
- 全局服务治理:统一在 Consul 中监控所有服务的健康状态,包括 Web 层的运行情况
1.2.2 服务发现的核心作用
Web 层的服务发现机制解决了三大问题:
- 地址动态获取:底层服务(如用户服务)的 IP 端口变更时,Web 层无需重启即可获取新地址
- 多实例负载均衡:当同一服务存在多个实例时,自动选择合适的节点调用
- 故障自动转移:检测到服务实例不健康时,自动跳过该实例,避免请求失败
二、Consul 配置与服务发现实现
2.2 服务发现核心代码解析
2.2.1 从 Consul 获取服务地址的执行流程
您提供的代码片段展示了从 Consul 获取用户服务地址并建立 gRPC 连接的完整过程。这一过程可以分解为以下关键步骤:
- 初始化 Consul 客户端:从配置中读取 Consul 服务器地址并创建客户端
- 服务发现查询:通过服务名称过滤获取目标服务实例
- 地址解析与连接:提取服务 IP 和端口,建立 gRPC 长连接
- 全局客户端管理:将 gRPC 客户端存储为全局变量供后续使用
// service_discovery.go - 服务发现实现
package service
import (
"fmt"
"log"
"github.com/hashicorp/consul/api"
"your-project/global"
"zap"
)
// InitSrvConn 初始化服务连接
func InitSrvConn() {
// 1. 初始化Consul客户端配置
cfg := api.DefaultConfig()
consulInfo := global.ServerConfig.ConsulInfo
cfg.Address = fmt.Sprintf("%s:%d", consulInfo.Host, consulInfo.Port)
// 2. 创建Consul客户端
client, err := api.NewClient(cfg)
if err != nil {
log.Fatalf("创建Consul客户端失败: %v", err)
}
// 3. 过滤查询用户服务
data, err := client.Agent().ServicesWithFilter(fmt.Sprintf("Service==\"%s\"", global.ServerConfig.UserSrvInfo.Name))
if err != nil {
log.Fatalf("过滤服务失败: %v", err)
}
// 4. 解析服务地址
userSrvHost := ""
userSrvPort := 0
fmt.Println("过滤后的服务:")
for _, value := range data {
userSrvHost = value.Address
userSrvPort = value.Port
break
}
// 5. 验证服务是否找到
if userSrvHost == "" {
zap.S().Fatal("[InitSrvConn] 连接 用户服务失败")
return
}
// 6. 建立gRPC连接
userconn, err := grpc.Dial(fmt.Sprintf("%s:%d", userSrvHost, userSrvPort), grpc.WithInsecure())
if err != nil {
zap.S().Errorw("[GetUserList] 连接 [用户服务失败]", "message", err.Error())
}
// 7. 生成gRPC客户端并存储为全局变量
userSrvClient := proto.NewUserClient(userconn)
global.UserSrvClient = userSrvClient
zap.S().Infof("用户服务连接成功: %s:%d", userSrvHost, userSrvPort)
}
2.2.2 在 Gin 中集成服务发现的完整流程
您提供的示例代码展示了在 Gin 路由处理函数中如何使用通过 Consul 发现的服务。下面我将结合您的代码,详细说明服务发现如何与实际业务接口结合:
// main.go - Gin服务启动流程
package main
import (
"context"
"fmt"
"github.com/gin-gonic/gin"
"google.golang.org/grpc"
"net/http"
"strconv"
"time"
"your-project/global"
"your-project/proto"
"your-project/service"
"your-project/response" // 假设这是模型包
"your-project/forms" // 假设这是表单验证包
"your-project/models" // 假设这是模型包
"your-project/utils" // 假设这是工具包
"zap"
)
func main() {
// 1. 初始化配置
initConfig()
// 2. 初始化服务连接(从Consul发现服务)
service.InitSrvConn()
// 3. 初始化Gin引擎
r := gin.Default()
// 4. 注册中间件
setupMiddlewares(r)
// 5. 注册路由
setupRoutes(r)
// 6. 注册Web服务到Consul
if err := service.RegisterWebServiceToConsul(); err != nil {
panic(fmt.Sprintf("Web服务注册失败: %v", err))
}
// 7. 启动Gin服务
addr := fmt.Sprintf(":%d", global.ServerConfig.Port)
zap.S().Infof("服务启动: %s", addr)
if err := r.Run(addr); err != nil {
panic(fmt.Sprintf("Gin服务启动失败: %v", err))
}
}
// 用户控制器 - 整合业务接口示例
func setupRoutes(r *gin.Engine) {
userGroup := r.Group("/users")
{
userGroup.GET("/", GetUserList) // 获取用户列表
userGroup.POST("/login", PassWordLogin) // 用户登录
}
}
// 获取用户列表 -
func GetUserList(ctx *gin.Context) {
// 从JWT中取出用户id(假设JWT验证已通过)
claims, exists := ctx.Get("claims")
if !exists {
ctx.JSON(http.StatusUnauthorized, gin.H{
"error": "未授权",
})
return
}
currentUser := claims.(*models.CustomClaims)
zap.S().Infof("用户信息:%v", currentUser.Id)
// 分页参数处理
pn := ctx.DefaultQuery("pn", "0")
pnInt, _ := strconv.Atoi(pn)
pSize := ctx.DefaultQuery("psize", "10")
pSizeInt, _ := strconv.Atoi(pSize)
// 通过全局变量获取已初始化的gRPC客户端
rsp, err := global.UserSrvClient.GetUserList(context.Background(), &proto.PageInfo{
Pn: uint32(pnInt),
PSize: uint32(pSizeInt),
})
if err != nil {
zap.S().Errorw("[GetUserList] 查询用户列表失败", "error", err)
utils.HandleGrpcErrorToHttp(err, ctx) // 自定义错误处理函数
return
}
// 构建响应数据
result := make([]interface{}, 0)
for _, value := range rsp.Data {
userResponse := 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, userResponse)
}
ctx.JSON(http.StatusOK, result)
}
// 用户登录 - 示例接口
func PassWordLogin(c *gin.Context) {
zap.S().Infof("用户登录开始")
// 表单验证
passWordLoginForm := forms.PassWordLoginForm{}
if err := c.ShouldBind(&passWordLoginForm); err != nil {
utils.HandleValidatorError(c, err) // 自定义验证错误处理
return
}
zap.S().Infof("用户登录表单验证成功")
// 验证验证码
if !utils.Store.Verify(passWordLoginForm.CaptchaId, passWordLoginForm.Captcha, true) {
zap.S().Infof("验证码错误")
c.JSON(http.StatusBadRequest, gin.H{
"msg": "验证码错误",
})
return
}
// 通过全局变量获取gRPC客户端,调用用户服务验证登录
rsp, err := global.UserSrvClient.GetUserByMobile(context.Background(), &proto.MobileRequest{
Mobile: passWordLoginForm.Mobile,
})
if err != nil {
zap.S().Errorw("[PassWordLogin] 查询用户失败", "error", err)
utils.HandleGrpcErrorToHttp(err, c)
return
}
// 验证密码(示例代码,实际应使用加密比较)
if passWordLoginForm.PassWord != rsp.PassWord {
c.JSON(http.StatusBadRequest, gin.H{
"msg": "密码错误",
})
return
}
// 生成JWT token
token, err := utils.GenerateToken(rsp.Id, rsp.NickName, rsp.Role)
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().Add(time.Hour * 24).Unix(),
})
}
三、Gin 服务集成 Consul 的关键流程解析
3.1 服务发现的时机选择
从代码可以看出,服务发现发生在两个关键阶段:
- 应用启动阶段:在
main
函数中调用service.InitSrvConn()
,在服务启动时就完成服务发现和连接建立 - 请求处理阶段:在具体的路由处理函数(如
GetUserList
)中使用已初始化的全局客户端
这种设计的优势在于:
- 避免首次请求时的延迟(冷启动问题)
- 通过全局变量复用连接,减少 TCP 握手开销
- 服务启动时若无法连接下游服务,则直接终止,避免提供不可用服务
3.2 服务发现与业务逻辑的结合
示例代码展示了服务发现如何与实际业务逻辑结合:
获取用户列表接口:
- 从 JWT 中提取用户身份信息
- 解析分页参数
- 调用通过 Consul 发现的用户服务
- 处理返回结果并构建响应
用户登录接口:
- 表单验证和验证码校验
- 调用用户服务验证用户信息
- 生成 JWT 令牌并返回
3.3 代码组织最佳实践
根据示例代码,建议以下代码组织方式:
- 服务发现逻辑:封装在
service
包中,提供初始化函数(如InitSrvConn
) - 全局客户端:存储在
global
包中,供所有需要调用服务的地方使用 - 路由处理:按业务模块组织路由和处理函数(如
user_routes.go
) - 工具函数:将通用功能(如错误处理、表单验证)封装在
utils
包中
四、服务注册中心接口与实现
4.1 服务注册核心接口详解
4.1.1 服务注册的关键参数说明
// consul.go - 服务注册实现中的关键配置项
registration := &api.AgentServiceRegistration{
ID: fmt.Sprintf("%s-%d", config.Name, config.Port), // 服务ID必须唯一,建议使用"服务名-端口"格式
Name: config.Name, // 服务名称,用于服务发现时的查询
Address: "0.0.0.0", // 服务绑定地址,0.0.0.0表示监听所有网络接口
Port: config.Port, // 服务端口,需与实际监听端口一致
Tags: []string{"gin", "web-service"}, // 服务标签,可用于过滤(如查询所有Gin框架的服务)
Check: &api.AgentServiceCheck{
// 健康检查配置核心参数:
HTTP: fmt.Sprintf("http://127.0.0.1:%d/health", config.Port), // 健康检查URL,建议使用回环地址
Interval: "10s", // 检查间隔,过短会增加开销,过长会延迟故障发现
Timeout: "5s", // 超时时间,应小于间隔时间
DeregisterCriticalServiceAfter: "30s", // 服务不健康后自动注销的时间,避免长时间保留失效实例
},
}
4.1.2 服务注册的最佳实践
- ID 唯一性:使用服务名 + 端口生成 ID,避免多实例部署时 ID 冲突
- 健康检查类型:Web 服务建议使用 HTTP 检查(相比 TCP 检查可验证应用层逻辑)
- 注销延迟:
DeregisterCriticalServiceAfter
建议设为 30-60 秒,给服务重启预留时间 - 标签规范:通过标签区分服务类型(如 web、srv)、环境(dev、prod)、版本(v1、v2)
4.2 健康检查接口实现原理
4.2.1 健康检查的两种模式
- 主动检查:Consul 定期向服务发送请求(如本文实现的 HTTP 检查)
- 被动检查:服务主动向 Consul 报告健康状态(适用于网络隔离场景)
4.2.2 健康检查接口的设计要点
// health.go - 健康检查路由实现细节
package router
import (
"github.com/gin-gonic/gin"
"your-project/global"
"time"
)
// SetupHealthRoutes 配置健康检查路由:
// - 路径固定为/health,符合Consul默认检查规范
// - 返回JSON格式状态,便于机器解析和人工查看
func SetupHealthRoutes(r *gin.Engine) {
r.GET("/health", func(c *gin.Context) {
// 1. 检查自身服务状态:
// - 可添加内存、CPU使用率检查
// - 验证数据库连接、Redis连接等基础资源
selfStatus := "UP"
if global.ServerConfig.Port == 0 {
selfStatus = "DOWN" // 配置未正确加载
}
// 2. 检查依赖服务状态:
// - 此处简化为判断客户端是否存在
// - 生产环境应发送实际请求验证服务可用性
depStatus := make(map[string]string)
if global.UserSrvClient != nil {
// 可选:发送轻量级请求验证服务响应
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
defer cancel()
_, err := global.UserSrvClient.HealthCheck(ctx, &proto.HealthCheckRequest{})
if err != nil {
depStatus["user-srv"] = "DOWN"
} else {
depStatus["user-srv"] = "UP"
}
} else {
depStatus["user-srv"] = "DOWN"
}
// 3. 返回健康状态:
// - 200状态码表示整体健康
// - 503状态码表示部分依赖不可用
c.JSON(200, gin.H{
"status": selfStatus,
"dependencies": depStatus,
"timestamp": time.Now().Unix(),
"service": global.ServerConfig.Name,
})
})
}
五、测试调试与错误处理
5.1 常见测试场景与调试方法
5.1.1 服务注册验证的三种方式
Consul UI 可视化验证:
- 访问
http://consul-host:8500
进入 Web 管理界面 - 在 "Services" 标签页查看
user-web
服务是否存在 - 点击服务名称,查看 "Checks" 标签页确认健康检查状态为 "passing"
- 访问
API 接口验证:
# 查询所有注册服务(返回JSON格式服务列表) curl http://consul-host:8500/v1/catalog/services # 查询指定服务的详细信息 curl http://consul-host:8500/v1/catalog/service/user-web # 验证健康检查接口(需确保Web服务已启动) curl http://web-service-host:8021/health
日志动态验证:
- 启动 Web 服务时观察日志,应输出 "Web 服务已注册到 Consul"
- 查看 Consul 服务器日志,确认收到注册请求
5.1.2 服务发现调试技巧
// 调试服务发现的辅助函数(生产环境建议注释掉)
func debugServiceDiscovery() {
serviceName := "user-srv"
host, port, err := service.GetServiceAddress(serviceName)
if err != nil {
log.Fatalf("服务发现失败: 错误详情, %v", err)
}
// 打印详细的服务元数据,便于调试
log.Printf("发现服务 %s 在 %s:%d", serviceName, host, port)
// 可选:打印服务的Tags、Meta等信息
}
5.2 错误处理最佳实践
5.2.1 服务发现异常的分级处理
// 带重试和降级的服务发现函数
func GetServiceWithFallback(serviceName string) (string, int, error) {
// 1. 定义重试策略:最多重试3次,间隔递增
maxRetries := 3
for i := 0; i < maxRetries; i++ {
host, port, err := service.GetServiceAddress(serviceName)
if err == nil {
return host, port, nil // 成功获取直接返回
}
// 打印带重试次数的错误日志,便于问题追踪
log.Printf("服务发现失败(尝试 %d/%d): %v", i+1, maxRetries, err)
// 指数退避策略:重试间隔逐渐增加,减少服务器压力
time.Sleep(time.Duration(i+1) * 2 * time.Second)
}
// 2. 服务发现失败后的降级处理:
// - 从缓存中获取历史地址(需实现缓存机制)
// - 返回预定义的备用地址(如备用服务节点)
fallbackHost, fallbackPort := getFallbackServiceAddress(serviceName)
if fallbackHost != "" {
log.Printf("使用降级地址: %s:%d", fallbackHost, fallbackPort)
return fallbackHost, fallbackPort, nil
}
return "", 0, fmt.Errorf("服务发现重试 %d 次失败且无降级方案", maxRetries)
}
5.2.2 网络异常的全链路处理
// 在gRPC连接中添加完整的错误处理链
userConn, err := grpc.Dial(
fmt.Sprintf("%s:%d", host, port),
grpc.WithInsecure(),
grpc.WithBlock(),
grpc.WithTimeout(5*time.Second), // 连接超时控制
grpc.WithKeepaliveParams(keepalive.ClientParameters{
Time: 10 * time.Second, // 发送心跳间隔
Timeout: 3 * time.Second, // 心跳超时
PermitWithoutStream: true, // 允许在无流时发送心跳
}),
)
if err != nil {
// 区分临时性错误和永久性错误
if opErr, ok := err.(*net.OpError); ok && opErr.Temporary() {
log.Printf("临时性连接错误,将尝试重连: %v", err)
// 此处可添加重连逻辑
} else {
log.Printf("永久性连接错误: %v", err)
// 记录错误并返回降级响应
return nil, err
}
}
六、Web 层服务集成 Consul 的优化策略
6.1 连接池与负载均衡的工程实践
6.1.1 连接池实现原理
连接池解决的核心问题:
- TCP 连接创建开销:每次调用都创建新连接会导致三次握手和四次挥手的性能损耗
- 文件句柄限制:大量短连接可能耗尽系统文件句柄资源
- 连接复用效率:复用已有连接可减少网络延迟和资源占用
// 全局连接池实现细节
var (
// 连接池配置参数可通过配置文件动态调整
userSrvConnPool = &connPool{
maxConn: 10, // 最大连接数,根据服务负载调整
conns: make(chan *grpc.ClientConn, 10), // 缓冲通道实现连接池
mu: sync.Mutex{}, // 互斥锁保护连接池状态
}
)
type connPool struct {
maxConn int
conns chan *grpc.ClientConn
mu sync.Mutex
// 记录连接创建时间,用于超时回收
createTimes map[*grpc.ClientConn]time.Time
}
// 从连接池获取连接的流程:
// 1. 先从通道中获取空闲连接
// 2. 若通道为空,创建新连接(不超过maxConn限制)
// 3. 检查连接是否可用(避免使用已关闭的连接)
func (p *connPool) Get() (*grpc.ClientConn, error) {
select {
case conn := <-p.conns:
// 检查连接是否有效
if err := p.checkConnValid(conn); err != nil {
conn.Close()
return p.createNewConn()
}
return conn, nil
default:
return p.createNewConn()
}
}
// 创建新连接时添加服务发现逻辑,确保获取最新地址
func (p *connPool) createNewConn() (*grpc.ClientConn, error) {
p.mu.Lock()
defer p.mu.Unlock()
if len(p.conns) >= p.maxConn {
return nil, fmt.Errorf("连接池已满,无法创建新连接")
}
host, port, err := service.GetServiceAddress("user-srv")
if err != nil {
return nil, err
}
conn, err := grpc.Dial(
fmt.Sprintf("%s:%d", host, port),
grpc.WithInsecure(),
grpc.WithBlock(),
)
if err != nil {
return nil, err
}
// 记录连接创建时间,用于超时回收
if p.createTimes == nil {
p.createTimes = make(map[*grpc.ClientConn]time.Time)
}
p.createTimes[conn] = time.Now()
return conn, nil
}
// 定期清理超时连接的后台goroutine
func (p *connPool) cleanUpExpiredConns() {
ticker := time.NewTicker(30 * time.Second)
defer ticker.Stop()
for range ticker.C {
p.mu.Lock()
now := time.Now()
for conn, createTime := range p.createTimes {
if now.Sub(createTime) > 5*time.Minute { // 5分钟未使用的连接
conn.Close()
delete(p.createTimes, conn)
}
}
p.mu.Unlock()
}
}
6.2 生产环境部署建议
6.2.1 Consul 集群高可用配置
- 节点数量:建议部署 3 或 5 个节点(奇数个),可容忍 1 或 2 个节点故障
- 数据加密:启用 TLS 加密通信,防止中间人攻击
- ACL 权限:配置细粒度访问控制,区分服务注册、查询等操作的权限
- 数据持久化:配置 Raft 日志持久化,确保节点重启后数据不丢失
6.2.2 连接超时与熔断策略
- 超时设置原则:下游服务的超时时间应小于上游服务的超时时间
- 熔断触发条件:连续失败次数超过阈值(如 5 次)则触发熔断
- 熔断恢复策略:设置半开状态,定期尝试少量请求,成功后恢复正常
- 降级响应设计:熔断期间返回预定义的降级响应,避免前端报错
6.2.3 配置动态更新机制
监听 Consul KV 变更:
// 监听配置变更的后台goroutine func watchConfigChanges() { client, err := getConsulClient() if err != nil { log.Fatalf("创建Consul客户端失败: %v", err) } kv := client.KV() lastIndex := uint64(0) for { // 查询配置键值对,带阻塞查询 options := &api.QueryOptions{ WaitIndex: lastIndex, WaitTime: 10 * time.Second, } pair, meta, err := kv.Get("config/web-service", options) if err != nil { log.Printf("查询配置失败: %v", err) time.Sleep(1 * time.Second) continue } if pair != nil { // 解析新配置并更新全局配置 if err := updateGlobalConfig(pair.Value); err != nil { log.Printf("更新配置失败: %v", err) } } lastIndex = meta.LastIndex } }
配置更新注意事项:
- 避免频繁更新导致服务抖动,设置最小更新间隔
- 关键配置(如数据库连接)更新时需平滑过渡
- 配置更新后记录变更日志,便于问题追溯
七、完整项目结构参考
通过本文的实践,我们完成了 Gin Web 服务与 Consul 注册中心的深度集成,覆盖了从服务注册、服务发现到健康检查、错误处理的全流程。在实际项目中,Web 层作为微服务的门面,其稳定性直接影响用户体验,合理利用 Consul 的服务治理能力能够有效提升系统的可靠性和可维护性。建议结合业务特点进一步完善负载均衡策略、连接池管理和动态配置更新机制,打造真正健壮的微服务前端架构。当遇到问题时,可通过 Consul UI、日志分析和服务发现调试工具逐步定位,确保每个环节的稳定性。
web-service/
├── config/ # 配置文件目录,采用环境隔离设计
│ ├── base.yaml # 基础公共配置,包含各环境通用参数
│ ├── dev.yaml # 开发环境配置,包含本地服务地址
│ ├── test.yaml # 测试环境配置,指向测试集群
│ └── prod.yaml # 生产环境配置,指向正式集群
├── global/ # 全局状态管理
│ ├── global.go # 存储全局配置、客户端等对象
│ └── constants.go # 定义项目常量
├── initialize/ # 初始化逻辑封装
│ ├── config.go # 配置文件解析与验证
│ ├── consul.go # Consul客户端初始化与服务注册
│ ├── logger.go # 日志系统初始化
│ └── grpc.go # gRPC客户端连接初始化
├── proto/ # gRPC服务定义
│ ├── user.proto # 用户服务接口定义(.proto文件)
│ ├── user_grpc.pb.go # 生成的gRPC客户端代码
│ └── user.pb.go # 生成的消息结构体代码
├── service/ # 核心业务逻辑
│ ├── discovery.go # 服务发现实现,包含负载均衡
│ ├── registration.go # 服务注册实现,包含健康检查
│ ├── connection_pool.go # gRPC连接池实现
│ └── fallback.go # 服务降级逻辑
├── router/ # 路由配置
│ ├── routes.go # 主路由注册,包含中间件
│ ├── health.go # 健康检查路由
│ ├── user_routes.go # 用户相关API路由
│ └── auth_routes.go # 认证授权路由
├── handler/ # 接口处理函数
│ ├── user_handler.go # 用户信息查询、修改等接口
│ ├── auth_handler.go # 登录、token管理等接口
│ ├── order_handler.go # 订单相关接口(示例)
│ └── middleware.go # 自定义中间件(如JWT认证)
├── model/ # 数据模型
│ ├── request.go # 请求参数结构体
│ ├── response.go # 响应结果结构体
│ └── entity.go # 业务实体模型
├── utils/ # 工具函数
│ ├── jwt.go # JWT生成与验证
│ ├── redis.go # Redis操作封装
│ └── error.go # 错误处理工具
└── main.go # 程序入口,包含启动流程
对于这篇文章如果大家对微服务不是很熟练的话,主要前四部分就好。可以看到在第五部分我也写到了负载均衡,对于简化配置来说,这是一个很重要的内容,后面的文章我会尽快写到。
还有就是可以看到在第三部分的举例中,我其实是将InitSrvConn 初始化服务连接这部分代码作为全局变量来写的,这就大大简化了代码量,如果不设一个全局变量,你每次都要建立一次连接,这不符合开发原则,所以关于全局变量的实践我后面会单独出一篇文章来写。
制作不易,大概花了6小时来写,如果这篇文章对大家有帮助可以点赞关注,你的支持就是我的动力😊!