You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
275 lines
5.8 KiB
275 lines
5.8 KiB
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))
|
|
}
|
|
}
|
|
|