package app import ( "encoding/json" "github.com/go-chi/chi/v5" "net/http" "strconv" "sync" ) type TapFunc[V any] func(V) V type Convertor[V any] func(s string) (V, error) type HandlerFunc func(w *ResponseWriter, r *Request) type ParamGetter func(key string) (string, bool) // Binder 参数绑定接口 type Binder interface { Bind(r *Request) error } func GetParam[V any](r *Params, key string, def V, convertor Convertor[V], taps []TapFunc[V]) V { var v V if str, ok := r.Get(key); ok { if x, err := convertor(str); err != nil { v = def } else { v = x } } else { v = def } for _, tap := range taps { v = tap(v) } return v } type Params struct { pg ParamGetter } func NewParams(pg ParamGetter) *Params { return &Params{pg} } func NewPathParams(r *Request) *Params { return NewParams(func(key string) (string, bool) { ctx := chi.RouteContext(r.Context()) for k := len(ctx.URLParams.Keys) - 1; k >= 0; k-- { if ctx.URLParams.Keys[k] == key { return ctx.URLParams.Values[k], true } } return "", false }) } func NewQueryParams(r *Request) *Params { return NewParams(func(key string) (string, bool) { if values, ok := r.URL.Query()[key]; ok && len(values) > 0 { return values[0], true } else { return "", false } }) } func (u *Params) Get(key string) (string, bool) { return u.pg(key) } func (u *Params) Value(key string, taps ...TapFunc[string]) string { val, _ := u.Get(key) for _, tap := range taps { val = tap(val) } return val } func (u *Params) Int(key string, def int, taps ...TapFunc[int]) int { return GetParam[int](u, key, def, func(s string) (int, error) { n, e := strconv.ParseInt(s, 10, 64) return int(n), e }, taps) } func (u *Params) Int32(key string, def int32, taps ...TapFunc[int32]) int32 { return GetParam[int32](u, key, def, func(s string) (int32, error) { n, e := strconv.ParseInt(s, 10, 64) return int32(n), e }, taps) } func (u *Params) Int64(key string, def int64, taps ...TapFunc[int64]) int64 { return GetParam[int64](u, key, def, func(s string) (int64, error) { return strconv.ParseInt(s, 10, 64) }, taps) } func (u *Params) Uint(key string, def uint, taps ...TapFunc[uint]) uint { return GetParam[uint](u, key, def, func(s string) (uint, error) { n, e := strconv.ParseUint(s, 10, 64) return uint(n), e }, taps) } func (u *Params) Uint32(key string, def uint32, taps ...TapFunc[uint32]) uint32 { return GetParam[uint32](u, key, def, func(s string) (uint32, error) { n, e := strconv.ParseUint(s, 10, 64) return uint32(n), e }, taps) } func (u *Params) Uint64(key string, def uint64, taps ...TapFunc[uint64]) uint64 { return GetParam[uint64](u, key, def, func(s string) (uint64, error) { return strconv.ParseUint(s, 10, 64) }, taps) } func (u *Params) Float32(key string, def float32, taps ...TapFunc[float32]) float32 { return GetParam[float32](u, key, def, func(s string) (float32, error) { f, e := strconv.ParseFloat(s, 32) return float32(f), e }, taps) } func (u *Params) Float64(key string, def float64, taps ...TapFunc[float64]) float64 { return GetParam[float64](u, key, def, func(s string) (float64, error) { return strconv.ParseFloat(s, 32) }, taps) } type Request struct { *http.Request *Params pathParams *Params queryParams *Params } func NewRequest(r *http.Request) *Request { return &Request{ Request: r, Params: NewParams(func(key string) (string, bool) { _ = r.ParseForm() if values, ok := r.Form[key]; ok && len(values) > 0 { return values[0], true } ctx := chi.RouteContext(r.Context()) for k := len(ctx.URLParams.Keys) - 1; k >= 0; k-- { if ctx.URLParams.Keys[k] == key { return ctx.URLParams.Values[k], true } } return "", false }), } } func (r *Request) get(key string) (string, bool) { _ = r.ParseForm() values, ok := r.PostForm[key] if !ok { values, ok = r.Form[key] } if !ok { values, ok = r.URL.Query()[key] } if ok { return values[0], true } return "", false } func (r *Request) PathParams() *Params { if r.pathParams == nil { r.pathParams = NewPathParams(r) } return r.pathParams } func (r *Request) QueryParams() *Params { if r.queryParams == nil { r.queryParams = NewQueryParams(r) } return r.queryParams } type ResponseWriter struct { http.ResponseWriter mutex sync.RWMutex sent bool } func NewResponseWriter(w http.ResponseWriter) *ResponseWriter { return &ResponseWriter{ ResponseWriter: w, mutex: sync.RWMutex{}, sent: false, } } func (w *ResponseWriter) IsSent() bool { w.mutex.RLock() defer w.mutex.RUnlock() return w.sent } func (w *ResponseWriter) Send(status int, body any) { w.mutex.Lock() defer w.mutex.Unlock() if w.sent { LogWarning("the response writer was sent") return } buf, err := json.Marshal(body) if err != nil { w.Error(err) return } w.sent = true w.WriteHeader(status) w.Header().Set("Content-Type", "application/json; charset=UTF-8") if _, err := w.Write(buf); err != nil { LogError(err) } } func (w *ResponseWriter) Error(err error) { var status int var code int var message string if ex, ok := err.(*Error); ok { status = http.StatusBadRequest code = ex.Code message = ex.Message if ex.Status > 0 { status = ex.Status } } else { status = http.StatusInternalServerError code = -1 message = err.Error() LogError(err) } if len(message) == 0 { message = http.StatusText(status) } w.Send(status, map[string]any{ "code": code, "message": message, "success": code == 0, }) } func (w *ResponseWriter) Ok(data any, message ...string) { info := map[string]any{ "code": 0, "message": "ok", "success": true, "data": data, } if len(message) > 0 { info["message"] = message[0] } w.Send(http.StatusOK, info) } func Handler(hf HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { hf(NewResponseWriter(w), NewRequest(r)) } }