第 1 章 项目整体架构与技术栈全景拆解

2025-12-03T10:00:00+08:00 | 18分钟阅读 | 更新于 2025-12-03T10:00:00+08:00

@

第 1 章 项目整体架构与技术栈全景拆解

1 学习目标

学完本章,你应该能够:

  1. 用一句话说清 CMDB 是什么、解决什么问题;
  2. 画出(或默写出)项目的整体分层拓扑图:从 main.go 到 HTTP/gRPC 双服务的完整链路;
  3. 列出项目的核心技术栈,并说清每一项技术为什么被选中;
  4. 理解"模块自注册"机制:为什么 main.go 里几乎看不到业务代码,以及新增模块时主程序为什么一行都不用改;
  5. 说清启动五步曲的固定顺序与依赖关系,以及优雅关闭为什么要"先停 gRPC、再停 HTTP"。

前置知识:

  • Go 基本语法(能看懂 import、struct、goroutine、init() 函数);
  • 知道 HTTP/RESTful 是什么(不要求写过服务端);
  • 听说过 gRPC/protobuf(不知道也行,本章先把它当"内部服务之间的快速通道"来理解);
  • 会基本的命令行操作。

本章你会动手做的事:

  1. 对照启动链路图,在源码中从 main.go 一路点进 cmd/start.go,亲手确认五步启动的顺序;
  2. 打开 apps/api.go、apps/internal.go 与 cmd/start.go:24,追踪 host 模块"匿名导入 → init() → Registry"的完整注册路径;
  3. 拿 go.mod 对照技术栈表,逐项确认每个依赖的用途与版本。

先看一张本章概念总览脑图,建立全局印象:

第 1 章总览分层架构启动链路双协议复用技术栈选型入口层 cmd服务层 protocol业务层 appsprovider 多云适配启动五步曲优雅关闭HTTP + gRPC一份业务代码路由即权限mcube + mcenterGORM + MySQL五家云 SDK

2 知识点通俗系统化讲解

2.1 先把架构想象成一栋楼

读源码最怕一上来就扎进文件堆。我们先把 CMDB 想象成一栋三层的楼:

类比:CMDB 是一栋三层楼 + 一个外包公司。一楼是前台大门,只负责开门迎客、把人往楼上引;二楼是服务大厅,开了两个窗口(HTTP 窗口给浏览器和 curl 用,gRPC 窗口给内部服务用),两个窗口背后坐的是同一批业务员;三楼是 20 个小部门(主机、凭证、任务、账单……),每个部门自己管自己的路由、逻辑和数据库;楼外的"外包公司"专门跟 5 家云厂商打交道,把各家千差万别的 API 报表统一成标准格式交回楼里。对应到工程里就是:main.go/cmd/ 是入口层,protocol/ 是双协议服务层,apps/ 是 20 个业务模块,provider/ 是多云适配层。

逐层对应到代码:

  • 一楼(入口层):main.go 和 cmd/ 目录。这层只干一件事——把门打开(解析命令行参数、加载配置),然后把客人往楼上引。你会发现 main.go 只有一行核心代码:
  • 二楼(服务层):protocol/ 目录。这层提供两种"服务窗口":一个 HTTP 窗口(给浏览器、curl 用,走 RESTful),一个 gRPC 窗口(给内部服务用,高性能二进制协议)。两个窗口背后是同一批"业务员"。
  • 三楼(业务层):apps/ 目录。这里是 20 个业务模块(主机、凭证、任务、账单……),每个模块是一个独立的小部门,自己管自己的路由、逻辑和数据库操作。
  • 楼外还有一个"外包公司":provider/ 目录。它专门负责跟 5 家云厂商打交道——每家云的 API 都不一样,这家"外包公司"把差异抹平,统一输出标准格式的数据。

2.2 完整启动链路(先看骨架,第 6 章再逐段精讲)

程序的启动顺序一环扣一环,顺序是固定的(cmd/start.go:32-99):

main.go
  └─ cmd.Execute()                         // cobra 根命令
       ├─ start 子命令 (cmd/start.go)
       │    ├─ loadGlobalConfig            // 读 TOML 配置文件或环境变量
       │    ├─ loadGlobalLogger            // 初始化 zap 日志
       │    ├─ loadCache                   // 初始化缓存(内存 or Redis)
       │    ├─ app.InitAllApp()            // 初始化所有已注册的业务模块
       │    └─ newService → service{http, grpc}
       │         ├─ http.Start()           // go-restful, /cmdb/api/v1/<module>
       │         └─ grpc.Start()           // gRPC server, :18060
       └─ init 子命令 (cmd/init.go)
            └─ createTables()              // 读 SQL 文件建表(默认路径 docs/schema/tables.sql,文件需自备)

