From 1bbc7db4059c0acc347d9fffc659fad266492a23 Mon Sep 17 00:00:00 2001 From: Yun Date: Sat, 16 Sep 2023 20:14:20 +0800 Subject: [PATCH] =?UTF-8?q?=E5=B0=81=E8=A3=85=E7=9A=84=E6=96=B9=E6=B3=95?= =?UTF-8?q?=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- aliyunx/smsx/smsx.go | 51 ++++ arrayx/arrayx.go | 77 ++++++ arrayx/arrayx_test.go | 44 +++ cachex/cachex.go | 59 ++++ configx/viperx/viperx.go | 61 +++++ connPoolx/more/connPoolx.go | 115 ++++++++ connPoolx/simple/connPoolx.go | 57 ++++ convx/auto_conv.go | 26 ++ convx/convx_test.go | 171 ++++++++++++ convx/to_float32.go | 50 ++++ convx/to_float64.go | 47 ++++ convx/to_int.go | 40 +++ convx/to_int32.go | 7 + convx/to_int64.go | 41 +++ convx/to_string.go | 44 +++ encryptx/aesx/aesx.go | 174 ++++++++++++ encryptx/aesx/aesx_test.go | 24 ++ encryptx/base64x/base64x.go | 51 ++++ encryptx/gpgx/gpgx.go | 41 +++ encryptx/md5x/md5x.go | 28 ++ encryptx/rsax/rsax.go | 119 ++++++++ encryptx/rsax/rsax_test.go | 18 ++ encryptx/sha1x/sha1x.go | 18 ++ encryptx/sha1x/sha1x_test.go | 13 + encryptx/sha256x/sha256x.go | 14 + httpx/curlx/curlx.go | 254 +++++++++++++++++ httpx/curlx/request.go | 218 +++++++++++++++ httpx/curlx/resopnse.go | 124 +++++++++ httpx/post.go | 3 + jwtx/jwtx.go | 83 ++++++ langx/code.go | 6 + langx/consts.go | 15 + langx/langx.go | 36 +++ langx/zh.go | 6 + loggerx/delete.go | 5 + loggerx/loggerx.go | 192 +++++++++++++ loggerx/loggerx_test.go | 19 ++ loggerx/readme.md | 12 + panicx/panicx.go | 31 +++ randomx/randomx.go | 43 +++ randomx/randomx_test.go | 30 ++ response/response.go | 90 ++++++ runtimex/runtimex.go | 23 ++ runtimex/runtimex_test.go | 11 + signx/signx.go | 71 +++++ tencentx/wechatx/accessToken.go | 37 +++ tencentx/wechatx/consts.go | 13 + tencentx/wechatx/cryptx/cryptx.go | 361 +++++++++++++++++++++++++ tencentx/wechatx/cryptx/cryptx_test.go | 69 +++++ tencentx/wechatx/message.go | 20 ++ tencentx/wechatx/notify.go | 87 ++++++ tencentx/wechatx/wechat.go | 21 ++ tencentx/wecomx/wecom.go | 13 + timer/example_test.go | 21 ++ timer/timer.go | 172 ++++++++++++ timer/timer_test.go | 21 ++ timex/timex.go | 30 ++ uniquex/uniquex.go | 94 +++++++ upload/upload.go | 50 ++++ 59 files changed, 3671 insertions(+) create mode 100644 aliyunx/smsx/smsx.go create mode 100644 arrayx/arrayx.go create mode 100644 arrayx/arrayx_test.go create mode 100644 cachex/cachex.go create mode 100644 configx/viperx/viperx.go create mode 100644 connPoolx/more/connPoolx.go create mode 100644 connPoolx/simple/connPoolx.go create mode 100644 convx/auto_conv.go create mode 100644 convx/convx_test.go create mode 100644 convx/to_float32.go create mode 100644 convx/to_float64.go create mode 100644 convx/to_int.go create mode 100644 convx/to_int32.go create mode 100644 convx/to_int64.go create mode 100644 convx/to_string.go create mode 100644 encryptx/aesx/aesx.go create mode 100644 encryptx/aesx/aesx_test.go create mode 100644 encryptx/base64x/base64x.go create mode 100644 encryptx/gpgx/gpgx.go create mode 100644 encryptx/md5x/md5x.go create mode 100644 encryptx/rsax/rsax.go create mode 100644 encryptx/rsax/rsax_test.go create mode 100644 encryptx/sha1x/sha1x.go create mode 100644 encryptx/sha1x/sha1x_test.go create mode 100644 encryptx/sha256x/sha256x.go create mode 100644 httpx/curlx/curlx.go create mode 100644 httpx/curlx/request.go create mode 100644 httpx/curlx/resopnse.go create mode 100644 httpx/post.go create mode 100644 jwtx/jwtx.go create mode 100644 langx/code.go create mode 100644 langx/consts.go create mode 100644 langx/langx.go create mode 100644 langx/zh.go create mode 100644 loggerx/delete.go create mode 100644 loggerx/loggerx.go create mode 100644 loggerx/loggerx_test.go create mode 100644 loggerx/readme.md create mode 100644 panicx/panicx.go create mode 100644 randomx/randomx.go create mode 100644 randomx/randomx_test.go create mode 100644 response/response.go create mode 100644 runtimex/runtimex.go create mode 100644 runtimex/runtimex_test.go create mode 100644 signx/signx.go create mode 100644 tencentx/wechatx/accessToken.go create mode 100644 tencentx/wechatx/consts.go create mode 100644 tencentx/wechatx/cryptx/cryptx.go create mode 100644 tencentx/wechatx/cryptx/cryptx_test.go create mode 100644 tencentx/wechatx/message.go create mode 100644 tencentx/wechatx/notify.go create mode 100644 tencentx/wechatx/wechat.go create mode 100644 tencentx/wecomx/wecom.go create mode 100644 timer/example_test.go create mode 100644 timer/timer.go create mode 100644 timer/timer_test.go create mode 100644 timex/timex.go create mode 100644 uniquex/uniquex.go create mode 100644 upload/upload.go diff --git a/aliyunx/smsx/smsx.go b/aliyunx/smsx/smsx.go new file mode 100644 index 0000000..f8ce7ba --- /dev/null +++ b/aliyunx/smsx/smsx.go @@ -0,0 +1,51 @@ +package smsx + +import ( + "errors" + "fmt" + + "github.com/aliyun/alibaba-cloud-sdk-go/services/dysmsapi" +) + +type sms struct { + endPoint string + accessKeyId string + accessKeySecret string +} + +func NewSms(endPoint string, accessKeyId string, accessKeySecret string) *sms { + return &sms{ + endPoint: endPoint, + accessKeyId: accessKeyId, + accessKeySecret: accessKeySecret, + } +} + +func (s *sms) Send(phone, code, signName, templateCode string) error { + client, err := dysmsapi.NewClientWithAccessKey(s.endPoint, s.accessKeyId, s.accessKeySecret) + if err != nil { + return err + } + + req := dysmsapi.CreateSendSmsRequest() + req.Scheme = "https" + req.PhoneNumbers = phone + req.SignName = signName + req.TemplateCode = templateCode + req.TemplateParam = fmt.Sprintf(`{"code":"%s"}`, code) + resp, err := client.SendSms(req) + // fmt.Printf("sms send: resp:%+v err:%+v", resp, err) + + if err != nil { + return err + } + if resp == nil { + return errors.New("没有获取到发送结果") + } + + if resp.Code != "OK" { + return errors.New(resp.Message) + } + + return nil +} diff --git a/arrayx/arrayx.go b/arrayx/arrayx.go new file mode 100644 index 0000000..412367a --- /dev/null +++ b/arrayx/arrayx.go @@ -0,0 +1,77 @@ +package arrayx + +// Author: yun +// Version: 2023年6月12日18:54:17 + +import ( + "reflect" + "sort" +) + +/** + * 判断是否在一个数组里面 + */ +func InArray(target interface{}, array interface{}) (exists bool, index int) { + exists = false + index = -1 + switch reflect.TypeOf(array).Kind() { + case reflect.Slice: + s := reflect.ValueOf(array) + for i := 0; i < s.Len(); i++ { + if reflect.DeepEqual(target, s.Index(i).Interface()) { + index = i + exists = true + return + } + } + } + return +} + +// 判断字符串是否在字符串数组中 +// TODO:待验证 +func InStringArray(target string, strArray []string) bool { + // 对字符串切片进行排序 + sort.Strings(strArray) + index := sort.SearchStrings(strArray, target) + // 先判断 &&左侧的条件,如果不满足则结束此处判断,不会再进行右侧的判断 + if index < len(strArray) && strArray[index] == target { + return true + } + return false +} + +// 数组去重 +func ArrayUniqueString(target []string) []string { + result := make([]string, 0, len(target)) + temp := map[string]struct{}{} + for _, item := range target { + if _, ok := temp[item]; !ok { + temp[item] = struct{}{} + result = append(result, item) + } + } + return result +} + +// 交集 +// 并集 +// 差集 +// 补集 +// 去重 +// 排序 +// 反转 +// 求和 +// 求平均值 +// 求最大值 +// 求最小值 +// 求最大值和最小值 +// 求最大值和最小值的差值 +// 求最大值和最小值的差值的绝对值 +// 求最大值和最小值的差值的绝对值的平均值 +// 求最大值和最小值的差值的绝对值的平均值的平方根 +// 求最大值和最小值的差值的绝对值的平均值的平方根的平方 +// 求最大值和最小值的差值的绝对值的平均值的平方根的平方的平均值 + + + diff --git a/arrayx/arrayx_test.go b/arrayx/arrayx_test.go new file mode 100644 index 0000000..479c638 --- /dev/null +++ b/arrayx/arrayx_test.go @@ -0,0 +1,44 @@ +package arrayx_test + +import ( + "testing" + "yunink/app/pkg/arrayx" +) + +func TestInArray(t *testing.T) { + arr1 := []int{1, 2, 3} + var val64 int64 = 1 + var val32 int32 = 1 + var val int = 1 + + exist, index := arrayx.InArray(val, arr1) + t.Log(exist, index) + if !exist { + t.Error("int64类型不应该在int数组里面") + } + + exist, index = arrayx.InArray(val32, arr1) + t.Log(exist, index) + if exist { + t.Error("int64类型不应该在int数组里面") + } + + exist, _ = arrayx.InArray(val64, arr1) + if exist { + t.Error("int64类型不应该在int数组里面") + } + arr2 := []interface{}{1, 2, 3} + exist, _ = arrayx.InArray(val64, arr2) + if exist { + t.Error("int64类型不应该在int数组里面") + } + arr3 := []interface{}{ + 1, + "huangxinyun", + 6.6, + } + exist, _ = arrayx.InArray("huangxinyun", arr3) + if !exist { + t.Error("int64类型不应该在int数组里面") + } +} diff --git a/cachex/cachex.go b/cachex/cachex.go new file mode 100644 index 0000000..60b5c1a --- /dev/null +++ b/cachex/cachex.go @@ -0,0 +1,59 @@ +package cachex + +import ( + "sync" + "time" +) + +// 内存缓存 +// 到设定的点就删除 + +var store sync.Map + +type cache struct {} + +type cacheData struct { + key string + data interface{} + expire time.Time +} + +func NewCache() *cache { + return &cache{} +} + +func (c *cache) Set(key string, value interface{}, expire time.Duration) { + cd := &cacheData{key,value,time.Now().Add(expire)} + store.Store(key, cd) +} + +func (c *cache) Get(key string) interface{} { + if v, ok := store.Load(key); ok { + + cc := v.(*cacheData) + if cc.expire.Before(time.Now()) { + store.Delete(key) + return nil + } + return cc.data + } + return nil +} + +func (c *cache) Delete(key string) { + store.Delete(key) +} + +func init() { + for { + store.Range(func(key, value interface{}) bool { + if value.(*cacheData).expire.Before(time.Now()) { + store.Delete(key) + } + return true + }) + + time.Sleep(time.Second * 5) + } + +} diff --git a/configx/viperx/viperx.go b/configx/viperx/viperx.go new file mode 100644 index 0000000..70d35e1 --- /dev/null +++ b/configx/viperx/viperx.go @@ -0,0 +1,61 @@ +package viperx + +// Author: yun +// Version: 2023年6月12日18:54:48 + +import ( + "flag" + "fmt" + "log" + "os" + "yunink/app/config" + + "github.com/fsnotify/fsnotify" + "github.com/spf13/viper" +) + +// 使用viper初始化配置 + +func InitConfig(path string) (*config.Config, error) { + + if len(path) == 0 { + flag.StringVar(&path, "c", "", "choose config file.") + flag.Parse() + if path == "" { // 优先级: 命令行 > 环境变量 > 默认值 + if configEnv := os.Getenv("config"); configEnv == "" { + path = "config.yaml" + log.Printf("您正在使用config的默认值,config的路径为%v\n", path) + } else { + path = configEnv + log.Printf("您正在使用GVA_CONFIG环境变量,config的路径为%v\n", path) + } + } else { + log.Printf("您正在使用命令行的-c参数传递的值,config的路径为%v\n", path) + } + } + + v := viper.New() + v.SetConfigFile(path) + err := v.ReadInConfig() + if err != nil { + return nil, err + } + // 监控配置文件的变化 + v.WatchConfig() + + resp := new(config.Config) + + // 监听配置的变化 + v.OnConfigChange(func(e fsnotify.Event) { + fmt.Println("config file changed:", e.Name) + if err := v.Unmarshal(&resp); err != nil { + log.Println(err) + } + }) + + if err := v.Unmarshal(&resp); err != nil { + log.Println(err) + return nil, err + } + return resp, nil +} diff --git a/connPoolx/more/connPoolx.go b/connPoolx/more/connPoolx.go new file mode 100644 index 0000000..f794623 --- /dev/null +++ b/connPoolx/more/connPoolx.go @@ -0,0 +1,115 @@ +package more + +import ( + "sync" + + uuid "github.com/satori/go.uuid" +) + +// 同一个key对应多个连接 +// 2023年6月29日13:45:25 + +// 场景: +// 1. 同时支持多个用户在线 +// 1. 一个用户对应多个连接 +// 2. 在其中一个用户连接中发送消息,同用户其他连接也能收到 + +type connPool struct { + pool map[string]map[string]PoolValue + idx map[string]string // 索引 + mu sync.RWMutex +} + +type PoolValue interface { + Close() error +} + +// 初始化连接池 +func NewConnPool() *connPool { + return &connPool{ + pool: make(map[string]map[string]PoolValue), + idx: make(map[string]string), + mu: sync.RWMutex{}, + } +} + +// 添加连接, 返回connId +func (c *connPool) Store(key string, value PoolValue) string { + c.mu.Lock() + defer c.mu.Unlock() + val, _ := c.pool[key] + + connId := uuid.NewV4().String() + val[connId] = value + c.pool[key] = val + c.idx[connId] = key + return connId +} + +// 获取一个用户的所有连接 +func (c *connPool) LoadByKey(key string) (conns map[string]PoolValue, ok bool) { + c.mu.RLock() + defer c.mu.RUnlock() + + valMap, ok := c.pool[key] + + return valMap, ok +} + +// 根据连接ID获取连接 +func (c *connPool) LoadByConnId(connId string) (*PoolValue, bool) { + c.mu.RLock() + defer c.mu.Unlock() + + key, ok := c.idx[connId] + if !ok { + return nil, false + } + + valMap, ok := c.pool[key] + if !ok { + return nil, false + } + + val, ok := valMap[connId] + if !ok { + return nil, false + } + return &val, true +} + +// 关闭连接 +func (c *connPool) Close(connId string) { + c.mu.Lock() + defer c.mu.Unlock() + + key, ok := c.idx[connId] + if !ok { + return + } + valMap, ok := c.pool[key] + if !ok { + return + } + val, ok := valMap[connId] + if !ok { + return + } + delete(valMap, connId) + + // 异步关闭 + go val.Close() +} + +// 遍历所有的连接 +func (c *connPool) Range(f func(key string, connId string, values PoolValue)) { + c.mu.RLock() + defer c.mu.Unlock() + + for key, val := range c.pool { + val := val + for keyPoo, valPoo := range val { + go f(key, keyPoo, valPoo) + } + } +} diff --git a/connPoolx/simple/connPoolx.go b/connPoolx/simple/connPoolx.go new file mode 100644 index 0000000..8f5644b --- /dev/null +++ b/connPoolx/simple/connPoolx.go @@ -0,0 +1,57 @@ +package connPoolx + +import ( + "errors" + "sync" +) + +// 连接池 + +// 核心思想:连接放在这里面统一管理,所有的新建与删除需要外部操作 + +// 适用场景:每一个key对应一个val + +type connPool struct { + pool sync.Map +} + +type PoolValue interface { + Close() error +} + +func NewConnPool() *connPool { + return &connPool{} +} + +// 同名将覆盖 +func (c *connPool) Store(key string, value PoolValue) { + c.pool.Store(key, value) +} + +func (c *connPool) Load(key string) (value PoolValue, ok bool) { + val, ok := c.pool.Load(key) + if !ok { + return nil, false + } + value, ok = val.(PoolValue) + if !ok { + return nil, false + } + return value, true +} + +func (c *connPool) Close(key string) error { + val, ok := c.pool.LoadAndDelete(key) + if ok { + value, ok := val.(PoolValue) + if !ok { + return errors.New("解析异常") + } + return value.Close() + } + return nil +} + +func (c *connPool) Range(f func(key any, value any) bool) { + c.pool.Range(f) +} diff --git a/convx/auto_conv.go b/convx/auto_conv.go new file mode 100644 index 0000000..10170c9 --- /dev/null +++ b/convx/auto_conv.go @@ -0,0 +1,26 @@ +package convx + +import ( + "errors" +) + +func AutoConv(toType string, target interface{}) (interface{}, error) { + + var v interface{} + err := errors.New("not support type") + switch toType { + case "float64": + v, err = ToFloat64(target) + case "float32": + v, err = ToFloat32(target) + case "int": + v, err = ToInt(target) + case "int64": + v, err = ToInt64(target) + case "int32": + v, err = ToInt32(target) + case "string": + v, err = ToString(target) + } + return v, err +} diff --git a/convx/convx_test.go b/convx/convx_test.go new file mode 100644 index 0000000..01b0075 --- /dev/null +++ b/convx/convx_test.go @@ -0,0 +1,171 @@ +package convx_test + +import ( + "yunink/app/pkg/convx" + "testing" +) + +func TestConvToString(t *testing.T) { + // 日志 + // t.Log("hello world") + + // 测试int + var v_int int = 123456789 + str, err := convx.ToString(v_int) + if err != nil { + t.Fail() + t.Log(err) + } + if str != "123456789" { + t.Fail() + t.Log(str) + } + + // 测试int32 + var v_int32 int32 = 123456789 + str, err = convx.ToString(v_int32) + if err != nil { + t.Fail() + t.Log(err) + } + if str != "123456789" { + t.Fail() + t.Log(str) + } + + // 测试int64 + var v_int64 int64 = 123456789 + str, err = convx.ToString(v_int64) + if err != nil { + t.Fail() + t.Log(err) + } + if str != "123456789" { + t.Fail() + t.Log(str) + } + + // 测试float32 + var v_float32 float32 = 123.12345 + str, err = convx.ToString(v_float32) + if err != nil { + t.Fail() + t.Log(err) + } + if str != "123.12345" { + t.Fail() + t.Log(str) + } + + // 测试float64 + var v_float64 float64 = 123456789.12345 + str, err = convx.ToString(v_float64) + if err != nil { + t.Fail() + t.Log(err) + } + if str != "123456789.12345" { + t.Fail() + t.Log(str) + } + + // 测试string + var v_string string = "123456789.12345" + str, err = convx.ToString(v_string) + if err != nil { + t.Fail() + t.Log(err) + } + if str != "123456789.12345" { + t.Fail() + t.Log(str) + } + + // 标记错误(继续运行) + // t.Fail() + + // 终止运行 + // t.FailNow() + +} + +func TestConvToInt(t *testing.T) { + // int + var v_int int = 123456789 + i, err := convx.ToInt(v_int) + if err != nil { + t.Fail() + t.Log(err) + } + if i != 123456789 { + t.Fail() + t.Log(i) + } + + // int32 + var v_int32 int32 = 123456789 + i, err = convx.ToInt(v_int32) + if err != nil { + t.Fail() + t.Log(err) + } + if i != 123456789 { + t.Fail() + t.Log(i) + } + + // int64 + var v_int64 int64 = 123456789 + i, err = convx.ToInt(v_int64) + if err != nil { + t.Fail() + t.Log(err) + } + if i != 123456789 { + t.Fail() + t.Log(i) + } + + // float32 + var v_float32 float32 = 1234.56789 + i, err = convx.ToInt(v_float32) + if err != nil { + t.Fail() + t.Log(err) + } + if i != 1234 { + t.Fail() + t.Log(i) + } + + // float64 + var v_float64 float64 = 1234.56789 + i, err = convx.ToInt(v_float64) + if err != nil { + t.Fail() + t.Log(err) + } + if i != 1234 { + t.Fail() + t.Log(i) + } + + // string + var v_string string = "1234" + i, err = convx.ToInt(v_string) + if err != nil { + t.Fail() + t.Log(err) + } + if i != 1234 { + t.Fail() + t.Log(i) + } + +} + +func TestConvToInt64(t *testing.T) {} + +func TestConvToFloat32(t *testing.T) {} + +func TestConvToFloat64(t *testing.T) {} diff --git a/convx/to_float32.go b/convx/to_float32.go new file mode 100644 index 0000000..e9f80e3 --- /dev/null +++ b/convx/to_float32.go @@ -0,0 +1,50 @@ +package convx + +import ( + "errors" + "strconv" +) + +// interface 转 float32 +func ToFloat32(val interface{}) (f float32, err error) { + if v, ok := val.(string); ok { + ff, err := strconv.ParseFloat(v, 32) + if err != nil { + return 0, err + } + f = float32(ff) + } else if v, ok := val.(float32); ok { + return v, nil + } else if v, ok := val.(float64); ok { + return float32(v), nil + } else if v, ok := val.(int); ok { + return float32(v), nil + } else if v, ok := val.(int8); ok { + return float32(v), nil + } else if v, ok := val.(int16); ok { + return float32(v), nil + } else if v, ok := val.(int32); ok { + return float32(v), nil + } else if v, ok := val.(int64); ok { + return float32(v), nil + } else if v, ok := val.(uint); ok { + return float32(v), nil + } else if v, ok := val.(uint8); ok { + return float32(v), nil + } else if v, ok := val.(uint16); ok { + return float32(v), nil + } else if v, ok := val.(uint32); ok { + return float32(v), nil + } else if v, ok := val.(uint64); ok { + return float32(v), nil + } else if v, ok := val.(bool); ok { + if v { + return 1, nil + } else { + return 0, nil + } + } else { + return 0, errors.New("类型转换失败") + } + return +} diff --git a/convx/to_float64.go b/convx/to_float64.go new file mode 100644 index 0000000..626d5ca --- /dev/null +++ b/convx/to_float64.go @@ -0,0 +1,47 @@ +package convx + +import ( + "strconv" + + "errors" +) + +// interface 转 float64 +func ToFloat64(val interface{}) (f float64, err error) { + if v, ok := val.(string); ok { + f, err = strconv.ParseFloat(v, 64) + } else if v, ok := val.(float64); ok { + return v, nil + } else if v, ok := val.(float32); ok { + return float64(v), nil + } else if v, ok := val.(int); ok { + return float64(v), nil + } else if v, ok := val.(int8); ok { + return float64(v), nil + } else if v, ok := val.(int16); ok { + return float64(v), nil + } else if v, ok := val.(int32); ok { + return float64(v), nil + } else if v, ok := val.(int64); ok { + return float64(v), nil + } else if v, ok := val.(uint); ok { + return float64(v), nil + } else if v, ok := val.(uint8); ok { + return float64(v), nil + } else if v, ok := val.(uint16); ok { + return float64(v), nil + } else if v, ok := val.(uint32); ok { + return float64(v), nil + } else if v, ok := val.(uint64); ok { + return float64(v), nil + } else if v, ok := val.(bool); ok { + if v { + return 1, nil + } else { + return 0, nil + } + } else { + return 0, errors.New("类型转换失败") + } + return +} diff --git a/convx/to_int.go b/convx/to_int.go new file mode 100644 index 0000000..98a247d --- /dev/null +++ b/convx/to_int.go @@ -0,0 +1,40 @@ +package convx + +import ( + "errors" + "strconv" +) + +// interface 转 int +func ToInt(val interface{}) (i int, err error) { + + if v, ok := val.(string); ok { + // 不支持小数转换 + i, err = strconv.Atoi(v) + } else if v, ok := val.(float32); ok { + i = int(v) + } else if v, ok := val.(float64); ok { + i = int(v) + } else if v, ok := val.(int); ok { + i = v + } else if v, ok := val.(int32); ok { + i = int(v) + } else if v, ok := val.(int64); ok { + i = int(v) + } else if v, ok := val.(uint); ok { + i = int(v) + } else if v, ok := val.(uint32); ok { + i = int(v) + } else if v, ok := val.(uint64); ok { + i = int(v) + } else if v, ok := val.(bool); ok { + if v { + i = 1 + } else { + i = 0 + } + } else { + return 0, errors.New("类型转换失败") + } + return +} diff --git a/convx/to_int32.go b/convx/to_int32.go new file mode 100644 index 0000000..71e0094 --- /dev/null +++ b/convx/to_int32.go @@ -0,0 +1,7 @@ +package convx + +func ToInt32(val interface{}) (i int32, err error) { + ii, err := ToInt(val) + i = int32(ii) + return +} diff --git a/convx/to_int64.go b/convx/to_int64.go new file mode 100644 index 0000000..e81c88d --- /dev/null +++ b/convx/to_int64.go @@ -0,0 +1,41 @@ +package convx + +import ( + "errors" + "strconv" +) + +// interface 转 int64 +func ToInt64(val interface{}) (i int64, err error) { + + if v, ok := val.(int64); ok { + i = v + } else if v, ok := val.(int32); ok { + i = int64(v) + } else if v, ok := val.(string); ok { + // string 转 int64 + // 第二个参数为基数(2~36),第三个参数位大小表示期望转换的结果类型,其值可以为0, 8, 16, 32和64,分别对应 int, int8, int16, int32和int64 + i, err = strconv.ParseInt(v, 10, 64) + } else if v, ok := val.(float64); ok { + i = int64(v) + } else if v, ok := val.(float32); ok { + i = int64(v) + } else if v, ok := val.(int); ok { + i = int64(v) + } else if v, ok := val.(uint); ok { + i = int64(v) + } else if v, ok := val.(uint32); ok { + i = int64(v) + } else if v, ok := val.(uint64); ok { + i = int64(v) + } else if v, ok := val.(bool); ok { + if v { + i = 1 + } else { + i = 0 + } + } else { + err = errors.New("不支持的参数类型") + } + return +} diff --git a/convx/to_string.go b/convx/to_string.go new file mode 100644 index 0000000..a545eda --- /dev/null +++ b/convx/to_string.go @@ -0,0 +1,44 @@ +package convx + +import ( + "errors" + "strconv" +) + +// interface 转 string +func ToString(val interface{}) (str string, err error) { + + var s string + if vv, ok := val.(float32); ok { + s = strconv.FormatFloat(float64(vv), 'f', -1, 32) + } else if vv, ok := val.(float64); ok { + s = strconv.FormatFloat(vv, 'f', -1, 64) + } else if vv, ok := val.(int); ok { + s = strconv.Itoa(vv) + } else if vv, ok := val.(int32); ok { + s = strconv.Itoa(int(vv)) + } else if vv, ok := val.(int64); ok { + s = strconv.FormatInt(vv, 10) + } else if vv, ok := val.(string); ok { + s = vv + } else if vv, ok := val.(bool); ok { + s = strconv.FormatBool(vv) + } else if vv, ok := val.(uint); ok { + s = strconv.FormatUint(uint64(vv), 10) + } else if vv, ok := val.(uint32); ok { + s = strconv.FormatUint(uint64(vv), 10) + } else if vv, ok := val.(uint64); ok { + s = strconv.FormatUint(vv, 10) + } else if vv, ok := val.(uint8); ok { + s = strconv.FormatUint(uint64(vv), 10) + } else if vv, ok := val.(uint16); ok { + s = strconv.FormatUint(uint64(vv), 10) + } else if vv, ok := val.(int8); ok { + s = strconv.FormatInt(int64(vv), 10) + } else if vv, ok := val.(int16); ok { + s = strconv.FormatInt(int64(vv), 10) + } else { + return s, errors.New("不支持的参数类型") + } + return s, nil +} diff --git a/encryptx/aesx/aesx.go b/encryptx/aesx/aesx.go new file mode 100644 index 0000000..ac08b94 --- /dev/null +++ b/encryptx/aesx/aesx.go @@ -0,0 +1,174 @@ +package aesx + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "encoding/hex" + "errors" + "fmt" + "io" +) + +func PKCS7Padding(ciphertext []byte, blockSize int) []byte { + padding := blockSize - len(ciphertext)%blockSize + padtext := bytes.Repeat([]byte{byte(padding)}, padding) + return append(ciphertext, padtext...) +} + +func PKCS7UnPadding(origData []byte) []byte { + length := len(origData) + unpadding := int(origData[length-1]) + return origData[:(length - unpadding)] +} + +// AES加密,CBC +func AesEncrypt(origData, key []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + blockSize := block.BlockSize() + origData = PKCS7Padding(origData, blockSize) + blockMode := cipher.NewCBCEncrypter(block, key[:blockSize]) + crypted := make([]byte, len(origData)) + blockMode.CryptBlocks(crypted, origData) + return crypted, nil +} + +// AES解密 +func AesDecrypt(crypted, key []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + blockSize := block.BlockSize() + blockMode := cipher.NewCBCDecrypter(block, key[:blockSize]) + origData := make([]byte, len(crypted)) + blockMode.CryptBlocks(origData, crypted) + origData = PKCS7UnPadding(origData) + return origData, nil +} + +/* +AES CBC 加密 +key:加密key +plaintext:加密明文 +ciphertext:解密返回字节字符串[ 整型以十六进制方式显示] +*/ +func AESCBCEncrypt(key, plaintext string) (ciphertext string,err error) { + plainbyte := []byte(plaintext) + keybyte := []byte(key) + if len(plainbyte)%aes.BlockSize != 0 { + return "",errors.New ("plaintext is not a multiple of the block size") + } + block, err := aes.NewCipher(keybyte) + if err != nil { + return "",err + } + + cipherbyte := make([]byte, aes.BlockSize+len(plainbyte)) + iv := cipherbyte[:aes.BlockSize] + if _, err := io.ReadFull(rand.Reader, iv); err != nil { + return "",err + } + + mode := cipher.NewCBCEncrypter(block, iv) + mode.CryptBlocks(cipherbyte[aes.BlockSize:], plainbyte) + + ciphertext = fmt.Sprintf("%x\n", cipherbyte) + return +} + +/* +AES CBC 解码 +key:解密key +ciphertext:加密返回的串 +plaintext:解密后的字符串 +*/ +func AESCBCDecrypter(key, ciphertext string) (plaintext string,err error) { + cipherbyte, _ := hex.DecodeString(ciphertext) + keybyte := []byte(key) + block, err := aes.NewCipher(keybyte) + if err != nil { + return "",err + } + if len(cipherbyte) < aes.BlockSize { + return "", errors.New("ciphertext too short") + } + + iv := cipherbyte[:aes.BlockSize] + cipherbyte = cipherbyte[aes.BlockSize:] + if len(cipherbyte)%aes.BlockSize != 0 { + return "", errors.New("ciphertext is not a multiple of the block size") + } + + mode := cipher.NewCBCDecrypter(block, iv) + mode.CryptBlocks(cipherbyte, cipherbyte) + + //fmt.Printf("%s\n", ciphertext) + plaintext = string(cipherbyte[:]) + return +} + +/* +AES GCM 加密 +key:加密key +plaintext:加密明文 +ciphertext:解密返回字节字符串[ 整型以十六进制方式显示] +*/ +func AESGCMEncrypt(key, plaintext string) (ciphertext, noncetext string, err error) { + plainbyte := []byte(plaintext) + keybyte := []byte(key) + block, err := aes.NewCipher(keybyte) + if err != nil { + return "", "", err + } + + // 由于存在重复的风险,请勿使用给定密钥使用超过2^32个随机值。 + nonce := make([]byte, 12) + if _, err := io.ReadFull(rand.Reader, nonce); err != nil { + return "", "", err + } + + aesgcm, err := cipher.NewGCM(block) + if err != nil { + return "", "", err + } + + cipherbyte := aesgcm.Seal(nil, nonce, plainbyte, nil) + ciphertext = fmt.Sprintf("%x\n", cipherbyte) + noncetext = fmt.Sprintf("%x\n", nonce) + return +} + +/* +AES CBC 解码 +key:解密key +ciphertext:加密返回的串 +plaintext:解密后的字符串 +*/ +func AESGCMDecrypter(key, ciphertext, noncetext string) (plaintext string, err error) { + cipherbyte, _ := hex.DecodeString(ciphertext) + nonce, _ := hex.DecodeString(noncetext) + keybyte := []byte(key) + block, err := aes.NewCipher(keybyte) + if err != nil { + return "", err + } + + aesgcm, err := cipher.NewGCM(block) + if err != nil { + return "", err + } + + plainbyte, err := aesgcm.Open(nil, nonce, cipherbyte, nil) + if err != nil { + return "", err + } + + //fmt.Printf("%s\n", ciphertext) + plaintext = string(plainbyte[:]) + return +} diff --git a/encryptx/aesx/aesx_test.go b/encryptx/aesx/aesx_test.go new file mode 100644 index 0000000..bb65e23 --- /dev/null +++ b/encryptx/aesx/aesx_test.go @@ -0,0 +1,24 @@ +package aesx_test + +import ( + "fmt" + "testing" + "yunink/app/pkg/encryptx/aesx" +) + +func TestAes(t *testing.T) { + key := "example key 1234" + plaintext := "exampleplaintext" + ciphertext, err := aesx.AESCBCEncrypt(key, plaintext) + fmt.Println(ciphertext, err) + + plaintext, err = aesx.AESCBCDecrypter(key, ciphertext) + fmt.Println(plaintext, err) + ///GCM + noncetext := "" + ciphertext, noncetext, err = aesx.AESGCMEncrypt(key, plaintext) + fmt.Println(ciphertext, err) + + plaintext, err = aesx.AESGCMDecrypter(key, ciphertext, noncetext) + fmt.Println(plaintext, err) +} diff --git a/encryptx/base64x/base64x.go b/encryptx/base64x/base64x.go new file mode 100644 index 0000000..a987af8 --- /dev/null +++ b/encryptx/base64x/base64x.go @@ -0,0 +1,51 @@ +package base64x + +import "encoding/base64" + +// 普通的 +func Base64StdEncode(s string) string { + return base64.StdEncoding.EncodeToString([]byte(s)) +} + +func Base64StdDecode(sEnc string) (string, error) { + sDec, err := base64.StdEncoding.DecodeString(sEnc) + if err != nil { + return "", err + } + return string(sDec), nil +} + +// URL和文件名安全的 +func Base64UrlEncode(s string) string { + return base64.URLEncoding.EncodeToString([]byte(s)) +} + +func Base64UrlDecode(sEnc string) (string, error) { + sDec, err := base64.URLEncoding.DecodeString(sEnc) + if err != nil { + return "", err + } + return string(sDec), nil +} + +// 无填充 +func Base64RawEncode(s string) string { + return base64.RawStdEncoding.EncodeToString([]byte(s)) +} + +func Base64RawDecode(sEnc string) (string, error) { + sDec, err := base64.RawStdEncoding.DecodeString(sEnc) + if err != nil { + return "", err + } + return string(sDec), nil +} + +func Base64RawUrlEncode(s string) string { + return base64.RawURLEncoding.EncodeToString([]byte(s)) +} + +func Base64RawUrlDecode(s string) (string, error) { + decoded, err := base64.RawURLEncoding.DecodeString(s) + return string(decoded), err +} diff --git a/encryptx/gpgx/gpgx.go b/encryptx/gpgx/gpgx.go new file mode 100644 index 0000000..f9a698f --- /dev/null +++ b/encryptx/gpgx/gpgx.go @@ -0,0 +1,41 @@ +package gpgx + +import ( + "fmt" + "os" + + "golang.org/x/crypto/openpgp" +) + +func Demo() { + keyRingReader, err := os.Open("signer-pubkey.asc") + if err != nil { + fmt.Println(err) + return + } + + signature, err := os.Open("signature.asc") + if err != nil { + fmt.Println(err) + return + } + + verification_target, err := os.Open("MysqL-5.7.9-win32.zip") + if err != nil { + fmt.Println(err) + return + } + + keyring, err := openpgp.ReadArmoredKeyRing(keyRingReader) + if err != nil { + fmt.Println("Read Armored Key Ring: " + err.Error()) + return + } + entity, err := openpgp.CheckArmoredDetachedSignature(keyring, verification_target, signature) + if err != nil { + fmt.Println("Check Detached Signature: " + err.Error()) + return + } + + fmt.Println(entity) +} diff --git a/encryptx/md5x/md5x.go b/encryptx/md5x/md5x.go new file mode 100644 index 0000000..355e2e9 --- /dev/null +++ b/encryptx/md5x/md5x.go @@ -0,0 +1,28 @@ +package md5x + +import ( + "crypto/md5" + "encoding/hex" + "io" + "os" +) + +// 字符串的md5 +func Md5String(value string) string { + m := md5.New() + m.Write([]byte(value)) + return hex.EncodeToString(m.Sum(nil)) +} + +// 文件的md5 +func Md5File(path string) (string, error) { + file, err := os.Open(path) + if err != nil { + return "", err + } + defer file.Close() + + hash := md5.New() + io.Copy(hash, file) + return hex.EncodeToString(hash.Sum(nil)), nil +} diff --git a/encryptx/rsax/rsax.go b/encryptx/rsax/rsax.go new file mode 100644 index 0000000..49901cf --- /dev/null +++ b/encryptx/rsax/rsax.go @@ -0,0 +1,119 @@ +package rsax + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/pem" + "os" +) + +//生成RSA私钥和公钥,保存到文件中 +// 与下面命令等价 +// 生成私钥 +// openssl genrsa -out rsa_private_key.pem 1024 +// 生成公钥 +// openssl rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem +// bits 证书大小 1024/2048 +func GenerateRSAKey(bits int) { + //GenerateKey函数使用随机数据生成器random生成一对具有指定字位数的RSA密钥 + //Reader是一个全局、共享的密码用强随机数生成器 + privateKey, err := rsa.GenerateKey(rand.Reader, bits) + if err != nil { + panic(err) + } + //保存私钥 + //通过x509标准将得到的ras私钥序列化为ASN.1 的 DER编码字符串 + X509PrivateKey := x509.MarshalPKCS1PrivateKey(privateKey) + //使用pem格式对x509输出的内容进行编码 + //创建文件保存私钥 + privateFile, err := os.Create("private.pem") + if err != nil { + panic(err) + } + defer privateFile.Close() + //构建一个pem.Block结构体对象 + privateBlock := pem.Block{Type: "RSA Private Key", Bytes: X509PrivateKey} + //将数据保存到文件 + pem.Encode(privateFile, &privateBlock) + + //保存公钥 + //获取公钥的数据 + publicKey := privateKey.PublicKey + //X509对公钥编码 + X509PublicKey, err := x509.MarshalPKIXPublicKey(&publicKey) + if err != nil { + panic(err) + } + //pem格式编码 + //创建用于保存公钥的文件 + publicFile, err := os.Create("public.pem") + if err != nil { + panic(err) + } + defer publicFile.Close() + //创建一个pem.Block结构体对象 + publicBlock := pem.Block{Type: "RSA Public Key", Bytes: X509PublicKey} + //保存到文件 + pem.Encode(publicFile, &publicBlock) +} + +// RSA加密 +// plainText 要加密的数据 +// path 公钥匙文件地址 +func RSA_Encrypt(plainText []byte, path string) []byte { + //打开文件 + file, err := os.Open(path) + if err != nil { + panic(err) + } + defer file.Close() + //读取文件的内容 + info, _ := file.Stat() + buf := make([]byte, info.Size()) + file.Read(buf) + //pem解码 + block, _ := pem.Decode(buf) + //x509解码 + + publicKeyInterface, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + panic(err) + } + //类型断言 + publicKey := publicKeyInterface.(*rsa.PublicKey) + //对明文进行加密 + cipherText, err := rsa.EncryptPKCS1v15(rand.Reader, publicKey, plainText) + if err != nil { + panic(err) + } + //返回密文 + return cipherText +} + +//RSA解密 +// cipherText 需要解密的byte数据 +// path 私钥文件路径 +func RSA_Decrypt(cipherText []byte, path string) []byte { + //打开文件 + file, err := os.Open(path) + if err != nil { + panic(err) + } + defer file.Close() + //获取文件内容 + info, _ := file.Stat() + buf := make([]byte, info.Size()) + file.Read(buf) + //pem解码 + block, _ := pem.Decode(buf) + //X509解码 + privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + panic(err) + } + //对密文进行解密 + plainText, _ := rsa.DecryptPKCS1v15(rand.Reader, privateKey, cipherText) + //返回明文 + return plainText +} diff --git a/encryptx/rsax/rsax_test.go b/encryptx/rsax/rsax_test.go new file mode 100644 index 0000000..d8d376d --- /dev/null +++ b/encryptx/rsax/rsax_test.go @@ -0,0 +1,18 @@ +package rsax_test + +import ( + "fmt" + "yunink/app/pkg/encryptx/rsax" + "testing" +) + +func TestRsa(t *testing.T) { + //加密 + data := []byte("hello world") + encrypt := rsax.RSA_Encrypt(data, "public.pem") + fmt.Println(string(encrypt)) + + // 解密 + decrypt := rsax.RSA_Decrypt(encrypt, "private.pem") + fmt.Println(string(decrypt)) +} diff --git a/encryptx/sha1x/sha1x.go b/encryptx/sha1x/sha1x.go new file mode 100644 index 0000000..f17cc86 --- /dev/null +++ b/encryptx/sha1x/sha1x.go @@ -0,0 +1,18 @@ +package sha1x + +import ( + "crypto/sha1" + "encoding/hex" +) + +func Sha1(s string) string { + h := sha1.New() + h.Write([]byte(s)) + + bs := h.Sum(nil) + return hex.EncodeToString(bs) + + // return fmt.Sprintf("%x", bs) + + // return res +} diff --git a/encryptx/sha1x/sha1x_test.go b/encryptx/sha1x/sha1x_test.go new file mode 100644 index 0000000..9cd24ea --- /dev/null +++ b/encryptx/sha1x/sha1x_test.go @@ -0,0 +1,13 @@ +package sha1x_test + +import ( + "fmt" + "testing" + sha1x "yunink/app/pkg/encryptx/sha1x" +) + +func TestSha1(t *testing.T) { + s := "sha1 this string" + r := sha1x.Sha1(s) + fmt.Println(r) +} diff --git a/encryptx/sha256x/sha256x.go b/encryptx/sha256x/sha256x.go new file mode 100644 index 0000000..fcfc0d8 --- /dev/null +++ b/encryptx/sha256x/sha256x.go @@ -0,0 +1,14 @@ +package sha256x + +import ( + "crypto/sha256" + "encoding/hex" +) + +//Sha256加密 +func Sha256(src string) string { + m := sha256.New() + m.Write([]byte(src)) + res := hex.EncodeToString(m.Sum(nil)) + return res +} diff --git a/httpx/curlx/curlx.go b/httpx/curlx/curlx.go new file mode 100644 index 0000000..b2ceca6 --- /dev/null +++ b/httpx/curlx/curlx.go @@ -0,0 +1,254 @@ +package curlx + +import ( + "bufio" + "compress/gzip" + "context" + "fmt" + "io" + "io/ioutil" + "net" + "net/http" + "time" + + "golang.org/x/net/proxy" +) + +/** + * Author: Yun + * Date: 2023年7月12日11:35:01 + */ + +type dataType string + +const ( + DataTypeForm dataType = "form" + DataTypeJson dataType = "json" + DataTypeXml dataType = "xml" + DataTypeEncode dataType = "encode" + DataTypeText dataType = "text" +) + +type method string + +const ( + MethodGet method = "GET" + MethodPost method = "POST" +) + +type CurlParams struct { + Url string + Method method // GET/POST + Params interface{} + Headers map[string]interface{} + Cookies interface{} + DataType dataType // FORM,JSON,XML +} + +var ( + defaultTimeOut = 180 // 默认(最长超时时间) + // 默认的transport + transport http.Transport = http.Transport{ + // Dial: func(netw, addr string) (net.Conn, error) { + // // 这里指定域名访问的IP + // // if addr == "api.hk.blueoceanpay.com:443" { + // // addr = "47.56.200.21:443" + // // } + // conn, err := net.DialTimeout(netw, addr, time.Second*time.Duration(timeOut)) // 设置建立连接超时 + // if err != nil { + // return nil, err + // } + // // conn.RemoteAddr().String() + // conn.SetDeadline(time.Now().Add(time.Second * time.Duration(timeOut))) // 设置发送接收数据超时 + // return conn, nil + // }, + // ResponseHeaderTimeout: time.Second * time.Duration(defaultTimeOut), // 响应超时 + DisableKeepAlives: false, // 短连接(默认是使用长连接,连接过多时会造成服务器拒绝服务问题) + MaxIdleConns: 0, // 所有host的连接池最大连接数量,默认无穷大 + MaxIdleConnsPerHost: 5, // 每个host的连接池最大空闲连接收,默认2 + MaxConnsPerHost: 0, // 每个host的最大连接数量 + IdleConnTimeout: time.Second * 2, // 空闲连接超时关闭的时间 + } + // client = &http.Client{} + +) + +type DialContext func(ctx context.Context, network, addr string) (net.Conn, error) + +type curlx struct { + transport *http.Transport +} + +func NewCurlx() *curlx { + return &curlx{ + transport: &transport, + } +} + +/** + * 使用Socks5代理 + */ +func (c *curlx) WithSocks5(address string) error { + baseDialer := &net.Dialer{ + Timeout: 180 * time.Second, + KeepAlive: 180 * time.Second, + } + dialSocksProxy, err := proxy.SOCKS5("tcp", address, nil, baseDialer) + if err != nil { + fmt.Println("proxy.SOCKS5 err", err) + return err + } + dialContext := (baseDialer).DialContext + if contextDialer, ok := dialSocksProxy.(proxy.ContextDialer); ok { + dialContext = contextDialer.DialContext + } + c.transport.DialContext = dialContext + return nil +} + +/** + * 不校验HTTPS证书 + */ +func (c *curlx) WithInsecureSkipVerify() { + c.transport.TLSClientConfig.InsecureSkipVerify = true +} + +/** + * 设置超时时间,单位秒 + */ +func (c *curlx) WithTimeout(timeout int) { + c.transport.ResponseHeaderTimeout = time.Second * time.Duration(timeout) +} + +/** + * 简单请求 + */ +func (c *curlx) Send(ctx context.Context, p *CurlParams) (res string, httpcode int, err error) { + response, err := c.sendExec(ctx, p) + if err != nil { + return "", -1, err + } + + defer response.Body.Close() // 处理完关闭 + + // stdout := os.Stdout // 将结果定位到标准输出,也可以直接打印出来,或定位到其他地方进行相应处理 + // _, err = io.Copy(stdout, response.Body) // 将第二个参数拷贝到第一个参数,直到第二参数到达EOF或发生错误,返回拷贝的值 + status := response.StatusCode // 获取状态码,正常是200 + + var body []byte + switch response.Header.Get("Content-Encoding") { + case "gzip": + reader, err := gzip.NewReader(response.Body) + if err != nil { + return "", status, err + } + for { + buf := make([]byte, 1024) + n, err := reader.Read(buf) + if err != nil && err != io.EOF { + panic(err) + } + if n == 0 { + break + } + body = append(body, buf...) + } + default: + body, _ = ioutil.ReadAll(response.Body) + } + return string(body), status, nil +} + +/** + * 执行发送 + * 注意:外部使用需要加这一句 defer response.Body.Close() + */ +func (c *curlx) sendExec(ctx context.Context, p *CurlParams) (resp *http.Response, err error) { + client := &http.Client{ + Timeout: time.Second * time.Duration(defaultTimeOut), // 设置该条连接的超时 + Transport: c.transport, // + } + + err = p.parseMethod() + if err != nil { + return nil, err + } + + // 判断和处理url + err = p.parseUrl() + if err != nil { + return nil, err + } + + // 处理参数 + reqParams, err := p.parseParams() + if err != nil { + return nil, err + } + + // 初始化句柄 + request, err := http.NewRequest( // 提交请求 用指定的方法 + string(p.Method), + p.Url, + reqParams, + ) + if err != nil { + return nil, err + } + + // 这里指定要访问的HOST,到时候服务器获取主机是获取到这个 + // request.Host = "api.hk.blueoceantech.co" + + // 设置上下文控制 + request = request.WithContext(ctx) + + // 处理请求头 + p.parseHeaders(request) + + // 处理Cookies + p.parseCookies(request) + + // 发起请求 + response, err := client.Do(request) + if err != nil { + return nil, err + } + return response, nil +} + +/** + * 流式请求 + */ +func (c *curlx) SendChan(ctx context.Context, p *CurlParams) (<-chan string, error) { + + data := make(chan string, 1000) + + go func() { + defer close(data) + + ctx, cancel := context.WithTimeout(context.Background(), time.Minute*30) + defer cancel() + + response, err := c.sendExec(ctx, p) + if err != nil { + return + } + defer response.Body.Close() // 处理完关闭 + + scanner := bufio.NewScanner(response.Body) + for scanner.Scan() { + text := scanner.Text() + if text == "" { + continue + } + // 30min超时 + select { + case <-ctx.Done(): + return + case data <- text: + } + } + }() + + return data, nil +} diff --git a/httpx/curlx/request.go b/httpx/curlx/request.go new file mode 100644 index 0000000..8ce61e9 --- /dev/null +++ b/httpx/curlx/request.go @@ -0,0 +1,218 @@ +package curlx + +import ( + "bytes" + "encoding/json" + "encoding/xml" + "errors" + "io" + "net/http" + "net/url" + "strconv" + "strings" +) + +/** + * 处理请求类型 + */ +func (p *CurlParams) parseMethod() error { + if p.Method == "" { + return errors.New("请求类型不能为空") + } + return nil +} + +/** + * 处理URL + */ +func (p *CurlParams) parseUrl() error { + _, err := url.Parse(p.Url) + if err != nil { + return err + } + return nil +} + +/** + * 处理请求头Header + */ +func (p *CurlParams) parseHeaders(r *http.Request) { + if p.Headers != nil { + if r.Header.Get("User-Agent") == "" { + r.Header.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:87.0) Gecko/20100101 Firefox/87.0 Send By Golang") + } + for k, v := range p.Headers { + if vv, ok := v.(string); ok { + r.Header.Set(k, vv) + continue + } + if vv, ok := v.([]string); ok { + for _, vvv := range vv { + r.Header.Add(k, vvv) + } + } + } + } +} + +/** + * 处理请求参数 + */ +func (p *CurlParams) parseParams() (str io.Reader, err error) { + err = nil + + // 初始化(如未初始化) + if p.Headers == nil { + p.Headers = make(map[string]interface{}) + } + + if p.Params != nil { + if p.DataType == DataTypeJson { + // 判断是否存在 + if _, ok := p.Headers["Content-Type"]; !ok { + p.Headers["Content-Type"] = "application/json" + } + strParam, ok := p.Params.(string) + if ok { + return bytes.NewReader([]byte(strParam)), nil + } + b, err := json.Marshal(p.Params) + if err == nil { + return bytes.NewReader(b), nil + } + } else if p.DataType == DataTypeXml { + if _, ok := p.Headers["Content-Type"]; !ok { + p.Headers["Content-Type"] = "application/xml" + } + var string_data string + if value, ok := p.Params.(string); ok { + string_data = string(value) + } else { + var by []byte + by, err = xml.Marshal(p.Params) + if err != nil { + return + } + string_data = string(by) + } + return strings.NewReader(string_data), nil + // switch p.Params.(type) { + // case map[string]string: + // // 请求参数转换成xml结构 + // b, err := goutils.Map2XML(p.Params.(map[string]string)) + // if err == nil { + // return bytes.NewBuffer(b) + // } + // default: + // b, err := xml.Marshal(p.Params) + // if err == nil { + // return bytes.NewBuffer(b) + // } + // } + } else if p.DataType == DataTypeText { + if _, ok := p.Headers["Content-Type"]; !ok { + p.Headers["Content-Type"] = "text/plain" + } + + var string_data string + if value, ok := p.Params.(string); ok { + string_data = string(value) + } else { + err = errors.New("TEXT类型的参数仅支持字符串") + return + } + + return strings.NewReader(string_data), nil + } else { + // FORM,"" + if _, ok := p.Headers["Content-Type"]; !ok { + p.Headers["Content-Type"] = "application/x-www-form-urlencoded" + } + + // 判断需要map[string]interface{}类型 + paramValue, ok := p.Params.(map[string]interface{}) + if !ok { + return strings.NewReader(""), errors.New("参数需map[string]interface{}") + } + + values := url.Values{} + for k, v := range paramValue { + // 字符串 + if v_string, ok := v.(string); ok { + values.Set(k, v_string) + } + // 字符串切片 + if vv, ok := v.([]string); ok { + for _, vvv := range vv { + values.Add(k+"[]", vvv) + } + } + // int转string + if v_int, ok := v.(int); ok { + values.Set(k, strconv.Itoa(v_int)) + } + // int64转string + if v_int64, ok := v.(int64); ok { + values.Set(k, strconv.FormatInt(v_int64, 10)) + } + // float32转string + if v_float32, ok := v.(float32); ok { + values.Set(k, strconv.FormatFloat(float64(v_float32), 'f', -1, 32)) + } + // float64转string + if v_float64, ok := v.(float64); ok { + values.Set(k, strconv.FormatFloat(v_float64, 'f', -1, 64)) + } + } + return strings.NewReader(values.Encode()), nil + } + + } + return +} + +/** + * 处理Cookie + */ +func (p *CurlParams) parseCookies(r *http.Request) { + switch p.Cookies.(type) { + case string: + cookies := p.Cookies.(string) + r.Header.Add("Cookie", cookies) + case map[string]string: + cookies := p.Cookies.(map[string]string) + for k, v := range cookies { + r.AddCookie(&http.Cookie{ + Name: k, + Value: v, + }) + } + case []*http.Cookie: + cookies := p.Cookies.([]*http.Cookie) + for _, cookie := range cookies { + r.AddCookie(cookie) + } + } +} + +// func (r *Request) parseQuery() { +// switch r.opts.Query.(type) { +// case string: +// str := r.opts.Query.(string) +// r.req.URL.RawQuery = str +// case map[string]interface{}: +// q := r.req.URL.Query() +// for k, v := range r.opts.Query.(map[string]interface{}) { +// if vv, ok := v.(string); ok { +// q.Set(k, vv) +// continue +// } +// if vv, ok := v.([]string); ok { +// for _, vvv := range vv { +// q.Add(k, vvv) +// } +// } +// } +// r.req.URL.RawQuery = q.Encode() +// } +// } diff --git a/httpx/curlx/resopnse.go b/httpx/curlx/resopnse.go new file mode 100644 index 0000000..11c7e0c --- /dev/null +++ b/httpx/curlx/resopnse.go @@ -0,0 +1,124 @@ +package curlx + +import ( + "net" + "net/http" + "strings" + + "github.com/tidwall/gjson" +) + +// Response response object +type Response struct { + resp *http.Response + req *http.Request + body []byte + err error +} + +// ResponseBody response body +type ResponseBody []byte + +// String fmt outout +func (r ResponseBody) String() string { + return string(r) +} + +// Read get slice of response body +func (r ResponseBody) Read(length int) []byte { + if length > len(r) { + length = len(r) + } + + return r[:length] +} + +// GetContents format response body as string +func (r ResponseBody) GetContents() string { + return string(r) +} + +// GetRequest get request object +func (r *Response) GetRequest() *http.Request { + return r.req +} + +// GetBody parse response body +func (r *Response) GetBody() (ResponseBody, error) { + return ResponseBody(r.body), r.err +} + +// GetParsedBody parse response body with gjson +func (r *Response) GetParsedBody() (*gjson.Result, error) { + pb := gjson.ParseBytes(r.body) + + return &pb, nil +} + +// GetStatusCode get response status code +func (r *Response) GetStatusCode() int { + return r.resp.StatusCode +} + +// GetReasonPhrase get response reason phrase +func (r *Response) GetReasonPhrase() string { + status := r.resp.Status + arr := strings.Split(status, " ") + + return arr[1] +} + +// IsTimeout get if request is timeout +func (r *Response) IsTimeout() bool { + if r.err == nil { + return false + } + netErr, ok := r.err.(net.Error) + if !ok { + return false + } + if netErr.Timeout() { + return true + } + + return false +} + +// GetHeaders get response headers +func (r *Response) GetHeaders() map[string][]string { + return r.resp.Header +} + +// GetHeader get response header +func (r *Response) GetHeader(name string) []string { + headers := r.GetHeaders() + for k, v := range headers { + if strings.ToLower(name) == strings.ToLower(k) { + return v + } + } + + return nil +} + +// GetHeaderLine get a single response header +func (r *Response) GetHeaderLine(name string) string { + header := r.GetHeader(name) + if len(header) > 0 { + return header[0] + } + + return "" +} + +// HasHeader get if header exsits in response headers +func (r *Response) HasHeader(name string) bool { + headers := r.GetHeaders() + for k := range headers { + if strings.ToLower(name) == strings.ToLower(k) { + return true + } + } + + return false +} diff --git a/httpx/post.go b/httpx/post.go new file mode 100644 index 0000000..5aa5af3 --- /dev/null +++ b/httpx/post.go @@ -0,0 +1,3 @@ +package httpx + +func Post(){} diff --git a/jwtx/jwtx.go b/jwtx/jwtx.go new file mode 100644 index 0000000..345d7d8 --- /dev/null +++ b/jwtx/jwtx.go @@ -0,0 +1,83 @@ +package jwtx + +import ( + "errors" + "time" + + "github.com/dgrijalva/jwt-go" +) + +var ( + TokenExpired error = errors.New("token is expired") + TokenNotValidYet error = errors.New("token not active yet") + TokenMalformed error = errors.New("that's not even a token") + TokenInvalid error = errors.New("couldn't handle this token") + TokenGenerateFailed error = errors.New("generate token failed!") + + // defaultSignKey string = "hcVI@Nm4cjhjPVvCpL5^1%jY" + defaultSignKey = "arcsine(0.728)≈46°43′8″" + + RedisUserKey string = "user:%d" +) + +type jwtx struct { + SigningKey []byte +} + +type Claims struct { + UserId int64 `json:"user_id"` + + jwt.StandardClaims +} + +func NewJWT() *jwtx { + return &jwtx{ + []byte(defaultSignKey), + } +} + +// 仅解析Token +func (j *jwtx) Decode(tokenString string) (*Claims, error) { + token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) { + return j.SigningKey, nil + }) + if err != nil { + if ve, ok := err.(*jwt.ValidationError); ok { + if ve.Errors&jwt.ValidationErrorMalformed == jwt.ValidationErrorMalformed { + return nil, TokenMalformed + } else if ve.Errors&jwt.ValidationErrorExpired == jwt.ValidationErrorExpired { + // Token is expired + return nil, TokenExpired + } else if ve.Errors&jwt.ValidationErrorNotValidYet == jwt.ValidationErrorNotValidYet { + return nil, TokenNotValidYet + } else { + return nil, TokenInvalid + } + } + } + claims, ok := token.Claims.(*Claims) + if !ok || !token.Valid { + return nil, TokenInvalid + } + return claims, nil +} + +// 根据参数生成和设置 token +func (j *jwtx) Encode(claims *Claims, expiresTime time.Time) (string, error) { + + expSecond := expiresTime.Unix() + nowSecond := time.Now().Unix() + + if expSecond-nowSecond < 0 { + return "", errors.New("时间无效") + } + + // 设置有效时间 + claims.StandardClaims.ExpiresAt = expiresTime.Unix() + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + tokenString, err := token.SignedString(j.SigningKey) + if err != nil { + return "", TokenGenerateFailed + } + return tokenString, nil +} diff --git a/langx/code.go b/langx/code.go new file mode 100644 index 0000000..a1b5607 --- /dev/null +++ b/langx/code.go @@ -0,0 +1,6 @@ +package langx + +var transCode = map[MessageKey]int{ + "login_success": 200, + "error": 400, +} diff --git a/langx/consts.go b/langx/consts.go new file mode 100644 index 0000000..863c157 --- /dev/null +++ b/langx/consts.go @@ -0,0 +1,15 @@ +package langx + +const ( + Success MessageKey = "success" + Error MessageKey = "error" + ErrorParam MessageKey = "error_param" + ErrorSaveDb MessageKey = "error_save_db" + ErrorMysql MessageKey = "error_mysql" + ErrorElasticSearch MessageKey = "error_elastic_search" + ErrorRocketMq MessageKey = "error_rocketmq" + ErrorSystem MessageKey = "error_system" // 系统错误 + ErrorMsgUnparse MessageKey = "error_msg_unparse" // 参数无法解析 + ErrorYouNotNowUser MessageKey = "error_you_not_now_user" // 该用户不是当前用户 + ErrorCloseWebSocket MessageKey = "error_close_websocket" // 遇到错误主动关闭Ws +) diff --git a/langx/langx.go b/langx/langx.go new file mode 100644 index 0000000..5935e2f --- /dev/null +++ b/langx/langx.go @@ -0,0 +1,36 @@ +package langx + +import ( + "fmt" + "strings" +) + +type MessageKey string + +// 获取翻译 +func GetTrans(key MessageKey, arr map[string]string) (code int, str string) { + code = GetCode(key) + str = GetMsg(key, arr) + return +} + +// 根据Key获取code +func GetCode(key MessageKey) int { + code, ok := transCode[key] + if !ok { + return 0 + } + return code +} + +// 拼接回复 +func GetMsg(key MessageKey, arr map[string]string) string { + str, ok := transZh[key] + if !ok { + return string(key) + } + for k, v := range arr { + str = strings.ReplaceAll(str, fmt.Sprintf("#%s#", k), v) + } + return str +} diff --git a/langx/zh.go b/langx/zh.go new file mode 100644 index 0000000..06d8504 --- /dev/null +++ b/langx/zh.go @@ -0,0 +1,6 @@ +package langx + +var transZh = map[MessageKey]string{ + "login_success": "成功", + "username": "你好 #name#", +} diff --git a/loggerx/delete.go b/loggerx/delete.go new file mode 100644 index 0000000..cb2625c --- /dev/null +++ b/loggerx/delete.go @@ -0,0 +1,5 @@ +package loggerx + + + + diff --git a/loggerx/loggerx.go b/loggerx/loggerx.go new file mode 100644 index 0000000..039a2f6 --- /dev/null +++ b/loggerx/loggerx.go @@ -0,0 +1,192 @@ +package loggerx + +// author:黄新云 +// lastTime:2023年6月30日21:28:04 +// desc: 日志封装类 + +// 优化方向 +// 可以写盘的时候添加缓存,但是有个难点就是如果采用缓存的方式必须保证这个是最后关闭的,如果不是则退出的时候会丢失日志(写在缓存里还没刷盘) + +// TODO:自动清除过期日志 + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "log" + "os" + "path/filepath" + "runtime" + "strings" + "sync" + + // "sync_log/global" + "time" + + "github.com/gin-gonic/gin" +) + +// 需要实现io.Writer接口 +type Logger struct { + file *os.File + fileName string + mu sync.Mutex + // channel string +} + +func InitLogger(prefix string) *Logger { + l := &Logger{} + + // 打开文件 + err := l.createNewFile(true) + if err != nil { + panic(err) + } + log.SetOutput(l) + log.SetFlags(log.LstdFlags | log.Llongfile | log.Lmicroseconds) // log.Lshortfile | log.LUTC + + // 保存Gin日志写入到文件+控制台 + gin.DefaultWriter = io.MultiWriter(l, os.Stdout) + gin.DefaultErrorWriter = io.MultiWriter(l, os.Stdout) + + // 赋值前缀 + if prefix != "" { + log.SetPrefix(fmt.Sprintf("[%s]", prefix)) + } + return l +} + +// func (l *Logger) Channel(ch string) (r *Logger) { +// rr := l +// rr.channel = ch +// return rr +// } + +// 超时删除 + +func (l *Logger) Write(b []byte) (n int, err error) { + + if l.file == nil { + // 新建一个file连接 + l.createNewFile(false) + } + if l.fileName != nowFileName() { + l.createNewFile(false) + } + + n, err = l.file.Write(b) + if err == nil && n < len(b) { + err = io.ErrShortWrite + } + if err != nil { + // 强制更新 + l.createNewFile(true) + } + + return n, err +} + +// 获取最新的文件名 +func nowFileName() string { + // ioc, _ := time.LoadLocation("Asia/Shanghai") + // timeDir := fmt.Sprint(time.Now().In(ioc).Format("2006/01/02/15")) // 2006-01-02 15:04:05 + timeDir := fmt.Sprint(time.Now().Local().Format("2006/01/02")) // 2006-01-02 15:04:05 + path := "./log/" + timeDir + ".log" + // fmt.Println(filepath.Abs(path)) + return path +} + +// 新建文件 +func (l *Logger) createNewFile(isMust bool) error { + l.mu.Lock() + defer l.mu.Unlock() + + fileName := nowFileName() + + if !isMust { + if l.file != nil && l.fileName == fileName { + return nil + } + } + + dir, _ := filepath.Split(fileName) // 识别目录与文件 + os.MkdirAll(dir, os.ModePerm) // 创建多层目录,如果存在不会报错 + + // 打开该文件,如果不存在则创建 + file, err := os.OpenFile(fileName, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644) + if err != nil { + // 打开失败,尝试创建 + fmt.Println("打开日志文件失败") + return err + } + // 关闭原来的文件 + if l.file != nil { + l.closeFile() + } + l.file = file + l.fileName = fileName + return nil +} + +// 关闭文件 +func (l *Logger) closeFile() error { + l.file.Sync() + return l.file.Close() +} + +func (l *Logger) Info(ctx context.Context, v ...any) { + l.logger(ctx, "info", v...) +} + +func (l *Logger) Infof(ctx context.Context, format string, v ...any) { + s := fmt.Sprintf(format, v...) + l.logger(ctx, "info", s) +} + +func (l *Logger) Error(ctx context.Context, v ...any) { + l.logger(ctx, "error", v...) +} + +func (l *Logger) Errorf(ctx context.Context, format string, v ...any) { + s := fmt.Sprintf(format, v...) + l.logger(ctx, "error", s) +} + +// 添加固定的内容 +// func (l *Logger) ContextWithFields(ctx context.Context, v ...any) { +// l.logger(ctx, "add", v...) +// } +// func (l *Logger) Field(key,val string) { +// l.logger(nil, "add", key,val) +// } + +func (l *Logger) logger(ctx context.Context, action string, v ...any) { + pc, file, line, _ := runtime.Caller(2) + // fmt.Println("runtime.Caller", pc, file, line, ok) + + funcName := runtime.FuncForPC(pc).Name() + funcName = filepath.Ext(funcName) + funcName = strings.TrimPrefix(funcName, ".") + + by, _ := json.Marshal(v) + + nowTime := time.Now().Local().Format("20060102 15:04:05.000000") + + traceId, _ := ctx.Value("trace_id").(string) + + writeStr := "[" + action + "]" + nowTime + " " + file + ":" + fmt.Sprintf("%d", line) + " " + funcName + " gid:" + getGID() + " " + traceId + " @data@: " + string(by) + "\n\n" + + l.Write([]byte(writeStr)) + + // log.Println("" + string(by)) +} + +func getGID() string { + b := make([]byte, 64) + b = b[:runtime.Stack(b, false)] + b = bytes.TrimPrefix(b, []byte("goroutine ")) + b = b[:bytes.IndexByte(b, ' ')] + return string(b) +} diff --git a/loggerx/loggerx_test.go b/loggerx/loggerx_test.go new file mode 100644 index 0000000..bfc097e --- /dev/null +++ b/loggerx/loggerx_test.go @@ -0,0 +1,19 @@ +package loggerx_test + +import ( + "context" + "testing" + "yunink/app/pkg/loggerx" +) + +// func TestMain(m *testing.M) { + +// } + +func TestLogger(t *testing.T) { + + l := loggerx.InitLogger("profix") + + l.Error(context.Background(), "test error") + +} diff --git a/loggerx/readme.md b/loggerx/readme.md new file mode 100644 index 0000000..4c0c7a0 --- /dev/null +++ b/loggerx/readme.md @@ -0,0 +1,12 @@ + +# 简介 + +这个是基于原生log实现的日志存储。 + +```go + +log.Println("ddddd") + +``` + + diff --git a/panicx/panicx.go b/panicx/panicx.go new file mode 100644 index 0000000..9ddf3f9 --- /dev/null +++ b/panicx/panicx.go @@ -0,0 +1,31 @@ +package panicx + +import ( + // "errors" + // "bop_notify/global" + "errors" + "fmt" + "log" + + // "os" + "runtime/debug" + // "time" +) + +func PanicCatch(a *error) (b bool) { + // *a = errors.New("fff") + errs := recover() + // fmt.Printf("这是全局捕捉的 %T \n", errs) + if errs == nil { + return true + } + + fmt.Println("Recover Error:", errs) + log.Println("errMsg", errs) + log.Println("errStack", string(debug.Stack())) + + *a = errors.New("recover捕捉到异常") + + return false + +} diff --git a/randomx/randomx.go b/randomx/randomx.go new file mode 100644 index 0000000..25c313c --- /dev/null +++ b/randomx/randomx.go @@ -0,0 +1,43 @@ +package randomx + +import ( + "errors" + "math/rand" + "time" +) + +// 生成指定位数的随机字符串 +func RandomStr(length int) (string, error) { + if length <= 0 { + return "", errors.New("位数必须大于0") + } + str := "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ黄新云" + bytes := []rune(str) + + // 初始化参数,并赋予空间 + var result = make([]rune, 0, length) + + // 初始化随机因子 + r := rand.New(rand.NewSource(time.Now().UnixNano())) + + for i := 0; i < length; i++ { + result = append(result, bytes[r.Intn(len(bytes))]) + } + + return string(result), nil +} + +// 生成指定空间的随机数 +func RandomInt(min, max int) (int, error) { + c := max - min + if c <= 0 { + return 0, errors.New("max值需要大于min") + } + + // 初始化随机因子 + r := rand.New(rand.NewSource(time.Now().UnixNano())) + + res := r.Intn(c) + + return min + res, nil +} diff --git a/randomx/randomx_test.go b/randomx/randomx_test.go new file mode 100644 index 0000000..16a79c6 --- /dev/null +++ b/randomx/randomx_test.go @@ -0,0 +1,30 @@ +package randomx_test + +import ( + "testing" + "yunink/app/pkg/randomx" +) + +func TestRandomInt(t *testing.T) { + i, err := randomx.RandomInt(100, 101) + if err != nil { + t.Fatal(err.Error()) + } + if i >= 100 && i <= 101 { + t.Log("正常") + } else { + t.Fatal("不正常", i) + } +} + +func TestRandomStr(t *testing.T) { + str, err := randomx.RandomStr(10) + if err != nil { + t.Fatal(err.Error()) + } + if len(str) == 10 { + t.Log("正常") + } else { + t.Fatal("不正常", str) + } +} diff --git a/response/response.go b/response/response.go new file mode 100644 index 0000000..06c8962 --- /dev/null +++ b/response/response.go @@ -0,0 +1,90 @@ +package response + +// Author: Yun +// Version: 2023年6月12日19:25:54 + +import ( + "math" + "net/http" + "yunink/app/pkg/langx" + + "github.com/gin-gonic/gin" +) + +type pagination struct { + Page int `json:"page"` + Size int `json:"size"` + TotalPage int `json:"total_page"` + TotalCount int `json:"total_count"` +} + +// 分页查询 +func PageToOffset(page, size int) (offset int, limit int) { + if page <= 0 { + page = 1 + } + offset = (page - 1) * size + limit = size + if limit == 0 { + // 默认查10条 + limit = 10 + } + return +} + +// 计算分页 +func GetPagination(page, size, total_count int) pagination { + pa := pagination{} + total_page := int(math.Ceil(float64(total_count) / float64(size))) + pa.Page = page + pa.Size = size + pa.TotalPage = total_page + pa.TotalCount = total_count + return pa +} + +// Msg格式化响应 +func FormatMessage(ctx *gin.Context, message langx.MessageKey, mag_param map[string]string, data interface{}) { + code, msg := langx.GetTrans(message, mag_param) + Response(ctx, code, msg, data) +} + +// 成功的响应 +func Success(ctx *gin.Context, data interface{}, info ...interface{}) { + Response(ctx, 200, "请求成功", data, info) +} + +func SuccessWithPage(ctx *gin.Context, data interface{}, page pagination) { + Response(ctx, 200, "请求成功", data, page) +} + +// 失败的响应 +func Error(ctx *gin.Context, msg string) { + Response(ctx, 400, msg, "") +} + +// 基础的响应 +func Response(ctx *gin.Context, code int, message string, data interface{}, other ...interface{}) { + + res := make(map[string]interface{}) + + res["code"] = code + res["message"] = message + + // data没有值则为空 + if data != "" { + res["data"] = data + } + + // 分页的内容 + for _, value := range other { + pagination, ok := value.(pagination) + if ok { + res["pagination"] = pagination + } + } + + // log.Print(res) + + ctx.JSON(http.StatusOK, res) +} diff --git a/runtimex/runtimex.go b/runtimex/runtimex.go new file mode 100644 index 0000000..75edfe9 --- /dev/null +++ b/runtimex/runtimex.go @@ -0,0 +1,23 @@ +package runtimex + +import ( + "bytes" + "runtime" + "strconv" +) + +// 2023年6月30日19:26:09 + +// 获取goroutine_id +func GetGid() (gid uint64) { + b := make([]byte, 64) + b = b[:runtime.Stack(b, false)] + b = bytes.TrimPrefix(b, []byte("goroutine ")) + b = b[:bytes.IndexByte(b, ' ')] + n, err := strconv.ParseUint(string(b), 10, 64) + if err != nil { + panic(err) + } + return n +} + diff --git a/runtimex/runtimex_test.go b/runtimex/runtimex_test.go new file mode 100644 index 0000000..da6d34c --- /dev/null +++ b/runtimex/runtimex_test.go @@ -0,0 +1,11 @@ +package runtimex_test + +import ( + "testing" + "yunink/app/pkg/runtimex" +) + +func TestGetgId(t *testing.T) { + t.Log("测试获取goroutine_id") + t.Log("goroutine_id:", runtimex.GetGid()) +} diff --git a/signx/signx.go b/signx/signx.go new file mode 100644 index 0000000..60c0c37 --- /dev/null +++ b/signx/signx.go @@ -0,0 +1,71 @@ +package signx + +import ( + "crypto/md5" + "errors" + "fmt" + "sort" + "strings" + "yunink/app/pkg/convx" +) + +// 缺点 +// 1.值必须是字符串,数组/对象这些不允许,或者需要通过一定规则转成字符串 + +// 签名规则 +// 1.除掉不参与签名的用户参数 +// 1.所有参数的key根据字典排序 +// 2.根据key=val&key2=val2的格式组装字符串 +// 3.后面拼接用户签名key `&key=xxxx`得道新字符串 +// 4.把新字符串用md5加密并转大写的道签名的值 +func GetSign(params map[string]interface{}, key string) (sign string, err error) { + delete(params, "platform_key") + delete(params, "sign") + delete(params, "key") + + // 排序 + keys := make([]string, len(params)) + i := 0 + for k := range params { + keys[i] = k + i++ + } + sort.Strings(keys) + var str string + for k, v := range keys { + if k > 0 { + str = str + "&" + } + val, err := convx.ToString(params[v]) + if err != nil { + return "", err + } + str = str + v + "=" + val + } + str = str + "&key=" + key + data := []byte(str) + has := md5.Sum(data) + md5str1 := fmt.Sprintf("%x", has) // []byte转16进制 + sign = strings.ToUpper(md5str1) + return sign, nil +} + +func VerifySign(params map[string]interface{}, key string) error { + sign, ok := params["sign"] + if !ok { + return errors.New("签名参数不存在") + } + sitn_str, ok := sign.(string) + if !ok { + return errors.New("sign必须是字符串") + } + g_sign, err := GetSign(params, key) + if err != nil { + return err + } + if sitn_str != g_sign { + return errors.New("验签失败") + } + + return nil +} diff --git a/tencentx/wechatx/accessToken.go b/tencentx/wechatx/accessToken.go new file mode 100644 index 0000000..fc9b448 --- /dev/null +++ b/tencentx/wechatx/accessToken.go @@ -0,0 +1,37 @@ +package wechatx + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" +) + +func (w *wechat) GetAccessToken() { + appid := "" + secret := "" + url := fmt.Sprintf("%s/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s", WechatHost, appid, secret) + + resp, err := http.Get(url) + if err != nil { + // + } + defer resp.Body.Close() + + by, err := ioutil.ReadAll(resp.Body) + if err != nil { + // + } + var response struct { + ErrCode int64 `json:"errcode"` + ErrMsg string `json:"errmsg"` + AccessToken string `json:"access_token"` + ExpiresIn int `json:"expires_in"` + } + json.Unmarshal(by, &response) + + if response.ErrCode != 0 { + // + } + +} diff --git a/tencentx/wechatx/consts.go b/tencentx/wechatx/consts.go new file mode 100644 index 0000000..03c4d9d --- /dev/null +++ b/tencentx/wechatx/consts.go @@ -0,0 +1,13 @@ +package wechatx + +import "encoding/xml" + +type WechatMessage struct { + XMLName xml.Name `xml:"xml"` + ToUserName string `json:"to_user_name" xml:"ToUserName"` // 接收人 + FromUserName string `json:"from_user_name" xml:"FromUserName"` // 发送人 + CreateTime int64 `json:"create_time" xml:"CreateTime"` // 创建时间 + MsgType string `json:"msg_type" xml:"MsgType"` // 消息类型 text + Content string `json:"content" xml:"Content"` // 内容 + MsgId string `json:"msg_id" xml:"MsgId"` +} diff --git a/tencentx/wechatx/cryptx/cryptx.go b/tencentx/wechatx/cryptx/cryptx.go new file mode 100644 index 0000000..5ec07fe --- /dev/null +++ b/tencentx/wechatx/cryptx/cryptx.go @@ -0,0 +1,361 @@ +package cryptx + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/sha1" + "encoding/base64" + "encoding/binary" + "encoding/xml" + "errors" + "fmt" + "math/rand" + "sort" + "strings" + "time" +) + +const ( + LETTERDIGITS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + LD_COUNT = 62 +) + +const ( + WXBizMsgCrypt_OK = 0 + WXBizMsgCrypt_ValidateSignature_Error = -40001 + WXBizMsgCrypt_ParseXml_Error = -40002 + WXBizMsgCrypt_ComputeSignature_Error = -40003 + WXBizMsgCrypt_IllegalAesKey = -40004 + WXBizMsgCrypt_ValidateAppid_Error = -40005 + WXBizMsgCrypt_EncryptAES_Error = -40006 + WXBizMsgCrypt_DecryptAES_Error = -40007 + WXBizMsgCrypt_IllegalBuffer = -40008 + WXBizMsgCrypt_EncodeBase64_Error = -40009 + WXBizMsgCrypt_DecodeBase64_Error = -40010 + WXBizMsgCrypt_GenReturnXml_Error = -40011 +) + +var ( + letterDigitArr = []byte(LETTERDIGITS) +) + +type ( + XMLParse struct { + XMLtmpl string + } + + WXBizMsgCrypt interface { + EncryptMsg(sReplyMsg string, sNonce string, timestamp int64) (int, string) + DecryptMsg(sPostData, sMsgSignature string, sTimeStamp int64, sNonce string) (int, string) + } + + wxBizMsgCrypt struct { + key string + token string + appid string + } +) + +// sToken: 公众平台上,开发者设置的Token +// sEncodingAESKey: 公众平台上,开发者设置的EncodingAESKey +// sAppId: 企业号的AppId +func NewWXBizMsgCrypt(sToken, sEncodingAESKey, sAppId string) (WXBizMsgCrypt, error) { + decodeBytes, err := base64.StdEncoding.DecodeString(sEncodingAESKey + "=") + if err != nil { + return nil, errors.New("EncodingAESKey err") + } + //assert len(key) == 32 + if len(decodeBytes) != 32 { + return nil, errors.New("EncodingAESKey length err") + } + c := &wxBizMsgCrypt{ + key: string(decodeBytes), + token: sToken, + appid: sAppId, + } + return c, nil +} + +// 将公众号回复用户的消息加密打包 +// sReplyMsg: 企业号待回复用户的消息,xml格式的字符串 +// sTimeStamp: 时间戳,可以自己生成,也可以用URL参数的timestamp,如为None则自动用当前时间 +// sNonce: 随机串,可以自己生成,也可以用URL参数的nonce +// sEncryptMsg: 加密后的可以直接回复用户的密文,包括msg_signature, timestamp, nonce, encrypt的xml格式的字符串, +// return:成功0,sEncryptMsg,失败返回对应的错误码None +func (c *wxBizMsgCrypt) EncryptMsg(sReplyMsg string, sNonce string, timestamp int64) (int, string) { + pc := NewPrpcrypt(c.key) + ret, encryptBytes := pc.encrypt([]byte(sReplyMsg), c.appid) + if ret != WXBizMsgCrypt_OK { + return ret, "" + } + if timestamp <= 0 { + timestamp = time.Now().Unix() + } + encrypt_text := string(encryptBytes) + timestampStr := fmt.Sprintf("%d", timestamp) + // 生成安全签名 + ret, signature := getSHA1(c.token, timestampStr, sNonce, encrypt_text) + if ret != WXBizMsgCrypt_OK { + return ret, "" + } + xmlParse := NewXMLParse() + resp_xml := xmlParse.generate(encrypt_text, signature, timestampStr, sNonce) + return ret, resp_xml +} + +// 检验消息的真实性,并且获取解密后的明文 +// sMsgSignature: 签名串,对应URL参数的msg_signature +// sTimeStamp: 时间戳,对应URL参数的timestamp +// sNonce: 随机串,对应URL参数的nonce +// sPostData: 密文,对应POST请求的数据 +// xml_content: 解密后的原文,当return返回0时有效 +// return: 成功0,失败返回对应的错误码 +// 验证安全签名 +func (c *wxBizMsgCrypt) DecryptMsg(sPostData, sMsgSignature string, sTimeStamp int64, sNonce string) (int, string) { + // 验证安全签名 + xmlParse := NewXMLParse() + ret, encrypt, _ := xmlParse.extract(sPostData) + if ret != WXBizMsgCrypt_OK { + return ret, "" + } + timestampStr := fmt.Sprintf("%d", sTimeStamp) + ret, signature := getSHA1(c.token, timestampStr, sNonce, encrypt) + if ret != WXBizMsgCrypt_OK { + return ret, "" + } + if signature != sMsgSignature { + return WXBizMsgCrypt_ValidateSignature_Error, "" + } + pc := NewPrpcrypt(c.key) + ret, xml_content := pc.decrypt([]byte(encrypt), c.appid) + return ret, string(xml_content) +} + +// 提供提取消息格式中的密文及生成回复消息格式的接口 +func NewXMLParse() *XMLParse { + xp := &XMLParse{ + // xml消息模板 + XMLtmpl: ` + + +%(timestamp)s + +`, + } + return xp +} + +type XmlResources struct { + XMLName xml.Name `xml:"xml"` + Encrypt string `xml:"Encrypt"` + ToUserName string `xml:"ToUserName"` +} + +// 提取出xml数据包中的加密消息 +// xmltext: 待提取的xml字符串 +// return: 提取出的加密消息字符串 +// +func (xp *XMLParse) extract(xmltext string) (int, string, string) { + var result XmlResources + err := xml.Unmarshal([]byte(xmltext), &result) + if err != nil { + fmt.Println(err) + return WXBizMsgCrypt_ParseXml_Error, "", "" + } + return WXBizMsgCrypt_OK, result.Encrypt, result.ToUserName +} + +// 生成xml消息 +// encrypt: 加密后的消息密文 +// signature: 安全签名 +// timestamp: 时间戳 +// nonce: 随机字符串 +// return: 生成的xml字符串 +// +func (xp *XMLParse) generate(encrypt, signature, timestampStr, nonce string) string { + resp_xml := strings.Replace(xp.XMLtmpl, "%(msg_encrypt)s", encrypt, 1) + resp_xml = strings.Replace(resp_xml, "%(msg_signaturet)s", signature, 1) + resp_xml = strings.Replace(resp_xml, "%(timestamp)s", timestampStr, 1) + resp_xml = strings.Replace(resp_xml, "%(nonce)s", nonce, 1) + return resp_xml +} + +// 计算公众平台的消息签名接口 +// 用SHA1算法生成安全签名 +// token: 票据 +// timestamp: 时间戳 +// encrypt: 密文 +// nonce: 随机字符串 +// return: 安全签名 +func getSHA1(token string, timestampStr, nonce, encrypt string) (int, string) { + sortlist := []string{token, timestampStr, nonce, encrypt} + sort.Strings(sortlist) + hashBytes := sha1.Sum([]byte(strings.Join(sortlist, ""))) + hashString := fmt.Sprintf("%x", hashBytes) + return WXBizMsgCrypt_OK, hashString +} + +type Prpcrypt struct { + key string + mode string +} + +// 提供接收和推送给公众平台消息的加解密接口 +func NewPrpcrypt(key string) *Prpcrypt { + // 设置加解密模式为AES的CBC模式 + pc := &Prpcrypt{ + key: key, + mode: "AES.CBC", + } + return pc +} + +// s对明文进行加密 +// textBytes: 需要加密的明文 +// return: 加密得到的字符串 +// +func (pc *Prpcrypt) encrypt(textBytes []byte, appid string) (int, []byte) { + //# 16位随机字符串添加到明文开头 + rand_bytes := pc.get_random_bytes() + len1 := len(rand_bytes) + len3 := len(textBytes) + len_bytes := htonl(len3) + len2 := len(len_bytes) + appid_bytes := []byte(appid) + len4 := len(appid_bytes) + toBytes := make([]byte, len1+len2+len3+len4) + copy(toBytes[0:len1], rand_bytes) + copy(toBytes[len1:(len1+len2)], len_bytes) + copy(toBytes[(len1+len2):(len1+len2+len3)], textBytes) + copy(toBytes[(len1+len2+len3):(len1+len2+len3+len4)], appid_bytes) + // 使用自定义的填充方式对明文进行补位填充 + pkcs7 := NewPKCS7Encoder() + plantBytes := pkcs7.encode(toBytes) + // 加密 + keyBytes := []byte(pc.key) + block, err := aes.NewCipher(keyBytes) //选择加密算法 + if err != nil { + fmt.Println(err) + return WXBizMsgCrypt_EncryptAES_Error, []byte{} + } + blockModel := cipher.NewCBCEncrypter(block, keyBytes[0:16]) + cipherBytes := make([]byte, len(plantBytes)) + blockModel.CryptBlocks(cipherBytes, plantBytes) + // 使用BASE64对加密后的字符串进行编码 + dstBytes := make([]byte, base64.StdEncoding.EncodedLen(len(cipherBytes))) + base64.StdEncoding.Encode(dstBytes, cipherBytes) + return WXBizMsgCrypt_OK, dstBytes +} + +// 对解密后的明文进行补位删除 +// cipherBytes: 密文 +// return: 删除填充补位后的明文 +// +func (pc *Prpcrypt) decrypt(cipherBytes []byte, appid string) (int, []byte) { + _dstBytes := make([]byte, base64.StdEncoding.DecodedLen(len(cipherBytes))) + n, err := base64.StdEncoding.Decode(_dstBytes, cipherBytes) + if err != nil { + fmt.Println(err) + return WXBizMsgCrypt_DecodeBase64_Error, []byte{} + } + dstBytes := _dstBytes[0:n] + keyBytes := []byte(pc.key) + block, err := aes.NewCipher(keyBytes) //选择加密算法 + if err != nil { + fmt.Println(err) + return WXBizMsgCrypt_IllegalBuffer, []byte{} + } + blockModel := cipher.NewCBCDecrypter(block, keyBytes[0:16]) + plantBytes := make([]byte, len(dstBytes)) + blockModel.CryptBlocks(plantBytes, dstBytes) + // + length := len(plantBytes) + pad := int(uint8(plantBytes[length-1])) + // 去除16位随机字符串 + content := plantBytes[16:(length - pad)] + xml_len := ntohl(content[0:4]) + xml_content := content[4:(xml_len + 4)] + from_appid := content[(xml_len + 4):len(content)] + if string(from_appid) != appid { + return WXBizMsgCrypt_ValidateAppid_Error, []byte{} + } + return WXBizMsgCrypt_OK, xml_content + +} + +// 随机生成16位字符串 +// return: 16位字符串 +// +func (pc *Prpcrypt) get_random_bytes() []byte { + rander := rand.New(rand.NewSource(time.Now().UnixNano())) + length := 16 + result := make([]byte, length) + for i, _ := range result { + result[i] = letterDigitArr[rander.Intn(LD_COUNT)] + //result[i] = letterDigitArr[52+((i+1)%10)] + } + return result +} + +// 提供基于PKCS7算法的加解密接口 +type PKCS7Encoder struct { + block_size int +} + +func NewPKCS7Encoder() *PKCS7Encoder { + p := &PKCS7Encoder{ + block_size: 32, + } + return p +} + +// 对需要加密的明文进行填充补位 +// text: 需要进行填充补位操作的明文 +// return: 补齐明文字符串 +func (p *PKCS7Encoder) encode(textBytes []byte) []byte { + length := len(textBytes) + if length == 0 { + return []byte{} + } + // 计算需要填充的位数 + amount_to_pad := p.block_size - (length % p.block_size) + if amount_to_pad == 0 { + amount_to_pad = p.block_size + } + // 获得补位所用的字符 + padArr := bytes.Repeat([]byte{byte(amount_to_pad)}, amount_to_pad) + encTextBytes := make([]byte, length+amount_to_pad) + copy(encTextBytes, textBytes) + copy(encTextBytes[length:(length+amount_to_pad)], padArr) + return encTextBytes +} + +// 删除解密后明文的补位字符 +// decrypted: 解密后的明文 +// return: 删除补位字符后的明文 +func (p *PKCS7Encoder) decode(decrypted []byte) []byte { + length := len(decrypted) + if length == 0 { + return []byte{} + } + pad := int(uint8(decrypted[length-1])) + if pad < 1 || pad > 32 { + pad = 0 + } + if pad >= length { + return []byte{} + } + return decrypted[0:(length - pad)] +} + +func htonl(n int) []byte { + data := make([]byte, 4) + binary.BigEndian.PutUint32(data, uint32(n)) + return data +} + +func ntohl(data []byte) int { + n := binary.BigEndian.Uint32(data[0:4]) + return int(n) +} \ No newline at end of file diff --git a/tencentx/wechatx/cryptx/cryptx_test.go b/tencentx/wechatx/cryptx/cryptx_test.go new file mode 100644 index 0000000..b13cfc8 --- /dev/null +++ b/tencentx/wechatx/cryptx/cryptx_test.go @@ -0,0 +1,69 @@ +package cryptx + +import ( + "fmt" + "testing" +) + +func TestWXBizMsgCrypt(t *testing.T) { + // 1.第三方回复加密消息给公众平台; + // 2.第三方收到公众平台发送的消息,验证消息的安全性,并对消息进行解密。 + encodingAESKey := "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFG" + token := "spamtest" + appid := "wx2c2769f8efd9abc2" + timestamp := int64(1409735669) + + to_xml := ` 1407743423 ` + nonce := "1320562132" + // 测试加密接口 + cryp_test, err := NewWXBizMsgCrypt(token, encodingAESKey, appid) + if err != nil { + return + } + ret, encrypt_xml := cryp_test.EncryptMsg(to_xml, nonce, timestamp) + fmt.Println(ret) + fmt.Println(encrypt_xml) + + msg_sign := "ea7a2ac5580e3c67d663b42c63b976b7e2bf26b9" + from_xml := `1409735669` + + // 测试解密接口 + //msg_sign := "5d197aaffba7e9b25a30732f161a50dee96bd5fa" + //from_xml := `14097356686054768590064713728` + ret, decryp_xml := cryp_test.DecryptMsg(from_xml, msg_sign, timestamp, nonce) + fmt.Println(ret) + fmt.Println(decryp_xml) +} + +func TestWXBizMsgCrypt2(t *testing.T) { + // 1.第三方回复加密消息给公众平台; + // 2.第三方收到公众平台发送的消息,验证消息的安全性,并对消息进行解密。 + encodingAESKey := "E7MKT2IwQzLCxtzc1sjeqjPrftqGFLxomFSv2py0UNx" + token := "weixin" + appid := "wx4e7d601bea5a8093" + timestamp := int64(1680706178) + + // to_xml := ` 1407743423 ` + nonce := "1536850936" + // // 测试加密接口 + cryp_test, err := NewWXBizMsgCrypt(token, encodingAESKey, appid) + if err != nil { + return + } + // ret, encrypt_xml := cryp_test.EncryptMsg(to_xml, nonce, timestamp) + // fmt.Println(ret) + // fmt.Println(encrypt_xml) + + msg_sign := "34d5a40b6e17013f0e0aa2e48de98355c15ba6c6" + from_xml := ` + + +` + + // 测试解密接口 + //msg_sign := "5d197aaffba7e9b25a30732f161a50dee96bd5fa" + //from_xml := `14097356686054768590064713728` + ret, decryp_xml := cryp_test.DecryptMsg(from_xml, msg_sign, timestamp, nonce) + fmt.Println(ret) + fmt.Println(decryp_xml) +} diff --git a/tencentx/wechatx/message.go b/tencentx/wechatx/message.go new file mode 100644 index 0000000..e8a59dc --- /dev/null +++ b/tencentx/wechatx/message.go @@ -0,0 +1,20 @@ +package wechatx + +import ( + "time" + + uuid "github.com/satori/go.uuid" +) + +// 构造消息 +func (w *wechat) BuildTextMsg(to string, content string) *WechatMessage { + msg := &WechatMessage{ + ToUserName: to, + FromUserName: w.sourceId, + CreateTime: time.Now().Unix(), + MsgType: "text", + Content: content, + MsgId: uuid.NewV4().String(), + } + return msg +} diff --git a/tencentx/wechatx/notify.go b/tencentx/wechatx/notify.go new file mode 100644 index 0000000..7a62f4d --- /dev/null +++ b/tencentx/wechatx/notify.go @@ -0,0 +1,87 @@ +package wechatx + +import ( + "encoding/xml" + "errors" + "fmt" + "yunink/app/pkg/convx" + encryptsha1 "yunink/app/pkg/encryptx/sha1x" + "yunink/app/pkg/tencentx/wechatx/cryptx" + + "github.com/gin-gonic/gin" +) + +// 解密微信消息 +func (w *wechat) DecryptMsg(ctx *gin.Context) (*WechatMessage, error) { + by, err := ctx.GetRawData() + if err != nil { + return nil, err + } + msg_sign := ctx.Query("msg_signature") + timestamp := ctx.Query("timestamp") + nonce := ctx.Query("nonce") + + t, _ := convx.ToInt64(timestamp) + + ct, err := cryptx.NewWXBizMsgCrypt(w.token, w.encodingAesKey, w.appid) + if err != nil { + return nil, err + } + + code, resp := ct.DecryptMsg(string(by), msg_sign, t, nonce) + if code != 0 { + return nil, errors.New("解析失败") + } + fmt.Println(resp) + wm := &WechatMessage{} + err = xml.Unmarshal([]byte(resp), &wm) + if err != nil { + return nil, err + } + + return wm, nil +} + +// 加密微信消息 +func (w *wechat) EncryptMsg(msg *WechatMessage) (string, error) { + ct, err := cryptx.NewWXBizMsgCrypt(w.token, w.encodingAesKey, w.appid) + if err != nil { + return "", err + } + + // if msg.MsgId == "" { + // msg.MsgId = uuid.NewV4().String() + // } + + // if msg.CreateTime == 0 { + // msg.CreateTime = time.Now().Unix() + // } + + xmlBy, err := xml.Marshal(msg) + + code, enMsg := ct.EncryptMsg(string(xmlBy), msg.MsgId, msg.CreateTime) + + if code != cryptx.WXBizMsgCrypt_OK { + return "", errors.New(fmt.Sprintf("%d", code)) + } + return enMsg, nil +} + +// 回调验证 +func (w *wechat) Verify(ctx *gin.Context) { + timestamp := ctx.Query("timestamp") + nonce := ctx.Query("nonce") + signature := ctx.Query("signature") + echostr := ctx.Query("echostr") + + verifyStr := fmt.Sprintf("%+v%+v%+v", nonce, timestamp, w.token) + + sha1Str := encryptsha1.Sha1(verifyStr) + if sha1Str == signature { + fmt.Fprint(ctx.Writer, echostr) + // ctx.String(200, echostr) + } else { + fmt.Fprint(ctx.Writer, "false") + // ctx.String(200, "false") + } +} diff --git a/tencentx/wechatx/wechat.go b/tencentx/wechatx/wechat.go new file mode 100644 index 0000000..b29534c --- /dev/null +++ b/tencentx/wechatx/wechat.go @@ -0,0 +1,21 @@ +package wechatx + +type wechat struct { + sourceId string + appid string + encodingAesKey string + token string +} + +func NewWeChat() *wechat { + return &wechat{ + sourceId: "gh_cdfaacccfe8d", + appid: "wx4e7d601bea5a8093", + token: "weixin", + encodingAesKey: "E7MKT2IwQzLCxtzc1sjeqjPrftqGFLxomFSv2py0UNx", + } +} + +const ( + WechatHost string = "https://api.weixin.qq.com" +) diff --git a/tencentx/wecomx/wecom.go b/tencentx/wecomx/wecom.go new file mode 100644 index 0000000..3ccd25f --- /dev/null +++ b/tencentx/wecomx/wecom.go @@ -0,0 +1,13 @@ +package wecom + +type wecom struct{} + +// 企业微信 + +const ( + WeComHost string = "" +) + +func NewWeCom() *wecom { + return &wecom{} +} diff --git a/timer/example_test.go b/timer/example_test.go new file mode 100644 index 0000000..615076d --- /dev/null +++ b/timer/example_test.go @@ -0,0 +1,21 @@ +package timer_test + +import ( + "context" + "fmt" + "yunink/app/pkg/timer" +) + +// 示例测试 + +func exampleDemo(ctx context.Context) bool { + fmt.Println("fff") + return false +} + +func ExampleB() { + ctx := context.Background() + timer.InitTimer(ctx) + timer.AddToTimer(1, exampleDemo) + // OutPut: +} diff --git a/timer/timer.go b/timer/timer.go new file mode 100644 index 0000000..4632eea --- /dev/null +++ b/timer/timer.go @@ -0,0 +1,172 @@ +package timer + +// 作者:黄新云 + +import ( + "context" + "fmt" + "log" + "sync" + "time" +) + +// 定时器 +// 原理:每毫秒的时间触发 +// TODO:不是最新的 + +type timerStr struct { + Callback callback // 需要回调的方法 + CanRunning chan (struct{}) + BeginTime time.Time // 初始化任务的时间 + NextTime time.Time // 下一次执行的时间 + SpaceTime time.Duration // 间隔时间 + Params []int +} + +var timerMap = make(map[int]*timerStr) +var timerMapMux sync.Mutex +var timerCount int // 当前定时数目 +var onceLimit sync.Once // 实现单例 +var nextTime = time.Now() // 下一次执行的时间 + +// 定时器类 +func InitTimer(ctx context.Context) { + onceLimit.Do(func() { + timer := time.NewTicker(1 * time.Millisecond) + go func(ctx context.Context) { + Loop: + for { + select { + case t := <-timer.C: + if t.Before(nextTime) { + // 当前时间小于下次发送时间:跳过 + continue + } + // 迭代定时器 + iteratorTimer(ctx, t) + // fmt.Println("timer: 执行") + case <-ctx.Done(): + // 跳出循环 + break Loop + } + } + log.Println("timer: initend") + }(ctx) + }) + +} + +// 添加需要定时的规则 +func AddToTimer(space time.Duration, call callback) int { + timerMapMux.Lock() + defer timerMapMux.Unlock() + + timerCount += 1 + // 计算出首次开始时间和时间间隔,保存在map里面 + + nowTime := time.Now() + + t := timerStr{ + Callback: call, + BeginTime: nowTime, + NextTime: nowTime.Add(space), + SpaceTime: space, + CanRunning: make(chan struct{}, 1), + } + timerMap[timerCount] = &t + + if t.NextTime.Before(nextTime) { + // 本条规则下次需要发送的时间小于系统下次发送时间:替换 + nextTime = t.NextTime + } + + return timerCount +} + +func DelToTimer(index int) { + timerMapMux.Lock() + defer timerMapMux.Unlock() + delete(timerMap, index) +} + +// 迭代定时器列表 +func iteratorTimer(ctx context.Context, nowTime time.Time) { + timerMapMux.Lock() + defer timerMapMux.Unlock() + + // fmt.Println("nowTime:", nowTime.Format("2006-01-02 15:04:05.000")) + + // 默认5秒后(如果没有值就暂停进来5秒) + newNextTime := nowTime.Add(time.Second * 5) + + for k, v := range timerMap { + v := v + // 判断执行的时机 + if v.NextTime.Before(nowTime) { + // fmt.Println("NextTime", v.NextTime.Format("2006-01-02 15:04:05.000")) + + // TODO:这个有问题:假如加上一个时间段还是比当前时间小,会导致连续多次执行 + v.NextTime = v.NextTime.Add(v.SpaceTime) + + if k == 0 { + // 循环的第一个需要替换默认值 + newNextTime = v.NextTime + } + + // 获取最小的 + if v.NextTime.Before(newNextTime) { + // 本规则下次发送时间小于系统下次需要执行的时间:替换 + newNextTime = v.NextTime + } + + // 处理中就跳过本次 + go func(ctx context.Context, v *timerStr) { + select { + case v.CanRunning <- struct{}{}: + // TODO: 需要考虑分布式锁 + defer func() { + // fmt.Printf("timer: 执行完成 %v %v \n", k, v.Tag) + select { + case <-v.CanRunning: + return + default: + return + } + }() + // fmt.Printf("timer: 准备执行 %v %v \n", k, v.Tag) + timerAction(ctx, v.Callback) + default: + // fmt.Printf("timer: 已在执行 %v %v \n", k, v.Tag) + return + } + }(ctx, v) + } + } + + // 实际下次时间小于预期下次时间:替换 + if nextTime.Before(newNextTime) { + // 判断一下避免异常 + if newNextTime.Before(nowTime) { + // 比当前时间小 + nextTime = nowTime + } else { + nextTime = newNextTime + } + } + + // fmt.Println("timer: one finish") +} + +// 定义各个回调函数 +type callback func(context.Context) bool + +// 定时器操作类 +// 这里不应painc +func timerAction(ctx context.Context, call callback) bool { + defer func() { + if err := recover(); err != nil { + fmt.Println("timer:定时器出错", err) + } + }() + return call(ctx) +} diff --git a/timer/timer_test.go b/timer/timer_test.go new file mode 100644 index 0000000..6fb1621 --- /dev/null +++ b/timer/timer_test.go @@ -0,0 +1,21 @@ +package timer_test + +import "testing" + +// 单元测试 + +func TestHelloWorld(t *testing.T) { + // 日志 + t.Log("hello world") + + s := "ddd" + t.Logf("Log测试%s", s) + // t.Errorf("ErrorF %s", s) + + // 标记错误(继续运行) + // t.Fail() + + // 终止运行 + // t.FailNow() + +} diff --git a/timex/timex.go b/timex/timex.go new file mode 100644 index 0000000..9d6cc5b --- /dev/null +++ b/timex/timex.go @@ -0,0 +1,30 @@ +package timex + +import ( + "fmt" + "time" +) + +type TimeStr string + +const( + yyyy_mm_dd TimeStr = "2006-01-02" + yyyymmdd TimeStr = "20060102" + hhiiss TimeStr = "15:04:05" + YmdHis TimeStr = "2006-01-02 15:04:05" + +) + +// 时间转时间戳 +func StrToTime() { + fmt.Println( time.ANSIC) +} + + +// 时间戳转时间 + + + + + + diff --git a/uniquex/uniquex.go b/uniquex/uniquex.go new file mode 100644 index 0000000..c900d92 --- /dev/null +++ b/uniquex/uniquex.go @@ -0,0 +1,94 @@ +package uniquex + +// Version: 2023年6月12日18:59:16 + +/** +说明: +1. 这是基于redis的全局锁 +2. 只要没释放锁会自动的续期,不用考虑锁被提前释放的问题 +3. 允许自定义超时时间 +**/ + +import ( + "context" + "time" + + uuid "github.com/satori/go.uuid" + + "github.com/go-redis/redis/v8" +) + +type uniquex struct { + ctx context.Context + cancel context.CancelFunc + redis *redis.Client + uniqueKey string + uuid string +} + +const tokenValidTime = time.Second * 5 + +func NewUniquex(ctx context.Context, redis *redis.Client, uniqueKey string) *uniquex { + ctx, cancel := context.WithCancel(ctx) + // 获取Token + return &uniquex{ + ctx: ctx, + cancel: cancel, + redis: redis, + uniqueKey: uniqueKey, + uuid: uuid.NewV4().String(), + } +} + +func (u *uniquex) Lock() error { + // 获取Token + b, err := u.redis.SetNX(u.ctx, u.uniqueKey, u.uuid, tokenValidTime).Result() + if !b { + return err + } + + go u.refreshToken() + + return nil + +} + +func (u *uniquex) Unlock() { + u.cancel() + + // 释放Token + script := ` + local token = redis.call('get',KEYS[1]); + if token == ARGV[1] + then + redis.call('del',KEYS[1]); + return 'SUCCESS'; + end; + return 'ERROR' + ` + u.redis.Eval(u.ctx, script, []string{u.uniqueKey}, u.uuid) + + return +} + +// 刷新Token +func (u *uniquex) refreshToken() { + script := ` + local token = redis.call('get',KEYS[1]) + if token == ARGV[1] + then + redis.call('set',KEYS[1],ARGV[2],ARGV[1]); + return 'SUCCESS'; + end; + return 'ERROR'; + ` + for { + time.Sleep(time.Second) + select { + case <-u.ctx.Done(): + return + default: + } + u.redis.Eval(u.ctx, script, []string{u.uniqueKey}, u.uuid, tokenValidTime.Seconds()) + } +} diff --git a/upload/upload.go b/upload/upload.go new file mode 100644 index 0000000..8dac971 --- /dev/null +++ b/upload/upload.go @@ -0,0 +1,50 @@ +package upload + +// Author: Yun +// Version: 2023年6月29日13:45:10 +// Description: 向一个URL上传服务器本地文件 + +import ( + "bytes" + "fmt" + "io" + "mime/multipart" + "net/http" + "os" +) + +// 表单上传 +func FormUpload(url, filePath string) ([]byte, error) { + // 文件上传 + bodyBuf := bytes.NewBufferString("") + bodyWriter := multipart.NewWriter(bodyBuf) + _, err := bodyWriter.CreateFormFile("file", filePath) + if err != nil { + return nil, err + } + fh, err := os.Open(filePath) + if err != nil { + return nil, err + } + boundary := bodyWriter.Boundary() + closeBuf := bytes.NewBufferString(fmt.Sprintf("\r\n--%s--\r\n", boundary)) + + requestReader := io.MultiReader(bodyBuf, fh, closeBuf) + fi, err := fh.Stat() + if err != nil { + return nil, err + } + req, err := http.NewRequest("POST", url, requestReader) + if err != nil { + return nil, err + } + req.Header.Add("Content-Type", "multipart/form-data; boundary="+boundary) + req.ContentLength = fi.Size() + int64(bodyBuf.Len()) + int64(closeBuf.Len()) + + res, err := http.DefaultClient.Do(req) + if err != nil { + return nil, err + } + defer res.Body.Close() + return io.ReadAll(res.Body) +}