Files
go-common/INTEGRATION.md

10 KiB
Raw Blame History

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 配置私有模块

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 推荐)

git config --global url."git@git.toowon.com:".insteadOf "https://git.toowon.com/"

2.3 安装依赖

go get git.toowon.com/jimmy/go-common@v2.0.0

go.mod 中:

require git.toowon.com/jimmy/go-common v2.0.0

配置示例见 config/example.json


3. 推荐项目结构

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. 启动初始化(只做一次)

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 示例

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 返回 []Usertotal、业务 error
Handler 解析请求、调 Service、通过 http.Handler 出参
http.Handler PageData → Response → JSON结构 / 编码 / 语种 / 时间统一)

禁止在 Service 拼 JSON禁止在业务项目自定义 Response 结构,禁止经 Factory 出参(无 app.Success / app.Error)。

6.2 标准响应格式

{
  "code": 0,
  "message": "success",
  "timestamp": 1704067200,
  "data": {}
}

分页时 data

{
  "list": [],
  "total": 100,
  "page": 1,
  "pageSize": 20
}

类型定义在 http 包:http.Responsehttp.PageData

6.3 Handler 用法(唯一方式)

中间件链须包含 LanguageTimezoneMiddlewareChain() 已默认组装)。

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 解耦)

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
-- migrations/20240101000001_create_users.sql
CREATE TABLE users (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(255) NOT NULL
);
-- migrations/20240101000001_create_users.down.sql
DROP TABLE IF EXISTS users;

独立 CLI 内部调用 migration.RunMigrationsFromConfigWithCommand,无需业务实现连接逻辑。

7.2 可选:经 Factory开发 / 小项目)

m, err := app.Migrator("migrations")
if err != nil {
    log.Fatal(err)
}
if err := m.Up(); err != nil {
    log.Fatal(err)
}

7.3 Docker

command: sh -c "./bin/migrate up && ./bin/server"
volumes:
  - ./config.json:/app/config.json:ro
  - ./migrations:/app/migrations:ro

8. 各模块按需对接

8.1 日志

log, _ := app.Logger()
defer log.Close()
log.Info("服务启动")

8.2 存储

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 邮件 / 短信

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

ex := app.Excel()
ex.ExportToFile("users.xlsx", "用户列表", columns, users)

8.5 国际化

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

import "git.toowon.com/jimmy/go-common/tools"

tools.Now()
tools.MD5("text")
tools.YuanToCents(100.5)

9. config.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


10. 反模式(禁止)

  • 每个 Handler 内 factory.Init / NewFromFile
  • 业务 Service 内写 HTTP JSON 响应
  • 自行 gorm.Open / redis.NewClient
  • 使用 Factory 透传:LogInfoRedisSetSuccessErrorNowMD5
  • 直接调用 http.Success(w, ...) 包级函数(应使用 http.Handler
  • 在业务项目复制 GoCommon 的 Response / 中间件实现

11. 故障排除

无法下载模块:

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 库概述
VERSION.md 版本发布

版本升级见 VERSION.md