用大白话解释每一步:

  1. 加载配置:数据库地址、监听端口、加密密钥等,都写在 etc/config.toml 里(也支持环境变量方式)。配置加载后存成全局单例 conf.C(),之后任何地方都能取用——如果没加载就取用,直接 panic 报错(conf/load.go:13-19),这是"快速失败"思想。
  2. 初始化日志:基于 zap,支持输出到控制台或文件、text 或 JSON 格式。
  3. 初始化缓存:默认用内存缓存,也可以切 Redis(cmd/start.go:165-183)。
  4. 初始化所有 app:app.InitAllApp() 会遍历所有注册进来的业务模块,逐个调用其 Config() 方法——这一步各模块会去拿数据库连接、装配依赖(cmd/start.go:49)。
  5. 启动双服务:HTTP 监听 127.0.0.1:8060,gRPC 监听 127.0.0.1:18060(默认值见 conf/config.go:72-81)。

还有一个容易忽略的细节:优雅关闭。程序监听了 SIGTERM/SIGINT/SIGHUP/SIGQUIT 四种终止信号,收到信号后先停 gRPC、再停 HTTP(cmd/start.go:185-206)。为什么要"优雅"?因为如果直接杀进程,正在处理的请求会被拦腰截断,数据库事务可能只提交了一半。先停 gRPC(不接受新请求、等存量请求处理完)再停 HTTP,能最大程度保证数据完整。

⚠️ 新手必踩的坑:启动五步的顺序不能乱。日志依赖配置(要先知道日志级别),业务模块依赖配置和日志(初始化时可能要连数据库、打日志)。自己写项目时如果把顺序搞反,就会遇到"日志还没初始化就往里写"这类隐性 bug——不报错、但日志悄悄丢了。另外记住 conf.C() 的脾气:配置没加载就取用,直接 panic。这是刻意的"快速失败",别把它当 bug。

2.3 双协议暴露:一份业务代码,两个服务窗口

这是本项目最值得学的架构决策之一。绝大多数 Go 教学项目只暴露 HTTP;而企业级项目往往是:内部服务之间用 gRPC(快、强类型),对外/前端用 HTTP(通用、好调试)。CMDB 的做法是:

  • HTTP 侧(protocol/http.go):用 go-restful 构建 RESTful 服务,路径前缀 /{应用名}/api,即 /cmdb/api(protocol/http.go:73-75)。挂了两个过滤器:CORS 跨域过滤器、mcenter 鉴权拦截器 RestfulServerInterceptor(protocol/http.go:34-42)。启动后还会自动生成 OpenAPI 文档(/apidocs.json),并把所有路由条目自动上报给权限中心 mcenter(protocol/http.go:96-140)——这就实现了"路由即权限点",不用手工去权限系统里录接口。
  • gRPC 侧(protocol/grpc.go):注册了 recovery(panic 恢复)和 mcenter 鉴权两个拦截器(protocol/grpc.go:24-28),启动 1 秒后向 mcenter 注册自己的实例地址(protocol/grpc.go:68),相当于服务自注册,供其他服务发现和调用。

关键点来了:HTTP 的 handler 并不重新写一遍业务逻辑,而是直接复用 gRPC 的服务实现。这句话的原理在第 5 章讲控制器层时会用源码详细展开,这里先记住结论:一份 ServiceServer 实现,同时被 gRPC 和 HTTP 两个协议层消费,业务代码零重复。

类比:这就像一家餐厅的堂食窗口和外卖通道——堂食(HTTP)谁都能来、菜单人话版、好沟通;外卖(gRPC)走专用通道、格式紧凑、跑得快。后厨(业务实现)只有一间:不管订单从哪个口进来,炒菜的师傅是同一批。对应到工程里就是:对外暴露 RESTful 好调试、好对接前端;对内走 gRPC 拿强类型和性能;业务代码只写一份,由 ServiceServer 统一承载。

2.4 模块注册机制:mcube app 模式

CMDB 的 main.go 里几乎看不到业务代码,秘密在于 mcube 框架的 app 注册模式:

