本项目采用标准的 Go 项目布局,遵循 Go 社区最佳实践,实现模块化设计、单一职责原则和高内聚、低耦合。
gitlab-cli-sdk/
├── cmd/
│ └── gitlab-cli/ # 命令行程序入口
│ └── main.go # main 函数 (19 行)
├── internal/ # 内部包(仅供本项目使用)
│ ├── cli/
│ │ └── cmd.go # CLI 命令定义和编排 (181 行)
│ ├── config/
│ │ └── config.go # 配置加载和凭证管理 (49 行)
│ ├── processor/
│ │ └── processor.go # 业务逻辑处理器 (329 行)
│ └── utils/
│ └── utils.go # 工具函数 (8 行)
├── pkg/ # 公共包(可被外部项目使用)
│ ├── client/
│ │ └── client.go # GitLab API 客户端封装 (187 行)
│ └── types/
│ └── types.go # 数据结构定义 (32 行)
├── docs/ # 文档
│ ├── ARCHITECTURE.md # 架构文档(本文件)
│ ├── QUICKSTART.md # 快速开始指南
│ └── README.md # 详细使用说明
├── bin/ # 编译输出目录
├── go.mod # Go 模块定义
├── go.sum # Go 依赖锁定
├── Makefile # 构建脚本
└── README.md # 项目说明
项目采用经典的分层架构,从上到下依次为:
┌─────────────────────────────────────┐
│ cmd/gitlab-cli (入口层) │ ← main 函数
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ internal/cli (命令层) │ ← Cobra 命令定义
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ internal/processor (业务逻辑层) │ ← 核心业务逻辑
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ pkg/client (API 客户端层) │ ← GitLab API 封装
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ GitLab REST API │ ← 外部服务
└─────────────────────────────────────┘
文件: cmd/gitlab-cli/main.go
职责: 应用程序启动入口
package main
import (
"gitlab-cli-sdk/internal/cli"
"gitlab-cli-sdk/internal/config"
)
func main() {
cfg := &config.CLIConfig{}
rootCmd := cli.BuildRootCommand(cfg, Version)
rootCmd.Execute()
}特点:
- 极简设计,只包含启动逻辑
- 定义应用版本号
- 遵循 Go 标准项目布局的
cmd/目录约定
文件: internal/cli/cmd.go
职责: CLI 命令构建和业务流程编排
主要函数:
BuildRootCommand()- 构建根命令buildUserCommand()- 构建用户管理命令组buildUserCreateCommand()- 用户创建命令buildUserCleanupCommand()- 用户清理命令runUserCreate()- 执行用户创建流程runUserCleanup()- 执行用户清理流程initializeClient()- 初始化 GitLab 客户端
设计特点:
- 使用构建器模式组织命令树
- 命令定义与业务逻辑分离
- 流程编排清晰,易于理解
- 将具体业务逻辑委托给
processor
文件: internal/config/config.go
职责: 配置加载和凭证管理
主要类型和函数:
type CLIConfig struct {
ConfigFile string
GitLabHost string
GitLabToken string
}
func LoadGitLabCredentials(cfg *CLIConfig) error
func Load(configFile string) (*types.UserConfig, error)设计特点:
- 支持环境变量和命令行参数
- 环境变量优先级处理
- YAML 配置文件解析
- 统一的错误处理
文件: internal/processor/processor.go
职责: 封装资源操作的核心业务逻辑
主要类型:
type ResourceProcessor struct {
Client *client.GitLabClient
}创建流程方法:
ProcessUserCreation()- 用户创建总协调ensureUser()- 确保用户存在(幂等操作)createGroups()- 批量创建组ensureGroup()- 确保组存在(幂等操作)createProjects()- 批量创建项目
清理流程方法:
ProcessUserCleanup()- 用户清理总协调deleteConfiguredGroups()- 删除配置的组deleteProjects()- 删除项目verifyGroupsDeletion()- 验证组删除deleteUserOwnedGroups()- 删除用户拥有的其他组verifyUserGroupsDeletion()- 验证用户组删除deleteUser()- 删除用户并验证
设计特点:
- 使用依赖注入(Client 字段)
- 每个方法职责单一,不超过 50 行
- 完善的日志输出
- 幂等性设计(ensure* 方法)
- 异步验证机制
文件: internal/utils/utils.go
职责: 通用工具函数
func GetVisibility(v string) string设计特点:
- 无状态纯函数
- 可独立测试
- 易于复用
文件: pkg/client/client.go
职责: GitLab API 客户端封装
主要类型:
type GitLabClient struct {
client *gitlab.Client
}API 方法分类:
用户操作:
NewGitLabClient()- 创建客户端CheckAuth()- 检查认证和权限GetUser()- 获取用户CreateUser()- 创建用户DeleteUser()- 删除用户
组操作:
GetGroup()- 获取组CreateGroup()- 创建组DeleteGroup()- 删除组ListUserGroups()- 列出用户拥有的组
项目操作:
GetProject()- 获取项目CreateProject()- 创建项目DeleteProject()- 删除项目
设计特点:
- 封装官方 GitLab SDK
- 统一的错误处理
- 404 状态码特殊处理
- 类型安全
- 作为
pkg/包,可被外部项目复用
文件: pkg/types/types.go
职责: 数据结构定义
type UserConfig struct {
Users []UserSpec `yaml:"users"`
}
type UserSpec struct {
Username string `yaml:"username"`
Email string `yaml:"email"`
Name string `yaml:"name"`
Password string `yaml:"password"`
Groups []GroupSpec `yaml:"groups"`
}
type GroupSpec struct {
Name string `yaml:"name"`
Path string `yaml:"path"`
Visibility string `yaml:"visibility"`
Projects []ProjectSpec `yaml:"projects"`
}
type ProjectSpec struct {
Name string `yaml:"name"`
Path string `yaml:"path"`
Description string `yaml:"description"`
Visibility string `yaml:"visibility"`
}设计特点:
- 清晰的数据结构
- YAML 映射标签
- 作为
pkg/包,可被外部项目复用
本项目严格遵循 Go 的包可见性约定:
- 特点: 仅供本项目内部使用,不能被外部项目导入
- 包含:
cli,config,processor,utils - 原因: 这些包包含项目特定的业务逻辑和配置,不应被外部复用
- 特点: 可以被其他项目导入和复用
- 包含:
client,types - 原因:
client提供了通用的 GitLab API 封装,其他项目可能需要类似功能types定义了 GitLab 资源的数据结构,可供其他工具使用
cmd/gitlab-cli/main.go
├── internal/cli
│ ├── internal/config
│ │ └── pkg/types
│ └── internal/processor
│ ├── pkg/client
│ ├── pkg/types
│ └── internal/utils
└── internal/config
└── pkg/types
pkg/client (独立,可被外部使用)
pkg/types (独立,可被外部使用)
- 每个包只负责一个功能域
- 每个文件只包含相关的功能
- 每个函数只做一件事
processor依赖于client接口,而不是具体实现- 使用依赖注入便于测试
- 新增命令:在
cli包中添加新的命令构建函数 - 新增资源类型:在
processor中添加新的处理方法 - 不需要修改现有代码
- Client 提供明确的 API 方法
- 每个方法功能单一
- 相关功能集中在同一包中
- 包之间通过清晰的接口交互
- 单向依赖关系
| 指标 | 重构前 | 重构后 | 改进 |
|---|---|---|---|
| 文件数 | 3 个 | 7 个包 + 7 个文件 | 模块化 ✅ |
| main.go | 535 行 | 19 行 | ↓ 96% |
| 最大文件 | 535 行 | 329 行 | ↓ 39% |
| 最大函数 | ~200 行 | ~50 行 | ↓ 75% |
| 包可见性 | 无 | internal/pkg 分离 | ✅ |
| 可复用性 | 低 | 高 (pkg/) | ✅ |
- 在
internal/cli/cmd.go中添加命令构建函数:
func buildXxxCommand(cfg *config.CLIConfig) *cobra.Command {
// 命令定义
}- 添加执行函数:
func runXxx(cfg *config.CLIConfig) error {
// 业务流程编排
}- 在
internal/processor/processor.go中添加业务逻辑:
func (p *ResourceProcessor) ProcessXxx(...) error {
// 具体实现
}- 在
pkg/types/types.go中定义数据结构 - 在
pkg/client/client.go中添加 API 方法 - 在
internal/processor/processor.go中添加处理逻辑 - 在
internal/cli/cmd.go中添加命令
// internal/processor/processor_test.go
package processor_test
import (
"testing"
"gitlab-cli-sdk/internal/processor"
"gitlab-cli-sdk/pkg/client"
)
type mockClient struct {
client.GitLabClient
}
func TestProcessUserCreation(t *testing.T) {
mockClient := &mockClient{}
proc := &processor.ResourceProcessor{Client: mockClient}
// 测试逻辑
}- 包名: 小写单数(
config,client, 不是configs,clients) - 导出类型: 大驼峰(
GitLabClient,UserConfig) - 导出函数: 大驼峰(
NewGitLabClient,LoadConfig) - 私有函数: 小驼峰(
ensureUser,deleteProjects) - 接口: 以
er结尾(如Reader,Writer)
- 每个导出的类型、函数都有注释
- 注释以类型/函数名开头
- 说明功能,不说明实现
- 使用中文提高团队可读性
- 使用
fmt.Errorf包装错误并添加上下文 - 在调用链中逐层添加信息
- 不吞噬错误
- 记录关键操作的错误日志
- 使用
log.Printf输出结构化日志 - 使用 emoji 增强可读性(✓, ⚠)
- 记录操作进度和结果
- 使用 GitLab SDK 的批量 API(如
ListGroups) - 实现资源验证的重试机制
- 合理的超时和等待时间
- Token 通过环境变量传递
- 不在日志中输出敏感信息
- 使用 sudo 模式确保权限隔离
- Go 项目标准布局
- 《Code Complete》第 7 章 - 高质量的子程序
- 《Clean Code》第 3 章 - 函数
- 《重构:改善既有代码的设计》
- Effective Go