项目目录结构
Go 官网并没有给出一个目录结构的标准模板,但是 golang-standards 倒是给出了一个。
中文readme
在此基础上,我的项目目录结构具体使用如下:【持续更新!!!】
https://github.com/gw-gong/template_project
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| . ├── api │ ├── router │ │ ├── service01.go │ │ └── service02.go │ └── rpc │ ├── <service_name>.pb.go │ ├── <service_name>.proto │ └── makefile ├── cmd │ ├── service01 │ │ └── main.go │ └── service02 │ └── main.go ├── config │ ├── protocol │ │ └── ... │ ├── service01 │ │ ├── common │ │ │ ├── config.go │ │ │ └── config.yaml │ │ └── net │ │ ├── config.go │ │ └── config.yaml │ └── service02 │ ├── common │ │ ├── config.go │ │ └── config.yaml │ └── net │ ├── config.go │ └── config.yaml ├── docs │ └── <diy> ├── go.mod ├── internal │ ├── app │ │ ├── service01 │ │ │ ├── handler │ │ │ │ ├── handler.go │ │ │ │ └── protocol.go │ │ │ └── rpc │ │ │ └── <service_name>.go │ │ └── service02 │ │ ├── handler │ │ │ ├── handler.go │ │ │ └── protocol.go │ │ └── rpc │ │ └── <service_name>.go │ └── pkg │ ├── protocol │ │ └── <structs_define> │ ├── db │ │ ├── mysql_client │ │ │ ├── interface_and_impl.go │ │ │ └── mysql.go │ │ └── redis_client │ │ ├── interface_and_impl.go │ │ └── redis.go │ ├── middleware │ │ └── <middleware_name>.go │ └── util │ └── some_tools.go └── web ├── <page01>.html ├── <page02>.html ├── error_pages │ └── ... └── static ├── css │ └── xxx.css └── js └── xxx.js
|
Request ID
1. Request ID 是什么?
- 定义:唯一标识一次请求的字符串,用于日志追踪、全链路监控。
- 作用
- 关联同一请求的所有日志(前端→网关→服务→数据库)。
- 快速定位故障点(如错误日志中查找特定 Request ID)。
- 支持分布式系统中的请求链路分析。
2. 生成方式
方案 |
长度 |
唯一性 |
有序性 |
适用场景 |
UUIDv4 |
36 字符 |
全球唯一 |
无序 |
高并发、跨系统追踪 |
ULID |
26 字符 |
全球唯一 |
有序 |
需要时间排序的场景 |
自定义短 ID |
16-20 字符 |
业务内唯一 |
可选 |
存储 / 传输成本敏感场景 |
哈希截取 |
8-12 字符 |
有极低冲突概率 |
无序 |
内部系统、临时追踪 |
代码示例(Go):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import ( "github.com/google/uuid" "github.com/oklog/ulid/v2" "crypto/rand" "encoding/base64" )
uuid.New().String()
ulid.MustNew(ulid.Timestamp(time.Now()), rand.Reader).String()
func ShortID() string { b := make([]byte, 8) rand.Read(b) return base64.RawURLEncoding.EncodeToString(b) }
|
3. 存储位置选择
位置 |
优点 |
缺点 |
适用场景 |
HTTP Header |
无侵入、性能高、标准实践 |
前端需特殊处理(跨域) |
分布式系统、API 网关 |
JSON Body |
用户可见、兼容性强 |
侵入业务结构、需解析 JSON |
客服系统、单体应用 |
同时使用 |
兼顾追踪效率和用户体验 |
增加传输开销 |
对可见性和自动化都有要求的场景 |
最佳实践:优先使用 Header(如 X-Request-ID
),必要时补充到 Body。
4. 命名规范
- X-Request-ID:
X-
前缀:标识自定义 HTTP Header(避免与标准字段冲突)。
Request-ID
:明确语义为 “请求唯一标识”。
- 其他常见命名:
X-Trace-ID
:侧重分布式追踪链路。
X-Correlation-ID
:关联多个相关请求(如批量操作)。
5. 代码实现(Go)
中间件生成并设置 Request ID:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| func RequestIDMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { reqID := r.Header.Get("X-Request-ID") if reqID == "" { reqID = uuid.New().String() } w.Header().Set("X-Request-ID", reqID) ctx := context.WithValue(r.Context(), "requestID", reqID) next.ServeHTTP(w, r.WithContext(ctx)) }) }
|
日志中记录 Request ID:
1 2 3 4 5
| func MyHandler(w http.ResponseWriter, r *http.Request) { reqID := r.Context().Value("requestID").(string) log.Printf("[%s] 处理请求", reqID) }
|
可选:同时返回 Request ID 到 Body:
1 2 3 4 5
| response := map[string]interface{}{ "data": "业务数据", "request_id": reqID, } json.NewEncoder(w).Encode(response)
|
6. 注意事项
-
跨域问题:
若需前端获取 Header,服务器需配置:
1
| w.Header().Set("Access-Control-Expose-Headers", "X-Request-ID")
|
-
性能优化:
- 使用 ULID 替代 UUID(有序性减少数据库索引碎片)。
- 短 ID 减少存储 / 传输开销(如 12 字符 Base62 编码)。
-
兼容性:
- 与 OpenTracing、W3C Trace Context 等标准结合时,考虑字段映射(如
traceparent
)。
7. 常见问题
问题 |
解决方案 |
日志分散无法关联 |
在所有服务的日志中强制添加 Request ID |
前端获取不到 Header |
配置 CORS Access-Control-Expose-Headers |
Request ID 生成性能瓶颈 |
使用预生成池或原子操作生成 ID |
长 ID 导致存储成本高 |
使用短 ID 或压缩算法(如 Base62) |
8. 工具推荐
- Go 库:
github.com/google/uuid
:生成 UUIDv4。
github.com/oklog/ulid
:生成有序 ULID。
go.uber.org/zap
:日志库支持上下文传递 Request ID。
- 分布式追踪:
- Jaeger、Zipkin、Skywalking 等自动注入 Trace ID。