110 lines
3.2 KiB
Go
110 lines
3.2 KiB
Go
// Package main 演示在 HTTP 服务里使用 errorx:
|
||||
|
|
// 业务层只管 return error,出口统一翻译成 {code, msg}。
|
|||
|
|
//
|
|||
|
|
// 运行:go run ./example/http
|
|||
|
|
package main
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"context"
|
|||
|
|
"encoding/json"
|
|||
|
|
"errors"
|
|||
|
|
"fmt"
|
|||
|
|
"net/http"
|
|||
|
|
"net/http/httptest"
|
|||
|
|
|
|||
|
|
"github.com/yuninks/errorx"
|
|||
|
|
"github.com/yuninks/langx"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
var (
|
|||
|
|
ErrParamInvalid = errorx.New("param_invalid", 10002, "参数错误: #field#")
|
|||
|
|
ErrUserNotFound = errorx.New("user_not_found", 10001, "用户不存在")
|
|||
|
|
ErrDB = errorx.New("db_error", 50001, "数据库繁忙")
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
func init() {
|
|||
|
|
langx.AppendTrans("en", map[string]string{
|
|||
|
|
"param_invalid": "invalid parameter: #field#",
|
|||
|
|
"user_not_found": "user not found",
|
|||
|
|
"db_error": "database is busy",
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Response 是对外的统一响应体
|
|||
|
|
type Response struct {
|
|||
|
|
Code int `json:"code"`
|
|||
|
|
Msg string `json:"msg"`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 业务层:只返回 error,完全不关心 HTTP 与语言
|
|||
|
|
func loadUser(ctx context.Context, id string) (string, error) {
|
|||
|
|
switch id {
|
|||
|
|
case "":
|
|||
|
|
return "", ErrParamInvalid.ErrKV("field", "id")
|
|||
|
|
case "404":
|
|||
|
|
return "", ErrUserNotFound.Err()
|
|||
|
|
case "500":
|
|||
|
|
return "", ErrDB.Wrap(errors.New("dial tcp 127.0.0.1:3306: connect: connection refused"))
|
|||
|
|
default:
|
|||
|
|
return "用户-" + id, nil
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 出口层:把 error 统一翻译成响应
|
|||
|
|
func userHandler(w http.ResponseWriter, r *http.Request) {
|
|||
|
|
ctx := withLang(r)
|
|||
|
|
name, err := loadUser(ctx, r.URL.Query().Get("id"))
|
|||
|
|
if err != nil {
|
|||
|
|
writeJSON(w, toResponse(ctx, err))
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
writeJSON(w, Response{Code: errorx.Success.GetCode(), Msg: name})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// withLang 把请求头里的语言放进 ctx。
|
|||
|
|
// langx 约定从 ctx 里读 "Accept-Language",也可以自己用 langx.SetCtxLang 指定。
|
|||
|
|
func withLang(r *http.Request) context.Context {
|
|||
|
|
return context.WithValue(r.Context(), "Accept-Language", r.Header.Get("Accept-Language"))
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// toResponse 是所有 error 的唯一出口:errorx 错误取业务码,其它错误按 500 处理
|
|||
|
|
func toResponse(ctx context.Context, err error) Response {
|
|||
|
|
var ei errorx.ErrorInterface
|
|||
|
|
if errors.As(err, &ei) {
|
|||
|
|
return Response{Code: ei.GetCode(), Msg: ei.Msg(ctx)}
|
|||
|
|
}
|
|||
|
|
return Response{Code: errorx.ErrInternal.GetCode(), Msg: errorx.ErrInternal.Msg(ctx)}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func writeJSON(w http.ResponseWriter, resp Response) {
|
|||
|
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
|||
|
|
_ = json.NewEncoder(w).Encode(resp)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func main() {
|
|||
|
|
fmt.Println("=== 同一个接口,不同入参与语言 ===")
|
|||
|
|
cases := []struct{ id, lang string }{
|
|||
|
|
{"", "zh"},
|
|||
|
|
{"404", "zh"},
|
|||
|
|
{"404", "en"},
|
|||
|
|
{"500", "en"},
|
|||
|
|
{"42", "zh"},
|
|||
|
|
}
|
|||
|
|
for _, c := range cases {
|
|||
|
|
r := httptest.NewRequest(http.MethodGet, "/user?id="+c.id, nil)
|
|||
|
|
r.Header.Set("Accept-Language", c.lang)
|
|||
|
|
w := httptest.NewRecorder()
|
|||
|
|
userHandler(w, r)
|
|||
|
|
// 统一返回 HTTP 200,业务状态放在 code 字段(国内常见约定),
|
|||
|
|
// 需要真实 HTTP 状态码时,可以在 toResponse 里按 code 映射。
|
|||
|
|
fmt.Printf("GET /user?id=%-4s [%s] -> HTTP %d %s", c.id, c.lang, w.Code, w.Body.String())
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
fmt.Println()
|
|||
|
|
fmt.Println("=== 日志里打印完整信息(带 cause 和调用栈)===")
|
|||
|
|
r := httptest.NewRequest(http.MethodGet, "/user?id=500", nil)
|
|||
|
|
_, err := loadUser(withLang(r), "500")
|
|||
|
|
fmt.Printf("%+v", err)
|
|||
|
|
fmt.Println()
|
|||
|
|
}
|