commit 754e1ae43c92983c5cf0c261a4dd3b6f89165411 Author: Shiny Date: Tue Dec 17 22:28:48 2024 +0800 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fd13d0f --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +.idea +.vscode +*.log +*.exe +/go.sum +yaml +.DS_Store \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..14362e0 --- /dev/null +++ b/go.mod @@ -0,0 +1,75 @@ +module code.zhecent.com/gopkg/light-core + +go 1.23.1 + +require ( + github.com/aliyun/alibaba-cloud-sdk-go v1.63.68 + github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible + github.com/gin-gonic/gin v1.10.0 + github.com/go-pay/gopay v1.5.106 + github.com/golang-jwt/jwt/v5 v5.2.1 + github.com/redis/go-redis/v9 v9.7.0 + github.com/shopspring/decimal v1.4.0 + github.com/spf13/cobra v1.8.1 + github.com/spf13/viper v1.19.0 + golang.org/x/crypto v0.31.0 + google.golang.org/protobuf v1.36.0 + gorm.io/driver/mysql v1.5.7 + gorm.io/gorm v1.25.12 +) + +require ( + github.com/bytedance/sonic v1.11.6 // indirect + github.com/bytedance/sonic/loader v0.1.1 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/cloudwego/base64x v0.1.4 // indirect + github.com/cloudwego/iasm v0.2.0 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-pay/crypto v0.0.1 // indirect + github.com/go-pay/errgroup v0.0.2 // indirect + github.com/go-pay/util v0.0.4 // indirect + github.com/go-pay/xlog v0.0.3 // indirect + github.com/go-pay/xtime v0.0.2 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.20.0 // indirect + github.com/go-sql-driver/mysql v1.7.0 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.7 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/sagikazarmark/locafero v0.4.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.11.0 // indirect + github.com/spf13/cast v1.6.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.12 // indirect + go.uber.org/atomic v1.9.0 // indirect + go.uber.org/multierr v1.9.0 // indirect + golang.org/x/arch v0.8.0 // indirect + golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect + golang.org/x/net v0.25.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/text v0.21.0 // indirect + golang.org/x/time v0.5.0 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/impl/int64Array.go b/impl/int64Array.go new file mode 100644 index 0000000..1384c12 --- /dev/null +++ b/impl/int64Array.go @@ -0,0 +1,85 @@ +package impl + +import ( + "database/sql/driver" + "encoding/json" +) + +type Int64Array []int64 + +func (v *Int64Array) Scan(value interface{}) error { + //判断类型 + switch data := value.(type) { + case []byte: + return json.Unmarshal(data, v) + case string: + return json.Unmarshal([]byte(data), v) + case nil: + return nil + } + return json.Unmarshal(value.([]byte), v) +} + +func (v Int64Array) Value() (driver.Value, error) { + return json.Marshal(v) +} + +func (v Int64Array) RemoveDuplicates() Int64Array { + newArr := make([]int64, 0) + for i := 0; i < len(v); i++ { + repeat := false + for j := i + 1; j < len(v); j++ { + if v[i] == v[j] { + repeat = true + break + } + } + if !repeat { + newArr = append(newArr, v[i]) + } + } + return newArr +} +func (v Int64Array) RemoveEmpty() Int64Array { + return v.RemoveValue(0) +} + +func (v Int64Array) RemoveValue(value int64) Int64Array { + newArr := make([]int64, 0) + for i := 0; i < len(v); i++ { + if v[i] != value { + newArr = append(newArr, v[i]) + } + } + return newArr +} + +func (v Int64Array) Contains(id int64) bool { + for _, i := range v { + if i == id { + return true + } + } + return false +} + +func (v Int64Array) Eq(arr []int64) bool { + if len(v) != len(arr) { + return false + } + for _, i := range arr { + //只要有一个不包含就是不一样的 + if v.Contains(i) == false { + return false + } + } + return true +} + +func (v Int64Array) ToIntSlice() []int { + out := make([]int, 0) + for _, i := range v { + out = append(out, int(i)) + } + return out +} diff --git a/impl/intArray.go b/impl/intArray.go new file mode 100644 index 0000000..43451e3 --- /dev/null +++ b/impl/intArray.go @@ -0,0 +1,86 @@ +package impl + +import ( + "database/sql/driver" + "encoding/json" +) + +type IntArray []int + +func (v *IntArray) Scan(value interface{}) error { + //判断类型 + switch data := value.(type) { + case []byte: + return json.Unmarshal(data, v) + case string: + return json.Unmarshal([]byte(data), v) + case nil: + return nil + } + return json.Unmarshal(value.([]byte), v) +} + +func (v IntArray) Value() (driver.Value, error) { + return json.Marshal(v) +} + +func (v IntArray) RemoveDuplicates() IntArray { + newArr := make([]int, 0) + for i := 0; i < len(v); i++ { + repeat := false + for j := i + 1; j < len(v); j++ { + if v[i] == v[j] { + repeat = true + break + } + } + if !repeat { + newArr = append(newArr, v[i]) + } + } + return newArr +} + +func (v IntArray) RemoveEmpty() IntArray { + return v.RemoveValue(0) +} + +func (v IntArray) RemoveValue(value int) IntArray { + newArr := make([]int, 0) + for i := 0; i < len(v); i++ { + if v[i] != value { + newArr = append(newArr, v[i]) + } + } + return newArr +} + +func (v IntArray) Contains(id int) bool { + for _, i := range v { + if i == id { + return true + } + } + return false +} + +func (v IntArray) Eq(arr []int) bool { + if len(v) != len(arr) { + return false + } + for _, i := range arr { + //只要有一个不包含就是不一样的 + if v.Contains(i) == false { + return false + } + } + return true +} + +func (v IntArray) ToIntSlice() []int { + out := make([]int, 0) + for _, i := range v { + out = append(out, int(i)) + } + return out +} diff --git a/impl/stringArray.go b/impl/stringArray.go new file mode 100644 index 0000000..00ab296 --- /dev/null +++ b/impl/stringArray.go @@ -0,0 +1,87 @@ +package impl + +import ( + "database/sql/driver" + "encoding/json" + "strings" +) + +type StringArray []string + +func (v *StringArray) Scan(value interface{}) error { + //判断类型 + switch data := value.(type) { + case []byte: + return json.Unmarshal(data, v) + case string: + return json.Unmarshal([]byte(data), v) + case nil: + return nil + } + return json.Unmarshal(value.([]byte), v) +} + +func (v StringArray) Value() (driver.Value, error) { + return json.Marshal(v) +} + +func (v StringArray) RemoveDuplicates() StringArray { + newArr := make([]string, 0) + for i := 0; i < len(v); i++ { + repeat := false + for j := i + 1; j < len(v); j++ { + if v[i] == v[j] { + repeat = true + break + } + } + if !repeat { + newArr = append(newArr, v[i]) + } + } + return newArr +} + +func (v StringArray) RemoveEmpty() StringArray { + return v.RemoveValue("") +} + +func (v StringArray) RemoveValue(value string) StringArray { + newArr := make([]string, 0) + for i := 0; i < len(v); i++ { + if !strings.EqualFold(v[i], value) { + newArr = append(newArr, v[i]) + } + } + return newArr +} + +func (v StringArray) Contains(str string) bool { + for _, i := range v { + if i == str { + return true + } + } + return false +} + +func (v StringArray) Eq(strArr []string) bool { + if len(v) != len(strArr) { + return false + } + for _, i := range strArr { + //只要有一个不包含就是不一样的 + if v.Contains(i) == false { + return false + } + } + return true +} + +func (v StringArray) ToStringSlice() []string { + out := make([]string, 0) + for _, i := range v { + out = append(out, i) + } + return out +} diff --git a/lightCore/Config.go b/lightCore/Config.go new file mode 100644 index 0000000..eeebf02 --- /dev/null +++ b/lightCore/Config.go @@ -0,0 +1,52 @@ +package lightCore + +var ConfigValue = &Config{} + +type Config struct { + App AppConfigModel `mapstructure:"app" yaml:"app"` + Server ServerConfigModel `mapstructure:"server" yaml:"server"` + Database DatabaseConfigModel `mapstructure:"database" yaml:"database"` + Redis RedisConfigModel `mapstructure:"redis" yaml:"redis"` + Admin AdminConfigModel `mapstructure:"admin" yaml:"admin"` +} + +func (t *Config) IsRelease() bool { + return t.Server.RunMode == "release" +} + +type AppConfigModel struct { + JwtSecret string `mapstructure:"jwt_secret" yaml:"jwt_secret"` + AliOssSign string `mapstructure:"ali_oss_sign" yaml:"ali_oss_sign"` +} + +type AdminConfigModel struct { + JwtSecret string `mapstructure:"jwt_secret" yaml:"jwt_secret"` + JwtExpires int `mapstructure:"jwt_expires" yaml:"jwt_expires"` + ApiPath string `mapstructure:"api_path" yaml:"api_path"` +} + +type ServerConfigModel struct { + RunMode string `mapstructure:"run_mode" yaml:"run_mode"` + HttpPort int `mapstructure:"http_port" yaml:"http_port"` + ReadTimeout int `mapstructure:"read_timeout" yaml:"read_timeout"` + WriteTimeout int `mapstructure:"write_timeout" yaml:"write_timeout"` +} + +type DatabaseConfigModel struct { + Type string `mapstructure:"type" yaml:"type"` + User string `mapstructure:"user" yaml:"user"` + Password string `mapstructure:"password" yaml:"password"` + Host string `mapstructure:"host" yaml:"host"` + Name string `mapstructure:"name" yaml:"name"` + Charset string `mapstructure:"charset" yaml:"charset"` + TablePrefix string `mapstructure:"table_prefix" yaml:"table_prefix"` + LogLevel string `mapstructure:"log_level" yaml:"log_level"` +} + +type RedisConfigModel struct { + Host string `mapstructure:"host" yaml:"host"` + Password string `mapstructure:"password" yaml:"password"` + MaxIdle string `mapstructure:"max_idle" yaml:"max_idle"` + MaxActive string `mapstructure:"max_active" yaml:"max_active"` + IdleTimeout string `mapstructure:"idle_timeout" yaml:"idle_timeout"` +} diff --git a/lightCore/Db.go b/lightCore/Db.go new file mode 100644 index 0000000..b03d4e3 --- /dev/null +++ b/lightCore/Db.go @@ -0,0 +1,21 @@ +package lightCore + +import ( + "code.zhecent.com/gopkg/light-core/pkg/myGorm" + "gorm.io/gorm" +) + +var DBEngine *gorm.DB + +func InitDB() { + orm := myGorm.NewSimpleORM( + ConfigValue.Database.User, + ConfigValue.Database.Password, + ConfigValue.Database.Host, + ConfigValue.Database.Name, + ConfigValue.Database.Charset, + ConfigValue.Server.RunMode, + ) + orm.SetLoggerLevel(ConfigValue.Database.LogLevel) + DBEngine = orm.ConnectMysql() +} diff --git a/lightCore/Fairing.go b/lightCore/Fairing.go new file mode 100644 index 0000000..61f9673 --- /dev/null +++ b/lightCore/Fairing.go @@ -0,0 +1,7 @@ +package lightCore + +import "github.com/gin-gonic/gin" + +type Fairing interface { + OnRequest(c *gin.Context) error +} diff --git a/lightCore/GromAdapter.go b/lightCore/GromAdapter.go new file mode 100644 index 0000000..7f2f6e4 --- /dev/null +++ b/lightCore/GromAdapter.go @@ -0,0 +1,11 @@ +package lightCore + +import "gorm.io/gorm" + +type GormAdapter struct { + *gorm.DB +} + +func NewGormAdapter(db *gorm.DB) *GormAdapter { + return &GormAdapter{DB: db} +} diff --git a/lightCore/IClass.go b/lightCore/IClass.go new file mode 100644 index 0000000..d5d8d65 --- /dev/null +++ b/lightCore/IClass.go @@ -0,0 +1,24 @@ +package lightCore + +import "github.com/gin-gonic/gin" + +type IClass interface { + Build(core *LightCore) +} + +type ClassMounts struct { + handles []IClass + errorHandler []gin.HandlerFunc +} + +func NewClassMounts(handles []IClass, errorHandler []gin.HandlerFunc) *ClassMounts { + return &ClassMounts{handles: handles, errorHandler: errorHandler} +} + +func (t *ClassMounts) Handles() []IClass { + return t.handles +} + +func (t *ClassMounts) ErrorHandler() []gin.HandlerFunc { + return t.errorHandler +} diff --git a/lightCore/IRouters.go b/lightCore/IRouters.go new file mode 100644 index 0000000..a422474 --- /dev/null +++ b/lightCore/IRouters.go @@ -0,0 +1,10 @@ +package lightCore + +type Routers struct { + groupPath string + cs *ClassMounts +} + +func NewRouters(groupPath string, cs *ClassMounts) *Routers { + return &Routers{groupPath: groupPath, cs: cs} +} diff --git a/lightCore/LightCore.go b/lightCore/LightCore.go new file mode 100644 index 0000000..923e6ec --- /dev/null +++ b/lightCore/LightCore.go @@ -0,0 +1,135 @@ +package lightCore + +import ( + "fmt" + "github.com/gin-gonic/gin" + "net/http" + "reflect" +) + +type LightCore struct { + gin *gin.Engine + g *gin.RouterGroup + props []interface{} +} + +func StartEngine() *LightCore { + gin.SetMode(ConfigValue.Server.RunMode) + g := &LightCore{gin: gin.New(), props: make([]interface{}, 0)} + g.gin.Use(gin.Logger(), gin.Recovery()) + return g +} +func (t *LightCore) GetGin() *gin.Engine { + return t.gin +} + +func (t *LightCore) Launch() { + err := t.gin.Run(fmt.Sprintf(":%d", ConfigValue.Server.HttpPort)) + if err != nil { + panic("Gin Launch Error") + } + + //server := &http.Server{ + // Addr: fmt.Sprintf(":%d", addr), + // Handler: t, + // MaxHeaderBytes: 1 << 20, + //} + //err := server.ListenAndServe() + //if err != nil { + // panic("Gin Launch Error") + //} +} + +func (t *LightCore) Attach(f Fairing) *LightCore { + t.gin.Use(func(context *gin.Context) { + err := f.OnRequest(context) + if err != nil { + context.AbortWithStatusJSON(http.StatusOK, gin.H{"error": "onRequest"}) + } else { + context.Next() + } + }) + return t +} +func (t *LightCore) LoadHtml(path string) *LightCore { + t.gin.LoadHTMLGlob(path) + return t +} + +func (t *LightCore) StaticFS(path string, fs http.FileSystem) *LightCore { + t.gin.StaticFS(path, fs) + return t +} + +func (t *LightCore) Beans(beans ...interface{}) *LightCore { + t.props = append(t.props, beans...) + return t +} + +func (t *LightCore) Handle(httpMethod, relativePath string, handler interface{}, mids ...gin.HandlerFunc) *LightCore { + if h := Convert(handler); h != nil { + if len(mids) > 0 { + mids = append(mids, h) + t.g.Handle(httpMethod, relativePath, mids...) + } else { + t.g.Handle(httpMethod, relativePath, h) + } + + } + return t +} + +func (t *LightCore) MountRaw(f func(s *gin.Engine)) *LightCore { + f(t.GetGin()) + return t +} + +func (t *LightCore) Mount(group string, errorHandlerFunc []gin.HandlerFunc, classes ...IClass) *LightCore { + t.g = t.gin.Group(group) + if len(errorHandlerFunc) > 0 { + t.g.Use(errorHandlerFunc...) + } + for _, class := range classes { + class.Build(t) + //其实就是找到相同类型的数据就赋值上去 + t.setProp(class) + } + return t +} + +func (t *LightCore) Mounts(routers []*Routers) *LightCore { + for _, router := range routers { + t.Mount(router.groupPath, router.cs.errorHandler, router.cs.Handles()...) + } + return t +} + +func (t *LightCore) SetNoRoute(f func(context *gin.Context)) *LightCore { + t.gin.NoRoute(f) + return t +} + +// 查找对应名字的属性 +func (t *LightCore) getProp(tp reflect.Type) interface{} { + for _, p := range t.props { + if tp == reflect.TypeOf(p) { + return p + } + } + return nil +} + +func (t *LightCore) setProp(class IClass) { + vClass := reflect.ValueOf(class).Elem() + for i := 0; i < vClass.NumField(); i++ { + f := vClass.Field(i) + if !f.IsNil() || f.Kind() != reflect.Ptr { + //已经赋值了,不需要初始化了。 + continue + } + if p := t.getProp(f.Type()); p != nil { + f.Set(reflect.New(f.Type().Elem())) + f.Elem().Set(reflect.ValueOf(p).Elem()) + } + } +} diff --git a/lightCore/Model.go b/lightCore/Model.go new file mode 100644 index 0000000..fe4656d --- /dev/null +++ b/lightCore/Model.go @@ -0,0 +1,5 @@ +package lightCore + +type Model interface { + IsLightModel() +} diff --git a/lightCore/Responder.go b/lightCore/Responder.go new file mode 100644 index 0000000..75c4aa6 --- /dev/null +++ b/lightCore/Responder.go @@ -0,0 +1,162 @@ +package lightCore + +import ( + "code.zhecent.com/gopkg/light-core/pkg/myAes" + "github.com/gin-gonic/gin" + "google.golang.org/protobuf/proto" + "net/http" + "reflect" +) + +var RespondList []Responder + +func init() { + RespondList = []Responder{ + new(EmptyResponder), + new(StringResponder), + new(ModelResponder), + new(SuccessResponder), + new(JsonErrorResponder), + new(ViewResponder), + new(ProtobufResponder), + new(ProtobufAesResponder), + new(RedirectResponder), + } +} + +type Responder interface { + RespondTo() gin.HandlerFunc +} + +func Convert(handler interface{}) gin.HandlerFunc { + hRef := reflect.ValueOf(handler) + for _, r := range RespondList { + rRef := reflect.ValueOf(r).Elem() + if hRef.Type().ConvertibleTo(rRef.Type()) { + rRef.Set(hRef) + return rRef.Interface().(Responder).RespondTo() + } + } + return nil +} + +type StringResponder func(*gin.Context) string + +func (t StringResponder) RespondTo() gin.HandlerFunc { + return func(context *gin.Context) { + context.String(http.StatusOK, t(context)) + } +} + +type ModelResponder func(*gin.Context) Model + +func (t ModelResponder) RespondTo() gin.HandlerFunc { + return func(context *gin.Context) { + context.JSON(http.StatusOK, gin.H{ + "code": 200, + "msg": "", + "data": t(context), + }) + } +} + +type JsonSuccess string + +type SuccessResponder func(*gin.Context) JsonSuccess + +func (t SuccessResponder) RespondTo() gin.HandlerFunc { + return func(context *gin.Context) { + context.JSON(http.StatusOK, gin.H{ + "code": 200, + "msg": string(t(context)), + "data": []int{}, + }) + } +} + +type JsonError error + +type JsonErrorResponder func(*gin.Context) JsonError + +func (t JsonErrorResponder) RespondTo() gin.HandlerFunc { + return func(context *gin.Context) { + err := t(context) + if err != nil { + context.JSON(http.StatusOK, gin.H{ + "code": 400, + "msg": err.Error(), + "data": []int{}, + }) + } else { + context.JSON(http.StatusOK, gin.H{ + "code": 200, + "msg": "success", + "data": []int{}, + }) + } + } +} + +type EmptyResponder func(*gin.Context) + +func (t EmptyResponder) RespondTo() gin.HandlerFunc { + //func(c *gin.Context)其实就是reply + //t,提示就是方法本身,其实就是reply + return func(context *gin.Context) { + //context是gin给的,全新的,没有任何资料的 + //t(context)其实就是运行reply + t(context) + //fmt.Println("========================") + //fmt.Println(context.Writer) + //fmt.Println("========================") + } + +} + +type View string + +type ViewResponder func(*gin.Context) View + +func (t ViewResponder) RespondTo() gin.HandlerFunc { + return func(context *gin.Context) { + context.HTML(http.StatusOK, string(t(context))+".html", context.Keys) + } +} + +type Protobuf proto.Message +type ProtobufResponder func(*gin.Context) Protobuf + +func (t ProtobufResponder) RespondTo() gin.HandlerFunc { + return func(context *gin.Context) { + context.ProtoBuf(http.StatusOK, t(context)) + } +} + +type ProtobufAes proto.Message +type ProtobufAesResponder func(*gin.Context) ProtobufAes + +func (t ProtobufAesResponder) RespondTo() gin.HandlerFunc { + return func(context *gin.Context) { + str, err := proto.Marshal(t(context)) + if err != nil { + context.String(http.StatusBadRequest, err.Error()) + } else { + decrypt, err := myAes.AesTool(context.GetString("LightAppKey"), context.GetString("LightAppIv")).Encrypt(string(str)) + if err != nil { + context.String(http.StatusBadRequest, err.Error()) + } else { + context.String(http.StatusOK, string(decrypt)) + } + } + } +} + +type Redirect string + +type RedirectResponder func(*gin.Context) Redirect + +func (t RedirectResponder) RespondTo() gin.HandlerFunc { + return func(context *gin.Context) { + context.Redirect(http.StatusFound, string(t(context))) + } +} diff --git a/lightCore/Start.go b/lightCore/Start.go new file mode 100644 index 0000000..fc7f4ba --- /dev/null +++ b/lightCore/Start.go @@ -0,0 +1,33 @@ +package lightCore + +import "code.zhecent.com/gopkg/light-core/pkg/myViper" + +type Start struct { + config *Config +} + +func NewByConfig(config *Config) *Start { + return &Start{config: config} +} + +func NewByYAML(fileName string, path string) *Start { + c := &Config{} + myViper.NewSimpleViper(c, "yaml", fileName, path).Apply() + return &Start{config: c} +} + +func (t *Start) Start() { + //校验配置 + if t.config.App.JwtSecret == "" { + panic("App JwtSecret Empty") + } + + if t.config.Server.RunMode == "" || t.config.Server.HttpPort == 0 { + panic("Server Config Error") + } + + ConfigValue = t.config + + //初始化数据库 + InitDB() +} diff --git a/lightCore/Task.go b/lightCore/Task.go new file mode 100644 index 0000000..2a3b362 --- /dev/null +++ b/lightCore/Task.go @@ -0,0 +1,76 @@ +package lightCore + +import ( + "fmt" + "sync" +) + +type TaskFunc func() + +var taskList chan *TaskExecutor //任务列表 +var once sync.Once + +func getTaskList() chan *TaskExecutor { + once.Do(func() { + taskList = make(chan *TaskExecutor, 0) + }) + return taskList +} + +func init() { + chList := getTaskList() //得到任务列表 + go func() { + for t := range chList { + doTask(t) + } + }() +} + +func doTask(t *TaskExecutor) { + go func() { + defer func() { + if t.callback != nil { + t.callback() + } + }() + t.Exec() + }() +} + +type TaskExecutor struct { + f TaskFunc + callback func() +} + +func NewTaskExecutor(f TaskFunc, callback func()) *TaskExecutor { + return &TaskExecutor{f: f, callback: callback} +} + +func (t *TaskExecutor) Exec() { + t.f() +} + +func Task(f TaskFunc, callBack func()) { + if f == nil { + return + } + newF := func() { + defer func() { + if e := recover(); e != nil { + if err, ok := e.(string); ok { + fmt.Println("===================================") + fmt.Println(fmt.Sprintf("协程运行错误:%s", err)) + fmt.Println("===================================") + } else { + fmt.Println("===================================") + fmt.Println(fmt.Sprintf("协程运行错误:%s", "未知")) + fmt.Println("===================================") + } + } + }() + f() + } + go func() { + getTaskList() <- NewTaskExecutor(newF, callBack) //添加任务队列 + }() +} diff --git a/pkg/myAes/aes.go b/pkg/myAes/aes.go new file mode 100644 index 0000000..d2f7f32 --- /dev/null +++ b/pkg/myAes/aes.go @@ -0,0 +1,83 @@ +package myAes + +import ( + "bytes" + basicAES "crypto/aes" + "crypto/cipher" + "encoding/base64" +) + +type Aes struct { + securityKey []byte + iv []byte +} + +/** + * constructor + */ +func AesTool(securityKey string, iv string) *Aes { + return &Aes{[]byte(securityKey), []byte(iv)} +} + +/** + * 加密 + * @param string $plainText 明文 + * @return bool|string + */ +func (a Aes) Encrypt(plainText string) (string, error) { + block, err := basicAES.NewCipher(a.securityKey) + if err != nil { + return "", err + } + plainTextByte := []byte(plainText) + blockSize := block.BlockSize() + plainTextByte = addPKCS7Padding(plainTextByte, blockSize) + cipherText := make([]byte, len(plainTextByte)) + mode := cipher.NewCBCEncrypter(block, a.iv) + mode.CryptBlocks(cipherText, plainTextByte) + return base64.StdEncoding.EncodeToString(cipherText), nil +} + +/** + * 解密 + * @param string $cipherText 密文 + * @return bool|string + */ +func (a Aes) Decrypt(cipherText string) ([]byte, error) { + block, err := basicAES.NewCipher(a.securityKey) + if err != nil { + return []byte{}, err + } + cipherDecodeText, decodeErr := base64.StdEncoding.DecodeString(cipherText) + if decodeErr != nil { + return []byte{}, decodeErr + } + + mode := cipher.NewCBCDecrypter(block, a.iv) + originCipherText := make([]byte, len(cipherDecodeText)) + mode.CryptBlocks(originCipherText, cipherDecodeText) + originCipherText = stripPKSC7Padding(originCipherText) + return originCipherText, nil +} + +/** + * 填充算法 + * @param string $source + * @return string + */ +func addPKCS7Padding(ciphertext []byte, blockSize int) []byte { + padding := blockSize - len(ciphertext)%blockSize + paddingText := bytes.Repeat([]byte{byte(padding)}, padding) + return append(ciphertext, paddingText...) +} + +/** + * 移去填充算法 + * @param string $source + * @return string + */ +func stripPKSC7Padding(cipherText []byte) []byte { + length := len(cipherText) + unpadding := int(cipherText[length-1]) + return cipherText[:(length - unpadding)] +} diff --git a/pkg/myAliMarket/index.go b/pkg/myAliMarket/index.go new file mode 100644 index 0000000..58f5cba --- /dev/null +++ b/pkg/myAliMarket/index.go @@ -0,0 +1,90 @@ +package myAliMarket + +import ( + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net/http" + "net/url" +) + +type Client struct { + url string + appCode string + param map[string]string + fullUrl string + response string +} + +func New(url string, appCode string) *Client { + return &Client{ + url: url, + appCode: appCode, + } +} + +func (t *Client) SetParam(param map[string]string) { + t.param = param +} + +func (t *Client) getFullUrl() (string, error) { + u, err := url.Parse(t.url) + if err != nil { + return "", err + } + q := u.Query() + for k, v := range t.param { + q.Add(k, v) + } + u.RawQuery = q.Encode() + t.fullUrl = u.String() + return t.fullUrl, nil +} + +func (t *Client) GetFullUrl() string { + return t.fullUrl +} + +func (t *Client) GetResponse() string { + return t.response +} + +func (t *Client) GetRequest(respData interface{}) error { + fullUrl, err := t.getFullUrl() + if err != nil { + return err + } + + client := &http.Client{} + request, err := http.NewRequest("GET", fullUrl, nil) + if err != nil { + return errors.New("http客户端初始化失败") + } + request.Header.Add("Authorization", fmt.Sprintf("APPCODE %s", t.appCode)) + + response, err := client.Do(request) + if err != nil { + return errors.New("认证服务器连接失败1") + } + + defer response.Body.Close() + + if response.StatusCode != http.StatusOK { + return errors.New("认证服务器连接失败2") + } + + var response2 []byte + response2, err = ioutil.ReadAll(response.Body) + if err != nil { + return errors.New("数据解析失败") + } + + t.response = string(response2) + + err = json.Unmarshal(response2, respData) + if err != nil { + return err + } + return nil +} diff --git a/pkg/myAliSms/index.go b/pkg/myAliSms/index.go new file mode 100644 index 0000000..dea07fa --- /dev/null +++ b/pkg/myAliSms/index.go @@ -0,0 +1,61 @@ +package myAliSms + +import ( + "encoding/json" + "errors" + "github.com/aliyun/alibaba-cloud-sdk-go/services/dysmsapi" +) + +type Client struct { + accessKeyId string + accessSecret string + signName string + templateCode string +} + +func NewClient(accessKeyId string, accessSecret string) *Client { + return &Client{accessKeyId: accessKeyId, accessSecret: accessSecret} +} + +func (t *Client) SetSignName(signName string) *Client { + t.signName = signName + return t +} + +func (t *Client) SetTemplateCode(code string) *Client { + t.templateCode = code + return t +} + +func (t *Client) SendSms(m map[string]interface{}, phone string) error { + if t.signName == "" { + return errors.New("签名不能为空") + } + client, err := dysmsapi.NewClientWithAccessKey("cn-hangzhou", t.accessKeyId, t.accessSecret) + if err != nil { + return err + } + + request := dysmsapi.CreateSendSmsRequest() + request.Scheme = "https" + request.PhoneNumbers = phone + request.SignName = t.signName + request.TemplateCode = t.templateCode + request.TemplateParam = t.mapToJson(m) + + response, err := client.SendSms(request) + if err != nil { + return err + } else { + if response.Code == "OK" { + return nil + } else { + return errors.New(response.Message) + } + } +} + +func (t *Client) mapToJson(TemplateParamMap map[string]interface{}) string { + mjson, _ := json.Marshal(TemplateParamMap) + return string(mjson) +} diff --git a/pkg/myCobra/cobra.go b/pkg/myCobra/cobra.go new file mode 100644 index 0000000..7f349bb --- /dev/null +++ b/pkg/myCobra/cobra.go @@ -0,0 +1,50 @@ +package myCobra + +import ( + "github.com/spf13/cobra" +) + +type SimpleCmd struct { + Use string + Short string + Example string + PreRun func() + RunE func() error + cobraModel *cobra.Command +} + +func (t *SimpleCmd) GetCobra() *cobra.Command { + if t.cobraModel == nil { + t.cobraModel = &cobra.Command{ + Use: t.Use, + Short: t.Short, + Example: t.Example, + SilenceUsage: true, + PreRun: func(cmd *cobra.Command, args []string) { + t.PreRun() + }, + RunE: func(cmd *cobra.Command, args []string) error { + return t.RunE() + }, + } + } + return t.cobraModel +} + +func (t *SimpleCmd) SetArgsFunc(argsFunc func(args []string) error) { + t.GetCobra().Args = func(cmd *cobra.Command, args []string) error { + return argsFunc(args) + } +} + +func (t *SimpleCmd) SetStringVar(p *string, name, shorthand string, value string, usage string) { + t.GetCobra().PersistentFlags().StringVarP(p, name, shorthand, value, usage) +} + +func (t *SimpleCmd) AddCommand(cmd *SimpleCmd) { + t.GetCobra().AddCommand(cmd.GetCobra()) +} + +func (t *SimpleCmd) Execute() error { + return t.GetCobra().Execute() +} diff --git a/pkg/myGorm/gorm.go b/pkg/myGorm/gorm.go new file mode 100644 index 0000000..ce5ff6b --- /dev/null +++ b/pkg/myGorm/gorm.go @@ -0,0 +1,88 @@ +package myGorm + +import ( + "fmt" + "gorm.io/driver/mysql" + "gorm.io/gorm" + "gorm.io/gorm/logger" + "log" + "os" + "strings" + "time" +) + +type SimpleORM struct { + user string + password string + host string + name string + charset string + runMode string + loggerConfig logger.Config + loggerLevel string +} + +func NewSimpleORM(user string, password string, host string, name string, charset string, runMode string) *SimpleORM { + orm := &SimpleORM{ + user: user, + password: password, + host: host, + name: name, + charset: charset, + runMode: runMode, + loggerConfig: logger.Config{ + SlowThreshold: time.Second, // 慢 SQL 阈值 + Colorful: false, // 禁用彩色打印 + LogLevel: logger.Info, + }, + } + if runMode == "release" { + orm.loggerConfig.LogLevel = logger.Silent + } + + return orm +} + +func (t *SimpleORM) getDSN() string { + if t.charset == "" { + t.charset = "utf8mb4" + } + return fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=%s&parseTime=True&loc=Local", + t.user, + t.password, + t.host, + t.name, + t.charset, + ) +} + +func (t *SimpleORM) SetLoggerConfig(l logger.Config) { + t.loggerConfig = l +} + +func (t *SimpleORM) SetLoggerLevel(level string) { + if strings.ToLower(level) == "silent" { + t.loggerConfig.LogLevel = logger.Silent + } else if strings.ToLower(level) == "error" { + t.loggerConfig.LogLevel = logger.Error + } else if strings.ToLower(level) == "warn" { + t.loggerConfig.LogLevel = logger.Warn + } else if strings.ToLower(level) == "info" { + t.loggerConfig.LogLevel = logger.Info + } +} + +func (t *SimpleORM) ConnectMysql() *gorm.DB { + Db, err := gorm.Open( + mysql.Open(t.getDSN()), &gorm.Config{ + Logger: logger.New( + log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer + t.loggerConfig, + ), + }, + ) + if err != nil { + panic("failed to connect database") + } + return Db +} diff --git a/pkg/myHttp/header.go b/pkg/myHttp/header.go new file mode 100644 index 0000000..191f9c3 --- /dev/null +++ b/pkg/myHttp/header.go @@ -0,0 +1,32 @@ +package myHttp + +func (t *Client) HasHeader(key string) bool { + for k, _ := range t.headers { + if key == k { + return true + } + } + return false +} + +func (t *Client) SetHeaders(header map[string]string) *Client { + t.headers = header + return t +} + +func (t *Client) AddHeader(key, value string) *Client { + t.headers[key] = value + return t +} + +func (t *Client) AddHeaders(header map[string]string) *Client { + for k, v := range header { + t.headers[k] = v + } + return t +} + +func (t *Client) DelHeader(key string) *Client { + delete(t.headers, key) + return t +} diff --git a/pkg/myHttp/index.go b/pkg/myHttp/index.go new file mode 100644 index 0000000..97af464 --- /dev/null +++ b/pkg/myHttp/index.go @@ -0,0 +1,22 @@ +package myHttp + +import ( + "code.zhecent.com/gopkg/light-core/pkg/myUrl" +) + +type Client struct { + url *myUrl.UrlCli + headers map[string]string +} + +func NewClient(url string) (*Client, error) { + urlObj, err := myUrl.NewUrlCliWithParse(url) + if err != nil { + return nil, err + } + return &Client{url: urlObj}, nil +} + +func (t *Client) GetMyUrl() *myUrl.UrlCli { + return t.url +} diff --git a/pkg/myHttp/json.go b/pkg/myHttp/json.go new file mode 100644 index 0000000..3b96c13 --- /dev/null +++ b/pkg/myHttp/json.go @@ -0,0 +1,57 @@ +package myHttp + +import ( + "bytes" + "encoding/json" + "errors" + "net/http" +) + +func (t *Client) PostJson(request interface{}, response interface{}) error { + var buf bytes.Buffer + encoder := json.NewEncoder(&buf) + encoder.SetEscapeHTML(false) + if err := encoder.Encode(request); err != nil { + return err + } + + // HTTP请求 + req, err := http.NewRequest("POST", t.url.String(), &buf) + if err != nil { + return err + } + + //添加请求头 + req.Header.Set("Content-Type", "application/json; charset=utf-8") + if len(t.headers) > 0 { + for key, value := range t.headers { + req.Header.Set(key, value) + } + } + + // 发送请求 + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return errors.New("http.Status:" + resp.Status) + } + return json.NewDecoder(resp.Body).Decode(response) +} + +func (t *Client) GetJson(response interface{}) error { + httpResp, err := http.Get(t.url.String()) + if err != nil { + return err + } + defer httpResp.Body.Close() + + if httpResp.StatusCode != http.StatusOK { + return errors.New("http.Status:" + httpResp.Status) + } + return json.NewDecoder(httpResp.Body).Decode(response) +} diff --git a/pkg/myJwt/jwt.go b/pkg/myJwt/jwt.go new file mode 100644 index 0000000..801eeec --- /dev/null +++ b/pkg/myJwt/jwt.go @@ -0,0 +1,115 @@ +package myJwt + +import ( + "errors" + "github.com/golang-jwt/jwt/v5" + "time" +) + +type Claims struct { + UserId int `json:"uid"` + jwt.RegisteredClaims +} + +func (t Claims) toSimpleJwt() *SimpleJwt { + return &SimpleJwt{ + Audience: t.Audience, + ExpiresAt: t.ExpiresAt, + Id: t.ID, + IssuedAt: t.IssuedAt, + Issuer: t.Issuer, + NotBefore: t.NotBefore, + Subject: t.Subject, + userId: t.UserId, + jwtSecret: "", + } + +} + +type SimpleJwt struct { + Audience jwt.ClaimStrings + ExpiresAt *jwt.NumericDate + Id string + IssuedAt *jwt.NumericDate + Issuer string + NotBefore *jwt.NumericDate + Subject string + + userId int + jwtSecret string +} + +func NewSimpleJwt(userId int, jwtSecret string) *SimpleJwt { + return &SimpleJwt{ + userId: userId, + jwtSecret: jwtSecret, + + ExpiresAt: jwt.NewNumericDate(time.Now().Add(90 * 24 * time.Hour)), + Issuer: "zhecent", + } +} + +func (t *SimpleJwt) SetExpiresAt(expiresAt time.Time) { + t.ExpiresAt = jwt.NewNumericDate(expiresAt) +} + +func (t *SimpleJwt) makeClaims() Claims { + return Claims{ + UserId: t.userId, + RegisteredClaims: jwt.RegisteredClaims{ + ExpiresAt: t.ExpiresAt, + Issuer: t.Issuer, + }, + } +} + +func (t *SimpleJwt) GetUserId() int { + return t.userId +} + +func (t *SimpleJwt) GenerateToken() (string, error) { + tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, t.makeClaims()) + token, err := tokenClaims.SignedString([]byte(t.jwtSecret)) + return token, err +} + +func (t *SimpleJwt) MustGenerateToken() string { + s, e := t.GenerateToken() + if e != nil { + panic(e.Error()) + } + return s +} + +func ParseToken(token string, jwtSecret string) (*SimpleJwt, error) { + tokenClaims, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) { + return []byte(jwtSecret), nil + }) + + if tokenClaims != nil { + if claims, ok := tokenClaims.Claims.(*Claims); ok && tokenClaims.Valid { + return claims.toSimpleJwt(), nil + } + } + + switch { + case errors.Is(err, jwt.ErrTokenMalformed): + return nil, errors.New("that's not even a token") + case errors.Is(err, jwt.ErrTokenSignatureInvalid): + // Invalid signature + return nil, errors.New("invalid signature") + case errors.Is(err, jwt.ErrTokenExpired) || errors.Is(err, jwt.ErrTokenNotValidYet): + // Token is either expired or not active yet + return nil, errors.New("timing is everything") + default: + return nil, errors.New("token校验失败") + } +} + +func MustParseToken(token string, jwtSecret string) *SimpleJwt { + s, e := ParseToken(token, jwtSecret) + if e != nil { + panic(e.Error()) + } + return s +} diff --git a/pkg/myOss/index.go b/pkg/myOss/index.go new file mode 100644 index 0000000..08a894f --- /dev/null +++ b/pkg/myOss/index.go @@ -0,0 +1,138 @@ +package myOss + +import ( + "fmt" + "github.com/aliyun/aliyun-oss-go-sdk/oss" + "io" + "net/http" + "strings" +) + +type Model struct { + Path string + Bucket string + ACLType string + Endpoint string + AccessKeyID string + SecretAccessKey string + StorageClassType string + CdnUrl string + ossClient *oss.Client + ossBucket *oss.Bucket +} + +func (t *Model) MustGetOssClient() *oss.Client { + if t.ossClient == nil { + client, err := oss.New(t.Endpoint, t.AccessKeyID, t.SecretAccessKey, oss.Timeout(10, 120)) + if err != nil { + panic("function oss.New() Filed, err:" + err.Error()) + } + t.ossClient = client + } + return t.ossClient +} + +func (t *Model) MustGetDefaultOssBucket() *oss.Bucket { + if t.ossBucket == nil { + client := t.MustGetOssClient() + bucket, err := client.Bucket(t.Bucket) + + if err != nil { + panic("function client.Bucket() Filed, err:" + err.Error()) + } + t.ossBucket = bucket + } + return t.ossBucket +} + +func (t *Model) PutObject(savePath, fileName string, reader io.Reader, contentType string) (*RespPath, error) { + err := t.MustGetDefaultOssBucket().PutObject( + fmt.Sprintf("%s/%s", savePath, fileName), + reader, + oss.ContentType(contentType), + t.GetObjectStorageClass(), + t.GetObjectAcl(), + ) + return &RespPath{ + Path: savePath, + Name: fileName, + Host: t.CdnUrl, + }, err + +} + +func (t *Model) MustPutObject(savePath, fileName string, reader io.Reader, contentType string) *RespPath { + p, err := t.PutObject(savePath, fileName, reader, contentType) + if err != nil { + panic(err.Error()) + } + return p +} + +func (t *Model) GetObjectMeta(key string) (http.Header, error) { + if strings.HasPrefix(key, "/") { + key = strings.TrimLeft(key, "/") + } + return t.MustGetDefaultOssBucket().GetObjectMeta(key) +} + +func (t *Model) DeleteObject(key string) error { + // 删除单个文件。objectName表示删除OSS文件时需要指定包含文件后缀在内的完整路径,例如abc/efg/123.jpg。 + // 如需删除文件夹,请将objectName设置为对应的文件夹名称。如果文件夹非空,则需要将文件夹下的所有object删除后才能删除该文件夹。 + //这里需要处理一下路径,如果第一个是/,要去掉 + + if strings.HasPrefix(key, "/") { + key = strings.TrimLeft(key, "/") + } + if t.IsFileExist(key) { + return t.MustGetDefaultOssBucket().DeleteObject(key) + } + return fmt.Errorf("文件不存在") +} + +func (t *Model) IsFileExist(key string) bool { + if strings.HasPrefix(key, "/") { + key = strings.TrimLeft(key, "/") + } + exist, err := t.MustGetDefaultOssBucket().IsObjectExist(key) + if err != nil { + panic(err.Error()) + } + return exist +} + +func (t *Model) GetObjectStorageClass() oss.Option { + switch t.StorageClassType { // 根据配置文件进行指定存储类型 + case "Standard": // 指定存储类型为标准存储 + return oss.ObjectStorageClass(oss.StorageStandard) + case "IA": // 指定存储类型为很少访问存储 + return oss.ObjectStorageClass(oss.StorageIA) + case "Archive": // 指定存储类型为归档存储。 + return oss.ObjectStorageClass(oss.StorageArchive) + case "ColdArchive": // 指定存储类型为归档存储。 + return oss.ObjectStorageClass(oss.StorageColdArchive) + default: // 无匹配结果就是标准存储 + return oss.ObjectStorageClass(oss.StorageStandard) + } +} + +func (t *Model) GetObjectAcl() oss.Option { + switch t.ACLType { // 根据配置文件进行指定访问权限 + case "private": // 指定访问权限为私有读写 + return oss.ObjectACL(oss.ACLPrivate) // 指定访问权限为公共读 + case "public-read": + return oss.ObjectACL(oss.ACLPublicRead) // 指定访问权限为公共读 + case "public-read-write": + return oss.ObjectACL(oss.ACLPublicReadWrite) // 指定访问权限为公共读写 + case "default": + return oss.ObjectACL(oss.ACLDefault) // 指定访问权限为公共读 + default: + return oss.ObjectACL(oss.ACLPrivate) // 默认为访问权限为公共读 + } +} + +type RespPath struct { + Path string + Name string + Host string +} diff --git a/pkg/myPay/alipay.go b/pkg/myPay/alipay.go new file mode 100644 index 0000000..be0f9db --- /dev/null +++ b/pkg/myPay/alipay.go @@ -0,0 +1,145 @@ +package myPay + +import ( + "context" + "errors" + "github.com/go-pay/gopay" + "github.com/go-pay/gopay/alipay" + "github.com/shopspring/decimal" + "net/http" +) + +type AliPay struct { + ctx context.Context + cli *alipay.Client + params *AliPayImpl + orderNo string + amount int + description string + notifyUrl string + returnUrl string +} + +func NewAliPay(params *AliPayImpl) *AliPay { + client, err := alipay.NewClient(params.AppId, params.PrivateKey, params.IsProd) + if err != nil { + panic(err.Error()) + } + + // 打开Debug开关,输出日志,默认是关闭的 + if params.IsProd { + client.DebugSwitch = gopay.DebugOff + } else { + client.DebugSwitch = gopay.DebugOn + } + + client.SetLocation(alipay.LocationShanghai). + SetCharset(alipay.UTF8). + SetSignType(alipay.RSA2). + AutoVerifySign([]byte(params.PublicKey)) + + if err := client.SetCertSnByContent([]byte(params.AppCertContent), []byte(params.AliPayRootCertContent), []byte(params.PublicKey)); err != nil { + panic(err.Error()) + } + + return &AliPay{params: params, cli: client, ctx: context.Background()} +} + +func (t *AliPay) GetCli() *alipay.Client { + return t.cli +} + +func (t *AliPay) SetCommonConfig(notifyUrl string, returnUrl string) *AliPay { + t.notifyUrl = notifyUrl + t.returnUrl = returnUrl + return t +} + +func (t *AliPay) SetOrderInfo(orderNo string, amount int, description string) *AliPay { + t.orderNo = orderNo + t.amount = amount + t.description = description + return t +} + +func (t *AliPay) SetCertSnByContent(appCertContent, aliPayRootCertContent []byte) error { + return t.cli.SetCertSnByContent(appCertContent, aliPayRootCertContent, []byte(t.params.PublicKey)) +} + +func (t *AliPay) SetCertSnByPath(appCertPath, aliPayRootCertPath, aliPayPublicCertPath string) error { + return t.cli.SetCertSnByPath(appCertPath, aliPayRootCertPath, aliPayPublicCertPath) +} + +func (t *AliPay) setBodyMap() gopay.BodyMap { + if t.description == "" || t.orderNo == "" || t.notifyUrl == "" { + panic("param is empty") + } + if t.amount == 0 { + panic("amount is zero") + } + + // 配置公共参数 + t.cli.SetNotifyUrl(t.notifyUrl). + SetReturnUrl(t.returnUrl) + + bm := make(gopay.BodyMap) + bm.Set("subject", t.description) + bm.Set("out_trade_no", t.orderNo) + bm.Set("total_amount", t.fen2Yuan(uint64(t.amount))) + return bm +} + +func (t *AliPay) GetWapPay(quitUrl string) (string, error) { + bm := t.setBodyMap() + bm.Set("quit_url", quitUrl) + bm.Set("product_code", "QUICK_WAP_WAY") + return t.cli.TradeWapPay(t.ctx, bm) +} + +func (t *AliPay) GetPagePay() (string, error) { + bm := t.setBodyMap() + bm.Set("product_code", "FAST_INSTANT_TRADE_PAY") + return t.cli.TradePagePay(t.ctx, bm) +} + +func (t *AliPay) GetAppPay() (string, error) { + bm := t.setBodyMap() + bm.Set("product_code", "QUICK_MSECURITY_PAY") + return t.cli.TradeAppPay(t.ctx, bm) +} + +func (t *AliPay) Notify(req *http.Request) (*AlipayNotifyResp, error) { + notifyReq, err := alipay.ParseNotifyToBodyMap(req) + if err != nil { + return nil, errors.New("解析回调失败") + } + + if ok, err := alipay.VerifySign(t.params.PublicKey, notifyReq); err != nil || ok == false { + return nil, errors.New("sign Error") + } + + return &AlipayNotifyResp{resp: notifyReq}, nil +} + +func (t *AliPay) GetOrderNo() string { + return t.orderNo +} + +func (t *AliPay) GetAmount() int { + return t.amount +} + +type AliPayImpl struct { + AppId string + PublicKey string + PrivateKey string + IsProd bool + AppCertContent string + AliPayRootCertContent string +} + +func (t *AliPay) fen2Yuan(price uint64) string { + d := decimal.New(1, 2) + result := decimal.NewFromInt(int64(price)).DivRound(d, 2).String() + return result +} diff --git a/pkg/myPay/alipay_notify.go b/pkg/myPay/alipay_notify.go new file mode 100644 index 0000000..7cb1da5 --- /dev/null +++ b/pkg/myPay/alipay_notify.go @@ -0,0 +1,28 @@ +package myPay + +import "github.com/go-pay/gopay" + +type AlipayNotifyResp struct { + resp gopay.BodyMap +} +type AlipayNotifyRespInfo struct { + TradeStatus string + OutTradeNo string + SellerId string + TradeNo string + GmtPayment string +} + +func (t *AlipayNotifyResp) IsSuccess() bool { + return t.resp.Get("trade_status") == "TRADE_SUCCESS" +} + +func (t *AlipayNotifyResp) GetResult() *AlipayNotifyRespInfo { + return &AlipayNotifyRespInfo{ + TradeStatus: t.resp.Get("trade_status"), + OutTradeNo: t.resp.Get("out_trade_no"), + SellerId: t.resp.Get("seller_id"), + TradeNo: t.resp.Get("trade_no"), + GmtPayment: t.resp.Get("gmt_payment"), + } +} diff --git a/pkg/myPay/wechat.go b/pkg/myPay/wechat.go new file mode 100644 index 0000000..8156fe8 --- /dev/null +++ b/pkg/myPay/wechat.go @@ -0,0 +1,261 @@ +package myPay + +import ( + "context" + "errors" + "github.com/go-pay/gopay" + "github.com/go-pay/gopay/wechat/v3" + "net/http" + "time" +) + +type Wechat struct { + ctx context.Context + cli *wechat.ClientV3 + params *WechatPayV3Impl + orderNo string + amount int + description string + notifyUrl string + profitSharing bool +} + +func NewWechat(params *WechatPayV3Impl) *Wechat { + // NewClientV3 初始化微信客户端 v3 + // mchid:商户ID 或者服务商模式的 sp_mchid + // serialNo:商户证书的证书序列号 + // apiV3Key:apiV3Key,商户平台获取 + // privateKey:私钥 apiclient_key.pem 读取后的内容 + client, err := wechat.NewClientV3(params.MchId, params.SerialNo, params.ApiV3Key, params.PKContent) + if err != nil { + panic(err.Error()) + } + + // 设置微信平台API证书和序列号(推荐开启自动验签,无需手动设置证书公钥等信息) + //client.SetPlatformCert([]byte(""), "") + + // 启用自动同步返回验签,并定时更新微信平台API证书(开启自动验签时,无需单独设置微信平台API证书和序列号) + client.SetPlatformCert([]byte(params.WxPkContent), params.WxPkSerialNo) + // 启用自动同步返回验签,并定时更新微信平台API证书 + //err = client.AutoVerifySign() + //if err != nil { + // return nil, err + //} + + // 打开Debug开关,输出日志,默认是关闭的 + if params.IsProd { + client.DebugSwitch = gopay.DebugOff + } else { + client.DebugSwitch = gopay.DebugOn + } + + return &Wechat{params: params, cli: client, ctx: context.Background()} +} + +func (t *Wechat) GetCli() *wechat.ClientV3 { + return t.cli +} + +func (t *Wechat) SetCommonConfig(notifyUrl string) *Wechat { + t.notifyUrl = notifyUrl + return t +} + +func (t *Wechat) SetOrderInfo(orderNo string, amount int, description string) *Wechat { + t.orderNo = orderNo + t.amount = amount + t.description = description + return t +} + +func (t *Wechat) setBodyMap() gopay.BodyMap { + if t.description == "" || t.orderNo == "" || t.notifyUrl == "" { + panic("param is empty") + } + if t.amount == 0 { + panic("amount is zero") + } + + bm := make(gopay.BodyMap) + bm.Set("description", t.description). + Set("out_trade_no", t.orderNo). + Set("time_expire", time.Now().Add(10*time.Minute).Format(time.RFC3339)). + Set("notify_url", t.notifyUrl). + SetBodyMap("amount", func(bm gopay.BodyMap) { + bm.Set("total", t.amount).Set("currency", "CNY") + }) + if t.profitSharing { + bm.SetBodyMap("settle_info", func(bm gopay.BodyMap) { + bm.Set("profit_sharing", true) + }) + } + + return bm +} + +func (t *Wechat) SetProfitSharing(b bool) *Wechat { + t.profitSharing = b + return t +} + +func (t *Wechat) GetApp() (*wechat.AppPayParams, error) { + wxRsp, err := t.cli.V3TransactionApp(t.ctx, t.setBodyMap().Set("appid", t.params.AppAppid)) + if err != nil { + return nil, err + } + + if wxRsp.Code == wechat.Success { + //校验签名 + if err2 := wechat.V3VerifySignByPK(wxRsp.SignInfo.HeaderTimestamp, wxRsp.SignInfo.HeaderNonce, wxRsp.SignInfo.SignBody, wxRsp.SignInfo.HeaderSignature, t.cli.WxPublicKey()); err != nil { + return nil, err2 + } + + //获取调起参数 + return t.cli.PaySignOfApp(t.params.AppAppid, wxRsp.Response.PrepayId) + } else { + return nil, errors.New(wxRsp.Error) + } +} + +func (t *Wechat) GetJsapi(openId string) (*wechat.JSAPIPayParams, error) { + wxRsp, err := t.cli.V3TransactionJsapi(t.ctx, + t.setBodyMap().Set("appid", t.params.MpAppid).SetBodyMap("payer", func(bm gopay.BodyMap) { + bm.Set("openid", openId) + }), + ) + if err != nil { + return nil, err + } + if wxRsp.Code == wechat.Success { + //校验签名 + if err2 := wechat.V3VerifySignByPK(wxRsp.SignInfo.HeaderTimestamp, wxRsp.SignInfo.HeaderNonce, wxRsp.SignInfo.SignBody, wxRsp.SignInfo.HeaderSignature, t.cli.WxPublicKey()); err != nil { + return nil, err2 + } + + //获取调起参数 + return t.cli.PaySignOfJSAPI(t.params.MpAppid, wxRsp.Response.PrepayId) + } else { + return nil, errors.New(wxRsp.Error) + } +} + +func (t *Wechat) GetJsapiForMini(openId string) (*wechat.JSAPIPayParams, error) { + wxRsp, err := t.cli.V3TransactionJsapi(t.ctx, + t.setBodyMap().Set("appid", t.params.MiniAppid).SetBodyMap("payer", func(bm gopay.BodyMap) { + bm.Set("openid", openId) + }), + ) + if err != nil { + return nil, err + } + + if wxRsp.Code == wechat.Success { + //校验签名 + if err2 := wechat.V3VerifySignByPK(wxRsp.SignInfo.HeaderTimestamp, wxRsp.SignInfo.HeaderNonce, wxRsp.SignInfo.SignBody, wxRsp.SignInfo.HeaderSignature, t.cli.WxPublicKey()); err != nil { + return nil, err2 + } + + //获取调起参数 + return t.cli.PaySignOfJSAPI(t.params.MiniAppid, wxRsp.Response.PrepayId) + } else { + return nil, errors.New(wxRsp.Error) + } +} + +func (t *Wechat) GetNative() (*wechat.Native, error) { + wxRsp, err := t.cli.V3TransactionNative(t.ctx, t.setBodyMap().Set("appid", t.params.MpAppid)) + if err != nil { + return nil, err + } + + if wxRsp.Code == wechat.Success { + //校验签名 + if err2 := wechat.V3VerifySignByPK(wxRsp.SignInfo.HeaderTimestamp, wxRsp.SignInfo.HeaderNonce, wxRsp.SignInfo.SignBody, wxRsp.SignInfo.HeaderSignature, t.cli.WxPublicKey()); err != nil { + return nil, err2 + } + + //获取调起参数 + return wxRsp.Response, err + } else { + return nil, errors.New(wxRsp.Error) + } +} + +func (t *Wechat) GetH5(ip string, appName string, appUrl string) (*wechat.H5Url, error) { + wxRsp, err := t.cli.V3TransactionH5(t.ctx, t.setBodyMap().Set("appid", t.params.MpAppid).SetBodyMap("scene_info", func(bm gopay.BodyMap) { + bm.Set("payer_client_ip", ip) + bm.SetBodyMap("h5_info", func(bm gopay.BodyMap) { + bm.Set("type", "Wap") + bm.Set("app_url", appUrl) + bm.Set("app_name", appName) + }) + })) + + if err != nil { + return nil, err + } + + if wxRsp.Code == wechat.Success { + //校验签名 + if err2 := wechat.V3VerifySignByPK(wxRsp.SignInfo.HeaderTimestamp, wxRsp.SignInfo.HeaderNonce, wxRsp.SignInfo.SignBody, wxRsp.SignInfo.HeaderSignature, t.cli.WxPublicKey()); err != nil { + return nil, err2 + } + + //获取调起参数 + return wxRsp.Response, nil + } else { + return nil, errors.New(wxRsp.Error) + } +} + +func (t *Wechat) Notify(req *http.Request) (*WechatNotifyResp, error) { + notifyReq, err := wechat.V3ParseNotify(req) + if err != nil { + return nil, errors.New("解析回调失败") + } + + err = notifyReq.VerifySignByPK(t.cli.WxPublicKey()) + if err != nil { + return nil, errors.New("sign Error") + } + + if notifyReq.EventType == "TRANSACTION.SUCCESS" { + result, err := notifyReq.DecryptPayCipherText(t.params.ApiV3Key) + if err != nil { + return nil, errors.New("解密错误") + } else { + return &WechatNotifyResp{resp: result}, nil + } + } + + return nil, errors.New(notifyReq.EventType) +} + +func (t *Wechat) GetOrderNo() string { + return t.orderNo +} + +func (t *Wechat) GetAmount() int { + return t.amount +} + +func NotifySuccess(msg string) (int, *wechat.V3NotifyRsp) { + return http.StatusOK, &wechat.V3NotifyRsp{Code: gopay.SUCCESS, Message: msg} +} + +func NotifyFail(msg string) (int, *wechat.V3NotifyRsp) { + return http.StatusBadRequest, &wechat.V3NotifyRsp{Code: gopay.FAIL, Message: msg} +} + +type WechatPayV3Impl struct { + MpAppid string + AppAppid string + MiniAppid string + MchId string + ApiV3Key string + SerialNo string + PKContent string + WxPkSerialNo string + WxPkContent string + IsProd bool +} diff --git a/pkg/myPay/wechat_notify.go b/pkg/myPay/wechat_notify.go new file mode 100644 index 0000000..3e288bd --- /dev/null +++ b/pkg/myPay/wechat_notify.go @@ -0,0 +1,15 @@ +package myPay + +import "github.com/go-pay/gopay/wechat/v3" + +type WechatNotifyResp struct { + resp *wechat.V3DecryptPayResult +} + +func (t *WechatNotifyResp) IsSuccess() bool { + return t.resp.TradeState == "SUCCESS" +} + +func (t *WechatNotifyResp) GetResult() *wechat.V3DecryptPayResult { + return t.resp +} diff --git a/pkg/myRedis/Ierator.go b/pkg/myRedis/Ierator.go new file mode 100644 index 0000000..c069263 --- /dev/null +++ b/pkg/myRedis/Ierator.go @@ -0,0 +1,23 @@ +package myRedis + +type Iterator struct { + data []interface{} + index int +} + +func NewIterator(data []interface{}) *Iterator { + return &Iterator{data: data} +} + +func (t *Iterator) HasNext() bool { + if t.data == nil || len(t.data) == 0 { + return false + } + return t.index < len(t.data) +} + +func (t *Iterator) Next() (ret interface{}) { + ret = t.data[t.index] + t.index = t.index + 1 + return +} diff --git a/pkg/myRedis/IntResult.go b/pkg/myRedis/IntResult.go new file mode 100644 index 0000000..6d5ed83 --- /dev/null +++ b/pkg/myRedis/IntResult.go @@ -0,0 +1,33 @@ +package myRedis + +type IntResult struct { + Result int64 + Err error +} + +func NewIntResult(result int64, err error) *IntResult { + return &IntResult{Result: result, Err: err} +} + +func (t *IntResult) Unwrap() int64 { + if t.Err != nil { + panic(t.Err) + } + + return t.Result +} + +func (t *IntResult) UnwrapOr(str int64) int64 { + if t.Err != nil { + return str + } else { + return t.Result + } +} + +func (t *IntResult) UnwrapOrElse(f func() int64) int64 { + if t.Err != nil { + return f() + } + return t.Result +} diff --git a/pkg/myRedis/InterfaceResult.go b/pkg/myRedis/InterfaceResult.go new file mode 100644 index 0000000..4a2aef0 --- /dev/null +++ b/pkg/myRedis/InterfaceResult.go @@ -0,0 +1,25 @@ +package myRedis + +type InterfaceResult struct { + Result interface{} + Err error +} + +func NewInterfaceResult(result interface{}, err error) *InterfaceResult { + return &InterfaceResult{Result: result, Err: err} +} + +func (t *InterfaceResult) Unwrap() interface{} { + if t.Err != nil { + panic(t.Err) + } + + return t.Result +} + +func (t *InterfaceResult) UnwrapOr(a interface{}) interface{} { + if t.Err != nil { + return a + } + return t.Result +} diff --git a/pkg/myRedis/OperationAttr.go b/pkg/myRedis/OperationAttr.go new file mode 100644 index 0000000..4e2e064 --- /dev/null +++ b/pkg/myRedis/OperationAttr.go @@ -0,0 +1,52 @@ +package myRedis + +import ( + "fmt" + "time" +) + +const ( + AttrExpr = "expr" + AttrNx = "nx" + AttrXx = "xx" +) + +type empty struct { +} + +type OperationAttr struct { + Name string + Value interface{} +} + +type OperationAttrs []*OperationAttr + +func (t OperationAttrs) Find(name string) *InterfaceResult { + for _, attr := range t { + if attr.Name == name { + return NewInterfaceResult(attr.Value, nil) + } + } + return NewInterfaceResult(nil, fmt.Errorf("OperationAttrs found error:%s", name)) +} + +func WithExpire(t time.Duration) *OperationAttr { + return &OperationAttr{ + Name: AttrExpr, + Value: t, + } +} + +func WithNX() *OperationAttr { + return &OperationAttr{ + Name: AttrNx, + Value: empty{}, + } +} + +func WithXX() *OperationAttr { + return &OperationAttr{ + Name: AttrXx, + Value: empty{}, + } +} diff --git a/pkg/myRedis/SimpleCache.go b/pkg/myRedis/SimpleCache.go new file mode 100644 index 0000000..9b49aac --- /dev/null +++ b/pkg/myRedis/SimpleCache.go @@ -0,0 +1,132 @@ +package myRedis + +import ( + "bytes" + "encoding/gob" + "encoding/json" + "time" +) + +const ( + SerializerNot = "" + SerializerJson = "json" + SerializerGob = "gob" +) + +type CacheGetterFunc func() interface{} + +type SimpleCache struct { + Operation *StringOperation + Expire time.Duration + CacheGetter CacheGetterFunc + Serializer string //序列化方式 +} + +func NewSimpleCache(operation *StringOperation, expire time.Duration, serializer string) *SimpleCache { + return &SimpleCache{Operation: operation, Expire: expire, Serializer: serializer} +} + +func (t *SimpleCache) SetCacheGetterFunc(f CacheGetterFunc) *SimpleCache { + t.CacheGetter = f + return t +} + +// 设置缓存 +func (t *SimpleCache) SetCache(key string, value interface{}) { + //if t.Serializer == SerializerNot { + //} + if t.Serializer == SerializerJson { + f := func() string { + j, e := json.Marshal(value) + if e != nil { + return e.Error() + } else { + return string(j) + } + } + t.Operation.Set(key, f(), WithExpire(t.Expire)).Unwrap() + } else if t.Serializer == SerializerGob { + f := func() string { + var buf = &bytes.Buffer{} + enc := gob.NewEncoder(buf) + if err := enc.Encode(value); err != nil { + return "" + } + return buf.String() + } + t.Operation.Set(key, f(), WithExpire(t.Expire)).Unwrap() + } else { + t.Operation.Set(key, value, WithExpire(t.Expire)).Unwrap() + } +} + +func (t *SimpleCache) GetCache(key string) (ret interface{}) { + //如果没有设置的话 + if t.CacheGetter == nil { + panic("没有设置CacheGetter") + } + + if t.Serializer == SerializerNot { + } + if t.Serializer == SerializerJson { + f := func() string { + j, e := json.Marshal(t.CacheGetter()) + if e != nil { + return e.Error() + } else { + return string(j) + } + } + ret = t.Operation.Get(key).UnwrapOrElse(func() string { + data := f() + t.Operation.Set(key, data, WithExpire(t.Expire)).Unwrap() + return data + }) + } + + if t.Serializer == SerializerGob { + f := func() string { + var buf = &bytes.Buffer{} + enc := gob.NewEncoder(buf) + if err := enc.Encode(t.CacheGetter()); err != nil { + return "" + } + return buf.String() + } + ret = t.Operation.Get(key).UnwrapOrElse(func() string { + data := f() + t.Operation.Set(key, data, WithExpire(t.Expire)).Unwrap() + return data + }) + } + + return +} + +func (t *SimpleCache) DelCache(key string) int64 { + return t.Operation.Del(key).UnwrapOr(0) +} + +func (t *SimpleCache) GetCacheForObject(key string, obj interface{}) interface{} { + ret := t.GetCache(key) + if ret == nil { + return nil + } + if t.Serializer == SerializerNot { + obj = ret + } else if t.Serializer == SerializerJson { + err := json.Unmarshal([]byte(ret.(string)), obj) + if err != nil { + return nil + } + } else if t.Serializer == SerializerGob { + + var buf = &bytes.Buffer{} + buf.WriteString(ret.(string)) + dec := gob.NewDecoder(buf) + if dec.Decode(obj) != nil { + return nil + } + } + return nil +} diff --git a/pkg/myRedis/SliceResult.go b/pkg/myRedis/SliceResult.go new file mode 100644 index 0000000..03faee9 --- /dev/null +++ b/pkg/myRedis/SliceResult.go @@ -0,0 +1,29 @@ +package myRedis + +type SliceResult struct { + Result []interface{} + Err error +} + +func NewSliceResult(result []interface{}, err error) *SliceResult { + return &SliceResult{Result: result, Err: err} +} + +func (t *SliceResult) Unwrap() []interface{} { + if t.Err != nil { + panic(t.Err) + } + return t.Result +} + +func (t *SliceResult) UnwrapOr(strs []interface{}) []interface{} { + if t.Err != nil { + return strs + } else { + return t.Result + } +} + +func (t *SliceResult) Iter() *Iterator { + return NewIterator(t.Result) +} diff --git a/pkg/myRedis/StringCache.go b/pkg/myRedis/StringCache.go new file mode 100644 index 0000000..b3f0eeb --- /dev/null +++ b/pkg/myRedis/StringCache.go @@ -0,0 +1,52 @@ +package myRedis + +import ( + "github.com/redis/go-redis/v9" + "time" +) + +type StringCache struct { + Operation *StringOperation + Expire time.Duration + DefaultString string +} + +func NewStringCache(redisClient *redis.Client) *StringCache { + return &StringCache{ + Operation: NewStringOperation(redisClient), + Expire: time.Second * 0, + DefaultString: "", + } +} + +func (t *StringCache) SetExpire(expire time.Duration) *StringCache { + t.Expire = expire + return t +} + +func (t *StringCache) SetDefaultString(defaultString string) *StringCache { + t.DefaultString = defaultString + return t +} + +func (t *StringCache) SetCache(key string, value string) { + t.Operation.Set(key, value, WithExpire(t.Expire)) +} + +func (t *StringCache) GetCache(key string) (ret string) { + ret = t.Operation.Get(key).UnwrapOrElse(func() string { + if t.DefaultString != "" { + t.SetCache(key, t.DefaultString) + } + return t.DefaultString + }) + return +} + +func (t *StringCache) IsExist(key string) bool { + return t.Operation.Exist(key).UnwrapOr(0) != 0 +} + +func (t *StringCache) DelCache(key string) int64 { + return t.Operation.Del(key).UnwrapOr(0) +} diff --git a/pkg/myRedis/StringOperation.go b/pkg/myRedis/StringOperation.go new file mode 100644 index 0000000..09e5434 --- /dev/null +++ b/pkg/myRedis/StringOperation.go @@ -0,0 +1,48 @@ +package myRedis + +import ( + "context" + "github.com/redis/go-redis/v9" + "time" +) + +type StringOperation struct { + ctx context.Context + client *redis.Client +} + +func NewStringOperation(client *redis.Client) *StringOperation { + return &StringOperation{ctx: context.Background(), client: client} +} + +func (t *StringOperation) Set(key string, value interface{}, attrs ...*OperationAttr) *InterfaceResult { + exp := OperationAttrs(attrs).Find(AttrExpr).UnwrapOr(0 * time.Second).(time.Duration) + + nx := OperationAttrs(attrs).Find(AttrNx).UnwrapOr(nil) + if nx != nil { + return NewInterfaceResult(t.client.SetNX(t.ctx, key, value, exp).Result()) + } + + xx := OperationAttrs(attrs).Find(AttrXx).UnwrapOr(nil) + if xx != nil { + return NewInterfaceResult(t.client.SetXX(t.ctx, key, value, exp).Result()) + } + + return NewInterfaceResult(t.client.Set(t.ctx, key, value, exp).Result()) +} + +func (t *StringOperation) Get(key string) *StringResult { + return NewStringResult(t.client.Get(t.ctx, key).Result()) +} + +func (t *StringOperation) MGet(key ...string) *SliceResult { + return NewSliceResult(t.client.MGet(t.ctx, key...).Result()) +} + +func (t *StringOperation) Del(key string) *IntResult { + return NewIntResult(t.client.Del(t.ctx, key).Result()) +} + +func (t *StringOperation) Exist(key string) *IntResult { + return NewIntResult(t.client.Exists(t.ctx, key).Result()) +} diff --git a/pkg/myRedis/StringResult.go b/pkg/myRedis/StringResult.go new file mode 100644 index 0000000..643e3da --- /dev/null +++ b/pkg/myRedis/StringResult.go @@ -0,0 +1,33 @@ +package myRedis + +type StringResult struct { + Result string + Err error +} + +func NewStringResult(result string, err error) *StringResult { + return &StringResult{Result: result, Err: err} +} + +func (t *StringResult) Unwrap() string { + if t.Err != nil { + panic(t.Err) + } + + return t.Result +} + +func (t *StringResult) UnwrapOr(str string) string { + if t.Err != nil { + return str + } else { + return t.Result + } +} + +func (t *StringResult) UnwrapOrElse(f func() string) string { + if t.Err != nil { + return f() + } + return t.Result +} diff --git a/pkg/myRedis/cli.go b/pkg/myRedis/cli.go new file mode 100644 index 0000000..a364d4b --- /dev/null +++ b/pkg/myRedis/cli.go @@ -0,0 +1,33 @@ +package myRedis + +import ( + "github.com/redis/go-redis/v9" + "time" +) + +type Client struct { + client *redis.Client +} + +func NewClient(client *redis.Client) *Client { + if client == nil { + panic("redis client is nil") + } + return &Client{client: client} +} + +func (t *Client) GetClient() *redis.Client { + return t.client +} + +func (t *Client) NewStringCache() *StringCache { + return NewStringCache(t.client) +} + +func (t *Client) NewJsonCache(expire time.Duration) *SimpleCache { + return NewSimpleCache(NewStringOperation(t.client), expire, SerializerJson) +} + +func (t *Client) NewGobCache(expire time.Duration) *SimpleCache { + return NewSimpleCache(NewStringOperation(t.client), expire, SerializerGob) +} diff --git a/pkg/myRedis/index.go b/pkg/myRedis/index.go new file mode 100644 index 0000000..1ecd8fa --- /dev/null +++ b/pkg/myRedis/index.go @@ -0,0 +1,60 @@ +package myRedis + +import ( + "context" + "fmt" + "github.com/redis/go-redis/v9" + "log" + "sync" +) + +type SimpleRedis struct { + Host string + Password string + hosts map[int]*Hosts +} + +type Hosts struct { + clientOnce sync.Once + client *redis.Client +} + +func NewSimpleRedis(host string, password string) *SimpleRedis { + return &SimpleRedis{Host: host, Password: password, hosts: map[int]*Hosts{}} +} + +func (t *SimpleRedis) connectRedis(index int) *redis.Client { + if t.hosts[index] == nil { + t.hosts[index] = &Hosts{ + clientOnce: sync.Once{}, + client: nil, + } + } + + t.hosts[index].clientOnce.Do(func() { + redisClient := redis.NewClient(&redis.Options{ + Addr: t.Host, + Password: t.Password, // no password set + DB: index, // use default DB + //连接池容量以闲置链接数量 + PoolSize: 15, + MinIdleConns: 10, + }) + pong, err := redisClient.Ping(context.Background()).Result() + if err != nil { + panic(fmt.Errorf("connect error:%s", err)) + } + log.Println(fmt.Sprintf("redis newClient success, index:%d, pong: %s", index, pong)) + + t.hosts[index].client = redisClient + }) + return t.hosts[index].client +} + +func (t *SimpleRedis) ConnectDefaultRedis() *Client { + return t.GetRedisClient(0) +} + +func (t *SimpleRedis) GetRedisClient(index int) *Client { + return NewClient(t.connectRedis(index)) +} diff --git a/pkg/myUrl/index.go b/pkg/myUrl/index.go new file mode 100644 index 0000000..306e421 --- /dev/null +++ b/pkg/myUrl/index.go @@ -0,0 +1,42 @@ +package myUrl + +import "net/url" + +type UrlCli struct { + url url.URL + params url.Values +} + +func NewUrlCli(scheme string, host string) *UrlCli { + return &UrlCli{url: url.URL{Scheme: scheme, Host: host}, params: url.Values{}} +} + +func NewUrlCliWithParse(data string) (*UrlCli, error) { + urlObj, err := url.Parse(data) + if err != nil { + return nil, err + } + return &UrlCli{ + url: *urlObj, + params: urlObj.Query(), + }, nil +} + +func (t *UrlCli) Set(m map[string]string) { + params := url.Values{} + for s, s2 := range m { + params.Set(s, s2) + } + t.params = params +} + +func (t *UrlCli) Add(key, value string) *UrlCli { + t.params.Set(key, value) + return t +} + +func (t *UrlCli) String() string { + baseURL := t.url + baseURL.RawQuery = t.params.Encode() + return baseURL.String() +} diff --git a/pkg/myViper/viper.go b/pkg/myViper/viper.go new file mode 100644 index 0000000..59d2231 --- /dev/null +++ b/pkg/myViper/viper.go @@ -0,0 +1,31 @@ +package myViper + +import ( + "fmt" + "github.com/spf13/viper" +) + +type SimpleViper struct { + config interface{} + configType string + configName string + configPath string +} + +func NewSimpleViper(config interface{}, configType string, configName string, configPath string) *SimpleViper { + return &SimpleViper{config: config, configType: configType, configName: configName, configPath: configPath} +} + +func (t *SimpleViper) Apply() { + v := viper.New() + v.SetConfigFile(fmt.Sprintf("%s/%s.yaml", t.configPath, t.configName)) + v.SetConfigType(t.configType) + err := v.ReadInConfig() + if err != nil { + panic(fmt.Errorf("Fatal error config file: %s \n", err)) + } + + if err := v.Unmarshal(t.config); err != nil { + panic(fmt.Errorf("Fatal Unmarshal error config file: %s \n", err)) + } +} diff --git a/test/start_test.go b/test/start_test.go new file mode 100644 index 0000000..63aa3de --- /dev/null +++ b/test/start_test.go @@ -0,0 +1,8 @@ +package test + +import ( + "testing" +) + +func TestStart(t *testing.T) { +} diff --git a/util/convert.go b/util/convert.go new file mode 100644 index 0000000..455fe57 --- /dev/null +++ b/util/convert.go @@ -0,0 +1,100 @@ +package util + +import ( + "math" + "reflect" + "strconv" + "strings" + "unsafe" +) + +// 字符串转Int +// intStr:数字的字符串 +func String2Int(intStr string) (intNum int) { + intNum, _ = strconv.Atoi(intStr) + return +} + +// 字符串转Int64 +// intStr:数字的字符串 +func String2Int64(intStr string) (int64Num int64) { + intNum, _ := strconv.Atoi(intStr) + int64Num = int64(intNum) + return +} + +// 字符串转Float64 +// floatStr:小数点数字的字符串 +func String2Float64(floatStr string) (floatNum float64) { + floatNum, _ = strconv.ParseFloat(floatStr, 64) + return +} + +// 字符串转Float32 +// floatStr:小数点数字的字符串 +func String2Float32(floatStr string) (floatNum float32) { + floatNum64, _ := strconv.ParseFloat(floatStr, 32) + floatNum = float32(floatNum64) + return +} + +// Int转字符串 +// intNum:数字字符串 +func Int2String(intNum int) (intStr string) { + intStr = strconv.Itoa(intNum) + return +} + +// Int64转字符串 +// intNum:数字字符串 +func Int642String(intNum int64) (int64Str string) { + //10, 代表10进制 + int64Str = strconv.FormatInt(intNum, 10) + return +} + +// Float64转字符串 +// floatNum:float64数字 +// prec:精度位数(不传则默认float数字精度) +func Float64ToString(floatNum float64, prec ...int) (floatStr string) { + if len(prec) > 0 { + floatStr = strconv.FormatFloat(floatNum, 'f', prec[0], 64) + return + } + floatStr = strconv.FormatFloat(floatNum, 'f', -1, 64) + return +} + +// Float32转字符串 +// floatNum:float32数字 +// prec:精度位数(不传则默认float数字精度) +func Float32ToString(floatNum float32, prec ...int) (floatStr string) { + if len(prec) > 0 { + floatStr = strconv.FormatFloat(float64(floatNum), 'f', prec[0], 32) + return + } + floatStr = strconv.FormatFloat(float64(floatNum), 'f', -1, 32) + return +} + +// 二进制转10进制 +func BinaryToDecimal(bit string) (num int) { + fields := strings.Split(bit, "") + lens := len(fields) + var tempF float64 = 0 + for i := 0; i < lens; i++ { + floatNum := String2Float64(fields[i]) + tempF += floatNum * math.Pow(2, float64(lens-i-1)) + } + num = int(tempF) + return +} + +// BytesToString 0 拷贝转换 slice byte 为 string +func BytesToString(b []byte) (s string) { + _bptr := (*reflect.SliceHeader)(unsafe.Pointer(&b)) + _sptr := (*reflect.StringHeader)(unsafe.Pointer(&s)) + _sptr.Data = _bptr.Data + _sptr.Len = _bptr.Len + return s +} diff --git a/util/md5.go b/util/md5.go new file mode 100644 index 0000000..04d800e --- /dev/null +++ b/util/md5.go @@ -0,0 +1,12 @@ +package util + +import ( + "crypto/md5" + "encoding/hex" +) + +func EncodeMD5(value string) string { + m := md5.New() + m.Write([]byte(value)) + return hex.EncodeToString(m.Sum(nil)) +} diff --git a/util/password/password.go b/util/password/password.go new file mode 100644 index 0000000..d1f2351 --- /dev/null +++ b/util/password/password.go @@ -0,0 +1,36 @@ +package password + +import ( + "crypto/md5" + "errors" + "golang.org/x/crypto/bcrypt" +) + +func Encode(userPassword string) (string, error) { + md5Password := md5.Sum([]byte(userPassword)) + encode, err := generatePassword(string(md5Password[:])) + if err != nil { + return "", errors.New("密码加密失败") + } else { + return string(encode), nil + } +} +func ValidateCode(userPassword string, hashed string) (isOK bool, err error) { + md5Password := md5.Sum([]byte(userPassword)) + isOk, err := validatePassword(string(md5Password[:]), hashed) + return isOk, err +} + +//generatePassword 给密码就行加密操作 +func generatePassword(userPassword string) ([]byte, error) { + return bcrypt.GenerateFromPassword([]byte(userPassword), bcrypt.DefaultCost) +} + +//validatePassword 密码比对 +func validatePassword(userPassword string, hashed string) (isOK bool, err error) { + if err = bcrypt.CompareHashAndPassword([]byte(hashed), []byte(userPassword)); err != nil { + return false, errors.New("密码错误!") + } + return true, nil + +} diff --git a/util/random.go b/util/random.go new file mode 100644 index 0000000..69c8062 --- /dev/null +++ b/util/random.go @@ -0,0 +1,42 @@ +package util + +import ( + "math/rand" + "time" +) + +// 随机生成字符串 +func RandomString(l int) string { + str := "0123456789AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz" + bytes := []byte(str) + var result []byte = make([]byte, 0, l) + r := rand.New(rand.NewSource(time.Now().UnixNano())) + for i := 0; i < l; i++ { + result = append(result, bytes[r.Intn(len(bytes))]) + } + return BytesToString(result) +} + +// 随机生成纯字符串 +func RandomPureString(l int) string { + str := "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz" + bytes := []byte(str) + var result []byte = make([]byte, 0, l) + r := rand.New(rand.NewSource(time.Now().UnixNano())) + for i := 0; i < l; i++ { + result = append(result, bytes[r.Intn(len(bytes))]) + } + return BytesToString(result) +} + +// 随机生成数字字符串 +func RandomNumber(l int) string { + str := "0123456789" + bytes := []byte(str) + var result []byte + r := rand.New(rand.NewSource(time.Now().UnixNano())) + for i := 0; i < l; i++ { + result = append(result, bytes[r.Intn(len(bytes))]) + } + return BytesToString(result) +} diff --git a/util/string.go b/util/string.go new file mode 100644 index 0000000..2c52a58 --- /dev/null +++ b/util/string.go @@ -0,0 +1,104 @@ +package util + +import ( + "strconv" + "strings" + "unicode" +) + +//判断是否是数字字符串 +func IsDigit(str string) bool { + for _, x := range []rune(str) { + if !unicode.IsDigit(x) { + return false + } + } + return true +} + +func Atoi(i string) int { + if i == "" { + return 0 + } + j, err := strconv.Atoi(i) + if err != nil { + return 0 + } else { + return j + } +} + +func StringToInt(s interface{}) int { + switch i := s.(type) { + case string: + return Atoi(i) + case float32: + return int(i) + case float64: + return int(i) + case int: + return i + case uint: + return int(i) + } + return 0 +} + +/** + * 驼峰转蛇形 snake string + * @description XxYy to xx_yy , XxYY to xx_y_y + * @date 2020/7/30 + * @param s 需要转换的字符串 + * @return string + **/ +func SnakeString(s string) string { + data := make([]byte, 0, len(s)*2) + j := false + num := len(s) + for i := 0; i < num; i++ { + d := s[i] + // or通过ASCII码进行大小写的转化 + // 65-90(A-Z),97-122(a-z) + //判断如果字母为大写的A-Z就在前面拼接一个_ + if i > 0 && d >= 'A' && d <= 'Z' && j { + data = append(data, '_') + } + if d != '_' { + j = true + } + data = append(data, d) + } + //ToLower把大写字母统一转小写 + return strings.ToLower(string(data[:])) +} + +/** + * 蛇形转驼峰 + * @description xx_yy to XxYx xx_y_y to XxYY + * @date 2020/7/30 + * @param s要转换的字符串 + * @return string + **/ +func CamelString(s string) string { + data := make([]byte, 0, len(s)) + j := false + k := false + num := len(s) - 1 + for i := 0; i <= num; i++ { + d := s[i] + if k == false && d >= 'A' && d <= 'Z' { + k = true + } + if d >= 'a' && d <= 'z' && (j || k == false) { + d = d - 32 + j = false + k = true + } + if k && d == '_' && num > i && s[i+1] >= 'a' && s[i+1] <= 'z' { + j = true + continue + } + data = append(data, d) + } + return string(data[:]) +} diff --git a/util/validator.go b/util/validator.go new file mode 100644 index 0000000..34145c7 --- /dev/null +++ b/util/validator.go @@ -0,0 +1,13 @@ +package util + +import "regexp" + +// 识别手机号码 +func IsMobile(mobile string) bool { + result, _ := regexp.MatchString(`^(1[0-9][0-9]\d{4,8})$`, mobile) + if result { + return true + } else { + return false + } +}