Initial commit
This commit is contained in:
@@ -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()
|
||||
}
|
||||
@@ -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"` // 条数
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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: "不展示",
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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:
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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:
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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"`
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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:
|
||||
}
|
||||
@@ -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"`
|
||||
}
|
||||
@@ -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"`
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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: "游戏统计",
|
||||
}
|
||||
@@ -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{},
|
||||
}
|
||||
)
|
||||
@@ -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
|
||||
}
|
||||
@@ -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] }
|
||||
@@ -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)
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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.
@@ -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())
|
||||
}
|
||||
@@ -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 ""
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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",
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
@@ -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])
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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 ""
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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"` // 创建时间
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+39
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package utils
|
||||
|
||||
import "github.com/google/uuid"
|
||||
|
||||
func UUID() string {
|
||||
return uuid.New().String()
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user