调整项目架构,让框架更优化
This commit is contained in:
400
INTEGRATION.md
400
INTEGRATION.md
@@ -1,366 +1,192 @@
|
||||
# GoCommon 业务项目对接操作手册
|
||||
|
||||
本文档供**引用 GoCommon 的业务项目**使用。按本手册对接即可,无需重复沟通基础设施实现方式。
|
||||
# GoCommon 业务项目对接手册
|
||||
|
||||
模块路径:`git.toowon.com/jimmy/go-common`
|
||||
|
||||
> 本文档只描述**目标架构**,重构时一步到位,**不提供过渡期用法,不考虑向后兼容**。
|
||||
> 目标架构一步到位,不考虑向后兼容。
|
||||
|
||||
---
|
||||
|
||||
## 1. 对接原则
|
||||
## 1. 原则
|
||||
|
||||
| 原则 | 说明 |
|
||||
|------|------|
|
||||
| Factory 只是入口 | 启动时初始化一次,通过 getter 获取各模块**对象** |
|
||||
| 能力在模块自身 | 使用 `log.Info()`、`db.Find()`、`store.Upload()`,不在 Factory 上堆透传方法 |
|
||||
| 按需引用 | 用什么模块取什么对象;无状态工具直接 `import tools` |
|
||||
| 不重写基础设施 | 数据库连接、Redis 客户端、统一 HTTP 出参、中间件链由 GoCommon 提供 |
|
||||
| 业务只管业务 | Service 返回数据;Handler 交给 `http.Handler` 出参;Migration 只写 SQL 文件 |
|
||||
| Factory 只是入口 | 启动初始化一次,getter 取模块对象 |
|
||||
| 能力在模块自身 | `log.Info()`、`db.Find()`、`store.Upload()`,不在 Factory 透传 |
|
||||
| 按需配置 | 只用到的模块写进 `config.json` |
|
||||
| 不重写基础设施 | 连接池、HTTP 出参、中间件链由本库提供 |
|
||||
| 业务只管业务 | Service 返回数据;出参用 `http.Handler`;迁移只写 SQL |
|
||||
|
||||
登录鉴权、参数校验、出站 HTTP Client 等由**业务项目自行实现**,不在本库范围。
|
||||
|
||||
---
|
||||
|
||||
## 2. 环境准备
|
||||
|
||||
### 2.1 配置私有模块
|
||||
## 2. 安装
|
||||
|
||||
```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/"
|
||||
|
||||
go get git.toowon.com/jimmy/go-common@v1.2.0
|
||||
```
|
||||
|
||||
### 2.3 安装依赖
|
||||
|
||||
```bash
|
||||
go get git.toowon.com/jimmy/go-common@v1.0.0
|
||||
```
|
||||
|
||||
在 `go.mod` 中:
|
||||
|
||||
```go
|
||||
require git.toowon.com/jimmy/go-common v1.0.0
|
||||
```
|
||||
|
||||
配置示例见 [`config/example.json`](./config/example.json)。
|
||||
配置示例:[`config/example.json`](./config/example.json)。
|
||||
|
||||
---
|
||||
|
||||
## 3. 推荐项目结构
|
||||
## 3. 推荐结构
|
||||
|
||||
```text
|
||||
your-project/
|
||||
├── config.json
|
||||
├── cmd/
|
||||
│ ├── server/main.go
|
||||
│ └── migrate/main.go # 从 templates/migrate/main.go 复制
|
||||
├── cmd/server/main.go # 复制 templates/server/main.go
|
||||
├── cmd/migrate/main.go # 复制 templates/migrate/main.go
|
||||
├── migrations/
|
||||
│ └── 20240101000001_create_users.sql
|
||||
├── locales/ # i18n(可选)
|
||||
│ ├── zh-CN.json
|
||||
│ └── en-US.json
|
||||
├── internal/
|
||||
│ ├── handler/
|
||||
│ └── service/
|
||||
├── locales/ # 可选
|
||||
├── internal/handler/
|
||||
├── internal/service/
|
||||
└── go.mod
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 启动初始化(只做一次)
|
||||
## 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))
|
||||
}
|
||||
```bash
|
||||
cp templates/server/main.go cmd/server/main.go
|
||||
```
|
||||
|
||||
**禁止**在每个 Handler 里 `NewFromFile` / `Init`。全局只初始化一次,通过 `factory.Default()` 或注入 `*factory.Factory`。
|
||||
模板默认已包含:`MustInit` + `buildOptions`(Option 挂载点)+ `MustLogger` + `Warmup` + `MiddlewareChain` + `NewHandler` + `Close`。
|
||||
|
||||
复制后主要改三处:
|
||||
|
||||
| 函数 | 用途 |
|
||||
|------|------|
|
||||
| `buildOptions()` | `WithLogger` / `WithStorage` 等自定义注入 |
|
||||
| `setupMiddleware()` | `chain.Append(业务鉴权…)` |
|
||||
| `registerRoutes()` | 注册业务路由 |
|
||||
|
||||
```bash
|
||||
go run ./cmd/server -config config.json -addr :8080
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 获取模块对象(Factory getter)
|
||||
## 5. 获取模块对象
|
||||
|
||||
连接与客户端由 Factory **lazy 初始化并缓存**。业务侧**不要**自行 `gorm.Open` / `redis.NewClient`。
|
||||
连接由 Factory lazy 缓存。业务侧不要自行 `gorm.Open` / `redis.NewClient`。
|
||||
|
||||
| 模块 | API | 用法 |
|
||||
| 模块 | 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()` |
|
||||
| 配置 | `Config()` | 原始配置 |
|
||||
| 数据库 | `Database()` / `MustDatabase()` | `*gorm.DB` |
|
||||
| Redis | `Redis()` / `MustRedis()` | `*redis.Client` |
|
||||
| 日志 | `Logger()` / `MustLogger()` | 退出用 `Close()` |
|
||||
| 存储 | `Storage()` / `MustStorage()` | Upload / GetURL |
|
||||
| 邮件 | `Email()` / `MustEmail()` | SendEmail / SendEmailAsync |
|
||||
| 短信 | `SMS()` / `MustSMS()` | SendSMS / SendSMSAsync |
|
||||
| Excel | `Excel()` | 每次新建导出器 |
|
||||
| 国际化 | `I18n()` / `MustI18n()` | 一般由 `NewHandler` 注入 |
|
||||
| HTTP 出参 | `NewHandler(w, r)` | `Success` / `Error` / `ErrorData` |
|
||||
| 中间件 | `MiddlewareChain()` | `Append` / `ThenFunc` |
|
||||
| 迁移 | `Migrator(dir)` | Up / Down / Status |
|
||||
| 生命周期 | `Close()` / `Warmup(...)` | 收口 / 启动预热 |
|
||||
|
||||
### 注入 Service 示例
|
||||
`MustXxx` 仅用于 **main / 启动注入**(失败 panic)。
|
||||
|
||||
```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
|
||||
func NewUserService(app *factory.Factory) *UserService {
|
||||
return &UserService{db: app.MustDatabase(), rds: app.MustRedis()}
|
||||
}
|
||||
```
|
||||
|
||||
`config.json` 未配置的模块,调用 getter 会返回错误——**只配置使用的模块即可**。
|
||||
|
||||
---
|
||||
|
||||
## 6. HTTP 统一出参
|
||||
|
||||
### 6.1 职责划分
|
||||
| 层级 | 职责 |
|
||||
|------|------|
|
||||
| Service | 返回数据或 error |
|
||||
| Handler | 解析、调 Service、`h.Success` / `h.Error` |
|
||||
| `http.Handler` | 统一 JSON 信封、i18n、timestamp |
|
||||
|
||||
| 层级 | 做什么 |
|
||||
|------|--------|
|
||||
| Service | 返回 `[]User`、`total`、业务 error |
|
||||
| Handler | 解析请求、调 Service、通过 `http.Handler` 出参 |
|
||||
| `http.Handler` | PageData → Response → JSON(结构 / 编码 / 语种 / 时间统一) |
|
||||
|
||||
**禁止**在 Service 拼 JSON,**禁止**在业务项目自定义 `Response` 结构,**禁止**经 Factory 出参(无 `app.Success` / `app.Error`)。
|
||||
|
||||
### 6.2 标准响应格式
|
||||
响应格式(HTTP 恒 200):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
"timestamp": 1704067200,
|
||||
"data": {}
|
||||
}
|
||||
{ "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()` 已默认组装)。
|
||||
分页 `data`:`{ "list", "total", "page", "pageSize" }`。
|
||||
|
||||
```go
|
||||
import commonhttp "git.toowon.com/jimmy/go-common/http"
|
||||
h := app.NewHandler(w, r) // 自动注入已配置的 i18n
|
||||
|
||||
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)
|
||||
var req ListUserRequest
|
||||
if err := h.ParseJSON(&req); err != nil {
|
||||
h.Error("common.invalid_request")
|
||||
return
|
||||
}
|
||||
users, total, err := svc.List(h.Pagination().GetPage(), h.Pagination().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`) |
|
||||
请求头:`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
|
||||
./bin/migrate up|status|down
|
||||
```
|
||||
|
||||
模板默认走 Factory:`MustInit` + 日志 + `Warmup(Database)` + `Migrator` + `Close`;同样可用 `buildOptions()` 注入自定义 DB/Logger。
|
||||
|
||||
```sql
|
||||
-- migrations/20240101000001_create_users.sql
|
||||
CREATE TABLE users (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
username VARCHAR(255) NOT NULL
|
||||
);
|
||||
```
|
||||
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 日志
|
||||
## 8. 模块速查
|
||||
|
||||
```go
|
||||
log, _ := app.Logger()
|
||||
defer log.Close()
|
||||
log.Info("服务启动")
|
||||
```
|
||||
// 日志
|
||||
app.MustLogger().Info("服务启动", nil)
|
||||
|
||||
### 8.2 存储
|
||||
|
||||
```go
|
||||
// 存储
|
||||
store, _ := app.Storage()
|
||||
store.Upload(ctx, "images/a.jpg", fileReader, "image/jpeg")
|
||||
url, _ := store.GetURL("images/a.jpg", 3600)
|
||||
```
|
||||
_ = store.Upload(ctx, "images/a.jpg", reader, "image/jpeg")
|
||||
|
||||
不经 Factory 时:`storage.NewStorage(storage.StorageTypeLocal, cfg)`。
|
||||
// 邮件 / 短信(HTTP 通知用 Async;验证码用同步)
|
||||
app.MustEmail().SendEmailAsync(ctx, []string{"a@b.com"}, "主题", "正文")
|
||||
app.MustSMS().SendSMSAsync(ctx, []string{"13800138000"}, map[string]string{"code": "123456"})
|
||||
|
||||
### 8.3 邮件 / 短信
|
||||
// Excel
|
||||
app.Excel().ExportToFile("users.xlsx", "用户列表", columns, users)
|
||||
|
||||
```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"
|
||||
// i18n(locales 目录;出参经 NewHandler 自动用)
|
||||
app.MustI18n().GetMessage("zh-CN", "user.not_found")
|
||||
|
||||
// tools(不经 Factory)
|
||||
tools.Now()
|
||||
tools.MD5("text")
|
||||
tools.YuanToCents(100.5)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. config.json 最小示例
|
||||
## 9. 最小 config.json
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -389,41 +215,23 @@ tools.YuanToCents(100.5)
|
||||
|
||||
---
|
||||
|
||||
## 10. 反模式(禁止)
|
||||
## 10. 反模式
|
||||
|
||||
- 每个 Handler 内 `factory.Init` / `NewFromFile`
|
||||
- 业务 Service 内写 HTTP JSON 响应
|
||||
- Handler 内重复 `Init`
|
||||
- Service 拼 HTTP JSON / 自定义 Response
|
||||
- 自行 `gorm.Open` / `redis.NewClient`
|
||||
- 使用 Factory 透传:`LogInfo`、`RedisSet`、`Success`、`Error`、`Now`、`MD5` 等
|
||||
- 直接调用 `http.Success(w, ...)` 包级函数(应使用 `http.Handler`)
|
||||
- 在业务项目复制 GoCommon 的 Response / 中间件实现
|
||||
- Factory 透传(`LogInfo`、`Success`、`Now` 等)
|
||||
- 请求路径滥用 `MustXxx`
|
||||
|
||||
---
|
||||
|
||||
## 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) | 版本发布 |
|
||||
| 无法下载模块 | 检查 `GOPRIVATE` 与 SSH insteadOf |
|
||||
| 依赖异常 | `go clean -modcache && go mod tidy` |
|
||||
| getter 报 config is nil | 补配置段,或不调用该模块 |
|
||||
| 迁移找不到文件 | 检查 `-dir` 与 SQL 命名 |
|
||||
|
||||
版本升级见 [VERSION.md](./VERSION.md)。
|
||||
版本发布见 [VERSION.md](./VERSION.md)。
|
||||
|
||||
Reference in New Issue
Block a user