从 PHP 到 AI + Golang,程序员自救转型手记(二):目录结构:初始化 GIT:设计并开发配置系统
这是一个系列 Blog,作者将以一个 PHP 全栈工程师的身份,利用 AI 工具(claude code、codex、deepseek、豆包等):从零开始学习 golang 语言,并最终完成 ai-go-mall(github | gitee)开源项目的制作,全程记录分享。
在上一期,我们已经讲到必须学什么,如何快速学习,以及完成立项前准备,本期将完成 ai-go-mall 的目录结构创建、初始化 GIT、设计并开发配置系统。
目录结构
- 创建
ai-go-mall目录,并执行go mod init ai-go-mall命令完成初始化 - 下载第一期准备好的
项目目录结构.md和编码风格最佳实践.md放入/doc目录 - 切换到
ai-go-mall目录,执行claude命令,直接让它:根据 @doc项目目录结构.md 创建需要的文件夹 - AI 需要授权时,一般选择同意,需要防止越过项目执行命令,防止安装盗版依赖,不建议使用中转站以防止中间人攻击,最后我们会对每一行代码进行
review。
.├── go.mod├── .claude/│ └── settings.local.json├── cmd/│ └── api/├── config/├── doc/│ ├── 项目目录结构.md│ └── 编码风格最佳实践.md├── internal/│ ├── handler/│ ├── model/│ ├── repository/│ └── service/├── pkg/│ ├── logger/│ └── validator/└── script/
- 直接将短时间应该用不上的目录删除,作者这里删除了
pkg和script,同时创建缺少的cmd/api/main.go文件
初始化 GIT
git 是全球领先的分布式版本控制系统,程序员必备,不会的快去学,很简单。
git initgit config user.name "yang"git config user.email "[email protected]"
如果不需要上传至远程仓库备份的话,name 和 email 可以随便写,接下来要求 claude:帮我建立 .gitignore 文件,忽略 .DS_Store、.css.map、.local.* 等通常需要忽略的文件。
作者这边生成的 .gitignore 文件内容如下:
# OS.DS_StoreThumbs.db# IDE.idea/.vscode/*.swp*.swo# Local config*.local.*.env.*# Go/bin//vendor/*.exe*.test*.out# Frontend*.css.map*.js.mapnode_modules//dist/# Build output/tmp//log/
作者手动将 *.swo、*.swp 改为了 *.sw?,去掉了 .env
设计并开发配置系统
经过多方对比,决定选择 Viper 管理配置,它是适用于 Go 应用程序的完整配置解决方案,github 上有 30k star,非常完善和通用。
规划配置系统(直接复制以下规划,让 cc 帮助实现):
- 使用 github.com/spf13/viper 管理配置
- 初版有
应用名称、开发服务端口号、数据库相关配置项,放在一个配置文件内即可 - 所有配置文件,使用 YAML 格式保存于 /config 目录下,配置系统能自动加载所有 YAML 配置文件,增加配置文件时,不需要手动改代码
- 额外一个 .env.yaml 环境配置文件,优先级最高,用于开发环境自定义数据库配置,GIT 需要忽略此文件
- 额外一个 .env.yaml.example 文件,GIT 不忽略,用于方便开发者将项目拉到本地之后,快速创建 .env.yaml 文件
- 数据库配置细节:带有 type 字段表示数据库类型,默认使用 PostgreSQL 数据库;带有 max_open_conns 等连接池配置;
AI 工作完成后,我们得到以下输出:
# 大模型是概率生成,不是固定查表,再加上采样策略、上下文、版本等影响,同问题输出都会有差异,以下 AI 输出、包括生成的代码仅供参考,可直接忽略,继续向下查看如何寻找问题和完成整改配置系统已实现完毕。以下是最终文件结构和验证结果:config/├── config.yaml # 主配置文件(Git 跟踪)├── .env.yaml # 本地环境配置(Git 忽略)└── .env.yaml.example # 环境配置示例(Git 跟踪)pkg/config/└── config.go # 配置加载逻辑cmd/api/└── main.go # 入口,调用配置系统核心设计:- config.Load("config") 自动扫描 /config 目录下所有 .yaml 文件,按文件名排序后逐个 Merge,.env.yaml 排最后加载,值覆盖前面的文件- 新增配置文件无需改代码,放入 /config 目录即可- 数据库默认类型为 PostgreSQL,连接池默认值 max_open=25 / max_idle=10 / max_lifetime=5m- 提供 ConnMaxLifetimeDuration() 方法将字符串转为 time.Duration验证结果:┌─────────────────┬─────────────┬─────────────────────┐│ 配置项│ config.yaml │ .env.yaml(覆盖后) │├─────────────────┼─────────────┼─────────────────────┤│ app.name│ ai-go-mall│ ai-go-mall│├─────────────────┼─────────────┼─────────────────────┤│ server.port │ 8080│ 8080│├─────────────────┼─────────────┼─────────────────────┤│ database.host │ localhost │ 192.168.1.100 │├─────────────────┼─────────────┼─────────────────────┤│ database.port │ 5432│ 5433│├─────────────────┼─────────────┼─────────────────────┤│ database.dbname │ ai-go-mall│ ai-go-mall-dev│└─────────────────┴─────────────┴─────────────────────┘✻ Churned for 2m 50s
检查结果,从目录结构的修改看起,发现一些问题:
- AI 将
.env.yaml和.env.yaml.example放入了/config目录,这与预期不符,其实我的本意是希望环境配置直接在根目录 - 然后是配置文件的加载逻辑放在了
pkg/config.go,这虽然很合理,但是我更希望配置加载直接在/config目录以内,即最终得到/config/config.go + 多个 .yaml 文件的目录结构(未来会迁移到internal目录,最终/config只存储yaml文件)。 - .env.yaml 里边只有数据库配置,没有 server.port 和 app.name 配置项
将以上发现直接发给 cc,让它继续修改,由于目录结构错误,暂时未对配置加载的代码进行 review:
# 大模型是概率生成,不是固定查表,再加上采样策略、上下文、版本等影响,同问题输出都会有差异,以下 AI 输出、包括生成的代码仅供参考,可直接忽略,继续向下查看如何寻找问题和完成整改三个问题已全部修正,最终结构如下:.├── .env.yaml# 根目录,Git 忽略 ✓├── .env.yaml.example# 根目录,Git 跟踪,含全部配置项 ✓├── .gitignore├── config/│ ├── config.go# 配置加载逻辑 ✓│ └── config.yaml# 主配置文件└── cmd/api/main.go改动要点:┌──────────────────────────────────┬───────────────────────────────────────────────────────────┐│ 问题 │ 修正│├──────────────────────────────────┼───────────────────────────────────────────────────────────┤│ .env.yaml 放入 /config │ 移至根目录│├──────────────────────────────────┼───────────────────────────────────────────────────────────┤│ config.go 放入 /pkg/config │ 移至 /config/config.go,import 路径变为 ai-go-mall/config │├──────────────────────────────────┼───────────────────────────────────────────────────────────┤│ .env.yaml.example 仅含数据库配置 │ 补充了 app.name、server.port 及数据库全套配置 │└──────────────────────────────────┴───────────────────────────────────────────────────────────┘加载顺序: config/*.yaml → 根目录 .env.yaml(最后加载,覆盖前面的值)。.env.yaml 不存在时优雅降级,不报错。✻ Worked for 1m 24s
接下来的工作就非常重要了,为了学习和商业级项目的目标,我们需要逐行理解代码,并找出代码中的不足,或者就特意的做一些自己更加喜欢的修改,作者找到的问题如下:
- config.Load 函数需要传递 config 目录路径,这在当前项目是不需要的,固定 /config 目录即可
- config.Load 有加载的意思,在 main.go 中调用,但我认为改为 config.Init 函数更合理
- 未加载到任何配置文件时,不需要报错
- 数据库配置缺失
timezone和prefix(历往项目习惯有个prefix配置项,可以不用,但可以有) - 不需要对数据库配置进行任何预处理,后续于连接数据库函数内处理
- 经过多方研究,再要求 AI 提供一个
config.Get函数,用于获取配置项(现在是cfg.App.Name,改为config.Get().App.Name),前者配置可以被外部修改,且包装一个方法扩展性总是更高一些的 - 对环境变量进行加载,并覆盖值到已有配置项,即调用
viper.AutomaticEnv()(非加载项目的 .env.yaml 环境配置文件),这种环境变量可以通过以下方式动态设定:
# Linux / MacSERVER_PORT=8080 APP_NAME=myapp go run main.go# Windows CMDset SERVER_PORT=8080&& set APP_NAME=myapp&& go run main.go# Windows PowerShell$env:SERVER_PORT="8080"; $env:APP_NAME="myapp"; go run main.go
动态设定的 APP_NAME 会覆盖到 app.name 配置项,cc 还修改了 go.mod 和 .gitignore 等文件,其中对 .gitignore 的修改如下:
# .gitignore- .env.*+ .env.yaml+ !.env.yaml.example
修改很合理,但是 + !.env.yaml.example 是多余的,直接删除即可。
继续要求 AI 按需求进行修改,再经过几轮对话之后,配置系统开发完成,完整代码开源于:github | gitee,核心代码如下:
- 又让 AI 增加了监听配置改变,以及配置数据的读写锁
- 监听配置改变时,当新配置为空可能不会覆盖,经研究后添加了
c.ZeroFields = true选项 - 同时询问了 AI,确定:结构体定义代码在最上面,是 golang 项目通用做法
// main.gopackage mainimport ("fmt""log""ai-go-mall/config")func main() {// 初始化配置if err := config.Init(); err != nil {log.Fatalf("config init: %v", err)}cfg := config.Get()fmt.Printf("应用名称: %sn", cfg.App.Name)fmt.Printf("服务端口: %dn", cfg.Server.Port)fmt.Printf("数据库类型: %sn", cfg.Database.Type)fmt.Printf("数据库地址: %s:%dn", cfg.Database.Host, cfg.Database.Port)fmt.Printf("数据库名称: %sn", cfg.Database.DBName)fmt.Printf("连接池配置: max_open=%d max_idle=%d max_lifetime=%dn",cfg.Database.MaxOpenConns,cfg.Database.MaxIdleConns,cfg.Database.ConnMaxLifetime,)}
app:name: ai-go-mallserver:port: 8080database:type: postgreshost: 127.0.0.1port: 5432user: postgrespassword: "123456"dbname: aigoprefix: sslmode: disabletimezone: Asia/Shanghaimax_open_conns: 25max_idle_conns: 10conn_max_lifetime: 300 # 秒
// config.gopackage configimport ("fmt""log""os""path/filepath""sync""github.com/fsnotify/fsnotify""github.com/go-viper/mapstructure/v2""github.com/spf13/viper")// App 应用配置type App struct {Name string}// Server 服务配置type Server struct {Port int}// Database 数据库配置type Database struct {TypestringHoststringPortintUserstringPasswordstringPrefixstringDBNamestring `mapstructure:"dbname"`SSLMode string `mapstructure:"sslmode"`Timezonestring `mapstructure:"timezone"`MaxOpenConnsint`mapstructure:"max_open_conns"`MaxIdleConnsint`mapstructure:"max_idle_conns"`ConnMaxLifetime int`mapstructure:"conn_max_lifetime"`}// Config 聚合配置type Config struct {AppAppServer ServerDatabase Database}var (vip*viper.Viperconfig = new(Config)mu sync.RWMutex)// 初始化配置func Init() error {if vip == nil {vip = viper.New()// 读取 config 目录下所有 yaml 文件files, err := filepath.Glob(filepath.Join("./config", "*.yaml"))if err != nil {return fmt.Errorf("get config files: %w", err)}for _, file := range files {vip.SetConfigFile(file)if err := vip.MergeInConfig(); err != nil {return fmt.Errorf("merge config file %s: %w", file, err)}}// 加载根目录 .env.yaml 文件(覆盖从 config 文件夹读取的配置)if _, err := os.Stat(".env.yaml"); err == nil {vip.SetConfigFile(".env.yaml")if err := vip.MergeInConfig(); err != nil {return fmt.Errorf("merge config file .env.yaml: %w", err)}}// 支持环境变量覆盖vip.AutomaticEnv()// 解析到结构体if err := vip.Unmarshal(config); err != nil {return fmt.Errorf("unmarshal config: %w", err)}// 监听配置改变vip.OnConfigChange(func(e fsnotify.Event) {mu.Lock()defer mu.Unlock()if err := vip.Unmarshal(config, func(c *mapstructure.DecoderConfig) {c.ZeroFields = true}); err != nil {log.Printf("config reload failed: %v", err)}})vip.WatchConfig()}return nil}// 返回全局配置副本。func Get() Config {mu.RLock()defer mu.RUnlock()return *config}// 获取 Viper 全局单例func GetViper() *viper.Viper {return vip}// 是否已经初始化func IsInit() bool {return vip != nil}
-
07.24
鹅鸭杀游戏基础规则介绍 让你领略不同的杀人游戏乐趣
-
07.24
鹅鸭杀阵容如何搭配合理 国服开黑组队阵容搭配技巧
-
07.24
《鹅鸭杀》游戏新手入门和发言技巧教学 怎么快速上手鹅鸭杀
-
07.24
鹅鸭杀如何创建房间
-
07.24
《鹅鸭杀》7人快节奏板子游戏攻略 乱斗场上杀手是谁
-
07.24
战术小队破晓攻势上线时间揭晓 战术小队破晓攻势全新内容解析
-
- 古今2风起蓬莱剑魔如何搭配阵容
- 07.24
-
- 明日方舟雅赛努斯复仇记活动玩法是什么
- 07.24
-
-
-
- Kimi K3 修Bug: 越修越多!
- 07.24
-
- 企业级AI知识库的技术架构应该怎么设计
- 07.24
-
-
下载
- |
-
-
下载
- 《行尸走肉第一章》免安装中文汉化硬盘版下载
- 单机|436 MB
- 一款以动作冒险为主题的游戏
-
-
下载
- 《街头霸王X铁拳》免安装中文汉化硬盘版下载
- 单机|111MB
- 一款非常好玩的格斗游戏
-
-
下载
- |
-
-
下载
- 《暗黑破坏神3》免安装繁体中文正式版下载
- 单机|7630 MB
- 一款以角色扮演为主题的游戏
-
-
下载
- 《马克思佩恩3》免安装硬盘版下载
- 单机|27033 MB
- 一款以第三人称射击为主题的游戏