程序员转型手记:PHP到AI+Golang目录与配置系统开发
时间:2026-07-24 | 作者:游戏探长 | 阅读:0
这是一个系列 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 完成初始化,这是 Go 项目的基础操作。
把之前准备好的 项目目录结构.md 和 编码风格最佳实践.md 放进 /doc 目录。接着切到 ai-go-mall 目录,执行 claude 命令,让它根据 @doc项目目录结构.md 创建需要的文件夹。
AI 申请授权时,只要不涉及越权执行命令、安装盗版依赖,或者使用不靠谱的中转站(防止中间人攻击),一般都可以同意。不过话说回来,最后每一行代码还得咱们自己过一遍。
.
├── 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 init
git config user.name "yang"
git config user.email "1094963513@qq.com"
如果不上传远程仓库,名字和邮箱随便写就行。接下来让 Claude 帮忙建个 .gitignore 文件,把 .DS_Store、*.css.map、*.local.* 这类常见忽略项都加进去。
生成的 .gitignore 内容如下:
# OS
.DS_Store
Thumbs.db
# IDE
.idea/
.vscode/
*.swp
*.swo
# Local config
*.local.*
.env
# Go
/bin/
/vendor/
*.exe
*.test
*.out
# Frontend
*.css.map
*.js.map
node_modules/
/dist/
# Build output
/tmp/
/log/
手动把 *.swo、*.swp 改成 *.sw,同时去掉了 .env 这条规则,因为后面会用到 .env.yaml,不能一起忽略。
设计并开发配置系统
对比了几种方案,最终选了 Viper 来管理配置。它是 Go 生态里最完整的配置解决方案,GitHub 上 30k 的 star 数就是最好的背书。 规划配置系统的核心思路如下: - 用github.com/spf13/viper 管理配置
- 初版包含 应用名称、服务端口号、数据库相关 配置项,一个配置文件搞定
- 所有配置文件用 YAML 格式,存放在 /config 目录下,配置系统能自动加载所有 YAML 文件,新增配置不需要改代码
- 额外加一个 .env.yaml 环境配置文件,优先级最高,用于开发环境自定义数据库配置,Git 需要忽略
- 再配一个 .env.yaml.example 文件,Git 不忽略,方便开发者快速创建自己的环境配置
- 数据库配置带上 type 字段,默认用 PostgreSQL,同时准备好连接池配置项
把这个规划扔给 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
检查一下目录结构,发现几个问题:
1. AI 把 .env.yaml 和 .env.yaml.example 放在了 /config 目录下。其实本意是让环境配置直接放在根目录。
2. 配置加载逻辑放在了 pkg/config.go,虽然合理,但更希望配置加载直接在 /config 目录下,最终结构是 /config/config.go + 多个 .yaml 文件。未来会迁到 internal 目录,那时 /config 只存 YAML 文件。
3. .env.yaml 里只有数据库配置,没有 server.port 和 app.name 配置项。
把这些问题发给 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
接下来是关键步骤——逐行理解代码,找出不足,或者按照自己的偏好做调整。找到的问题如下:
1. config.Load 函数需要传递 config 目录路径,但当前项目直接用 /config 目录即可,没必要传参。
2. config.Load 有“加载”的意思,在 main.go 调用,但改为 config.Init 函数更合理。
3. 没加载到任何配置文件时,不需要报错。
4. 数据库配置缺少 timezone 和 prefix(往期项目习惯加个 prefix 配置项,可以不用,但不能没有)。
5. 不需要对数据库配置做任何预处理,后续在连接数据库的函数里处理。
6. 研究后,要求 AI 提供一个 config.Get 函数,用于获取配置项(现在是 cfg.App.Name,改为 config.Get().App.Name),前者配置可能被外部修改,包装一个方法扩展性更好。
7. 对环境变量进行加载,并覆盖已有配置项,即调用 viper.AutomaticEnv()(不是加载项目的 .env.yaml 环境配置文件)。环境变量可以通过以下方式动态设定:
# Linux / Mac
SERVER_PORT=8080 APP_NAME=myapp go run main.go
# Windows CMD
set 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 配置项。AI 还修改了 go.mod 和 .gitignore 等文件,其中 .gitignore 的修改如下:
# .gitignore
- .env.*
+ .env.yaml
+ !.env.yaml.example
修改很合理,但 + !.env.yaml.example 是多余的,直接删除即可。
继续让 AI 按需求修改,经过几轮对话后,配置系统开发完成。完整代码开源于:github | gitee,核心代码如下:
1. 让 AI 增加了监听配置改变,以及配置数据的读写锁。
2. 监听配置改变时,当新配置为空可能不会覆盖,研究后添加了 c.ZeroFields = true 选项。
3. 同时确认了:结构体定义代码在最上面,是 Golang 项目的通用做法。
// main.go
package main
import (
"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-mall
server:
port: 8080
database:
type: postgres
host: 127.0.0.1
port: 5432
user: postgres
password: "123456"
dbname: aigo
prefix:
sslmode: disable
timezone: Asia/Shanghai
max_open_conns: 25
max_idle_conns: 10
conn_max_lifetime: 300 # 秒
// config.go
package config
import (
"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 {
Type string
Host string
Port int
User string
Password string
Prefix string
DBName string `mapstructure:"dbname"`
SSLMode string `mapstructure:"sslmode"`
Timezone string `mapstructure:"timezone"`
MaxOpenConns int `mapstructure:"max_open_conns"`
MaxIdleConns int `mapstructure:"max_idle_conns"`
ConnMaxLifetime int `mapstructure:"conn_max_lifetime"`
}
// Config 聚合配置
type Config struct {
App App
Server Server
Database Database
}
var (
vip *viper.Viper
config = 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
}
免责声明:文中图文均来自网络,如有侵权请联系删除,心愿游戏发布此文仅为传递信息,不代表心愿游戏认同其观点或证实其描述。
相关文章
更多-
- PHP创建数据库的SQL语句教程
- 时间:2026-08-23
-
- PHP安全替换配置文件内容的方法与实战技巧
- 时间:2026-08-20
-
- PHP后台管理系统开发从入门到精通实战指南
- 时间:2026-08-19
-
- PHP中如何验证含星号通配符的括号字符串是否有效
- 时间:2026-08-18
-
- PHP中如何验证带星号通配符的括号字符串是否有效
- 时间:2026-08-18
-
- PHP中按分号分割含嵌套分号的多条SQL语句方法
- 时间:2026-08-18
-
- PHP循环中下拉框选项变更事件绑定方法及键盘导航支持
- 时间:2026-08-18
-
- PHP表单提交多个选中月份并显示对应数据的方法
- 时间:2026-08-18
精选合集
更多大家都在玩
大家都在看
更多-
- 为什么湿头发更容易断裂 蚂蚁庄园今日答案9.15
- 时间:2026-09-14
-
- 蚂蚁庄园今天答题答案2026年9月15日
- 时间:2026-09-14
-
- 蚂蚁庄园答题今日答案2026年9月15日
- 时间:2026-09-14
-
- 蚂蚁庄园小课堂2026年9月15日最新题目答案
- 时间:2026-09-14
-
- 小鸡答题今天的答案是什么2026年9月15日
- 时间:2026-09-14
-
- 蚂蚁庄园每日答题答案2026年9月15日
- 时间:2026-09-14
-
- 糖尿病患者禁食所有含糖食物吗 蚂蚁庄园今日答案9月15日
- 时间:2026-09-14
-
- 2026年9月14日蚂蚁新村答案
- 时间:2026-09-14
