- 新增.gitignore,忽略日志、node_modules、dist及编辑器配置文件 - 创建基础React应用及组件App,集成React、Vite和样式 - 设计响应式CSS变量和全局样式,支持暗黑模式 - 新增图标SVG符号定义,用于社交及文档链接展示 - 配置TypeScript项目(tsconfig)和ESLint规则保障代码质量 - 配置Vite开发服务器,支持代理接口与模块别名 - 增加前端API客户端封装与健康检查接口定义 - 增加类型定义文件,映射后端数据模型接口 - 创建index.html作为前端入口页面 - 新增package.json定义依赖和运行脚本,包含React及Ant Design等库
12 KiB
Phase 1: 项目骨架 & 基础设施 — 详细计划
所属主计划: main-plan.md 预计时间: 第 1-2 天 核心学习: Go 项目组织、package 管理、Gin 基础
学习目标
| Go 概念 | 说明 | Java 对照 |
|---|---|---|
go mod init / go mod tidy |
依赖管理 | Maven pom.xml / Gradle build.gradle,但更轻量 |
| package 组织 | 按功能分目录,每个目录一个 package | Java package 按目录组织 |
func main() |
程序入口 | public static void main(String[]) |
init() |
包初始化函数,main 之前自动执行 | Spring @PostConstruct / static initializer |
pointer * |
指针类型,传递引用而非拷贝 | Java 中除了基本类型,对象默认是引用 |
value & |
取地址 | Java 无直接对应 |
defer |
函数返回前执行,常用于清理资源 | try-finally 的 finally 块 |
| error 返回值 | 函数返回 error,调用方检查 | Java checked exception,但更显式 |
:= vs var |
短声明 vs 显式声明 | var 类型推断 |
| struct tag | 结构体字段上的元数据注解 | Java annotation(@JsonProperty 等) |
任务分解
Step 1.0: 初始化 Go Module
任务: 在项目根目录执行 go mod init,建立 Go 模块。
关键点:
- 模块路径已经在 CLAUDE.md 中定义为
synoth.com/janus - 不要用
github.com/xxx—— 这是学习项目,不需要托管到 GitHub go.mod相当于 Java Maven 的pom.xml,记录模块名、Go 版本、依赖
验证: cat go.mod 确认模块声明正确
Step 1.1: 安装核心依赖
任务: 通过 go get 安装以下依赖包:
| 包 | 用途 | Java 类比 |
|---|---|---|
github.com/gin-gonic/gin |
Web 框架,路由 + 中间件 | Spring MVC |
gorm.io/gorm |
ORM 核心库 | JPA / Hibernate |
gorm.io/driver/postgres |
GORM PostgreSQL 驱动 | PostgreSQL JDBC Driver |
github.com/spf13/viper |
配置管理(YAML + 环境变量) | Spring @ConfigurationProperties |
github.com/joho/godotenv |
加载 .env 文件到环境变量 |
dotenv-java |
关键点:
go get会自动更新go.mod和生成go.sum(类似pom.xml+ 依赖锁)go.sum记录每个依赖的校验和,确保可复现构建- 间接依赖(indirect)会被自动标记
验证: go.mod 中出现所有依赖,go.sum 非空
Step 1.2: 实现配置加载 — internal/config/config.go
任务: 用 Viper 加载 config.yaml + 环境变量覆盖。
需要定义的结构体:
// Config —— 顶层配置结构体
type Config struct {
Server ServerConfig `mapstructure:"server"`
Database DatabaseConfig `mapstructure:"database"`
}
// ServerConfig —— HTTP 服务器配置
type ServerConfig struct {
Port int `mapstructure:"port"` // 默认 8080
Mode string `mapstructure:"mode"` // debug / release / test
}
// DatabaseConfig —— PostgreSQL 连接配置
type DatabaseConfig struct {
Host string `mapstructure:"host"`
Port int `mapstructure:"port"`
User string `mapstructure:"user"`
Password string `mapstructure:"password"`
DBName string `mapstructure:"dbname"`
SSLMode string `mapstructure:"sslmode"`
}
需要实现的函数:
Load(path string) (*Config, error)— 加载配置文件- 支持环境变量覆盖(如
DATABASE_PASSWORD覆盖 YAML 中的明文密码)
Go 学习点:
mapstructuretag 是 Viper 专用的 struct tag,类似 Java@ConfigurationProperties(prefix="...")*Config返回指针而非值 — Go 中大型 struct 传指针避免拷贝- error 作为返回值,调用方必须处理
Viper 配置方式:
v := viper.New()
v.SetConfigFile(path) // 指定配置文件路径
v.AutomaticEnv() // 自动读取环境变量
v.SetEnvKeyReplacer(...) // 将 "." 替换为 "_"(database.host → DATABASE_HOST)
验证: 写一个简单的测试(可选),或者直接在 main 中调用 Load 打印结果
Step 1.3: 编写配置文件 — config.yaml
任务: 在项目根目录创建默认配置文件。
内容要点:
server:
port: 8080
mode: debug # Gin 模式:debug(带日志)/ release(生产)/ test
database:
host: localhost
port: 5432
user: janus
password: "" # 留空,通过环境变量或 .env 传入
dbname: janus
sslmode: disable # 本地开发关 SSL,生产用 require
Go 学习点:
- YAML 用缩进表示层级,没有 XML/JSON 的括号
- Viper 默认支持 YAML、JSON、TOML 等格式
- 敏感信息(密码)不写死在 YAML 中,通过环境变量注入
验证: 文件语法正确(可以用在线 YAML 验证器或 yamllint)
Step 1.4: 添加 .env 支持
任务: 创建 .env.example 模板文件,并在 main.go 中用 godotenv.Load() 加载。
.env.example 内容:
DATABASE_PASSWORD=your_password_here
关键点:
.env加入.gitignore,避免敏感信息提交.env.example提交到仓库,作为模板godotenv.Load()在main()最开始调用,失败不阻断启动(.env可能不存在)
Go 学习点:
godotenv.Load()读取.env并设置到进程环境变量- Viper 的
AutomaticEnv()自动读取环境变量,两者配合工作
验证: 创建 .env 文件后,os.Getenv("DATABASE_PASSWORD") 能读到值
Step 1.5: 实现 GORM 数据库连接
任务: 在 internal/config/ 或单独的 internal/database/ 中实现 DB 连接初始化。
需要实现的函数:
func NewDatabase(cfg *DatabaseConfig) (*gorm.DB, error)
实现要点:
- 构造 PostgreSQL DSN(Data Source Name):
host=localhost user=janus password=xxx dbname=janus port=5432 sslmode=disable gorm.Open(postgres.Open(dsn), &gorm.Config{})打开连接- 获取底层
*sql.DB,配置连接池参数:SetMaxOpenConns(25)— 最大打开连接数SetMaxIdleConns(10)— 最大空闲连接数SetConnMaxLifetime(5 * time.Minute)— 连接最大存活时间
db.Ping()验证连接是否可用
Go 学习点:
- GORM 的
gorm.Open()返回(*gorm.DB, error),惯例返回 error *sql.DB是 Go 标准库的数据库句柄,GORM 在其上构建- 连接池配置对标 Java HikariCP 的
maximumPoolSize、minimumIdle、maxLifetime defer不在这里用(连接需要在整个应用生命周期保持),而是在main.go中管理
验证: 启动程序,GORM 打印连接日志无报错
Step 1.6: 实现健康检查 Handler — internal/handler/health_handler.go
任务: 实现一个简单的健康检查端点。
代码结构:
package handler
import "github.com/gin-gonic/gin"
type HealthHandler struct {
db *gorm.DB // 用于检查数据库连接
}
func NewHealthHandler(db *gorm.DB) *HealthHandler { ... }
func (h *HealthHandler) Check(c *gin.Context) {
// 1. ping 数据库
// 2. 返回 JSON: { "status": "ok", "db": "connected" }
}
返回格式:
{
"status": "ok",
"timestamp": "2026-07-08T12:00:00Z",
"db": "connected"
}
Go 学习点:
(h *HealthHandler)是指针接收者(pointer receiver)— 方法可以修改 h 的状态gin.Context封装了 HTTP 请求和响应,类似 Spring 的HttpServletRequest+HttpServletResponsec.JSON(200, obj)自动设置 Content-Type 并序列化 JSON- Gin 的 handler 签名是
func(c *gin.Context),不使用返回值 — 通过c写响应
验证: curl http://localhost:8080/health 返回 JSON
Step 1.7: 实现路由注册 — internal/index/index.go
任务: 集中管理所有路由注册。
代码结构:
package index
func Setup(r *gin.Engine, handler *handler.HealthHandler) {
r.GET("/health", handler.Check)
}
Go 学习点:
- 函数接收
*gin.Engine(Gin 的路由引擎),在其上注册路由 - 这是"依赖注入"的最简形式 —— 手动传参,不用框架的 DI 容器
- 路由分离到单独文件,避免
main.go膨胀
验证: 路由文件编译通过,程序运行后 /health 可访问
Step 1.8: 实现程序入口 — cmd/server/main.go
任务: 组装所有依赖,启动 HTTP 服务器。
流程:
main()
├── godotenv.Load(".env") // 加载环境变量
├── config.Load("config.yaml") // 加载配置
├── database.NewDatabase(&cfg.Database) // 初始化 DB
├── handler.NewHealthHandler(db) // 创建 handler
├── gin.New() / gin.Default() // 创建 Gin 引擎
├── index.Setup(r, healthHandler) // 注册路由
└── r.Run(":8080") // 启动服务器
Gin 模式选择:
gin.Default()— 带 Logger 和 Recovery 中间件(开发推荐)gin.New()— 空白引擎,手动添加中间件
Go 学习点:
main函数必须在package main中,否则无法编译成可执行文件main()无参数、无返回值 — 退出用os.Exit(code)defer用于在 main 返回前关闭数据库连接等资源- 包的
init()函数在 main 之前自动执行(GORM 驱动注册就是通过 init)
验证: go run ./cmd/server/main.go 启动成功,访问 http://localhost:8080/health
Step 1.9: 创建 .gitignore
任务: 确保敏感文件和构建产物不提交。
必须忽略的内容:
# 环境变量
.env
# 构建产物
/server
/janus
*.exe
# IDE
.idea/
.vscode/
*.swp
# 依赖
vendor/
# 临时文件
tmp/
temp/
文件清单(Phase 1 产出)
janus/
├── cmd/server/main.go ← Step 1.8 实现
├── internal/
│ ├── config/
│ │ └── config.go ← Step 1.2 实现
│ ├── handler/
│ │ └── health_handler.go ← Step 1.6 实现
│ └── index/
│ └── index.go ← Step 1.7 实现
├── config.yaml ← Step 1.3 编写
├── .env.example ← Step 1.4 编写
├── .gitignore ← Step 1.9 编写
├── go.mod ← Step 1.0 生成
└── go.sum ← Step 1.1 生成
Phase 1 完成标准
go build ./...编译通过,无错误go vet ./...静态分析无警告- 程序启动后
curl http://localhost:8080/health返回{"status":"ok",...} - GORM 成功连接 PostgreSQL(或优雅报错"数据库未启动"而不 panic)
- 配置文件中的值被正确读取(改端口后服务在新端口启动)
.env中的密码能覆盖config.yaml中的空值
常见问题 & 排错
| 问题 | 可能原因 | 解决 |
|---|---|---|
go get 失败/慢 |
网络问题 | 设置 GOPROXY=https://goproxy.cn,direct |
cannot find package |
模块路径不对 | 检查 go.mod 的 module 名和 import 路径一致 |
| GORM 连接报错 | PostgreSQL 未启动 | pg_isready 检查,或用 Docker 启动 PostgreSQL |
import cycle not allowed |
循环依赖 | Go 禁止包之间循环引用,检查 import 关系 |
| Viper 读不到配置 | 路径问题 | 用绝对路径或相对于执行目录的路径 |
:= vs = 报错 |
短声明只能用于新变量 | 已声明的变量用 = 赋值 |
关键设计决策
- 手动依赖注入 — 不使用 wire/di 框架。Phase 1 依赖少,手动传参最清晰,也是 Go 社区的常见做法
- Config 用指针返回 —
func Load() (*Config, error)而非(Config, error),避免大 struct 拷贝 - health check 注入
*gorm.DB— 真实检查数据库连通性,而非返回假 OK gin.Default()而非gin.New()— 开发阶段带 Logger 和 Recovery 中间件更方便