封装的方法类

This commit is contained in:
Yun
2023-09-16 20:14:20 +08:00
commit 1bbc7db405
59 changed files with 3671 additions and 0 deletions
+51
View File
@@ -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
}
+77
View File
@@ -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
}
// 交集
// 并集
// 差集
// 补集
// 去重
// 排序
// 反转
// 求和
// 求平均值
// 求最大值
// 求最小值
// 求最大值和最小值
// 求最大值和最小值的差值
// 求最大值和最小值的差值的绝对值
// 求最大值和最小值的差值的绝对值的平均值
// 求最大值和最小值的差值的绝对值的平均值的平方根
// 求最大值和最小值的差值的绝对值的平均值的平方根的平方
// 求最大值和最小值的差值的绝对值的平均值的平方根的平方的平均值
+44
View File
@@ -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数组里面")
}
}
+59
View File
@@ -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)
}
}
+61
View File
@@ -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
}
+115
View File
@@ -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)
}
}
}
+57
View File
@@ -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)
}
+26
View File
@@ -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
}
+171
View File
@@ -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) {}
+50
View File
@@ -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
}
+47
View File
@@ -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
}
+40
View File
@@ -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
}
+7
View File
@@ -0,0 +1,7 @@
package convx
func ToInt32(val interface{}) (i int32, err error) {
ii, err := ToInt(val)
i = int32(ii)
return
}
+41
View File
@@ -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
}
+44
View File
@@ -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
}
+174
View File
@@ -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
}
+24
View File
@@ -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)
}
+51
View File
@@ -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
}
+41
View File
@@ -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)
}
+28
View File
@@ -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
}
+119
View File
@@ -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
}
+18
View File
@@ -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))
}
+18
View File
@@ -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
}
+13
View File
@@ -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)
}
+14
View File
@@ -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
}
+254
View File
@@ -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
}
+218
View File
@@ -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()
// }
// }
+124
View File
@@ -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
}
+3
View File
@@ -0,0 +1,3 @@
package httpx
func Post(){}
+83
View File
@@ -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°438″"
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
}
+6
View File
@@ -0,0 +1,6 @@
package langx
var transCode = map[MessageKey]int{
"login_success": 200,
"error": 400,
}
+15
View File
@@ -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
)
+36
View File
@@ -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
}
+6
View File
@@ -0,0 +1,6 @@
package langx
var transZh = map[MessageKey]string{
"login_success": "成功",
"username": "你好 #name#",
}
+5
View File
@@ -0,0 +1,5 @@
package loggerx
+192
View File
@@ -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)
}
+19
View File
@@ -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")
}
+12
View File
@@ -0,0 +1,12 @@
# 简介
这个是基于原生log实现的日志存储。
```go
log.Println("ddddd")
```
+31
View File
@@ -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
}
+43
View File
@@ -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
}
+30
View File
@@ -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)
}
}
+90
View File
@@ -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)
}
+23
View File
@@ -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
}
+11
View File
@@ -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())
}
+71
View File
@@ -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
}
+37
View File
@@ -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 {
//
}
}
+13
View File
@@ -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"`
}
+361
View File
@@ -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:成功0sEncryptMsg,失败返回对应的错误码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: `<xml>
<Encrypt><![CDATA[%(msg_encrypt)s]]></Encrypt>
<MsgSignature><![CDATA[%(msg_signaturet)s]]></MsgSignature>
<TimeStamp>%(timestamp)s</TimeStamp>
<Nonce><![CDATA[%(nonce)s]]></Nonce>
</xml>`,
}
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)
}
+69
View File
@@ -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 := ` <xml><ToUserName><![CDATA[oia2TjjewbmiOUlr6X-1crbLOvLw]]></ToUserName><FromUserName><![CDATA[gh_7f083739789a]]></FromUserName><CreateTime>1407743423</CreateTime><MsgType> <![CDATA[video]]></MsgType><Video><MediaId><![CDATA[eYJ1MbwPRJtOvIEabaxHs7TX2D-HV71s79GUxqdUkjm6Gs2Ed1KF3ulAOA9H1xG0]]></MediaId><Title><![CDATA[testCallBackReplyVideo中文]]></Title><Descript ion><![CDATA[testCallBackReplyVideo]]></Description></Video></xml>`
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 := `<xml><Encrypt><![CDATA[QCTqNwyPmqRu4xuyqVsja3pIr0TeNW/5tIi3Ul+15UnsdXcZoLmTZJtCxJn7e5lKBp7A5lykhD8Hsejz9M+OBTXLrkuqRD0Ky0fkfk4ZCIZhHc+pWuNZjalZGDQ3tzz2rCfn2pzEHGE3jnSWP4EdLdailWTo/Hz7gxBnLf2xI3xsS9kxyErmv14LhdccB7hVqa224efcb5lT85XKfrku7b4W5L7MA6g8/TYBFrkHCZz9l9sjMgEzNkErAHdayzM6eKCOUm51PQv0Sp3jPjq7NEUi9DZa8nnrz/w3P5clO+Hmf6yqwJZAE26+nGYtltcZmOu8UCZrqZDkAD+MwPeYSGo4C1j6R71zsBwI3sk/Bea52AAGqjvGVPGK3oTvku775T93MwTzjJMZTxX3xF2QrPbbU5pFIBZWF8KW0Giuqui500qRz9Ix5oVK3TK4ZCCZpHJelUvy5gcP4ldbDStZhQaM21pdlitL6Kt/GucIpIWHkxiObKu1pBfU94cR0ZCgoja8E6xtx+cGMIgCN33/QHkwM+xeb6rCF0HhDZwd9TwdetxpRbqRDkfzc+LaeoF8HHbFhK+3tKP9qZBAdZm1v4YUFK2db1juaoyXHAXFDn3I8PZ5zSP8vddBDSfoLgv+]]></Encrypt><MsgSignature><![CDATA[ea7a2ac5580e3c67d663b42c63b976b7e2bf26b9]]></MsgSignature><TimeStamp>1409735669</TimeStamp><Nonce><![CDATA[1320562132]]></Nonce></xml>`
// 测试解密接口
//msg_sign := "5d197aaffba7e9b25a30732f161a50dee96bd5fa"
//from_xml := `<xml><ToUserName><![CDATA[gh_10f6c3c3ac5a]]></ToUserName><FromUserName><![CDATA[oyORnuP8q7ou2gfYjqLzSIWZf0rs]]></FromUserName><CreateTime>1409735668</CreateTime><MsgType><![CDATA[text]]></MsgType><Content><![CDATA[abcdteT]]></Content><MsgId>6054768590064713728</MsgId><Encrypt><![CDATA[hyzAe4OzmOMbd6TvGdIOO6uBmdJoD0Fk53REIHvxYtJlE2B655HuD0m8KUePWB3+LrPXo87wzQ1QLvbeUgmBM4x6F8PGHQHFVAFmOD2LdJF9FrXpbUAh0B5GIItb52sn896wVsMSHGuPE328HnRGBcrS7C41IzDWyWNlZkyyXwon8T332jisa+h6tEDYsVticbSnyU8dKOIbgU6ux5VTjg3yt+WGzjlpKn6NPhRjpA912xMezR4kw6KWwMrCVKSVCZciVGCgavjIQ6X8tCOp3yZbGpy0VxpAe+77TszTfRd5RJSVO/HTnifJpXgCSUdUue1v6h0EIBYYI1BD1DlD+C0CR8e6OewpusjZ4uBl9FyJvnhvQl+q5rv1ixrcpCumEPo5MJSgM9ehVsNPfUM669WuMyVWQLCzpu9GhglF2PE=]]></Encrypt></xml>`
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 := ` <xml><ToUserName><![CDATA[oia2TjjewbmiOUlr6X-1crbLOvLw]]></ToUserName><FromUserName><![CDATA[gh_7f083739789a]]></FromUserName><CreateTime>1407743423</CreateTime><MsgType> <![CDATA[video]]></MsgType><Video><MediaId><![CDATA[eYJ1MbwPRJtOvIEabaxHs7TX2D-HV71s79GUxqdUkjm6Gs2Ed1KF3ulAOA9H1xG0]]></MediaId><Title><![CDATA[testCallBackReplyVideo中文]]></Title><Descript ion><![CDATA[testCallBackReplyVideo]]></Description></Video></xml>`
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 := `<xml>
<ToUserName><![CDATA[gh_cdfaacccfe8d]]></ToUserName>
<Encrypt><![CDATA[JGi3SHl4GFSzQ2axn/k0PUTm7O3tF1+PXdZ+rrHRrwxaYJFF+PmT1K+jZ9NrlsXjcdSgDeN34DV4VkYKRRTPZ1W0SD0w6H7AlMeC8tlSSpEEPMmNisu3+13OHyExzWgXWt+Fu54HGhrOJkIf8dWHVwOXv/ulwmU857iL8yrIYPH2i4mXjSJxLivdrmp+5o3i+DxzKC5FdeDxZtBgLNxnFsW/MxOjC7+SHo/0SQtiMbhwkR9ByNzirxNSUcUuEJnYTX9gHRoaaiEkaf/T+s3ZuLa2NlHkGlWCMDJQK87SrsKUfsVcfx6Ihde0sTxeAkiIBdWrzjcvMT8SEqmx8bWI9BtSc9ya+nvo0MT/BP7fvnCDRy23l2KtimHImmT+q5MzsESFK+bTU5Afcmn9m55QmdmW7ArPz+AFciwaIJrjtXyV3KNCZW64m+UfUUDAOVxtT7NtPQXaZNy8O1HV1zJnW7YOltTmNvZV+hRAbOKBk4C1Lqv1ekf8I8e2y4/STxPL]]></Encrypt>
</xml>`
// 测试解密接口
//msg_sign := "5d197aaffba7e9b25a30732f161a50dee96bd5fa"
//from_xml := `<xml><ToUserName><![CDATA[gh_10f6c3c3ac5a]]></ToUserName><FromUserName><![CDATA[oyORnuP8q7ou2gfYjqLzSIWZf0rs]]></FromUserName><CreateTime>1409735668</CreateTime><MsgType><![CDATA[text]]></MsgType><Content><![CDATA[abcdteT]]></Content><MsgId>6054768590064713728</MsgId><Encrypt><![CDATA[hyzAe4OzmOMbd6TvGdIOO6uBmdJoD0Fk53REIHvxYtJlE2B655HuD0m8KUePWB3+LrPXo87wzQ1QLvbeUgmBM4x6F8PGHQHFVAFmOD2LdJF9FrXpbUAh0B5GIItb52sn896wVsMSHGuPE328HnRGBcrS7C41IzDWyWNlZkyyXwon8T332jisa+h6tEDYsVticbSnyU8dKOIbgU6ux5VTjg3yt+WGzjlpKn6NPhRjpA912xMezR4kw6KWwMrCVKSVCZciVGCgavjIQ6X8tCOp3yZbGpy0VxpAe+77TszTfRd5RJSVO/HTnifJpXgCSUdUue1v6h0EIBYYI1BD1DlD+C0CR8e6OewpusjZ4uBl9FyJvnhvQl+q5rv1ixrcpCumEPo5MJSgM9ehVsNPfUM669WuMyVWQLCzpu9GhglF2PE=]]></Encrypt></xml>`
ret, decryp_xml := cryp_test.DecryptMsg(from_xml, msg_sign, timestamp, nonce)
fmt.Println(ret)
fmt.Println(decryp_xml)
}
+20
View File
@@ -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
}
+87
View File
@@ -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")
}
}
+21
View File
@@ -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"
)
+13
View File
@@ -0,0 +1,13 @@
package wecom
type wecom struct{}
// 企业微信
const (
WeComHost string = ""
)
func NewWeCom() *wecom {
return &wecom{}
}
+21
View File
@@ -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:
}
+172
View File
@@ -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)
}
+21
View File
@@ -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()
}
+30
View File
@@ -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)
}
// 时间戳转时间
+94
View File
@@ -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())
}
}
+50
View File
@@ -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)
}