每个业务模块(如 apps/host)在自己的包里写一个 init() 函数,向框架"自报家门",注册三种身份之一(仓库根目录底稿《CMDB源码拆解底稿.md》1.3 节):

  • app.RegistryGrpcApp(svr) —— 我要暴露 gRPC 方法;
  • app.RegistryRESTfulApp(h) —— 我要暴露 HTTP 路由;
  • app.RegistryInternalApp(svr) —— 我是内部服务,给其他模块调用(不对外)。

这些模块如何被主程序感知?靠两个"聚合文件"用匿名导入触发 init():

// apps/api.go:7-12 —— 聚合所有 HTTP 层
import (
    _ "github.com/infraboard/cmdb/apps/bill/api"
    _ "github.com/infraboard/cmdb/apps/dict/api"
    _ "github.com/infraboard/cmdb/apps/host/api"
    _ "github.com/infraboard/cmdb/apps/resource/api"
    _ "github.com/infraboard/cmdb/apps/secret/api"
    _ "github.com/infraboard/cmdb/apps/task/api"
)
// apps/internal.go:4-10 —— 聚合所有 impl 层
import (
    _ "github.com/infraboard/cmdb/apps/bill/impl"
    _ "github.com/infraboard/cmdb/apps/health/impl"
    _ "github.com/infraboard/cmdb/apps/host/impl"
    _ "github.com/infraboard/cmdb/apps/rds/impl"
    _ "github.com/infraboard/cmdb/apps/resource/impl"
    _ "github.com/infraboard/cmdb/apps/secret/impl"
    _ "github.com/infraboard/cmdb/apps/task/impl"
)

而 cmd/start.go:24 有一行 _ "github.com/infraboard/cmdb/apps",它像多米诺骨牌的第一张:导入 apps 包 → 触发 apps/api.go 和 apps/internal.go 的导入 → 触发每个模块 init() → 模块全部注册进框架。之后 app.InitAllApp()、app.LoadGrpcApp()、app.LoadRESTfulApp() 就能统一驱动它们。

这种模式的好处:新增一个业务模块,主程序、protocol 层一行代码都不用改,只需要在 apps/api.go / apps/internal.go 加一行匿名导入。这就是"微内核 + 插件"的雏形。

类比:这套机制像新员工入职报到:HR 花名册(聚合文件)上添一行名字,员工自己拿着工牌(init() 里的 Registry 调用)去前台登记是坐哪个工位(gRPC/HTTP/内部),公司大门(main.go)和门禁系统(protocol 层)完全不用改造。对应到工程里就是:Go 的包机制保证"被导入就执行 init()",mcube 把注册结果收进框架,主程序与业务模块彻底解耦。

2.5 技术栈全景:为什么选它们?

下表来自 go.mod 的真实依赖(仓库根目录底稿《CMDB源码拆解底稿.md》0.2 节):

维度选型为什么选它(大白话)
语言Go 1.18(go.mod:3)部署简单(编译出一个二进制)、并发模型适合"批量拉云资源"这种 IO 密集场景
HTTP 框架go-restful v3 + go-restful-openapi相比 Gin,go-restful 原生支持 WebService/Route 分组和 OpenAPI 元数据,适合"路由即文档、路由即权限"的工程化玩法
RPC 框架gRPC + protobuf强类型契约:接口定义写在 .proto 文件里,一次生成服务端+客户端代码,内部调用性能远高于 JSON over HTTP
ORMGORM v1.23.10 + MySQL 驱动快速开发为主,配合"垂直分表"的统一资源模型使用
应用框架infraboard/mcube v1.9.5自研微框架,提供 app 注册、分页、异常、缓存等基建,让业务代码只关注业务
鉴权infraboard/mcenter配套的权限中心:token 校验、endpoint 权限点、服务实例注册,CMDB 启动时自动对接
CLIspf13/cobraGo 生态事实标准的命令行框架,start/init/version 子命令都靠它
校验go-playground/validator/v10用 struct tag(如 validate:"required")声明式校验参数
云 SDK阿里云 alibabacloud-go、腾讯云 tencentcloud-sdk-go、华为云 huaweicloud-sdk-go-v3、AWS aws-sdk-go-v2、VMware govmomi每家云一套官方 SDK,provider 层负责把它们包装成统一接口
配置BurntSushi/toml + caarlos0/envTOML 文件为主,环境变量为辅(容器化部署友好)
加密mcube/crypto/cbc凭证 Secret 的 CBC 对称加密(apps/secret/interface.go:14)

2.6 必须提前知道的一个真相:这是个"进行中的项目"

