Initial commit

This commit is contained in:
2026-05-20 16:13:58 +08:00
commit ceffbe382e
500 changed files with 157469 additions and 0 deletions
+64
View File
@@ -0,0 +1,64 @@
package checkWx
import (
"bytes"
"context"
"stat/common/httputil"
"stat/common/log"
"time"
)
const ApiToken string = "866mcJyA4teCehxivaCJec3qbghWStjA"
type Resp struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data interface{} `json:"data"`
}
type CheckResp struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data struct {
Pass bool `json:"pass"`
} `json:"data"`
}
//获取订单信息
func CheckWx(execUrl, domian string) (checkResp CheckResp, err error) {
c, cancle := context.WithTimeout(context.Background(), 5*time.Second)
defer cancle()
code, err := httputil.NewCtx(c).GetWithJResp(&checkResp, execUrl+"?"+bindUrl(domian), nil)
log.Info("http method CheckWx response code ==>", log.Any("statusCode", code), log.Any("checkResp", checkResp))
if err != nil {
log.Error("CheckWx failed", log.Any("Error", err))
}
return
}
func TgSend(msgPayload, text string) (resp Resp, err error) {
params := map[string]interface{}{
"tgName": "ys_bot",
"text": text,
"msgPayload": msgPayload,
"chatId": -228259065,
}
c, cancle := context.WithTimeout(context.Background(), 5*time.Second)
defer cancle()
code, err := httputil.NewCtx(c).POSTJsonWithJResp(&resp, "http://uni.ysappb.me/api/tg/send", nil, params)
log.Info("http method TgSend response code ==>", log.Any("statusCode", code), log.Any("resp", resp))
if err != nil {
log.Error("TgSend failed", log.Any("Error", err))
}
return
}
func bindUrl(domain string) string {
buf := bytes.Buffer{}
buf.WriteString("apiToken=")
buf.WriteString(ApiToken + "&")
buf.WriteString("req_url=")
buf.WriteString(domain)
return buf.String()
}
+6
View File
@@ -0,0 +1,6 @@
package common
type Page struct {
PageNum int64 `form:"pageNum" json:"pageNum" binding:"required"` // 页数
PageSize int64 `form:"pageSize" json:"pageSize" binding:"required"` // 条数
}
+27
View File
@@ -0,0 +1,27 @@
package config
import (
"encoding/json"
"io/ioutil"
"os"
"stat/common/log"
)
func LoadJSON(path string, cfg interface{}) error {
f, err := os.Open(path)
if err != nil {
log.Error("common conf load json open file", log.Any("path", path), log.E(err))
return err
}
bs, err := ioutil.ReadAll(f)
if err != nil {
log.Error("common conf load json real all", log.Any("path", path), log.E(err))
return err
}
err = json.Unmarshal(bs, cfg)
if err != nil {
log.Error("common conf load json Unmarshal", log.Any("path", path), log.E(err))
return err
}
return nil
}
+43
View File
@@ -0,0 +1,43 @@
package crypt
import (
"bytes"
"crypto/aes"
)
func PKCS7PaddingEx(ciphertext []byte, blockSize int) []byte {
padding := blockSize - len(ciphertext)%blockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(ciphertext, padtext...)
}
func PKCS7UnPaddingEx(origData []byte) []byte {
length := len(origData)
unpadding := int(origData[length-1])
return origData[:(length - unpadding)]
}
func EcbDecrypt(data, key []byte) []byte {
block, _ := aes.NewCipher(key)
decrypted := make([]byte, len(data))
size := block.BlockSize()
for bs, be := 0, size; bs < len(data); bs, be = bs+size, be+size {
block.Decrypt(decrypted[bs:be], data[bs:be])
}
return PKCS7UnPaddingEx(decrypted)
}
func EcbEncrypt(data, key []byte) []byte {
block, _ := aes.NewCipher(key)
data = PKCS7PaddingEx(data, block.BlockSize())
decrypted := make([]byte, len(data))
size := block.BlockSize()
for bs, be := 0, size; bs < len(data); bs, be = bs+size, be+size {
block.Encrypt(decrypted[bs:be], data[bs:be])
}
return decrypted
}
+566
View File
@@ -0,0 +1,566 @@
package crypt
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/hmac"
"crypto/md5"
"crypto/rand"
"crypto/sha1"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"encoding/json"
"errors"
"hash/crc32"
"io"
"net/url"
"os"
"reflect"
"sort"
"strconv"
"strings"
"github.com/dgrijalva/jwt-go"
)
// CreateToken 生成Token算法
func CreateToken(secret string, tokenClaims map[string]interface{}) (tokeness string, err error) {
if secret == "" {
return "", errors.New("secret is empty")
}
claims := jwt.MapClaims(tokenClaims)
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
tokeness, err = token.SignedString([]byte(secret))
if err != nil {
return "", err
}
return tokeness, nil
}
func keyFunc(secret string) jwt.Keyfunc {
return func(token *jwt.Token) (interface{}, error) {
return []byte(secret), nil
}
}
// ParseToken 解析Token
func ParseToken(secret string, tokeness string) (map[string]interface{}, error) {
if secret == "" || tokeness == "" {
return nil, errors.New("secret or tokeness is empty")
}
token, err := jwt.Parse(tokeness, keyFunc(secret))
if err != nil {
return nil, err
}
//验证token,如果token被修改过则为false
if !token.Valid {
return nil, errors.New("token is invalid")
}
claimsToken, ok := token.Claims.(jwt.MapClaims)
if !ok {
return nil, errors.New("cannot convert claim to MapClaim")
}
return claimsToken, nil
}
// StructToStr 结构体转json 先对结构体按照字典顺序排序 并返回字符串
func StructToStr(obj interface{}) (string, error) {
if obj == nil {
return "", errors.New("obj is not nil")
}
data := make(map[string]interface{})
bytes, err := json.Marshal(obj)
if err != nil {
return "", err
}
err = json.Unmarshal(bytes, &data)
if err != nil {
return "", err
}
str, err := MapToStr(data)
if err != nil {
return "", err
}
return str, nil
}
// MapToStr 对map按照字典顺序排序 并返回字符串
func MapToStr(data map[string]interface{}) (string, error) {
if data == nil || len(data) == 0 {
return "", errors.New("data is nil or empty")
}
newData := make(map[string]interface{})
keys := make([]string, 0, len(data))
for k := range data {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
newData[k] = data[k]
}
b, err := json.Marshal(newData)
if err != nil {
return "", err
}
str := string(b)
return str, nil
}
// JSON2Struct json转struct
func JSON2Struct(str string, obj interface{}) {
if err := json.Unmarshal([]byte(str), &obj); err != nil {
return
}
}
// JSONArray2Struct json转struct
func JSONArray2Struct(str []string, obj interface{}) []interface{} {
var data []interface{}
for _, v := range str {
JSON2Struct(v, &obj)
data = append(data, obj)
}
return data
}
// JSON2Map json转map
func JSON2Map(str string) (map[string]interface{}, error) {
var mapResult map[string]interface{}
if err := json.Unmarshal([]byte(str), &mapResult); err != nil {
return nil, err
}
return mapResult, nil
}
// StructToStrNormal 结构体转str
func StructToStrNormal(obj interface{}) (string, error) {
str, err := json.Marshal(obj)
if err != nil {
return "", err
}
return string(str), nil
}
func UrlValueToStr(values url.Values, exclude string) (string, string) {
if values == nil {
return "", ""
}
var buf bytes.Buffer
var sign string
keys := make([]string, 0, len(values))
for k, _ := range values {
if k == exclude {
sign = strings.Join(values[k], "")
continue
}
keys = append(keys, k)
}
sort.Strings(keys)
for i, k := range keys {
if i != 0 {
buf.WriteString("&")
}
buf.WriteString(k)
buf.WriteString("=")
buf.WriteString(strings.Join(values[k], ""))
}
return buf.String(), sign
}
// 对参数进行排序后 拼接成url字符串 只对一级字符串类型的参数做处理
func MapToUrlOnlyStr(data map[string]interface{}) string {
if data == nil || len(data) == 0 {
return ""
}
var buf bytes.Buffer
keys := make([]string, 0, len(data))
for k := range data {
keys = append(keys, k)
}
sort.Strings(keys)
for i, k := range keys {
if i != 0 {
buf.WriteString("&")
}
buf.WriteString(k)
buf.WriteString("=")
if v, ok := data[k].(string); ok {
buf.WriteString(v)
}
}
return buf.String()
}
// MapToStr 对map按照字典顺序排序 并返回字符串url 拼接的字段
func MapToUrlStr(data map[string]interface{}) string {
if data == nil || len(data) == 0 {
return ""
}
var buf bytes.Buffer
keys := make([]string, 0, len(data))
for k := range data {
keys = append(keys, k)
}
sort.Strings(keys)
for i, k := range keys {
if i != 0 {
buf.WriteString("&")
}
buf.WriteString(k)
buf.WriteString("=")
if v, ok := data[k].(string); ok {
buf.WriteString(v)
} else {
t := reflect.TypeOf(data[k])
if t != nil && t.Kind() == reflect.Map {
if v, ok := data[k].(map[string]interface{}); ok {
buf.WriteString("{")
str := MapToUrlStr(v)
buf.WriteString(str)
buf.WriteString("}")
}
}
if t != nil && (t.Kind() == reflect.Slice || t.Kind() == reflect.Array) {
arraybyte, _ := json.Marshal(data[k])
buf.WriteString(string(arraybyte))
}
}
}
return buf.String()
}
// MapToURL 集合转URL
func MapToURL(m map[string]string) string {
if m == nil || len(m) == 0 {
return ""
}
v := url.Values{}
for k := range m {
v.Set(k, string(m[k]))
}
return v.Encode()
}
func Strcut2UrlValue(obj interface{}) (string, error) {
t := reflect.TypeOf(obj)
v := reflect.ValueOf(obj)
if t.Kind() != reflect.Struct {
return "", errors.New("obj must be struct type")
}
u := url.Values{}
for i := 0; i < t.NumField(); i++ {
var value string
tag := t.Field(i).Tag.Get("json")
switch t.Field(i).Type.Kind() {
case reflect.String:
value = v.Field(i).String()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
value = strconv.Itoa(int(v.Field(i).Int()))
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
value = strconv.FormatUint(v.Field(i).Uint(), 10)
case reflect.Slice, reflect.Array:
buf := bytes.Buffer{}
b, _ := json.Marshal(v.Field(i).Interface())
t := strings.TrimRight(strings.TrimLeft(string(b), "["), "]")
array := strings.Split(t, ",")
if len(array) == 0 {
value = ""
}
if len(array) == 1 {
value = array[0]
} else {
for i, v := range array {
if i == 0 {
buf.WriteString(v)
continue
}
buf.WriteString("&")
buf.WriteString(tag)
buf.WriteString("=")
buf.WriteString(v)
value = buf.String()
}
}
}
u.Add(tag, value)
}
dcodeurl, err := url.QueryUnescape(u.Encode())
return dcodeurl, err
}
// StrToMd5 字符串转md5
func StrToMd5(str string) string {
data := []byte(str)
return ByteToMd5(data)
}
// ByteToMd5 数组转md5
func ByteToMd5(data []byte) string {
md5Ctx := md5.New()
md5Ctx.Write(data)
cipherStr := md5Ctx.Sum(nil)
return hex.EncodeToString(cipherStr)
}
// FileToMd5 文件转MD5
func FileToMd5(fileName string) string {
file, err := os.Open(fileName)
if err != nil {
return ""
}
defer file.Close()
md5Ctx := md5.New()
if _, err := io.Copy(md5Ctx, file); err != nil {
return ""
}
cipherStr := md5Ctx.Sum(nil)
return hex.EncodeToString(cipherStr)
}
// HashCode 对一个字符串生成唯一的hasHcode 码
func HashCode(src string) int {
v := int(crc32.ChecksumIEEE([]byte(src)))
if v > 0 {
return v
}
if -v > 0 {
return -v
}
return 0
}
// HexToString converts
func HexToString(data []byte) string {
return hex.EncodeToString(data)
}
// StrToSha256 字符串转sha256
func StrToSha256(str string) string {
shaCtx := sha256.New()
shaCtx.Write([]byte(str))
shaBytes := shaCtx.Sum(nil)
cipherStr := hex.EncodeToString(shaBytes[:])
return cipherStr
}
// StrToSha1 字符串转sha1
func StrToSha1(str string, secret string) []byte {
h := hmac.New(sha1.New, []byte(secret))
h.Write([]byte(str))
return h.Sum(nil)
}
// StrToSha1 字符串转sha1 转16进制后
func StrToHmacSha1(str, secret string) string {
return hex.EncodeToString(StrToSha1(str, secret))
}
// StrToHmacSha256 加密 字符串转sha256加密字符串
func StrToHmacSha256(str string, secret string) string {
h := hmac.New(sha256.New, []byte(secret))
h.Write([]byte(str))
cipherStr := hex.EncodeToString(h.Sum(nil))
return cipherStr
}
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)]
}
// AesEncrypt AES加密
func AesEncrypt(origData, key string) ([]byte, error) {
origDataByte := []byte(origData)
return CoreAesEncrypt(origDataByte, key)
}
// AesDecrypt AES解密
func AesDecrypt(crypted, key string) (string, error) {
cryptedByte := []byte(crypted)
return CoreAesDecrypt(cryptedByte, key)
}
// AesDecrypt AES解密
func AesDecryptByte(cryptedByte []byte, key string) (string, error) {
return CoreAesDecrypt(cryptedByte, key)
}
// CoreAesEncrypt AES加密
func CoreAesEncrypt(origData []byte, key string) ([]byte, error) {
keybyte := []byte(key)
block, err := aes.NewCipher(keybyte)
if err != nil {
return nil, err
}
blockSize := block.BlockSize()
origData = PKCS7Padding(origData, blockSize)
blockMode := cipher.NewCBCEncrypter(block, keybyte[:blockSize])
crypted := make([]byte, len(origData))
blockMode.CryptBlocks(crypted, origData)
return crypted, nil
}
// CoreAesDecrypt AES解密
func CoreAesDecrypt(crypted []byte, key string) (string, error) {
keybyte := []byte(key)
block, err := aes.NewCipher(keybyte)
if err != nil {
return "", err
}
blockSize := block.BlockSize()
blockMode := cipher.NewCBCDecrypter(block, keybyte[:blockSize])
origData := make([]byte, len(crypted))
blockMode.CryptBlocks(origData, crypted)
origData = PKCS7UnPadding(origData)
return string(origData), nil
}
// XorEnc
func XorEnc(src string, xorKey string) string {
var result string
j := 0
bt := []rune(src)
xor := []rune(xorKey)
for i := 0; i < len(bt); i++ {
s := strconv.FormatInt(int64(bt[i]^xor[j]), 10)
result = result + s
j = 1 % len(xor)
}
return result
}
// XorDec
func XorDec(src string, xorKey string) string {
var result string
j := 0
bt := []rune(src)
xor := []rune(xorKey)
for i := 0; i < len(bt); i++ {
s := strconv.FormatInt(int64(bt[i]^xor[j]), 10)
result = result + s
j = i % len(xor)
}
return result
}
func XorDecode(src, key string) string {
srcByte, _ := base64.StdEncoding.DecodeString(src)
keyByte, _ := base64.StdEncoding.DecodeString(key)
srcByte = xor(srcByte, keyByte)
return base64.StdEncoding.EncodeToString(srcByte)
}
func XorEncode(src, key string) string {
return XorDecode(src, key)
}
func xor(src []byte, key []byte) []byte {
for i := 0; i < len(src); i++ {
src[i] ^= key[i%len(key)]
}
return src
}
func XORLength(src []byte, key []byte, length int) []byte {
for i := 0; i < length; i++ {
src[i] ^= key[i%len(key)]
}
return src[:length]
}
// https://core.telegram.org/api/end-to-end#sending-and-receiving-messages-in-a-secret-chat
func CoreAesEncryptEx(plain []byte, nonceLen int, key string) ([]byte, error) {
nonce := make([]byte, nonceLen)
_, err := io.ReadFull(rand.Reader, nonce)
if err != nil {
return nil, err
}
var largeShaRaw []byte
largeShaRaw = append(largeShaRaw, []byte(key)...)
largeShaRaw = append(largeShaRaw, nonce...)
largeShaRawMid := len(largeShaRaw) / 2
msgKeyLarge := sha256.Sum256(largeShaRaw)
msgKey := msgKeyLarge[8:24] //16 bytes
var shaRawA []byte
shaRawA = append(shaRawA, msgKey...)
shaRawA = append(shaRawA, largeShaRaw[:largeShaRawMid]...)
sha256a := sha256.Sum256(shaRawA) //32 bytes
var shaRawB []byte
shaRawB = append(shaRawB, largeShaRaw[largeShaRawMid:]...)
shaRawB = append(shaRawB, msgKey...)
sha256b := sha256.Sum256(shaRawB) //32 bytes
var aesKey []byte //32 bytes AES-256
aesKey = append(aesKey, sha256a[:8]...) //a: 8 bytes
aesKey = append(aesKey, sha256b[8:24]...) //b: 16 bytes
aesKey = append(aesKey, sha256a[24:32]...) //a: 8 bytes
var aesIV []byte // 16 bytes
aesIV = append(aesIV, sha256b[:4]...) //b: 4 bytes
aesIV = append(aesIV, sha256a[12:20]...) //a: 8 bytes
aesIV = append(aesIV, sha256b[28:]...) //b: 4 bytes
block, err := aes.NewCipher(aesKey)
if err != nil {
return nil, err
}
blockSize := block.BlockSize()
ciphertext := PKCS7Padding(plain, blockSize)
mode := cipher.NewCBCEncrypter(block, aesIV)
cipher := make([]byte, nonceLen+len(ciphertext))
copy(cipher, nonce)
mode.CryptBlocks(cipher[nonceLen:], ciphertext)
return cipher, nil
}
func CoreAesDecryptEx(crypt []byte, nonceLen int, key string) ([]byte, error) {
nonce := make([]byte, nonceLen)
copy(nonce, crypt)
var largeShaRaw []byte
largeShaRaw = append(largeShaRaw, []byte(key)...)
largeShaRaw = append(largeShaRaw, nonce...)
largeShaRawMid := len(largeShaRaw) / 2
msgKeyLarge := sha256.Sum256(largeShaRaw)
msgKey := msgKeyLarge[8:24] //16 bytes
var shaRawA []byte
shaRawA = append(shaRawA, msgKey...)
shaRawA = append(shaRawA, largeShaRaw[:largeShaRawMid]...)
sha256a := sha256.Sum256(shaRawA) //32 bytes
var shaRawB []byte
shaRawB = append(shaRawB, largeShaRaw[largeShaRawMid:]...)
shaRawB = append(shaRawB, msgKey...)
sha256b := sha256.Sum256(shaRawB) //32 bytes
var aesKey []byte //32 bytes AES-256
aesKey = append(aesKey, sha256a[:8]...) //a: 8 bytes
aesKey = append(aesKey, sha256b[8:24]...) //b: 16 bytes
aesKey = append(aesKey, sha256a[24:32]...) //a: 8 bytes
var aesIV []byte // 16 bytes
aesIV = append(aesIV, sha256b[:4]...) //b: 4 bytes
aesIV = append(aesIV, sha256a[12:20]...) //a: 8 bytes
aesIV = append(aesIV, sha256b[28:]...) //b: 4 bytes
block, err := aes.NewCipher(aesKey)
if err != nil {
return nil, err
}
realData := crypt[nonceLen:]
blockMode := cipher.NewCBCDecrypter(block, aesIV)
origData := make([]byte, len(realData))
blockMode.CryptBlocks(origData, realData)
return PKCS7UnPadding(origData), nil
}
+27
View File
@@ -0,0 +1,27 @@
package crypt
import (
"stat/common/log"
"testing"
)
var (
PlatPublicKey = `
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCWtWnWmTgLemO+GBMWIX9N5yx2
YMc+4MczBogNdTFjF4rrd/+Y1G9qJ8ZDHR7qjBQgJjzcZrGYRUf2wiY6T9ble9+h
s14pYPpXqkSoD2VOQI4+idb6nRgVQ6rXyMQps7ORcshmEtIS/Ra3aG3XaAFAUCfO
E3nHGyldtNIGvuBTAQIDAQAB
-----END PUBLIC KEY-----`
)
func TestRSA(t *testing.T) {
//data := make([]byte, 0)
testData := "是对的发送打赏打赏打赏打赏的"
rawContext, err := RSAEncryptByPublicKey(PlatPublicKey, []byte(testData))
if err != nil {
log.Error("wnth RSAEncryptByPublicKey err", log.Any("err", err))
return
}
log.Info(string(rawContext))
}
+164
View File
@@ -0,0 +1,164 @@
package crypt
import (
"bytes"
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"stat/common/log"
)
func splitBytesSlice(data []byte, n int) [][]byte {
var chunk []byte
chunks := make([][]byte, 0, len(data)/n+1)
for len(data) >= n {
chunk, data = data[:n], data[n:]
chunks = append(chunks, chunk)
}
if len(data) > 0 {
chunks = append(chunks, data[:])
}
return chunks
}
func rsaBytesToPublicKey(pub []byte) (*rsa.PublicKey, error) {
// fmt.Printf("pubKey is %s", string(pub))
block, _ := pem.Decode(pub)
enc := x509.IsEncryptedPEMBlock(block)
b := block.Bytes
var err error
if enc {
b, err = x509.DecryptPEMBlock(block, nil)
if err != nil {
log.Error("RSABytesToPublicKey DecryptPEMBlock err", log.E(err))
return nil, err
}
}
pk, err := x509.ParsePKIXPublicKey(b)
if err != nil {
log.Error("RSABytesToPublicKey ParsePKIXPublicKey err", log.E(err))
return nil, err
}
if pk_, ok := pk.(*rsa.PublicKey); ok {
return pk_, nil
}
log.Error("RSABytesToPublicKey error input not PublicKey")
return nil, errors.New("Not RSA PublicKey format")
}
func rsaBytesToPrivateKey(priv []byte) (*rsa.PrivateKey, error) {
block, _ := pem.Decode(priv)
enc := x509.IsEncryptedPEMBlock(block)
b := block.Bytes
var err error
if enc {
b, err = x509.DecryptPEMBlock(block, nil)
if err != nil {
log.Error("RSABytesToPrivateKey DecryptPEMBlock err", log.E(err))
return nil, err
}
}
key, err := x509.ParsePKCS8PrivateKey(b)
if err != nil {
log.Error("RSABytesToPrivateKey ParsePKCS8PrivateKey err", log.E(err))
return nil, err
}
if pk_, ok := key.(*rsa.PrivateKey); ok {
return pk_, nil
}
log.Error("RSABytesToPrivateKey error input not PrivateKey")
return nil, errors.New("Not RSA PrivateKey format")
}
// 公钥加密
func RSAEncryptByPublicKey(pemPubKey string, data []byte) ([]byte, error) {
pubKey, err := rsaBytesToPublicKey([]byte(pemPubKey))
if err != nil {
log.Error("RSAEncryptByPublicKey rsaBytesToPublicKey err", log.E(err))
return nil, err
}
partLen := pubKey.N.BitLen()/8 - 11
chunks := splitBytesSlice(data, partLen)
result := bytes.Buffer{}
for _, chunk := range chunks {
cipherSilce, err := rsa.EncryptPKCS1v15(rand.Reader, pubKey, chunk)
if err != nil {
log.Error("RSAEncryptByPublicKey EncryptPKCS1v15 err", log.E(err))
return nil, err
}
result.Write(cipherSilce)
}
return result.Bytes(), nil
}
// 私玥签名
func RSASignByPrivateKey(pemPrivateKey string, data []byte, hash crypto.Hash) ([]byte, error) {
fmt.Printf("pemPrivateKey is %s\n", pemPrivateKey)
privateKey, err := rsaBytesToPrivateKey([]byte(pemPrivateKey))
if err != nil {
log.Error("RSASignByPrivateKey rsaBytesToPrivateKey err", log.E(err))
return nil, err
}
h := hash.New()
h.Write(data)
hashed := make([]byte, 0)
hashed = h.Sum(hashed)
return rsa.SignPKCS1v15(rand.Reader, privateKey, hash, hashed)
}
func RSAVerifyByPublicKey(publicKey string, data, sign []byte, hash crypto.Hash) (err error) {
pubKey, err := rsaBytesToPublicKey([]byte(publicKey))
if err != nil {
log.Error("RSAEncryptByPublicKey rsaBytesToPublicKey err", log.E(err))
return err
}
h := hash.New()
h.Write(data)
hashed := make([]byte, 0)
hashed = h.Sum(hashed)
return rsa.VerifyPKCS1v15(pubKey, hash, hashed, sign)
}
// 公钥解密
func RSADecryptByPrivateKey(pemPrivateKey string, cipherData []byte) ([]byte, error) {
privateKey, err := rsaBytesToPrivateKey([]byte(pemPrivateKey))
if err != nil {
log.Error("RSADecryptByPrivateKey rsaBytesToPrivateKey err", log.E(err))
return nil, err
}
blockLen := privateKey.N.BitLen() / 8
chunks := splitBytesSlice(cipherData, blockLen)
result := bytes.Buffer{}
for _, chunk := range chunks {
plain, err := rsa.DecryptPKCS1v15(rand.Reader, privateKey, chunk)
if err != nil {
log.Error("RSADecryptByPrivateKey DecryptPKCS1v15 err", log.E(err))
return nil, err
}
result.Write(plain)
}
return result.Bytes(), nil
}
// 私玥签名
func RSASignByPrivateKeySHA256(pemPrivateKey string, data []byte, hash crypto.Hash) ([]byte, error) {
fmt.Printf("pemPrivateKey is %s\n", pemPrivateKey)
privateKey, err := rsaBytesToPrivateKey([]byte(pemPrivateKey))
if err != nil {
log.Error("RSASignByPrivateKey rsaBytesToPrivateKey err", log.E(err))
return nil, err
}
h := sha256.New()
h.Write(data)
// hashed := make([]byte, 0)
hashed := h.Sum(nil)
return rsa.SignPSS(rand.Reader, privateKey, hash, hashed, nil)
}
+22
View File
@@ -0,0 +1,22 @@
package consts
type RunMod string
const (
RunModeDev RunMod = "dev"
RunModeTest RunMod = "test"
RunModRelease RunMod = "release"
)
const (
ShowMagic int = 8564
)
var StatusDesc = map[bool]string{
true: "打开",
false: "关闭",
}
var IsShowDesc = map[bool]string{
true: "展示",
false: "不展示",
}
+120
View File
@@ -0,0 +1,120 @@
package redisconst
import (
"fmt"
"strings"
"time"
)
const (
visitLogFmt = "visitlog:%s:%d"
AvSearchPre = "yc_av_search_cache:%s:%d"
PostSearchPre = "yc_post_search_cache:%s:%d"
//SpCache = "sp_cache"
FiveMVisit = "yc_visitFmt:%s"
BloomUser = "yc_bloom_user:%d"
AppUserToken = "yc_app_user_token:%d"
H5AppUserToken = "yc_h5app_user_token:%d"
captchaKey = "yc_captcha_mobile_%s"
CaptchaExpire = 5 * time.Minute
traderChatCacheFmt = "trader:cache:%d"
TraderChatCacheExpire = 2 * time.Minute
mediaM3U8Fmt = "m3u8cache:%s"
MediaM3U8CacheExpire = time.Hour * 24 * 7
//禁止登陆设置
userForbit = "userForbit:%d"
ipForbit = "ipForbit:%s"
//用户关注限制
careLimitKey = "carelimit:%d"
CareLimitKeyExpire = 15 * time.Second
//用户发帖限制
commentLimitKey = "commentlimit:%d"
CommentLimitKeyExpire = 15 * time.Second
//用户firstVipGiftCheck
firstVipGiftCheck = "gameVipCheck:%d"
//每用户访问次数限制
userCallLimit = "userCall:%d"
ipCallLimit = "ipCall:%s"
CallLimitExpire = time.Second * 10
//游戏orderkey
gameOrderKey = "gameOrderKey:%s"
GameOrderKeyExpire = time.Second * 10
)
func BloomUserKey(userId int64) string {
return fmt.Sprintf(BloomUser, userId)
}
func FiveMVisitKey(time string) string {
return fmt.Sprintf(FiveMVisit, time)
}
func UserVisitLogKey(uid int64, t time.Time) string {
return fmt.Sprintf(visitLogFmt, t.Format("20060102"), uid)
}
func AvSearchCacheKey(keyword string, skip int64) string {
return fmt.Sprintf(AvSearchPre, keyword, skip)
}
func PostSearchCacheKey(keyword string, skip int64) string {
return fmt.Sprintf(PostSearchPre, keyword, skip)
}
func MobileCaptchaKey(mobile string) string {
return fmt.Sprintf(captchaKey, mobile)
}
func AppUserTokenKey(userId int64) string {
return fmt.Sprintf(AppUserToken, userId)
}
func H5AppUserTokenKey(userId int64) string {
return fmt.Sprintf(H5AppUserToken, userId)
}
func TraderChatCacheKey(uid uint64) string {
return fmt.Sprintf(traderChatCacheFmt, uid)
}
func MediaM3U8Key(name string) string {
return fmt.Sprintf(mediaM3U8Fmt, name)
}
func UserForbitKey(userId int64) string {
return fmt.Sprintf(userForbit, userId)
}
func CareLimitKey(userId int64) string {
return fmt.Sprintf(careLimitKey, userId)
}
func CommentLimitKey(userId int64) string {
return fmt.Sprintf(commentLimitKey, userId)
}
func FirstVipGiftCheck(userId int64) string {
return fmt.Sprintf(firstVipGiftCheck, userId)
}
func UserCallLimitKey(userId int64) string {
return fmt.Sprintf(userCallLimit, userId)
}
func IpCallLimitKey(ip string) string {
return fmt.Sprintf(ipCallLimit, ip)
}
func IpForbitKey(ip string) string {
ip = strings.ReplaceAll(ip, ":", "-")
return fmt.Sprintf(ipForbit, ip)
}
func GameOrderKey(orderId string) string {
return fmt.Sprintf(gameOrderKey, orderId)
}
+168
View File
@@ -0,0 +1,168 @@
package errors
type CodeType int64
const NotFind = "mongo: no documents in result"
// 错误级别
const (
ParamLevel = 1000 // 参数级
StoreLevel = 3000 // 存储级
ServerLevel = 4000 // 服务级
BusinessLevel = 6000 // 业务逻辑级
)
// app 端用到了
// 1000, 重新登陆
// 4002 短信发送失败
// 6017 手机号码已绑定
// 6023 user_video操作错误
// 这里作出兼容
func (c CodeType) Error() string {
return ""
}
const (
InvalidateToken CodeType = ParamLevel + iota // 不可用的token,客户端需要重新登陆
MethodNotFound // 方法为找到,指gin方法未找到
ApiNotFound // api未找到
Limiting // 限流
NoToken // 无token 1004
)
const (
DBQuery CodeType = StoreLevel + iota
DBInsert
DBUpdate
DBDelete
RedisSet
RedisGet
RedisDel
ProxyPutObject
EtcdGet
EtcdPut
RedisRPush
RedisLPop
)
const (
HttpClient CodeType = ServerLevel + iota // httpclient
TypePointer // 指针类型
SmsLimit // 短信限制
SmsSend // 短信发送失败
SmsTmp // 短信模版
SmsTmpParse // 短信模版解析
UserId // 用户ID
OpenFile // 打开文件
FsErr // seaweedfs
CtxTimeout // context超时
UnmarshalJson // json
FSFiler // fs filer
HttpGet //
ReadData // read data
IOCopy // io copy
Pass // pass
PassConfID // pass config id
Tips // 提示错误
)
const (
// 用户权限
UserPermission CodeType = BusinessLevel + iota
Unknown // 未知错误
InvalidKeyword // 无效的关键字
UserName // 用户名称
Mobile // 手机号码
Captcha // 验证码
CaptchaErr // 验证码错误
Psw // 密码
PayCode // 用户支付密码
Number // 数字类型
String // 字符串类型
VideoId // 视频ID
Progress // 进度
Ids // id数组
CatagoryExist // 视频种类已存在
MobileBinded // 手机号码已绑定
Rate // 返佣金比例
Brokerage // 返佣
Logo // 用户logo
UploadFile //
EmptyFile //
Operate // user_video操作错误
UserDisable //
NickName //
Gender //
InviteCode //
Coat //
ReLogin //
PopType //
AgentBinded //
M3u8FileName //
M3u8Read //
VideoName //
ExchangeCodeErr // 兑换码错误
ChanAcount //
Recharge //
Withdraw //
ExportErr // 导出参数错误
DomainErr // 主域名错误
ForbiddenWord // 禁言
ForbiddenLogin // 禁止登陆
DuplicateLike // 重复点赞
AliPayUnBind // 支付宝未绑定
BankCardUnBind // 银行卡未绑定
VideoUploadExist // 视频重复上传
Task //
PostsNewArgsInvalid // 帖子参数错误
PostDupi // 帖子重复发送
PostNotExisted // 帖子不存在
PostLikeOnLiked // 重复点赞帖子
PostUndoLikeOnUnliked // 尚未点赞,不可取消点赞
PostFavOnFaved // 重复 收藏
PostUndoFavOnUnfaved // 尚未收藏,不可取消收藏
PostDisLikeOnDisLiked // 重复屏蔽帖子
PostUndoDisLikeOnUnliked // 尚未屏蔽,不可取消屏蔽
UserDoNotForSelf // 用户不能对自己进行此项操作
UserWatchDupi // 不可重复关注同一用户
UserNoWatch // 没有关注该用户,不可取消关注
PostRepleNewArgsInvalid // 帖子 回复 参数错误
PostReplyNotExisted // 评论不存在
PostReplyAlready // 评论不存在
NoWatchableUsers // 没有可以关注的用户池
PostReplyLikeOnLiked // 重复点赞帖子
PostReplyUndoLikeOnUnliked // 尚未点赞,不可取消点赞
PostPriceInvalid // 帖子定价异常,不可购买
PostIsBuyed // 帖子已购买,不可重复购买
NoBalance // 余额不足
BalanceErr // 编辑余额错误
NoAccount // 没账户
AccountDisabled // 账户已被封禁
BuySelf // 自己帖子,不用购买
RedisError // 服务器繁忙
Button // 落地页按钮
ReplyTimeShort // 回复间隔时间太短
OperateLog // 日志出错
RoleErr // 角色权限不够
DocExists // 查找的文档已存在
PostUnknownError // 未知错误
SeekScoreConfirm // 商人追分确认
GetOrderInfo // 商人充值订单报错
PromSeq // 商区错误
SignErr // 签名错误
BadRequest // 传参错误
ReplyPoolExisted // 评论已存在
)
//代充专用
const (
TakeChatFail CodeType = iota + 18000 //发起代充聊天失败 18000
GetOrderInfoFail //获取代理充值订单信息失败 18001
AppIDNotExist //代充AppID 不存在 18002
TraderNotExist //此商人不存在 18003
TraderBalanceNotEnough //商人余额不足 18004
TraderHasLocked //商人被禁止登陆 18005
TraderSeekScoreFail //商人追分失败 18006
TraderSeekScoreFailDueNotEnough //商人追分失败 18007
)
+39
View File
@@ -0,0 +1,39 @@
package errors
import (
"fmt"
)
// SrvErr 错误定义
type SrvErr struct {
Code CodeType `json:"code"`
Msg string `json:"msg"`
Tips string `json:"tip,omitempty"`
Cause string `json:"-"`
}
// New create an error
func New(code CodeType, msg string, cause string) *SrvErr {
return &SrvErr{
Code: code,
Msg: msg,
Cause: cause,
}
}
// New create an error
func NewTips(code CodeType, tips string, cause string) *SrvErr {
return &SrvErr{
Code: code,
Msg: tips,
Tips: tips,
Cause: cause,
}
}
func (s *SrvErr) Error() string {
if s == nil {
return ""
}
return fmt.Sprintf("code = %d, msg = %s ,case = %v", s.Code, s.Msg, s.Cause)
}
+24
View File
@@ -0,0 +1,24 @@
package errors
import (
"fmt"
"runtime"
)
func MakeWithErr(code CodeType, err error) error {
pc, fn, line, _ := runtime.Caller(1)
cause := fmt.Sprintf("[error] in %s[%s:%d] %v", runtime.FuncForPC(pc).Name(), fn, line, err.Error())
return New(code, err.Error(), cause)
}
func MakeWithMsg(code CodeType, msg string) error {
pc, fn, line, _ := runtime.Caller(1)
cause := fmt.Sprintf("[error] in %s[%s:%d] %v", runtime.FuncForPC(pc).Name(), fn, line, msg)
return New(code, msg, cause)
}
func MakeWithTips(code CodeType, tips string) error {
pc, fn, line, _ := runtime.Caller(1)
cause := fmt.Sprintf("[error] in %s[%s:%d] %v", runtime.FuncForPC(pc).Name(), fn, line, tips)
return NewTips(code, tips, cause)
}
+30
View File
@@ -0,0 +1,30 @@
package file
import (
"context"
"errors"
"fmt"
"mime/multipart"
"stat/common/proxy"
)
type FileObject struct {
File *multipart.FileHeader
}
// PutObject 上传文件
func PutObject(ctx context.Context, apiUrl string, fo *FileObject) (urlstr string, err error) {
if fo.File == nil {
err = errors.New("invalid file")
return
}
file, err := fo.File.Open()
if err != nil {
err = errors.New(fmt.Sprintf("invalid file %+v", err))
return
}
defer file.Close()
urlstr, err = proxy.PutObject(ctx, apiUrl, fo.File.Filename, file)
return
}
+161
View File
@@ -0,0 +1,161 @@
package genId
import (
"stat/common/genId/lid"
"stat/common/mongos"
)
var roleIdLid *lid.Lid // 角色id
var landingpageIdLid *lid.Lid // 落地页id
var versionLid *lid.Lid // 版本id自增器
var commodityLid *lid.Lid // 商品自增器
var adsLocLid *lid.Lid // 广告位置id自增器
var adsLid *lid.Lid // 广告id自增器
var lfLid *lid.Lid // 楼凤id自增器
var lfComplaintLid *lid.Lid // 楼凤投诉自增器
var modelBrokerLid *lid.Lid // 嫩模经纪人(茶老板)自增器
var modelLid *lid.Lid // 嫩模自增器
var novelLid *lid.Lid // 小说自增器
var audioBook *lid.Lid // 有声小说自增器
var landpageConfig *lid.Lid // 落地页配置id
var appCategory *lid.Lid //
var appLocLid *lid.Lid // 应用中心位置id自增器
func Start() {
var err error
collection := mongos.MgoManager.Database().Collection("generate_id")
roleIdLid, err = lid.NewLid(collection, "role_id", 1)
if err != nil {
panic(err)
}
landingpageIdLid, err = lid.NewLid(collection, "langdingpage_id", 1)
if err != nil {
panic(err)
}
versionLid, err = lid.NewLid(collection, "version_id", 1)
if err != nil {
panic(err)
}
commodityLid, err = lid.NewLid(collection, "commodity_id", 1)
if err != nil {
panic(err)
}
adsLocLid, err = lid.NewLid(collection, "advertise_loc_id", 1)
if err != nil {
panic(err)
}
adsLid, err = lid.NewLid(collection, "advertise_id", 1)
if err != nil {
panic(err)
}
lfLid, err = lid.NewLid(collection, "loufeng_id", 1)
if err != nil {
panic(err)
}
lfComplaintLid, err = lid.NewLid(collection, "loufeng_complaint_id", 1)
if err != nil {
panic(err)
}
modelBrokerLid, err = lid.NewLid(collection, "young_model_broker_id", 1)
if err != nil {
panic(err)
}
modelLid, err = lid.NewLid(collection, "young_model_id", 1)
if err != nil {
panic(err)
}
novelLid, err = lid.NewLid(collection, "novel_id", 100)
if err != nil {
panic(err)
}
audioBook, err = lid.NewLid(collection, "audio_book_id", 100)
if err != nil {
panic(err)
}
landpageConfig, err = lid.NewLid(collection, "landpage_config_id", 100)
if err != nil {
panic(err)
}
appCategory, err = lid.NewLid(collection, "app_category_id", 100)
if err != nil {
panic(err)
}
appLocLid, err = lid.NewLid(collection, "app_loc_id", 100)
if err != nil {
panic(err)
}
}
func GetNextRoleId() int64 {
return roleIdLid.Get()
}
func GetNextLandingpageId() int64 {
return landingpageIdLid.Get()
}
func GetNextVersionId() int64 {
return versionLid.Get()
}
func GetNextCommodity() int64 {
return commodityLid.Get()
}
func GetNextAdsLocId() int64 {
return adsLocLid.Get()
}
func GetNextAdsId() int64 {
return adsLid.Get()
}
func GetNextLfId() int64 {
return lfLid.Get()
}
func GetNextLfComplaintId() int64 {
return lfComplaintLid.Get()
}
func GetNextModelBrokerId() int64 {
return modelBrokerLid.Get()
}
func GetNextModelId() int64 {
return modelLid.Get()
}
func GetNextNovelId() int64 {
return novelLid.Get()
}
func GetNextAudioBookId() int64 {
return audioBook.Get()
}
func GetLandpageConfigId() int64 {
return landpageConfig.Get()
}
func GetAppCategoryId() int64 {
return appCategory.Get()
}
func GetNextAppLocId() int64 {
return appLocLid.Get()
}
+102
View File
@@ -0,0 +1,102 @@
package lid
import (
"context"
"stat/common/mongos"
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
)
const notFindMsg = "mongo: no documents in result"
type IdModel struct {
BusinessId string `bson:"business_id"` //业务id
Step int64 `bson:"step"` //步长
MaxId int64 `bson:"max_id"` //最大id
Description string `bson:"description"` //描述
CreatedAt time.Time `bson:"createdAt"` //创建时间
UpdatedAt time.Time `bson:"updatedAt"` //更改时间
}
type Lid struct {
db *mongo.Collection // 数据库连接
businessId string // 业务id
ch chan int64 // id缓冲池
min, max int64 // id段最小值,最大值
}
// NewLid 创建一个lid,db:数据库连接;businessId:业务id;len:缓冲池大小
func NewLid(db *mongo.Collection, businessId string, len int) (*Lid, error) {
lid := Lid{
db: db,
businessId: businessId,
ch: make(chan int64, len),
}
go lid.productId()
return &lid, nil
}
// Get 获取自增id
func (l *Lid) Get() int64 {
return <-l.ch
}
// productId 生产id
func (l *Lid) productId() {
l.reset()
for {
if l.min >= l.max {
l.reset()
}
l.min++
l.ch <- l.min
}
}
// reset 不断尝试从数据库获取,直到成功
func (l *Lid) reset() {
for {
err := l.getFromDB()
if err == nil {
return
}
time.Sleep(time.Second)
continue
}
}
// getFromDB 从数据库获取id段
func (l *Lid) getFromDB() error {
var (
maxId int64
step int64
idModel IdModel
)
txErr := mongos.Transaction(context.Background(), func(sctx mongo.SessionContext) (err error) {
err = l.db.FindOne(sctx, bson.M{"business_id": l.businessId}).Decode(&idModel)
if err != nil {
if err.Error() == notFindMsg {
_, err = l.db.InsertOne(sctx, bson.M{"business_id": l.businessId, "max_id": 0, "step": 10, "description": "id自增器", "createdAt": time.Now(), "updatedAt": time.Now()})
if err != nil {
return
}
} else {
return
}
}
maxId = idModel.MaxId
step = idModel.Step
_, err = l.db.UpdateOne(sctx, bson.M{"business_id": l.businessId}, bson.M{"$set": bson.M{"max_id": maxId + step}})
if err != nil {
return
}
l.min = maxId
l.max = maxId + step
return
})
return txErr
}
+40
View File
@@ -0,0 +1,40 @@
package gencode
import (
"math/rand"
"time"
)
/**
* 生成邀请码
*/
var (
//所有的字符
ALL_CHARS = []string{
"1", "2", "3", "4", "5",
"6", "7", "8", "9", "A",
"B", "C", "D", "E", "F",
"G", "H", "J", "K",
"L", "M", "N", "O", "P",
"Q", "R", "S", "T", "U",
"V", "W", "X", "Y", "Z"}
aLen = len(ALL_CHARS)
)
const (
//默认长度
DEFAULT_LEN = 6
)
func init() {
rand.Seed(time.Now().Unix())
}
// 生成指定长度的码
func CodeGenerate(len int) (code string) {
for i := 0; i < len; i++ {
index := rand.Intn(aLen)
code += ALL_CHARS[index]
}
return
}
+67
View File
@@ -0,0 +1,67 @@
package common
import (
"fmt"
"runtime/debug"
"stat/common/log"
"sync/atomic"
"time"
)
var (
routinePanicHandler func(string)
routineCount int64
)
func SetPanicHandler(handler func(string)) {
routinePanicHandler = handler
}
func Go(f func()) {
atomic.AddInt64(&routineCount, 1)
go func() {
defer func() {
atomic.AddInt64(&routineCount, -1)
if r := recover(); r != nil {
log.Error("【Warning】server occur error,please attention", log.Any("Detail", r))
if routinePanicHandler != nil {
routinePanicHandler(fmt.Sprintf("[Panic] routineCount:%d \n err: %+v\n", routineCount, r) + string(debug.Stack()))
}
}
}()
f()
}()
}
func GoParam(i int, f func(i int)) {
atomic.AddInt64(&routineCount, 1)
go func(i int) {
defer func() {
atomic.AddInt64(&routineCount, -1)
if r := recover(); r != nil {
log.Error("【Warning】serevr occour error,please attention", log.Any("Detail", r))
if routinePanicHandler != nil {
routinePanicHandler(fmt.Sprintf("[Panic] routineCount:%d \n err: %+v\n", routineCount, r) + string(debug.Stack()))
}
}
}()
f(i)
}(i)
}
// WaitGoQuit 等待go退出,timeoutSec 超时秒数,<=0表示不超时
func WaitGoQuit(timeoutSec int32) {
for {
count := atomic.LoadInt64(&routineCount)
if count == 0 {
return
}
time.Sleep(time.Second)
if timeoutSec > 0 {
timeoutSec--
if timeoutSec == 0 {
return
}
}
}
}
+34
View File
@@ -0,0 +1,34 @@
package googauth
import (
"github.com/dgryski/dgoogauth"
)
const windowSize = 30
const htopWindowSize = 5
var HotpCfg = dgoogauth.OTPConfig{
Secret: "MRSHM6LTMFYHAMJSGM2DKNQ=",
HotpCounter: 1,
WindowSize: htopWindowSize,
}
func New(secret string, name string) string {
cfg := &dgoogauth.OTPConfig{
Secret: secret,
WindowSize: windowSize,
}
return cfg.ProvisionURI(name)
}
func Verify(secret string, pwd string) (bool, error) {
cfg := &dgoogauth.OTPConfig{
Secret: secret,
WindowSize: windowSize,
}
return cfg.Authenticate(pwd)
}
func VerifyHtop(cfg *dgoogauth.OTPConfig, authCode string, counter int) (bool, error) {
return cfg.Authenticate(authCode)
}
+93
View File
@@ -0,0 +1,93 @@
package googauth
import (
"encoding/base32"
"fmt"
"stat/common"
"testing"
"github.com/dgryski/dgoogauth"
)
func TestHotp(t *testing.T) {
secret := base32.StdEncoding.EncodeToString([]byte(fmt.Sprintf("admin%s", common.RandStr(10))))
fmt.Printf(secret)
//d := int64(rand.Intn(100000))
//randstr := strconv.FormatInt(d, 10)
//ra := strconv.FormatInt(120868, 10)
////serc := NewHotp("MFSG22LOORUXAVTINJRFGZ2Q", randstr)
//res, _ := VerifyHotp("MFSG22LOORUXAVTINJRFGZ2Q", ra)
////fmt.Println(err)
////fmt.Println(res)
////fmt.Println(serc)
//fmt.Println(res)
//h := NewHOTP([]byte("abcdefghijk"), 6)
//fmt.Println(h.GenerateCode())
//fmt.Println(h)
}
// func TestRandBase(t *testing.T) {
// for i := 0; i < 10; i++ {
// tn := common.RandIntBase(1564, 1, 5000)
// fmt.Println(tn)
// h := NewHOTP([]byte("abcdefghijk"), 5)
// code := h.GenerateCode(uint64(tn))
// res := h.VerifyCode(code, uint64(tn))
// fmt.Println(res)
// }
// }
//func TestCode(t *testing.T) {
// for i := 0; i < 10; i++ {
// count := common.RandIntBase(12345678, 1, 99999999)
// fmt.Println(count)
// dd := dgoogauth.ComputeCode("MRSHM6LTMFYHAMJSGM2DKNQ=", 1234)
// authCode := strconv.FormatInt(int64(dd), 10)
// re, _ := VerifyHotp("MRSHM6LTMFYHAMJSGM2DKNQ=", authCode, 1234)
// fmt.Println(re)
// }
//}
func TestHotpCode(t *testing.T) {
var HotpCfg = dgoogauth.OTPConfig{
Secret: "MRSHM6LTMFYHAMJSGM2DKNQ=",
HotpCounter: 1,
WindowSize: 3,
}
var cotp dgoogauth.OTPConfig
// reuse our test values from above
// perhaps create more?
cotp.Secret = "2SH3V3GDW7ZNMGYE"
cotp.HotpCounter = 1
cotp.WindowSize = 10
//var counterCodes = []struct {
// code int
// result bool
// counter int
//}{
// { /* 1 */ 293240, true, 2}, // increments on success
// { /* 1 */ 293240, false, 3}, // and failure
// { /* 5 */ 932068, true, 6}, // inside of window
// { /* 10 */ 481725, false, 7}, // outside of window
// { /* 10 */ 481725, false, 8}, // outside of window
// { /* 10 */ 481725, true, 11}, // now inside of window
//}
//for _, s := range counterCodes {
// authCode := strconv.FormatInt(int64(s.code), 10)
// res, _ := cotp.Authenticate(authCode)
// fmt.Println(res)
//}
for i := 0; i < 5; i++ {
res, _ := VerifyHtop(&HotpCfg, "807708", 322904702)
//res2, _ := cotp.Authenticate("293240")
fmt.Println(res)
//fmt.Println(res2)
//fmt.Println(cotp)
}
}
+67
View File
@@ -0,0 +1,67 @@
package googauth
import (
"crypto/hmac"
"crypto/sha1"
"encoding/binary"
"strconv"
)
// HOTP implementation
type HOTP struct {
secret []byte
digits int
}
// At generate code
func (h HOTP) At(counter uint64) string {
counterBytes := make([]byte, 8)
binary.BigEndian.PutUint64(counterBytes, counter)
hash := hmac.New(sha1.New, h.secret)
hash.Write(counterBytes)
hs := hash.Sum(nil)
offset := hs[19] & 0x0f
binCodeBytes := make([]byte, 4)
binCodeBytes[0] = hs[offset] & 0x7f
binCodeBytes[1] = hs[offset+1] & 0xff
binCodeBytes[2] = hs[offset+2] & 0xff
binCodeBytes[3] = hs[offset+3] & 0xff
binCode := binary.BigEndian.Uint32(binCodeBytes)
mod := uint32(1)
for i := 0; i < h.digits; i++ {
mod *= 10
}
code := binCode % mod
codeString := strconv.FormatUint(uint64(code), 10)
if len(codeString) < h.digits {
paddingByteLength := h.digits - len(codeString)
paddingBytes := make([]byte, paddingByteLength)
for i := 0; i < paddingByteLength; i++ {
paddingBytes[i] = '0'
}
codeString = string(paddingBytes) + codeString
}
return codeString
}
// Verify verify OTP code
func (h HOTP) VerifyCode(code string, counter uint64) bool {
realCode := h.At(counter)
if realCode == code {
return true
}
return false
}
// Verify verify OTP code
func (h HOTP) GenerateCode(counter uint64) string {
return h.At(counter)
}
// NewHOTP generate new HOTP instance
func NewHOTP(secret []byte, digits int) (h *HOTP) {
h = new(HOTP)
h.secret = secret
h.digits = digits
return
}
+190
View File
@@ -0,0 +1,190 @@
package http
import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"mime/multipart"
"net/http"
"reflect"
"time"
)
var (
client = http.Client{
Timeout: 30 * time.Second,
Transport: &http.Transport{
MaxIdleConnsPerHost: 5,
MaxConnsPerHost: 100,
},
}
)
type ByteSize int64
const (
_ = iota // ignore first value by assigning to blank identifier
KB ByteSize = 1 << (10 * iota)
MB
)
var (
outOfBodySizeErr = errors.New("out of body size limit")
)
type ResponseBody struct {
StatusCode int
Data []byte
}
type RequestFormData map[string]string
// Post
// Note: 只会读取小于1M的数据
func Post(url string, header map[string]string, body io.Reader) (fr *ResponseBody, err error) {
fr, err = do(http.MethodPost, url, header, body, int64(1024*MB))
return
}
// PostFormData 提交标准的form表单
func PostFormData(url string, fromdata RequestFormData) (fr *ResponseBody, err error) {
var b bytes.Buffer
writer := multipart.NewWriter(&b)
for k, v := range fromdata {
if err = writer.WriteField(k, v); err != nil {
return
}
}
if err = writer.Close(); err != nil {
return
}
header := map[string]string{"Content-Type": writer.FormDataContentType()}
fr, err = do(http.MethodPost, url, header, &b, 2*int64(KB))
return
}
// Get
// Note: 只会读取小于1M的数据
func Get(url string, header map[string]string) (fr *ResponseBody, err error) {
fr, err = do(http.MethodGet, url, header, nil, int64(KB))
return
}
// GetLargeFile 获取大文件。2M
func GetLargeFile(url string, header map[string]string) (fr *ResponseBody, err error) {
fr, err = do(http.MethodGet, url, header, nil, int64(20*MB))
return
}
func do(method string, url string, header map[string]string, body io.Reader, size int64) (fr *ResponseBody, err error) {
req, err := http.NewRequest(method, url, body)
if err != nil {
return
}
for k, v := range header {
req.Header.Set(k, v)
}
resp, err := client.Do(req)
if err != nil {
return
}
defer resp.Body.Close()
if resp.ContentLength > size {
err = outOfBodySizeErr
return
}
data, err := ioutil.ReadAll(io.LimitReader(resp.Body, size))
if err != nil {
return
}
fr = &ResponseBody{
StatusCode: resp.StatusCode,
Data: data,
}
return
}
// 推送对象到文件服务器
func PostObject(ctx context.Context, url string, header map[string]string, body []byte, objName string) (string, error) {
base64data := base64.StdEncoding.EncodeToString(body)
req := struct {
Name string `json:"fileName"`
Data string `json:"fileData"`
}{
Name: objName,
Data: base64data,
}
jsonBytes, _ := json.Marshal(req)
resp, err := Post(url, header, bytes.NewReader(jsonBytes))
if err != nil {
return "", err
}
type Obj struct {
Domain string `json:"domain"`
Name string `json:"fileName"`
}
fsResp := struct {
Code int `json:"code"`
Object Obj `json:"data"`
Msg string `json:"msg"`
}{}
if err = json.Unmarshal(resp.Data, &fsResp); err != nil {
return "", err
}
if fsResp.Code != 200 || fsResp.Object.Name == "" {
return "", fmt.Errorf("上传fs失败")
}
return fsResp.Object.Name, nil
}
func PostWithBind(ctx context.Context, bind interface{}, url string, body io.Reader) error {
if err := verifyBind(bind); err != nil {
return err
}
req, err := http.NewRequestWithContext(ctx, "POST", url, body)
if err != nil {
return err
}
req.Header.Add("Content-Type", "application/json")
resp, err := client.Do(req)
if err != nil {
return err
}
defer func() { _ = resp.Body.Close() }()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("response status code:%d", resp.StatusCode)
}
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
return json.Unmarshal(data, bind)
}
func verifyBind(bind interface{}) error {
bType := reflect.TypeOf(bind)
if bType.Kind() != reflect.Ptr {
return errors.New("bind must be a Ptr")
}
return nil
}
+16
View File
@@ -0,0 +1,16 @@
package http
import "fmt"
func ExampleGet() {
urlstr := "https://palce.hzbeisheng.com/api/sms"
data, err := Get(urlstr, nil)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(data.Data))
// Output:
}
+110
View File
@@ -0,0 +1,110 @@
package httpUtils
import (
"bytes"
"errors"
"io"
"io/ioutil"
"mime/multipart"
"net/http"
"time"
)
var (
client = http.Client{
Timeout: 30 * time.Second,
Transport: &http.Transport{
MaxIdleConnsPerHost: 5,
MaxConnsPerHost: 100,
},
}
)
type ByteSize int64
const (
_ = iota // ignore first value by assigning to blank identifier
KB ByteSize = 1 << (10 * iota)
MB
)
var (
outOfBodySizeErr = errors.New("out of body size limit")
)
type ResponseBody struct {
StatusCode int
Data []byte
}
type RequestFormData map[string]string
// Post
// Note: 只会读取小于1M的数据
func Post(url string, header map[string]string, body io.Reader) (fr *ResponseBody, err error) {
fr, err = do(http.MethodPost, url, header, body, int64(1024*MB))
return
}
// PostFormData 提交标准的form表单
func PostFormData(url string, fromdata RequestFormData) (fr *ResponseBody, err error) {
var b bytes.Buffer
writer := multipart.NewWriter(&b)
for k, v := range fromdata {
if err = writer.WriteField(k, v); err != nil {
return
}
}
if err = writer.Close(); err != nil {
return
}
header := map[string]string{"Content-Type": writer.FormDataContentType()}
fr, err = do(http.MethodPost, url, header, &b, 2*int64(KB))
return
}
// Get
// Note: 只会读取小于1M的数据
func Get(url string, header map[string]string) (fr *ResponseBody, err error) {
fr, err = do(http.MethodGet, url, header, nil, int64(KB))
return
}
// GetLargeFile 获取大文件。8M
func GetLargeFile(url string, header map[string]string) (fr *ResponseBody, err error) {
fr, err = do(http.MethodGet, url, header, nil, int64(8*MB))
return
}
func do(method string, url string, header map[string]string, body io.Reader, size int64) (fr *ResponseBody, err error) {
req, err := http.NewRequest(method, url, body)
if err != nil {
return
}
for k, v := range header {
req.Header.Set(k, v)
}
resp, err := client.Do(req)
if err != nil {
return
}
defer resp.Body.Close()
if resp.ContentLength > size {
err = outOfBodySizeErr
return
}
data, err := ioutil.ReadAll(io.LimitReader(resp.Body, size))
if err != nil {
return
}
fr = &ResponseBody{
StatusCode: resp.StatusCode,
Data: data,
}
return
}
+18
View File
@@ -0,0 +1,18 @@
package httpUtils
import (
"fmt"
)
func ExampleGet() {
urlstr := "https://palce.hzbeisheng.com/api/sms"
data, err := Get(urlstr, nil)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(data.Data))
// Output:
}
+303
View File
@@ -0,0 +1,303 @@
package httpUtils
import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"stat/common/log"
"strconv"
"github.com/ddliu/go-httpclient"
)
// HTTPClient htttp客户端
type HTTPClient struct {
client *httpclient.HttpClient
}
type ProxyCfg struct {
Host string
Timeout int //秒
Source string
IsActive bool //是否开启代理
}
func (p *ProxyCfg) Build(rawurl string, rawHeaders map[string]string) (string, map[string]string, error) {
if !p.IsActive {
return rawurl, rawHeaders, nil
}
u, err := url.Parse(rawurl)
if err != nil {
return "", nil, err
}
target := url.URL{Scheme: u.Scheme, Host: u.Host}
u.Host = p.Host
u.Scheme = "http"
m := make(map[string]string)
m["X-Proxy-Target-Host"] = target.String()
m["X-Proxy-Source"] = "ys"
if p.Timeout > 0 {
m["X-Proxy-Timeout"] = strconv.Itoa(p.Timeout)
}
if len(rawHeaders) > 0 {
for k, v := range rawHeaders {
m[k] = v
}
}
return u.String(), m, nil
}
type httpClientPool struct {
cliPool map[string]*httpclient.HttpClient
poolSize int
offset int
}
// HTTPResponse http响应
type HTTPResponse struct {
StatusCode int `json:"statusCode"`
Body *httpclient.Response `json:"boby"`
}
var httpCliPool = httpClientPool{cliPool: map[string]*httpclient.HttpClient{}, poolSize: 50}
func New() *HTTPClient {
h := httpclient.NewHttpClient().Defaults(httpclient.Map{
httpclient.OPT_CONNECTTIMEOUT: 60,
httpclient.OPT_TIMEOUT: 300,
})
return &HTTPClient{client: h}
}
func Begin() *HTTPClient {
h := httpclient.NewHttpClient().Defaults(httpclient.Map{
httpclient.OPT_CONNECTTIMEOUT: 10,
httpclient.OPT_TIMEOUT: 60,
})
return &HTTPClient{client: h.Begin()}
}
// GetHTTPClient 获取httpclient
func Client() *HTTPClient {
cliID := "CLIENT-ID-"
if len(httpCliPool.cliPool) < httpCliPool.poolSize {
h := httpclient.NewHttpClient().Defaults(httpclient.Map{
httpclient.OPT_CONNECTTIMEOUT: 10,
httpclient.OPT_TIMEOUT: 120,
})
cliID = cliID + strconv.FormatInt(int64(len(httpCliPool.cliPool)+1), 10)
httpCliPool.cliPool[cliID] = h
httpCliPool.offset = httpCliPool.poolSize
return &HTTPClient{client: h}
}
if httpCliPool.offset == httpCliPool.poolSize+1 {
httpCliPool.offset = 1
}
cliID = cliID + strconv.FormatInt(int64(httpCliPool.offset), 10)
httpCliPool.offset = httpCliPool.offset + 1
h, ok := httpCliPool.cliPool[cliID]
if !ok || h == nil {
h = httpclient.NewHttpClient().WithOption(httpclient.OPT_TIMEOUT, 60)
httpCliPool.cliPool[cliID] = h
return &HTTPClient{client: h}
}
return &HTTPClient{client: h}
}
func jsonUnmarshalResp(bind interface{}, resp *HTTPResponse) error {
defer func() { _ = resp.Body.Body.Close() }()
if bind == nil { //传入空表示只执行 defer
return nil
}
b, err := resp.Body.ReadAll()
if err != nil {
log.Error("httputil jsonUnmarshalResp readall err", log.E(err))
return err
}
err = json.Unmarshal(b, bind)
if err != nil {
log.Error("httputil jsonUnmarshalResp Unmarshal err", log.E(err))
}
return err
}
// Get Get方法
func (h *HTTPClient) Get(url string, headers map[string]string, params ...interface{}) (*HTTPResponse, error) {
h.client.WithHeaders(headers)
response, err := h.client.Get(url, params...)
if err != nil {
log.Error(fmt.Sprintf("http client Get method failed %+v:", err))
return nil, err
}
return &HTTPResponse{StatusCode: response.StatusCode, Body: response}, nil
}
func (h *HTTPClient) PGet(p *ProxyCfg, url string, headers map[string]string, params ...interface{}) (*HTTPResponse, error) {
purl, pheaders, err := p.Build(url, headers)
if err != nil {
return nil, err
}
return h.Get(purl, pheaders, params)
}
// Get GetBytes
func (h *HTTPClient) GetBytes(url string, headers map[string]string, params ...interface{}) ([]byte, error) {
h.client.WithHeaders(headers)
response, err := h.client.Get(url, params...)
if err != nil {
log.Error(fmt.Sprintf("http client Get method failed %+v:", err))
return nil, err
}
data, err := response.ReadAll()
if err != nil {
log.ZapLog.Error("resp readAll errror", log.Any("Error", err))
return data, err
}
defer func() {
_ = response.Body.Close()
}()
return data, nil
}
// GetWithJResp 结果json.Unmarshal到bind中
func (h *HTTPClient) GetWithJResp(bind interface{}, url string, headers map[string]string, params ...interface{}) (int, error) {
resp, err := h.Get(url, headers, params...)
if err != nil {
return 0, err
}
if resp.StatusCode == http.StatusOK {
err = jsonUnmarshalResp(bind, resp)
}
return resp.StatusCode, err
}
func (h *HTTPClient) PGetWithJResp(p *ProxyCfg, bind interface{}, url string, headers map[string]string, params ...interface{}) (int, error) {
purl, pheaders, err := p.Build(url, headers)
if err != nil {
return 0, err
}
return h.GetWithJResp(bind, purl, pheaders, params...)
}
// Post Post方法
func (h *HTTPClient) Post(url string, headers map[string]string, params interface{}) (*HTTPResponse, error) {
h.client.WithHeaders(headers)
response, err := h.client.Post(url, params)
if err != nil {
log.Error(fmt.Sprintf("http client Post method failed %+v:", err))
return nil, err
}
return &HTTPResponse{StatusCode: response.StatusCode, Body: response}, nil
}
func (h *HTTPClient) PPost(p *ProxyCfg, bind interface{}, url string, headers map[string]string, params interface{}) (*HTTPResponse, error) {
purl, pheaders, err := p.Build(url, headers)
if err != nil {
return nil, err
}
return h.Post(purl, pheaders, params)
}
// POSTJson PostJson方法
func (h *HTTPClient) POSTJson(url string, headers map[string]string, data interface{}) (*HTTPResponse, error) {
h.client.WithHeaders(headers)
response, err := h.client.PostJson(url, data)
if err != nil {
log.Error(fmt.Sprintf("http client POSTJson method failed %+v:", err))
return nil, err
}
return &HTTPResponse{StatusCode: response.StatusCode, Body: response}, nil
}
// POSTJson PostJson方法, bind json.Unmarshal
func (h *HTTPClient) POSTJsonWithJResp(bind interface{}, url string, headers map[string]string, data interface{}) (int, error) {
resp, err := h.POSTJson(url, headers, data)
if err != nil {
log.Error("POSTJsonWithResp err", log.E(err))
return 0, err
}
if resp.StatusCode == http.StatusOK {
err = jsonUnmarshalResp(bind, resp)
}
return resp.StatusCode, err
}
func (h *HTTPClient) PPOSTJsonWithJResp(p *ProxyCfg, bind interface{}, url string, headers map[string]string, data interface{}) (int, error) {
if p == nil {
return 0, fmt.Errorf("ProxyCfg is nil")
}
purl, pheaders, err := p.Build(url, headers)
if err != nil {
return 0, err
}
return h.POSTJsonWithJResp(bind, purl, pheaders, data)
}
// PostMultipart PostMultipart 上传文件
func (h *HTTPClient) PostMultipart(url string, headers map[string]string, params interface{}) (*HTTPResponse, error) {
h.client.WithHeaders(headers)
response, err := h.client.PostMultipart(url, params)
if err != nil {
log.Error(fmt.Sprintf("http client PostMultipart method failed %+v:", err))
return nil, err
}
return &HTTPResponse{StatusCode: response.StatusCode, Body: response}, nil
}
// Put 流式传输 可用于上传文件
func (h *HTTPClient) Put(url string, headers map[string]string, body io.Reader) (*HTTPResponse, error) {
h.client.WithHeaders(headers)
response, err := h.client.Put(url, body)
if err != nil {
log.Error(fmt.Sprintf("http client PostMultipart method failed %+v:", err))
return nil, err
}
return &HTTPResponse{StatusCode: response.StatusCode, Body: response}, nil
}
// PutJSON PutJSON方法
func (h *HTTPClient) PutJSON(url string, headers map[string]string, data interface{}) (*HTTPResponse, error) {
h.client.WithHeaders(headers)
response, err := h.client.PutJson(url, data)
if err != nil {
log.Error(fmt.Sprintf("http client PostMultipart method failed %+v:", err))
return nil, err
}
return &HTTPResponse{StatusCode: response.StatusCode, Body: response}, nil
}
// Header Header
func (h *HTTPClient) Header(url string) (*HTTPResponse, error) {
response, err := h.client.Head(url)
if err != nil {
log.Error(fmt.Sprintf("http client PostMultipart method failed %+v:", err))
return nil, err
}
return &HTTPResponse{StatusCode: response.StatusCode, Body: response}, nil
}
// POSTWithJResp 结果json.Unmarshal到bind中 用于上传文件, content-type :"application/x-www-form-urlencoded" 如参数以@开头 则为上传文件
func (h *HTTPClient) POSTWithJResp(bind interface{}, url string, headers map[string]string, params interface{}) (int, error) {
resp, err := h.Post(url, headers, params)
if err != nil {
return 0, err
}
if resp.StatusCode == http.StatusOK {
err = jsonUnmarshalResp(bind, resp)
}
return resp.StatusCode, err
}
// POSTWithJResp 结果json.Unmarshal到bind中 用于上传文件, content-type :"application/x-www-form-urlencoded" 如参数以@开头 则为上传文件
func (h *HTTPClient) PPOSTWithJResp(p *ProxyCfg, bind interface{}, url string, headers map[string]string, params interface{}) (int, error) {
resp, err := h.PPost(p, bind, url, headers, params)
if err != nil {
return 0, err
}
if resp.StatusCode == http.StatusOK {
err = jsonUnmarshalResp(bind, resp)
}
return resp.StatusCode, err
}
+307
View File
@@ -0,0 +1,307 @@
package httputil
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"stat/common/log"
"strconv"
"github.com/ddliu/go-httpclient"
)
//HTTPClient htttp客户端
type HTTPClient struct {
client *httpclient.HttpClient
}
type ProxyCfg struct {
Host string
Timeout int //秒
Source string
IsActive bool //是否开启代理
}
func NewHTTPClient(client *httpclient.HttpClient) *HTTPClient {
return &HTTPClient{client}
}
func (p *ProxyCfg) Build(rawurl string, rawHeaders map[string]string) (string, map[string]string, error) {
if !p.IsActive {
return rawurl, rawHeaders, nil
}
u, err := url.Parse(rawurl)
if err != nil {
return "", nil, err
}
target := url.URL{Scheme: u.Scheme, Host: u.Host}
u.Host = p.Host
u.Scheme = "http"
m := make(map[string]string)
m["X-Proxy-Target-Host"] = target.String()
m["X-Proxy-Source"] = "ys"
if p.Timeout > 0 {
m["X-Proxy-Timeout"] = strconv.Itoa(p.Timeout)
}
if len(rawHeaders) > 0 {
for k, v := range rawHeaders {
m[k] = v
}
}
return u.String(), m, nil
}
type httpClientPool struct {
cliPool map[string]*httpclient.HttpClient
poolSize int
offset int
}
//HTTPResponse http响应
type HTTPResponse struct {
StatusCode int `json:"statusCode"`
Body *httpclient.Response `json:"boby"`
}
var httpCliPool = httpClientPool{cliPool: map[string]*httpclient.HttpClient{}, poolSize: 50}
func New() *HTTPClient {
h := httpclient.NewHttpClient().Defaults(httpclient.Map{
httpclient.OPT_CONNECTTIMEOUT: 5,
httpclient.OPT_TIMEOUT: 60,
})
return &HTTPClient{client: h}
}
func NewCtx(ctx context.Context) *HTTPClient {
h := httpclient.NewHttpClient().WithOptions(httpclient.Map{
httpclient.OPT_CONNECTTIMEOUT: 5,
httpclient.OPT_TIMEOUT: 60,
httpclient.OPT_CONTEXT: ctx,
})
return &HTTPClient{client: h.Begin()}
}
//GetHTTPClient 获取httpclient
func Client() *HTTPClient {
cliID := "CLIENT-ID-"
if len(httpCliPool.cliPool) < httpCliPool.poolSize {
h := httpclient.NewHttpClient().Defaults(httpclient.Map{
httpclient.OPT_CONNECTTIMEOUT: 5,
httpclient.OPT_TIMEOUT: 60,
})
cliID = cliID + strconv.FormatInt(int64(len(httpCliPool.cliPool)+1), 10)
httpCliPool.cliPool[cliID] = h
httpCliPool.offset = httpCliPool.poolSize
return &HTTPClient{client: h}
}
if httpCliPool.offset == httpCliPool.poolSize+1 {
httpCliPool.offset = 1
}
cliID = cliID + strconv.FormatInt(int64(httpCliPool.offset), 10)
httpCliPool.offset = httpCliPool.offset + 1
h, ok := httpCliPool.cliPool[cliID]
if !ok || h == nil {
h = httpclient.NewHttpClient().WithOption(httpclient.OPT_TIMEOUT, 60)
httpCliPool.cliPool[cliID] = h
return &HTTPClient{client: h}
}
return &HTTPClient{client: h}
}
func jsonUnmarshalResp(bind interface{}, resp *HTTPResponse) error {
defer func() { _ = resp.Body.Body.Close() }()
if bind == nil { //传入空表示只执行 defer
return nil
}
b, err := resp.Body.ReadAll()
if err != nil {
log.Error("httputil jsonUnmarshalResp readall err", log.E(err))
return err
}
err = json.Unmarshal(b, bind)
if err != nil {
log.Error("httputil jsonUnmarshalResp Unmarshal err", log.E(err))
}
return err
}
// Get Get方法
func (h *HTTPClient) Get(url string, headers map[string]string, params ...interface{}) (*HTTPResponse, error) {
h.client.WithHeaders(headers)
response, err := h.client.Get(url, params...)
if err != nil {
log.Error(fmt.Sprintf("http client Get method failed %+v:", err))
return nil, err
}
return &HTTPResponse{StatusCode: response.StatusCode, Body: response}, nil
}
func (h *HTTPClient) PGet(p *ProxyCfg, url string, headers map[string]string, params ...interface{}) (*HTTPResponse, error) {
purl, pheaders, err := p.Build(url, headers)
if err != nil {
return nil, err
}
return h.Get(purl, pheaders, params)
}
// Get GetBytes
func (h *HTTPClient) GetBytes(url string, headers map[string]string, params ...interface{}) ([]byte, error) {
h.client.WithHeaders(headers)
response, err := h.client.Get(url, params...)
if err != nil {
log.Error(fmt.Sprintf("http client Get method failed %+v:", err))
return nil, err
}
data, err := response.ReadAll()
if err != nil {
log.ZapLog.Error("resp readAll errror", log.Any("Error", err))
return data, err
}
defer func() {
_ = response.Body.Close()
}()
return data, nil
}
// GetWithJResp 结果json.Unmarshal到bind中
func (h *HTTPClient) GetWithJResp(bind interface{}, url string, headers map[string]string, params ...interface{}) (int, error) {
resp, err := h.Get(url, headers, params...)
if err != nil {
return 0, err
}
if resp.StatusCode == http.StatusOK {
err = jsonUnmarshalResp(bind, resp)
}
return resp.StatusCode, err
}
func (h *HTTPClient) PGetWithJResp(p *ProxyCfg, bind interface{}, url string, headers map[string]string, params ...interface{}) (int, error) {
purl, pheaders, err := p.Build(url, headers)
if err != nil {
return 0, err
}
return h.GetWithJResp(bind, purl, pheaders, params...)
}
//Post Post方法
func (h *HTTPClient) Post(url string, headers map[string]string, params interface{}) (*HTTPResponse, error) {
h.client.WithHeaders(headers)
response, err := h.client.Post(url, params)
if err != nil {
log.Error(fmt.Sprintf("http client Post method failed %+v:", err))
return nil, err
}
return &HTTPResponse{StatusCode: response.StatusCode, Body: response}, nil
}
func (h *HTTPClient) PPost(p *ProxyCfg, bind interface{}, url string, headers map[string]string, params interface{}) (*HTTPResponse, error) {
purl, pheaders, err := p.Build(url, headers)
if err != nil {
return nil, err
}
return h.Post(purl, pheaders, params)
}
//POSTJson PostJson方法
func (h *HTTPClient) POSTJson(url string, headers map[string]string, data interface{}) (*HTTPResponse, error) {
h.client.WithHeaders(headers)
response, err := h.client.PostJson(url, data)
if err != nil {
log.Error(fmt.Sprintf("http client POSTJson method failed %+v:", err))
return nil, err
}
return &HTTPResponse{StatusCode: response.StatusCode, Body: response}, nil
}
//POSTJson PostJson方法, bind json.Unmarshal
func (h *HTTPClient) POSTJsonWithJResp(bind interface{}, url string, headers map[string]string, data interface{}) (int, error) {
resp, err := h.POSTJson(url, headers, data)
if err != nil {
log.Error("POSTJsonWithResp err", log.E(err))
return 0, err
}
err = jsonUnmarshalResp(bind, resp)
return resp.StatusCode, err
}
func (h *HTTPClient) PPOSTJsonWithJResp(p *ProxyCfg, bind interface{}, url string, headers map[string]string, data interface{}) (int, error) {
if p == nil {
return 0, fmt.Errorf("ProxyCfg is nil")
}
purl, pheaders, err := p.Build(url, headers)
if err != nil {
return 0, err
}
return h.POSTJsonWithJResp(bind, purl, pheaders, data)
}
//PostMultipart PostMultipart 上传文件
func (h *HTTPClient) PostMultipart(url string, headers map[string]string, params interface{}) (*HTTPResponse, error) {
h.client.WithHeaders(headers)
response, err := h.client.PostMultipart(url, params)
if err != nil {
log.Error(fmt.Sprintf("http client PostMultipart method failed %+v:", err))
return nil, err
}
return &HTTPResponse{StatusCode: response.StatusCode, Body: response}, nil
}
//Put 流式传输 可用于上传文件
func (h *HTTPClient) Put(url string, headers map[string]string, body io.Reader) (*HTTPResponse, error) {
h.client.WithHeaders(headers)
response, err := h.client.Put(url, body)
if err != nil {
log.Error(fmt.Sprintf("http client PostMultipart method failed %+v:", err))
return nil, err
}
return &HTTPResponse{StatusCode: response.StatusCode, Body: response}, nil
}
//PutJSON PutJSON方法
func (h *HTTPClient) PutJSON(url string, headers map[string]string, data interface{}) (*HTTPResponse, error) {
h.client.WithHeaders(headers)
response, err := h.client.PutJson(url, data)
if err != nil {
log.Error(fmt.Sprintf("http client PostMultipart method failed %+v:", err))
return nil, err
}
return &HTTPResponse{StatusCode: response.StatusCode, Body: response}, nil
}
//Header Header
func (h *HTTPClient) Header(url string) (*HTTPResponse, error) {
response, err := h.client.Head(url)
if err != nil {
log.Error(fmt.Sprintf("http client PostMultipart method failed %+v:", err))
return nil, err
}
return &HTTPResponse{StatusCode: response.StatusCode, Body: response}, nil
}
// POSTWithJResp 结果json.Unmarshal到bind中 用于上传文件, content-type :"application/x-www-form-urlencoded" 如参数以@开头 则为上传文件
func (h *HTTPClient) POSTWithJResp(bind interface{}, url string, headers map[string]string, params interface{}) (int, error) {
resp, err := h.Post(url, headers, params)
if err != nil {
return 0, err
}
if resp.StatusCode == http.StatusOK {
err = jsonUnmarshalResp(bind, resp)
}
return resp.StatusCode, err
}
// POSTWithJResp 结果json.Unmarshal到bind中 用于上传文件, content-type :"application/x-www-form-urlencoded" 如参数以@开头 则为上传文件
func (h *HTTPClient) PPOSTWithJResp(p *ProxyCfg, bind interface{}, url string, headers map[string]string, params interface{}) (int, error) {
resp, err := h.PPost(p, bind, url, headers, params)
if err != nil {
return 0, err
}
if resp.StatusCode == http.StatusOK {
err = jsonUnmarshalResp(bind, resp)
}
return resp.StatusCode, err
}
+105
View File
@@ -0,0 +1,105 @@
package httputil
import (
"encoding/json"
"fmt"
"os"
"strconv"
"testing"
)
type ResponseInfo struct {
Gzipped bool `json:"gzipped"`
Method string `json:"method"`
Origin string `json:"origin"`
Useragent string `json:"user-agent"` // http://httpbin.org/user-agent
Form map[string]string `json:"form"`
Files map[string]string `json:"files"`
Headers map[string]string `json:"headers"`
Cookies map[string]string `json:"cookies"`
}
var info ResponseInfo
type Resport struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data interface{} `json:"data"`
}
func Test_Get_Httpcli(t *testing.T) {
client := Client()
var headers = map[string]string{"auth_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVjZTJiNzNmZTI3Y2YyMmE4ZjQyMTVkMCIsInVzZXJOYW1lIjoidHVib2xpbmcxNjgiLCJpc0xvZ2luIjp0cnVlLCJsb2dpblRpbWUiOjE1NjQxNDYwNzI5MDUsImlhdCI6MTU2NDE0NjA3Mn0.VQICBk71KDITus83pZEq3C1y_WbKPZc0HUfmIm_eby8"}
respo, err := client.Get("https://no5.xiexiaojia.com/appjmall/api/showCore/appShowList", headers)
defer respo.Body.Body.Close()
if err != nil {
fmt.Printf("%+v", err)
}
resport := Resport{}
body, err := respo.Body.ReadAll()
//反序列化
err = json.Unmarshal(body, &resport)
fmt.Println("response", resport)
//案例获取数据
fmt.Println("response comName:", resport.Data.([]interface{})[0].(map[string]interface{})["comName"])
}
func Test_Post_Httpcli(t *testing.T) {
client := Client()
fmt.Println("http 客户端", client)
var headers = map[string]string{"auth_token": "xxxxx"}
var params = map[string]string{"userId": "5ce2b73fe27cf22a8f4215d0", "price": "5000", "count": "1", "commId": "5ce40e03e27cf22a8f4215d8",
"comName": "日本ceruru.b 脐带血面膜保湿补水修复5片",
"comImageCar": "http://oam.szbslhb.com/4,01fb06b4adf1.jpg",
"attributeCar": "",
}
respo, err := client.Post("https://no5.xiexiaojia.com/appjmall/api/cart/addCommToCart", headers, params)
defer respo.Body.Body.Close()
if err != nil {
fmt.Printf("%+v", err)
}
resport := Resport{}
body, err := respo.Body.ReadAll()
if err != nil {
res, _ := respo.Body.ToString()
fmt.Println("response", res)
return
}
//反序列化
err = json.Unmarshal(body, &resport)
fmt.Println("response", resport)
}
func Test_PostJson_Httpcli(t *testing.T) {
client := Client()
var headers = map[string]string{"Content-Type": "application/json"}
fmt.Println("http 客户端", client)
respo, err := client.Get("https://www.kancloud.cn/cattong/go_command_tutorial/261347", headers)
defer respo.Body.Body.Close()
if err != nil {
fmt.Printf("%+v", err)
}
body, err := respo.Body.ToString()
fmt.Println("response", body)
}
func Test_PutFile_Httpcli(t *testing.T) {
client := Client()
file, err := os.Open("/Users/mac/Downloads/1567069549ABBQCTFC.mp4")
info, _ := file.Stat()
var headers = map[string]string{
"Content-Type": "application/octet-stream",
"Authorization": "bucketName=bucket-01;accessKey=FSd58ed59fccc16812903c37068de8969b;ttl=1h",
"Content-Disposition": "fileName=test02.mp4;size=" + strconv.FormatInt(info.Size(), 10),
"Signature": "9cea21bf95f235e7d9c5a70888cf742cfb9c946ed25aef8b70ce77c953ccbff0",
}
fmt.Println("http 客户端", client)
respo, err := client.Put("http://localhost:8888/fs/api/uploadStream", headers, file)
defer respo.Body.Body.Close()
if err != nil {
fmt.Printf("%+v", err)
}
body, err := respo.Body.ToString()
fmt.Println("response", body)
}
+113
View File
@@ -0,0 +1,113 @@
package common
import (
"context"
"fmt"
"net"
"stat/common/httputil"
"stat/common/log"
"time"
)
var (
defaultCity = "广州"
defaultProvince = "广东"
ipNewURL = "https://loc.ztgba.com/self/getLocationByIp"
ipNewURLV2 = "https://loc.ztgba.com/self/isChina"
NoChina int32 = 0
)
//老接口
type LocationResp struct {
RegionName string `json:"region_name"`
CityName string `json:"city_name"`
IP string `json:"ip"`
}
func GetLocationByIP(ip string) (string, string) {
//return defaultCity, defaultProvince
var params = map[string]string{"ip": ip}
loc := LocationResp{}
c, cancle := context.WithTimeout(context.Background(), 3*time.Second)
defer cancle()
_, err := httputil.NewCtx(c).POSTJsonWithJResp(&loc, ipNewURL, nil, params)
if err != nil {
log.Error("GetLocationByIP error", log.Any("ip", ip), log.Any("err", err))
return defaultCity, defaultProvince
}
Go(func() {
select {
case <-c.Done():
fmt.Print("current http method GetLocationByIP ctx", c.Err())
}
})
if len(loc.RegionName) == 0 && len(loc.CityName) == 0 {
return defaultCity, defaultProvince
}
if loc.RegionName == "局域网" {
return defaultCity, defaultProvince
}
if loc.RegionName == "柬埔寨" {
return defaultCity, defaultProvince
}
if len(loc.CityName) == 0 {
return loc.RegionName, loc.RegionName
}
return loc.CityName, loc.RegionName
}
type CountryResp struct {
Country string `json:"country_name"`
RegionName string `json:"region_name"`
IP string `json:"ip"`
}
type CountryRespV2 struct {
Code int32 `json:"code"`
Data map[string]int32 `json:"data"`
}
//InChina
//香港返回false
func InChina(ip string) bool {
var params = map[string]string{"ip": ip}
resp := CountryResp{}
_, err := httputil.New().POSTJsonWithJResp(&resp, ipNewURL, nil, params)
if err != nil {
log.Error("InChina error", log.Any("ip", ip), log.Any("err", err))
return true
}
if resp.RegionName == "香港" {
return false
}
return resp.Country == "中国"
}
func GetLocalIP() string {
addrs, err := net.InterfaceAddrs()
if err != nil {
return ""
}
for _, addr := range addrs {
if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
if ipnet.IP.To4() != nil && ipnet.IP.IsGlobalUnicast() {
return ipnet.IP.String()
}
}
}
return ""
}
func InChinaV2(ip string) bool {
var params = map[string]string{"ip": ip}
resp := CountryRespV2{}
_, err := httputil.New().GetWithJResp(&resp, ipNewURLV2, nil, params)
if err != nil {
log.Error("InChina error", log.Any("ip", ip), log.Any("err", err))
return true
}
if value, ok := resp.Data[ip]; ok {
return value != NoChina
}
return false
}
+85
View File
@@ -0,0 +1,85 @@
package kafka
import (
"fmt"
"stat/common/log"
"time"
"github.com/Shopify/sarama"
)
const (
ClientID string = "pf_sp_kafka"
)
//kafka 使用
var (
gSyncProducer sarama.SyncProducer
)
/**********************************************客户端 *********************/
// 初始化kafka生产者 发送消息入口
func InitKafkaProducter(addrs []string) error {
config := sarama.NewConfig()
config.Version = sarama.V2_0_0_0
config.Producer.Return.Successes = true
config.Net.KeepAlive = 2 * time.Hour
cli, err := sarama.NewClient(addrs, config)
if err != nil {
log.Error("startUp Kafka Init Kafka error", log.E(err))
return err
}
gSyncProducer, err = SyncProducter(cli)
if err != nil {
log.Error("startUp Kafka new SyncProducter error", log.E(err))
return nil
}
return nil
}
// SyncSendMessage 同步发送确保消息成功
func SyncSendMessage(topic string, message []byte) {
if gSyncProducer != nil {
msg := sarama.ProducerMessage{
Topic: topic,
Value: sarama.ByteEncoder(message),
}
partition, offset, err := gSyncProducer.SendMessage(&msg)
if err != nil {
log.Error("gSyncProducer SendMessage Fail", log.Any("topic", topic), log.E(err))
return
}
fmt.Println(partition, offset)
} else {
fmt.Println("err err")
}
}
//创建同步生产者 用于对消息的顺序有严格要求的场景 性能相对较低
func SyncProducter(client sarama.Client) (sarama.SyncProducer, error) {
producer, err := sarama.NewSyncProducerFromClient(client)
if err != nil {
log.Error("create syncProducer error", log.E(err))
return nil, err
}
return producer, nil
}
/**********************************************客户端 end*********************/
/**********************************************服务端 *********************/
func InitKafkaConsumerGroup(addrs []string, groupName string) sarama.ConsumerGroup {
config := sarama.NewConfig()
config.Version = sarama.V2_0_0_0
config.Consumer.Return.Errors = true
group, err := sarama.NewConsumerGroup(addrs, groupName, config)
if err != nil {
panic(err)
}
return group
}
+64
View File
@@ -0,0 +1,64 @@
package log
import (
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"os"
"time"
)
var (
zapLogger *zap.Logger
ZapLog *zap.Logger
Debug = zapLogger.Debug
Info = zapLogger.Info
Warn = zapLogger.Warn
Error = zapLogger.Error
Fatal = zapLogger.Fatal
)
func newEncoderConfig() zapcore.EncoderConfig {
return zapcore.EncoderConfig{
// Keys can be anything except the empty string.
TimeKey: "T",
LevelKey: "L",
NameKey: "N",
CallerKey: "C",
MessageKey: "M",
StacktraceKey: "S",
LineEnding: zapcore.DefaultLineEnding,
EncodeLevel: zapcore.CapitalLevelEncoder,
EncodeTime: timeEncoder,
EncodeDuration: zapcore.StringDurationEncoder,
EncodeCaller: zapcore.ShortCallerEncoder,
}
}
func init() {
core := zapcore.NewCore(
zapcore.NewConsoleEncoder(newEncoderConfig()),
zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout)),
zap.DebugLevel,
)
zapLogger = zap.New(core, zap.AddCaller())
ZapLog = zapLogger
Debug = zapLogger.Debug
Info = zapLogger.Info
Warn = zapLogger.Warn
Error = zapLogger.Error
Fatal = zapLogger.Fatal
}
func timeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
enc.AppendString(t.Format("2006-01-02 15:04:05"))
}
type Field = zap.Field
func Any(key string, value interface{}) Field {
return zap.Any(key, value)
}
func E(err error) Field {
return Any("err", err)
}
+19
View File
@@ -0,0 +1,19 @@
package log
import (
"fmt"
"log"
)
type Logger struct{}
// Info logs routine messages about cron's operation.
func (s *Logger) Info(msg string, keysAndValues ...interface{}) {
log.Default().Output(2, msg+" "+fmt.Sprint(keysAndValues...))
}
// Error logs an error condition.
func (s *Logger) Error(err error, msg string, keysAndValues ...interface{}) {
line := fmt.Sprintf("%s %v %s", msg, err, fmt.Sprint(keysAndValues...))
log.Default().Output(2, line)
}
+34
View File
@@ -0,0 +1,34 @@
package m3u8
import (
"bytes"
"context"
"io/ioutil"
"sync"
)
var (
cacheEnkey []byte
cacheTvEnkey []byte
initOnce sync.Once
)
// 安全初始化 m3u8 key
func init() {
initOnce.Do(func() {
if len(cacheEnkey) == 0 {
data, err := ioutil.ReadFile("conf/m3u8.key")
if err != nil {
panic(err)
}
cacheEnkey = data
}
})
}
// Enkey 密匙key
func Enkey(ctx context.Context) (buffer *bytes.Buffer, err error) {
buffer = bytes.NewBuffer(cacheEnkey)
return
}
+114
View File
@@ -0,0 +1,114 @@
package m3u8
import (
//"sz-app-server/pub/common/utils"
"crypto/md5"
"encoding/hex"
"fmt"
"stat/common/utils"
"strings"
"time"
"github.com/grafov/m3u8"
)
// makeMediaPlayList 根据from,重新生成播放列表
func makeMediaPlayList(from *m3u8.MediaPlaylist, cdnDomain string, dirPath string, keyUri string, authKey string) (media *m3u8.MediaPlaylist, err error) {
media, err = m3u8.NewMediaPlaylist(from.WinSize(), from.Count())
if err != nil {
return
}
base := utils.JoinUrlPath(cdnDomain, dirPath)
now := time.Now()
for _, segment := range from.Segments {
if segment == nil {
continue
}
sg := new(m3u8.MediaSegment)
sg.URI = utils.JoinUrlPath(base, segment.URI)
if len(authKey) > 0 {
//生成鉴权url
urlPath := utils.JoinUrlPath(dirPath, segment.URI)
urlAuth := generateUrlAuth(now, urlPath, authKey)
sg.URI = sg.URI + urlAuth
}
sg.Title = segment.Title
sg.Duration = segment.Duration
if err = media.AppendSegment(sg); err != nil {
return
}
}
_ = media.SetDefaultKey("AES-128", keyUri, "", "", "")
media.Close()
return
}
// 生成鉴权url
func generateUrlAuth(now time.Time, path string, authKey string) string {
timestamp := now.Unix()
// randId := maths.GenUUID()
signStr := fmt.Sprintf("%s-%d-0-0-%s", path, timestamp, authKey)
md5Str := getMD5Sign(signStr)
urlAuth := fmt.Sprintf("?md=%d-0-0-%s", timestamp, md5Str)
return urlAuth
}
// getMD5Sign 得到签名
func getMD5Sign(buf string) string {
md5Ctx := md5.New()
md5Ctx.Write([]byte(buf))
cipherStr := md5Ctx.Sum(nil)
nsign := hex.EncodeToString(cipherStr)
return nsign
}
func newMediaPlayList(from *m3u8.MediaPlaylist, cdnDomain, origDomain, dirPath, keyUri string, needHeader bool) (media *m3u8.MediaPlaylist, err error) {
if needHeader {
return makeMediaPlayListWithHeader(from, cdnDomain, origDomain, dirPath, keyUri)
} else {
return makeMediaPlayList(from, cdnDomain, dirPath, keyUri, AuthKey)
}
}
func makeMediaPlayListWithHeader(from *m3u8.MediaPlaylist, cdnDomain, origDomain string, dirPath string, keyUri string) (media *m3u8.MediaPlaylist, err error) {
media, err = m3u8.NewMediaPlaylist(from.WinSize(), from.Count()+headerMediaPlayList.Count())
if err != nil {
return
}
//合并片m3u8
for _, v := range headerMediaPlayList.Segments {
if v == nil {
continue
}
sg := new(m3u8.MediaSegment)
sg.URI = v.URI
if origDomain != "" && strings.HasPrefix(v.URI, origDomain) && origDomain != cdnDomain {
sg.URI = strings.ReplaceAll(v.URI, origDomain, cdnDomain)
}
sg.Title = v.Title
sg.Duration = v.Duration
if err = media.AppendSegment(sg); err != nil {
return
}
}
base := utils.JoinUrlPath(cdnDomain, dirPath)
for k, segment := range from.Segments {
if segment == nil {
continue
}
sg := new(m3u8.MediaSegment)
sg.Discontinuity = k == 0
sg.URI = utils.JoinUrlPath(base, segment.URI)
sg.Title = segment.Title
sg.Duration = segment.Duration
if err = media.AppendSegment(sg); err != nil {
return
}
}
_ = media.SetDefaultKey("AES-128", keyUri, "", "", "")
media.Close()
return
}
+135
View File
@@ -0,0 +1,135 @@
package m3u8
import (
"bytes"
"fmt"
"io"
"stat/common/enum/redisconst"
"stat/common/errors"
"stat/common/http"
"stat/common/log"
"stat/common/redis"
"stat/common/utils"
"strings"
"github.com/grafov/m3u8"
)
const (
keyUri = "/api/stat/media/enkey"
AuthKey = "fT5xSg4hltHpzVy6aV9rVECDJ1J1pN"
)
var headerMediaPlayList *m3u8.MediaPlaylist
// TransFormWithSafely 被检查权限的播放列表
// 主要检查用户是否有观看该视频的权限
// apiType(1:app,2:h5app,3:web)
func TransFormWithSafely(req *Req, cdnDomain, origDomain string, needHeader bool, apiType int) (*m3u8.MediaPlaylist, *bytes.Buffer, error) {
if req.Name == "" {
return nil, nil, errors.MakeWithMsg(errors.M3u8FileName, "file name is empty")
}
if cdnDomain == "" || origDomain == "" {
return nil, nil, errors.MakeWithMsg(errors.DomainErr, "cdnDomain or origDomain is empty")
}
return transform(req.Name, cdnDomain, origDomain, needHeader, apiType)
}
func AppendsMediaS3Version(name string) string {
// 默认使用V1
if name != "" && !strings.HasPrefix(name, "v2/") && !strings.HasPrefix(name, "v1/") && !strings.HasPrefix(name, "v3/") &&
!strings.HasPrefix(name, "/v2") && !strings.HasPrefix(name, "/v1") && !strings.HasPrefix(name, "/v3") {
return "/v1"
}
return ""
}
// transform m3u8文件转换,将不正确的m3u8文件,替换为正确的m3u8文件
func transform(name string, cdnDomain, origDomain string, needHeader bool, apiType int) (media *m3u8.MediaPlaylist, buff *bytes.Buffer, err error) {
m3u8reader, err := readFromRedis(name, origDomain)
if err != nil {
return
}
p, t, err := m3u8.DecodeFrom(m3u8reader, false)
if err != nil {
err = errors.MakeWithErr(errors.M3u8Read, err)
return
}
tmptKeyurl := keyUri
fmt.Println("<<<<<<<<<<<<<<<<<<<start get media list>>>>>>>>>>>>>>>")
switch t {
case m3u8.MEDIA:
pl := p.(*m3u8.MediaPlaylist)
dirPath := utils.PathForInfo(name).Dir
media, err = newMediaPlayList(pl, cdnDomain, origDomain, dirPath, tmptKeyurl, needHeader)
if err != nil {
return
}
buff = media.Encode()
case m3u8.MASTER:
mpl := p.(*m3u8.MasterPlaylist)
buff = mpl.Encode()
default:
err = errors.MakeWithMsg(errors.M3u8FileName, fmt.Sprintf("invalid m3u8 type %d", t))
}
return
}
//func readFromCDN(name, domain string) (reader io.Reader, err error) {
// requestUrl := utils.JoinUrlPath(domain, name)
// resp, err := http.GetLargeFile(requestUrl, nil)
// if err != nil {
// return
// }
// if resp.StatusCode != 200 {
// msg := fmt.Sprintf("read m3u8 fail, url: %s, status code %d", requestUrl, resp.StatusCode)
// err = errors.MakeWithMsg(errors.M3u8Read, msg)
// return
// }
// reader = bytes.NewReader(resp.Data)
// return
//}
// 从redis缓存中读取
func readFromRedis(name, domain string) (reader io.Reader, err error) {
m3u8Key := redisconst.MediaM3U8Key(name)
data, err := redis.StatRds.GetBytes(m3u8Key)
if err == nil && len(data) > 0 {
reader = bytes.NewReader(data)
return
}
requestUrl := utils.JoinUrlPath(domain, name)
resp, err := http.GetLargeFile(requestUrl, nil)
if err != nil {
//fmt.Println("requestUrl111", requestUrl)
return
}
if resp.StatusCode != 200 {
msg := fmt.Sprintf("read m3u8 fail, url: %s, status code %d", requestUrl, resp.StatusCode)
//fmt.Println(msg)
err = errors.MakeWithMsg(errors.M3u8Read, msg)
return
}
if err := redis.StatRds.Set(m3u8Key, resp.Data,
redisconst.MediaM3U8CacheExpire); err != nil {
log.Info("m3u8:readFromRedis redis set err", log.E(err))
}
reader = bytes.NewReader(resp.Data)
return
}
func GetHeaderMediaSegUrls() []string {
urls := make([]string, 0)
if headerMediaPlayList == nil {
return urls
}
for _, segment := range headerMediaPlayList.Segments {
if segment == nil {
continue
}
urls = append(urls, segment.URI)
}
return urls
}
+13
View File
@@ -0,0 +1,13 @@
package m3u8
type Req struct {
Token string `json:"token" form:"token"`
VId int64 `json:"vId" form:"vId"`
Name string `json:"name" form:"name"`
NeedHeader bool `json:"needHeader" form:"needHeader"`
}
type PostVideoReq struct {
Token string `json:"token" form:"token"`
Name string `json:"name" form:"name" binding:"required" comment:"must be set"`
}
+507
View File
@@ -0,0 +1,507 @@
package mongos
import (
"context"
"errors"
"reflect"
"stat/common/utils"
"strings"
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
type MongoTool struct {
db *Manager
collName string
}
type PageAndSort struct {
Page int64
PageSize int64
Sort bson.M //bson.M{_id : -1 }
}
func New(db *Manager, collName string) *MongoTool {
return &MongoTool{db: db, collName: collName}
}
func (b *MongoTool) GetCollection() *mongo.Collection {
return b.collection()
}
func (b *MongoTool) collection() *mongo.Collection {
opts := options.CollectionOptions{Registry: Registry}
return b.db.Database().Collection(b.collName, &opts)
}
func (b *MongoTool) FindById(ctx context.Context, bind interface{}, id interface{}, opts ...*options.FindOneOptions) error {
param := bson.M{"_id": id}
return b.FindOne(ctx, bind, param, opts...)
}
func (b *MongoTool) FindOne(ctx context.Context, bind interface{}, query bson.M, opts ...*options.FindOneOptions) error {
err := b.collection().FindOne(ctx, query, opts...).Decode(bind)
if err != nil && err == mongo.ErrNoDocuments {
return nil
}
return err
}
func (b *MongoTool) FindOneEx(ctx context.Context, bind interface{}, query bson.M, opts ...*options.FindOneOptions) error {
err := b.collection().FindOne(ctx, query, opts...).Decode(bind)
return err
}
func (b *MongoTool) FindMOneAndUpdate(ctx context.Context, bind interface{}, query bson.M, update interface{}, opts ...*options.FindOneAndUpdateOptions) error {
setReturn := false
for _, opt := range opts {
if opt != nil && opt.ReturnDocument != nil {
setReturn = true
break
}
}
if !setReturn {
after := options.After
opts = append(opts, &options.FindOneAndUpdateOptions{ReturnDocument: &after})
}
um := utils.Struct2Map(update)
um["updatedAt"] = time.Now()
set := bson.M{
"$set": um,
}
result := b.collection().FindOneAndUpdate(ctx, query, set, opts...)
err := result.Decode(bind)
return err
}
func (b *MongoTool) FindMOneAndUpdateInc(ctx context.Context, bind interface{}, query bson.M, update interface{}, inc interface{}, opts ...*options.FindOneAndUpdateOptions) error {
um := utils.Struct2Map(update)
um["updatedAt"] = time.Now()
im := utils.Struct2Map(inc)
set := bson.M{
"$set": um,
}
if im != nil {
set["$inc"] = inc
}
setReturn := false
for _, opt := range opts {
if opt != nil && opt.ReturnDocument != nil {
setReturn = true
break
}
}
if !setReturn {
after := options.After
opts = append(opts, &options.FindOneAndUpdateOptions{ReturnDocument: &after})
}
result := b.collection().FindOneAndUpdate(ctx, query, set, opts...)
err := result.Decode(bind)
return err
}
func (b *MongoTool) FindOneAndUpdateTiny(
ctx context.Context,
query bson.M,
update bson.M,
opts ...*options.FindOneAndUpdateOptions) error {
result := b.collection().FindOneAndUpdate(ctx, query, update, opts...)
return result.Err()
}
func (b *MongoTool) FindOneAndUpdateReturnTiny(ctx context.Context,
bind interface{},
query bson.M,
update bson.M,
afterDoc bool,
opts ...*options.FindOneAndUpdateOptions) error {
if afterDoc {
opts = append(opts, options.FindOneAndUpdate().SetReturnDocument(options.After))
} else {
opts = append(opts, options.FindOneAndUpdate().SetReturnDocument(options.Before))
}
result := b.collection().FindOneAndUpdate(ctx, query, update, opts...).Decode(bind)
return result
}
func (b *MongoTool) FindOneAndUpdate(ctx context.Context, bind interface{}, query bson.M, update bson.M, opts ...*options.FindOneAndUpdateOptions) error {
setReturn := false
for _, opt := range opts {
if opt != nil && opt.ReturnDocument != nil {
setReturn = true
break
}
}
if !setReturn {
after := options.After
opts = append(opts, &options.FindOneAndUpdateOptions{ReturnDocument: &after})
}
b.setUpdatedAt(update, time.Now())
result := b.collection().FindOneAndUpdate(ctx, query, update, opts...)
err := result.Decode(bind)
return err
}
func (b *MongoTool) Find(ctx context.Context, bind interface{}, query bson.M, opt ...*options.FindOptions) error {
err := b.validSlice(bind)
cur, err := b.collection().Find(ctx, query, opt...)
if err != nil && err == mongo.ErrNoDocuments {
return nil
} else if err != nil {
return err
}
err = cur.All(ctx, bind)
return err
}
func (b *MongoTool) FindByPage(ctx context.Context, bind interface{}, query bson.M, pageAndSort *PageAndSort, opts ...*options.FindOptions) error {
err := b.validSlice(bind)
if err != nil {
return err
}
opt := &options.FindOptions{}
if pageAndSort == nil {
return errors.New("page must not nil")
}
if pageAndSort.Page == 0 || pageAndSort.PageSize == 0 {
return errors.New("page and PageSize must not nil")
}
skip := (pageAndSort.Page - 1) * pageAndSort.PageSize
opt.Skip = &skip
opt.Limit = &pageAndSort.PageSize
if pageAndSort.Sort != nil {
opt.Sort = pageAndSort.Sort
}
opts = append(opts, opt)
cur, err := b.collection().Find(ctx, query, opts...)
if err != nil && err == mongo.ErrNoDocuments {
return nil
} else if err != nil {
return err
}
err = cur.All(ctx, bind)
return err
}
func (b *MongoTool) FindOneAndDelete(ctx context.Context, bind interface{}, query bson.M, opts ...*options.FindOneAndDeleteOptions) error {
result := b.collection().FindOneAndDelete(ctx, query, opts...)
if result.Err() != nil {
return result.Err()
}
return result.Decode(bind)
}
func (b *MongoTool) InsertOneTiny(ctx context.Context, doc interface{}) (err error) {
_, err = b.collection().InsertOne(ctx, doc)
return
}
func (b *MongoTool) InsertOne(ctx context.Context, doc interface{}) (string, error) {
//设置创建时间
b.addCreatedAt(doc)
//设置修改时间
b.addUpdatedAt(doc)
result, err := b.collection().InsertOne(ctx, doc)
if err != nil {
return "", err
}
if id, ok := result.InsertedID.(primitive.ObjectID); ok {
return id.Hex(), nil
}
return "", err
}
func (b *MongoTool) InsertMany(ctx context.Context, docs interface{}) ([]interface{}, error) {
err := b.validSlice(docs)
if err != nil {
return nil, err
}
res := b.setTimeForSlice(docs)
result, err := b.collection().InsertMany(ctx, res)
if err != nil {
return nil, err
}
return result.InsertedIDs, err
}
// struct方式
func (b *MongoTool) UpdateMOne(ctx context.Context, query bson.M, update interface{}) (*mongo.UpdateResult, error) {
um := utils.Struct2Map(update)
um["updatedAt"] = time.Now()
set := bson.M{
"$set": um,
}
return b.collection().UpdateOne(ctx, query, set)
}
func (b *MongoTool) UpdateMOneInc(ctx context.Context, query bson.M, update interface{}, inc interface{}) (*mongo.UpdateResult, error) {
um := utils.Struct2Map(update)
um["updatedAt"] = time.Now()
set := bson.M{
"$set": um,
}
im := utils.Struct2Map(inc)
if im != nil {
set["$inc"] = im
}
return b.collection().UpdateOne(ctx, query, set)
}
// 原生bson.M 方式
func (b *MongoTool) UpdateOne(ctx context.Context, query bson.M, update bson.M) (*mongo.UpdateResult, error) {
b.setUpdatedAt(update, time.Now())
return b.collection().UpdateOne(ctx, query, update)
}
// struct方式
func (b *MongoTool) UpsertMOne(ctx context.Context, query bson.M, update interface{}) (*mongo.UpdateResult, error) {
um := utils.Struct2Map(update)
um["updatedAt"] = time.Now()
set := bson.M{
"$set": um,
}
upsert := true
opt := &options.UpdateOptions{
Upsert: &upsert,
}
now := time.Now()
result, err := b.collection().UpdateOne(ctx, query, set, opt)
if err == nil {
if result.UpsertedCount >= 1 {
_, err = b.collection().UpdateOne(ctx, query, bson.M{"$set": bson.M{"createdAt": now}})
}
}
return result, err
}
func (b *MongoTool) UpsertOneEx(ctx context.Context, query bson.M, update bson.M) (*mongo.UpdateResult, error) {
opt := &options.UpdateOptions{}
opt.SetUpsert(true)
result, err := b.collection().UpdateOne(ctx, query, update, opt)
return result, err
}
func (b *MongoTool) UpsertOne(ctx context.Context, query bson.M, update bson.M) (*mongo.UpdateResult, error) {
upsert := true
opt := &options.UpdateOptions{
Upsert: &upsert,
}
now := time.Now()
b.setUpdatedAt(update, now)
result, err := b.collection().UpdateOne(ctx, query, update, opt)
if err == nil {
if result.UpsertedCount >= 1 {
_, err = b.collection().UpdateOne(ctx, query, bson.M{"$set": bson.M{"createdAt": now}})
}
}
return result, err
}
func (b *MongoTool) UpdateMMany(ctx context.Context, query bson.M, update interface{}) (*mongo.UpdateResult, error) {
//b.setUpdatedAt(update, time.Now())
return b.collection().UpdateMany(ctx, query, update)
}
func (b *MongoTool) UpdateMany(ctx context.Context, query bson.M, update bson.M) (*mongo.UpdateResult, error) {
b.setUpdatedAt(update, time.Now())
return b.collection().UpdateMany(ctx, query, update)
}
func (b *MongoTool) DeleteById(ctx context.Context, id interface{}) (int64, error) {
param := bson.M{"_id": id}
result, err := b.collection().DeleteOne(ctx, param)
if err != nil {
return 0, err
}
return result.DeletedCount, nil
}
func (b *MongoTool) DeleteOne(ctx context.Context, query bson.M, opts ...*options.DeleteOptions) (int64, error) {
result, err := b.collection().DeleteOne(ctx, query)
if err != nil {
return 0, err
}
return result.DeletedCount, nil
}
func (b *MongoTool) DeleteMany(ctx context.Context, query bson.M, opts ...*options.DeleteOptions) (int64, error) {
result, err := b.collection().DeleteMany(ctx, query, opts...)
if err != nil {
return 0, err
}
deletedCount := result.DeletedCount
return deletedCount, nil
}
func (b *MongoTool) Aggregate(ctx context.Context, bind interface{}, pipeline []bson.M, opts ...*options.AggregateOptions) error {
err := b.validSlice(bind)
if err != nil {
return err
}
cur, err := b.collection().Aggregate(ctx, pipeline, opts...)
if err != nil {
return err
}
return cur.All(ctx, bind)
}
func (b *MongoTool) ObjectAggregate(ctx context.Context, bind interface{}, pipeline []bson.M, opts ...*options.AggregateOptions) error {
cur, err := b.collection().Aggregate(ctx, pipeline, opts...)
if err != nil {
return err
}
if cur.Next(ctx) {
return cur.Decode(bind)
}
return nil
}
func (b *MongoTool) QueryOne(ctx context.Context, bind interface{}, query interface{}, opts ...*options.FindOneOptions) error {
err := b.collection().FindOne(ctx, query, opts...).Decode(bind)
if err != nil && err == mongo.ErrNoDocuments {
return nil
}
return err
}
func (b *MongoTool) Count(ctx context.Context, query interface{}, opts ...*options.CountOptions) (int64, error) {
return b.collection().CountDocuments(ctx, query, opts...)
}
func (b *MongoTool) EstimatedCount(ctx context.Context, opts ...*options.EstimatedDocumentCountOptions) (int64, error) {
return b.collection().EstimatedDocumentCount(ctx, opts...)
}
func (b *MongoTool) BulkWrite(ctx context.Context, models []mongo.WriteModel, opts ...*options.BulkWriteOptions) (*mongo.BulkWriteResult, error) {
return b.collection().BulkWrite(ctx, models, opts...)
}
// CreateIndex 创建数据索引.options 在index里面创建
func (b *MongoTool) CreateIndex(ctx context.Context, models []mongo.IndexModel) ([]string, error) {
data, err := b.collection().Indexes().CreateMany(ctx, models)
if err != nil {
if strings.Contains(err.Error(), "IndexKeySpecsConflict") {
return data, nil
}
if strings.Contains(err.Error(), "IndexOptionsConflict") {
return data, nil
}
return data, err
}
return data, nil
}
// DropIndex 删除数据索引.options 在index里面创建
func (b *MongoTool) DropIndex(ctx context.Context, indexName string) error {
_, err := b.collection().Indexes().DropOne(ctx, indexName)
if err != nil {
return err
}
return nil
}
func (b *MongoTool) addCreatedAt(val interface{}) {
if _, ok := val.(bson.M); ok {
return
}
if _, ok := val.(map[string]interface{}); ok {
return
}
v := reflect.ValueOf(val).Elem().FieldByName("CreatedAt")
if v.IsValid() {
v.Set(reflect.ValueOf(time.Now()))
}
}
func (b *MongoTool) addUpdatedAt(val interface{}) {
if _, ok := val.(bson.M); ok {
return
}
if _, ok := val.(map[string]interface{}); ok {
return
}
v := reflect.ValueOf(val).Elem().FieldByName("UpdatedAt")
if v.IsValid() {
v.Set(reflect.ValueOf(time.Now()))
}
}
func (b *MongoTool) setTimeForSlice(docs interface{}) []interface{} {
arr := reflect.ValueOf(docs).Elem()
result := make([]interface{}, 0)
for i := 0; i < arr.Len(); i++ {
ele := arr.Index(i).Elem()
now := time.Now()
if ma := ele.FieldByName("UpdatedAt"); ma.IsValid() {
ma.Set(reflect.ValueOf(now))
}
if ca := ele.FieldByName("CreatedAt"); ca.IsValid() {
ca.Set(reflect.ValueOf(now))
}
result = append(result, ele.Interface())
}
return result
}
func (b *MongoTool) validSlice(bind interface{}) error {
if reflect.TypeOf(bind).Kind() != reflect.Ptr {
return errors.New("interface must be a Ptr")
}
if reflect.TypeOf(bind).Elem().Kind() != reflect.Slice {
return errors.New("interface must be a slice")
}
return nil
}
func (b *MongoTool) setUpdatedAt(doc bson.M, now time.Time) {
set, exists := doc["$set"]
if !exists {
doc["$set"] = bson.M{"updatedAt": now}
return
}
if res, ok := set.(bson.M); ok {
res["updatedAt"] = now
}
}
func (b *MongoTool) setCreatedAt(doc bson.M, now time.Time) {
set, exists := doc["$set"]
if !exists {
doc["$set"] = bson.M{"createdAt": now}
return
}
if res, ok := set.(bson.M); ok {
res["createdAt"] = now
}
}
// Exists 是否存在数据
func (b *MongoTool) Exists(ctx context.Context, filter interface{}, opts ...*options.FindOneOptions) (bool, error) {
var limit int64 = 1
lo := &options.CountOptions{Limit: &limit}
n, err := b.Count(ctx, filter, lo)
if err != nil {
return false, err
}
return n > 0, nil
}
+119
View File
@@ -0,0 +1,119 @@
package mongos
import (
"fmt"
"github.com/shopspring/decimal"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/bsoncodec"
"go.mongodb.org/mongo-driver/bson/bsonrw"
"go.mongodb.org/mongo-driver/bson/bsontype"
"go.mongodb.org/mongo-driver/bson/primitive"
"reflect"
"time"
)
var Registry = func() *bsoncodec.Registry {
builder := bson.NewRegistryBuilder()
builder.RegisterDecoder(reflect.TypeOf(time.Time{}), &localTimeDecoder{})
builder.RegisterDecoder(reflect.TypeOf(decimal.Decimal{}), &decimalDecoder{})
builder.RegisterEncoder(reflect.TypeOf(decimal.Decimal{}), &decimalEncoder{})
builder.RegisterDefaultDecoder(reflect.Float32, &floatDecoder{})
builder.RegisterDefaultDecoder(reflect.Float64, &floatDecoder{})
return builder.Build()
}()
type floatDecoder struct {
}
func (dvd *floatDecoder) DecodeValue(dc bsoncodec.DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
var f float64
var err error
switch vr.Type() {
case bsontype.Int32:
i32, err := vr.ReadInt32()
if err != nil {
return err
}
f = float64(i32)
case bsontype.Int64:
i64, err := vr.ReadInt64()
if err != nil {
return err
}
f = float64(i64)
case bsontype.Double:
f, err = vr.ReadDouble()
if err != nil {
return err
}
default:
return fmt.Errorf("cannot decode %v into a float32 or float64 type", vr.Type())
}
val.SetFloat(f)
return nil
}
type localTimeDecoder struct{}
func (*localTimeDecoder) DecodeValue(dc bsoncodec.DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
dvd := bsoncodec.DefaultValueDecoders{}
err := dvd.TimeDecodeValue(dc, vr, val)
if err != nil {
return err
}
t := val.Interface().(time.Time)
val.Set(reflect.ValueOf(t.Local()))
return nil
}
type decimalDecoder struct{}
func (*decimalDecoder) DecodeValue(dc bsoncodec.DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
if !val.IsValid() || val.Type() != reflect.TypeOf(decimal.Decimal{}) {
return bsoncodec.ValueDecoderError{Name: "DecimalDecodeValue", Types: []reflect.Type{reflect.TypeOf(decimal.Decimal{})}, Received: val}
}
var (
num bool
)
if vr.Type() == bson.TypeInt32 || vr.Type() == bson.TypeInt64 {
vr.ReadInt32()
vr.ReadInt64()
num = true
}
if num {
d := decimal.NewFromFloat(0.0)
val.Set(reflect.ValueOf(d))
return nil
}
mongodecimal, err := vr.ReadDecimal128()
if err != nil {
return err
}
d, err := decimal.NewFromString(mongodecimal.String())
if err != nil {
return err
}
val.Set(reflect.ValueOf(d))
return nil
}
//
type decimalEncoder struct{}
func (*decimalEncoder) EncodeValue(ctx bsoncodec.EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
if !val.IsValid() || val.Type() != reflect.TypeOf(decimal.Decimal{}) {
return bsoncodec.ValueDecoderError{Name: "DecimalEncodeValue", Types: []reflect.Type{reflect.TypeOf(decimal.Decimal{})}, Received: val}
}
if d, ok := val.Interface().(decimal.Decimal); ok {
mongodecimal, err := primitive.ParseDecimal128(d.StringFixed(2))
if err != nil {
return err
}
val = reflect.ValueOf(mongodecimal)
}
dve := bsoncodec.DefaultValueEncoders{}
return dve.Decimal128EncodeValue(ctx, vw, val)
}
+138
View File
@@ -0,0 +1,138 @@
package mongos
import (
"context"
"stat/common/log"
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/x/mongo/driver/connstring"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
var MgoManager *Manager
type Options struct {
Url string `json:"url"` // 数据库连接
}
// Manager 数据库连接管理器
// 重置连接等
type Manager struct {
client *mongo.Client
url string
db string
}
func NewManager(url string) *Manager {
return &Manager{url: url}
}
func (m *Manager) Database() *mongo.Database {
return m.client.Database(m.db)
}
func (m *Manager) Connect() error {
cs, err := connstring.Parse(m.url)
if err != nil {
log.Error("db connect parse err", log.E(err))
return err
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
client, err := mongo.Connect(ctx, options.Client().ApplyURI(m.url))
if err != nil {
log.Error("[warn] connect mongo db err", log.E(err))
return err
}
if err = client.Ping(ctx, nil); err != nil {
log.Error("[warn] ping mongo db err", log.E(err))
return err
}
m.db = cs.Database
m.client = client
log.Info("mongos:Init ok!", log.Any("dbName", m.db), log.Any("dbHost", cs.Hosts))
return nil
}
func (m *Manager) DisConnect() {
if m.client != nil {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := m.client.Disconnect(ctx); err != nil {
log.Error("db disconnect err", log.E(err))
}
m.client = nil
}
}
//Transaction 开启事务处理包裹处理,里面处理的全是利用的事务的ctx
func (m *Manager) Transaction(ctx context.Context, fn func(sctx mongo.SessionContext) error) error {
return m.client.UseSession(
ctx,
func(sctx mongo.SessionContext) error {
return runTransactionWithRetry(sctx, func(ctx mongo.SessionContext) error {
err := sctx.StartTransaction()
if err != nil {
return err
}
err = fn(sctx)
if err != nil {
sctx.AbortTransaction(sctx)
return err
}
return commitWithRetry(sctx)
})
},
)
}
func runTransactionWithRetry(sctx mongo.SessionContext, txnFn func(mongo.SessionContext) error) error {
for {
err := txnFn(sctx) // Performs transaction.
if err == nil {
return nil
}
// If transient error, retry the whole transaction
if cmdErr, ok := err.(mongo.CommandError); ok && cmdErr.HasErrorLabel("TransientTransactionError") {
continue
}
return err
}
}
// commitWithRetry is an example function demonstrating transaction retry logic.
func commitWithRetry(sctx mongo.SessionContext) error {
for {
err := sctx.CommitTransaction(sctx)
switch e := err.(type) {
case nil:
return nil
case mongo.CommandError:
// Can retry commit
if e.HasErrorLabel("UnknownTransactionCommitResult") {
continue
}
return e
default:
return e
}
}
}
func Transaction(ctx context.Context, fn func(sctx mongo.SessionContext) error) error {
return MgoManager.Transaction(ctx, fn)
}
func ToBsonM(v interface{}) bson.M {
b, _ := bson.Marshal(v)
var r = bson.M{}
_ = bson.Unmarshal(b, &r)
return r
}
+259
View File
@@ -0,0 +1,259 @@
package pollute
import (
"bytes"
"compress/gzip"
"encoding/json"
"errors"
"fmt"
"github.com/PuerkitoBio/goquery"
"github.com/xxjwxc/gowp/workpool"
"io/ioutil"
"math/rand"
"net/http"
"net/url"
"stat/common/log"
"strings"
"time"
)
//http://www.ce8.com/check
type ce8PolluteServerConfig struct {
DotNetApi string `json:"dotNetApi"`
}
type ce8PollutionFullReq struct {
URL string `json:"url"`
}
type ce8PollutionFullResp struct {
Code int `json:"code"`
State bool `json:"state"`
Runtime interface{} `json:"runtime"`
Data struct {
URL string `json:"url"`
Pollution bool `json:"pollution"`
PollutionNodeCount int `json:"pollution_node_count"`
NodeList []struct {
PollutionResult struct {
IP string `json:"ip"`
Location string `json:"location"`
Pollution bool `json:"pollution"`
IsTimeout bool `json:"is_timeout"`
} `json:"pollution_result"`
} `json:"node_list"`
} `json:"data"`
}
func ce8PolluteCheck(domains []CheckReq) ([]CheckResult, error) {
if len(domains) == 0 {
return nil, nil
}
doc, err := ce8GetCheckDoc()
if err != nil {
log.Warn("ce8PolluteCheck ce8GetCheckDoc err", log.Any("domain", domains), log.E(err))
return nil, err
}
sc, err := ce8GetPolluteServerConfig(doc)
if err != nil {
log.Warn("ce8PolluteCheck ce8GetPolluteServerConfig err", log.Any("domain", domains), log.E(err))
return nil, err
}
csrfToken := ce8GetCSRFToken(doc)
if csrfToken == "" {
log.Warn("ce8PolluteCheck ce8GetCSRFToken empty", log.Any("domain", domains))
}
log.Info("ce8PolluteCheck ce8PollutionFulls start", log.Any("domainsLen", len(domains)))
checkResult, err := ce8PollutionFulls(domains, *sc, csrfToken)
if err != nil {
log.Info("ce8PolluteCheck ce8PollutionFulls Finish Err", log.Any("domain", domains), log.E(err))
return nil, err
}
log.Info("ce8PolluteCheck ce8PollutionFulls Finish OK", log.Any("checkResultLen", len(checkResult)))
return checkResult, err
}
func ce8GetCheckDoc() (*goquery.Document, error) {
const ce8CheckURL = "https://ce8.com/define"
req, err := http.NewRequest("GET", ce8CheckURL, nil)
if err != nil {
log.Warn("ce8GetCheckDoc newRequest", log.Any("url", ce8CheckURL), log.E(err))
return nil, err
}
req.Header.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9")
req.Header.Add("Accept-Language", " en-US,en;q=0.9")
req.Header.Add("Accept-Encoding", "gzip")
req.Header.Add("Cache-Control", "no-cache")
req.Header.Add("Connection", "keep-alive")
req.Header.Add("Host", "www.ce8.com")
req.Header.Add("Pragma", "no-cache")
req.Header.Add("sec-ch-ua", "\"Chromium\";v=\"88\", \"Google Chrome\";v=\"88\", \";Not A Brand\";v=\"99\"")
req.Header.Add("sec-ch-ua-mobile", "?0")
req.Header.Add("Sec-Fetch-Dest", "document")
req.Header.Add("Sec-Fetch-Mode", "navigate")
req.Header.Add("Sec-Fetch-Site", "none")
req.Header.Add("Sec-Fetch-User", "?1")
req.Header.Add("Upgrade-Insecure-Requests", "1")
req.Header.Add("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36")
res, err := http.DefaultClient.Do(req)
if err != nil {
log.Warn("ce8GetCheckDoc Client Do", log.Any("url", ce8CheckURL), log.E(err))
return nil, err
}
defer func() { _ = res.Body.Close() }()
if res.StatusCode != http.StatusOK {
log.Warn("ce8GetCheckDoc err http statusCode", log.Any("statusCode", res.StatusCode))
return nil, errors.New(fmt.Sprintf("StatusCode:%d", res.StatusCode))
}
bodyReader := res.Body
switch res.Header.Get("Content-Encoding") {
case "gzip":
bodyReader, err = gzip.NewReader(res.Body)
if err != nil {
log.Warn("ce8GetCheckDoc gzip newReader", log.E(err))
return nil, err
}
defer func() { _ = bodyReader.Close() }()
default:
}
doc, err := goquery.NewDocumentFromReader(bodyReader)
if err != nil {
log.Warn("ce8GetCheckDoc NewDocumentFromReader err", log.Any("url", ce8CheckURL), log.E(err))
return nil, err
}
return doc, nil
}
func ce8GetPolluteServerConfig(doc *goquery.Document) (*ce8PolluteServerConfig, error) {
script := doc.Find("script").Text()
scriptLines := strings.Split(script, "\n")
var serverConfigLine = ""
for _, line := range scriptLines {
if strings.Contains(line, "serverConfig") && strings.Contains(line, "dotNetApi") {
serverConfigLine = line
break
}
}
if serverConfigLine == "" {
log.Warn("ce8GetPolluteServerConfig Get Line Fail")
return nil, errors.New("not found serverConfig")
}
start := strings.Index(serverConfigLine, "{")
end := strings.LastIndex(serverConfigLine, "}")
if start == -1 || end == -1 {
log.Warn("ce8GetPolluteServerConfig err Line Content", log.Any("line", serverConfigLine))
return nil, errors.New("parseServerConfig error")
}
jsonString := serverConfigLine[start : end+1]
var serverConfig ce8PolluteServerConfig
err := json.Unmarshal([]byte(jsonString), &serverConfig)
if err != nil {
log.Warn("ce8GetPolluteServerConfig unmarshal ServerConfig err", log.E(err))
return nil, err
}
if serverConfig.DotNetApi == "" {
log.Warn("ce8GetPolluteServerConfig dotNetApi empty", log.Any("jsonString", jsonString))
return nil, errors.New("serverConfig dotNetApi empty")
}
return &serverConfig, nil
}
func ce8GetCSRFToken(doc *goquery.Document) string {
var csrfToken = ""
doc.Find("meta").EachWithBreak(func(i int, s *goquery.Selection) bool {
if name, _ := s.Attr("name"); name == "csrf-token" {
csrfToken, _ = s.Attr("content")
return false
}
return true
})
return csrfToken
}
func ce8PollutionFulls(reqs []CheckReq, sc ce8PolluteServerConfig, csrfToken string) ([]CheckResult, error) {
ch := make(chan CheckResult, len(reqs))
wp := workpool.New(15)
reqs = reqs[:] //copy
//在服务器上经常出现超时,这里先做下随机
rand.Shuffle(len(reqs), func(i, j int) { reqs[i], reqs[j] = reqs[j], reqs[i] })
for i := range reqs {
req := reqs[i]
wp.Do(func() error {
domain := req.Domain
r := CheckResult{Domain: domain, Payload: req.Payload, Err: errors.New("exception occurred")}
polled, err := ce8PollutionFull(domain, sc, csrfToken)
r.Polled = polled
r.Err = err
ch <- r
return nil
})
}
err := wp.Wait()
if err != nil {
return nil, err
}
var result []CheckResult
count := len(ch)
for i := 0; i < count; i++ {
result = append(result, <-ch)
}
close(ch)
ch = nil
return result, nil
}
func ce8PollutionFull(domain string, sc ce8PolluteServerConfig, csrfToken string) (bool, error) {
u, err := url.Parse(sc.DotNetApi)
if err != nil {
log.Warn("ce8PollutionFull parseAPI", log.Any("DotNetApi", sc.DotNetApi), log.E(err))
return false, err
}
u.Path = "/api/define/pollution_full"
reqBody, _ := json.Marshal(&ce8PollutionFullReq{URL: domain})
checkURL := u.String()
req, err := http.NewRequest("POST", checkURL, bytes.NewReader(reqBody))
if err != nil {
log.Warn("ce8PollutionFull newRequest", log.Any("url", checkURL), log.E(err))
return false, err
}
req.Header.Add("Connection", "keep-alive")
req.Header.Add("Pragma", "no-cache")
req.Header.Add("Cache-Control", "no-cache")
req.Header.Add("Accept", "application/json, text/javascript, */*; q=0.01")
if csrfToken != "" {
req.Header.Add("X-CSRF-TOKEN", csrfToken)
}
req.Header.Add("User-Agent", "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36")
req.Header.Add("Content-Type", "application/json; charset=UTF-8")
req.Header.Add("Origin", "http://www.ce8.com")
req.Header.Add("Referer", "http://www.ce8.com/")
req.Header.Add("Accept-Language", "en-US,en;q=0.9")
client := http.Client{Timeout: 150 * time.Second}
res, err := client.Do(req)
if err != nil {
log.Warn("ce8PollutionFull Client Do", log.Any("checkURL", checkURL), log.Any("domain", domain), log.E(err))
return false, err
}
defer func() { _ = res.Body.Close() }()
if res.StatusCode != http.StatusOK {
log.Warn("ce8PollutionFull Resp not OK", log.Any("checkURL", checkURL), log.Any("domain", domain), log.Any("statusCode", res.StatusCode))
return false, errors.New(fmt.Sprintf("StatusCode:%d", res.StatusCode))
}
body, err := ioutil.ReadAll(res.Body)
if err != nil {
log.Warn("ce8PollutionFull Resp ReadAll", log.Any("checkURL", checkURL), log.Any("domain", domain), log.E(err))
return false, err
}
var fullResp ce8PollutionFullResp
err = json.Unmarshal(body, &fullResp)
if err != nil {
log.Warn("ce8PollutionFull Unmarshal", log.Any("checkURL", checkURL), log.Any("domain", domain), log.Any("body", body), log.E(err))
return false, err
}
if fullResp.Code == 0 {
log.Info("ce8PollutionFull Code OK", log.Any("checkURL", checkURL), log.Any("domain", domain), log.Any("pollution", fullResp.Data.Pollution))
return fullResp.Data.Pollution, nil
}
log.Warn("ce8PollutionFull Code", log.Any("checkURL", checkURL), log.Any("domain", domain), log.Any("code", fullResp.Code))
return false, errors.New(fmt.Sprintf("respcode expect 0,got:%d", fullResp.Code))
}
+28
View File
@@ -0,0 +1,28 @@
package pollute
type CheckFrom = int
const (
Ce8 CheckFrom = iota
)
type CheckReq struct {
Domain string
Payload string
}
type CheckResult struct {
Domain string
Polled bool
Err error
Payload string
}
func Check(domains []CheckReq, checkFrom CheckFrom) ([]CheckResult, error) {
switch checkFrom {
case Ce8:
fallthrough
default:
return ce8PolluteCheck(domains)
}
}
+15
View File
@@ -0,0 +1,15 @@
package pollute
import (
"fmt"
"testing"
)
func TestPolluteCheck(t *testing.T) {
var reqs []CheckReq
reqs = append(reqs, CheckReq{Domain: "https://ppfff.me", Payload: "1"})
reqs = append(reqs, CheckReq{Domain: "pppff.me", Payload: "2"})
reqs = append(reqs, CheckReq{Domain: "https://ppff.live", Payload: "3"})
result, err := Check(reqs, Ce8)
fmt.Println(result, err)
}
+96
View File
@@ -0,0 +1,96 @@
package proxy
import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"stat/common/httpUtils"
"stat/common/utils"
"sync"
)
var (
buffer = bytes.Buffer{}
lock sync.Mutex
)
var (
header = map[string]string{"Content-Type": "application/json"}
)
const (
Success = 200
)
// PutObject put object to proxy
func PutObject(ctx context.Context, apiUrl, objpath string, body io.Reader) (path string, err error) {
lock.Lock()
defer lock.Unlock()
buffer.Reset()
_, err = buffer.ReadFrom(body)
if err != nil {
return
}
base64data := base64.StdEncoding.EncodeToString(buffer.Bytes())
r := objreq{
Name: utils.PathForInfo(objpath).FullFileName,
Data: base64data,
}
jsonBytes, err := json.Marshal(r)
if err != nil {
return
}
resp, err := httpUtils.Post(apiUrl, header, bytes.NewReader(jsonBytes))
if err != nil {
return
}
var or objresp
err = json.Unmarshal(resp.Data, &or)
if err != nil {
fmt.Println(apiUrl, string(resp.Data))
return
}
if or.Code != Success {
err = errors.New(or.Msg)
return
}
path = or.Object.Name
return
}
func PutBase64Object(ctx context.Context, apiUrl, objpath string, base64data string) (path string, err error) {
r := objreq{
Name: utils.PathForInfo(objpath).FullFileName,
Data: base64data,
}
jsonbytes, err := json.Marshal(r)
if err != nil {
return
}
resp, err := httpUtils.Post(apiUrl, header, bytes.NewReader(jsonbytes))
if err != nil {
return
}
var or objresp
err = json.Unmarshal(resp.Data, &or)
if err != nil {
return
}
if or.Code != Success {
err = errors.New(or.Msg)
return
}
path = or.Object.Name
return
}
+31
View File
@@ -0,0 +1,31 @@
package proxy
import (
"context"
"fmt"
"log"
"os"
)
func ExamplePutObject() {
file, err := os.OpenFile("/Users/mac/Desktop/abc.jpeg", os.O_RDONLY, 0655)
if err != nil {
fmt.Println(err)
return
}
filestat, err := file.Stat()
if err != nil {
fmt.Println(err)
return
}
path, err := PutObject(context.TODO(), "http://img.bbaazz.me/file/upload", filestat.Name(), file)
if err != nil {
fmt.Println(err)
return
}
log.Println("path", path, "err", err)
// Output:
}
+17
View File
@@ -0,0 +1,17 @@
package proxy
type objreq struct {
Name string `json:"fileName"`
Data string `json:"fileData"`
}
type Obj struct {
Domain string `json:"domain"`
Name string `json:"fileName"`
}
type objresp struct {
Code int `json:"code"`
Object Obj `json:"data"`
Msg string `json:"msg"`
}
+15
View File
@@ -0,0 +1,15 @@
package common
/*type AppReqType int32
const (
AppRecommend AppReqType = 1 //推荐
AppFree AppReqType = 2 //免费约妹神器
)
*/
type CategoryRank struct {
CID int64 `json:"cid" bson:"cid"`
Name string `json:"name" bson:"-"`
Rank int64 `json:"rank" bson:"rank"`
}
+37
View File
@@ -0,0 +1,37 @@
package common
import (
"math/rand"
"time"
)
//生成随机字符串
func RandStr(strLen int) string {
rand.Seed(time.Now().UnixNano())
data := make([]byte, strLen)
var num int
for i := 0; i < strLen; i++ {
num = rand.Intn(57) + 65
for {
if num > 90 && num < 97 {
num = rand.Intn(57) + 65
} else {
break
}
}
data[i] = byte(num)
}
return string(data)
}
func ArrayReverseInt(s []int) []int {
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
s[i], s[j] = s[j], s[i]
}
return s
}
func ArrayReverseByte(s []byte) []byte {
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
s[i], s[j] = s[j], s[i]
}
return s
}
+33
View File
@@ -0,0 +1,33 @@
package redis
import (
"errors"
"fmt"
"github.com/go-redis/redis"
)
var StatRds *Manager
type Manager struct {
client *redis.Client
options *redis.Options
}
// Redis 配置项
type Options = redis.Options
func NewManagerWithUrl(url string, fn func(opt *Options)) (*Manager, error) {
opts, err := redis.ParseURL(url)
if err != nil {
return nil, err
}
if opts == nil {
err = errors.New(fmt.Sprintf("NewManagerWithUrl failed url %s", url))
return nil, err
}
if fn != nil {
fn(opts)
}
return &Manager{options: opts}, nil
}
+73
View File
@@ -0,0 +1,73 @@
package redis
import (
"fmt"
"time"
)
const (
AdvertiseClickDayHashTemplate = "stat-center.advertise.day.click.%s" // 广告点击
AdvertiseClickDayHashIdHourSep = "@"
IpLimiterKeyTemplate = "stat-center.%s:%d.ip.limiter.key#%s"
IpLimiterDayRecordKey = "stat-center.ip.limiter.day.record.key"
DownloadCheckBotMsgHashKeyTmpl = "stat-center.download.check.bot.msg.hash.key.%s" // 下载提示消息
ServerStartTimeHashKeyTemplate = "stat-center.server.start_time.hash.key.%d"
ServerKeepAliveHashKeyTemplate = "stat-center.server.keepalive.hash.key.%d"
ServerCloseFlagHashKeyTemplate = "stat-center.server.close.flag.hash.key.%d"
)
func GetServerStartTimeHashKey(index int) string {
return fmt.Sprintf(ServerStartTimeHashKeyTemplate, index)
}
func GetServerKeepAliveHashKey(index int) string {
return fmt.Sprintf(ServerKeepAliveHashKeyTemplate, index)
}
func GetServerCloseFlagHashKey(index int) string {
return fmt.Sprintf(ServerCloseFlagHashKeyTemplate, index)
}
func GetDownloadCheckBotMsgHashKey() string {
return fmt.Sprintf(DownloadCheckBotMsgHashKeyTmpl, time.Now().Format("2006-02-01-15"))
}
func GetIpLimitDayKey(serverIp string, serverPort int) string {
ts := time.Now().Local().Format("2006-02-01")
return fmt.Sprintf(IpLimiterKeyTemplate, serverIp, serverPort, ts)
}
func GetIpLimitSpecifiedDayKey(serverIp string, serverPort int, day time.Time) string {
ts := day.Local().Format("2006-02-01")
return fmt.Sprintf(IpLimiterKeyTemplate, serverIp, serverPort, ts)
}
func GetAdvertiseClickDayHash(now time.Time) *Hash {
t := now.Local().Format("2006-02-01")
return NewHash(StatRds.Client(), fmt.Sprintf(AdvertiseClickDayHashTemplate, t))
}
func GetAdvertiseClickHourName(appId int32, adsId int64, hour int) string {
return fmt.Sprintf("%d%s%d%s%d", appId, AdvertiseClickDayHashIdHourSep, adsId, AdvertiseClickDayHashIdHourSep, hour)
}
const (
NameSource = "name:source" //姓名池
NameSourceExpire = 2 * 24 * time.Hour
AppClickDaySetTemplate = "nh.app.day.click.%s" // app点击
AppClickDaySetIdHourSep = "@"
)
func GetAppClickDaySet(now time.Time) *Hash {
t := now.Local().Format("2006-02-01")
return NewHash(StatRds.Client(), fmt.Sprintf(AppClickDaySetTemplate, t))
}
func GetAppClickHourName(appId string, hour int) string {
return fmt.Sprintf("%s%s%d", appId, AppClickDaySetIdHourSep, hour)
}
func GetAppClickHourNameByAppId(appId int64, parentAppId int64, hour int) string {
return fmt.Sprintf("%s_%s%s%d", appId, parentAppId, AppClickDaySetIdHourSep, hour)
}
+107
View File
@@ -0,0 +1,107 @@
package redis
import (
"time"
"github.com/go-redis/redis"
)
//key: {name:value, name:value, name:value}
type Hash struct {
key string
client *redis.Client
}
func NewHash(client *redis.Client, key string) *Hash {
return &Hash{
key: key,
client: client,
}
}
func (s *Hash) Key() string {
return s.key
}
// Expire 设置集合过期时间
func (s *Hash) Expire(expiration time.Duration) (bool, error) {
return s.client.Expire(s.key, expiration).Result()
}
// Delete 删除一个或多个 Name
func (s *Hash) Delete(names ...string) (int64, error) {
return s.client.HDel(s.key, names...).Result()
}
// Exists 检测Name是否在哈希表中
func (s *Hash) Exists(name string) (bool, error) {
return s.client.HExists(s.key, name).Result()
}
// NameValues 获取所有哈希表数据
func (s *Hash) NameValues() (map[string]string, error) {
return s.client.HGetAll(s.key).Result()
}
// Names 获取所有哈希表的 Name
func (s *Hash) Names() ([]string, error) {
return s.client.HKeys(s.key).Result()
}
// Values 获取所有哈希表的 Total
func (s *Hash) Values() ([]string, error) {
return s.client.HVals(s.key).Result()
}
// SetNX 当name不存在时才向哈希表中设置一对Name:Total
func (s *Hash) SetNX(name string, value interface{}) (bool, error) {
return s.client.HSetNX(s.key, name, value).Result()
}
// Set 向哈希表中设置一对Name:Total
func (s *Hash) Set(name string, value interface{}) (bool, error) {
return s.client.HSet(s.key, name, value).Result()
}
// Get 获取哈希表中Name对应的Value
func (s *Hash) Get(name string) (string, error) {
return s.client.HGet(s.key, name).Result()
}
// IncInt 返回累加之后的新值
func (s *Hash) IncInt(name string, value int64) (int64, error) {
return s.client.HIncrBy(s.key, name, value).Result()
}
// IncFloat 返回累加之后的新值
func (s *Hash) IncFloat(name string, value float64) (float64, error) {
return s.client.HIncrByFloat(s.key, name, value).Result()
}
// Len 返回哈希表的大小
func (s *Hash) Len() (int64, error) {
return s.client.HLen(s.key).Result()
}
// Scan 扫描哈希表数据
func (s *Hash) Scan(match string, piece int64, callback func(name, value string)) error {
proc := func(l []string) {
for i := 0; i+1 < len(l); i += 2 {
callback(l[i], l[i+1])
}
}
lst, cur, err := s.client.HScan(s.key, 0, match, piece).Result()
if err != nil {
return err
}
proc(lst)
for cur > 0 {
lst, cur, err = s.client.HScan(s.key, cur, match, piece).Result()
if err != nil {
return err
}
proc(lst)
}
return nil
}
+110
View File
@@ -0,0 +1,110 @@
package redis
import (
_ "embed"
"encoding/json"
"errors"
"fmt"
"sync"
"time"
"github.com/go-redis/redis"
)
//go:embed limit.lua
var lua string
const (
SUCCESS = "success"
FAILED = "failed"
)
type Bucket struct {
Name string `json:"na"`
Capacity int64 `json:"ca"`
Rate int64 `json:"ra"`
Size int64 `json:"si"`
GenerateAt int64 `json:"ge_at"`
CreatedAt int64 `json:"cr_at"`
UpdatedAt int64 `json:"up_at"`
PassedCount int64 `json:"pa_co"`
RejectCount int64 `json:"re_co"`
PassedHours map[int64]int64 `json:"pa_ho"`
RejectHours map[int64]int64 `json:"re_ho"`
}
type BucketList []*Bucket
func (a BucketList) Len() int { return len(a) }
func (a BucketList) Less(i, j int) bool {
return a[i].PassedCount > a[j].PassedCount
}
func (a BucketList) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
type TokenBucket struct {
keyGen func() string
capacity int64
rate int64
script *redis.Script
client *redis.Client
expire time.Duration
lock sync.RWMutex
curKey string
}
func NewTokenBucket(client *redis.Client, keyGen func() string, capacity, rate int64, expire time.Duration) *TokenBucket {
return &TokenBucket{
expire: expire,
capacity: capacity,
rate: rate,
client: client,
script: redis.NewScript(lua),
keyGen: keyGen,
}
}
func (s *TokenBucket) TakeTokens(name string, count int64) error {
key := s.keyGen()
now := time.Now().Local()
cmd := []string{
key, //key
name, //name
fmt.Sprintf("%d", s.capacity),
fmt.Sprintf("%d", s.rate),
fmt.Sprintf("%d", count),
fmt.Sprintf("%d", now.UnixMilli()),
fmt.Sprintf("%d", now.Hour()),
}
ret, err := s.script.Run(s.client, cmd).String()
if err != nil {
return err
}
if ret != SUCCESS {
return errors.New("sorry, I can't work anymore~")
}
var same = false
if s.lock.TryRLock() {
same = key == s.curKey
s.lock.RUnlock()
} else {
return nil
}
if !same {
if s.lock.TryLock() {
if ok, err := s.client.Expire(key, s.expire).Result(); err == nil && ok {
s.curKey = key
}
s.lock.Unlock()
}
}
return nil
}
func (s *TokenBucket) Traverse(callback func(key string, item *Bucket)) error {
return NewHash(s.client, s.keyGen()).Scan("", 1000, func(name, value string) {
bucket := Bucket{}
if err := json.Unmarshal([]byte(value), &bucket); err == nil {
callback(name, &bucket)
}
})
}
+90
View File
@@ -0,0 +1,90 @@
local CAPACITY = "ca"
local RATE = "ra"
local SIZE = "si"
local GENERATE_AT = "ge_at"
local CREATED_AT = "cr_at"
local UPDATED_AT = "up_at"
local PASSED_COUNT = "pa_co"
local REJECT_COUNT = "re_co"
local PASSED_HOURS = "pa_ho"
local REJECT_HOURS = "re_ho"
local SUCCESS = "success"
local FAILED = "failed"
local sKey = tostring(KEYS[1] or "")
local sName = tostring(KEYS[2] or "")
local iCapacity = tonumber(KEYS[3] or 0)
local iRate = tonumber(KEYS[4] or 0)
local iCount = tonumber(KEYS[5] or 0)
local iNow = tonumber(KEYS[6] or 0)
local sHour = tostring(KEYS[7] or "")
if sKey == "" or sName == "" or sHour == "" then
return FAILED .. " param error1"
end
if iCapacity <= 0 or iRate <= 0 or iCount <= 0 or iNow <= 0 then
return FAILED .. "param error2"
end
local bucket = {}
local data = redis.pcall("HGET", sKey, sName)
local init = false
if data == nil or data == false or data == "" then
init = true
else
bucket = cjson.decode(data)
if bucket == nil then
init = true
end
end
if init then
bucket[CAPACITY] = iCapacity
bucket[RATE] = iRate
bucket[SIZE] = iCapacity
bucket[GENERATE_AT] = iNow
bucket[CREATED_AT] = iNow
bucket[UPDATED_AT] = iNow
bucket[PASSED_COUNT] = 0
bucket[REJECT_COUNT] = 0
bucket[PASSED_HOURS] = {}
bucket[REJECT_HOURS] = {}
else
bucket[CAPACITY] = iCapacity
bucket[RATE] = iRate
end
local bk_size = tonumber(bucket[SIZE] or 0)
local bk_capacity = tonumber(bucket[CAPACITY] or 0)
if bk_size < bk_capacity then
local bk_last = tonumber(bucket[GENERATE_AT] or 0)
local dura = iNow - bk_last
local incr = math.floor(dura * iRate / 1000)
if incr >= 1 then
bk_size = math.min(bk_size + incr, bk_capacity)
bucket[SIZE] = bk_size
bucket[GENERATE_AT] = iNow
end
end
local message = FAILED
if iCount <= bk_size then
bucket[SIZE] = bk_size - iCount
bucket[PASSED_COUNT] = tonumber(bucket[PASSED_COUNT] or 0) + 1
local hours = bucket[PASSED_HOURS]
if hours == nil then
hours = {}
end
hours[sHour] = tonumber(hours[sHour] or 0) + 1
bucket[PASSED_HOURS] = hours
message = SUCCESS
else
bucket[REJECT_COUNT] = tonumber(bucket[REJECT_COUNT] or 0) + 1
local hours = bucket[REJECT_HOURS]
if hours == nil then
hours = {}
end
hours[sHour] = tonumber(hours[sHour] or 0) + 1
bucket[REJECT_HOURS] = hours
message = FAILED
end
bucket[UPDATED_AT] = iNow
data = cjson.encode(bucket)
redis.pcall("HSET", sKey, sName, data)
return message
+28
View File
@@ -0,0 +1,28 @@
package redis
import (
"fmt"
"testing"
"time"
)
func TestF2(t *testing.T) {
ts := time.Now().Local().Format("2006-02-01-15")
t.Log(ts)
}
func TestScan(t *testing.T) {
mgr, err := NewManagerWithUrl("redis://127.0.0.1:6379/1", nil)
if err != nil {
t.Error(err)
}
set := NewHash(mgr.Client(), "set-test-key")
for i := 0; i < 1000; i++ {
set.Set(fmt.Sprintf("name:%d", i), fmt.Sprintf("value:%d", i))
}
err = set.Scan("", 20, func(name, value string) {
t.Logf("N: %s, V:%s\n", name, value)
})
t.Log(err)
}
+34
View File
@@ -0,0 +1,34 @@
package redis
import (
"github.com/go-redis/redis"
"log"
)
func (m *Manager) Client() *redis.Client {
if m.client == nil {
m.initConnect()
}
return m.client
}
func (m *Manager) Close() {
if m.client != nil {
m.client.Close()
m.client = nil
}
}
func (m *Manager) initConnect() error {
return m.Connect(m.options)
}
func (m *Manager) Connect(opts *Options) error {
c := redis.NewClient(opts)
if err := c.Ping().Err(); err != nil {
log.Panicf("[error] ping redis err %+v\n", err)
return err
}
m.client = c
return nil
}
+93
View File
@@ -0,0 +1,93 @@
package redis
import (
"errors"
"time"
goredis "github.com/go-redis/redis"
)
func (m *Manager) Get(key string) (*string, error) {
if key == "" {
return nil, errors.New("key is empty")
}
data, err := m.Client().Get(key).Result()
if err != nil {
if err == goredis.Nil {
return nil, nil
}
return nil, err
} else {
return &data, nil
}
}
func (m *Manager) Set(key string, value interface{}, expire time.Duration) error {
if err := m.Client().Set(key, value, expire).Err(); err != nil {
return err
}
return nil
}
func (m *Manager) Del(keys ...string) error {
if err := m.Client().Del(keys...).Err(); err != nil {
return err
}
return nil
}
func (m *Manager) GetBytes(key string) ([]byte, error) {
cmd := m.Client().Get(key)
if err := cmd.Err(); err != nil {
return []byte{}, err
}
return cmd.Bytes()
}
func (m *Manager) SAdd(key string, members ...interface{}) error {
if err := m.Client().SAdd(key, members...).Err(); err != nil {
return err
}
return nil
}
func (m *Manager) Exists(keys ...string) (bool, error) {
cmd := m.Client().Exists(keys...)
if err := cmd.Err(); err != nil {
return false, err
}
return cmd.Val() > 0, nil
}
func (m *Manager) Expire(key string, expire time.Duration) error {
if err := m.Client().Expire(key, expire).Err(); err != nil {
return err
}
return nil
}
func (m *Manager) Keys(pattern string) ([]string, error) {
list := m.Client().Keys(pattern)
if err := list.Err(); err != nil {
return []string{}, err
}
return list.Val(), nil
}
func (m *Manager) Incr(pattern string) (int64, error) {
val, err := m.Client().Incr(pattern).Result()
if err != nil {
return 0, err
}
return val, nil
}
//获取key的剩余生存时间
func (m *Manager) TTL(pattern string) time.Duration {
durCmd := m.Client().TTL(pattern)
if durCmd != nil {
return durCmd.Val()
}
return 0
}
+75
View File
@@ -0,0 +1,75 @@
package fengniao
import (
"crypto/sha1"
"errors"
"fmt"
"net/http"
"stat/common/httputil"
"stat/common/log"
"stat/common/utils"
)
type requestBody struct {
MercId string `json:"mercId"`
Secret string `json:"secret"`
TemplateId string `json:"templateId"`
SmsCode string `json:"smsCode"`
Mobile string `json:"mobile"`
SignType string `json:"signType"` // 固定sha1
Sign string `json:"sign"` // 签名
}
type msg struct {
Status string `json:"status"`
TaskId string `json:"taskId"`
}
type responseBody struct {
Code int `json:"code"`
Msg msg `json:"msg"`
Err string `json:"err"`
}
type FengNiao struct {
Url string
MercId string
Secret string
Tmpl string
}
// Send 发送, 目前蜂鸟貌似只支持数字
func (f *FengNiao) Send(mobile string, code string) error {
data := fmt.Sprintf("mercId=%s&templateId=%s&smsCode=%s&mobile=%s&secret=%s", f.MercId, f.Tmpl, code, mobile, f.Secret)
sha := sha1.New()
_, err := sha.Write([]byte(data))
if err != nil {
log.Error("fengniao send error", log.Any("mobile", mobile), log.Any("templateId", f.Tmpl), log.E(err))
return err
}
args, _ := utils.JSONStruct2Map(&requestBody{
MercId: f.MercId,
Secret: f.Secret,
TemplateId: f.Tmpl,
SmsCode: code,
SignType: "sha1",
Mobile: mobile,
Sign: fmt.Sprintf("%x", sha.Sum(nil)),
})
rp := responseBody{}
statusCode, err := httputil.Client().POSTJsonWithJResp(&rp, f.Url, nil, args)
if err != nil {
log.Error("fengniao POSTJsonWithResp err", log.E(err))
return err
}
if statusCode != http.StatusOK {
log.Error("fengniao status code not 200")
return errors.New("fengniao statusCode not 200")
}
if rp.Code != 200 {
log.Error("fengniao code not 200", log.Any("errdesc", rp.Err))
err = errors.New(rp.Err)
}
return err
}
+58
View File
@@ -0,0 +1,58 @@
package sms
import (
"stat/common/sms/fengniao"
"stat/common/sms/yunpian"
"stat/common/ysphone"
"strings"
)
type Provider = int
const (
tmplChinaId = "5ecfc70ab6a62a37c00baf74" //大陆模版
interNationId = "5ecfbfc3b6a62a37c00ba945" //国际模版
chinaTmpl = "【PFYH】您的验证码是:%s。请不要把验证码泄露给其他人。"
interTmpl = "[YueHui] Your verification code is: %s Please do not disclose the verification code to others."
tmplYunPian = "【泡芙官网】您的验证码是:%s。请不要把验证码泄漏给其他人。"
)
const (
FengNiao Provider = 1 //蜂鸟
YunPian Provider = 2 //云片
)
func sendByFengNiao(mobile string, code string) error {
newMobile := ysphone.FormatPhoneNumber(mobile)
var tmplId string
if strings.HasPrefix(newMobile, "+86") {
tmplId = tmplChinaId
} else {
tmplId = interNationId
}
return (&fengniao.FengNiao{
Url: "https://rise.sudukj.com/api/sms/sendSms",
MercId: "10103",
Secret: "a7934623f508122fab27373fdee0e410",
Tmpl: tmplId,
}).Send(newMobile, code)
}
func sendByYunPian(mobile string, code string) error {
return (&yunpian.YunPian{
Apikey: "a2c52f92f570d839e203ce0c41eb4bc5",
Tmpl: tmplYunPian,
}).Send(mobile, code)
}
// Send 发送短信
func Send(p Provider, mobile string, code string) error {
switch p {
case FengNiao:
return sendByFengNiao(mobile, code)
case YunPian:
return sendByYunPian(mobile, code)
default:
return sendByFengNiao(mobile, code)
}
}
+11
View File
@@ -0,0 +1,11 @@
package sms
import (
"testing"
)
func TestSend(t *testing.T) {
// log.Init(log.Options{Level: "debug"})
err := Send(FengNiao, "+85581359280", "221156")
t.Log(err)
}
+34
View File
@@ -0,0 +1,34 @@
package yunpian
import (
"errors"
"fmt"
"stat/common/log"
ypclnt "github.com/yunpian/yunpian-go-sdk/sdk"
)
//YunPian 云片短信
type YunPian struct {
Apikey string
Tmpl string
}
//Send 发送验证码
func (yun *YunPian) Send(mobile string, code string) error {
text := fmt.Sprintf(yun.Tmpl, code)
clnt := ypclnt.New(yun.Apikey)
param := ypclnt.NewParam(2)
param[ypclnt.MOBILE] = mobile
param[ypclnt.TEXT] = text
resp := clnt.Sms().SingleSend(param)
if resp == nil {
log.Error("YunPian send error", log.Any("mobile", mobile), log.Any("code", code))
return errors.New("SingleSend error")
}
if !resp.IsSucc() {
log.Error("YunPian send fail:", log.Any("resp", resp))
return errors.New("SingleSend fail")
}
return nil
}
+42
View File
@@ -0,0 +1,42 @@
package statutils
type DeviceName string
const (
DeviceNameAndroid DeviceName = "android"
DeviceNameIos DeviceName = "ios"
DeviceNamePc DeviceName = "pc"
DeviceNameLight DeviceName = "light"
DeviceNameH5 DeviceName = "h5"
DeviceNameH5Android DeviceName = "h5_android"
DeviceNameH5Ios DeviceName = "h5_ios"
DeviceNameH5Pc DeviceName = "h5_pc"
)
func (d DeviceName) String() string {
return string(d)
}
const (
StatProjectRecharge = "recharge"
StatProjectRechargeOld = "recharge_old"
StatProjectRechargeNew = "recharge_new"
StatProjectRechargeGold = "recharge_gold"
StatProjectRechargeVip = "recharge_vip"
StatProjectOrder = "order"
StatProjectRegister = "register"
StatProjectVisit = "visit"
StatProjectGame = "game"
)
var StatProjectNameDict = map[string]string{
StatProjectRecharge: "充值统计",
StatProjectRechargeOld: "老用户充",
StatProjectRechargeNew: "新用户充",
StatProjectRechargeGold: "金币充值",
StatProjectRechargeVip: "会员充值",
StatProjectOrder: "订单统计",
StatProjectRegister: "注册用户",
StatProjectVisit: "访问统计",
StatProjectGame: "游戏统计",
}
+110
View File
@@ -0,0 +1,110 @@
package statutils
import (
"stat/common/utils"
"stat/models/syncrecord"
"sync"
"time"
)
type SyncItem struct {
AppID int32 // 应用ID
JobName string // 任务名称
JobDesc string // 任务描述
today time.Time // 内部日期
todayCount int64 // 今日拉取条数
CurrentCount int64 // 当前拉取条数
RequestBegin time.Time // 请求开始时间
RequestEnd time.Time // 请求结束时间
ProcessBegin time.Time // 处理开始时间
ProcessEnd time.Time // 处理结束时间
jobBegin time.Time // 任务开始时间
jobEnd time.Time // 任务结束时间
DataLatest time.Time // 拉到的数据的最后时间
Error error // 失败错误
load bool // 是否载入redis数据
}
func (s *SyncItem) IncCount(value int64) {
s.todayCount += value
s.CurrentCount += value
}
func (s *SyncItem) TodayCount() int64 {
return s.todayCount
}
func (s *SyncItem) JobBegin() time.Time {
return s.jobBegin
}
func (s *SyncItem) SetJobBegin(start time.Time) {
today := utils.BeginningOfDay(start)
s.jobBegin = start
if !s.load {
s.load = true
s.today = today
GetTodayCount(s)
}
if !s.today.Equal(today) {
s.today = today
s.todayCount = 0
s.CurrentCount = 0
}
}
func (s *SyncItem) JobEnd() time.Time {
return s.jobEnd
}
func (s *SyncItem) SetJobEnd(end time.Time) {
s.jobEnd = end
SetTodayCount(s)
}
type SyncMonitor struct {
mu sync.Mutex
dict map[int32]map[string]*SyncItem
}
func (s *SyncMonitor) SetData(appId int32, job string, setFunc func(item *SyncItem)) {
s.mu.Lock()
defer s.mu.Unlock()
jobs := s.dict[appId]
if jobs == nil {
jobs = map[string]*SyncItem{}
s.dict[appId] = jobs
}
item := jobs[job]
if item == nil {
item = &SyncItem{AppID: appId, JobName: job, JobDesc: syncrecord.SyncJobDescribe[job]}
jobs[job] = item
}
item.AppID = appId
item.JobName = job
setFunc(item)
// protect some item attr
item.AppID = appId
item.JobName = job
item.JobDesc = syncrecord.SyncJobDescribe[job]
}
func (s *SyncMonitor) Traverse(fn func(item *SyncItem)) {
s.mu.Lock()
defer s.mu.Unlock()
for _, jobs := range s.dict {
for _, item := range jobs {
fn(item)
}
}
}
var (
GlobalSyncMonitor = &SyncMonitor{
dict: map[int32]map[string]*SyncItem{},
}
)
+65
View File
@@ -0,0 +1,65 @@
package statutils
import (
"errors"
"fmt"
"stat/common/redis"
"strconv"
"time"
)
var hash *redis.Hash
func getHash(key string) (*redis.Hash, error) {
if hash != nil {
if hash.Key() == key {
return hash, nil
} else {
client := redis.StatRds.Client()
if client != nil {
hash = redis.NewHash(client, key)
hash.Expire(48 * time.Hour)
return hash, nil
}
return nil, errors.New("can not get redis client")
}
} else {
client := redis.StatRds.Client()
if client != nil {
hash = redis.NewHash(client, key)
hash.Expire(48 * time.Hour)
return hash, nil
}
return nil, errors.New("can not get redis client")
}
}
func SetTodayCount(item *SyncItem) error {
hashKey := fmt.Sprintf("sync-job-data-count-%s", item.JobBegin().Local().Format("2006-02-01"))
jobKey := fmt.Sprintf("app:%d-job:%s", item.AppID, item.JobName)
todayHash, err := getHash(hashKey)
if err != nil {
return err
}
_, err = todayHash.Set(jobKey, item.todayCount)
return err
}
func GetTodayCount(item *SyncItem) error {
hashKey := fmt.Sprintf("sync-job-data-count-%s", item.JobBegin().Local().Format("2006-02-01"))
jobKey := fmt.Sprintf("app:%d-job:%s", item.AppID, item.JobName)
todayHash, err := getHash(hashKey)
if err != nil {
return err
}
countS, err := todayHash.Get(jobKey)
if err != nil {
return err
}
countI, err := strconv.ParseInt(countS, 10, 64)
if err != nil {
return err
}
item.todayCount += countI
return nil
}
+243
View File
@@ -0,0 +1,243 @@
package statutils
import (
"encoding/json"
"fmt"
"runtime"
"sort"
"stat/common/utils"
"sync"
"time"
"unsafe"
)
var ServerMon = Monitor{}
type ErrorRecord struct {
Error error `json:"error"`
Occur time.Time `json:"occur"`
}
type JobItem struct {
Name string `json:"name"`
Call string `json:"call"`
Count int64 `json:"count"`
Start time.Time `json:"start"`
Stop time.Time `json:"stop"`
Avg time.Duration `json:"avg"`
Gap time.Duration `json:"gap"`
Error error `json:"error"`
SuccCount int64 `json:"succ_count"`
ErrorCount int64 `json:"error_count"`
Errors []*ErrorRecord `json:"errors"`
}
func SpendTimeIcon(d time.Duration) string {
if d < 5*time.Second {
return "\U0001F7E2\U0001F7E2\U0001F7E2"
} else if d < 10*time.Second {
return "\U0001F7E2\U0001F7E2\U0001F7E0"
} else if d < 30*time.Second {
return "\U0001F7E2\U0001F7E0\U0001F7E0"
} else if d < 80*time.Second {
return "\U0001F7E0\U0001F7E0\U0001F7E0"
} else if d < 220*time.Second {
return "\U0001F7E0\U0001F7E0🔴"
} else if d < 600*time.Second {
return "\U0001F7E0🔴🔴"
} else if d < 1640*time.Second {
return "🔴🔴🔴"
} else {
return "🆘🆘🆘"
}
}
func (s *JobItem) Status() string {
if s.Count > s.SuccCount+s.ErrorCount {
return "💛执行中"
} else {
if s.Error != nil {
return "💔执行出错"
} else {
return "💚执行成功"
}
}
}
func (s *JobItem) Display() string {
text := fmt.Sprintf("【函数】%s\n", s.Name)
text += fmt.Sprintf("【调用】%s\n", s.Call)
text += fmt.Sprintf("【进入】%d次\n", s.Count)
text += fmt.Sprintf("【状态】%s\n", s.Status())
text += fmt.Sprintf("【开始】%s\n", s.Start.Format("15:04:05"))
text += fmt.Sprintf("【结束】%s\n", s.Stop.Format("15:04:05"))
text += fmt.Sprintf("【用时】%s\n", s.Stop.Sub(s.Start))
text += fmt.Sprintf("【速度】%s\n", SpendTimeIcon(s.Stop.Sub(s.Start)))
text += fmt.Sprintf("【平均】%s\n", s.Avg)
text += fmt.Sprintf("【间隔】%s\n", s.Gap)
text += fmt.Sprintf("【成功】%d次\n", s.SuccCount)
text += fmt.Sprintf("【出错】%d次\n", s.ErrorCount)
if len(s.Errors) > 0 {
text += fmt.Sprintf("【最前出错信息】🔻%s\n", s.Errors[0].Error.Error())
}
if len(s.Errors) > 1 {
text += fmt.Sprintf("【最后出错信息】🔺%s\n", s.Errors[len(s.Errors)-1].Error.Error())
}
return text
}
func newJob(name string, call string) *JobItem {
now := time.Now()
return &JobItem{
Name: name,
Call: call,
Count: 1,
Start: now,
Stop: now,
Errors: make([]*ErrorRecord, 0),
}
}
func (s *JobItem) Begin() {
now := time.Now()
if s.Count > 0 {
s.Gap = now.Sub(s.Start)
}
s.Start = now
s.Count++
}
func (s *JobItem) End(err error) {
now := time.Now()
s.Error = err
s.Stop = now
if err != nil {
s.ErrorCount++
s.Errors = append(s.Errors, &ErrorRecord{
Error: err,
Occur: now,
})
} else {
s.SuccCount++
s.Avg = time.Duration(int64(s.Avg+(s.Stop.Sub(s.Start))) / s.SuccCount)
}
}
type Monitor struct {
lock sync.Mutex
jobs map[string]*JobItem
}
func (s *Monitor) call(callFunc func() error, funcName string, callName string) error {
func() {
s.lock.Lock()
defer s.lock.Unlock()
if s.jobs == nil {
s.jobs = make(map[string]*JobItem)
}
job, ok := s.jobs[funcName]
if !ok {
job = newJob(funcName, callName)
s.jobs[funcName] = job
} else {
job.Begin()
}
}()
err := callFunc()
func() {
s.lock.Lock()
defer s.lock.Unlock()
job := s.jobs[funcName]
if job != nil {
job.End(err)
}
}()
return err
}
func (s *Monitor) Run(fn func() error) error {
defer utils.RecoverError(nil)
callName := GetCallName(2)
funcName := GetFuncName(fn)
call := func() (err error) {
defer utils.RecoverError(func(err1 error) {
err = err1
})
err = fn()
return err
}
return s.call(call, funcName, callName)
}
func (s *Monitor) RunWithName(fn func() error, funcName string) error {
defer utils.RecoverError(nil)
callName := GetCallName(2)
call := func() (err error) {
defer utils.RecoverError(func(err1 error) {
err = err1
})
err = fn()
return err
}
return s.call(call, funcName, callName)
}
func (s *Monitor) Run1(fn func(v time.Time) error, param time.Time) error {
defer utils.RecoverError(nil)
callName := GetCallName(2)
funcName := GetFuncName(fn)
call := func() (err error) {
defer utils.RecoverError(func(err1 error) {
err = err1
})
err = fn(param)
return err
}
return s.call(call, funcName, callName)
}
func (s *Monitor) ToJson() string {
s.lock.Lock()
defer s.lock.Unlock()
data, _ := json.Marshal(s.jobs)
return string(data)
}
func (s *Monitor) Display() string {
s.lock.Lock()
defer s.lock.Unlock()
list := make(JobItemList, 0)
for _, job := range s.jobs {
list = append(list, job)
}
sort.Sort(list)
text := fmt.Sprintf("后台自动任务(%d个):\n", len(s.jobs))
for _, job := range list {
text += job.Display()
text += "***** *****\n"
}
return text
}
func GetCallName(skip int) string {
pc, _, _, _ := runtime.Caller(skip)
return runtime.FuncForPC(pc).Name()
}
func GetFuncName(fn interface{}) string {
funcPC := func(fn interface{}) uintptr {
ptr := (*[2]uintptr)(unsafe.Pointer(&fn))[1]
return **((**uintptr)(unsafe.Pointer(&ptr)))
}
pc := funcPC(fn)
return runtime.FuncForPC(pc).Name()
}
type JobItemList []*JobItem
func (a JobItemList) Len() int { return len(a) }
func (a JobItemList) Less(i, j int) bool {
return a[i].Name < a[j].Name
}
func (a JobItemList) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
+20
View File
@@ -0,0 +1,20 @@
package statutils
import (
"testing"
)
func AAA() error {
return nil
}
func TTT(fn func() error) string {
return GetFuncName(fn)
}
func TestGetFuncName(t *testing.T) {
s := TTT(AAA)
t.Log("函数名:" + s)
}
+728
View File
@@ -0,0 +1,728 @@
package statutils
import (
"fmt"
"math"
)
type Value struct {
Value int64 `json:"value" bson:"value"` // Value
Count int64 `json:"count" bson:"count"` // Count
}
func (s *Value) CountRef() *int64 {
return &s.Count
}
func (s *Value) ValueRef() *int64 {
return &s.Value
}
func (s *Value) Add(value int64) {
s.Count++
s.Value += value
}
func (s *Value) Sub(d *Value) *Value {
ret := &Value{
Count: s.Count - d.Count,
Value: s.Value - d.Value,
}
return ret
}
func (s *Value) Plus(d *Value) *Value {
ret := &Value{
Count: s.Count + d.Count,
Value: s.Value + d.Value,
}
return ret
}
func (s *Value) ACC(d *Value) {
s.Count += d.Count
s.Value += d.Value
}
func (s *Value) Percentage(den *Value) *Value {
ret := &Value{
Count: percentageDiv(s.Count, den.Count),
Value: percentageDiv(s.Value, den.Value),
}
return ret
}
func (s *Value) Permil(den *Value) *Value {
ret := &Value{
Count: permilDiv(s.Count, den.Count),
Value: permilDiv(s.Value, den.Value),
}
return ret
}
func (s *Value) Mul(v int64) *Value {
ret := &Value{
Count: s.Count * v,
Value: s.Value * v,
}
return ret
}
type Direct struct {
Total Value `json:"total" bson:"total"` // Total
Natural Value `json:"natural" bson:"natural"` // Natural
}
func (s *Direct) CountRef() *int64 {
return &s.Total.Count
}
func (s *Direct) Count() int64 {
return s.Total.Count
}
func (s *Direct) ValueRef() *int64 {
return &s.Total.Value
}
func (s *Direct) Value() int64 {
return s.Total.Value
}
func (s *Direct) Add(value int64, device *ClientInfo) {
s.Total.Add(value)
if device.Natural {
s.Natural.Add(value)
}
}
func (s *Direct) Sub(d *Direct) *Direct {
ret := &Direct{
Total: *s.Total.Sub(&d.Total),
Natural: *s.Natural.Sub(&d.Natural),
}
return ret
}
func (s *Direct) Plus(d *Direct) *Direct {
ret := &Direct{
Total: *s.Total.Plus(&d.Total),
Natural: *s.Natural.Plus(&d.Natural),
}
return ret
}
func (s *Direct) ACC(d *Direct) {
s.Total.ACC(&d.Total)
s.Natural.ACC(&d.Natural)
}
func (s *Direct) Percentage(den *Direct) *Direct {
ret := &Direct{
Total: *s.Total.Percentage(&den.Total),
Natural: *s.Total.Percentage(&den.Total),
}
return ret
}
func (s *Direct) Permil(den *Direct) *Direct {
ret := &Direct{
Total: *s.Total.Permil(&den.Total),
Natural: *s.Natural.Permil(&den.Natural),
}
return ret
}
func (s *Direct) Mul(v int64) *Direct {
ret := &Direct{
Total: *s.Total.Mul(v),
Natural: *s.Natural.Mul(v),
}
return ret
}
type From struct {
Total Value `json:"total" bson:"total"` // Total
// ---------------------分类1------------------------
Web Value `json:"web" bson:"web"` // 网站
Share Value `json:"share" bson:"share"` // 分享
Normal Value `json:"normal" bson:"normal"` // 自来
District Value `json:"district" bson:"district"` // 渠道
// ---------------------分类2------------------------
Direct Direct `json:"direct" bson:"direct"` // 直推
Fission Value `json:"fission" bson:"fission"` // 裂变
Deduction Value `json:"deduction" bson:"deduction"` // 扣量
}
type StatH5 struct {
// 总计
Total From `json:"total" bson:"total"` // Total = Pc + Ios + Android
// 分类1
Pc From `json:"h5Pc" bson:"h5Pc"` // H5PC
Ios From `json:"h5Ios" bson:"h5Ios"` // H5IOS
Android From `json:"h5Android" bson:"h5Android"` // Android
// 分类2
Guest From `json:"h5Guest" bson:"h5Guest"` // Guest
Formal From `json:"h5Formal" bson:"h5Formal"` // H5正式用户
}
func (s *StatH5) Sub(d *StatH5) *StatH5 {
ret := &StatH5{
Total: *s.Total.Sub(&d.Total),
Pc: *s.Pc.Sub(&d.Pc),
Ios: *s.Ios.Sub(&d.Ios),
Android: *s.Android.Sub(&d.Android),
Guest: *s.Guest.Sub(&d.Guest),
Formal: *s.Formal.Sub(&d.Formal),
}
return ret
}
func (s *StatH5) Plus(d *StatH5) *StatH5 {
ret := &StatH5{
Total: *s.Total.Plus(&d.Total),
Pc: *s.Pc.Plus(&d.Pc),
Ios: *s.Ios.Plus(&d.Ios),
Android: *s.Android.Plus(&d.Android),
Guest: *s.Guest.Plus(&d.Guest),
Formal: *s.Formal.Plus(&d.Formal),
}
return ret
}
func (s *StatH5) ACC(d *StatH5) {
s.Total.ACC(&d.Total)
s.Pc.ACC(&d.Pc)
s.Ios.ACC(&d.Ios)
s.Android.ACC(&d.Android)
s.Guest.ACC(&d.Guest)
s.Formal.ACC(&d.Formal)
}
func (s *StatH5) Mul(v int64) *StatH5 {
ret := &StatH5{
Total: *s.Total.Mul(v),
Pc: *s.Pc.Mul(v),
Ios: *s.Ios.Mul(v),
Android: *s.Android.Mul(v),
Guest: *s.Guest.Mul(v),
Formal: *s.Formal.Mul(v),
}
return ret
}
func (s *StatH5) Add(value int64, device *ClientInfo) {
s.Total.Add(value, device)
if device.H5Android {
s.Android.Add(value, device)
} else if device.H5Ios {
s.Ios.Add(value, device)
} else if device.H5Pc {
s.Pc.Add(value, device)
}
if device.Formal {
s.Formal.Add(value, device)
} else {
s.Guest.Add(value, device)
}
}
func (s *StatH5) CountRef() *int64 {
return s.Total.CountRef()
}
func (s *StatH5) Count() int64 {
return s.Total.Count()
}
func (s *StatH5) ValueRef() *int64 {
return s.Total.ValueRef()
}
func (s *StatH5) Value() int64 {
return s.Total.Value()
}
func (s *StatH5) Percentage(den *StatH5) *StatH5 {
ret := &StatH5{
Total: *s.Total.Percentage(&den.Total),
Pc: *s.Pc.Percentage(&den.Pc),
Ios: *s.Ios.Percentage(&den.Ios),
Android: *s.Android.Percentage(&den.Android),
Guest: *s.Guest.Percentage(&den.Guest),
Formal: *s.Formal.Percentage(&den.Formal),
}
return ret
}
func (s *StatH5) Permil(den *StatH5) *StatH5 {
ret := &StatH5{
Total: *s.Total.Permil(&den.Total),
Pc: *s.Pc.Permil(&den.Pc),
Ios: *s.Ios.Permil(&den.Ios),
Android: *s.Android.Permil(&den.Android),
Guest: *s.Guest.Permil(&den.Guest),
Formal: *s.Formal.Permil(&den.Formal),
}
return ret
}
func (s *StatH5) Rows(div int64, compare *StatH5) [][]string {
if div == 0 {
div = 1
}
ret := make([][]string, 0)
if compare != nil {
ret = append(ret, s.Total.Row("H5总计", div, &compare.Total))
ret = append(ret, s.Android.Row("H5安卓", div, &compare.Android))
ret = append(ret, s.Ios.Row("H5苹果", div, &compare.Ios))
ret = append(ret, s.Pc.Row("H5电脑", div, &compare.Pc))
ret = append(ret, s.Guest.Row("H5游客", div, &compare.Guest))
ret = append(ret, s.Formal.Row("H5注册", div, &compare.Formal))
} else {
ret = append(ret, s.Total.Row("H5总计", div, nil))
ret = append(ret, s.Android.Row("H5安卓", div, nil))
ret = append(ret, s.Ios.Row("H5苹果", div, nil))
ret = append(ret, s.Pc.Row("H5电脑", div, nil))
ret = append(ret, s.Guest.Row("H5游客", div, nil))
ret = append(ret, s.Formal.Row("H5注册", div, nil))
}
return ret
}
type Stat struct {
Total From `json:"total" bson:"total"` // Total
Android From `json:"android" bson:"android"` // Android
Ios From `json:"ios" bson:"ios"` // IOS
Light StatLight `json:"light" bson:"light"` // Light
H5 StatH5 `json:"h5" bson:"h5"` // H5
}
func (s *Stat) Mul(v int64) *Stat {
ret := &Stat{
Total: *s.Total.Mul(v),
Android: *s.Android.Mul(v),
Ios: *s.Ios.Mul(v),
Light: *s.Light.Mul(v),
H5: *s.H5.Mul(v),
}
return ret
}
func (s *Stat) Percentage(den *Stat) *Stat {
ret := &Stat{
Total: *s.Total.Percentage(&den.Total),
Android: *s.Android.Percentage(&den.Android),
Ios: *s.Ios.Percentage(&den.Ios),
Light: *s.Light.Percentage(&den.Light),
H5: *s.H5.Percentage(&den.H5),
}
return ret
}
func (s *Stat) Permil(den *Stat) *Stat {
ret := &Stat{
Total: *s.Total.Permil(&den.Total),
Android: *s.Android.Permil(&den.Android),
Ios: *s.Ios.Permil(&den.Ios),
Light: *s.Light.Permil(&den.Light),
H5: *s.H5.Permil(&den.H5),
}
return ret
}
func (s *Stat) Sub(d *Stat) *Stat {
ret := &Stat{
Total: *s.Total.Sub(&d.Total),
Android: *s.Android.Sub(&d.Android),
Ios: *s.Ios.Sub(&d.Ios),
Light: *s.Light.Sub(&d.Light),
H5: *s.H5.Sub(&d.H5),
}
return ret
}
func (s *Stat) Plus(d *Stat) *Stat {
ret := &Stat{
Total: *s.Total.Plus(&d.Total),
Android: *s.Android.Plus(&d.Android),
Ios: *s.Ios.Plus(&d.Ios),
Light: *s.Light.Plus(&d.Light),
H5: *s.H5.Plus(&d.H5),
}
return ret
}
func (s *Stat) ACC(d *Stat) {
s.Total.ACC(&d.Total)
s.Android.ACC(&d.Android)
s.Ios.ACC(&d.Ios)
s.Light.ACC(&d.Light)
s.H5.ACC(&d.H5)
}
func (s *Stat) Add(value int64, device *ClientInfo) {
s.Total.Add(value, device)
if device.Android {
s.Android.Add(value, device)
} else if device.Ios {
s.Ios.Add(value, device)
} else if device.Light {
s.Light.Add(value, device)
} else if device.H5 {
s.H5.Add(value, device)
}
}
func (s *Stat) CountRef() *int64 {
return s.Total.CountRef()
}
func (s *Stat) Count() int64 {
return s.Total.Count()
}
func (s *Stat) ValueRef() *int64 {
return s.Total.ValueRef()
}
func (s *Stat) Value() int64 {
return s.Total.Value()
}
func (s *Stat) Rows(div int64, compare *Stat) [][]string {
if div == 0 {
div = 1
}
ret := make([][]string, 0)
if compare != nil {
ret = append(ret, s.Android.Row("安卓", div, &compare.Android))
ret = append(ret, s.Ios.Row("苹果", div, &compare.Ios))
ret = append(ret, s.Light.Rows(div, &compare.Light)...)
ret = append(ret, s.H5.Rows(div, &compare.H5)...)
ret = append(ret, s.Total.Row("合计", div, &compare.Total))
} else {
ret = append(ret, s.Android.Row("安卓", div, nil))
ret = append(ret, s.Ios.Row("苹果", div, nil))
ret = append(ret, s.Light.Rows(div, nil)...)
ret = append(ret, s.H5.Rows(div, nil)...)
ret = append(ret, s.Total.Row("合计", div, nil))
}
return ret
}
func (s *From) Sub(d *From) *From {
ret := &From{
Total: *s.Total.Sub(&d.Total),
Web: *s.Web.Sub(&d.Web),
Normal: *s.Normal.Sub(&d.Normal),
District: *s.District.Sub(&d.District),
Share: *s.Share.Sub(&d.Share),
Direct: *s.Direct.Sub(&d.Direct),
Fission: *s.Fission.Sub(&d.Fission),
Deduction: *s.Deduction.Sub(&d.Deduction),
}
return ret
}
func (s *From) Plus(d *From) *From {
ret := &From{
Total: *s.Total.Plus(&d.Total),
Web: *s.Web.Plus(&d.Web),
Normal: *s.Normal.Plus(&d.Normal),
District: *s.District.Plus(&d.District),
Share: *s.Share.Plus(&d.Share),
Direct: *s.Direct.Plus(&d.Direct),
Fission: *s.Fission.Plus(&d.Fission),
Deduction: *s.Deduction.Plus(&d.Deduction),
}
return ret
}
func (s *From) ACC(d *From) {
s.Total.ACC(&d.Total)
s.Web.ACC(&d.Web)
s.Normal.ACC(&d.Normal)
s.District.ACC(&d.District)
s.Share.ACC(&d.Share)
s.Direct.ACC(&d.Direct)
s.Fission.ACC(&d.Fission)
s.Deduction.ACC(&d.Deduction)
}
func (s *From) Mul(v int64) *From {
ret := &From{
Total: *s.Total.Mul(v),
Web: *s.Web.Mul(v),
Normal: *s.Normal.Mul(v),
District: *s.District.Mul(v),
Share: *s.Share.Mul(v),
Direct: *s.Direct.Mul(v),
Fission: *s.Fission.Mul(v),
Deduction: *s.Deduction.Mul(v),
}
return ret
}
func (s *From) Percentage(den *From) *From {
ret := &From{
Total: *s.Total.Percentage(&den.Total),
Web: *s.Web.Percentage(&den.Web),
Normal: *s.Normal.Percentage(&den.Normal),
District: *s.District.Percentage(&den.District),
Share: *s.Share.Percentage(&den.Share),
Direct: *s.Direct.Percentage(&den.Direct),
Fission: *s.Fission.Percentage(&den.Fission),
Deduction: *s.Deduction.Percentage(&den.Deduction),
}
return ret
}
func (s *From) Permil(den *From) *From {
ret := &From{
Total: *s.Total.Permil(&den.Total),
Web: *s.Web.Permil(&den.Web),
Normal: *s.Normal.Permil(&den.Normal),
District: *s.District.Permil(&den.District),
Share: *s.Share.Permil(&den.Share),
Direct: *s.Direct.Permil(&den.Direct),
Fission: *s.Fission.Permil(&den.Fission),
Deduction: *s.Deduction.Permil(&den.Deduction),
}
return ret
}
func (s *From) Row(title string, div int64, compare *From) []string {
if div == 0 {
div = 1
}
if compare == nil {
return []string{
title,
fmt.Sprintf("%d", s.Total.Value/div),
fmt.Sprintf("%d", s.Web.Value/div),
fmt.Sprintf("%d", s.Normal.Value/div),
fmt.Sprintf("%d", s.District.Value/div),
fmt.Sprintf("%d", s.Share.Value/div),
fmt.Sprintf("%d", s.Direct.Total.Value/div),
fmt.Sprintf("%d", s.Fission.Value/div),
fmt.Sprintf("%d", s.Deduction.Value/div),
}
} else {
diff := s.Sub(compare)
rate := diff.Permil(compare)
return []string{
title,
fmt.Sprintf("%d\n%s", s.Total.Value/div, DisplayPermilToPercentage(rate.Total.Value)),
fmt.Sprintf("%d\n%s", s.Web.Value/div, DisplayPermilToPercentage(rate.Web.Value)),
fmt.Sprintf("%d\n%s", s.Normal.Value/div, DisplayPermilToPercentage(rate.Normal.Value)),
fmt.Sprintf("%d\n%s", s.District.Value/div, DisplayPermilToPercentage(rate.District.Value)),
fmt.Sprintf("%d\n%s", s.Share.Value/div, DisplayPermilToPercentage(rate.Share.Value)),
fmt.Sprintf("%d\n%s", s.Direct.Total.Value/div, DisplayPermilToPercentage(rate.Direct.Total.Value)),
fmt.Sprintf("%d\n%s", s.Fission.Value/div, DisplayPermilToPercentage(rate.Fission.Value)),
fmt.Sprintf("%d\n%s", s.Deduction.Value/div, DisplayPermilToPercentage(rate.Deduction.Value)),
}
}
}
func (s *From) Header(title string) []string {
return []string{title, "总量", "官网", "自来", "渠道", "分享", "直推", "裂变", "扣量"}
}
func (s *From) Add(value int64, client *ClientInfo) {
s.Total.Add(value)
// 分类1统计
if client.Web {
s.Web.Add(value)
} else if client.Share {
s.Share.Add(value)
} else if client.Normal {
s.Normal.Add(value)
} else if client.District {
s.District.Add(value)
}
// 分类2统计
if client.Deduction {
s.Deduction.Add(value)
} else {
if client.Direct {
s.Direct.Add(value, client)
} else {
s.Fission.Add(value)
}
}
}
func (s *From) CountRef() *int64 {
return s.Total.CountRef()
}
func (s *From) Count() int64 {
return s.Total.Count
}
func (s *From) ValueRef() *int64 {
return s.Total.ValueRef()
}
func (s *From) Value() int64 {
return s.Total.Value
}
func percentageDiv(num, den int64) int64 {
if num == 0 {
return 0
} else {
if den == 0 {
return math.MaxInt64
} else {
return num * 100 / den
}
}
}
func permilDiv(num, den int64) int64 {
if num == 0 {
return 0
} else {
if den == 0 {
return math.MaxInt64
} else {
return num * 1000 / den
}
}
}
func DisplayPermilToPercentage(v int64) string {
var ret string
var x float64
if v == math.MaxInt64 {
s := "##.##"
ret = fmt.Sprintf("%s", s)
return ret
}
if v < -99999 {
s := "--.--"
ret = fmt.Sprintf("%s", s)
return ret
}
if v > 99999 {
s := "++.++"
ret = fmt.Sprintf("%s", s)
return ret
}
if v < 0 {
x = float64(v) / -10.0
s := fmt.Sprintf("-%.1f%%", x)
ret = fmt.Sprintf("%s", s)
} else if v > 0 {
x = float64(v) / 10.0
s := fmt.Sprintf("+%.1f%%", x)
ret = fmt.Sprintf("%s", s)
} else {
s := "00.0%"
ret = fmt.Sprintf("%s", s)
}
return ret
}
type StatLight struct {
// 总计
Total From `json:"total" bson:"total"` // Total = Pc + Ios + Android
// 分类
Guest From `json:"guest" bson:"guest"` // Guest
Formal From `json:"formal" bson:"formal"` // 正式用户
}
func (s *StatLight) Sub(d *StatLight) *StatLight {
ret := &StatLight{
Total: *s.Total.Sub(&d.Total),
Guest: *s.Guest.Sub(&d.Guest),
Formal: *s.Formal.Sub(&d.Formal),
}
return ret
}
func (s *StatLight) Plus(d *StatLight) *StatLight {
ret := &StatLight{
Total: *s.Total.Plus(&d.Total),
Guest: *s.Guest.Plus(&d.Guest),
Formal: *s.Formal.Plus(&d.Formal),
}
return ret
}
func (s *StatLight) ACC(d *StatLight) {
s.Total.ACC(&d.Total)
s.Guest.ACC(&d.Guest)
s.Formal.ACC(&d.Formal)
}
func (s *StatLight) Mul(v int64) *StatLight {
ret := &StatLight{
Total: *s.Total.Mul(v),
Guest: *s.Guest.Mul(v),
Formal: *s.Formal.Mul(v),
}
return ret
}
func (s *StatLight) Add(value int64, device *ClientInfo) {
s.Total.Add(value, device)
if device.Formal {
s.Formal.Add(value, device)
} else {
s.Guest.Add(value, device)
}
}
func (s *StatLight) CountRef() *int64 {
return s.Total.CountRef()
}
func (s *StatLight) Count() int64 {
return s.Total.Count()
}
func (s *StatLight) ValueRef() *int64 {
return s.Total.ValueRef()
}
func (s *StatLight) Value() int64 {
return s.Total.Value()
}
func (s *StatLight) Percentage(den *StatLight) *StatLight {
ret := &StatLight{
Total: *s.Total.Percentage(&den.Total),
Guest: *s.Guest.Percentage(&den.Guest),
Formal: *s.Formal.Percentage(&den.Formal),
}
return ret
}
func (s *StatLight) Permil(den *StatLight) *StatLight {
ret := &StatLight{
Total: *s.Total.Permil(&den.Total),
Guest: *s.Guest.Permil(&den.Guest),
Formal: *s.Formal.Permil(&den.Formal),
}
return ret
}
func (s *StatLight) Rows(div int64, compare *StatLight) [][]string {
if div == 0 {
div = 1
}
ret := make([][]string, 0)
if compare != nil {
ret = append(ret, s.Total.Row("轻量合计", div, &compare.Total))
ret = append(ret, s.Guest.Row("轻量安装", div, &compare.Guest))
ret = append(ret, s.Formal.Row("轻量注册", div, &compare.Formal))
} else {
ret = append(ret, s.Total.Row("轻量合计", div, nil))
ret = append(ret, s.Guest.Row("轻量安装", div, nil))
ret = append(ret, s.Formal.Row("轻量注册", div, nil))
}
return ret
}
+162
View File
@@ -0,0 +1,162 @@
package statutils
import (
_ "embed"
"os"
"strings"
"golang.org/x/exp/slices"
"github.com/vicanso/go-charts/v2"
"github.com/wcharczuk/go-chart/v2/drawing"
"github.com/golang/freetype"
"github.com/golang/freetype/truetype"
)
//go:embed 微软雅黑.ttf
var FontData []byte
var Font *truetype.Font
func init() {
f, err := freetype.ParseFont(FontData)
if err == nil {
Font = f
}
}
func (s *Stat) Table(title string, div int64, compare *Stat, filePath string) error {
if div == 0 {
div = 1
}
rows := make([][]string, 0)
if compare != nil {
rows = append(rows, s.Android.Row("安卓", div, &compare.Android))
rows = append(rows, s.Ios.Row("苹果", div, &compare.Ios))
rows = append(rows, s.Light.Rows(div, &compare.Light)...)
rows = append(rows, s.H5.Rows(div, &compare.H5)...)
rows = append(rows, s.Total.Row("合计", div, &compare.Total))
} else {
rows = append(rows, s.Android.Row("安卓", div, nil))
rows = append(rows, s.Ios.Row("苹果", div, nil))
rows = append(rows, s.Light.Rows(div, nil)...)
rows = append(rows, s.H5.Rows(div, nil)...)
rows = append(rows, s.Total.Row("合计", div, nil))
}
charts.SetDefaultWidth(810)
bgColor := drawing.ColorWhite
p, err := charts.TableOptionRender(charts.TableChartOption{
Header: s.Total.Header(title),
BackgroundColor: bgColor,
HeaderBackgroundColor: drawing.ColorFromHex("808080"),
RowBackgroundColors: []charts.Color{
bgColor,
},
HeaderFontColor: drawing.ColorBlack,
FontColor: drawing.ColorBlack,
Padding: charts.Box{
Top: 15,
Right: 10,
Bottom: 15,
Left: 10,
},
Data: rows,
Font: Font,
TextAligns: []string{
charts.AlignCenter,
charts.AlignLeft,
},
CellStyle: func(tc charts.TableCell) *charts.Style {
text := strings.TrimSpace(tc.Text)
style := &charts.Style{
Padding: charts.Box{
Bottom: 5,
},
}
if strings.Contains(text, "-") {
style.FontColor = drawing.ColorRed
} else if strings.Contains(text, "+") {
style.FontColor = drawing.ColorGreen
}
if slices.Contains([]int{1, 2, 3}, tc.Row) {
style.FillColor = drawing.ColorFromHex("ec8f75")
} else if slices.Contains([]int{4, 5, 6}, tc.Row) {
style.FillColor = drawing.ColorFromHex("788fc7")
} else if slices.Contains([]int{7, 8}, tc.Row) {
style.FillColor = drawing.ColorFromHex("ced6d2")
} else if slices.Contains([]int{9}, tc.Row) {
style.FillColor = drawing.ColorFromHex("808080")
}
return style
},
})
if err != nil {
return err
}
buf, err := p.Bytes()
if err != nil {
return err
}
return WriteBytesToFile(buf, filePath)
}
func WriteBytesToFile(buf []byte, filename string) error {
err := os.WriteFile(filename, buf, 0600)
if err != nil {
return err
}
return nil
}
func CommonTable(headers []string, rows [][]string, tableWidth int, filePath string) error {
charts.SetDefaultWidth(tableWidth)
bgColor := drawing.ColorWhite
p, err := charts.TableOptionRender(charts.TableChartOption{
Header: headers,
BackgroundColor: bgColor,
HeaderBackgroundColor: drawing.ColorFromHex("808080"),
RowBackgroundColors: []charts.Color{
bgColor,
},
HeaderFontColor: drawing.ColorBlack,
FontColor: drawing.ColorBlack,
Padding: charts.Box{
Top: 15,
Right: 10,
Bottom: 15,
Left: 10,
},
Data: rows,
Font: Font,
TextAligns: []string{
charts.AlignCenter,
charts.AlignLeft,
},
CellStyle: func(tc charts.TableCell) *charts.Style {
style := &charts.Style{
Padding: charts.Box{
Bottom: 5,
},
}
if tc.Row%2 == 0 {
style.FillColor = drawing.ColorFromHex("f8f8f8")
} else {
style.FillColor = drawing.ColorWhite
}
return style
},
})
if err != nil {
return err
}
buf, err := p.Bytes()
if err != nil {
return err
}
return WriteBytesToFile(buf, filePath)
}
+145
View File
@@ -0,0 +1,145 @@
package statutils
import (
"stat/common/utils"
"stat/models/channelmod"
"strings"
)
type DeviceCategory struct {
Android bool
Ios bool
Light bool
H5 bool
H5Android bool
H5Ios bool
H5Pc bool
}
type SourceCategory struct {
Web bool // 是否官网
Normal bool // 是否自来
District bool // 是否渠道
Share bool // 是否分享
}
type OtherCategory struct {
Direct bool // 是否直推
Deduction bool // 是否扣量
Natural bool // 是否自然
Formal bool // H5正式用户
}
type ClientInfo struct {
//------设备------
DeviceCategory
//------来源------
SourceCategory
//------其他------
OtherCategory
}
func IsClientH5OrLight(devs ...string) bool {
for _, name := range devs {
dev := strings.ToLower(strings.TrimSpace(name))
if dev == string(DeviceNameLight) || dev == string(DeviceNameH5) || dev == string(DeviceNameH5Android) || dev == string(DeviceNameH5Ios) || dev == string(DeviceNameH5Pc) {
return true
}
}
return false
}
func IsClientH5(devs ...string) bool {
for _, name := range devs {
dev := strings.ToLower(strings.TrimSpace(name))
if dev == string(DeviceNameH5) || dev == string(DeviceNameH5Android) || dev == string(DeviceNameH5Ios) || dev == string(DeviceNameH5Pc) {
return true
}
}
return false
}
func IsClientPkg(devs ...string) bool {
for _, name := range devs {
dev := strings.ToLower(strings.TrimSpace(name))
if dev == string(DeviceNameIos) || dev == string(DeviceNameAndroid) {
return true
}
}
return false
}
func IsClientLight(devs ...string) bool {
for _, name := range devs {
dev := strings.ToLower(strings.TrimSpace(name))
if dev == string(DeviceNameLight) {
return true
}
}
return false
}
func IsClientAndroid(devs ...string) bool {
for _, name := range devs {
dev := strings.ToLower(strings.TrimSpace(name))
if dev == string(DeviceNameAndroid) {
return true
}
}
return false
}
func IsClientIos(devs ...string) bool {
for _, name := range devs {
dev := strings.ToLower(strings.TrimSpace(name))
if dev == string(DeviceNameIos) {
return true
}
}
return false
}
func GetDeviceInfo(dev1, dev2 string) DeviceCategory {
ret := DeviceCategory{}
ret.Android = utils.TowIsOR(dev1, dev2, string(DeviceNameAndroid))
if !ret.Android {
ret.Ios = utils.TowIsOR(dev1, dev2, string(DeviceNameIos))
if !ret.Ios {
ret.Light = utils.TowIsOR(dev1, dev2, string(DeviceNameLight))
if !ret.Light {
ret.H5 = true
ret.H5Android = utils.TowIsOR(dev1, dev2, string(DeviceNameH5Android))
if !ret.H5Android {
ret.H5Ios = utils.TowIsOR(dev1, dev2, string(DeviceNameH5Ios))
if !ret.H5Ios {
ret.H5Pc = true
}
}
}
}
}
return ret
}
func GetClientInfo(dev1, dev2 string, districtCode string, isDirect bool, isDeduction bool, isFormal bool) *ClientInfo {
ret := &ClientInfo{
DeviceCategory: GetDeviceInfo(dev1, dev2),
}
isOfficial := channelmod.IsOfficialChannelOrEmpty(districtCode)
if districtCode == "modelweb" {
ret.Web = true
} else if districtCode == "h5share" {
ret.Share = true
} else if isOfficial || !isDirect {
ret.Normal = true
} else {
ret.District = true
}
ret.Direct = isDirect
if isDirect && isOfficial {
ret.Natural = true
}
ret.Deduction = isDeduction
ret.Formal = isFormal
return ret
}
+81
View File
@@ -0,0 +1,81 @@
package statutils
import "testing"
func TestIsDeviceH5(t *testing.T) {
type args struct {
devs []string
}
tests := []struct {
name string
args args
want bool
}{
{
name: "a",
args: args{
devs: []string{"andro", "android", "h5_android"},
},
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := IsClientH5(tt.args.devs...); got != tt.want {
t.Errorf("IsClientH5() = %v, want %v", got, tt.want)
}
})
}
}
func TestIsDeviceAndroid(t *testing.T) {
type args struct {
devs []string
}
tests := []struct {
name string
args args
want bool
}{
{
name: "a",
args: args{
devs: []string{"andro", "android", "h5_android1"},
},
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := IsClientAndroid(tt.args.devs...); got != tt.want {
t.Errorf("IsClientH5() = %v, want %v", got, tt.want)
}
})
}
}
func TestIsDeviceIos(t *testing.T) {
type args struct {
devs []string
}
tests := []struct {
name string
args args
want bool
}{
{
name: "a",
args: args{
devs: []string{"ios", "android", "h5_android1"},
},
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := IsClientIos(tt.args.devs...); got != tt.want {
t.Errorf("IsClientH5() = %v, want %v", got, tt.want)
}
})
}
}
Binary file not shown.
+86
View File
@@ -0,0 +1,86 @@
package stdLog
import "fmt"
type CodeType int
type CodeMsg struct {
Code int `json:"code"`
Msg string `json:"msg"`
Tip string `json:"tip,omitempty"`
}
// 错误级别
const (
ParamLevel = 10000 // 参数级
StoreLevel = 20000 // 存储级
ServerLevel = 30000 // 服务级
BusinessLevel = 40000 // 业务逻辑级
)
// 通用code
const (
Success CodeType = 200 //Success 通用成功
Failure CodeType = 400 //Failure 通用失败
)
const (
ParamError CodeType = ParamLevel + iota // 10000 参数错误
NoToken // 10001 没有token
ParamDeductionRatioError // 10002 无效的扣量值
InvalidPhoneNumber // 10003 无效的手机号
QueryError // 10004 查询失败
)
const (
AdminAccountNotExists CodeType = ServerLevel + iota // 30000 管理员账号不存在
AdminPasswordError // 30001 管理员密码错误
AdminGetAuthorityFailed // 30002 获取权限失败
ChannelNotExists // 30003 渠道不存在
AgentAccountNotExists // 30004 代理商账号不存在
AgentPasswordError // 30005 代理商密码错误
AuthorityRoleExists // 30006 该权限角色已存在
AgentHasLocked // 30007 禁止登陆
VerifySmsCodeFailed // 30008 验证码错误
DisableWithdraw // 30009 禁止提现
WithdrawBalanceLess // 30010 提现金额不足
WithdrawFailed // 30011 提现失败
WithdrawHasDone // 30012 提现已经处理
WithdrawRequestFailed // 30013 提现第三方请求失败
WithdrawMobileError // 30014 手机号不一致
NotSupportWithdraw // 30015 不支持提现
AccountExists // 30016 账号已存在
AgentAccountBanned // 30017 账号已禁用
GoogleAuthFailed // 30018 谷歌验证失败
AdvertiseLocExists // 30019 广告位置已存在
AdvertiseInvalid // 30020 无效的广告
AppInfoError // 30021 应用信息错误
InvalidPrice // 30022 无效的金额
ChannelExists // 30023 渠道已存在
ActivityNoExistErr // 30024 活动不存在
LotteryNumReachedTheLimit // 30025 今日抽奖次数达到限制
LotteryCodeIsInvalid // 30026 兑换码无效
LotteryCodeIsUsed // 30026 兑换码已使用
SettlementDouble // 30028 重复结算
DownloadM3u8File // 30029 文件服务器访问失败
DownloadFileError // 30030 文件服务器访问失败
ErrApplicationNotExist // 应用不存在
RedisSet // 设置redis错误
AppDbError // app数据库错误
AppCategoryDbError // appcategory数据库错误
AppExistErr // app不能重复
ApplicationLocExists // 30036 广告位置已存在
)
const (
QueryDataError CodeType = BusinessLevel + iota // 数据查询失败
InsertDataError // 添加数据失败
UpdateDataError // 更新数据失败
DelDataError // 删除数据失败
DataNotExist // 数据不存在
DataEmpty
)
func (c CodeType) Error() string {
return fmt.Sprintf("code=%d,msg=%v,tip=%v", c, c.Msg(), c.Tip())
}
+44
View File
@@ -0,0 +1,44 @@
package stdLog
import (
"fmt"
)
// SrvErr 错误定义
type SrvErr struct {
Code CodeType `json:"code"`
Msg string `json:"msg"`
Cause string `json:"-"`
}
// New create an error
func New(code CodeType, msg string, cause string) *SrvErr {
return &SrvErr{
Code: code,
Msg: msg,
Cause: cause,
}
}
func (s *SrvErr) Error() string {
if s == nil {
return ""
}
return fmt.Sprintf("code = %d, msg = %s ,case = %v", s.Code, s.Msg, s.Cause)
}
func (c CodeType) Msg() string {
msg, ok := codeAndMsg[c]
if ok {
return msg
}
return ""
}
func (c CodeType) Tip() string {
tip, ok := codeAndTip[c]
if ok {
return tip
}
return ""
}
+70
View File
@@ -0,0 +1,70 @@
package stdLog
import (
"errors"
"net/http"
"os"
"reflect"
"stat/common/log"
"stat/common/utils"
"time"
"github.com/gin-gonic/gin"
)
//ServeJSON 返回数据并处理多语言
func ServerJSON(c *gin.Context, code CodeType, data interface{}) {
if code != Success { //发生错误,记录日志
var version, sysType, devType string
admin, _ := utils.GetAdmin(c)
log.Warn("Error:",
log.Any("admin", admin),
log.Any("IP", c.ClientIP()),
log.Any("Version", version),
log.Any("SysType", sysType),
log.Any("DevType", devType),
log.Any("Router", c.Request.RequestURI),
log.Any("PID", os.Getpid()),
log.Any("Error", data),
log.Any("Code", code))
c.JSON(http.StatusOK, gin.H{
"code": code,
"msg": code.Msg(),
"tip": code.Tip(),
"data": data,
"time": time.Now().UTC().Format("2006-01-02T15:04:05.000Z"),
})
} else {
if data != nil {
t := reflect.TypeOf(data)
if !(t.Kind() == reflect.Map || t.Kind() == reflect.Struct) {
//log.Warn("[===TypeError===] Return Data Type error is not struct", log.Any("path", c.Request.URL.Path))
}
}
// 返回数据加密
c.JSON(http.StatusOK, gin.H{
"code": code,
"msg": code.Msg(),
"tip": code.Tip(),
"data": data,
"time": time.Now().UTC().Format("2006-01-02T15:04:05.000Z"),
})
}
}
//GetUID 获取用户uid
//oauth.Auth执行后uid有效
func GetUID(ctx *gin.Context) (uid uint64, err error) {
val, exists := ctx.Get("USER_ID")
if !exists {
err = errors.New("userId not exist")
return
}
uid, ok := val.(uint64)
if !ok {
return 0, errors.New("userId type error")
}
return
}
+41
View File
@@ -0,0 +1,41 @@
package stdLog
var codeAndMsg = map[CodeType]string{
Success: "success",
Failure: "Failed",
ParamError: "Param error",
AdminAccountNotExists: "Account is not exists",
AdminPasswordError: "Password error",
AdminGetAuthorityFailed: "Get authority failed",
NoToken: "no token",
ParamDeductionRatioError: "invalid deduction ratio",
ChannelNotExists: "channel not exists",
AgentAccountNotExists: "Account is not exists",
AgentPasswordError: "Password error",
AuthorityRoleExists: "authority role is existed",
AgentHasLocked: "agent has been locked",
DisableWithdraw: "trader disabled",
NotSupportWithdraw: "not support withdraw",
AccountExists: "account is exists",
InvalidPhoneNumber: "Invalid phone number",
AgentAccountBanned: "account is banned",
GoogleAuthFailed: "google two step auth failed",
AdvertiseLocExists: "advertise location exists",
WithdrawBalanceLess: "balance not enough",
WithdrawMobileError: "mobile is different",
VerifySmsCodeFailed: "sms code is not correct",
WithdrawFailed: "withdraw failed",
AdvertiseInvalid: "invalid advertisement",
AppInfoError: "application info error",
InvalidPrice: "invliad price",
ChannelExists: "channel exists",
QueryDataError: "query data error",
UpdateDataError: "update data error",
DelDataError: "delete data error",
DataNotExist: "data does not exist",
ActivityNoExistErr: "activity does not exist",
LotteryNumReachedTheLimit: "lottery num reached the limit",
LotteryCodeIsInvalid: "lottery code is invalid",
LotteryCodeIsUsed: "lottery code is used",
ApplicationLocExists: "application location exists",
}
+41
View File
@@ -0,0 +1,41 @@
package stdLog
var codeAndTip = map[CodeType]string{
Success: "成功",
Failure: "失败",
ParamError: "参数错误",
AdminAccountNotExists: "该账号不存在",
AdminPasswordError: "密码错误",
AdminGetAuthorityFailed: "获取权限失败",
NoToken: "没有token",
ParamDeductionRatioError: "无效的扣量值",
ChannelNotExists: "渠道不存在",
AgentAccountNotExists: "该账号不存在",
AgentPasswordError: "密码错误",
AuthorityRoleExists: "该权限角色已存在",
AgentHasLocked: "禁止登陆",
DisableWithdraw: "禁止交易",
NotSupportWithdraw: "不支持提现",
AccountExists: "账号已存在",
InvalidPhoneNumber: "手机号码无效",
AgentAccountBanned: "该账号已禁用",
GoogleAuthFailed: "两步验证失败",
AdvertiseLocExists: "该广告位置已经存在,请重新输入位置id",
WithdrawBalanceLess: "余额不足,请确认!",
WithdrawMobileError: "手机号码不一致,请确认!",
VerifySmsCodeFailed: "验证码不正确,请确认!",
WithdrawFailed: "提现失败!",
AdvertiseInvalid: "无效的广告",
AppInfoError: "应用信息不正确",
InvalidPrice: "无效的金额",
ChannelExists: "渠道已存在",
QueryDataError: "数据查询失败",
UpdateDataError: "更新数据失败",
DelDataError: "删除数据失败",
DataNotExist: "数据不存在",
ActivityNoExistErr: "活动不存在",
LotteryNumReachedTheLimit: "今日抽奖次数达到限制",
LotteryCodeIsInvalid: "兑换码无效",
LotteryCodeIsUsed: "兑换码已使用",
ApplicationLocExists: "该广告位置已经存在,请重新输入位置id",
}
+148
View File
@@ -0,0 +1,148 @@
package tf
import (
"fmt"
"net/http"
"stat/common/log"
"strings"
"github.com/PuerkitoBio/goquery"
)
type Result struct {
Valid bool
Desc string
AppName string
AppIcon string
}
// address: https://testflight.apple.com/join/whsnQnSb
// https://testflight.apple.com/join/M3cBOOzR
func CheckTFValid(address string) (*Result, error) {
var checkResult Result
req, err := http.NewRequest("GET", address, nil)
if err != nil {
log.Warn("CheckTFValid newRequest", log.Any("address", address), log.E(err))
checkResult.Desc = err.Error()
return nil, err
}
req.Header.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9")
req.Header.Add("Accept-Language", "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7,zh-TW;q=0.6")
req.Header.Add("Cache-Control", "no-cache")
req.Header.Add("Connection", "keep-alive")
req.Header.Add("Pragma", "no-cache")
req.Header.Add("Sec-Fetch-Dest", "document")
req.Header.Add("Sec-Fetch-Mode", "navigate")
req.Header.Add("Sec-Fetch-Site", "none")
req.Header.Add("Sec-Fetch-User", "?1")
req.Header.Add("Upgrade-Insecure-Requests", "1")
req.Header.Add("User-Agent", "Mozilla/5.0 (iPhone; CPU iPhone OS 13_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.5 Mobile/15E148 Safari/604.1")
res, err := http.DefaultClient.Do(req)
if err != nil {
log.Warn("CheckTFValid Client Do", log.Any("address", address), log.E(err))
return nil, err
}
defer func() { _ = res.Body.Close() }()
//不为200 肯定出问题了
if res.StatusCode != http.StatusOK {
checkResult.Desc = fmt.Sprintf("HttpStatus:%d", res.StatusCode)
return &checkResult, nil
}
//状态为200,解析内容
doc, err := goquery.NewDocumentFromReader(res.Body)
if err != nil {
log.Warn("CheckTFValid NewDocumentFromReader err", log.Any("address", address), log.E(err))
return &checkResult, err
}
valid, desc := checkValid(doc)
if !valid {
checkResult.Desc = desc
return &checkResult, nil
}
appName := getAppName(doc)
appIcon := getAppIcon(doc)
checkResult.Valid = true
checkResult.AppName = appName
checkResult.AppIcon = appIcon
return &checkResult, nil
}
func checkValid(doc *goquery.Document) (bool, string) {
foundStartTesting := false
doc.Find("a").EachWithBreak(func(i int, s *goquery.Selection) bool {
text := strings.TrimSpace(s.Text())
if text == "Start Testing" || text == "开始测试" {
foundStartTesting = true
return false
}
return true
})
if foundStartTesting {
return true, "" //TF包有效
}
betaStatus := ""
doc.Find(".beta-status").EachWithBreak(func(i int, s *goquery.Selection) bool {
betaStatus = strings.TrimSpace(s.Text())
return false
})
if betaStatus == "" {
return true, ""
}
if strings.Contains(betaStatus, "install TestFlight") || strings.Contains(betaStatus, "安装 TestFlight") {
return true, ""
}
//TODO: 这里还需要测试,目前来看,出现betaStatus就是无效的
return false, betaStatus
}
func getAppName(doc *goquery.Document) string {
var appName string
doc.Find("title").EachWithBreak(func(i int, selection *goquery.Selection) bool {
//<title>加入 Beta 版“原创短视频” - TestFlight - Apple</title>
//<title>Join the READ-O beta - TestFlight - Apple</title>
v := strings.TrimSpace(selection.Text())
rawLen := len(v)
v = strings.TrimPrefix(v, "加入 Beta 版")
v = strings.TrimPrefix(v, "Join the")
v = strings.TrimSuffix(v, "beta - TestFlight - Apple")
v = strings.TrimSuffix(v, "- TestFlight - Apple")
v = strings.TrimSpace(v)
v = strings.TrimLeft(v, "“")
v = strings.TrimRight(v, "”")
currLen := len(v)
if rawLen == currLen {
return true
}
appName = v
return false
})
return appName
}
func getAppIcon(doc *goquery.Document) string {
var iconUrl string
doc.Find(".app-icon.callout.ios").EachWithBreak(func(i int, selection *goquery.Selection) bool {
nodes := selection.Nodes
if len(nodes) == 0 {
return true
}
attrs := selection.Nodes[0].Attr
for _, attr := range attrs {
if attr.Key == "style" {
href := attr.Val
href = strings.TrimPrefix(href, "background-image: url(")
href = strings.TrimSuffix(href, ");")
if len(href) != len(attr.Val) {
iconUrl = href
return false
}
}
}
return true
})
return iconUrl
}
+20
View File
@@ -0,0 +1,20 @@
package tf
import (
"fmt"
"testing"
)
func TestCheckTFValid(t *testing.T) {
tfs := []string{
"https://testflight.apple.com/join/70PGk4lI",
//"https://testflight.apple.com/join/M3cBOOzR",
}
check := func(url string) {
result, err := CheckTFValid(url)
fmt.Println(url, result, err)
}
for i := 0; i < len(tfs); i++ {
check(tfs[i])
}
}
+441
View File
@@ -0,0 +1,441 @@
package tgbot
import (
"context"
"errors"
"fmt"
"go.uber.org/zap"
"stat/common/log"
"stat/common/utils"
"strings"
"sync"
"time"
"golang.org/x/exp/slices"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
)
type TgChats struct {
BroadcastChats []int64 // 广播群
CommandChats []int64 // 命令群
MonitorChats []int64 // 监控群
DownloadChats []int64 // 下载监控群
DomainChats []int64 // 域名监控群
DevelopChats []int64 // 开发调试群
PrivateUsers []int64 // 私聊用户列表
BlocStats []int64 // 集团统计群
Advertise []int64 // 广告监控群
ChannelApk []int64 // 渠道包生成
Project []int64 // 项目的消息
AdvsSend []int64 //广告群
Rchssend []int64 //充值群
}
type FileType int32
const (
FileTypeNone FileType = 0
FileTypePhoto FileType = 1
FileTypeDocument FileType = 2
FileTypeMusic FileType = 3
FileTypeVideo FileType = 4
)
type TgBot struct {
bot *tgbotapi.BotAPI
commands *TgCommands
chats *TgChats
messages *TgMessageList
shutdown chan interface{}
allow map[int64][]string
executing sync.Map
}
func NewTgBot(token string, chats *TgChats, allow map[int64][]string) (*TgBot, error) {
ret := &TgBot{
chats: chats,
commands: &TgCommands{
commands: make(map[string]*Command),
},
messages: &TgMessageList{
list: make([]tgbotapi.Chattable, 0),
},
shutdown: make(chan interface{}),
allow: allow,
}
if allow == nil {
ret.allow = make(map[int64][]string)
}
if chats == nil {
return nil, errors.New("chats can not be nil")
}
bot, err := tgbotapi.NewBotAPI(token)
if err != nil {
return nil, err
}
ret.bot = bot
ret.processMessages()
go ret.start()
return ret, nil
}
func (s *TgBot) RegisterCommand(cmds ...*Command) {
for _, cmd := range cmds {
s.commands.SetCommand(cmd)
}
}
func (s *TgBot) start() {
defer utils.RecoverError(func(err error) {
fmt.Printf("TgBot Start panic:%v", err)
})
ctx := context.Background()
u := tgbotapi.NewUpdate(0)
u.Timeout = 1
updates := s.bot.GetUpdatesChan(u)
for update := range updates {
if update.Message != nil {
if update.Message.IsCommand() {
go func() {
defer utils.RecoverError(func(err error) {
fmt.Printf("TgBot processCommand panic:%v", err)
})
if err := s.processCommand(ctx, update.Message); err != nil {
var uErr *UserError
if errors.As(err, &uErr) {
uErr.SendBack()
} else {
s.SendDevelopMessage(err.Error())
}
}
}()
}
} else if update.CallbackQuery != nil {
go func() {
defer utils.RecoverError(func(err error) {
fmt.Printf("TgBot processCallback panic:%v", err)
})
if err := s.processCallback(ctx, update.CallbackQuery); err != nil {
var uErr *UserError
if errors.As(err, &uErr) {
uErr.SendBack()
} else {
s.SendDevelopMessage(err.Error())
}
}
}()
}
}
close(s.shutdown)
}
func (s *TgBot) Stop() {
s.bot.StopReceivingUpdates()
close(s.shutdown)
}
func (s *TgBot) processMessages() {
go func() {
defer utils.RecoverError(func(err error) {
log.Error("TgBot processMessages panic", zap.Error(err))
})
for {
select {
case <-s.shutdown:
return
default:
}
if msg := s.messages.Pop(); msg != nil {
if _, err1 := s.bot.Send(msg); err1 != nil {
log.Error("TgBot Send error", zap.Error(err1))
}
}
time.Sleep(time.Second)
}
}()
}
func (s *TgBot) SendBroadcastMessage(text string, filePath string, fileType FileType) error {
if s == nil {
return errors.New("bot can not be nil")
}
msgs := getMessageList(text, s.chats.BroadcastChats, filePath, fileType)
for _, msg := range msgs {
s.messages.Push(msg)
}
return nil
}
func (s *TgBot) SendBroadcastEntity(ent *EntityList) error {
if s == nil {
return errors.New("bot can not be nil")
}
if ent == nil {
return errors.New("entity can not be nil")
}
for _, chat := range s.chats.BroadcastChats {
entities, text := ent.ToEntities()
msg := tgbotapi.NewMessage(chat, text)
msg.Entities = entities
s.messages.Push(msg)
}
return nil
}
func (s *TgBot) SendBroadcastMessageToChats(text string, filePath string, fileType FileType, chats []int64) error {
if s == nil {
return errors.New("bot can not be nil")
}
msgs := getMessageList(text, chats, filePath, fileType)
for _, msg := range msgs {
s.messages.Push(msg)
}
return nil
}
func (s *TgBot) SendMonitorMessage(text string) error {
if s == nil {
return errors.New("bot can not be nil")
}
msgs := getMessageList(text, s.chats.MonitorChats, "", FileTypeNone)
for _, msg := range msgs {
s.messages.Push(msg)
}
return nil
}
func (s *TgBot) SendChannelApkMessage(text string) {
if s == nil {
return
}
msgs := getMessageList(text, s.chats.ChannelApk, "", FileTypeNone)
for _, msg := range msgs {
s.messages.Push(msg)
}
return
}
func (s *TgBot) SendDevelopMessage(text string) {
if s == nil {
return
}
msgs := getMessageList(text, s.chats.DevelopChats, "", FileTypeNone)
for _, msg := range msgs {
s.messages.Push(msg)
}
return
}
func (s *TgBot) SendBlocStatMessage(text string) {
if s == nil {
return
}
msgs := getMessageList(text, s.chats.BlocStats, "", FileTypeNone)
for _, msg := range msgs {
s.messages.Push(msg)
}
return
}
func (s *TgBot) SendDownloadMessage(text string) error {
if s == nil {
return errors.New("bot can not be nil")
}
msgs := getMessageList(text, s.chats.DownloadChats, "", FileTypeNone)
for _, msg := range msgs {
s.messages.Push(msg)
}
return nil
}
func (s *TgBot) SendDomainMessage(text string) error {
if s == nil {
return errors.New("bot can not be nil")
}
msgs := getMessageList(text, s.chats.DomainChats, "", FileTypeNone)
for _, msg := range msgs {
s.messages.Push(msg)
}
return nil
}
func (s *TgBot) SendAdvertiseMessage(text string) error {
if s == nil {
return errors.New("bot can not be nil")
}
msgs := getMessageList(text, s.chats.Advertise, "", FileTypeNone)
for _, msg := range msgs {
s.messages.Push(msg)
}
return nil
}
func (s *TgBot) SendProjectMessage(text string) error {
if s == nil {
return errors.New("bot can not be nil")
}
msgs := getMessageList(text, s.chats.Project, "", FileTypeNone)
for _, msg := range msgs {
s.messages.Push(msg)
}
return nil
}
func (s *TgBot) isCmdAuthenticated(cmd *Command) bool {
chatAllowCmds := s.allow[cmd.message.Chat.ID]
if slices.Contains(s.chats.CommandChats, cmd.message.Chat.ID) || // 在命令群 1
slices.Contains(s.chats.Rchssend, cmd.message.Chat.ID) || // 在命令群 2
(cmd.message.Chat.IsPrivate() && slices.Contains(s.chats.PrivateUsers, cmd.message.From.ID)) || // 允许的私聊用户ID
slices.Contains(chatAllowCmds, cmd.Cmd) || // 群已指定允许的命令
cmd.Cmd == "what" { // 公共命令,显示群信息
return true
} else {
return false
}
}
func (s *TgBot) processCommand(ctx context.Context, message *tgbotapi.Message) error {
cmd, err := s.parseCommand(message, CommandTypeCommand)
if err != nil {
return UnknownCommandError(message)
}
if cmd.skip { // not to this bot
return nil
}
if s.isCmdAuthenticated(cmd) { // 群已指定允许的命令
return s.handleCommand(ctx, cmd)
} else {
return AuthError(message)
}
}
func (s *TgBot) processCallback(ctx context.Context, callback *tgbotapi.CallbackQuery) error {
message := callback.Message
message.From = callback.From
message.Text = callback.Data
cmd, err := s.parseCommand(message, CommandTypeCallback)
if err != nil {
return UnknownCommandError(message)
}
if cmd.skip { // not to this bot
return nil
}
if s.isCmdAuthenticated(cmd) {
return s.handleCommand(ctx, cmd)
} else {
return AuthError(message)
}
}
func (s *TgBot) handleCommand(ctx context.Context, cmd *Command) (ret error) {
defer utils.RecoverError(func(err error) {
ret = CommandPanic(cmd, err)
})
userID := cmd.message.From.ID
if _, loaded := s.executing.LoadOrStore(userID, cmd.Cmd); loaded {
return NewUserError(cmd, "你上次的命令尚未执行完成,请稍后再重试。。。")
}
defer s.executing.Delete(userID)
if cmd.Process != nil {
if err := cmd.Process(ctx, cmd); err != nil {
var uErr *UserError
if errors.As(err, &uErr) {
return uErr.SendBack()
} else {
return CommandError(cmd, err)
}
}
} else {
ret = UnknownCommandError(cmd.message)
return
}
return
}
func (s *TgBot) parseCommand(msg *tgbotapi.Message, cmdType CommandType) (*Command, error) {
ret := Command{owner: s, message: msg}
quoted := false
fields := strings.FieldsFunc(msg.Text, func(r rune) bool {
if r == '"' {
quoted = !quoted
}
return !quoted && r == ' '
})
args := make([]string, 0)
for _, field := range fields {
_field := strings.TrimSpace(field)
if strings.HasPrefix(field, "@") {
ret.botNames = append(ret.botNames, strings.Replace(_field, "@", "", 1))
} else {
args = append(args, _field)
}
}
if len(args) > 0 {
if strings.HasPrefix(args[0], "/") {
ret.Cmd = strings.Replace(args[0], "/", "", 1)
args = DeleteSliceElem(args, args[0])
}
}
ret.skip = !(slices.Contains(ret.botNames, s.bot.Self.UserName) || len(ret.botNames) == 0)
ret.Params = args
item := s.commands.GetCommand(ret.Cmd)
if item != nil {
ret.Desc = item.Desc
ret.Help = item.Help
ret.Process = item.Process
ret.CmdType = cmdType
return &ret, nil
} else {
return nil, errors.New("unknown tg command")
}
}
func (s *TgBot) Sendrchstesttochat(text string, filePath string, fileType FileType) error {
if s == nil {
return errors.New("bot can not be nil")
}
msgs := getMessageList(text, s.chats.AdvsSend, filePath, fileType)
for _, msg := range msgs {
s.messages.Push(msg)
}
return nil
}
func (s *TgBot) Sendrchstochat(text string, filePath string, fileType FileType) error {
if s == nil {
return errors.New("bot can not be nil")
}
msgs := getMessageList(text, s.chats.Rchssend, filePath, fileType)
for _, msg := range msgs {
s.messages.Push(msg)
}
return nil
}
func (s *TgBot) SendAdvsSendMessage(text string) {
if s == nil {
return
}
msgs := getMessageList(text, s.chats.AdvsSend, "", FileTypeNone)
for _, msg := range msgs {
s.messages.Push(msg)
}
return
}
func (s *TgBot) SendrchssendMessage(text string) {
if s == nil {
return
}
msgs := getMessageList(text, s.chats.Rchssend, "", FileTypeNone)
for _, msg := range msgs {
s.messages.Push(msg)
}
return
}
+254
View File
@@ -0,0 +1,254 @@
package tgbot
import (
"context"
"errors"
"strconv"
"strings"
"sync"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
)
type CommandType int32
const (
CommandTypeUnknown CommandType = 0
CommandTypeCommand CommandType = 1
CommandTypeCallback CommandType = 2
)
type TgMessageList struct {
lock sync.Mutex
list []tgbotapi.Chattable
}
func (s *TgMessageList) Push(msg tgbotapi.Chattable) {
s.lock.Lock()
defer s.lock.Unlock()
if s.list == nil {
s.list = make([]tgbotapi.Chattable, 0)
}
s.list = append(s.list, msg)
}
func (s *TgMessageList) Pop() tgbotapi.Chattable {
s.lock.Lock()
defer s.lock.Unlock()
var ret tgbotapi.Chattable
if len(s.list) > 0 {
ret = s.list[0]
s.list = s.list[1:]
}
return ret
}
type TgCommands struct {
commands map[string]*Command
lock sync.Mutex
}
func (s *TgCommands) GetCommand(cmd string) *Command {
s.lock.Lock()
defer s.lock.Unlock()
return s.commands[cmd]
}
func (s *TgCommands) SetCommand(cmd *Command) {
s.lock.Lock()
defer s.lock.Unlock()
s.commands[cmd.Cmd] = cmd
}
type CommandFunc func(ctx context.Context, cmd *Command) error
type Command struct {
Cmd string // 命令
Help string // 命令帮助
Desc string // 命令描述
CmdType CommandType // 命令类型
Process CommandFunc // 处理函数
Params []string // 参数
botNames []string // @
skip bool // 是否跳过处理
owner *TgBot
ParseMode string
PhotoPath string
FilePath string
message *tgbotapi.Message
}
func (c *Command) Message() *tgbotapi.Message {
return c.message
}
func (c *Command) HasFlag(flag string) bool {
for _, arg := range c.Params {
if strings.Contains(arg, "=") {
ss := strings.Split(arg, "=")
if len(ss) > 0 {
if ss[0] == flag {
return true
}
}
} else {
if arg == flag {
return true
}
}
}
return false
}
func (c *Command) GetFlagStringValue(flag string) string {
for index, arg := range c.Params {
pos := strings.Index(arg, "=")
if pos > 0 {
cmd := arg[:pos]
if cmd == flag {
content := arg[pos+1:]
if strings.HasPrefix(content, `"`) && strings.HasSuffix(content, `"`) {
if text, err := strconv.Unquote(content); err != nil {
return content
} else {
return text
}
} else {
return content
}
}
} else {
if arg == flag {
if index+1 < len(c.Params) {
return c.Params[index+1]
}
}
}
}
return ""
}
func (c *Command) GetFlagInt64Value(flag string) (int64, error) {
s := c.GetFlagStringValue(flag)
if s != "" {
return strconv.ParseInt(s, 10, 64)
}
return 0, errors.New("flag value is empty")
}
func (c *Command) GetFlagInt32Value(flag string) (int32, error) {
s := c.GetFlagStringValue(flag)
if s != "" {
i, err := strconv.ParseInt(s, 10, 64)
return int32(i), err
}
return 0, errors.New("flag value is empty")
}
func (c *Command) GetFlagStringSlice(flag string) []string {
s := c.GetFlagStringValue(flag)
return strings.Split(s, ",")
}
func (c *Command) GetFlagInt64Slice(flag string) []int64 {
ret := make([]int64, 0)
s := c.GetFlagStringValue(flag)
ss := strings.Split(s, ",")
for _, v := range ss {
if i, err := strconv.ParseInt(v, 10, 64); err == nil {
ret = append(ret, i)
}
}
return ret
}
func (c *Command) GetFlagInt32Slice(flag string) []int32 {
ret := make([]int32, 0)
s := c.GetFlagStringValue(flag)
ss := strings.Split(s, ",")
for _, v := range ss {
if i, err := strconv.ParseInt(v, 10, 64); err == nil {
ret = append(ret, int32(i))
}
}
return ret
}
func (c *Command) SendBack(text string) error {
if c.message != nil {
if c.CmdType == CommandTypeCallback {
if c.PhotoPath != "" || c.FilePath != "" {
return c.SendReply(text, nil)
} else {
return c.SendEdit(text, nil)
}
} else if c.CmdType == CommandTypeCommand {
return c.SendReply(text, nil)
} else {
return c.SendText(text)
}
} else {
return errors.New("tg message is nil")
}
}
func (c *Command) SendEntity(ent *EntityList) error {
if c == nil {
return errors.New("bot cmd can not be nil")
}
if ent == nil {
return errors.New("entity can not be nil")
}
if c.message == nil {
return errors.New("tg message is nil")
}
chat := c.message.Chat.ID
entities, text := ent.ToEntities()
msg := tgbotapi.NewMessage(chat, text)
msg.Entities = entities
c.owner.messages.Push(msg)
return nil
}
func (c *Command) SendReply(text string, replyMarkup interface{}) error {
if c.message != nil {
return replyMessage(c.owner, c.message, text, replyMarkup, c.ParseMode, c.PhotoPath, c.FilePath)
} else {
return errors.New("tg message is nil")
}
}
func (c *Command) SendEdit(text string, replyMarkup interface{}) error {
if c.message != nil {
return editMessage(c.owner, c.message, text, replyMarkup, c.ParseMode)
} else {
return errors.New("tg message is nil")
}
}
func (c *Command) SendText(text string) error {
if c.message != nil {
return sendMessageToChat(c.owner, text, c.message.Chat.ID, c.ParseMode, c.PhotoPath, c.FilePath)
} else {
return errors.New("tg message is nil")
}
}
func (c *Command) SendFile(text string, fileName string) error {
if c.message != nil {
c.FilePath = fileName
return sendMessageToChat(c.owner, text, c.message.Chat.ID, c.ParseMode, c.PhotoPath, c.FilePath)
} else {
return errors.New("tg message is nil")
}
}
func (c *Command) SendDelete() error {
if c.message != nil {
msg := tgbotapi.NewDeleteMessage(c.message.Chat.ID, c.message.MessageID)
_, err := c.owner.bot.Request(msg)
return err
} else {
return errors.New("tg message is nil")
}
}
+109
View File
@@ -0,0 +1,109 @@
package tgbot
import (
"fmt"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
"strings"
"unicode/utf16"
)
type Entity struct {
Title string
Content string
}
func (p *Entity) GetTitle() string {
return fmt.Sprintf("%s", p.Title)
}
func (p *Entity) Entity(startOffset int) ([]tgbotapi.MessageEntity, string) {
ret := make([]tgbotapi.MessageEntity, 0)
ret = append(ret, tgbotapi.MessageEntity{
Type: "expandable_blockquote",
Offset: startOffset,
Length: p.TextU16Len(),
})
if p.TitleU16Len() > 0 {
ret = append(ret, tgbotapi.MessageEntity{
Type: "bold",
Offset: startOffset,
Length: p.TitleU16Len(),
})
}
if p.ContentU16Len() > 0 {
ret = append(ret, tgbotapi.MessageEntity{
Type: "bold",
Offset: startOffset + p.TitleU16Len() + 1,
Length: p.ContentU16Len(),
})
}
if p.TitleU16Len() > 0 {
return ret, p.GetTitle() + "\n" + p.Content
}
return ret, p.Content
}
func (p *Entity) TextU16Len() int {
text := p.GetTitle()
if text != "" {
text += "\n"
}
text += p.Content
return len(utf16.Encode([]rune(text)))
}
func (p *Entity) TitleU16Len() int {
return len(utf16.Encode([]rune(p.GetTitle())))
}
func (p *Entity) ContentU16Len() int {
return len(utf16.Encode([]rune(p.Content)))
}
type EntityList struct {
Title string
Desc string
Entities []*Entity
}
func NewEntityList(title, desc string, entities []*Entity) *EntityList {
return &EntityList{
Title: strings.TrimSpace(title),
Desc: strings.TrimSpace(desc),
Entities: entities,
}
}
func (p *EntityList) ToEntities() ([]tgbotapi.MessageEntity, string) {
ret := make([]tgbotapi.MessageEntity, 0)
var text string
var startOffset int
// 添加总标题
titleU16Len := len(utf16.Encode([]rune(p.Title)))
ret = append(ret, tgbotapi.MessageEntity{
Type: "bold",
Offset: 0,
Length: titleU16Len,
})
text += p.Title + "\n"
startOffset += titleU16Len + 1
// 添加总描述
if p.Desc != "" {
descU16Len := len(utf16.Encode([]rune(p.Desc)))
ret = append(ret, tgbotapi.MessageEntity{
Type: "bold",
Offset: startOffset,
Length: descU16Len,
})
text += p.Desc + "\n"
startOffset += descU16Len + 1
}
// 添加每个Prompt
for _, ent := range p.Entities {
promptEntities, promptText := ent.Entity(startOffset)
ret = append(ret, promptEntities...)
text += promptText + "\n"
startOffset += ent.TextU16Len() + 1
}
return ret, text
}
+50
View File
@@ -0,0 +1,50 @@
package tgbot
import (
"fmt"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
)
type UserError struct {
msg string
cmd *Command
}
func NewUserError(cmd *Command, text string) error {
ret := &UserError{
msg: text,
cmd: cmd,
}
return ret
}
func (e *UserError) Error() string {
return e.msg
}
func (e *UserError) SendBack() error {
if e.cmd.CmdType == CommandTypeCallback {
return e.cmd.SendEdit(e.msg, nil)
} else if e.cmd.CmdType == CommandTypeCommand {
return e.cmd.SendReply(e.msg, nil)
} else {
return e.cmd.SendText(e.msg)
}
}
func CommandError(cmd *Command, err error) error {
return fmt.Errorf("命令失败,来自【%s:%d】%s,命令【%s】错误:\n%s", cmd.message.Chat.Title, cmd.message.Chat.ID, UserDisplay(cmd.message), cmd.Cmd, err.Error())
}
func CommandPanic(cmd *Command, err error) error {
return fmt.Errorf("命令异常,来自【%s:%d】%s,命令【%s】错误:\n%s", cmd.message.Chat.Title, cmd.message.Chat.ID, UserDisplay(cmd.message), cmd.Cmd, err.Error())
}
func AuthError(message *tgbotapi.Message) error {
return fmt.Errorf("非官方群,来自【%s:%d】%s, 内容:\n%s", message.Chat.Title, message.Chat.ID, UserDisplay(message), message.Text)
}
func UnknownCommandError(message *tgbotapi.Message) error {
return fmt.Errorf("未知命令,来自【%s:%d】%s, 内容:\n%s", message.Chat.Title, message.Chat.ID, UserDisplay(message), message.Text)
}
+155
View File
@@ -0,0 +1,155 @@
package tgbot
import (
"errors"
"strings"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
)
func sendMessageToChat(bot *TgBot, text string, chat int64, parseMode string, photoPath string, filePath string) error {
blocks := getTextBlocks(text)
if len(blocks) == 0 {
return errors.New("发送的文本为空")
}
var msg tgbotapi.Chattable
for _, block := range blocks {
if photoPath != "" {
tmp := tgbotapi.NewPhoto(chat, tgbotapi.FilePath(photoPath))
tmp.Caption = text
tmp.ParseMode = parseMode
msg = tmp
} else if filePath != "" {
tmp := tgbotapi.NewDocument(chat, tgbotapi.FilePath(filePath))
tmp.Caption = text
tmp.ParseMode = parseMode
msg = tmp
} else {
tmp := tgbotapi.NewMessage(chat, block)
tmp.ParseMode = parseMode
msg = tmp
}
bot.messages.Push(msg)
}
return nil
}
func replyMessage(bot *TgBot, message *tgbotapi.Message, text string, replyMarkup interface{}, parseMode string, photoPath string, filePath string) error {
blocks := getTextBlocks(text)
if len(blocks) == 0 {
return errors.New("发送的文本为空")
}
var msg tgbotapi.Chattable
for _, block := range blocks {
if photoPath != "" {
tmp := tgbotapi.NewPhoto(message.Chat.ID, tgbotapi.FilePath(photoPath))
tmp.Caption = text
tmp.ParseMode = parseMode
tmp.ReplyMarkup = replyMarkup
tmp.ReplyToMessageID = message.MessageID
msg = tmp
} else if filePath != "" {
tmp := tgbotapi.NewDocument(message.Chat.ID, tgbotapi.FilePath(filePath))
tmp.Caption = text
tmp.ParseMode = parseMode
tmp.ReplyMarkup = replyMarkup
tmp.ReplyToMessageID = message.MessageID
msg = tmp
} else {
tmp := tgbotapi.NewMessage(message.Chat.ID, block)
tmp.ParseMode = parseMode
tmp.ReplyMarkup = replyMarkup
tmp.ReplyToMessageID = message.MessageID
msg = tmp
}
bot.messages.Push(msg)
}
return nil
}
func editMessage(bot *TgBot, message *tgbotapi.Message, text string, replyMarkup interface{}, parseMode string) error {
blocks := getTextBlocks(text)
if len(blocks) == 0 {
return errors.New("发送的文本为空")
}
var msg tgbotapi.Chattable
for index, block := range blocks {
if index == 0 {
tmp := tgbotapi.NewEditMessageText(message.Chat.ID, message.MessageID, block)
tmp.ParseMode = parseMode
msg = tmp
if markup, ok := replyMarkup.(tgbotapi.InlineKeyboardMarkup); ok {
tmp = tgbotapi.NewEditMessageTextAndMarkup(message.Chat.ID, message.MessageID, block, markup)
tmp.ParseMode = parseMode
msg = tmp
}
} else {
tmp := tgbotapi.NewMessage(message.Chat.ID, block)
tmp.ParseMode = parseMode
msg = tmp
}
bot.messages.Push(msg)
}
return nil
}
func getMessageList(text string, chats []int64, filePath string, fileType FileType) []tgbotapi.Chattable {
msgList := make([]tgbotapi.Chattable, 0)
blocks := getTextBlocks(text)
chatfile := filePath
for _, chat := range chats {
filePath = chatfile
for _, block := range blocks {
if filePath != "" {
if fileType == FileTypePhoto {
msg := tgbotapi.NewPhoto(chat, tgbotapi.FilePath(filePath))
msg.Caption = block
msgList = append(msgList, msg)
} else if fileType == FileTypeDocument {
if chat == -1002009498163 {
if text != "天宇公司综合日报" {
continue
}
}
msg := tgbotapi.NewDocument(chat, tgbotapi.FilePath(filePath))
msg.Caption = block
msgList = append(msgList, msg)
} else {
msgList = append(msgList, tgbotapi.NewMessage(chat, block))
}
filePath = ""
} else {
if fileType == FileTypeDocument {
continue
}
msgList = append(msgList, tgbotapi.NewMessage(chat, block))
}
}
}
return msgList
}
func getTextBlocks(content string) []string {
blocks := make([]string, 0)
lines := strings.Split(content, "\n")
text := ""
for _, line := range lines {
if len(text)+len(line) < MaxMessageSize {
text += line + "\n"
} else {
blocks = append(blocks, text)
if len(line) > MaxMessageSize {
text = "[此行太长被忽略...]\n"
} else {
text = line + "\n"
}
}
}
if len(text) > 0 {
blocks = append(blocks, text)
}
return blocks
}
+14
View File
@@ -0,0 +1,14 @@
package tgbot
import (
"fmt"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
)
func UserDisplay(message *tgbotapi.Message) string {
if message.From != nil {
return fmt.Sprintf("用户【%s:%d】", message.From.FirstName+message.From.LastName, message.From.ID)
}
return ""
}
+13
View File
@@ -0,0 +1,13 @@
package tgbot
const MaxMessageSize = 4090
func DeleteSliceElem(slice []string, elem string) []string {
tmp := make([]string, 0, len(slice))
for _, v := range slice {
if v != elem {
tmp = append(tmp, v)
}
}
return tmp
}
+203
View File
@@ -0,0 +1,203 @@
package topicmessage
import (
"time"
)
/* 通信协议,第三版*/
// StatcenterSyncReq
type StatcenterSyncReq struct {
Job string `json:"job" bson:"job"` // 同步
UserId uint64 `json:"userId" bson:"userId"` // 用户(注册)
PlatformId string `json:"platformId" bson:"platformId"` // 原始平台Id(访问)
SuccessTime time.Time `json:"successTime"` // 成功时间(购买会员、提现、充值)
MaxSize int64 `json:"maxSize" bson:"maxSize"` // 最大条数
}
type StatcenterSyncResp struct {
Code int `json:"code"` // 200正常 其他异常
Msg string `json:"msg"` // 错误消息
Job string `json:"job" bson:"job"` // 类型
VisitList []*UserVisitMsg `json:"visitList"` // 日活记录
RegisterList []*UserRegisterMsg `json:"registerList"` // 注册记录
WithdrawList []*UserWithdrawMsg `json:"withdrawList"` // 提现记录
RechargeList []*UserRechargeMsg `json:"rechargeList"` // 充值记录
VipList []*UserBuyVIPMsg `json:"buyVipList"` // VIP购买记录
GameRecharge []*UserRechargeMsg `json:"gameRecharge"` // 游戏充值订单
GameWithdraw []*UserWithdrawMsg `json:"gameWithdraw"` // 游戏提现订单
GameWater []*UserGameWaterMsg `json:"gameWater"` // 游戏流水
}
// 用户访问消息
type UserVisitMsg struct {
UserId uint64 `json:"userId" bson:"userId"` // 用户ID
PlatformId string `json:"platformId" bson:"platformId"` // 原始平台流水Id
AppId int32 `json:"appId" bson:"appId"` // appID
SysType string `json:"sysType" bson:"sysType"` // 操作系统类型 安卓 IOS
DevType string `json:"devType,omitempty" bson:"devType"` // 设备型号
IP string `json:"ip,omitempty" bson:"ip"` // IP
Version string `json:"version,omitempty" bson:"version"` // APP版本号
DevID string `json:"devID,omitempty" bson:"devID"` // 设备ID
VisitAt time.Time `json:"visitAt,omitempty" bson:"visitAt"` // 访问时间
}
// 用户用户注册消息
type UserRegisterMsg struct {
UserId uint64 `json:"userId" bson:"userId"` // 用户ID
AppId int32 `json:"appId" bson:"appId"` // appID
SysType string `json:"sysType" bson:"sysType"` // 操作系统类型 安卓 IOS
PlatformId string `json:"platformId" bson:"platformId"` // 原始平台流水Id
DevType string `json:"devType" bson:"devType"` // 设备类型
Mobile string `json:"mobile" bson:"mobile"` // 手机号
IP string `json:"ip,omitempty" bson:"ip"` // IP
Name string `json:"name" bson:"name"` // 名称
IsDirect bool `json:"isDirect" bson:"isDirect"` // true:是直推用户
DistrictCode string `json:"districtCode" bson:"districtCode" ` // 渠道码
PromSeqe string `json:"promSeqe" bson:"promSeqe"` // 全名代理
PromCode string `json:"promCode" bson:"promCode"` // 推广序列
UserAgent string `json:"userAgent" bson:"userAgent"` // User-Agent识别
RegisterTime time.Time `json:"registerTime" bson:"registerTime"` // 注册时间时间
}
// 用户充值消息
type UserRechargeMsg struct {
UserId uint64 `json:"userId" bson:"userId"` // 用户ID
AppId int32 `json:"appId" bson:"appId"` // appID
PlatformId string `json:"platformId" bson:"platformId"` // 原始平台流水Id
SysType string `json:"sysType" bson:"sysType"` // 操作系统类型 安卓 IOS
DevType string `json:"devType" bson:"devType"` // 设备类型
ChannelName string `json:"channelName" bson:"channelName"` // 支付渠道名字
CID string `json:"cid" bson:"cid"` // 渠道id
Type string `bson:"type" json:"type"` // 充值方式
Repeat bool `bson:"repeat" json:"repeat"` // 复冲: true 重复充值 false 第一次
OrderId string `json:"orderId" bson:"orderId"` // 流水id
Money int64 `json:"money" bson:"money"` // 充值金额 订单金额
PayMoney int64 `json:"payMoney" bson:"payMoney"` // 实际到账金额 用户实际支付金额
Status int `json:"status" bson:"status"` // 2付款失败 3付款成功(目前只有成功才发送)
Rate string `json:"rate"` // 渠道费率
SyncStatus int64 `json:"syncStatus" bson:"syncStatus"` // 同步大白鲨:0 需要同步 1:不需要同步
SuccessAt time.Time `json:"successAt" bson:"successAt"` // 成功时间
CreatedAt time.Time `json:"createdAt" bson:"createdAt"` // 创建时间
}
// 用户提现接口
type UserWithdrawMsg struct {
AppId int32 `json:"appId" bson:"appId"` //appID
UID uint64 `json:"uid" bson:"uid"` //用户id
PlatformId string `json:"platformId" bson:"platformId"` //原始平台流水Id
Name string `json:"name" bson:"name"` //用户名
OID string `json:"oid" bson:"oid"` //第三方支付平台id
Money int64 `json:"money" bson:"money"` //提现金额
PayMoney int64 `json:"payMoney" bson:"payMoney"` //实际到账
PayType string `json:"payType" bson:"payType"` //提现方式,alipay,bankcard
ActName string `json:"actName" bson:"actName"` //交易账户持有人
Act string `json:"act" bson:"act"` //交易账户
DeviceType string `json:"deviceType" bson:"deviceType"` //设备类型
DevID string `json:"devID" bson:"devID"` //设备id
Status int `json:"status" bson:"status"` //5提现成功
SuccessAt time.Time `json:"successAt" bson:"successAt"` //回调成功时间
UpdatedAt time.Time `json:"updatedAt" bson:"updatedAt"` //刷新时间
CreatedAt time.Time `json:"createdAt" bson:"createdAt"` //提现申请时间
IsDirect bool `json:"isDirect" bson:"isDirect"` //true:是直推用户
DistrictCode string `json:"districtCode" bson:"districtCode"` //渠道码
SyncStatus int64 `json:"syncStatus" bson:"syncStatus"` // 同步大白鲨:0 需要同步 1:不需要同步
PayChannel string `json:"payChannel" bson:"payChannel"` //提现渠道
PromCode string `json:"promCode" bson:"promCode"` //用户推广序列
}
/* 通信协议,第三版*/
type BuyVIPType int64
const (
BuyVIPTypeMoney BuyVIPType = 0 + iota // 访问
BuyVIPTypeGold // 金币
)
// 用户购买VIP
type UserBuyVIPMsg struct {
AppId int32 `json:"appId" bson:"appId"` //appID
UID uint64 `json:"uid" bson:"uid"` //用户id
PlatformId string `json:"platformId" bson:"platformId"` //原始平台流水Id
Name string `json:"name" bson:"name"` //用户名
Money int64 `json:"money" bson:"money"` //提现金额
Amount int64 `json:"amount" bson:"amount"` //金币
BuyVIPType BuyVIPType `json:"buyVIPType" bson:"buyVIPType"` //支付方式
OID string `json:"oid" bson:"oid"` //第三方支付平台id
DeviceType string `json:"deviceType" bson:"deviceType"` //设备类型
Status int `json:"status" bson:"status"` //1未审核 5提现成功
SuccessAt time.Time `json:"successTime"` //成功时间(购买会员、提现、充值)
IsDirect bool `json:"isDirect" bson:"isDirect"` //true:是直推用户
DistrictCode string `json:"districtCode" bson:"districtCode"` //渠道码
PromCode string `json:"promCode" bson:"promCode"` //用户推广序列
}
type StatcenterGameReq struct {
Date time.Time `json:"date" bson:"date"`
}
type GetReportStatResp struct {
Code int `json:"code"` // 200正常 其他异常
Msg string `json:"msg"` // 错误消息
Date string `json:"date" bson:"date"`
GameRchgAmount int64 `json:"gameRchgAmount" bson:"gameRchgAmount"` //充值总金额
GameDCRchgAmount int64 `json:"gameDCRchgAmount" bson:"gameDCRchgAmount"` //代充总金额
GameWtdrAmount int64 `json:"gameWtdrAmount" bson:"gameWtdrAmount"` //游戏提现总金额
GameWtdrRealAmount int64 `json:"gameWtdrRealAmount" bson:"gameWtdrRealAmount"` //游戏实际提现总金额
GameIncAmount int64 `json:"gameIncAmount" bson:"gameIncAmount"` //游戏上分总金额
GameDecAmount int64 `json:"gameDecAmount" bson:"gameDecAmount"` //游戏下分总金额
ValidBet int64 `json:"validBet" bson:"validBet"` //有效投注金额
Profit int64 `json:"profit" bson:"profit"` //系统盈利
//游戏成功充值次数
GameRchgCount int64 `json:"gameRchgCount" bson:"gameRchgCount"`
//活跃人数
GamePlayCount int64 `json:"gamePlayCount" bson:"gamePlayCount"`
//游戏成功充值人数
GameRchgUsers int64 `json:"gameRchgUsers" bson:"gameRchgUsers"`
//游戏首次充值总金额
GameFirstRchgAmount int64 `json:"gameFirstRchgAmount" bson:"gameFirstRchgAmount"`
//游戏首次成功充值次数
GameFirstRchgCount int64 `json:"gameFirstRchgCount" bson:"gameFirstRchgCount"`
//游戏首次成功充值人数
GameFirstRchgUsers int64 `json:"gameFirstRchgUsers" bson:"gameFirstRchgUsers"`
//游戏成功代充次数
GameDCRchgCount int64 `json:"gameDCRchgCount" bson:"gameDCRchgCount"`
//游戏成功提现次数
GameWtdrCount int64 `json:"gameWtdrCount" bson:"gameWtdrCount"`
//游戏上分次数
GameInchCount int64 `json:"gameInchCount" bson:"gameInchCount"`
//游戏下分次数
GameDecCount int64 `json:"gameDecCount" bson:"gameDecCount"`
//游戏6元赠送总金额
GameGiftIncAmount int64 `json:"gameGiftIncAmount" bson:"gameGiftIncAmount"`
//游戏6元赠送次数
GameGiftIncCount int64 `json:"gameGiftIncCount" bson:"gameGiftIncCount"`
//游戏充值赠送总金额
GameRchGiftIncAmount int64 `json:"gameRchGiftIncAmount" bson:"gameRchGiftIncAmount"`
//游戏充值赠送次数
GameRchGiftIncCount int64 `json:"gameRchGiftIncCount" bson:"gameRchGiftIncCount"`
//游戏VIP赠送总金额
GameVipGiftIncAmount int64 `json:"gameVipGiftIncAmount" bson:"gameVipGiftIncAmount"`
//游戏VIP赠送次数
GameVipGiftIncCount int64 `json:"gameVipGiftIncCount" bson:"gameVipGiftIncCount"`
// 游戏抽水
Revenue int64 `json:"revenue" bson:"revenue"`
//更新时间
UpdatedAt time.Time `json:"updatedAt" bson:"updatedAt"`
CreatedAt time.Time `json:"createdAt" bson:"createdAt"` //创建时间
}
// 用户棋牌流水
type UserGameWaterMsg struct {
AppId int32 `json:"appId" bson:"appId"` // appID
UID uint64 `json:"uid" bson:"uid"` // 用户id
PlatformId string `json:"platformId" bson:"platformId"` // 原始平台流水Id
Date time.Time `json:"date" bson:"date"` // 日期(2021-04-14)
ValidBet int64 `bson:"validBet" json:"validBet"` // 有效投注金额vs
Profit int64 `bson:"profit" json:"profit"` // 系统盈利
Revenue int64 `bson:"revenue" json:"revenue"` // 游戏抽水
SuccessAt time.Time `json:"successTime"` // 成功时间(购买会员、提现、充值)
UpdatedAt time.Time `json:"updatedAt" bson:"updatedAt"` // 更新时间
CreatedAt time.Time `json:"createdAt" bson:"createdAt"` // 创建时间
}
+64
View File
@@ -0,0 +1,64 @@
package utils
import (
"encoding/json"
"fmt"
"go.mongodb.org/mongo-driver/bson"
)
//ToBsonM 将struct转换为 Bson Map
func ToBsonM(s interface{}) (bson.M, error) {
data, err := bson.Marshal(s)
if err != nil {
return nil, err
}
m := make(bson.M)
if err := bson.Unmarshal(data, &m); err != nil {
return nil, err
}
return m, nil
}
// 将page转换为数据库的skip、limit
func ToDBPage(pageNum int64, pageSize int64) (skip int64, limit int64) {
return (pageNum - 1) * pageSize, pageSize
}
//JSONStruct2Map 将struct转换为Map
func JSONStruct2Map(obj interface{}) (map[string]interface{}, error) {
m := make(map[string]interface{})
j, err := json.Marshal(obj)
if err != nil {
return nil, err
}
if err := json.Unmarshal(j, &m); err != nil {
return nil, err
}
return m, err
}
//Map2JSONStruct 将map转换为Json struct
func Map2JSONStruct(v interface{}, m map[string]interface{}) error {
j, err := json.Marshal(m)
if err != nil {
return err
}
if err := json.Unmarshal(j, v); err != nil {
return err
}
return err
}
// struct转换为Map
func Struct2Map(obj interface{}) map[string]interface{} {
m := make(map[string]interface{})
b, err := json.Marshal(obj)
if err != nil {
return m
}
if err = json.Unmarshal(b, &m); err != nil {
fmt.Println(err)
}
return m
}
+108
View File
@@ -0,0 +1,108 @@
package utils
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net"
"net/http"
"sync"
"time"
)
var once sync.Once
var _client *http.Client
func GetHttpClient() *http.Client {
once.Do(func() {
_client = &http.Client{
Timeout: time.Minute * 1, //设置超时时间
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).DialContext,
TLSHandshakeTimeout: 10 * time.Second, //限制 TLS握手的时间
ResponseHeaderTimeout: 10 * time.Second, //限制读取response header的时间,默认 timeout + 5*time.Second
ExpectContinueTimeout: 1 * time.Second, //限制client在发送包含 Expect: 100-continue的header到收到继续发送body的response之间的时间等待。
MaxIdleConns: 100, //所有host的连接池最大连接数量,默认无穷大
MaxIdleConnsPerHost: 30, //每个host的连接池最大空闲连接数,默认2
MaxConnsPerHost: 10, //每个host的最大连接数量
IdleConnTimeout: 3 * time.Minute, //how long an idle connection is kept in the connection pool.
},
}
})
return _client
}
func getBytes(request interface{}) ([]byte, error) {
var body []byte
switch t := request.(type) {
case []byte:
body = t
case string:
body = []byte(t)
default:
var err error
body, err = json.Marshal(request)
if err != nil {
return nil, err
}
}
return body, nil
}
type OnHttpResponse func(res *http.Response) error
func HttpRequest(req *http.Request, onResponse OnHttpResponse) error {
client := GetHttpClient()
res, err := client.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
if onResponse != nil {
err = onResponse(res)
}
return err
}
func HttpMethod(url, method string, header http.Header, body io.Reader, onResponse OnHttpResponse) error {
req, err := http.NewRequest(method, url, body)
if err != nil {
return err
}
if header != nil {
req.Header = header
}
return HttpRequest(req, onResponse)
}
func HttpGet(url string, header http.Header, body io.Reader, onResponse OnHttpResponse) error {
return HttpMethod(url, http.MethodGet, header, body, onResponse)
}
func HttpPost(url string, header http.Header, body io.Reader, onResponse OnHttpResponse) error {
return HttpMethod(url, http.MethodPost, header, body, onResponse)
}
func HttpPostRequestJson(url string, request interface{}, resp interface{}) error {
header := make(http.Header)
header.Set("Content-Type", "application/json")
body, err := getBytes(request)
if err != nil {
return err
}
return HttpPost(url, header, bytes.NewReader(body), func(res *http.Response) error {
if res.StatusCode != http.StatusOK {
return fmt.Errorf("http request (%s) error %s", url, res.Status)
}
content, err := io.ReadAll(res.Body)
if err != nil {
return err
}
return json.Unmarshal(content, resp)
})
}
+84
View File
@@ -0,0 +1,84 @@
package utils
import (
"errors"
"github.com/dgrijalva/jwt-go"
"github.com/gin-gonic/gin"
)
const (
AdminAccountKey = "AdminAccount"
AccountTypeKey = "AccountType"
)
//CreateToken 生成Token算法
func CreateToken(secret string, tokenClaims map[string]interface{}) (tokeness string, err error) {
if secret == "" {
return "", errors.New("secret is empty")
}
claims := jwt.MapClaims(tokenClaims)
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
tokeness, err = token.SignedString([]byte(secret))
if err != nil {
return "", err
}
return tokeness, nil
}
func keyFunc(secret string) jwt.Keyfunc {
return func(token *jwt.Token) (interface{}, error) {
return []byte(secret), nil
}
}
//ParseToken 解析Token
func ParseToken(secret string, tokeness string) (map[string]interface{}, error) {
if secret == "" || tokeness == "" {
return nil, errors.New("secret or tokeness is empty")
}
token, err := jwt.Parse(tokeness, keyFunc(secret))
if err != nil {
return nil, err
}
//验证token,如果token被修改过则为false
if !token.Valid {
return nil, errors.New("token is invalid")
}
claimsToken, ok := token.Claims.(jwt.MapClaims)
if !ok {
return nil, errors.New("cannot convert claim to MapClaim")
}
return claimsToken, nil
}
// GetAdmin 获取管理员账号
// oauth.Auth执行后有效
func GetAdmin(ctx *gin.Context) (account string, err error) {
val, exists := ctx.Get(AdminAccountKey)
if !exists {
err = errors.New("admin account not exist")
return
}
account, ok := val.(string)
if !ok {
return "", errors.New("admin account type error")
}
return
}
// GetAccountType 获取账号类型
// oauth.Auth执行后有效
func GetAccountType(ctx *gin.Context) (string, error) {
var err error
val, exists := ctx.Get(AccountTypeKey)
if !exists {
err = errors.New("claims not exist")
return "", err
}
accType, ok := val.(string)
if !ok {
return "", errors.New("claims type error")
}
return accType, nil
}
+63
View File
@@ -0,0 +1,63 @@
package utils
import (
"sync"
"time"
)
type ExpiredLock struct {
items map[string]time.Time
lock sync.RWMutex
}
func NewExpiredLock() *ExpiredLock {
return &ExpiredLock{
items: make(map[string]time.Time),
}
}
func (kl *ExpiredLock) Count() int64 {
kl.lock.RLock()
defer kl.lock.RUnlock()
return int64(len(kl.items))
}
func (kl *ExpiredLock) LockedCount() int64 {
kl.lock.RLock()
defer kl.lock.RUnlock()
now := time.Now()
count := int64(0)
for _, expired := range kl.items {
if now.Before(expired) {
count++
}
}
return count
}
func (kl *ExpiredLock) UnLock(key string, expired time.Time) {
kl.lock.Lock()
defer kl.lock.Unlock()
expire, exists := kl.items[key]
if !exists || expire != expired {
return
}
delete(kl.items, key)
}
func (kl *ExpiredLock) TryLock(key string, expired time.Time) bool {
kl.lock.Lock()
defer kl.lock.Unlock()
expire, exists := kl.items[key]
if !exists {
kl.items[key] = expired
return true
} else {
if time.Now().After(expire) {
kl.items[key] = expired
return true
} else {
return false
}
}
}
+39
View File
@@ -0,0 +1,39 @@
package utils
import (
"fmt"
"math/rand"
)
func RandDigits(n uint) string {
s := ""
for n != 0 {
s += fmt.Sprintf("%d", rand.Intn(10))
n--
}
return s
}
const lowerLetter = "abcdefghijklmnopqrstuvwxyz"
func RandLowerString(n int) string {
b := make([]byte, n)
for i := range b {
b[i] = lowerLetter[rand.Intn(len(lowerLetter))]
}
return string(b)
}
func GetRandomUserName(base string, len int) string {
return base + RandDigits(2) + RandLowerString(len)
}
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
func RandStringBytes(n int) string {
b := make([]byte, n)
for i := range b {
b[i] = letterBytes[rand.Intn(len(letterBytes))]
}
return string(b)
}
+53
View File
@@ -0,0 +1,53 @@
package utils
import (
"os"
"path/filepath"
"strings"
)
// PathInfo 路径信息
type PathInfo struct {
Dir string // 目录
Ext string // 扩展名
Filename string // 没有扩展名的文件名名称
FullFileName string // 文件全名
}
// PathForInfo 解析路径
// 返回文件,路径,文件名等信息
func PathForInfo(path string) (pi PathInfo) {
if path == "" {
return
}
ext := filepath.Ext(path)
dir, name := filepath.Split(path)
pi.Dir = dir
pi.Ext = ext
pi.Filename = strings.TrimRight(name, ext)
pi.FullFileName = name
return
}
func GetFileSize(filepath string) (int64, error) {
fi, err := os.Stat(filepath)
if err != nil {
return 0, err
}
// get the size
return fi.Size(), nil
}
// JoinUrlPath 域名与uri拼接
func JoinUrlPath(host string, uri string) (urlpath string) {
switch {
case strings.HasSuffix(host, "/") && strings.HasPrefix(uri, "/"):
urlpath = host[0:len(host)-1] + uri
case !strings.HasSuffix(host, "/") && !strings.HasPrefix(uri, "/"):
urlpath = host + "/" + uri
default:
urlpath = host + uri
}
return
}
+110
View File
@@ -0,0 +1,110 @@
package utils
// 定义一个集合接口
type ISet[T comparable] interface {
Add(...T) // 增加集合元素
Remove(...T) // 删除集合元素
Contains(T) bool // 集合是否包含元素
Inter(ISet[T]) ISet[T] // 集合求交集
Union(ISet[T]) ISet[T] // 集合求合集
Diff(ISet[T]) ISet[T] // 集合求差集
Equal(ISet[T]) bool // 集合是否相等
Traver(func(T)) // 遍历集合元素
Elements() []T // 获取集合所有元素列表
IsEmpty() bool // 集合是否为空
}
// Set 使用字典实现集合
type Set[T comparable] struct {
dict map[T]struct{}
}
func NewSet[T comparable](src []T) ISet[T] {
ret := &Set[T]{dict: make(map[T]struct{})}
for _, t := range src {
ret.dict[t] = struct{}{}
}
return ret
}
// Add 实现集合接口
func (s *Set[T]) Add(items ...T) {
for _, t := range items {
s.dict[t] = struct{}{}
}
}
func (s *Set[T]) Remove(items ...T) {
for _, t := range items {
delete(s.dict, t)
}
}
func (s *Set[T]) Contains(item T) bool {
if _, ok := s.dict[item]; ok {
return true
}
return false
}
func (s *Set[T]) Inter(other ISet[T]) ISet[T] {
result := &Set[T]{
dict: make(map[T]struct{}),
}
for k, _ := range s.dict {
if other.Contains(k) {
result.Add(k)
}
}
return result
}
func (s *Set[T]) Union(other ISet[T]) ISet[T] {
result := &Set[T]{
dict: make(map[T]struct{}),
}
s.Traver(func(item T) {
result.Add(item)
})
other.Traver(func(item T) {
result.Add(item)
})
return result
}
func (s *Set[T]) Diff(other ISet[T]) ISet[T] {
result := &Set[T]{
dict: make(map[T]struct{}),
}
s.Traver(func(item T) {
if !other.Contains(item) {
result.Add(item)
}
})
return result
}
func (s *Set[T]) Equal(other ISet[T]) bool {
// 如果 (A - B) 和 (B - A) 都是空集,则说明两个集合的元素完全相同,因此它们相等
d1 := s.Diff(other)
d2 := other.Diff(s)
return d1.IsEmpty() && d2.IsEmpty()
}
func (s *Set[T]) Traver(onItem func(item T)) {
for k, _ := range s.dict {
onItem(k)
}
}
func (s *Set[T]) Elements() []T {
ret := make([]T, 0)
for k, _ := range s.dict {
ret = append(ret, k)
}
return ret
}
func (s *Set[T]) IsEmpty() bool {
return len(s.dict) == 0
}
+40
View File
@@ -0,0 +1,40 @@
package utils
import "strings"
func RepairDC(dc string) string {
ret := strings.ReplaceAll(dc, "*", "")
return strings.TrimSpace(ret)
}
func TowIsOR(v1, v2 string, isV string) bool {
return strings.ToUpper(v1) == strings.ToUpper(isV) || strings.ToUpper(v2) == strings.ToUpper(isV)
}
func TowIsORs(v1, v2 string, isVs []string) bool {
v1 = strings.ToUpper(v1)
v2 = strings.ToUpper(v2)
for _, v := range isVs {
vU := strings.ToUpper(v)
if v1 == vU || v2 == vU {
return true
}
}
return false
}
func ThreeIsOR(v1, v2, v3 string, isV string) bool {
return strings.ToUpper(v1) == strings.ToUpper(isV) || strings.ToUpper(v2) == strings.ToUpper(isV) || strings.ToUpper(v3) == strings.ToUpper(isV)
}
type StrSliceConstructFilter func(index int) (string, bool) // 返回 是否保留,项值
func StrSliceConstruct(length int, filter StrSliceConstructFilter) []string {
ret := make([]string, 0)
if filter == nil {
return ret
}
for i := 0; i < length; i++ {
if item, keep := filter(i); keep {
ret = append(ret, item)
}
}
return ret
}
+47
View File
@@ -0,0 +1,47 @@
package utils
import (
"fmt"
"reflect"
"testing"
)
func TestStrSliceConstruct(t *testing.T) {
type args struct {
length int
filter StrSliceConstructFilter
}
tests := []struct {
name string
args args
want []string
}{
{
name: "1",
args: args{
length: 5,
filter: func(index int) (string, bool) {
return fmt.Sprintf("%d", index), true
},
},
want: []string{"0", "1", "2", "3", "4"},
},
{
name: "2",
args: args{
length: 5,
filter: func(index int) (string, bool) {
return fmt.Sprintf("%d", index*index), true
},
},
want: []string{"0", "1", "4", "9", "16"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := StrSliceConstruct(tt.args.length, tt.args.filter); !reflect.DeepEqual(got, tt.want) {
t.Errorf("StrSliceConstruct() = %v, want %v", got, tt.want)
}
})
}
}
+44
View File
@@ -0,0 +1,44 @@
package utils
import (
"fmt"
"sync"
)
type WaitGroup struct {
wg sync.WaitGroup
fs []func()
}
func NewWaitGroup() *WaitGroup {
wg := &WaitGroup{}
wg.fs = make([]func(), 0)
return wg
}
func (w *WaitGroup) Add(f ...func()) *WaitGroup {
if w == nil {
panic("WaitGroup can not be null\n")
}
if w.fs == nil {
panic("WaitGroup may be not init\n")
}
w.fs = append(w.fs, f...)
return w
}
func (w *WaitGroup) RunWaits() {
w.wg.Add(len(w.fs))
for _, f := range w.fs {
go func(wg *sync.WaitGroup, fn func()) {
defer func() {
if err := recover(); err != nil {
fmt.Printf("%v\n", err)
}
wg.Done()
}()
fn()
}(&w.wg, f)
}
w.wg.Wait()
}
+181
View File
@@ -0,0 +1,181 @@
package utils
import (
"time"
"github.com/jinzhu/now"
)
const (
DateISO = "2006-01-02"
TimeISO = "2006-01-02 15:04:05"
)
var (
LocBJ, _ = time.LoadLocation("Asia/Shanghai")
)
func init() {
time.Local = LocBJ
}
func Now() time.Time {
return time.Now().Local()
}
func NowMillisecond() int64 {
return Now().UnixNano() / int64(time.Millisecond)
}
func BeginOfYesterday() time.Time {
t := Now().AddDate(0, 0, -1)
year, month, day := t.Date()
return time.Date(year, month, day, 0, 0, 0, 0, t.Location())
}
func BeginOfYesterday0() time.Time {
t := Now().AddDate(0, 0, -2)
year, month, day := t.Date()
return time.Date(year, month, day, 0, 0, 0, 0, t.Location())
}
func BeginOfToday() time.Time {
t := Now()
year, month, day := t.Date()
return time.Date(year, month, day, 0, 0, 0, 0, t.Location())
}
func BeginOfHour() time.Time {
t := Now()
year, month, day := t.Date()
return time.Date(year, month, day, t.Hour(), 0, 0, 0, t.Location())
}
func EndOfToday() time.Time {
t := Now()
year, month, day := t.Date()
return time.Date(year, month, day, 23, 59, 59, 0, t.Location())
}
func BeginTodayMillsecond() int64 {
return BeginOfToday().UnixNano() / int64(time.Millisecond)
}
// ServerCurrMillisecond 获得时间毫秒数 13位
func ServerCurrMillisecond(t time.Time) int64 {
return t.UnixNano() / 1000000
}
func BeginWithTime(t time.Time) time.Time {
year, month, day := t.Date()
return time.Date(year, month, day, 0, 0, 0, 0, t.Location())
}
func BeginMonthWithTime(t time.Time) time.Time {
year, month, _ := t.Date()
return time.Date(year, month, 1, 0, 0, 0, 0, t.Location())
}
func BeginWithTimeLocal(t time.Time) time.Time {
localTime := t.Local()
year, month, day := localTime.Date()
return time.Date(year, month, day, 0, 0, 0, 0, time.Local)
}
func EndWithTime(t time.Time) time.Time {
year, month, day := t.Date()
return time.Date(year, month, day, 23, 59, 59, 0, t.Location())
}
func IsToday(t time.Time) bool {
if t.IsZero() {
return false
}
today := Now()
return t.Year() == today.Year() &&
t.Month() == today.Month() &&
t.Day() == today.Day()
}
// BeginningOfTomorrow 明天的开始
func BeginningOfTomorrow(t time.Time) time.Time {
return BeginningOfDay(t).AddDate(0, 0, 1)
}
// BeginningOfDay 一天的开始
func BeginningOfDay(t time.Time) time.Time {
return now.New(t).BeginningOfDay()
}
// StrTimeToTime 时间字符串转时间
func StrTimeToTime(strTime string) (t time.Time) {
timeLayout := "2006-01-02T15:04:05-07:00"
loc, _ := time.LoadLocation("Local")
t, _ = time.ParseInLocation(timeLayout, strTime, loc)
return
}
func RecentMinute(tim time.Time, scaleMinute int64) time.Time {
minute := int64(tim.Minute())
lave := minute % scaleMinute
formatMinute := minute - lave
return time.Date(tim.Year(), tim.Month(), tim.Day(), tim.Hour(), int(formatMinute), 0, 0, tim.Location())
}
var (
GlobalShardHour = time.Hour
GlobalShardDay = 24 * time.Hour
GlobalShardWeek = 7 * 24 * time.Hour
GlobalShardMonth = 30 * 24 * time.Hour
DefaultStatBeginTime, _ = time.ParseInLocation("2006-01-02", "2021-03-01", time.Local)
)
type TimeSegment struct {
Begin time.Time
End time.Time
}
type TimeSegmentList []TimeSegment
func CalcTimeSegments(start, end time.Time, step time.Duration) TimeSegmentList {
ret := make(TimeSegmentList, 0)
if start.After(end) {
return ret
}
if start.Add(step).After(end) {
ret = append(ret, TimeSegment{
Begin: start,
End: end,
})
return ret
}
b := start
for b.Before(end) {
e := b.Add(step)
if e.After(end) {
e = end
}
ret = append(ret, TimeSegment{Begin: b, End: e})
b = b.Add(step)
}
return ret
}
func BeginOfMonth(t time.Time) time.Time {
year, month, _ := t.In(time.Local).Date()
return time.Date(year, month, 1, 0, 0, 0, 0, t.Location())
}
func EndOfMonth(t time.Time) time.Time {
return BeginOfMonth(t).AddDate(0, 1, 0).Add(-time.Nanosecond)
}
func MonthDateRange(t time.Time) (string, string) {
b := BeginOfMonth(t)
e := EndOfMonth(t)
return GetDateString(b), GetDateString(e)
}
func GetDateString(t time.Time) string {
return t.In(LocBJ).Format(DateISO)
}
+18
View File
@@ -0,0 +1,18 @@
package utils
import (
"testing"
"time"
)
func TestRecentMinute(t *testing.T) {
time.Local, _ = time.LoadLocation("Asia/Shanghai")
start, _ := time.ParseInLocation(time.RFC3339, "2023-07-28T00:00:00.000+08:00", time.Local)
end, _ := time.ParseInLocation(time.RFC3339, "2023-07-28T00:07:00.000+08:00", time.Local)
t.Log(start, end)
list := CalcTimeSegments(start, end, GlobalShardHour)
for _, item := range list {
t.Log(item.Begin, item.End)
}
}
+148
View File
@@ -0,0 +1,148 @@
package utils
import (
"fmt"
"golang.org/x/exp/constraints"
"runtime/debug"
"sort"
"stat/common/log"
"time"
)
func RecoverError(onError func(err error)) {
if r := recover(); r != nil {
err := fmt.Errorf("internal error: %v", r)
if onError != nil {
onError(err)
}
log.Error(fmt.Sprintf("[Panic] RecoverError err: %+v\n", r) + string(debug.Stack()))
}
}
/*func PValue[T any](value T) *T {
ret := value
return &ret
}
*/
type M map[string]interface{}
func (m M) HasKey(key string) bool {
if _, ok := m[key]; ok {
return true
}
return false
}
type OnPage func(skip int64, limit int64) error
func Abs[T constraints.Integer | constraints.Float](v T) T {
if v < 0 {
return -v
}
return v
}
func EnsureRange[T constraints.Ordered](value, min, max T) T {
if value < min {
value = min
}
if value > max {
value = max
}
return value
}
func InRange[T constraints.Ordered](value, min, max T) bool {
if value < min {
return false
}
if value > max {
return false
}
return true
}
func TimeIsBetween(t, min, max time.Time, includeEndPoint bool) bool {
if includeEndPoint {
return (t.Equal(min) || t.After(min)) && (t.Equal(max) || t.Before(max))
} else {
return (t.Equal(min) || t.After(min)) && t.Before(max)
}
}
func SlicePaged[T any](slice []T, chunkSize int, onPiece func(piece []T) error) error {
array := SplitSlice(slice, chunkSize)
for _, arr := range array {
if err := onPiece(arr); err != nil {
return err
}
}
return nil
}
func CountPaged(totalCount int64, pageSize int64, onPage OnPage) error {
pageTotal := int(totalCount / pageSize)
if totalCount%pageSize != 0 {
pageTotal++
}
for page := 0; page < pageTotal; page++ {
if err := onPage(int64(page)*pageSize, pageSize); err != nil {
return err
}
}
return nil
}
func SplitSlice[T any](s []T, chunkSize int) [][]T {
if chunkSize <= 0 {
return nil
}
var chunks [][]T
for i := 0; i < len(s); i += chunkSize {
end := i + chunkSize
if end > len(s) {
end = len(s)
}
chunks = append(chunks, s[i:end])
}
return chunks
}
func GetSlicePageRange[T any](s []T, pageNum, pageSize int) []T {
totalLen := len(s)
start, end := GetPageRange(totalLen, pageNum, pageSize)
return s[start:end]
}
func GetPageRange(totalLen, pageNum, pageSize int) (start, end int) {
if pageNum < 1 {
pageNum = 1
}
if pageSize < 1 {
pageSize = 10
}
start = (pageNum - 1) * pageSize
if start > totalLen {
start = totalLen
}
end = start + pageSize
if end > totalLen {
end = totalLen
}
return
}
func IsSliceEqual[T constraints.Ordered](src []T, dst []T) bool {
sort.Slice(src, func(i, j int) bool { return src[i] < src[j] })
sort.Slice(dst, func(i, j int) bool { return dst[i] < dst[j] })
if len(src) != len(dst) {
return false
}
for i := range src {
if src[i] != dst[i] {
return false
}
}
return true
}
+7
View File
@@ -0,0 +1,7 @@
package utils
import "github.com/google/uuid"
func UUID() string {
return uuid.New().String()
}
+49
View File
@@ -0,0 +1,49 @@
package utils
import (
"errors"
"fmt"
"strconv"
)
const TotalRows = 1048576
const MinColumns = 1
const MaxColumns = 16384
// ColumnNumberToName provides a function to convert the integer to Excel
// sheet column title.
//
// Example:
//
// excelize.ColumnNumberToName(37) // returns "AK", nil
func ColumnNumberToName(num int) (string, error) {
if num < MinColumns || num > MaxColumns {
return "", fmt.Errorf("the column number must be greater than or equal to %d and less than or equal to %d", MinColumns, MaxColumns)
}
var col string
for num > 0 {
col = string(rune((num-1)%26+65)) + col
num = (num - 1) / 26
}
return col, nil
}
// CoordinatesToCellName converts [X, Y] coordinates to alpha-numeric cell
// name or returns an error.
//
// Example:
//
// excelize.CoordinatesToCellName(1, 1) // returns "A1", nil
// excelize.CoordinatesToCellName(1, 1, true) // returns "$A$1", nil
func CoordinatesToCellName(col, row int, abs ...bool) (string, error) {
if col < 1 || row < 1 {
return "", errors.New("col row < 0")
}
if row > TotalRows {
return "", errors.New("row number exceeds maximum limit")
}
colName, err := ColumnNumberToName(col)
return colName + strconv.Itoa(row), err
}
+55
View File
@@ -0,0 +1,55 @@
package v10
import (
"github.com/go-playground/validator/v10"
"reflect"
"sync"
"github.com/gin-gonic/gin/binding"
)
type DefaultValidator struct {
once sync.Once
validate *validator.Validate
}
var _ binding.StructValidator = &DefaultValidator{}
func (v *DefaultValidator) ValidateStruct(obj interface{}) error {
if kindOfData(obj) == reflect.Struct {
v.lazyinit()
if err := v.validate.Struct(obj); err != nil {
return error(err)
}
}
return nil
}
func (v *DefaultValidator) Engine() interface{} {
v.lazyinit()
return v.validate
}
func (v *DefaultValidator) lazyinit() {
v.once.Do(func() {
v.validate = validator.New()
v.validate.SetTagName("binding")
// add any custom validations etc. here
})
}
func kindOfData(data interface{}) reflect.Kind {
value := reflect.ValueOf(data)
valueType := value.Kind()
if valueType == reflect.Ptr {
valueType = value.Elem().Kind()
}
return valueType
}
+152
View File
@@ -0,0 +1,152 @@
package v10
import (
"github.com/nyaruka/phonenumbers"
"reflect"
"regexp"
"strconv"
"time"
"github.com/go-playground/validator/v10"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// IsPureChar 匹配中文、字母、数字
func IsPureChar(src string) bool {
return regexp.MustCompile("^[a-z0-9A-Z\u4e00-\u9fa5]+$").MatchString(src)
}
//IsPhoneNumber 匹配所有国际化的手机号码(手机卡 + 上网卡)
func IsGlobalizationPhoneNumber(phoneNumber string) bool {
num, _ := phonenumbers.Parse(phoneNumber, "CN")
return phonenumbers.IsValidNumber(num)
}
// IsEn 是否是英文的正则表达式验证
func IsEn(data string) bool {
return regexp.MustCompile(`^[a-zA-Z]+$`).MatchString(data)
}
// IsEnZh 是否是中英文的正则表达式验证
func IsEnZh(data string) bool {
return regexp.MustCompile("^[a-zA-Z|\u4e00-\u9fa5]+$").MatchString(data)
}
// IsEnNum 是否是英文数字的正则表达式验证
func IsEnNum(data string) bool {
return regexp.MustCompile(`^[a-zA-Z0-9]+$`).MatchString(data)
}
// IsEnNumSpe 是否是英文数字特殊符号的验证
func IsEnNumSpe(data string) bool {
return regexp.MustCompile(`^[a-zA-Z0-9|~!@#$%^&*()/\|,.<>?"'();:_+-=\[\]{}]+$`).MatchString(data)
}
// IsObjectID 是否objectID的验证
func IsObjectID(data string) bool {
_, err := primitive.ObjectIDFromHex(data)
if err != nil {
return false
}
return true
}
// IsLengthBetween 判断data字符串长度是否在范围内
func IsLengthBetween(data string, min int, max int) bool {
return len(data) >= min && len(data) <= max
}
// IsNil nil判断
func IsNil(i interface{}) bool {
vi := reflect.ValueOf(i)
if vi.Kind() == reflect.Ptr {
return vi.IsNil()
}
return false
}
// Phone 验证手机号码
func Phone(
v *validator.Validate, topStruct reflect.Value, currentStructOrField reflect.Value,
field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string,
) bool {
if data, ok := field.Interface().(string); ok {
if len(data) <= 10 || len(data) >= 15 {
return false
}
if !IsGlobalizationPhoneNumber(data) {
return false
}
return true
}
return false
}
//Time_Utc 转换成UTC时间
func Time_Utc(fl validator.FieldLevel) bool {
timeParse, err := time.Parse(time.RFC3339, fl.Field().String())
if err != nil {
return false
}
timevalue := reflect.ValueOf(timeParse)
fl.Field().Set(timevalue)
return true
}
// StrLength 验证长度
func StrLength(
v *validator.Validate, topStruct reflect.Value, currentStructOrField reflect.Value,
field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string,
) bool {
num, err := strconv.Atoi(param)
if err != nil {
return false
}
if data, ok := field.Interface().(string); ok {
if len(data) == num {
return true
}
}
return false
}
// NeedType 需要类型
func NeedType(
v *validator.Validate, topStruct reflect.Value, currentStructOrField reflect.Value,
field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string,
) bool {
if data, ok := field.Interface().(string); ok {
switch param {
case "en": // 英文
return IsEn(data)
case "enZh": // 中英文
return IsEnZh(data)
case "enNum": // 英文数字
return IsEnNum(data)
case "enNumSpe": // 英文数字特殊符号
return IsEnNumSpe(data)
case "objectID": // 是否为mongo的id
return IsObjectID(data)
}
}
return false
}
//RegisterValidator 注解自定义参数验证
func RegisterValidator() gin.HandlerFunc {
return func(c *gin.Context) {
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
//v.RegisterValidation("phone", Phone)
// v.RegisterValidation("len", StrLength)
// v.RegisterValidation("min", validator.IsGte)
// v.RegisterValidation("max", validator.IsLte)
// v.RegisterValidation("eq", validator.IsEq)
// v.RegisterValidation("type", NeedType)
v.RegisterValidation("time_utc", Time_Utc)
}
c.Next()
}
}

Some files were not shown because too many files have changed in this diff Show More