diff --git a/go.mod b/go.mod index 5b8692a..c1d2932 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,8 @@ require ( github.com/samber/lo v1.38.1 github.com/spf13/cast v1.5.0 github.com/spf13/viper v1.15.0 + github.com/tidwall/gjson v1.14.4 + github.com/tidwall/sjson v1.2.5 ) require ( @@ -34,6 +36,8 @@ require ( github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.4.2 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.0 // indirect golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect golang.org/x/sys v0.3.0 // indirect diff --git a/go.sum b/go.sum index 8ac6a0d..28402e3 100644 --- a/go.sum +++ b/go.sum @@ -202,6 +202,15 @@ github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKs github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM= +github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= diff --git a/support/global_params.go b/support/global_params.go new file mode 100644 index 0000000..a06f3a0 --- /dev/null +++ b/support/global_params.go @@ -0,0 +1,228 @@ +package support + +import ( + "time" + + "github.com/samber/lo" + "github.com/spf13/cast" + "github.com/tidwall/gjson" + "github.com/tidwall/sjson" + + "git.fsdpf.net/go/contracts" +) + +type GlobalParams struct { + user *contracts.User + gjson gjson.Result +} + +// 获取用户信息 +func (this GlobalParams) User() contracts.User { + return *this.user +} + +// 获取指定路径的 value +func (this GlobalParams) Get(key string) contracts.GlobalParams { + return &GlobalParams{ + user: this.user, + gjson: this.gjson.Get(key), + } +} + +// +func (this GlobalParams) Array() []contracts.GlobalParams { + return lo.Map(this.gjson.Array(), func(item gjson.Result, _ int) contracts.GlobalParams { + return &GlobalParams{user: this.user, gjson: gjson.Parse(item.Raw)} + }) +} + +// 获取golang原始类型 +func (this GlobalParams) Value() any { + return this.gjson.Value() +} + +func (this GlobalParams) Raw() string { + return this.gjson.Raw +} + +// 设置json值 +func (this *GlobalParams) Set(p string, v any) bool { + if s, err := sjson.Set(this.Raw(), p, v); err != nil { + return false + } else { + this.gjson = gjson.Parse(s) + } + return true +} + +// 设置json原始值 +func (this *GlobalParams) SetRaw(p, v string) bool { + if s, err := sjson.SetRaw(this.Raw(), p, v); err != nil { + return false + } else { + this.gjson = gjson.Parse(s) + } + return true +} + +// 删除路径内容 +func (this *GlobalParams) Delete(p string) bool { + if s, err := sjson.Delete(this.Raw(), p); err != nil { + return false + } else { + this.gjson = gjson.Parse(s) + } + return true +} + +// 路径包裹 +func (this *GlobalParams) Wrapped(p string) contracts.GlobalParams { + return this.AppendTo("", p) +} + +// 包裹内容 +func (this *GlobalParams) AppendTo(root, p string) contracts.GlobalParams { + if root != "" && !gjson.Valid(root) { + root = "" + } + return NewGlobalParam(lo.Must(sjson.SetRaw(root, p, this.Raw())), *this.user) +} + +// 判断值是否存在 +func (this GlobalParams) Exists() bool { + return this.gjson.Exists() +} + +// to Bool +func (this GlobalParams) Bool() bool { + return cast.ToBool(this.Value()) +} + +// to Time +func (this GlobalParams) Time() time.Time { + return cast.ToTime(this.Value()) +} + +// to Location Time +func (this GlobalParams) TimeInDefaultLocation(location *time.Location) time.Time { + return cast.ToTimeInDefaultLocation(this, location) +} + +// to float64 +func (this GlobalParams) Float64() float64 { + return cast.ToFloat64(this.Value()) +} + +// to float32 +func (this GlobalParams) Float32() float32 { + return cast.ToFloat32(this.Value()) +} + +// to int64 +func (this GlobalParams) Int64() int64 { + return cast.ToInt64(this.Value()) +} + +// to int32 +func (this GlobalParams) Int32() int32 { + return cast.ToInt32(this.Value()) +} + +// to int16 +func (this GlobalParams) Int16() int16 { + return cast.ToInt16(this.Value()) +} + +// to int8 +func (this GlobalParams) Int8() int8 { + return cast.ToInt8(this.Value()) +} + +// to int +func (this GlobalParams) Int() int { + return cast.ToInt(this.Value()) +} + +// to uint +func (this GlobalParams) Uint() uint { + return cast.ToUint(this.Value()) +} + +// to uint64 +func (this GlobalParams) Uint64() uint64 { + return cast.ToUint64(this.Value()) +} + +// to uint32 +func (this GlobalParams) Uint32() uint32 { + return cast.ToUint32(this.Value()) +} + +// to uint16 +func (this GlobalParams) Uint16() uint16 { + return cast.ToUint16(this.Value()) +} + +// to uint8 +func (this GlobalParams) Uint8() uint8 { + return cast.ToUint8(this.Value()) +} + +// to string +func (this GlobalParams) String() string { + return cast.ToString(this.Value()) +} + +// to map[string]string +func (this GlobalParams) StringMapString() map[string]string { + return cast.ToStringMapString(this.Value()) +} + +// to map[string][]string +func (this GlobalParams) StringMapStringSlice() map[string][]string { + return cast.ToStringMapStringSlice(this.Value()) +} + +// to map[string]bool +func (this GlobalParams) StringMapBool() map[string]bool { + return cast.ToStringMapBool(this.Value()) +} + +// to map[string]int +func (this GlobalParams) StringMapInt() map[string]int { + return cast.ToStringMapInt(this.Value()) +} + +// to map[string]int64 +func (this GlobalParams) StringMapInt64() map[string]int64 { + return cast.ToStringMapInt64(this.Value()) +} + +// to map[string]any +func (this GlobalParams) StringMap() map[string]any { + return cast.ToStringMap(this.Value()) +} + +// to []any +func (this GlobalParams) Slice() []any { + return cast.ToSlice(this.Value()) +} + +// to []bool +func (this GlobalParams) BoolSlice() []bool { + return cast.ToBoolSlice(this.Value()) +} + +// to []string +func (this GlobalParams) StringSlice() []string { + return cast.ToStringSlice(this.Value()) +} + +// to []int +func (this GlobalParams) IntSlice() []int { + return cast.ToIntSlice(this.Value()) +} + +func NewGlobalParam(data string, user contracts.User) contracts.GlobalParams { + return &GlobalParams{user: &user, gjson: gjson.Parse(data)} +} diff --git a/support/response.go b/support/response.go new file mode 100644 index 0000000..7ec63d0 --- /dev/null +++ b/support/response.go @@ -0,0 +1,205 @@ +package support + +import ( + "encoding/json" + "net/http" + "os" + "runtime" + "strings" + + "git.fsdpf.net/go/contracts" + "github.com/gorilla/websocket" + "github.com/samber/lo" +) + +type JsonResponse struct { + raw []byte +} + +type MsgResponse struct { + code int + msg string +} + +type ErrResponse struct { + *MsgResponse + stack []byte +} + +type FileResponse struct { + disposition string + name string +} + +var wsUpgrader = websocket.Upgrader{ + CheckOrigin: func(r *http.Request) bool { + return true + }, +} + +func (this JsonResponse) Get(path ...string) contracts.GlobalParams { + return lo.Ternary(len(path) == 0, NewGlobalParam(string(this.raw), nil), NewGlobalParam(string(this.raw), nil).Get(strings.Join(path, "."))) +} + +func (this JsonResponse) Send(w http.ResponseWriter, r *http.Request) { + if r.Header.Get("Upgrade") != "" && strings.ToLower(r.Header.Get("Upgrade")) == "websocket" { + c, _ := wsUpgrader.Upgrade(w, r, nil) + c.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, string(this.raw))) + c.Close() + } else { + w.Header().Set("Content-Type", "application/json") + w.Write(this.raw) + } +} + +func (this MsgResponse) Get(path ...string) contracts.GlobalParams { + if resp, err := json.Marshal(map[string]any{ + "code": this.code, + "msg": this.msg, + }); err == nil { + return lo.Ternary(len(path) == 0, NewGlobalParam(string(resp), nil), NewGlobalParam(string(resp), nil).Get(strings.Join(path, "."))) + } + + return nil +} + +func (this MsgResponse) Send(w http.ResponseWriter, r *http.Request) { + if resp, err := json.Marshal(this.Get().Value()); err == nil { + (&JsonResponse{raw: resp}).Send(w, r) + } else { + HttpResponse(err).Send(w, r) + } +} + +func (this ErrResponse) Get(path ...string) contracts.GlobalParams { + if resp, err := json.Marshal(map[string]any{ + "code": this.code, + "msg": this.msg, + "error": string(this.stack), + }); err == nil { + return lo.Ternary(len(path) == 0, NewGlobalParam(string(resp), nil), NewGlobalParam(string(resp), nil).Get(strings.Join(path, "."))) + } + + return nil +} + +func (this ErrResponse) Send(w http.ResponseWriter, r *http.Request) { + if resp, err := json.Marshal(this.Get().Value()); err == nil { + (&JsonResponse{raw: resp}).Send(w, r) + } else { + HttpResponse(err).Send(w, r) + } +} + +func (this FileResponse) Get(path ...string) contracts.GlobalParams { + if resp, err := json.Marshal(map[string]any{ + "name": this.name, + "disposition": this.disposition, + }); err == nil { + return lo.Ternary(len(path) == 0, NewGlobalParam(string(resp), nil), NewGlobalParam(string(resp), nil).Get(strings.Join(path, "."))) + } + + return nil +} + +func (this FileResponse) Send(w http.ResponseWriter, r *http.Request) { + if this.Get("disposition").String() != "" { + w.Header().Set("Content-Disposition", `attachment; filename="`+this.Get("disposition").String()+`"`) + } + if _, err := os.Stat(this.Get("name").String()); err != nil && os.IsNotExist(err) { + w.WriteHeader(http.StatusNotFound) + } else { + http.ServeFile(w, r, this.Get("name").String()) + } +} + +func NewJsonResponse(b []byte) contracts.HttpResponse { + return &JsonResponse{raw: b} +} + +func NewFileResponse(name string, disposition string) contracts.HttpResponse { + return &FileResponse{name: name, disposition: disposition} +} + +func NewMsgResponse(msg string, code int) contracts.HttpResponse { + return &MsgResponse{msg: msg, code: code} +} + +func NewErrResponse(p ...any) contracts.HttpResponse { + code := contracts.InternalServerError.Code + msg := contracts.InternalServerError.Error() + data := any(nil) + + for i := 0; i < len(p); i++ { + switch v := p[i].(type) { + case error: + msg = v.Error() + case string: + msg = v + case int: + code = v + case contracts.Errno: + msg = v.Error() + code = v.Code + case *contracts.Errno: + msg = v.Error() + code = v.Code + default: + data = v + } + } + + stackBuf := make([]byte, 1024) + n := runtime.Stack(stackBuf[:], false) + + b, _ := json.Marshal(struct { + Code int `json:"code"` + Msg string `json:"msg"` + Data any `json:"data,omitempty"` + Stack string `json:"stack"` + }{ + Code: code, + Msg: msg, + Data: data, + Stack: string(stackBuf[:n]), + }) + + return NewJsonResponse(b) +} + +func HttpResponse(data any) contracts.HttpResponse { + var err error = contracts.InternalServerError + + switch v := data.(type) { + case contracts.HttpResponse: + return v + case contracts.Errno: + return NewMsgResponse(v.Error(), v.Code) + case *contracts.Errno: + return NewMsgResponse(v.Error(), v.Code) + case error: + err = v + case []byte: + if b, e := json.Marshal(map[string]any{ + "code": contracts.OK.Code, + "msg": contracts.OK.Msg, + "data": json.RawMessage(v), + }); e == nil { + return &JsonResponse{raw: b} + } else { + err = e + } + default: + if b, e := json.Marshal(map[string]any{ + "code": contracts.OK.Code, + "msg": contracts.OK.Msg, + "data": v, + }); e == nil { + return &JsonResponse{raw: b} + } else { + err = e + } + } + + return NewMsgResponse(err.Error(), contracts.InternalServerError.Code) +} diff --git a/support/util.go b/support/util.go index c96f4b5..37e97c0 100644 --- a/support/util.go +++ b/support/util.go @@ -5,6 +5,8 @@ import ( "reflect" "strings" "unsafe" + + "github.com/spf13/cast" ) func GetFieldValue(i any, k string) any { @@ -42,6 +44,20 @@ func GetFieldValue(i any, k string) any { return kv.Interface() } +func GetStructField(item any, key string) (field reflect.StructField, ok bool) { + vItem := reflect.TypeOf(item) + + if vItem.Kind() == reflect.Ptr { + vItem = vItem.Elem() + } + + if vItem.Kind() != reflect.Struct { + return field, false + } + + return vItem.FieldByName(UcFirst(key)) +} + func InterfaceToSlice(i any) (ret []any) { rv := reflect.ValueOf(i) @@ -87,6 +103,54 @@ func SliceConvert(origSlice any, newSliceType reflect.Type) any { return newSlice.Elem().Interface() } +func DeepSearch(m map[string]any, path []string) map[string]any { + for _, k := range path { + m2, ok := m[k] + if !ok { + // intermediate key does not exist + // => create it and continue from there + m3 := make(map[string]interface{}) + m[k] = m3 + m = m3 + continue + } + m3, ok := m2.(map[string]interface{}) + if !ok { + // intermediate key is a value + // => replace with a new map + m3 = make(map[string]interface{}) + m[k] = m3 + } + // continue search from here + m = m3 + } + return m +} + +func MergeMap(shadow map[string]any, m map[string]any) map[string]any { + if shadow == nil { + shadow = make(map[string]any) + } + + var m2 map[string]any + + for k, val := range m { + switch val.(type) { + case map[string]any: + m2 = val.(map[string]any) + case map[any]any: + m2 = cast.ToStringMap(val) + default: + // immediate value + shadow[k] = val + continue + } + // recursively merge to shadow map + shadow[k] = MergeMap(cast.ToStringMap(shadow[k]), m2) + } + return shadow +} + func UcFirst(s string) string { if s == "" { return "" diff --git a/support/utils_test.go b/support/utils_test.go new file mode 100644 index 0000000..2d0b3e5 --- /dev/null +++ b/support/utils_test.go @@ -0,0 +1,46 @@ +package support + +import ( + "testing" +) + +func TestDeepSearch(t *testing.T) { + m := map[string]any{ + "a": 1, + "b": map[string]any{ + "c": 2, + }, + } + + mb := DeepSearch(m, []string{"b", "a"}) + + t.Log(m) + + mb["cc"] = "3" + + t.Log(m) +} + +func TestMergeMap(t *testing.T) { + m1 := map[string]any{ + "a": 1, + "b": map[string]any{ + "c": 2, + "a": 1, + "b": map[string]any{ + "c": 2, + "a": 1, + }, + }, + } + + m2 := map[string]any{ + "a": 11, + "b": map[string]any{ + "c": 22, + "cc": 33, + }, + } + + t.Log(MergeMap(m1, m2)) +}