学习开源项目要有一个清醒认知:不是所有目录里的代码都是"完成态"。CMDB 的 20 个业务模块中,有真实实现的只有 7 个(resource、secret、host、task、rds〔部分〕、bill〔部分〕、health——判据见 apps/internal.go 的匿名导入,正好 7 行);dict 只挂了 HTTP 路由层(apps/api.go 有它、apps/internal.go 没有它);其余 12 个模块(cost、account、order、database、metric、mongodb、redis、oss、disk、dns、eip、lb)只有 proto 定义、没有实现、没有路由——是作者预留的"空地皮"(仓库根目录底稿《CMDB源码拆解底稿.md》7.3 节)。

这不是缺点,反而对学习者是好事:你既能学到"建好的楼",又能拿"空地皮"练手——第 8 章的二开实战就会带你在一块空地皮上盖房子。

⚠️ 新手必踩的坑:clone 代码后翻到某个模块(比如 apps/redis),发现里面除了 pb 文件啥都没有,第一反应往往是"我下错仓库了?"。没有——那就是仅 proto 占位的"空地皮"。判断方法第 3 章会教,这里先建立预期:本项目 20 个模块中 7 个已落地、dict 半落地(仅路由)、12 个是占位,看源码时先掂量一下脚下这块地是"楼"还是"地皮",能省下大量困惑。

3 核心代码片段逐行解析

3.1 片段:main.go —— 短得令人发指的入口

// main.go:7 —— 整个程序的入口只有一次委托
package main

import "github.com/infraboard/cmdb/cmd"

func main() {
	cmd.Execute() // 把控制权交给 cobra 命令行框架
}
  • cmd.Execute() 是 cobra 根命令的入口。所有真正的启动逻辑都在 cmd/ 包的各个子命令里。
  • 为什么这么写? 因为命令行程序往往不止一种启动方式(启动服务 start、初始化 init、查版本 version),把 main.go 薄成一张纸,复杂度全部下沉到 cmd/ 包,职责更清晰。

3.2 片段:start 子命令的启动编排(节选自 cmd/start.go:28-72)

var serviceCmd = &cobra.Command{
	Use:   "start",                    // 子命令名:cmdb start
	Short: "CMDB后端API服务",
	RunE: func(cmd *cobra.Command, args []string) error {
		// 1. 初始化全局配置(支持 file/env 两种来源)
		if err := loadGlobalConfig(confType); err != nil {
			return err
		}
		// 2. 初始化全局日志(依赖配置先就绪)
		if err := loadGlobalLogger(); err != nil {
			return err
		}
		// 3. 加载缓存(memory 或 redis,看配置)
		if err := loadCache(); err != nil {
			return err
		}
		// 4. 初始化所有注册的业务模块(每个模块的 Config() 被调用)
		if err := app.InitAllApp(); err != nil {
			return err
		}
		// 5. 监听退出信号(SIGTERM 等 4 种)
		ch := make(chan os.Signal, 1)
		signal.Notify(ch, syscall.SIGTERM, syscall.SIGINT, syscall.SIGHUP, syscall.SIGQUIT)

		// 6. 构建 service 对象(内含 http + grpc 两个服务)
		svr, err := newService(conf.C())
		if err != nil {
			return err
		}

		// 7. 另起一个 goroutine 等信号(因为第 8 步会阻塞主 goroutine)
		go svr.waitSign(ch)
		// 8. 启动服务(HTTP 阻塞运行,gRPC 在内部以 goroutine 启动)
		if err := svr.start(); err != nil {
			if !strings.Contains(err.Error(), "http: Server closed") {
				return err
			}
		}
		return nil
	},
}

注意 1~4 步的顺序是刻意安排的:日志依赖配置(要先知道日志级别)、业务模块依赖配置和日志(模块初始化时可能要连数据库、打日志)。如果你自己写项目时把顺序搞反了,就会遇到"日志还没初始化就往里写"的隐性 bug。

3.3 片段:service 的 start 与优雅关闭(cmd/start.go:92-99, 185-206 节选)

func (s *service) start() error {
	// 打印已加载的模块清单,启动时一眼看清"哪些模块装进来了"
	s.log.Infof("loaded grpc app: %s", app.LoadedGrpcApp())
	s.log.Infof("loaded http app: %s", app.LoadedRESTfulApp())
	s.log.Infof("loaded internal app: %s", app.LoadedInternalApp())

	go s.grpc.Start()      // gRPC 放到子 goroutine 里跑
	return s.http.Start()  // HTTP 在当前 goroutine 阻塞运行(作为主服务)
}

