# HTTP Restful工具文档 ## 概述 HTTP Restful工具提供了标准化的HTTP请求和响应处理功能,提供公共方法供外部调用,保持低耦合。 ## 功能特性 - **低耦合设计**:提供公共方法,不封装Handler结构 - **标准化的响应结构**:`{code, message, timestamp, data}` - **分离HTTP状态码和业务状态码** - **支持分页响应** - **提供便捷的请求参数解析方法** - **支持JSON请求体解析** - **Factory黑盒模式**:推荐使用 `factory.Success()` 等方法 ## 响应结构 ### 标准响应结构 ```json { "code": 0, "message": "success", "timestamp": 1704067200, "data": {} } ``` **结构体类型(暴露在 factory 中):** ```go // 在 factory 包中可以直接使用 type Response struct { Code int `json:"code"` // 业务状态码,0表示成功 Message string `json:"message"` // 响应消息 Timestamp int64 `json:"timestamp"` // 时间戳 Data interface{} `json:"data"` // 响应数据 } ``` ### 分页响应结构 ```json { "code": 0, "message": "success", "timestamp": 1704067200, "data": { "list": [], "total": 100, "page": 1, "pageSize": 10 } } ``` **结构体类型(暴露在 factory 中):** ```go // 在 factory 包中可以直接使用 type PageData struct { List interface{} `json:"list"` // 数据列表 Total int64 `json:"total"` // 总记录数 Page int `json:"page"` // 当前页码 PageSize int `json:"pageSize"` // 每页大小 } type PageResponse struct { Code int `json:"code"` Message string `json:"message"` Timestamp int64 `json:"timestamp"` Data *PageData `json:"data"` } ``` ### 使用暴露的结构体 外部项目可以直接使用 `factory.Response`、`factory.PageData` 等类型: ```go import "git.toowon.com/jimmy/go-common/factory" // 创建标准响应对象 response := factory.Response{ Code: 0, Message: "success", Data: userData, } // 创建分页数据对象 pageData := &factory.PageData{ List: users, Total: 100, Page: 1, PageSize: 20, } // 传递给 Success 方法 fac.Success(w, pageData) ``` ## 使用方法 ### 方式一:使用Factory黑盒方法(推荐)⭐ 这是最简单的方式,直接使用 `factory.Success()` 等方法: ```go import ( "net/http" "git.toowon.com/jimmy/go-common/factory" commonhttp "git.toowon.com/jimmy/go-common/http" "git.toowon.com/jimmy/go-common/tools" ) func GetUser(w http.ResponseWriter, r *http.Request) { fac, _ := factory.NewFactoryFromFile("config.json") // 获取查询参数(使用类型转换方法) id := tools.ConvertInt64(r.URL.Query().Get("id"), 0) // 返回成功响应(使用factory方法) fac.Success(w, data) } func CreateUser(w http.ResponseWriter, r *http.Request) { fac, _ := factory.NewFactoryFromFile("config.json") // 解析JSON(使用公共方法) var req struct { Name string `json:"name"` } if err := commonhttp.ParseJSON(r, &req); err != nil { commonhttp.WriteJSON(w, http.StatusBadRequest, 400, "请求参数解析失败", nil) return } // 返回成功响应(使用factory方法) fac.Success(w, data, "创建成功") } func GetUserList(w http.ResponseWriter, r *http.Request) { fac, _ := factory.NewFactoryFromFile("config.json") // 获取分页参数(使用factory方法,推荐) pagination := fac.ParsePaginationRequest(r) page := pagination.GetPage() pageSize := pagination.GetPageSize() // 获取查询参数(直接使用HTTP原生方法) keyword := r.URL.Query().Get("keyword") // 查询数据 list, total := getDataList(keyword, page, pageSize) // 返回分页响应(使用factory方法) fac.SuccessPage(w, list, total, page, pageSize) } // 注册路由 http.HandleFunc("/user", GetUser) http.HandleFunc("/users", GetUserList) ``` ### 方式二:直接使用公共方法 如果不想使用Factory,可以直接使用 `http` 包的公共方法: ```go import ( "net/http" commonhttp "git.toowon.com/jimmy/go-common/http" "git.toowon.com/jimmy/go-common/tools" ) func GetUser(w http.ResponseWriter, r *http.Request) { // 获取查询参数 id := tools.ConvertInt64(r.URL.Query().Get("id"), 0) // 返回成功响应 commonhttp.Success(w, data) } func CreateUser(w http.ResponseWriter, r *http.Request) { // 解析JSON var req struct { Name string `json:"name"` } if err := commonhttp.ParseJSON(r, &req); err != nil { commonhttp.WriteJSON(w, http.StatusBadRequest, 400, "请求参数解析失败", nil) return } // 返回成功响应 commonhttp.Success(w, data, "创建成功") } ``` ### 成功响应 ```go // 使用Factory(推荐) fac.Success(w, data) // 只有数据,使用默认消息 "success" fac.Success(w, data, "操作成功") // 数据+消息 // 或直接使用公共方法 commonhttp.Success(w, data) // 只有数据 commonhttp.Success(w, data, "操作成功") // 数据+消息 ``` ### 错误响应 ```go // 使用Factory(推荐) fac.Error(w, 1001, "用户不存在") // 业务错误(HTTP 200,业务code非0) fac.SystemError(w, "服务器内部错误") // 系统错误(HTTP 500) // 或直接使用公共方法 commonhttp.Error(w, 1001, "用户不存在") commonhttp.SystemError(w, "服务器内部错误") commonhttp.WriteJSON(w, http.StatusBadRequest, 400, "请求参数错误", nil) // 自定义HTTP状态码(仅公共方法) ``` ### 分页响应 ```go // 使用Factory(推荐) fac.SuccessPage(w, list, total, page, pageSize) fac.SuccessPage(w, list, total, page, pageSize, "查询成功") // 或直接使用公共方法 commonhttp.SuccessPage(w, list, total, page, pageSize) commonhttp.SuccessPage(w, list, total, page, pageSize, "查询成功") ``` ### 解析请求 #### 解析JSON请求体 ```go // 使用公共方法 var req struct { Name string `json:"name"` Email string `json:"email"` } if err := commonhttp.ParseJSON(r, &req); err != nil { commonhttp.WriteJSON(w, http.StatusBadRequest, 400, "请求参数解析失败", nil) return } ``` #### 获取查询参数 ```go import "git.toowon.com/jimmy/go-common/tools" // 字符串直接获取 name := r.URL.Query().Get("name") // 使用类型转换方法 id := tools.ConvertInt(r.URL.Query().Get("id"), 0) userId := tools.ConvertInt64(r.URL.Query().Get("userId"), 0) isActive := tools.ConvertBool(r.URL.Query().Get("isActive"), false) price := tools.ConvertFloat64(r.URL.Query().Get("price"), 0.0) ``` #### 获取表单参数 ```go import "git.toowon.com/jimmy/go-common/tools" // 字符串直接获取 name := r.FormValue("name") // 使用类型转换方法 age := tools.ConvertInt(r.FormValue("age"), 0) userId := tools.ConvertInt64(r.FormValue("userId"), 0) isActive := tools.ConvertBool(r.FormValue("isActive"), false) ``` #### 获取请求头 ```go // 直接使用HTTP原生方法 token := r.Header.Get("Authorization") contentType := r.Header.Get("Content-Type") if contentType == "" { contentType = "application/json" // 设置默认值 } ``` #### 获取分页参数 **方式1:使用 PaginationRequest 结构(推荐)** ```go // 定义请求结构(包含分页字段) type ListUserRequest struct { Keyword string `json:"keyword"` commonhttp.PaginationRequest // 嵌入分页请求结构 } // 从JSON请求体解析(分页字段会自动解析) var req ListUserRequest if err := commonhttp.ParseJSON(r, &req); err != nil { commonhttp.WriteJSON(w, http.StatusBadRequest, 400, "请求参数解析失败", nil) return } // 使用分页方法 page := req.GetPage() // 获取页码(默认1) pageSize := req.GetPageSize() // 获取每页数量(默认20,最大100) offset := req.GetOffset() // 计算偏移量 ``` **方式2:从查询参数/form解析分页** ```go // 使用公共方法 pagination := commonhttp.ParsePaginationRequest(r) page := pagination.GetPage() pageSize := pagination.GetPageSize() offset := pagination.GetOffset() ``` #### 获取时区 ```go // 使用公共方法 // 如果使用了middleware.Timezone中间件,可以从context中获取时区信息 // 如果未设置,返回默认时区 AsiaShanghai timezone := commonhttp.GetTimezone(r) ``` ## 完整示例 ### 使用Factory(推荐) ```go package main import ( "log" "net/http" "git.toowon.com/jimmy/go-common/factory" commonhttp "git.toowon.com/jimmy/go-common/http" "git.toowon.com/jimmy/go-common/tools" ) // 用户结构 type User struct { ID int64 `json:"id"` Name string `json:"name"` Email string `json:"email"` } // 用户列表接口 func GetUserList(w http.ResponseWriter, r *http.Request) { fac, _ := factory.NewFactoryFromFile("config.json") // 获取分页参数(使用公共方法) pagination := commonhttp.ParsePaginationRequest(r) page := pagination.GetPage() pageSize := pagination.GetPageSize() // 获取查询参数(直接使用HTTP原生方法) keyword := r.URL.Query().Get("keyword") // 查询数据 users, total := queryUsers(keyword, page, pageSize) // 返回分页响应(使用factory方法) fac.SuccessPage(w, users, total, page, pageSize) } // 创建用户接口 func CreateUser(w http.ResponseWriter, r *http.Request) { fac, _ := factory.NewFactoryFromFile("config.json") // 解析请求体(使用公共方法) var req struct { Name string `json:"name"` Email string `json:"email"` } if err := commonhttp.ParseJSON(r, &req); err != nil { fac.WriteJSON(w, http.StatusBadRequest, 400, "请求参数解析失败", nil) return } // 参数验证 if req.Name == "" { fac.Error(w, 1001, "用户名不能为空") return } // 创建用户 user, err := createUser(req.Name, req.Email) if err != nil { fac.SystemError(w, "创建用户失败") return } // 返回成功响应(使用factory方法) fac.Success(w, user, "创建成功") } // 获取用户详情接口 func GetUser(w http.ResponseWriter, r *http.Request) { fac, _ := factory.NewFactoryFromFile("config.json") // 获取查询参数(使用类型转换方法) id := tools.ConvertInt64(r.URL.Query().Get("id"), 0) if id == 0 { commonhttp.WriteJSON(w, http.StatusBadRequest, 400, "用户ID不能为空", nil) return } // 查询用户 user, err := getUserByID(id) if err != nil { fac.SystemError(w, "查询用户失败") return } if user == nil { fac.Error(w, 1002, "用户不存在") return } // 返回成功响应(使用factory方法) fac.Success(w, user) } func main() { http.HandleFunc("/users", func(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: GetUserList(w, r) case http.MethodPost: CreateUser(w, r) default: commonhttp.WriteJSON(w, http.StatusMethodNotAllowed, 405, "方法不支持", nil) } }) http.HandleFunc("/user", GetUser) log.Println("Server started on :8080") log.Fatal(http.ListenAndServe(":8080", nil)) } ``` ## API 参考 ### Factory HTTP响应结构体(暴露给外部项目使用) #### Response 标准响应结构体,外部项目可以直接使用 `factory.Response`。 **字段:** - `Code`: 业务状态码,0表示成功 - `Message`: 响应消息 - `Timestamp`: 时间戳(Unix时间戳,秒) - `Data`: 响应数据 **示例:** ```go response := factory.Response{ Code: 0, Message: "success", Data: userData, } ``` #### PageData 分页数据结构体,外部项目可以直接使用 `factory.PageData`。 **字段:** - `List`: 数据列表 - `Total`: 总记录数 - `Page`: 当前页码 - `PageSize`: 每页大小 **示例:** ```go pageData := &factory.PageData{ List: users, Total: 100, Page: 1, PageSize: 20, } fac.Success(w, pageData) ``` #### PageResponse 分页响应结构体,外部项目可以直接使用 `factory.PageResponse`。 **字段:** - `Code`: 业务状态码 - `Message`: 响应消息 - `Timestamp`: 时间戳 - `Data`: 分页数据(*PageData) ### Factory HTTP响应方法(推荐使用) #### (f *Factory) Success(w http.ResponseWriter, data interface{}, message ...string) 成功响应,HTTP 200,业务code 0。 **参数:** - `data`: 响应数据,可以为nil - `message`: 响应消息(可选),如果为空则使用默认消息 "success" **示例:** ```go fac.Success(w, data) // 只有数据,使用默认消息 "success" fac.Success(w, data, "操作成功") // 数据+消息 ``` #### (f *Factory) Error(w http.ResponseWriter, code int, message string) 业务错误响应,HTTP 200,业务code非0。 **示例:** ```go fac.Error(w, 1001, "用户不存在") ``` #### (f *Factory) SystemError(w http.ResponseWriter, message string) 系统错误响应,HTTP 500,业务code 500。 **示例:** ```go fac.SystemError(w, "服务器内部错误") ``` #### (f *Factory) WriteJSON(w http.ResponseWriter, httpCode, code int, message string, data interface{}) 写入JSON响应(自定义)。 **参数:** - `httpCode`: HTTP状态码 - `code`: 业务状态码 - `message`: 响应消息 - `data`: 响应数据 **说明:** - 此方法不在 Factory 中,直接使用 `commonhttp.WriteJSON()` - 用于需要自定义HTTP状态码的场景(如 400, 401, 403, 404 等) **示例:** ```go commonhttp.WriteJSON(w, http.StatusBadRequest, 400, "请求参数错误", nil) commonhttp.WriteJSON(w, http.StatusUnauthorized, 401, "未登录", nil) ``` #### (f *Factory) SuccessPage(w http.ResponseWriter, list interface{}, total int64, page, pageSize int, message ...string) 分页成功响应。 **参数:** - `list`: 数据列表 - `total`: 总记录数 - `page`: 当前页码 - `pageSize`: 每页大小 - `message`: 响应消息(可选,如果为空则使用默认消息 "success") **示例:** ```go fac.SuccessPage(w, users, total, page, pageSize) fac.SuccessPage(w, users, total, page, pageSize, "查询成功") ``` ### HTTP公共方法(直接使用) #### WriteJSON(w http.ResponseWriter, httpCode, code int, message string, data interface{}) 写入JSON响应(自定义HTTP状态码和业务状态码)。 **说明:** - 此方法不在 Factory 中,直接使用 `commonhttp.WriteJSON()` - 用于需要自定义HTTP状态码的场景(如 400, 401, 403, 404 等) **示例:** ```go commonhttp.WriteJSON(w, http.StatusBadRequest, 400, "请求参数错误", nil) commonhttp.WriteJSON(w, http.StatusUnauthorized, 401, "未登录", nil) ``` #### ParseJSON(r *http.Request, v interface{}) error 解析JSON请求体。 **示例:** ```go var req CreateUserRequest if err := commonhttp.ParseJSON(r, &req); err != nil { // 处理错误 } ``` #### 获取查询参数和表单参数 **推荐方式:使用类型转换工具** ```go import "git.toowon.com/jimmy/go-common/tools" // 字符串直接使用HTTP原生方法 name := r.URL.Query().Get("name") if name == "" { name = "default" // 设置默认值 } // 类型转换使用tools包 id := tools.ConvertInt(r.URL.Query().Get("id"), 0) userId := tools.ConvertInt64(r.URL.Query().Get("userId"), 0) isActive := tools.ConvertBool(r.URL.Query().Get("isActive"), false) price := tools.ConvertFloat64(r.URL.Query().Get("price"), 0.0) // 表单参数类似 age := tools.ConvertInt(r.FormValue("age"), 0) ``` **类型转换方法说明:** - `tools.ConvertInt(value string, defaultValue int) int` - 转换为int - `tools.ConvertInt64(value string, defaultValue int64) int64` - 转换为int64 - `tools.ConvertUint64(value string, defaultValue uint64) uint64` - 转换为uint64 - `tools.ConvertUint32(value string, defaultValue uint32) uint32` - 转换为uint32 - `tools.ConvertBool(value string, defaultValue bool) bool` - 转换为bool - `tools.ConvertFloat64(value string, defaultValue float64) float64` - 转换为float64 **获取请求头:** ```go // 直接使用HTTP原生方法 token := r.Header.Get("Authorization") contentType := r.Header.Get("Content-Type") ``` #### ParsePaginationRequest(r *http.Request) *PaginationRequest 从请求中解析分页参数。 **说明:** - 支持从查询参数和form表单中解析 - 优先级:查询参数 > form表单 - 如果请求体是JSON格式且包含分页字段,建议先使用`ParseJSON`解析完整请求体到包含`PaginationRequest`的结构体中 #### GetTimezone(r *http.Request) string 从请求的context中获取时区。 **说明:** - 如果使用了middleware.Timezone中间件,可以从context中获取时区信息 - 如果未设置,返回默认时区 AsiaShanghai #### Success(w http.ResponseWriter, data interface{}, message ...string) 成功响应(公共方法)。 **参数:** - `data`: 响应数据,可以为nil - `message`: 响应消息(可选),如果为空则使用默认消息 "success" **示例:** ```go commonhttp.Success(w, data) // 只有数据 commonhttp.Success(w, data, "操作成功") // 数据+消息 ``` #### Error(w http.ResponseWriter, code int, message string) 错误响应(公共方法)。 #### SystemError(w http.ResponseWriter, message string) 系统错误响应(公共方法)。 #### WriteJSON(w http.ResponseWriter, httpCode, code int, message string, data interface{}) 写入JSON响应(公共方法,不在Factory中)。 **说明:** - 用于需要自定义HTTP状态码的场景 - 直接使用 `commonhttp.WriteJSON()`,不在 Factory 中 #### SuccessPage(w http.ResponseWriter, list interface{}, total int64, page, pageSize int, message ...string) 分页成功响应(公共方法)。 ### 分页请求结构 #### PaginationRequest 分页请求结构,支持从JSON和form中解析分页参数。 **字段:** - `Page`: 页码(默认1) - `PageSize`: 每页数量 **方法:** - `GetPage() int`: 获取页码,如果未设置则返回默认值1 - `GetPageSize() int`: 获取每页数量,如果未设置则返回默认值20,最大限制100 - `GetOffset() int`: 计算数据库查询的偏移量 #### ParsePaginationRequest(r *http.Request) *PaginationRequest 从请求中解析分页参数(内部函数,Handler内部使用)。 ## 状态码说明 ### HTTP状态码 - `200`: 正常响应(包括业务错误) - `400`: 请求参数错误 - `401`: 未授权 - `403`: 禁止访问 - `404`: 资源不存在 - `500`: 系统内部错误 ### 业务状态码 - `0`: 成功 - `非0`: 业务错误(具体错误码由业务定义) ## 注意事项 1. **HTTP状态码与业务状态码分离**: - 业务错误(如用户不存在、参数验证失败等)返回HTTP 200,业务code非0 - 只有系统异常(如数据库连接失败、程序panic等)才返回HTTP 500 2. **分页参数限制**: - page最小值为1 - pageSize最小值为1,最大值为100 3. **响应格式统一**: - 所有响应都遵循标准结构 - timestamp为Unix时间戳(秒) 4. **错误处理**: - 使用`Error`方法返回业务错误(HTTP 200,业务code非0) - 使用`SystemError`返回系统错误(HTTP 500) - 其他HTTP错误状态码(400, 401, 403, 404等)使用`WriteJSON`方法直接指定 5. **推荐使用Factory**: - 使用 `factory.Success()` 等方法,代码更简洁 - 直接使用 `http` 包的公共方法,保持低耦合 - 不需要Handler结构,减少不必要的封装 ## 示例 完整示例请参考: - `examples/http_handler_example.go` - 使用Factory和公共方法 - `examples/http_pagination_example.go` - 分页示例 - `examples/factory_blackbox_example.go` - Factory黑盒模式示例