# GoCommon 业务项目对接操作手册 本文档供**引用 GoCommon 的业务项目**使用。按本手册对接即可,无需重复沟通基础设施实现方式。 模块路径:`git.toowon.com/jimmy/go-common` > 本文档只描述**目标架构**,重构时一步到位,**不提供过渡期用法,不考虑向后兼容**。 --- ## 1. 对接原则 | 原则 | 说明 | |------|------| | Factory 只是入口 | 启动时初始化一次,通过 getter 获取各模块**对象** | | 能力在模块自身 | 使用 `log.Info()`、`db.Find()`、`store.Upload()`,不在 Factory 上堆透传方法 | | 按需引用 | 用什么模块取什么对象;无状态工具直接 `import tools` | | 不重写基础设施 | 数据库连接、Redis 客户端、统一 HTTP 出参、中间件链由 GoCommon 提供 | | 业务只管业务 | Service 返回数据;Handler 交给 `http.Handler` 出参;Migration 只写 SQL 文件 | --- ## 2. 环境准备 ### 2.1 配置私有模块 ```bash go env -w GOPRIVATE=git.toowon.com go env -w GONOPROXY=git.toowon.com go env -w GONOSUMDB=git.toowon.com ``` ### 2.2 Git 认证(SSH 推荐) ```bash git config --global url."git@git.toowon.com:".insteadOf "https://git.toowon.com/" ``` ### 2.3 安装依赖 ```bash go get git.toowon.com/jimmy/go-common@v2.0.0 ``` 在 `go.mod` 中: ```go require git.toowon.com/jimmy/go-common v2.0.0 ``` 配置示例见 [`config/example.json`](./config/example.json)。 --- ## 3. 推荐项目结构 ```text your-project/ ├── config.json ├── cmd/ │ ├── server/main.go │ └── migrate/main.go # 从 templates/migrate/main.go 复制 ├── migrations/ │ └── 20240101000001_create_users.sql ├── locales/ # i18n(可选) │ ├── zh-CN.json │ └── en-US.json ├── internal/ │ ├── handler/ │ └── service/ └── go.mod ``` --- ## 4. 启动初始化(只做一次) ```go package main import ( "log" "net/http" "git.toowon.com/jimmy/go-common/factory" ) func main() { if err := factory.Init("config.json"); err != nil { log.Fatal(err) } app := factory.Default() chain := app.MiddlewareChain() chain.Append(yourAuthMiddleware) userSvc, err := NewUserService(app) if err != nil { log.Fatal(err) } http.Handle("/api/users", chain.ThenFunc(func(w http.ResponseWriter, r *http.Request) { listUsers(w, r, userSvc, app) })) log.Fatal(http.ListenAndServe(":8080", nil)) } ``` **禁止**在每个 Handler 里 `NewFromFile` / `Init`。全局只初始化一次,通过 `factory.Default()` 或注入 `*factory.Factory`。 --- ## 5. 获取模块对象(Factory getter) 连接与客户端由 Factory **lazy 初始化并缓存**。业务侧**不要**自行 `gorm.Open` / `redis.NewClient`。 | 模块 | API | 用法 | |------|-----|------| | 配置 | `app.Config()` | 读原始配置 | | 数据库 | `app.Database()` | `db.Find(&users)`、`db.Transaction(...)` | | Redis | `app.Redis()` | `rds.Set(ctx, key, val, ttl)` | | 日志 | `app.Logger()` | `log.Info(...)`,退出前 `log.Close()` | | 存储 | `app.Storage()` | `store.Upload(ctx, key, reader)` | | 邮件 | `app.Email()` | `mail.SendEmail(...)` | | 短信 | `app.SMS()` | `sms.SendSMS(...)` | | Excel | `app.Excel()` | `ex.ExportToFile(...)` | | 国际化 | `app.I18n()` | 供 `http.Handler` 注入;或 `i18n.GetMessage(...)` | | 中间件链 | `app.MiddlewareChain()` | `chain.Append(...).ThenFunc(...)` | | 迁移 | `app.Migrator("migrations")` | `m.Up()` / `m.Status()` / `m.Down()` | ### 注入 Service 示例 ```go type UserService struct { db *gorm.DB rds *redis.Client } func NewUserService(app *factory.Factory) (*UserService, error) { db, err := app.Database() if err != nil { return nil, err } rds, err := app.Redis() if err != nil { return nil, err } return &UserService{db: db, rds: rds}, nil } func (s *UserService) List(page, size int) (users []User, total int64, err error) { s.db.Model(&User{}).Count(&total) err = s.db.Offset((page-1)*size).Limit(size).Find(&users).Error return } ``` `config.json` 未配置的模块,调用 getter 会返回错误——**只配置使用的模块即可**。 --- ## 6. HTTP 统一出参 ### 6.1 职责划分 | 层级 | 做什么 | |------|--------| | Service | 返回 `[]User`、`total`、业务 error | | Handler | 解析请求、调 Service、通过 `http.Handler` 出参 | | `http.Handler` | PageData → Response → JSON(结构 / 编码 / 语种 / 时间统一) | **禁止**在 Service 拼 JSON,**禁止**在业务项目自定义 `Response` 结构,**禁止**经 Factory 出参(无 `app.Success` / `app.Error`)。 ### 6.2 标准响应格式 ```json { "code": 0, "message": "success", "timestamp": 1704067200, "data": {} } ``` 分页时 `data`: ```json { "list": [], "total": 100, "page": 1, "pageSize": 20 } ``` 类型定义在 `http` 包:`http.Response`、`http.PageData`。 ### 6.3 Handler 用法(唯一方式) 中间件链须包含 `Language`、`Timezone`(`MiddlewareChain()` 已默认组装)。 ```go import commonhttp "git.toowon.com/jimmy/go-common/http" func listUsers(w http.ResponseWriter, r *http.Request, svc *UserService, app *factory.Factory) { i18n, _ := app.I18n() h := commonhttp.NewHandler(w, r, commonhttp.WithI18n(i18n)) var req ListUserRequest if err := h.ParseJSON(&req); err != nil { h.Error("common.invalid_request") return } p := h.Pagination() users, total, err := svc.List(p.GetPage(), p.GetPageSize()) if err != nil { h.Error("user.list_failed") return } h.SuccessPage(users, total) } ``` `http.Handler` 统一负责: - `Content-Type: application/json; charset=utf-8` - 从 context 读取语种、时区 - 消息码(如 `user.not_found`)经 i18n 转为文案与业务 code - `timestamp` 按统一时区策略写入 ### 6.4 请求头约定 | Header | 用途 | |--------|------| | `Accept-Language` | 响应消息语种 | | `X-Timezone` | 时区(默认 `Asia/Shanghai`) | --- ## 7. 数据库迁移 执行框架已封装,业务**只写 SQL 文件**。 ### 7.1 推荐:独立 migrate 命令(与 Web 解耦) ```bash cp templates/migrate/main.go cmd/migrate/main.go go build -o bin/migrate cmd/migrate/main.go ./bin/migrate up ./bin/migrate status ./bin/migrate down ./bin/migrate up -config config.json -dir migrations ``` ```sql -- migrations/20240101000001_create_users.sql CREATE TABLE users ( id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(255) NOT NULL ); ``` ```sql -- migrations/20240101000001_create_users.down.sql DROP TABLE IF EXISTS users; ``` 独立 CLI 内部调用 `migration.RunMigrationsFromConfigWithCommand`,无需业务实现连接逻辑。 ### 7.2 可选:经 Factory(开发 / 小项目) ```go m, err := app.Migrator("migrations") if err != nil { log.Fatal(err) } if err := m.Up(); err != nil { log.Fatal(err) } ``` ### 7.3 Docker ```yaml command: sh -c "./bin/migrate up && ./bin/server" volumes: - ./config.json:/app/config.json:ro - ./migrations:/app/migrations:ro ``` --- ## 8. 各模块按需对接 ### 8.1 日志 ```go log, _ := app.Logger() defer log.Close() log.Info("服务启动") ``` ### 8.2 存储 ```go store, _ := app.Storage() store.Upload(ctx, "images/a.jpg", fileReader, "image/jpeg") url, _ := store.GetURL("images/a.jpg", 3600) ``` 不经 Factory 时:`storage.NewStorage(storage.StorageTypeLocal, cfg)`。 ### 8.3 邮件 / 短信 ```go mail, _ := app.Email() defer mail.Close() // HTTP 通知类:异步,不阻塞请求 mail.SendEmailAsync(r.Context(), []string{"a@b.com"}, "主题", "正文") // 验证码等需等待结果:同步 mail.SendEmail([]string{"a@b.com"}, "主题", "正文") sms, _ := app.SMS() defer sms.Close() sms.SendSMSAsync(r.Context(), []string{"13800138000"}, map[string]string{"code": "123456"}) sms.SendSMS([]string{"13800138000"}, map[string]string{"code": "123456"}) ``` ### 8.4 Excel ```go ex := app.Excel() ex.ExportToFile("users.xlsx", "用户列表", columns, users) ``` ### 8.5 国际化 ```go i18n, _ := app.I18n() i18n.LoadFromDir("locales") msg := i18n.GetMessage("zh-CN", "user.not_found") ``` HTTP 出参的 i18n 通过 `NewHandler(..., WithI18n(i18n))` 注入,Handler 内用消息码调用 `h.Error("user.not_found")`。 ### 8.6 无状态工具(不经 Factory) ```go import "git.toowon.com/jimmy/go-common/tools" tools.Now() tools.MD5("text") tools.YuanToCents(100.5) ``` --- ## 9. config.json 最小示例 ```json { "database": { "type": "mysql", "host": "localhost", "port": 3306, "user": "root", "password": "password", "database": "mydb" }, "logger": { "level": "info", "output": "both", "filePath": "./logs/app.log", "async": true }, "i18n": { "defaultLang": "zh-CN", "localesDir": "locales" } } ``` 完整字段见 [`config/example.json`](./config/example.json)。 --- ## 10. 反模式(禁止) - 每个 Handler 内 `factory.Init` / `NewFromFile` - 业务 Service 内写 HTTP JSON 响应 - 自行 `gorm.Open` / `redis.NewClient` - 使用 Factory 透传:`LogInfo`、`RedisSet`、`Success`、`Error`、`Now`、`MD5` 等 - 直接调用 `http.Success(w, ...)` 包级函数(应使用 `http.Handler`) - 在业务项目复制 GoCommon 的 Response / 中间件实现 --- ## 11. 故障排除 **无法下载模块:** ```bash go env -w GOPRIVATE=git.toowon.com git config --global url."git@git.toowon.com:".insteadOf "https://git.toowon.com/" go get git.toowon.com/jimmy/go-common@latest ``` **依赖错误:** `go clean -modcache && go mod tidy` **getter 报 config is nil:** 补充 `config.json` 对应段,或不调用该 getter **迁移找不到文件:** 检查 `-dir` 与 SQL 文件命名 --- ## 12. 文档与版本 | 文档 | 用途 | |------|------| | 本文档 | 业务项目对接 | | [README.md](./README.md) | 库概述 | | [VERSION.md](./VERSION.md) | 版本发布 | 版本升级见 [VERSION.md](./VERSION.md)。