func (s *service) waitSign(sign chan os.Signal) {
	for sg := range sign {           // 循环接收信号
		switch v := sg.(type) {
		default:
			s.log.Infof("receive signal '%v', start graceful shutdown", v.String())
			// 先停 gRPC:注销 mcenter 注册 + 等存量请求结束
			if err := s.grpc.Stop(); err != nil {
				s.log.Errorf("grpc graceful shutdown err: %s, force exit", err)
			}
			// 再停 HTTP:给 30 秒宽限期处理存量请求
			if err := s.http.Stop(); err != nil {
				s.log.Errorf("http graceful shutdown err: %s, force exit", err)
			}
			return
		}
	}
}
  • waitSign 里的 grpc.Stop() 不是简单关闭,而是先向 mcenter 注销实例再 GracefulStop()(protocol/grpc.go:95-110),保证别的服务不会把请求打到一个垂死的实例上。
  • HTTP 的 Stop() 用 context.WithTimeout(30*time.Second) 兜底:最多等 30 秒,超时强制退出(protocol/http.go:111-120)。

4 业务逻辑流程图与核心要点总结

4.1 一图看懂整体架构

                        ┌─────────────────────────────────────┐
                        │           客户端 / SDK 调用方          │
                        │  (curl / 前端 / 其他微服务 / client包) │
                        └──────────────┬──────────────────────┘
                    HTTP (RESTful)     │     gRPC (protobuf)
                 127.0.0.1:8060        │      127.0.0.1:18060
                        ┌──────────────▼──────────────────────┐
                        │  protocol 层                          │
                        │  http.go: CORS + mcenter鉴权过滤器     │
                        │  grpc.go: recovery + 鉴权拦截器        │
                        │  (启动时向 mcenter 注册路由/实例)       │
                        └──────────────┬──────────────────────┘
                                       │ app.LoadRESTfulApp / LoadGrpcApp
                        ┌──────────────▼──────────────────────┐
                        │  apps 业务层(20 个模块,7 个已落地)     │
                        │  ┌─────┐ ┌──────┐ ┌──────┐ ┌──────┐  │
                        │  │secret│ │task  │ │ host │ │resource│ │
                        │  └──┬──┘ └──┬───┘ └──┬───┘ └──┬───┘  │
                        │     │(互相通过 app.GetGrpcApp 调用)      │
                        └─────┼────────┼────────┼───────┼──────┘
                              │        │        │       │
                    ┌─────────▼────────▼────────▼───────▼──────┐
                    │  MySQL (resource_meta/spec/cost/status/   │
                    │  resource_host/secret/task/...)           │
                    └─────────▲────────────────────────────────┘
                              │ 同步时
                    ┌─────────┴────────────────────────────────┐
                    │  provider 层(多云适配)                     │
                    │  aliyun / txyun / huawei / aws / vsphere  │
                    │  统一输出 *resource.Resource                 │
                    └─────────▲────────────────────────────────┘
                              │ HTTPS API
                    ┌─────────┴────────────────────────────────┐
                    │  云厂商:阿里云 / 腾讯云 / 华为云 / AWS / VMware │
                    └──────────────────────────────────────────┘

4.2 核心要点总结

  1. 启动五步曲:配置 → 日志 → 缓存 → InitAllApp → 双服务启动,顺序不可乱;
  2. 双协议、一份业务:HTTP handler 复用 gRPC 实现,业务代码只写一遍;
  3. 模块自注册:init() + 匿名导入聚合,main.go 保持零业务;
  4. 外部依赖两个:MySQL(必须)和 mcenter(鉴权/注册中心,protocol/http.go:26-29 中连接失败会直接 panic);
  5. provider 层只管"拉数据、转格式",不写库、不对账——对账逻辑在业务层(第 6 章详解)。

5 自测题(对话式)

学完不等于学会。下面三道题来自本章内容,先自己口头作答,再看候选人参考答案。

面试官
如果让你把这个项目的 HTTP 和 gRPC 拆成两个独立进程部署,protocol/ 层的代码需要怎么改?这样拆有什么收益和代价?(提示:想想共享的 conf.C() 和数据库连接)
候选人
先看现状:一个 service{http, grpc} 进程里,两边共享同一份 conf.C() 配置单例、同一个 MySQL 连接池(GetDB() 是包级单例),而且 HTTP handler 是进程内直接复用 gRPC 的 ServiceServer 实现——是函数调用,不走网络。
拆成两个进程要改的点:newService 拆成两套启动逻辑;最关键的是 HTTP 进程不能再进程内复用 gRPC 实现,要么改成真正的 gRPC 网络调用(用 client 包),要么把业务实现抽成双方都能调用的独立层;配置和连接池则各自进程各持一份。
收益:故障隔离(对外 HTTP 挂了不影响内部 RPC)、可以按流量独立扩缩容、安全边界更清晰。代价:原来零开销的进程内调用变成一次网络序列化、两份配置/日志/监控要维护、mcenter 里要注册两个实例、部署复杂度上升。
结论:中小规模下"单体双窗口"性价比最高;等流量和团队规模上来了再拆,这也是微服务演进的常见路径。
面试官
main.go 只有一行 cmd.Execute(),业务模块是靠什么机制"自动"被加载的?请按"匿名导入 → init() → Registry"链条,写出 host 模块从被感知到被 InitAllApp() 初始化的完整路径。
候选人
核心是 Go 的包机制 + mcube 的注册模式,链条是:
① cmd/start.go:24 匿名导入 apps 包;
② apps 包里的两个聚合文件 apps/api.go、apps/internal.go 又匿名导入了所有模块的 api/impl 子包(其中就有 apps/host/impl);
③ Go 保证包被导入就执行其 init(),于是 host/impl 的 init() 调用 app.RegistryGrpcApp(svr),把包级单例注册进框架;
④ 之后 app.InitAllApp() 遍历所有已注册模块,回调其 Config()——这一步才真正拿数据库连接、装配依赖;
⑤ 最后 LoadGrpcApp()/LoadRESTfulApp() 把实现分别装配到 gRPC Server 和 HTTP 路由上。
这个设计的工程价值:新增模块只需要在聚合文件加一行匿名导入,主程序和 protocol 层零改动——"微内核 + 插件"的雏形。
面试官
为什么优雅关闭要先停 gRPC 再停 HTTP,而不是反过来?如果反过来会发生什么?(提示:gRPC 停止时会向 mcenter 注销实例)
候选人
顺序设计的核心是让流量有序排空。grpc.Stop() 不是简单关闭:它会先向 mcenter 注销实例再 GracefulStop()。先停 gRPC,意味着注册中心第一时间把这个实例摘掉,内部服务不再发新请求过来,同时等存量请求处理完;这时 HTTP 还活着,外部请求还能被完整处理。之后再停 HTTP,且有 30 秒超时宽限兜底。
如果反过来:先停 HTTP,对外窗口立刻关闭,用户请求直接失败;而 gRPC 实例还挂在 mcenter 上"报活着",内部调用会继续打进来,等轮到 gRPC 关闭时,这些请求可能被拦腰截断——数据库事务提交一半,数据不一致的风险反而更大。
一句话总结:先摘"内部流量的入口",再关"对外的窗口",保证存量请求都能体面地跑完。

6 本章小结

本章我们从"一栋楼"的比喻出发,建立了 CMDB 的全局认知:入口层(cmd)→ 服务层(protocol,HTTP+gRPC 双窗口)→ 业务层(apps,模块自注册)→ 多云适配层(provider)。技术栈方面,go-restful 负责 HTTP、gRPC+protobuf 负责内部 RPC、GORM/原生 SQL 负责数据、mcube/mcenter 负责框架基建与鉴权。同时我们明确了项目的真实状态:20 个模块中 7 个已落地、dict 半落地(仅路由)、12 个是 proto 占位——这既是学习对象,也是二开练手场。

复习提示: - 启动五步曲顺序不可乱:配置 → 日志 → 缓存 → InitAllApp → 双服务启动;优雅关闭先 gRPC(注销 mcenter 实例)后 HTTP(30 秒宽限)。 - 双协议、一份业务:HTTP handler 复用 gRPC 实现;模块靠"匿名导入 → init() → Registry"自注册,main.go 保持零业务。 - 记住项目真实状态:20 个模块 7 个落地、dict 仅挂路由、12 个 proto 占位,provider 只管"拉数据、转格式"不写库。 - 下一章我们把项目真正跑起来——环境搭建与完整启动流程,动手时间到了。
About Me

没什么想介绍的,一个很大众的码农…

喜欢代码,车,马,真的是 🐎

讨厌别人让我给自己的代码写注释 最厌烦别人的程序没有写注释

目标

学AI,加油!加油!