Initial commit

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
fish03
2026-09-23 14:43:16 +07:00
co-authored by Claude Opus 5.5
commit bef9f8b4ed
663 changed files with 81305 additions and 0 deletions
+138
View File
@@ -0,0 +1,138 @@
package comm
import (
"encoding/json"
"sync"
"time"
"videoweb/pub/common/godef"
"videoweb/pub/common/lock"
"videoweb/pub/common/log"
"github.com/go-redis/redis"
"go.uber.org/zap"
)
type MessageType int64
const (
MessageTypeError MessageType = iota // 心跳消息
MessageTypeAlive
MessageTypeMediaAdd // 添加一个媒体
MessageTypeMediaDel // 删除一个媒体
MessageTypePostAdd // 添加一个帖子
MessageTypePostDel // 删除一个帖子
)
type message struct {
Type MessageType
Payload []byte
}
type OnReceive func(data []byte)
type Communicate struct {
sendChannel string // 发送消息的频道
recvChannel string // 接收消息的频道
client *redis.Client // Redis的客户端
lock sync.RWMutex
callbacks map[MessageType][]OnReceive
publish *lock.Queue[*message]
}
func NewCommunicate(client *redis.Client, sendChannel string, recvChannel string) *Communicate {
return &Communicate{
sendChannel: sendChannel,
recvChannel: recvChannel,
client: client,
callbacks: make(map[MessageType][]OnReceive),
publish: lock.NewQueue[*message](),
}
}
func (c *Communicate) RegisterReceiver(mType MessageType, receive OnReceive) {
c.lock.Lock()
defer c.lock.Unlock()
c.callbacks[mType] = append(c.callbacks[mType], receive)
}
func (c *Communicate) findEvent(mType MessageType) []OnReceive {
c.lock.RLock()
defer c.lock.RUnlock()
return c.callbacks[mType]
}
func (c *Communicate) dispatchMessage(mType MessageType, payload []byte) {
events := c.findEvent(mType)
for _, event := range events {
event(payload)
}
}
func (c *Communicate) handleRecv(data *redis.Message) {
if data.Channel == "error" {
log.Error("recv from channel %s failed", zap.Any("error", data.Payload))
return
}
if data.Channel == c.recvChannel {
msg := &message{}
err := json.Unmarshal([]byte(data.Payload), msg)
if err != nil {
log.Error("recv from channel %s failed", zap.Any("error", err))
}
c.dispatchMessage(msg.Type, msg.Payload)
}
}
func (c *Communicate) Send(mType MessageType, payload []byte) error {
msg := &message{
Type: mType,
Payload: payload,
}
c.publish.Enqueue(msg)
return nil
}
func (c *Communicate) Start() {
// 启动接收线程
godef.Go(func() {
sub := c.client.Subscribe(c.recvChannel)
defer sub.Close()
// 循环接收消息
for {
msg, err := sub.ReceiveMessage()
if err != nil {
msg = &redis.Message{
Channel: "error",
Payload: err.Error(),
}
c.handleRecv(msg)
time.Sleep(time.Second)
} else {
c.handleRecv(msg)
}
}
})
// 启动发送线程
godef.Go(func() {
for {
time.Sleep(time.Second)
for !c.publish.IsEmpty() {
msg, ok := c.publish.Dequeue()
if !ok {
break
}
data, err := json.Marshal(msg)
if err != nil {
log.Error("Communicate msg marshal failed", zap.Any("error", err))
break
}
err = c.client.Publish(c.sendChannel, data).Err()
if err != nil {
c.publish.Enqueue(msg) // 再写回队列
log.Error("Communicate send msg failed", zap.Any("error", err))
break
}
}
}
})
}
+131
View File
@@ -0,0 +1,131 @@
package common
import (
"errors"
"fmt"
"net/http"
"os"
"videoweb/pub/common/enum/consts"
"videoweb/pub/common/log"
"videoweb/pub/common/srvdef"
"videoweb/pub/common/useragent"
"strconv"
"time"
"github.com/gin-gonic/gin"
)
var (
cryptSecret string
env consts.RunMod
//routineCount int64
)
// InitGinSecret InitResponseParam
func InitGinSecret(secret string, env_ consts.RunMod) {
cryptSecret = secret
env = env_
}
// ServerJSON 返回数据并处理多语言
func ServerJSON(c *gin.Context, codeOrErr srvdef.ErrInfoer, data interface{}) {
code := codeOrErr.GetCode()
tip := codeOrErr.GetTip()
msg := codeOrErr.GetMsg()
var hash bool
if code != srvdef.Success { //发生错误,记录日志
var version, sysType, devType string
id := GetUserId(c)
ua, _ := GetUA(c)
if ua.Ver != "" {
version = ua.Ver
}
if ua.SysType != "" {
sysType = ua.SysType
}
if devType != "" {
devType = ua.DevType
}
log.Warn("Error:",
log.Any("UID", strconv.FormatInt(id, 10)),
log.Any("IP", c.ClientIP()),
log.Any("Version", version),
log.Any(consts.CtxSysType, 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,
"hash": hash,
"msg": msg,
"tip": 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(consts.CtxUserID)
if !exists {
err = errors.New("userId not exist")
return
}
uid, ok := val.(uint64)
if !ok {
return 0, errors.New("userId type error")
}
return
}
func GetUserId(ctx *gin.Context) int64 {
return ctx.GetInt64(consts.CtxUserID)
}
// GetUA
func GetUA(ctx *gin.Context) (u useragent.UA, err error) {
val, exists := ctx.Get(consts.CtxUA)
if !exists {
err = errors.New("user-agent not exists")
return
}
u, ok := val.(useragent.UA)
if !ok {
err = errors.New("user-agent type error")
return
}
return u, nil
}
// GetIP 获取真实IP
func GetIP(ctx *gin.Context) string {
relIP, exists := ctx.Get(consts.CtxIP)
if !exists {
if relIP == "" {
relIP = ctx.ClientIP()
}
}
v, ok := relIP.(string)
if !ok {
return ""
}
log.Info(fmt.Sprintf("[IP-ROUTER] %s,PID %d", ctx.GetHeader("X-Forwarded-For"), os.Getpid()))
return v
}
func IsNilOrEmpty(in interface{}) bool {
if in == nil || in == "" {
return true
}
return false
}
func AlphabeticColl() []string {
return []string{"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M",
"N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"}
}
+135
View File
@@ -0,0 +1,135 @@
package common
import (
"encoding/json"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
)
type ID = primitive.ObjectID
// ToJsonM 将struct转换为 Json Map
func ToJsonM(obj interface{}) (map[string]interface{}, error) {
j, err := json.Marshal(obj)
if err != nil {
return nil, err
}
m := make(map[string]interface{})
if err := json.Unmarshal(j, &m); err != nil {
return nil, err
}
return m, err
}
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
}
func MapToJsonString(m map[string]string) (string, error) {
if data, err := json.Marshal(m); err != nil {
return "", err
} else {
return string(data), nil
}
}
// MapToStruct s is struct ptr
func MapToStruct(s interface{}, m map[string]interface{}) error {
bsonBytes, err := bson.Marshal(m)
if err != nil {
return err
}
return bson.Unmarshal(bsonBytes, s)
}
// 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
}
func IDArray(ids []string) ([]ID, error) {
var idArray []ID
for _, id := range ids {
_id, err := primitive.ObjectIDFromHex(id)
if err != nil {
return nil, err
}
idArray = append(idArray, _id)
}
return idArray, nil
}
func ObjectIDs2String(obj []primitive.ObjectID) []string {
arr := []string{}
if len(obj) == 0 {
return arr
}
for _, o := range obj {
arr = append(arr, o.Hex())
}
return arr
}
func String2ObjectID(obj []string) []primitive.ObjectID {
arr := []primitive.ObjectID{}
for _, o := range obj {
oid, err := primitive.ObjectIDFromHex(o)
if err != nil {
continue
}
arr = append(arr, oid)
}
return arr
}
// PercentOf - calculate what percent [number1] is of [number2].
// ex. 300 is 12.5% of 2400
func PercentOf(part int, total int) float64 {
return (float64(part) * float64(100)) / float64(total)
}
func MergeMap(src, dest map[string]interface{}) map[string]interface{} {
if len(src) == 0 {
return dest
}
if len(dest) == 0 {
return src
}
if len(src) == 0 && len(dest) == 0 {
return make(map[string]interface{})
}
for k, v := range dest {
src[k] = v
}
return src
}
+119
View File
@@ -0,0 +1,119 @@
package cronmgr
import (
"context"
"errors"
"sync"
"videoweb/pub/common/log"
"github.com/robfig/cron/v3"
)
var c *cron.Cron
// runIngs 正在运行中的任务
var runIngs map[*context.Context]struct{}
var r_lock sync.Mutex
// RegisterCron 注册一个定时任务
func RegisterCron(spec string, cmd func(), errfunc func(err interface{})) (cron.EntryID, error) {
if len(spec) == 0 || cmd == nil {
return -1, errors.New("spec or cmd is nil")
}
return registerCron(spec, func() {
defer func() {
if err := recover(); err != nil {
if errfunc != nil {
errfunc(err)
}
log.Errorf("cron task error.%v", err)
}
}()
cmd()
})
}
// RegisterCronAsyncOne 注册一个协程执行的定时任务 限制了cmd同时只有一个协程在执行
func RegisterCronAsyncOne(spec string, cmd func(), errfunc func(err interface{})) (cron.EntryID, error) {
if len(spec) == 0 || cmd == nil {
return -1, errors.New("spec or cmd is nil")
}
ctx := context.Background()
return registerCron(spec, func() {
go func(c *context.Context) {
defer func() {
if err := recover(); err != nil {
if errfunc != nil {
errfunc(err)
}
log.Errorf("cron task error.%v", err)
}
}()
if taskRunIng(c) {
cmd()
taskRunEnd(c)
} else {
log.Warnf("Limits cmd to only one coroutine executing at the same time")
}
}(&ctx)
})
}
// RegisterCronAsync 注册一个协程执行的定时任务
func RegisterCronAsync(spec string, cmd func(), errfunc func(err interface{})) (cron.EntryID, error) {
if len(spec) == 0 || cmd == nil {
return -1, errors.New("spec or cmd is nil")
}
return registerCron(spec, func() {
go func() {
defer func() {
if err := recover(); err != nil {
if errfunc != nil {
errfunc(err)
}
log.Errorf("cron task error.%v", err)
}
}()
cmd()
}()
})
}
func registerCron(spec string, cmd func()) (cron.EntryID, error) {
if c == nil {
c = cron.New(cron.WithSeconds())
runIngs = make(map[*context.Context]struct{})
}
id, err := c.AddFunc(spec, cmd)
if err != nil {
log.Warnf("add cron [%s] fail", spec)
return -1, err
}
c.Start()
log.Infof("add cron [%d] spec:[%s].", id, spec)
return id, nil
}
// UnregisterCron 注销一个定时任务
func UnregisterCron(id cron.EntryID) bool {
if id > 0 {
c.Remove(id)
log.Infof("remove cron id:[%d]", id)
}
log.Infof("remove cron [%d] not found", id)
return false
}
func taskRunIng(ctx *context.Context) bool {
r_lock.Lock()
defer r_lock.Unlock()
if _, ok := runIngs[ctx]; ok {
return false
}
runIngs[ctx] = struct{}{}
return true
}
func taskRunEnd(ctx *context.Context) {
r_lock.Lock()
defer r_lock.Unlock()
delete(runIngs, ctx)
}
+148
View File
@@ -0,0 +1,148 @@
package cronx
import (
"context"
"sync"
"time"
"videoweb/pub/common/log"
"github.com/robfig/cron/v3"
"go.uber.org/zap"
)
type CronJob interface {
Run() error
Desc() string
Name() string
}
type CronJobAndSpec struct {
CronJob CronJob
Spec string
}
type BaseJob struct {
ctx context.Context
spec string
name string
desc string
l *zap.SugaredLogger
}
type cronFunc struct {
*BaseJob
fn func(ctx context.Context, l *zap.SugaredLogger) error
}
func addError(fn func(ctx context.Context, l *zap.SugaredLogger)) func(context.Context, *zap.SugaredLogger) error {
return func(ctx context.Context, l *zap.SugaredLogger) error {
l.Info("start")
defer l.Info("end")
fn(ctx, l)
return nil
}
}
func (f cronFunc) Run() error { return f.fn(f.ctx, f.l) }
func NewCronFunc(ctx context.Context, name, desc string, fn func(ctx context.Context, l *zap.SugaredLogger)) cronFunc {
return cronFunc{
BaseJob: NewBaseJob(ctx, name, desc),
fn: addError(fn),
}
}
func NewCronFuncWithError(ctx context.Context, name, desc string, fn func(ctx context.Context, l *zap.SugaredLogger) error) cronFunc {
return cronFunc{
BaseJob: NewBaseJob(ctx, name, desc),
fn: fn,
}
}
func NewBaseJob(ctx context.Context, name, desc string) *BaseJob {
return &BaseJob{
ctx: ctx,
name: name,
desc: desc,
l: log.ZapLogS.Named(name),
}
}
func (j *BaseJob) Name() string { return j.name }
func (j *BaseJob) Desc() string { return j.desc }
func (j *BaseJob) L() *zap.SugaredLogger { return j.l }
func (j *BaseJob) Context() context.Context { return j.ctx }
type CronLogger struct {
l *zap.SugaredLogger
}
func NewCronLogger() *CronLogger {
return &CronLogger{
l: log.ZapLogS.Named("cron"),
}
}
func (l *CronLogger) Info(msg string, kvs ...interface{}) {
l.l.Infow(msg, kvs)
}
func (l *CronLogger) Error(err error, msg string, kvs ...interface{}) {
l.l.Errorw(err.Error()+msg, kvs)
}
func StartJobs(jobs ...CronJobAndSpec) (closer func()) {
var once sync.Once
l := NewCronLogger()
c := cron.New(cron.WithSeconds(), cron.WithLocation(time.Local), cron.WithChain(cron.Recover(l)))
for i := range jobs {
j := jobs[i].CronJob
spec := jobs[i].Spec
name := j.Name()
_, err := c.AddFunc(spec, func() {
defer log.Infof("%s end", name)
log.Infof("%s start", name)
if err := j.Run(); err != nil {
log.Errorf("%s run failed: %s", name, err.Error())
}
})
if err != nil {
log.Fatalf("%s add failed: %s", err)
}
}
c.Start()
return func() {
once.Do(func() {
c.Stop()
})
}
}
type CronFuncType func(ctx context.Context, l *zap.SugaredLogger)
func TraceTime(fn CronFuncType) CronFuncType {
return func(ctx context.Context, l *zap.SugaredLogger) {
now := time.Now()
defer func() {
elapse := time.Since(now)
if elapse > time.Second {
l.Infof("takes %s", elapse.Truncate(time.Millisecond))
} else {
l.Infof("takes %d ms", elapse/time.Millisecond)
}
}()
l.Info("start run")
fn(ctx, l)
l.Info("end run")
}
}
// MustSecondSpec is a helper method to validate spec at compile time
func MustSecondSpec(spec string) string {
parser := cron.NewParser(
cron.Second | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor,
)
_, err := parser.Parse(spec)
if err != nil {
panic(err)
}
return spec
}
+43
View File
@@ -0,0 +1,43 @@
package crypt
import (
"bytes"
"crypto/aes"
)
func PKCS7PaddingEx(ciphertext []byte, blockSize int) []byte {
padding := blockSize - len(ciphertext)%blockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(ciphertext, padtext...)
}
func PKCS7UnPaddingEx(origData []byte) []byte {
length := len(origData)
unpadding := int(origData[length-1])
return origData[:(length - unpadding)]
}
func EcbDecrypt(data, key []byte) []byte {
block, _ := aes.NewCipher(key)
decrypted := make([]byte, len(data))
size := block.BlockSize()
for bs, be := 0, size; bs < len(data); bs, be = bs+size, be+size {
block.Decrypt(decrypted[bs:be], data[bs:be])
}
return PKCS7UnPaddingEx(decrypted)
}
func EcbEncrypt(data, key []byte) []byte {
block, _ := aes.NewCipher(key)
data = PKCS7PaddingEx(data, block.BlockSize())
decrypted := make([]byte, len(data))
size := block.BlockSize()
for bs, be := 0, size; bs < len(data); bs, be = bs+size, be+size {
block.Encrypt(decrypted[bs:be], data[bs:be])
}
return decrypted
}
+566
View File
@@ -0,0 +1,566 @@
package crypt
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/hmac"
"crypto/md5"
"crypto/rand"
"crypto/sha1"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"encoding/json"
"errors"
"hash/crc32"
"io"
"net/url"
"os"
"reflect"
"sort"
"strconv"
"strings"
"github.com/dgrijalva/jwt-go"
)
// CreateToken 生成Token算法
func CreateToken(secret string, tokenClaims map[string]interface{}) (tokeness string, err error) {
if secret == "" {
return "", errors.New("secret is empty")
}
claims := jwt.MapClaims(tokenClaims)
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
tokeness, err = token.SignedString([]byte(secret))
if err != nil {
return "", err
}
return tokeness, nil
}
func keyFunc(secret string) jwt.Keyfunc {
return func(token *jwt.Token) (interface{}, error) {
return []byte(secret), nil
}
}
// ParseToken 解析Token
func ParseToken(secret string, tokeness string) (map[string]interface{}, error) {
if secret == "" || tokeness == "" {
return nil, errors.New("secret or tokeness is empty")
}
token, err := jwt.Parse(tokeness, keyFunc(secret))
if err != nil {
return nil, err
}
//验证token,如果token被修改过则为false
if !token.Valid {
return nil, errors.New("token is invalid")
}
claimsToken, ok := token.Claims.(jwt.MapClaims)
if !ok {
return nil, errors.New("cannot convert claim to MapClaim")
}
return claimsToken, nil
}
// StructToStr 结构体转json 先对结构体按照字典顺序排序 并返回字符串
func StructToStr(obj interface{}) (string, error) {
if obj == nil {
return "", errors.New("obj is not nil")
}
data := make(map[string]interface{})
bytes, err := json.Marshal(obj)
if err != nil {
return "", err
}
err = json.Unmarshal(bytes, &data)
if err != nil {
return "", err
}
str, err := MapToStr(data)
if err != nil {
return "", err
}
return str, nil
}
// MapToStr 对map按照字典顺序排序 并返回字符串
func MapToStr(data map[string]interface{}) (string, error) {
if data == nil || len(data) == 0 {
return "", errors.New("data is nil or empty")
}
newData := make(map[string]interface{})
keys := make([]string, 0, len(data))
for k := range data {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
newData[k] = data[k]
}
b, err := json.Marshal(newData)
if err != nil {
return "", err
}
str := string(b)
return str, nil
}
// JSON2Struct json转struct
func JSON2Struct(str string, obj interface{}) {
if err := json.Unmarshal([]byte(str), &obj); err != nil {
return
}
}
// JSONArray2Struct json转struct
func JSONArray2Struct(str []string, obj interface{}) []interface{} {
var data []interface{}
for _, v := range str {
JSON2Struct(v, &obj)
data = append(data, obj)
}
return data
}
// JSON2Map json转map
func JSON2Map(str string) (map[string]interface{}, error) {
var mapResult map[string]interface{}
if err := json.Unmarshal([]byte(str), &mapResult); err != nil {
return nil, err
}
return mapResult, nil
}
// StructToStrNormal 结构体转str
func StructToStrNormal(obj interface{}) (string, error) {
str, err := json.Marshal(obj)
if err != nil {
return "", err
}
return string(str), nil
}
func UrlValueToStr(values url.Values, exclude string) (string, string) {
if values == nil {
return "", ""
}
var buf bytes.Buffer
var sign string
keys := make([]string, 0, len(values))
for k, _ := range values {
if k == exclude {
sign = strings.Join(values[k], "")
continue
}
keys = append(keys, k)
}
sort.Strings(keys)
for i, k := range keys {
if i != 0 {
buf.WriteString("&")
}
buf.WriteString(k)
buf.WriteString("=")
buf.WriteString(strings.Join(values[k], ""))
}
return buf.String(), sign
}
// 对参数进行排序后 拼接成url字符串 只对一级字符串类型的参数做处理
func MapToUrlOnlyStr(data map[string]interface{}) string {
if data == nil || len(data) == 0 {
return ""
}
var buf bytes.Buffer
keys := make([]string, 0, len(data))
for k := range data {
keys = append(keys, k)
}
sort.Strings(keys)
for i, k := range keys {
if i != 0 {
buf.WriteString("&")
}
buf.WriteString(k)
buf.WriteString("=")
if v, ok := data[k].(string); ok {
buf.WriteString(v)
}
}
return buf.String()
}
// MapToStr 对map按照字典顺序排序 并返回字符串url 拼接的字段
func MapToUrlStr(data map[string]interface{}) string {
if data == nil || len(data) == 0 {
return ""
}
var buf bytes.Buffer
keys := make([]string, 0, len(data))
for k := range data {
keys = append(keys, k)
}
sort.Strings(keys)
for i, k := range keys {
if i != 0 {
buf.WriteString("&")
}
buf.WriteString(k)
buf.WriteString("=")
if v, ok := data[k].(string); ok {
buf.WriteString(v)
} else {
t := reflect.TypeOf(data[k])
if t != nil && t.Kind() == reflect.Map {
if v, ok := data[k].(map[string]interface{}); ok {
buf.WriteString("{")
str := MapToUrlStr(v)
buf.WriteString(str)
buf.WriteString("}")
}
}
if t != nil && (t.Kind() == reflect.Slice || t.Kind() == reflect.Array) {
arraybyte, _ := json.Marshal(data[k])
buf.WriteString(string(arraybyte))
}
}
}
return buf.String()
}
// MapToURL 集合转URL
func MapToURL(m map[string]string) string {
if m == nil || len(m) == 0 {
return ""
}
v := url.Values{}
for k := range m {
v.Set(k, string(m[k]))
}
return v.Encode()
}
func Strcut2UrlValue(obj interface{}) (string, error) {
t := reflect.TypeOf(obj)
v := reflect.ValueOf(obj)
if t.Kind() != reflect.Struct {
return "", errors.New("obj must be struct type")
}
u := url.Values{}
for i := 0; i < t.NumField(); i++ {
var value string
tag := t.Field(i).Tag.Get("json")
switch t.Field(i).Type.Kind() {
case reflect.String:
value = v.Field(i).String()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
value = strconv.Itoa(int(v.Field(i).Int()))
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
value = strconv.FormatUint(v.Field(i).Uint(), 10)
case reflect.Slice, reflect.Array:
buf := bytes.Buffer{}
b, _ := json.Marshal(v.Field(i).Interface())
t := strings.TrimRight(strings.TrimLeft(string(b), "["), "]")
array := strings.Split(t, ",")
if len(array) == 0 {
value = ""
}
if len(array) == 1 {
value = array[0]
} else {
for i, v := range array {
if i == 0 {
buf.WriteString(v)
continue
}
buf.WriteString("&")
buf.WriteString(tag)
buf.WriteString("=")
buf.WriteString(v)
value = buf.String()
}
}
}
u.Add(tag, value)
}
dcodeurl, err := url.QueryUnescape(u.Encode())
return dcodeurl, err
}
// StrToMd5 字符串转md5
func StrToMd5(str string) string {
data := []byte(str)
return ByteToMd5(data)
}
// ByteToMd5 数组转md5
func ByteToMd5(data []byte) string {
md5Ctx := md5.New()
md5Ctx.Write(data)
cipherStr := md5Ctx.Sum(nil)
return hex.EncodeToString(cipherStr)
}
// FileToMd5 文件转MD5
func FileToMd5(fileName string) string {
file, err := os.Open(fileName)
if err != nil {
return ""
}
defer file.Close()
md5Ctx := md5.New()
if _, err := io.Copy(md5Ctx, file); err != nil {
return ""
}
cipherStr := md5Ctx.Sum(nil)
return hex.EncodeToString(cipherStr)
}
// HashCode 对一个字符串生成唯一的hasHcode 码
func HashCode(src string) int {
v := int(crc32.ChecksumIEEE([]byte(src)))
if v > 0 {
return v
}
if -v > 0 {
return -v
}
return 0
}
// HexToString converts
func HexToString(data []byte) string {
return hex.EncodeToString(data)
}
// StrToSha256 字符串转sha256
func StrToSha256(str string) string {
shaCtx := sha256.New()
shaCtx.Write([]byte(str))
shaBytes := shaCtx.Sum(nil)
cipherStr := hex.EncodeToString(shaBytes[:])
return cipherStr
}
// StrToSha1 字符串转sha1
func StrToSha1(str string, secret string) []byte {
h := hmac.New(sha1.New, []byte(secret))
h.Write([]byte(str))
return h.Sum(nil)
}
// StrToSha1 字符串转sha1 转16进制后
func StrToHmacSha1(str, secret string) string {
return hex.EncodeToString(StrToSha1(str, secret))
}
// StrToHmacSha256 加密 字符串转sha256加密字符串
func StrToHmacSha256(str string, secret string) string {
h := hmac.New(sha256.New, []byte(secret))
h.Write([]byte(str))
cipherStr := hex.EncodeToString(h.Sum(nil))
return cipherStr
}
func PKCS7Padding(ciphertext []byte, blockSize int) []byte {
padding := blockSize - len(ciphertext)%blockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(ciphertext, padtext...)
}
func PKCS7UnPadding(origData []byte) []byte {
length := len(origData)
unpadding := int(origData[length-1])
return origData[:(length - unpadding)]
}
// AesEncrypt AES加密
func AesEncrypt(origData, key string) ([]byte, error) {
origDataByte := []byte(origData)
return CoreAesEncrypt(origDataByte, key)
}
// AesDecrypt AES解密
func AesDecrypt(crypted, key string) (string, error) {
cryptedByte := []byte(crypted)
return CoreAesDecrypt(cryptedByte, key)
}
// AesDecrypt AES解密
func AesDecryptByte(cryptedByte []byte, key string) (string, error) {
return CoreAesDecrypt(cryptedByte, key)
}
// CoreAesEncrypt AES加密
func CoreAesEncrypt(origData []byte, key string) ([]byte, error) {
keybyte := []byte(key)
block, err := aes.NewCipher(keybyte)
if err != nil {
return nil, err
}
blockSize := block.BlockSize()
origData = PKCS7Padding(origData, blockSize)
blockMode := cipher.NewCBCEncrypter(block, keybyte[:blockSize])
crypted := make([]byte, len(origData))
blockMode.CryptBlocks(crypted, origData)
return crypted, nil
}
// CoreAesDecrypt AES解密
func CoreAesDecrypt(crypted []byte, key string) (string, error) {
keybyte := []byte(key)
block, err := aes.NewCipher(keybyte)
if err != nil {
return "", err
}
blockSize := block.BlockSize()
blockMode := cipher.NewCBCDecrypter(block, keybyte[:blockSize])
origData := make([]byte, len(crypted))
blockMode.CryptBlocks(origData, crypted)
origData = PKCS7UnPadding(origData)
return string(origData), nil
}
// XorEnc
func XorEnc(src string, xorKey string) string {
var result string
j := 0
bt := []rune(src)
xor := []rune(xorKey)
for i := 0; i < len(bt); i++ {
s := strconv.FormatInt(int64(bt[i]^xor[j]), 10)
result = result + s
j = 1 % len(xor)
}
return result
}
// XorDec
func XorDec(src string, xorKey string) string {
var result string
j := 0
bt := []rune(src)
xor := []rune(xorKey)
for i := 0; i < len(bt); i++ {
s := strconv.FormatInt(int64(bt[i]^xor[j]), 10)
result = result + s
j = i % len(xor)
}
return result
}
func XorDecode(src, key string) string {
srcByte, _ := base64.StdEncoding.DecodeString(src)
keyByte, _ := base64.StdEncoding.DecodeString(key)
srcByte = xor(srcByte, keyByte)
return base64.StdEncoding.EncodeToString(srcByte)
}
func XorEncode(src, key string) string {
return XorDecode(src, key)
}
func xor(src []byte, key []byte) []byte {
for i := 0; i < len(src); i++ {
src[i] ^= key[i%len(key)]
}
return src
}
func XORLength(src []byte, key []byte, length int) []byte {
for i := 0; i < length; i++ {
src[i] ^= key[i%len(key)]
}
return src[:length]
}
// https://core.telegram.org/api/end-to-end#sending-and-receiving-messages-in-a-secret-chat
func CoreAesEncryptEx(plain []byte, nonceLen int, key string) ([]byte, error) {
nonce := make([]byte, nonceLen)
_, err := io.ReadFull(rand.Reader, nonce)
if err != nil {
return nil, err
}
var largeShaRaw []byte
largeShaRaw = append(largeShaRaw, []byte(key)...)
largeShaRaw = append(largeShaRaw, nonce...)
largeShaRawMid := len(largeShaRaw) / 2
msgKeyLarge := sha256.Sum256(largeShaRaw)
msgKey := msgKeyLarge[8:24] //16 bytes
var shaRawA []byte
shaRawA = append(shaRawA, msgKey...)
shaRawA = append(shaRawA, largeShaRaw[:largeShaRawMid]...)
sha256a := sha256.Sum256(shaRawA) //32 bytes
var shaRawB []byte
shaRawB = append(shaRawB, largeShaRaw[largeShaRawMid:]...)
shaRawB = append(shaRawB, msgKey...)
sha256b := sha256.Sum256(shaRawB) //32 bytes
var aesKey []byte //32 bytes AES-256
aesKey = append(aesKey, sha256a[:8]...) //a: 8 bytes
aesKey = append(aesKey, sha256b[8:24]...) //b: 16 bytes
aesKey = append(aesKey, sha256a[24:32]...) //a: 8 bytes
var aesIV []byte // 16 bytes
aesIV = append(aesIV, sha256b[:4]...) //b: 4 bytes
aesIV = append(aesIV, sha256a[12:20]...) //a: 8 bytes
aesIV = append(aesIV, sha256b[28:]...) //b: 4 bytes
block, err := aes.NewCipher(aesKey)
if err != nil {
return nil, err
}
blockSize := block.BlockSize()
ciphertext := PKCS7Padding(plain, blockSize)
mode := cipher.NewCBCEncrypter(block, aesIV)
cipher := make([]byte, nonceLen+len(ciphertext))
copy(cipher, nonce)
mode.CryptBlocks(cipher[nonceLen:], ciphertext)
return cipher, nil
}
func CoreAesDecryptEx(crypt []byte, nonceLen int, key string) ([]byte, error) {
nonce := make([]byte, nonceLen)
copy(nonce, crypt)
var largeShaRaw []byte
largeShaRaw = append(largeShaRaw, []byte(key)...)
largeShaRaw = append(largeShaRaw, nonce...)
largeShaRawMid := len(largeShaRaw) / 2
msgKeyLarge := sha256.Sum256(largeShaRaw)
msgKey := msgKeyLarge[8:24] //16 bytes
var shaRawA []byte
shaRawA = append(shaRawA, msgKey...)
shaRawA = append(shaRawA, largeShaRaw[:largeShaRawMid]...)
sha256a := sha256.Sum256(shaRawA) //32 bytes
var shaRawB []byte
shaRawB = append(shaRawB, largeShaRaw[largeShaRawMid:]...)
shaRawB = append(shaRawB, msgKey...)
sha256b := sha256.Sum256(shaRawB) //32 bytes
var aesKey []byte //32 bytes AES-256
aesKey = append(aesKey, sha256a[:8]...) //a: 8 bytes
aesKey = append(aesKey, sha256b[8:24]...) //b: 16 bytes
aesKey = append(aesKey, sha256a[24:32]...) //a: 8 bytes
var aesIV []byte // 16 bytes
aesIV = append(aesIV, sha256b[:4]...) //b: 4 bytes
aesIV = append(aesIV, sha256a[12:20]...) //a: 8 bytes
aesIV = append(aesIV, sha256b[28:]...) //b: 4 bytes
block, err := aes.NewCipher(aesKey)
if err != nil {
return nil, err
}
realData := crypt[nonceLen:]
blockMode := cipher.NewCBCDecrypter(block, aesIV)
origData := make([]byte, len(realData))
blockMode.CryptBlocks(origData, realData)
return PKCS7UnPadding(origData), nil
}
+27
View File
@@ -0,0 +1,27 @@
package crypt
import (
"testing"
"videoweb/pub/common/log"
)
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))
}
+145
View File
@@ -0,0 +1,145 @@
package crypt
import (
"bytes"
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"errors"
"videoweb/pub/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) {
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
}
+126
View File
@@ -0,0 +1,126 @@
package consts
import (
"strings"
"time"
)
type RunMod string
const (
RunModeDev RunMod = "dev"
RunModeTest RunMod = "test"
RunModeProd RunMod = "prod"
RunModRelease RunMod = "release"
)
// gin中ctx使用的key
const (
CtxUserID = "USER_ID"
CtxUpUserID = "UP_USER_ID"
CtxUpInfo = "UP_INFO"
CtxUA = "UA"
CtxUAH5 = "UAH5"
CtxIP = "IP"
CtxDevId = "DEVID"
CtxDevType = "DEVTYPE"
CtxSysType = "SYSTYPE"
CtxVersion = "VERSION"
CtxBuildId = "BUILDID"
CtxToken = "TOKEN"
CtxOnlineTime = "ONLINE_TIME"
)
const (
AppSysType_And = ""
AppSysType_Ios = ""
)
// Etcd 的Key
const (
EtcdWarmupDB = "/micro/config/com.sz.api.server/warmupdb"
EtcdSzDB = "/micro/config/com.sz.api.server/db"
EtcdRds = "/micro/config/com.sz.api.server/redis"
)
const (
AdminJwtTokenExpire = 30 * 24 * time.Hour
UserJwtTokenExpire = 30 * 24 * time.Hour
UserSignInExpire = 60 * 24 * time.Hour
)
const (
UAHeader = "User-Agent"
)
const (
IOS = "ios"
Android = "android"
H5 = "h5"
PC = "pc"
H5IOS = "h5_ios"
H5Android = "h5_android"
H5PC = "h5_pc"
Light = "light"
)
const (
CaptchaLen = 6 //验证码长度
)
const (
TypeIOS = "ios"
TypeAndroid = "and"
TypeWeb = "h5"
)
// 机型
const (
WebTypeIOS = "ios"
WebTypeAndroid = "android"
WebSysTypeH5 = "h5"
)
// func HandleSysType(sysType string) (sys string) {
// if strings.Contains(strings.ToLower(sysType), TypeIOS) {
// sys = IOS
// }
//
// if strings.Contains(strings.ToLower(sysType), TypeAndroid) {
// sys = Android
// }
//
// if strings.Contains(strings.ToLower(sysType), TypeWeb) {
// sys = H5
// }
// return
// }
func ToWebSysType(sysType string) (sys string) {
if strings.Contains(strings.ToLower(sysType), TypeIOS) {
sys = WebTypeIOS
}
if strings.Contains(strings.ToLower(sysType), TypeAndroid) {
sys = WebTypeAndroid
}
if strings.Contains(strings.ToLower(sysType), TypeWeb) {
sys = WebSysTypeH5
}
return
}
func ToNewSysType(sysType string) (sys string) {
if strings.Contains(strings.ToLower(sysType), TypeIOS) {
sys = WebTypeIOS
}
if strings.Contains(strings.ToLower(sysType), TypeAndroid) {
sys = WebTypeAndroid
}
if strings.Contains(strings.ToLower(sysType), TypeWeb) {
sys = WebSysTypeH5
}
return
}
+100
View File
@@ -0,0 +1,100 @@
package redisconst
import (
"fmt"
"strings"
"time"
)
const (
visitLogFmt = "bd_visitlog:%s:%d"
FiveMVisit = "bd_visitFmt:%s"
BloomUser = "bd_bloom_user:%d"
AppUserToken = "bd_app_user_token:%d"
captchaKey = "bd_captcha_mobile_%s"
CaptchaExpire = 5 * time.Minute
replayNonceKey = "bd_replay:nonce:_%s"
mediaM3U8Fmt = "m3u8cache:%s"
MediaM3U8CacheExpire = time.Hour * 24 * 7
//禁止登陆设置
userForbit = "userForbit:%d"
ipForbit = "ipForbit:%s"
//每分钟Ip注册次数
registerLimist = "registerLimit:%s"
RegisterLimistExpire = time.Minute * 30
//用户关注限制
careLimitKey = "carelimit:%d"
CareLimitKeyExpire = 15 * time.Second
//用户评论限制
commentLimitKey = "commentlimit:%s"
CommentLimitKeyExpire = 1 * time.Minute
CommentDayLimitKeyExpire = 10 * time.Hour
//每用户访问次数限制
userCallLimit = "userCall:%d"
ipCallLimit = "ipCall:%s"
CallLimitExpire = 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 MobileCaptchaKey(mobile string) string {
return fmt.Sprintf(captchaKey, mobile)
}
func AppUserTokenKey(userId int64) string {
return fmt.Sprintf(AppUserToken, userId)
}
func MediaM3U8Key(name string) string {
return fmt.Sprintf(mediaM3U8Fmt, name)
}
func UserForbitKey(userId int64) string {
return fmt.Sprintf(userForbit, userId)
}
func RegisterLimitKey(ip string) string {
ip = strings.ReplaceAll(ip, ":", "-")
return fmt.Sprintf(registerLimist, ip)
}
func CareLimitKey(userId int64) string {
return fmt.Sprintf(careLimitKey, userId)
}
func ReplayNonceKey(nonce string) string {
return fmt.Sprintf(replayNonceKey, nonce)
}
func CommentLimitKey(ip string) string {
return fmt.Sprintf(commentLimitKey, ip)
}
func CommentOneDayLimitKey(ip string) string {
return fmt.Sprintf("commentdaylimit:%s", ip)
}
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)
}
+27
View File
@@ -0,0 +1,27 @@
package video
type VideoType int64
type videoStatus int64
// 0-上架、1-未上架
const (
Enable videoStatus = iota
Disable
)
// 1是AV、2是SP
const (
_ VideoType = iota
AV
SP
)
const AVLimit = 1
type recordType int64
const (
_ recordType = iota
Watch
Downlaoad
)
+169
View File
@@ -0,0 +1,169 @@
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 // 评论已存在
LimitIPErr // 限制IP登陆
)
// 代充专用
const (
TakeChatFail CodeType = iota + 18000 //发起代充聊天失败 18000
GetOrderInfoFail //获取代理充值订单信息失败 18001
AppIDNotExist //代充AppID 不存在 18002
TraderNotExist //此商人不存在 18003
TraderBalanceNotEnough //商人余额不足 18004
TraderHasLocked //商人被禁止登陆 18005
TraderSeekScoreFail //商人追分失败 18006
TraderSeekScoreFailDueNotEnough //商人追分失败 18007
)
+39
View File
@@ -0,0 +1,39 @@
package errors
import (
"fmt"
)
// SrvErr 错误定义
type SrvErr struct {
Code CodeType `json:"code"`
Msg string `json:"msg"`
Tips string `json:"tip,omitempty"`
Cause string `json:"-"`
}
// New create an error
func New(code CodeType, msg string, cause string) *SrvErr {
return &SrvErr{
Code: code,
Msg: msg,
Cause: cause,
}
}
// New create an error
func NewTips(code CodeType, tips string, cause string) *SrvErr {
return &SrvErr{
Code: code,
Msg: tips,
Tips: tips,
Cause: cause,
}
}
func (s *SrvErr) Error() string {
if s == nil {
return ""
}
return fmt.Sprintf("code = %d, msg = %s ,case = %v", s.Code, s.Msg, s.Cause)
}
+24
View File
@@ -0,0 +1,24 @@
package errors
import (
"fmt"
"runtime"
)
func MakeWithErr(code CodeType, err error) error {
pc, fn, line, _ := runtime.Caller(1)
cause := fmt.Sprintf("[error] in %s[%s:%d] %v", runtime.FuncForPC(pc).Name(), fn, line, err.Error())
return New(code, err.Error(), cause)
}
func MakeWithMsg(code CodeType, msg string) error {
pc, fn, line, _ := runtime.Caller(1)
cause := fmt.Sprintf("[error] in %s[%s:%d] %v", runtime.FuncForPC(pc).Name(), fn, line, msg)
return New(code, msg, cause)
}
func MakeWithTips(code CodeType, tips string) error {
pc, fn, line, _ := runtime.Caller(1)
cause := fmt.Sprintf("[error] in %s[%s:%d] %v", runtime.FuncForPC(pc).Name(), fn, line, tips)
return NewTips(code, tips, cause)
}
+19
View File
@@ -0,0 +1,19 @@
package errors
// error tips
const (
//基础错误提示
ET_Store = "存储错误"
ET_DBOp = "DB操作错误"
ET_RdsOp = "Rds操作错误"
ET_Param = "参数错误"
ET_DBQury = "DB查询错误"
ET_DBNoResult = "DB查询数据不存在"
ET_TransactionsErr = "事务运行错误"
//----------------------------------------------
ET_UserNoExist = "用户不存在"
ET_BloggerApply = "博主申请条件不满足"
ET_BloggerAlreadyIs = "用户已经是博主"
ET_BalanceNotEnough = "余额不足"
)
+6
View File
@@ -0,0 +1,6 @@
package fconst
const (
FileDir = "file-tmp"
FsDir = "/fs"
)
+26
View File
@@ -0,0 +1,26 @@
package godef
import (
"runtime"
"videoweb/pub/common/log"
"videoweb/pub/common/tgbot"
)
func Go(f func()) {
//atomic.AddInt64(&routineCount, 1)
go func() {
defer func() {
//atomic.AddInt64(&routineCount, -1)
if r := recover(); r != nil {
buf := make([]byte, 2048)
length := runtime.Stack(buf, false)
info := string(buf[:length])
log.Error("cat a unkown excetion", log.Any("excetion :", info), log.Any("e", r))
//发送tg消息
msg := "goroutine crash,the stack:\n" + info
tgbot.SendToProgram("程序异常", msg)
}
}()
f()
}()
}
+34
View File
@@ -0,0 +1,34 @@
package googauth
import (
"github.com/dgryski/dgoogauth"
)
const windowSize = 30
const hotpWindowSize = 5
var HotpCfg = dgoogauth.OTPConfig{
Secret: "MRSHM6LTMFYHAMJSGM2DKNQ=",
HotpCounter: 1,
WindowSize: hotpWindowSize,
}
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 VerifyHotp(cfg *dgoogauth.OTPConfig, authCode string, counter int) (bool, error) {
return cfg.Authenticate(authCode)
}
+67
View File
@@ -0,0 +1,67 @@
package googauth
import (
"crypto/hmac"
"crypto/sha1"
"encoding/binary"
"strconv"
)
// HOTP implementation
type HOTP struct {
secret []byte
digits int
}
// At generate code
func (h HOTP) At(counter uint64) string {
counterBytes := make([]byte, 8)
binary.BigEndian.PutUint64(counterBytes, counter)
hash := hmac.New(sha1.New, h.secret)
hash.Write(counterBytes)
hs := hash.Sum(nil)
offset := hs[19] & 0x0f
binCodeBytes := make([]byte, 4)
binCodeBytes[0] = hs[offset] & 0x7f
binCodeBytes[1] = hs[offset+1] & 0xff
binCodeBytes[2] = hs[offset+2] & 0xff
binCodeBytes[3] = hs[offset+3] & 0xff
binCode := binary.BigEndian.Uint32(binCodeBytes)
mod := uint32(1)
for i := 0; i < h.digits; i++ {
mod *= 10
}
code := binCode % mod
codeString := strconv.FormatUint(uint64(code), 10)
if len(codeString) < h.digits {
paddingByteLength := h.digits - len(codeString)
paddingBytes := make([]byte, paddingByteLength)
for i := 0; i < paddingByteLength; i++ {
paddingBytes[i] = '0'
}
codeString = string(paddingBytes) + codeString
}
return codeString
}
// Verify verify OTP code
func (h HOTP) VerifyCode(code string, counter uint64) bool {
realCode := h.At(counter)
if realCode == code {
return true
}
return false
}
// Verify verify OTP code
func (h HOTP) GenerateCode(counter uint64) string {
return h.At(counter)
}
// NewHOTP generate new HOTP instance
func NewHOTP(secret []byte, digits int) (h *HOTP) {
h = new(HOTP)
h.secret = secret
h.digits = digits
return
}
+27
View File
@@ -0,0 +1,27 @@
package http
import "strings"
// ApplyClientForwardHeaders 向上游透传客户端 IP 与浏览器 UA。
func ApplyClientForwardHeaders(header map[string]string, clientIP, userAgent string) {
if header == nil {
return
}
if ip := strings.TrimSpace(clientIP); ip != "" {
header["X-Real-IP"] = ip
}
if ua := strings.TrimSpace(userAgent); ua != "" {
header["User-Agent"] = ua
}
}
// ApplyClientForwardHeadersWithXUA 透传 IP、浏览器 UA,以及可选的 X-User-Agent。
func ApplyClientForwardHeadersWithXUA(header map[string]string, clientIP, userAgent, xUserAgent string) {
ApplyClientForwardHeaders(header, clientIP, userAgent)
if header == nil {
return
}
if xua := strings.TrimSpace(xUserAgent); xua != "" {
header["X-User-Agent"] = xua
}
}
+24
View File
@@ -0,0 +1,24 @@
package http
// ForwardExcludedHeaders 是本服务转发请求到上游 serverAddr 时必须剔除的请求头集合。
// key 使用 net/http.Header 的规范形式 (textproto.CanonicalMIMEHeaderKey),
// 调用方可直接用 http.Header 的 key 做查找。
//
// 包含两类:
// 1. RFC 7230 §6.1 定义的 hop-by-hop 头,不允许跨代理转发。
// 2. 本进程内部注入的控制标志,只对本进程的中间件有意义,不能让上游看见。
var ForwardExcludedHeaders = map[string]bool{
// hop-by-hop
"Connection": true,
"Keep-Alive": true,
"Proxy-Authenticate": true,
"Proxy-Authorization": true,
"Te": true,
"Trailers": true,
"Transfer-Encoding": true,
"Upgrade": true,
// 内部 DoS 防护标志,由 server.headToGet 注入,
// 仅供 templatecache / filecache 判断是否回填缓存。
"X-Origin-Head": true,
}
+242
View File
@@ -0,0 +1,242 @@
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 ResponseBody2 struct {
StatusCode int
Data []byte
Header http.Header
}
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
}
func PostJson(url string, body io.Reader) (fr *ResponseBody, err error) {
header := map[string]string{}
header["Content-Type"] = "application/json"
fr, err = do(http.MethodPost, url, header, body, int64(1024*KB))
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
}
func DoHost(method string, url string, header map[string]string, body io.Reader, host string) (fr *ResponseBody2, err error) {
fr, err = doHost(method, url, header, body, int64(2*MB), host)
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 doHost(method string, url string, header map[string]string, body io.Reader, size int64, host string) (fr *ResponseBody2, err error) {
req, err := http.NewRequest(method, url, body)
if err != nil {
return
}
for k, v := range header {
req.Header.Set(k, v)
}
req.Host = host
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 = &ResponseBody2{
StatusCode: resp.StatusCode,
Data: data,
Header: resp.Header,
}
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
}
func GetWithBody(url string, header map[string]string, body io.Reader) (fr *ResponseBody, err error) {
fr, err = do(http.MethodGet, url, header, body, int64(20*MB))
return
}
+16
View File
@@ -0,0 +1,16 @@
package http
import "fmt"
func ExampleGet() {
urlstr := "https://palce.hzbeisheng.com/api/sms"
data, err := Get(urlstr, nil)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(data.Data))
// Output:
}
+321
View File
@@ -0,0 +1,321 @@
package httputil
import (
"context"
"encoding/json"
"fmt"
"io"
"net/url"
"strconv"
"videoweb/pub/common/log"
"github.com/ddliu/go-httpclient"
)
// HTTPClient htttp客户端
type HTTPClient struct {
client *httpclient.HttpClient
}
type ProxyCfg struct {
Host string
Timeout int //秒
Source string
}
func (p *ProxyCfg) Build(rawurl string, rawHeaders map[string]string) (string, map[string]string, error) {
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: 10,
httpclient.OPT_TIMEOUT: 120,
})
return &HTTPClient{client: h}
}
func NewCtx(ctx context.Context) *HTTPClient {
h := httpclient.NewHttpClient().WithOptions(httpclient.Map{
httpclient.OPT_CONNECTTIMEOUT: 10,
httpclient.OPT_TIMEOUT: 10,
httpclient.OPT_CONTEXT: ctx,
})
return &HTTPClient{client: h.Begin()}
}
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
}
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...)
}
// Get Get方法
func (h *HTTPClient) Delete(url string, headers map[string]string, params ...interface{}) (*HTTPResponse, error) {
h.client.WithHeaders(headers)
response, err := h.client.Delete(url, params...)
if err != nil {
log.Error(fmt.Sprintf("http client Delete method failed %+v:", err))
return nil, err
}
return &HTTPResponse{StatusCode: response.StatusCode, Body: response}, nil
}
// DeleteWithJResp 结果json.Unmarshal到bind中
func (h *HTTPClient) DeleteWithJResp(bind interface{}, url string, headers map[string]string, params ...interface{}) (int, error) {
resp, err := h.Delete(url, headers, params...)
if err != nil {
return 0, err
}
err = jsonUnmarshalResp(bind, resp)
return resp.StatusCode, err
}
// 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
}
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
}
err = jsonUnmarshalResp(bind, resp)
return resp.StatusCode, err
}
+105
View File
@@ -0,0 +1,105 @@
package httputil
import (
"encoding/json"
"fmt"
"os"
"strconv"
"testing"
)
type ResponseInfo struct {
Gzipped bool `json:"gzipped"`
Method string `json:"method"`
Origin string `json:"origin"`
Useragent string `json:"user-agent"` // http://httpbin.org/user-agent
Form map[string]string `json:"form"`
Files map[string]string `json:"files"`
Headers map[string]string `json:"headers"`
Cookies map[string]string `json:"cookies"`
}
var info ResponseInfo
type Resport struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data interface{} `json:"data"`
}
func Test_Get_Httpcli(t *testing.T) {
client := Client()
var headers = map[string]string{"auth_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVjZTJiNzNmZTI3Y2YyMmE4ZjQyMTVkMCIsInVzZXJOYW1lIjoidHVib2xpbmcxNjgiLCJpc0xvZ2luIjp0cnVlLCJsb2dpblRpbWUiOjE1NjQxNDYwNzI5MDUsImlhdCI6MTU2NDE0NjA3Mn0.VQICBk71KDITus83pZEq3C1y_WbKPZc0HUfmIm_eby8"}
respo, err := client.Get("https://no5.xiexiaojia.com/appjmall/api/showCore/appShowList", headers)
defer respo.Body.Body.Close()
if err != nil {
fmt.Printf("%+v", err)
}
resport := Resport{}
body, err := respo.Body.ReadAll()
//反序列化
err = json.Unmarshal(body, &resport)
fmt.Println("response", resport)
//案例获取数据
fmt.Println("response comName:", resport.Data.([]interface{})[0].(map[string]interface{})["comName"])
}
func Test_Post_Httpcli(t *testing.T) {
client := Client()
fmt.Println("http 客户端", client)
var headers = map[string]string{"auth_token": "xxxxx"}
var params = map[string]string{"userId": "5ce2b73fe27cf22a8f4215d0", "price": "5000", "count": "1", "commId": "5ce40e03e27cf22a8f4215d8",
"comName": "日本ceruru.b 脐带血面膜保湿补水修复5片",
"comImageCar": "http://oam.szbslhb.com/4,01fb06b4adf1.jpg",
"attributeCar": "",
}
respo, err := client.Post("https://no5.xiexiaojia.com/appjmall/api/cart/addCommToCart", headers, params)
defer respo.Body.Body.Close()
if err != nil {
fmt.Printf("%+v", err)
}
resport := Resport{}
body, err := respo.Body.ReadAll()
if err != nil {
res, _ := respo.Body.ToString()
fmt.Println("response", res)
return
}
//反序列化
err = json.Unmarshal(body, &resport)
fmt.Println("response", resport)
}
func Test_PostJson_Httpcli(t *testing.T) {
client := Client()
var headers = map[string]string{"Content-Type": "application/json"}
fmt.Println("http 客户端", client)
respo, err := client.Get("https://www.kancloud.cn/cattong/go_command_tutorial/261347", headers)
defer respo.Body.Body.Close()
if err != nil {
fmt.Printf("%+v", err)
}
body, err := respo.Body.ToString()
fmt.Println("response", body)
}
func Test_PutFile_Httpcli(t *testing.T) {
client := Client()
file, err := os.Open("/Users/mac/Downloads/1567069549ABBQCTFC.mp4")
info, _ := file.Stat()
var headers = map[string]string{
"Content-Type": "application/octet-stream",
"Authorization": "bucketName=bucket-01;accessKey=FSd58ed59fccc16812903c37068de8969b;ttl=1h",
"Content-Disposition": "fileName=test02.mp4;size=" + strconv.FormatInt(info.Size(), 10),
"Signature": "9cea21bf95f235e7d9c5a70888cf742cfb9c946ed25aef8b70ce77c953ccbff0",
}
fmt.Println("http 客户端", client)
respo, err := client.Put("http://localhost:8888/fs/api/uploadStream", headers, file)
defer respo.Body.Body.Close()
if err != nil {
fmt.Printf("%+v", err)
}
body, err := respo.Body.ToString()
fmt.Println("response", body)
}
+212
View File
@@ -0,0 +1,212 @@
package linode
import (
"bytes"
"errors"
"io"
"mime"
"os"
"path"
"path/filepath"
"videoweb/pub/common/fconst"
"videoweb/pub/common/log"
"strings"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/aws/aws-sdk-go/service/s3/s3manager"
"github.com/karrick/godirwalk"
"github.com/xxjwxc/gowp/workpool"
)
const (
//region = "default"
//endpoint = "https://ap-south-1.linodeobjects.com"
//accessKey = "0RALF74H45ANVYPRQA1Y"
//secretKey = "S1yCKUqxFJHqlFPZXJ3BbD8T6P0Yz6loUDntUrsS"
//bucket = "fengshang"
M = 1024 * 1024
G = M * 1024
defaultConcurrency = 150
defaultTryMax = 5
defaultContentType = "application/octet-stream"
)
type Client struct {
session *session.Session
cfg Config
}
type UploadReq struct {
Key string
ContentType string
Data []byte
}
func (u *UploadReq) Valid() bool {
return u.Key != "" && len(u.Data) != 0
}
type Config struct {
Region string
Endpoint string
AccessKey string
SecretKey string
Bucket string
Concurrency int
TryMax int
}
func traverseDir(d string) ([]string, error) {
var fileNames []string
fn := func(name string, entry *godirwalk.Dirent) error {
if !entry.IsRegular() {
return nil
}
fileNames = append(fileNames, name)
return nil
}
pathName := d
err := godirwalk.Walk(pathName, &godirwalk.Options{
Unsorted: true,
Callback: fn,
})
if err != nil {
log.Info("list files failed", log.Any("path", pathName), log.E(err))
return nil, err
}
return fileNames, nil
}
func New(cfg Config) (*Client, error) {
if cfg.TryMax <= 0 {
cfg.TryMax = defaultTryMax
}
if cfg.Concurrency <= 0 {
cfg.Concurrency = defaultConcurrency
}
s, err := session.NewSession(&aws.Config{
Region: aws.String(cfg.Region),
Endpoint: aws.String(cfg.Endpoint),
Credentials: credentials.NewStaticCredentials(cfg.AccessKey, cfg.SecretKey, ""),
})
if err != nil {
log.Error("Linode NewSession err", log.E(err))
return nil, err
}
return &Client{session: s, cfg: cfg}, nil
}
//func WithDefault() (*Client, error) {
// return New(&Config{Concurrency: defaultConcurrency, TryMax: defaultTryMax})
//}
func (c *Client) UploadFolder(folderName string) error {
absFiles, err := traverseDir(folderName)
if err != nil {
return err
}
if len(absFiles) == 0 {
return nil
}
wp := workpool.New(c.cfg.Concurrency)
for _, absFilename := range absFiles {
var absName = absFilename
wp.Do(func() error {
var err error
for i := 0; i < c.cfg.TryMax; i++ {
start := time.Now()
err = c.uploadToLinNode(absName)
diff := time.Now().Sub(start)
if diff < time.Second {
time.Sleep(time.Second - diff)
}
if err == nil {
break
}
}
return err
})
}
return wp.Wait()
}
func (c *Client) UploadRaw(key, contentType string, body io.Reader) (string, error) {
if contentType == "" {
contentType = defaultContentType
}
input := &s3manager.UploadInput{
Bucket: aws.String(c.cfg.Bucket),
Key: aws.String(key),
ACL: aws.String("public-read"),
Body: body,
ContentType: aws.String(contentType),
}
result, err := s3manager.NewUploader(c.session, func(uploader *s3manager.Uploader) {
uploader.PartSize = 1 * G
}).Upload(input)
if err != nil {
log.Info("UpdateRaw Upload Err", log.Any("key", key), log.E(err))
return "", err
}
log.Info("UpdateRaw Upload OK", log.Any("key", key), log.Any("Location", result.Location))
return "", nil
}
func (c *Client) Upload(req UploadReq) error {
if !req.Valid() {
return errors.New("invalid upload request")
}
if req.ContentType == "" {
req.ContentType = defaultContentType
ext := path.Ext(req.Key)
if ext != "" {
req.ContentType = mime.TypeByExtension(ext)
}
}
var err error
for i := 0; i < defaultTryMax; i++ {
buf := bytes.NewReader(req.Data)
input := &s3.PutObjectInput{
Bucket: aws.String(c.cfg.Bucket),
Key: aws.String(req.Key),
ACL: aws.String("public-read"),
Body: buf,
ContentType: aws.String(req.ContentType),
}
_, err := s3.New(c.session).PutObject(input)
if err == nil {
log.Info("Linode Upload OK", log.Any("key", req.Key))
return nil
}
time.Sleep(time.Millisecond) //出让CPU
log.Error("Linode Upload Try err", log.Any("key", req.Key), log.Any("tryCount", i+1), log.E(err))
}
log.Error("Linode Upload err", log.Any("key", req.Key), log.E(err))
return err
}
// 多线程调用
func (m *Client) uploadToLinNodeWithKey(fileName, key string) error {
f, err := os.OpenFile(fileName, os.O_RDONLY, 0644)
if err != nil {
log.Info("uploadToLinNodeWithKey OpenFile", log.Any("fileName", fileName), log.E(err))
return err
}
defer func() { _ = f.Close() }()
ext := filepath.Ext(fileName)
contentType := ""
if ext != "" {
contentType = mime.TypeByExtension(ext)
}
_, err = m.UploadRaw(key, contentType, f)
return err
}
func (m *Client) uploadToLinNode(absFilename string) error {
key := strings.TrimPrefix(absFilename, fconst.FsDir)
return m.uploadToLinNodeWithKey(absFilename, key)
}
+28
View File
@@ -0,0 +1,28 @@
package linode
import (
"errors"
"fmt"
"github.com/xxjwxc/gowp/workpool"
"testing"
)
func TestAA(t *testing.T) {
wp := workpool.New(100)
for i := 0; i < 200000; i++ {
var t = i
wp.Do(func() error {
fmt.Println(t)
if t == 22 {
return errors.New("111")
}
return nil
})
}
err := wp.Wait()
if err != nil {
fmt.Println("fail", err)
} else {
fmt.Println("OK")
}
}
+86
View File
@@ -0,0 +1,86 @@
package lock
import (
"sync"
)
type IIdent interface {
GetID() int64
}
type Cache[T IIdent] struct {
dict map[int64]T
lock sync.RWMutex
}
func NewCache[T IIdent]() *Cache[T] {
return &Cache[T]{
dict: make(map[int64]T),
}
}
func (c *Cache[T]) Get(Id int64) (T, bool) {
c.lock.RLock()
defer c.lock.RUnlock()
t, ok := c.dict[Id]
return t, ok
}
func (c *Cache[T]) Keys() []int64 {
c.lock.RLock()
defer c.lock.RUnlock()
keys := make([]int64, 0, len(c.dict))
for k := range c.dict {
keys = append(keys, k)
}
return keys
}
func (c *Cache[T]) Set(items ...T) {
c.lock.Lock()
defer c.lock.Unlock()
for _, item := range items {
c.dict[item.GetID()] = item
}
}
func (c *Cache[T]) Del(items ...T) {
c.lock.Lock()
defer c.lock.Unlock()
for _, item := range items {
delete(c.dict, item.GetID())
}
}
func (c *Cache[T]) Update(dict map[int64]T) {
c.lock.Lock()
defer c.lock.Unlock()
c.dict = dict
}
func (c *Cache[T]) SetItems(items []T) {
c.lock.Lock()
defer c.lock.Unlock()
for _, item := range items {
c.dict[item.GetID()] = item
}
}
func (c *Cache[T]) Has(Id int64) bool {
c.lock.RLock()
defer c.lock.RUnlock()
_, ok := c.dict[Id]
return ok
}
func (c *Cache[T]) GetItems(Ids []int64) []T {
ret := make([]T, 0)
c.lock.RLock()
defer c.lock.RUnlock()
for _, id := range Ids {
if item, ok := c.dict[id]; ok {
ret = append(ret, item)
}
}
return ret
}
+143
View File
@@ -0,0 +1,143 @@
package lock
import (
"sync"
)
type Dict[K comparable, V any] struct {
dict map[K]V
lock sync.RWMutex
}
func NewDict[K comparable, V any]() *Dict[K, V] {
return &Dict[K, V]{
dict: make(map[K]V),
}
}
// Count get dict key count
func (d *Dict[K, V]) Count() int {
d.lock.RLock()
defer d.lock.RUnlock()
return len(d.dict)
}
// Keys get dict keys
func (d *Dict[K, V]) Keys() []K {
d.lock.RLock()
defer d.lock.RUnlock()
keys := make([]K, 0, len(d.dict))
for k := range d.dict {
keys = append(keys, k)
}
return keys
}
// Set set k v to dict, overwrite old value if exists
func (d *Dict[K, V]) Set(key K, value V) {
d.lock.Lock()
defer d.lock.Unlock()
d.dict[key] = value
}
// Has if exists, return true
func (d *Dict[K, V]) Has(key K) bool {
d.lock.RLock()
defer d.lock.RUnlock()
_, exists := d.dict[key]
return exists
}
// Get if exists, return value and true
func (d *Dict[K, V]) Get(key K) (V, bool) {
d.lock.RLock()
defer d.lock.RUnlock()
value, exists := d.dict[key]
return value, exists
}
// Del if exists and del success, return true
func (d *Dict[K, V]) Del(key K) bool {
d.lock.Lock()
defer d.lock.Unlock()
_, exists := d.dict[key]
delete(d.dict, key)
return exists
}
// Remove if exists and remove success, return old value and true
func (d *Dict[K, V]) Remove(key K) (V, bool) {
d.lock.Lock()
defer d.lock.Unlock()
value, exists := d.dict[key]
delete(d.dict, key)
return value, exists
}
// SetIfNotExists if not exists and set success, return true
func (d *Dict[K, V]) SetIfNotExists(key K, value V) bool {
d.lock.Lock()
defer d.lock.Unlock()
_, exists := d.dict[key]
if !exists {
d.dict[key] = value
return true
}
return false
}
// GetOrSetIfNotExists if key exists return old:true, or, set new value, and return new:false
func (d *Dict[K, V]) GetOrSetIfNotExists(key K, new V) (V, bool) {
d.lock.Lock()
defer d.lock.Unlock()
old, exists := d.dict[key]
if !exists {
d.dict[key] = new
return new, false
}
return old, true
}
// Travel travels all dict keys and values
func (d *Dict[K, V]) Travel(travel func(key K, value V) bool) {
d.lock.RLock()
defer d.lock.RUnlock()
for key, value := range d.dict {
cont := travel(key, value)
if !cont {
break
}
}
}
// Clear dict directly
func (d *Dict[K, V]) Clear() {
d.lock.Lock()
defer d.lock.Unlock()
d.dict = map[K]V{}
}
// Update replace dict directly
func (d *Dict[K, V]) Update(dict map[K]V) {
d.lock.Lock()
defer d.lock.Unlock()
d.dict = dict
}
// Replace replace dict directly and return old dict
func (d *Dict[K, V]) Replace(dict map[K]V) map[K]V {
d.lock.Lock()
defer d.lock.Unlock()
old := d.dict
d.dict = dict
return old
}
// Append dict directly
func (d *Dict[K, V]) Append(dict map[K]V) {
d.lock.Lock()
defer d.lock.Unlock()
for k, v := range dict {
d.dict[k] = v
}
}
+75
View File
@@ -0,0 +1,75 @@
package lock
import "sync"
// Queue 定义一个泛型队列结构
type Queue[T any] struct {
items []T
lock sync.RWMutex // 添加读写锁
}
// NewQueue 创建一个新的队列
func NewQueue[T any]() *Queue[T] {
return &Queue[T]{
items: make([]T, 0),
}
}
// Enqueue 入队操作 - 将元素添加到队列末尾
func (q *Queue[T]) Enqueue(item T) {
q.lock.Lock() // 写操作需要写锁
defer q.lock.Unlock()
q.items = append(q.items, item)
}
// Dequeue 出队操作 - 从队列头部移除并返回元素
func (q *Queue[T]) Dequeue() (T, bool) {
q.lock.Lock() // 写操作需要写锁
defer q.lock.Unlock()
var zero T
if len(q.items) == 0 {
return zero, false
}
item := q.items[0]
q.items = q.items[1:]
return item, true
}
// Peek 查看队列头部元素但不移除
func (q *Queue[T]) Peek() (T, bool) {
q.lock.RLock() // 读操作使用读锁
defer q.lock.RUnlock()
var zero T
if len(q.items) == 0 {
return zero, false
}
return q.items[0], true
}
// IsEmpty 检查队列是否为空
func (q *Queue[T]) IsEmpty() bool {
q.lock.RLock() // 读操作使用读锁
defer q.lock.RUnlock()
return len(q.items) == 0
}
// Size 返回队列中的元素个数
func (q *Queue[T]) Size() int {
q.lock.RLock() // 读操作使用读锁
defer q.lock.RUnlock()
return len(q.items)
}
// Clear 清空队列
func (q *Queue[T]) Clear() {
q.lock.Lock() // 写操作需要写锁
defer q.lock.Unlock()
q.items = make([]T, 0)
}
+135
View File
@@ -0,0 +1,135 @@
package log
import (
"context"
"fmt"
"log"
"os"
"time"
"videoweb/app/appg"
"videoweb/pub/common/log/zapcorex"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
var (
zapLogger *zap.Logger
zapLoggerS *zap.SugaredLogger
ZapLog *zap.Logger
ZapLogS *zap.SugaredLogger
Debug = zapLogger.Debug
Info = zapLogger.Info
Warn = zapLogger.Warn
Error = zapLogger.Error
Panic = zapLogger.Panic
Fatal = zapLogger.Fatal
Debugf = zapLoggerS.Debugf
Infof = zapLoggerS.Infof
Warnf = zapLoggerS.Warnf
Errorf = zapLoggerS.Errorf
Fatalf = zapLoggerS.Fatalf
Debugw = zapLoggerS.Debugw
Infow = zapLoggerS.Infow
Warnw = zapLoggerS.Warnw
Errorw = zapLoggerS.Errorw
Fatalw = zapLoggerS.Fatalw
TestLog = func(msg string, fields ...Field) {
if appg.Conf.RunMod == "prod" {
return
}
zapLogger.Info(msg, fields...)
}
)
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",
FunctionKey: "F",
LineEnding: zapcore.DefaultLineEnding,
EncodeLevel: zapcore.CapitalColorLevelEncoder,
EncodeTime: timeEncoder,
EncodeDuration: zapcore.StringDurationEncoder,
EncodeCaller: zapcore.ShortCallerEncoder,
ConsoleSeparator: " ",
}
}
func init() {
core := zapcore.NewCore(
zapcorex.NewConsoleEncoder(newEncoderConfig()),
zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout)),
zap.DebugLevel,
)
zapLogger = zap.New(core, zap.AddCaller())
zapLoggerS = zapLogger.Sugar()
ZapLog = zapLogger
ZapLogS = zapLoggerS
Debug = zapLogger.Debug
Info = zapLogger.Info
Warn = zapLogger.Warn
Error = zapLogger.Error
Panic = zapLogger.Panic
Fatal = zapLogger.Fatal
Debugf = zapLoggerS.Debugf
Infof = zapLoggerS.Infof
Warnf = zapLoggerS.Warnf
Errorf = zapLoggerS.Errorf
Fatalf = zapLoggerS.Fatalf
Debugw = zapLoggerS.Debugw
Infow = zapLoggerS.Infow
Warnw = zapLoggerS.Warnw
Errorw = zapLoggerS.Errorw
Fatalw = zapLoggerS.Fatalw
}
func timeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
enc.AppendString(t.In(time.Local).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)
}
type contextKey int64
var (
loggerContextKey contextKey
)
func ContextWithLogger(ctx context.Context, l *zap.SugaredLogger) context.Context {
return context.WithValue(ctx, loggerContextKey, l)
}
func LoggerFromContext(ctx context.Context) *zap.SugaredLogger {
v := ctx.Value(loggerContextKey)
if v == nil {
return ZapLogS
}
return v.(*zap.SugaredLogger)
}
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,31 @@
// Copyright (c) 2016 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// Package bufferpool houses zap's shared internal buffer pool. Third-party
// packages can recreate the same functionality with buffers.NewPool.
package bufferpool
import "go.uber.org/zap/buffer"
var (
_pool = buffer.NewPool()
// Get retrieves a buffer from the pool, creating one if necessary.
Get = _pool.Get
)
+44
View File
@@ -0,0 +1,44 @@
// Copyright (c) 2016 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// Package color adds coloring functionality for TTY output.
package zapcorex
import "fmt"
// Foreground colors.
const (
Black Color = iota + 30
Red
Green
Yellow
Blue
Magenta
Cyan
White
)
// Color represents a text color.
type Color uint8
// Add adds the coloring to the given string.
func (c Color) Add(s string) string {
return fmt.Sprintf("\x1b[%dm%s\x1b[0m", uint8(c), s)
}
+150
View File
@@ -0,0 +1,150 @@
package zapcorex
import (
"fmt"
"strings"
"sync"
"videoweb/pub/common/log/zapcorex/bufferpool"
"go.uber.org/zap/buffer"
"go.uber.org/zap/zapcore"
)
var _sliceEncoderPool = sync.Pool{
New: func() interface{} {
return &sliceArrayEncoder{elems: make([]interface{}, 0, 2)}
},
}
func getSliceEncoder() *sliceArrayEncoder {
return _sliceEncoderPool.Get().(*sliceArrayEncoder)
}
func putSliceEncoder(e *sliceArrayEncoder) {
e.elems = e.elems[:0]
_sliceEncoderPool.Put(e)
}
type consoleEncoder struct {
*jsonEncoder
}
// NewConsoleEncoder creates an encoder whose output is designed for human -
// rather than machine - consumption. It serializes the core log entry data
// (message, level, timestamp, etc.) in a plain-text format and leaves the
// structured context as JSON.
//
// Note that although the console encoder doesn't use the keys specified in the
// encoder configuration, it will omit any element whose key is set to the empty
// string.
func NewConsoleEncoder(cfg zapcore.EncoderConfig) zapcore.Encoder {
if cfg.ConsoleSeparator == "" {
// Use a default delimiter of '\t' for backwards compatibility
cfg.ConsoleSeparator = "\t"
}
return consoleEncoder{newJSONEncoder(cfg, true)}
}
func (c consoleEncoder) Clone() zapcore.Encoder {
return consoleEncoder{c.jsonEncoder.Clone().(*jsonEncoder)}
}
func (c consoleEncoder) EncodeEntry(ent zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) {
line := bufferpool.Get()
// We don't want the entry's metadata to be quoted and escaped (if it's
// encoded as strings), which means that we can't use the JSON encoder. The
// simplest option is to use the memory encoder and fmt.Fprint.
//
// If this ever becomes a performance bottleneck, we can implement
// ArrayEncoder for our plain-text format.
arr := getSliceEncoder()
if c.TimeKey != "" && c.EncodeTime != nil {
c.EncodeTime(ent.Time, arr)
}
if c.LevelKey != "" && c.EncodeLevel != nil {
c.EncodeLevel(ent.Level, arr)
}
if ent.LoggerName != "" && c.NameKey != "" {
nameEncoder := c.EncodeName
if nameEncoder == nil {
// Fall back to FullNameEncoder for backward compatibility.
nameEncoder = zapcore.FullNameEncoder
}
nameEncoder(ent.LoggerName, arr)
}
for i := range arr.elems {
if i > 0 {
line.AppendString(c.ConsoleSeparator)
}
fmt.Fprint(line, arr.elems[i])
}
putSliceEncoder(arr)
// Add the message itself.
if c.MessageKey != "" {
c.addSeparatorIfNecessary(line)
line.AppendString(ent.Message)
}
// Add any structured context.
c.writeContext(line, fields)
if ent.Caller.Defined {
if c.FunctionKey != "" {
c.addSeparatorIfNecessary(line)
if idx := strings.LastIndex(ent.Caller.Function, "/"); idx > 0 {
line.AppendString(White.Add(ent.Caller.Function[idx+1:]))
} else {
line.AppendString(White.Add(ent.Caller.Function))
}
}
line.AppendString(c.ConsoleSeparator)
if c.CallerKey != "" && c.EncodeCaller != nil {
line.AppendString(White.Add(ent.Caller.TrimmedPath()))
}
}
// If there's no stacktrace key, honor that; this allows users to force
// single-line output.
if ent.Stack != "" && c.StacktraceKey != "" {
line.AppendByte('\n')
line.AppendString(ent.Stack)
}
line.AppendString(c.LineEnding)
return line, nil
}
func (c consoleEncoder) writeContext(line *buffer.Buffer, extra []zapcore.Field) {
context := c.jsonEncoder.Clone().(*jsonEncoder)
defer func() {
// putJSONEncoder assumes the buffer is still used, but we write out the buffer so
// we can free it.
context.buf.Free()
putJSONEncoder(context)
}()
addFields(context, extra)
context.closeOpenNamespaces()
if context.buf.Len() == 0 {
return
}
c.addSeparatorIfNecessary(line)
line.AppendByte('{')
line.Write(context.buf.Bytes())
line.AppendByte('}')
}
func (c consoleEncoder) addSeparatorIfNecessary(line *buffer.Buffer) {
if line.Len() > 0 {
line.AppendString(c.ConsoleSeparator)
}
}
+9
View File
@@ -0,0 +1,9 @@
package zapcorex
import "go.uber.org/zap/zapcore"
func addFields(enc zapcore.ObjectEncoder, fields []zapcore.Field) {
for i := range fields {
fields[i].AddTo(enc)
}
}
+518
View File
@@ -0,0 +1,518 @@
package zapcorex
import (
"encoding/base64"
"encoding/json"
"math"
"sync"
"time"
"unicode/utf8"
"videoweb/pub/common/log/zapcorex/bufferpool"
"go.uber.org/zap/buffer"
"go.uber.org/zap/zapcore"
)
// For JSON-escaping; see jsonEncoder.safeAddString below.
const _hex = "0123456789abcdef"
var _jsonPool = sync.Pool{New: func() interface{} {
return &jsonEncoder{}
}}
func getJSONEncoder() *jsonEncoder {
return _jsonPool.Get().(*jsonEncoder)
}
func putJSONEncoder(enc *jsonEncoder) {
if enc.reflectBuf != nil {
enc.reflectBuf.Free()
}
enc.EncoderConfig = nil
enc.buf = nil
enc.spaced = false
enc.openNamespaces = 0
enc.reflectBuf = nil
enc.reflectEnc = nil
_jsonPool.Put(enc)
}
type jsonEncoder struct {
*zapcore.EncoderConfig
buf *buffer.Buffer
spaced bool // include spaces after colons and commas
openNamespaces int
// for encoding generic values by reflection
reflectBuf *buffer.Buffer
reflectEnc *json.Encoder
}
// NewJSONEncoder creates a fast, low-allocation JSON encoder. The encoder
// appropriately escapes all field keys and values.
//
// Note that the encoder doesn't deduplicate keys, so it's possible to produce
// a message like
//
// {"foo":"bar","foo":"baz"}
//
// This is permitted by the JSON specification, but not encouraged. Many
// libraries will ignore duplicate key-value pairs (typically keeping the last
// pair) when unmarshaling, but users should attempt to avoid adding duplicate
// keys.
func NewJSONEncoder(cfg zapcore.EncoderConfig) zapcore.Encoder {
return newJSONEncoder(cfg, false)
}
func newJSONEncoder(cfg zapcore.EncoderConfig, spaced bool) *jsonEncoder {
return &jsonEncoder{
EncoderConfig: &cfg,
buf: bufferpool.Get(),
spaced: spaced,
}
}
func (enc *jsonEncoder) AddArray(key string, arr zapcore.ArrayMarshaler) error {
enc.addKey(key)
return enc.AppendArray(arr)
}
func (enc *jsonEncoder) AddObject(key string, obj zapcore.ObjectMarshaler) error {
enc.addKey(key)
return enc.AppendObject(obj)
}
func (enc *jsonEncoder) AddBinary(key string, val []byte) {
enc.AddString(key, base64.StdEncoding.EncodeToString(val))
}
func (enc *jsonEncoder) AddByteString(key string, val []byte) {
enc.addKey(key)
enc.AppendByteString(val)
}
func (enc *jsonEncoder) AddBool(key string, val bool) {
enc.addKey(key)
enc.AppendBool(val)
}
func (enc *jsonEncoder) AddComplex128(key string, val complex128) {
enc.addKey(key)
enc.AppendComplex128(val)
}
func (enc *jsonEncoder) AddDuration(key string, val time.Duration) {
enc.addKey(key)
enc.AppendDuration(val)
}
func (enc *jsonEncoder) AddFloat64(key string, val float64) {
enc.addKey(key)
enc.AppendFloat64(val)
}
func (enc *jsonEncoder) AddInt64(key string, val int64) {
enc.addKey(key)
enc.AppendInt64(val)
}
func (enc *jsonEncoder) resetReflectBuf() {
if enc.reflectBuf == nil {
enc.reflectBuf = bufferpool.Get()
enc.reflectEnc = json.NewEncoder(enc.reflectBuf)
// For consistency with our custom JSON encoder.
enc.reflectEnc.SetEscapeHTML(false)
} else {
enc.reflectBuf.Reset()
}
}
var nullLiteralBytes = []byte("null")
// Only invoke the standard JSON encoder if there is actually something to
// encode; otherwise write JSON null literal directly.
func (enc *jsonEncoder) encodeReflected(obj interface{}) ([]byte, error) {
if obj == nil {
return nullLiteralBytes, nil
}
enc.resetReflectBuf()
if err := enc.reflectEnc.Encode(obj); err != nil {
return nil, err
}
enc.reflectBuf.TrimNewline()
return enc.reflectBuf.Bytes(), nil
}
func (enc *jsonEncoder) AddReflected(key string, obj interface{}) error {
valueBytes, err := enc.encodeReflected(obj)
if err != nil {
return err
}
enc.addKey(key)
_, err = enc.buf.Write(valueBytes)
return err
}
func (enc *jsonEncoder) OpenNamespace(key string) {
enc.addKey(key)
enc.buf.AppendByte('{')
enc.openNamespaces++
}
func (enc *jsonEncoder) AddString(key, val string) {
enc.addKey(key)
enc.AppendString(val)
}
func (enc *jsonEncoder) AddTime(key string, val time.Time) {
enc.addKey(key)
enc.AppendTime(val)
}
func (enc *jsonEncoder) AddUint64(key string, val uint64) {
enc.addKey(key)
enc.AppendUint64(val)
}
func (enc *jsonEncoder) AppendArray(arr zapcore.ArrayMarshaler) error {
enc.addElementSeparator()
enc.buf.AppendByte('[')
err := arr.MarshalLogArray(enc)
enc.buf.AppendByte(']')
return err
}
func (enc *jsonEncoder) AppendObject(obj zapcore.ObjectMarshaler) error {
enc.addElementSeparator()
enc.buf.AppendByte('{')
err := obj.MarshalLogObject(enc)
enc.buf.AppendByte('}')
return err
}
func (enc *jsonEncoder) AppendBool(val bool) {
enc.addElementSeparator()
enc.buf.AppendBool(val)
}
func (enc *jsonEncoder) AppendByteString(val []byte) {
enc.addElementSeparator()
enc.buf.AppendByte('"')
enc.safeAddByteString(val)
enc.buf.AppendByte('"')
}
func (enc *jsonEncoder) AppendComplex128(val complex128) {
enc.addElementSeparator()
// Cast to a platform-independent, fixed-size type.
r, i := float64(real(val)), float64(imag(val))
enc.buf.AppendByte('"')
// Because we're always in a quoted string, we can use strconv without
// special-casing NaN and +/-Inf.
enc.buf.AppendFloat(r, 64)
enc.buf.AppendByte('+')
enc.buf.AppendFloat(i, 64)
enc.buf.AppendByte('i')
enc.buf.AppendByte('"')
}
func (enc *jsonEncoder) AppendDuration(val time.Duration) {
cur := enc.buf.Len()
if e := enc.EncodeDuration; e != nil {
e(val, enc)
}
if cur == enc.buf.Len() {
// User-supplied EncodeDuration is a no-op. Fall back to nanoseconds to keep
// JSON valid.
enc.AppendInt64(int64(val))
}
}
func (enc *jsonEncoder) AppendInt64(val int64) {
enc.addElementSeparator()
enc.buf.AppendInt(val)
}
func (enc *jsonEncoder) AppendReflected(val interface{}) error {
valueBytes, err := enc.encodeReflected(val)
if err != nil {
return err
}
enc.addElementSeparator()
_, err = enc.buf.Write(valueBytes)
return err
}
func (enc *jsonEncoder) AppendString(val string) {
enc.addElementSeparator()
enc.buf.AppendByte('"')
enc.safeAddString(val)
enc.buf.AppendByte('"')
}
func (enc *jsonEncoder) AppendTimeLayout(time time.Time, layout string) {
enc.addElementSeparator()
enc.buf.AppendByte('"')
enc.buf.AppendTime(time, layout)
enc.buf.AppendByte('"')
}
func (enc *jsonEncoder) AppendTime(val time.Time) {
cur := enc.buf.Len()
if e := enc.EncodeTime; e != nil {
e(val, enc)
}
if cur == enc.buf.Len() {
// User-supplied EncodeTime is a no-op. Fall back to nanos since epoch to keep
// output JSON valid.
enc.AppendInt64(val.UnixNano())
}
}
func (enc *jsonEncoder) AppendUint64(val uint64) {
enc.addElementSeparator()
enc.buf.AppendUint(val)
}
func (enc *jsonEncoder) AddComplex64(k string, v complex64) { enc.AddComplex128(k, complex128(v)) }
func (enc *jsonEncoder) AddFloat32(k string, v float32) { enc.AddFloat64(k, float64(v)) }
func (enc *jsonEncoder) AddInt(k string, v int) { enc.AddInt64(k, int64(v)) }
func (enc *jsonEncoder) AddInt32(k string, v int32) { enc.AddInt64(k, int64(v)) }
func (enc *jsonEncoder) AddInt16(k string, v int16) { enc.AddInt64(k, int64(v)) }
func (enc *jsonEncoder) AddInt8(k string, v int8) { enc.AddInt64(k, int64(v)) }
func (enc *jsonEncoder) AddUint(k string, v uint) { enc.AddUint64(k, uint64(v)) }
func (enc *jsonEncoder) AddUint32(k string, v uint32) { enc.AddUint64(k, uint64(v)) }
func (enc *jsonEncoder) AddUint16(k string, v uint16) { enc.AddUint64(k, uint64(v)) }
func (enc *jsonEncoder) AddUint8(k string, v uint8) { enc.AddUint64(k, uint64(v)) }
func (enc *jsonEncoder) AddUintptr(k string, v uintptr) { enc.AddUint64(k, uint64(v)) }
func (enc *jsonEncoder) AppendComplex64(v complex64) { enc.AppendComplex128(complex128(v)) }
func (enc *jsonEncoder) AppendFloat64(v float64) { enc.appendFloat(v, 64) }
func (enc *jsonEncoder) AppendFloat32(v float32) { enc.appendFloat(float64(v), 32) }
func (enc *jsonEncoder) AppendInt(v int) { enc.AppendInt64(int64(v)) }
func (enc *jsonEncoder) AppendInt32(v int32) { enc.AppendInt64(int64(v)) }
func (enc *jsonEncoder) AppendInt16(v int16) { enc.AppendInt64(int64(v)) }
func (enc *jsonEncoder) AppendInt8(v int8) { enc.AppendInt64(int64(v)) }
func (enc *jsonEncoder) AppendUint(v uint) { enc.AppendUint64(uint64(v)) }
func (enc *jsonEncoder) AppendUint32(v uint32) { enc.AppendUint64(uint64(v)) }
func (enc *jsonEncoder) AppendUint16(v uint16) { enc.AppendUint64(uint64(v)) }
func (enc *jsonEncoder) AppendUint8(v uint8) { enc.AppendUint64(uint64(v)) }
func (enc *jsonEncoder) AppendUintptr(v uintptr) { enc.AppendUint64(uint64(v)) }
func (enc *jsonEncoder) Clone() zapcore.Encoder {
clone := enc.clone()
clone.buf.Write(enc.buf.Bytes())
return clone
}
func (enc *jsonEncoder) clone() *jsonEncoder {
clone := getJSONEncoder()
clone.EncoderConfig = enc.EncoderConfig
clone.spaced = enc.spaced
clone.openNamespaces = enc.openNamespaces
clone.buf = bufferpool.Get()
return clone
}
func (enc *jsonEncoder) EncodeEntry(ent zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) {
final := enc.clone()
final.buf.AppendByte('{')
if final.LevelKey != "" {
final.addKey(final.LevelKey)
cur := final.buf.Len()
final.EncodeLevel(ent.Level, final)
if cur == final.buf.Len() {
// User-supplied EncodeLevel was a no-op. Fall back to strings to keep
// output JSON valid.
final.AppendString(ent.Level.String())
}
}
if final.TimeKey != "" {
final.AddTime(final.TimeKey, ent.Time)
}
if ent.LoggerName != "" && final.NameKey != "" {
final.addKey(final.NameKey)
cur := final.buf.Len()
nameEncoder := final.EncodeName
// if no name encoder provided, fall back to FullNameEncoder for backwards
// compatibility
if nameEncoder == nil {
nameEncoder = zapcore.FullNameEncoder
}
nameEncoder(ent.LoggerName, final)
if cur == final.buf.Len() {
// User-supplied EncodeName was a no-op. Fall back to strings to
// keep output JSON valid.
final.AppendString(ent.LoggerName)
}
}
if ent.Caller.Defined {
if final.CallerKey != "" {
final.addKey(final.CallerKey)
cur := final.buf.Len()
final.EncodeCaller(ent.Caller, final)
if cur == final.buf.Len() {
// User-supplied EncodeCaller was a no-op. Fall back to strings to
// keep output JSON valid.
final.AppendString(ent.Caller.String())
}
}
if final.FunctionKey != "" {
final.addKey(final.FunctionKey)
final.AppendString(ent.Caller.Function)
}
}
if final.MessageKey != "" {
final.addKey(enc.MessageKey)
final.AppendString(ent.Message)
}
if enc.buf.Len() > 0 {
final.addElementSeparator()
final.buf.Write(enc.buf.Bytes())
}
addFields(final, fields)
final.closeOpenNamespaces()
if ent.Stack != "" && final.StacktraceKey != "" {
final.AddString(final.StacktraceKey, ent.Stack)
}
final.buf.AppendByte('}')
if final.LineEnding != "" {
final.buf.AppendString(final.LineEnding)
} else {
final.buf.AppendString(zapcore.DefaultLineEnding)
}
ret := final.buf
putJSONEncoder(final)
return ret, nil
}
func (enc *jsonEncoder) truncate() {
enc.buf.Reset()
}
func (enc *jsonEncoder) closeOpenNamespaces() {
for i := 0; i < enc.openNamespaces; i++ {
enc.buf.AppendByte('}')
}
}
func (enc *jsonEncoder) addKey(key string) {
enc.addElementSeparator()
enc.buf.AppendByte('"')
enc.safeAddString(key)
enc.buf.AppendByte('"')
enc.buf.AppendByte(':')
if enc.spaced {
enc.buf.AppendByte(' ')
}
}
func (enc *jsonEncoder) addElementSeparator() {
last := enc.buf.Len() - 1
if last < 0 {
return
}
switch enc.buf.Bytes()[last] {
case '{', '[', ':', ',', ' ':
return
default:
enc.buf.AppendByte(',')
if enc.spaced {
enc.buf.AppendByte(' ')
}
}
}
func (enc *jsonEncoder) appendFloat(val float64, bitSize int) {
enc.addElementSeparator()
switch {
case math.IsNaN(val):
enc.buf.AppendString(`"NaN"`)
case math.IsInf(val, 1):
enc.buf.AppendString(`"+Inf"`)
case math.IsInf(val, -1):
enc.buf.AppendString(`"-Inf"`)
default:
enc.buf.AppendFloat(val, bitSize)
}
}
// safeAddString JSON-escapes a string and appends it to the internal buffer.
// Unlike the standard library's encoder, it doesn't attempt to protect the
// user from browser vulnerabilities or JSONP-related problems.
func (enc *jsonEncoder) safeAddString(s string) {
for i := 0; i < len(s); {
if enc.tryAddRuneSelf(s[i]) {
i++
continue
}
r, size := utf8.DecodeRuneInString(s[i:])
if enc.tryAddRuneError(r, size) {
i++
continue
}
enc.buf.AppendString(s[i : i+size])
i += size
}
}
// safeAddByteString is no-alloc equivalent of safeAddString(string(s)) for s []byte.
func (enc *jsonEncoder) safeAddByteString(s []byte) {
for i := 0; i < len(s); {
if enc.tryAddRuneSelf(s[i]) {
i++
continue
}
r, size := utf8.DecodeRune(s[i:])
if enc.tryAddRuneError(r, size) {
i++
continue
}
enc.buf.Write(s[i : i+size])
i += size
}
}
// tryAddRuneSelf appends b if it is valid UTF-8 character represented in a single byte.
func (enc *jsonEncoder) tryAddRuneSelf(b byte) bool {
if b >= utf8.RuneSelf {
return false
}
if 0x20 <= b && b != '\\' && b != '"' {
enc.buf.AppendByte(b)
return true
}
switch b {
case '\\', '"':
enc.buf.AppendByte('\\')
enc.buf.AppendByte(b)
case '\n':
enc.buf.AppendByte('\\')
enc.buf.AppendByte('n')
case '\r':
enc.buf.AppendByte('\\')
enc.buf.AppendByte('r')
case '\t':
enc.buf.AppendByte('\\')
enc.buf.AppendByte('t')
default:
// Encode bytes < 0x20, except for the escape sequences above.
enc.buf.AppendString(`\u00`)
enc.buf.AppendByte(_hex[b>>4])
enc.buf.AppendByte(_hex[b&0xF])
}
return true
}
func (enc *jsonEncoder) tryAddRuneError(r rune, size int) bool {
if r == utf8.RuneError && size == 1 {
enc.buf.AppendString(`\ufffd`)
return true
}
return false
}
+183
View File
@@ -0,0 +1,183 @@
// Copyright (c) 2016 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package zapcorex
import (
"time"
"go.uber.org/zap/zapcore"
)
// MapObjectEncoder is an ObjectEncoder backed by a simple
// map[string]interface{}. It's not fast enough for production use, but it's
// helpful in tests.
type MapObjectEncoder struct {
// Fields contains the entire encoded log context.
Fields map[string]interface{}
// cur is a pointer to the namespace we're currently writing to.
cur map[string]interface{}
}
// NewMapObjectEncoder creates a new map-backed ObjectEncoder.
func NewMapObjectEncoder() *MapObjectEncoder {
m := make(map[string]interface{})
return &MapObjectEncoder{
Fields: m,
cur: m,
}
}
// AddArray implements ObjectEncoder.
func (m *MapObjectEncoder) AddArray(key string, v zapcore.ArrayMarshaler) error {
arr := &sliceArrayEncoder{elems: make([]interface{}, 0)}
err := v.MarshalLogArray(arr)
m.cur[key] = arr.elems
return err
}
// AddObject implements ObjectEncoder.
func (m *MapObjectEncoder) AddObject(k string, v zapcore.ObjectMarshaler) error {
newMap := NewMapObjectEncoder()
m.cur[k] = newMap.Fields
return v.MarshalLogObject(newMap)
}
// AddBinary implements ObjectEncoder.
func (m *MapObjectEncoder) AddBinary(k string, v []byte) { m.cur[k] = v }
// AddByteString implements ObjectEncoder.
func (m *MapObjectEncoder) AddByteString(k string, v []byte) { m.cur[k] = string(v) }
// AddBool implements ObjectEncoder.
func (m *MapObjectEncoder) AddBool(k string, v bool) { m.cur[k] = v }
// AddDuration implements ObjectEncoder.
func (m MapObjectEncoder) AddDuration(k string, v time.Duration) { m.cur[k] = v }
// AddComplex128 implements ObjectEncoder.
func (m *MapObjectEncoder) AddComplex128(k string, v complex128) { m.cur[k] = v }
// AddComplex64 implements ObjectEncoder.
func (m *MapObjectEncoder) AddComplex64(k string, v complex64) { m.cur[k] = v }
// AddFloat64 implements ObjectEncoder.
func (m *MapObjectEncoder) AddFloat64(k string, v float64) { m.cur[k] = v }
// AddFloat32 implements ObjectEncoder.
func (m *MapObjectEncoder) AddFloat32(k string, v float32) { m.cur[k] = v }
// AddInt implements ObjectEncoder.
func (m *MapObjectEncoder) AddInt(k string, v int) { m.cur[k] = v }
// AddInt64 implements ObjectEncoder.
func (m *MapObjectEncoder) AddInt64(k string, v int64) { m.cur[k] = v }
// AddInt32 implements ObjectEncoder.
func (m *MapObjectEncoder) AddInt32(k string, v int32) { m.cur[k] = v }
// AddInt16 implements ObjectEncoder.
func (m *MapObjectEncoder) AddInt16(k string, v int16) { m.cur[k] = v }
// AddInt8 implements ObjectEncoder.
func (m *MapObjectEncoder) AddInt8(k string, v int8) { m.cur[k] = v }
// AddString implements ObjectEncoder.
func (m *MapObjectEncoder) AddString(k string, v string) { m.cur[k] = v }
// AddTime implements ObjectEncoder.
func (m MapObjectEncoder) AddTime(k string, v time.Time) { m.cur[k] = v }
// AddUint implements ObjectEncoder.
func (m *MapObjectEncoder) AddUint(k string, v uint) { m.cur[k] = v }
// AddUint64 implements ObjectEncoder.
func (m *MapObjectEncoder) AddUint64(k string, v uint64) { m.cur[k] = v }
// AddUint32 implements ObjectEncoder.
func (m *MapObjectEncoder) AddUint32(k string, v uint32) { m.cur[k] = v }
// AddUint16 implements ObjectEncoder.
func (m *MapObjectEncoder) AddUint16(k string, v uint16) { m.cur[k] = v }
// AddUint8 implements ObjectEncoder.
func (m *MapObjectEncoder) AddUint8(k string, v uint8) { m.cur[k] = v }
// AddUintptr implements ObjectEncoder.
func (m *MapObjectEncoder) AddUintptr(k string, v uintptr) { m.cur[k] = v }
// AddReflected implements ObjectEncoder.
func (m *MapObjectEncoder) AddReflected(k string, v interface{}) error {
m.cur[k] = v
return nil
}
// OpenNamespace implements ObjectEncoder.
func (m *MapObjectEncoder) OpenNamespace(k string) {
ns := make(map[string]interface{})
m.cur[k] = ns
m.cur = ns
}
// sliceArrayEncoder is an ArrayEncoder backed by a simple []interface{}. Like
// the MapObjectEncoder, it's not designed for production use.
type sliceArrayEncoder struct {
elems []interface{}
}
func (s *sliceArrayEncoder) AppendArray(v zapcore.ArrayMarshaler) error {
enc := &sliceArrayEncoder{}
err := v.MarshalLogArray(enc)
s.elems = append(s.elems, enc.elems)
return err
}
func (s *sliceArrayEncoder) AppendObject(v zapcore.ObjectMarshaler) error {
m := NewMapObjectEncoder()
err := v.MarshalLogObject(m)
s.elems = append(s.elems, m.Fields)
return err
}
func (s *sliceArrayEncoder) AppendReflected(v interface{}) error {
s.elems = append(s.elems, v)
return nil
}
func (s *sliceArrayEncoder) AppendBool(v bool) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendByteString(v []byte) { s.elems = append(s.elems, string(v)) }
func (s *sliceArrayEncoder) AppendComplex128(v complex128) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendComplex64(v complex64) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendDuration(v time.Duration) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendFloat64(v float64) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendFloat32(v float32) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendInt(v int) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendInt64(v int64) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendInt32(v int32) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendInt16(v int16) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendInt8(v int8) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendString(v string) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendTime(v time.Time) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendUint(v uint) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendUint64(v uint64) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendUint32(v uint32) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendUint16(v uint16) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendUint8(v uint8) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendUintptr(v uintptr) { s.elems = append(s.elems, v) }
+44
View File
@@ -0,0 +1,44 @@
package m3u8
import (
"bytes"
"context"
"fmt"
"io/ioutil"
"sync"
)
var (
cacheEnkey []byte
initOnce sync.Once
initErr error
)
// 安全初始化 m3u8 key(文件缺失时不再 panic,交由调用方降级)
func initEnkey() {
initOnce.Do(func() {
data, err := ioutil.ReadFile("conf/m3u8.key")
if err != nil {
initErr = err
return
}
if len(data) == 0 {
initErr = fmt.Errorf("conf/m3u8.key is empty")
return
}
cacheEnkey = data
})
}
// Enkey 密匙 key
func Enkey(ctx context.Context) (buffer *bytes.Buffer, err error) {
initEnkey()
if initErr != nil {
return nil, initErr
}
if len(cacheEnkey) == 0 {
return nil, fmt.Errorf("m3u8 key not loaded")
}
buffer = bytes.NewBuffer(cacheEnkey)
return
}
+42
View File
@@ -0,0 +1,42 @@
package m3u8
import (
//"sz-app-server/pub/common/utils"
"time"
"videoweb/pub/common/utils"
"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(0, 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
}
+146
View File
@@ -0,0 +1,146 @@
package m3u8
import (
"bytes"
"fmt"
"io"
"strings"
"videoweb/pub/common/enum/redisconst"
"videoweb/pub/common/errors"
"videoweb/pub/common/http"
"videoweb/pub/common/log"
"videoweb/pub/common/utils"
"videoweb/pub/redis"
"videoweb/pub/storage/proxy"
"github.com/grafov/m3u8"
)
const (
keyUri = "/enkey"
webkeyUri = "/api/web/enkey"
h5KeyUri = "/enkey"
AuthKey = "fT5xSg4hltHpzVy6aV9rVECDJ1J1pN"
)
// resolveTsAuthKey 优先使用传入的 tsAuthKey,为空则回退默认 AuthKey。
func resolveTsAuthKey(tsAuthKey string) string {
if key := strings.TrimSpace(tsAuthKey); key != "" {
return key
}
return AuthKey
}
var headerMediaPlayList *m3u8.MediaPlaylist
// TransFormWithSafely 被检查权限的播放列表
// 主要检查用户是否有观看该视频的权限
func TransFormWithSafely(req *Req, cdnDomain, origDomain string, web bool, tsAuthKey string) (*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, web, tsAuthKey)
}
// transform m3u8文件转换,将不正确的m3u8文件,替换为正确的m3u8文件
func transform(name string, cdnDomain, origDomain string, web bool, tsAuthKey string) (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
}
// 处理媒资库 桶1(v1) 桶2(v2) 桶3(v3)
mediaVerson := proxy.AppendsMediaS3Version(name)
if len(mediaVerson) > 0 {
name = mediaVerson + name
}
authKey := resolveTsAuthKey(tsAuthKey)
keyurl := keyUri
if web {
keyurl = webkeyUri
}
switch t {
case m3u8.MEDIA:
pl := p.(*m3u8.MediaPlaylist)
dirPath := utils.PathForInfo(name).Dir
media, err = makeMediaPlayList(pl, cdnDomain, dirPath, keyurl, authKey)
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.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
}
fmt.Println("requestUrl", requestUrl, resp.StatusCode)
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
}
if err := redis.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
}
+133
View File
@@ -0,0 +1,133 @@
package m3u8
import (
"bytes"
"crypto/md5"
"encoding/hex"
"fmt"
"strconv"
"time"
"videoweb/pub/common/errors"
"videoweb/pub/common/utils"
"videoweb/pub/pubdef"
"videoweb/pub/storage/proxy"
"github.com/grafov/m3u8"
)
// transform m3u8文件转换,将不正确的m3u8文件,替换为正确的m3u8文件
// apiType(1:app,2:h5app,3:web)
func TransformEx(name string, cdnDomain, origDomain string, apiType int, authKey string) (
media *m3u8.MediaPlaylist, buff *bytes.Buffer, err error) {
if name == "" {
err = errors.MakeWithMsg(errors.M3u8FileName, "m3u8 file name is empty")
return
}
if cdnDomain == "" || origDomain == "" {
err = errors.MakeWithMsg(errors.DomainErr, "cdnDomain or origDomain is empty")
return
}
// 处理媒资库 桶1(v1) 桶2(v2) 桶3(v3)
mediaVerson := proxy.AppendsMediaS3Version(name)
if len(mediaVerson) > 0 {
name = mediaVerson + name
}
authKey = resolveTsAuthKey(authKey)
//log.Info("Test", log.Any("name", name), log.Any("cdnDomain", cdnDomain), log.Any("origDomain", origDomain), log.Any("authKey", authKey))
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
}
apiPath := keyUri
if apiType == 2 {
apiPath = h5KeyUri
} else if apiType == 3 {
apiPath = webkeyUri
}
switch t {
case m3u8.MEDIA:
pl := p.(*m3u8.MediaPlaylist)
dirPath := utils.PathForInfo(name).Dir
media, err = makeTSList(pl, cdnDomain, dirPath, apiPath, authKey)
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
}
// makeTSList 根据from,重新生成播放列表
func makeTSList(from *m3u8.MediaPlaylist, cdnDomain string,
dirPath string, apiPath string, authKey string) (
media *m3u8.MediaPlaylist, err error) {
media, err = m3u8.NewMediaPlaylist(from.WinSize(), from.Count())
if err != nil {
return
}
now := time.Now()
base := utils.JoinUrlPath(cdnDomain, dirPath)
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", apiPath, "", "", "")
media.Close()
return
}
// 生成鉴权url
func generateUrlAuth(now time.Time, path string, authKey string) string {
timestamp := now.Unix()
signStr := fmt.Sprintf("%s-%d-0-0-%s", path, timestamp, authKey)
md5Str := getMD5Sign(signStr)
// TS CDN 鉴权参数后追加应用标识,便于流量统计。
urlAuth := fmt.Sprintf("?md=%d-0-0-%s&c=%d", timestamp, md5Str, pubdef.AppId)
return urlAuth
}
// 生成鉴权url 七牛云
func generateUrlAuthQiniu(now time.Time, path string, authKey string) string {
timestamp := strconv.FormatInt(now.Add(2*time.Hour).Unix(), 16)
base64 := path
signStr := authKey + base64 + timestamp
md5Str := getMD5Sign(signStr)
urlAuth := fmt.Sprintf("?sign=%s&t=%s", md5Str, timestamp)
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
}
+13
View File
@@ -0,0 +1,13 @@
package m3u8
type Req struct {
Token string `json:"token" form:"token"`
VId int64 `json:"vId" form:"vId"`
Name string `json:"name" form:"name"`
NeedHeader bool `json:"needHeader" form:"needHeader"`
}
type PostVideoReq struct {
Token string `json:"token" form:"token"`
Name string `json:"name" form:"name" binding:"required" comment:"must be set"`
}
+18
View File
@@ -0,0 +1,18 @@
package m3u8
//sErr "sz-app-server/pub/common/errors"
// func CheckParams(req *Req) (err error) {
// if req.Token == "" {
// err = sErr.MakeWithMsg(sErr.NoToken, "no token")
// return
// }
// if req.VId <= 0 {
// err = sErr.MakeWithMsg(sErr.VideoId, "invalid video id")
// return
// }
// if req.Name == "" {
// err = sErr.MakeWithMsg(sErr.VideoName, "invalid name")
// }
// return
// }
+68
View File
@@ -0,0 +1,68 @@
package maths
import (
"math/big"
"strings"
uuid "github.com/satori/go.uuid"
)
const CODE62 = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
const CodeLenth = 62
/**
* 10进制 为 62进制 字符串。大数字类型
*
* param: *big.Int number 待转换的数字,非常大的
* return: string
*/
func From10To62Big(number *big.Int) string {
tmpNumber := big.NewInt(0)
var result []byte
for number.Cmp(big.NewInt(0)) != 0 {
var tmp []byte
tmpNumber.Rem(number, big.NewInt(CodeLenth))
tmp = append(tmp, CODE62[tmpNumber.Int64()])
result = append(tmp, result...)
number.Div(number, big.NewInt(CodeLenth))
}
return string(result)
}
/**
* 10进制 为 62进制 字符串。大数字类型
*
* param: int64 num 待转换的数字
* return: string
*/
func From10To62(number int64) string {
var result []byte
for number != 0 {
var tmp []byte
number1 := number % CodeLenth
tmp = append(tmp, CODE62[number1])
result = append(tmp, result...)
number = number / CodeLenth
}
return string(result)
}
func GenUUID() string {
u1 := uuid.Must(uuid.NewV4(), nil).String()
uuidStr := strings.Replace(u1, "-", "", -1)
number, _ := new(big.Int).SetString(uuidStr, 16)
return From10To62Big(number)
}
func Abs(n int64) int64 {
if n < 0 {
return -n
}
return n
}
func Gen32String() string {
u1 := uuid.Must(uuid.NewV4(), nil).String()
uuidStr := strings.Replace(u1, "-", "", -1)
return uuidStr
}
+52
View File
@@ -0,0 +1,52 @@
package netutil
import (
"net"
"net/url"
"path"
"strings"
)
func GetLocalIP() string {
addrs, err := net.InterfaceAddrs()
if err != nil {
return ""
}
for _, address := range addrs {
if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
if ipnet.IP.To4() != nil {
return ipnet.IP.String()
}
}
}
return ""
}
func MergeURL(baseURL, relativePath string) string {
// 解析基础URL
base, err := url.Parse(baseURL)
if err != nil {
return ""
}
// 如果相对路径是绝对URL,直接返回
if strings.HasPrefix(relativePath, "http://") || strings.HasPrefix(relativePath, "https://") {
return relativePath
}
// 合并路径
mergedPath := path.Join(base.Path, relativePath)
// 处理路径中的 ./ 和 ../
mergedPath = path.Clean(mergedPath)
// 确保路径以/开头(如果原始base.Path以/结尾)
if strings.HasSuffix(base.Path, "/") && !strings.HasPrefix(mergedPath, "/") {
mergedPath = "/" + mergedPath
}
// 更新URL的Path部分
base.Path = mergedPath
return base.String()
}
+10
View File
@@ -0,0 +1,10 @@
package netutil
import (
"fmt"
"testing"
)
func Test_GetLocalIP(t *testing.T) {
fmt.Println(GetLocalIP())
}
+580
View File
@@ -0,0 +1,580 @@
package pageopt
import (
"reflect"
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// A A from Array
type A = []interface{}
// M M from Map
type M = bson.M
// *******************************************//
// Matcher
type Matcher interface {
Filter() M
}
// MergeM
func MergeM(mats []Matcher) M {
merge := M{}
for _, m := range mats {
if m == nil {
continue
}
ql := m.Filter()
for k, v := range ql {
merge[k] = v
}
}
return merge
}
// *******************************************//
type MergeMatch struct {
merge M
}
func NewMergeMatch(mats ...Matcher) Matcher {
mergeM := MergeM(mats)
return &MergeMatch{mergeM}
}
func (m *MergeMatch) Filter() M {
return m.merge
}
// *******************************************//
// AssignMatch
type AssignMatch struct {
Key string
Val interface{}
}
func NewAssignMatch(key string, val interface{}) Matcher {
return &AssignMatch{
key,
val,
}
}
func (m *AssignMatch) Filter() M {
if IsEmpt(m.Val) {
return M{}
}
return M{
m.Key: m.Val,
}
}
// *******************************************//
type Sort = bson.D
func SortDesc(Key string) Sort {
return Sort{{Key, -1}}
}
func SortAsc(Key string) Sort {
return Sort{{Key, 1}}
}
// *******************************************//
// RegexMatch
type RegexMatch struct {
Key string
Val interface{}
Options string
}
func NewRegexMatch(key string, val interface{}, opt string) Matcher {
return &RegexMatch{
Key: key,
Val: val,
Options: opt,
}
}
func (r *RegexMatch) Filter() M {
if IsEmpt(r.Val) {
return M{}
}
return M{
r.Key: M{"$regex": r.Val, "$options": r.Options},
}
}
// *******************************************//
func IsNil(i interface{}) bool {
vi := reflect.ValueOf(i)
return vi.IsNil()
}
func IsEmpt(i interface{}) bool {
v := reflect.ValueOf(i)
switch v.Kind() {
case reflect.Ptr:
return v.IsNil()
case reflect.Slice, reflect.Array:
return v.Len() == 0
}
return false
}
// *******************************************//
type NEMatch struct {
Key string
NE interface{}
}
func (m *NEMatch) Filter() M {
if IsEmpt(m.NE) {
return M{}
}
return M{m.Key: M{"$ne": m.NE}}
}
func NewNEMatch(Key string, ne interface{}) Matcher {
return &NEMatch{
Key: Key,
NE: ne,
}
}
// *******************************************//
// GEMatch
type GEMatch struct {
Key string
GT interface{}
}
func NewGEMatch(Key string, gt interface{}) Matcher {
return &GEMatch{
Key: Key,
GT: gt,
}
}
func (m *GEMatch) Filter() M {
if IsEmpt(m.GT) {
return M{}
}
return M{m.Key: M{"$gt": m.GT}}
}
// *******************************************//
// GTEMatch
type GTEMatch struct {
Key string
GTE interface{}
}
func NewGTEMatch(Key string, gte interface{}) Matcher {
return &GTEMatch{
Key: Key,
GTE: gte,
}
}
func (m *GTEMatch) Filter() M {
if IsEmpt(m.GTE) {
return M{}
}
return M{m.Key: M{"$gte": m.GTE}}
}
// *******************************************//
// GTMatch
type GTMatch struct {
Key string
GT interface{}
}
func NewGTMatch(Key string, gt interface{}) Matcher {
return &GTMatch{
Key: Key,
GT: gt,
}
}
func (m *GTMatch) Filter() M {
if IsEmpt(m.GT) {
return M{}
}
return M{m.Key: M{"$gt": m.GT}}
}
// *******************************************//
// LTMatch
type LTMatch struct {
Key string
LT interface{}
}
func NewLTMatch(Key string, lt interface{}) Matcher {
return &LTMatch{
Key: Key,
LT: lt,
}
}
func (m *LTMatch) Filter() M {
if IsEmpt(m.LT) {
return M{}
}
return M{m.Key: M{"$lt": m.LT}}
}
// *******************************************//
// LTEMatch
type LTEMatch struct {
Key string
LTE interface{}
}
func NewLTEMatch(Key string, lte interface{}) Matcher {
return &LTEMatch{
Key: Key,
LTE: lte,
}
}
func (m *LTEMatch) Filter() M {
if IsEmpt(m.LTE) {
return M{}
}
return M{m.Key: M{"$lte": m.LTE}}
}
// *******************************************//
// GTEAndLTEMatch
type GTEAndLTEMatch struct {
Key string
GTE interface{}
LTE interface{}
}
func NewGTEAndLTEMatch(Key string, gte, lte interface{}) Matcher {
return &GTEAndLTEMatch{
Key,
gte,
lte,
}
}
func (m *GTEAndLTEMatch) Filter() M {
if IsEmpt(m.GTE) || IsEmpt(m.LTE) {
return M{}
}
return M{
m.Key: M{
"$gte": m.GTE,
"$lte": m.LTE,
},
}
}
// *******************************************//
// GTEAndLTMatch
type GTEAndLTMatch struct {
Key string
GTE interface{}
LT interface{}
}
func NewGTEAndLTMatch(key string, gte, lt interface{}) Matcher {
return &GTEAndLTMatch{
key,
gte,
lt,
}
}
func (m *GTEAndLTMatch) Filter() M {
if IsEmpt(m.GTE) || IsEmpt(m.LT) {
return M{}
}
return M{
m.Key: M{
"$gte": m.GTE,
"$lt": m.LT,
},
}
}
// *******************************************//
// ExistsMatch
type ExistsMatch struct {
Key string
IsExisted *bool
}
func NewExistsMatch(key string, isExisted *bool) Matcher {
return &ExistsMatch{
Key: key,
IsExisted: isExisted,
}
}
func (e *ExistsMatch) Filter() M {
if IsEmpt(e.IsExisted) {
return M{}
}
return M{
e.Key: M{"$exists": e.IsExisted},
}
}
// *******************************************//
// InMatch
type InMatch struct {
Key string
Slice interface{}
}
func NewInMatch(key string, slice interface{}) Matcher {
return &InMatch{
Key: key,
Slice: slice,
}
}
func (i *InMatch) Filter() M {
if IsNil(i.Slice) {
return M{}
}
return M{
i.Key: M{"$in": i.Slice},
}
}
// *******************************************//
// NinMatch
type NinMatch struct {
Key string
Slice interface{}
}
func NewNinMatch(key string, slice interface{}) Matcher {
return &NinMatch{
Key: key,
Slice: slice,
}
}
// *******************************************//
func (n *NinMatch) Filter() M {
if IsNil(n.Slice) {
return M{}
}
return M{
n.Key: M{"$nin": n.Slice},
}
}
// *******************************************//
// ManualOrMatch
type ManualOrMatch struct {
Key string
Mats []Matcher
}
func NewManualOrMatch(key string, mats []Matcher) Matcher {
return &ManualOrMatch{
Key: key,
Mats: mats,
}
}
func (o *ManualOrMatch) Filter() M {
if len(o.Mats) == 0 {
return M{}
}
fs := []interface{}{}
for _, v := range o.Mats {
f := v.Filter()
if len(f) != 0 {
fs = append(fs, v.Filter())
}
}
if len(fs) == 0 {
return M{}
}
return M{"$or": fs}
}
// *******************************************//
// BitsAllSetMatch
type BitsAllSetMatch struct {
Key string
Bits []int
}
func NewBitsAllSetMatch(key string, bits []int) Matcher {
return &BitsAllSetMatch{
Key: key,
Bits: bits,
}
}
func (b *BitsAllSetMatch) Filter() M {
if len(b.Bits) == 0 {
return M{}
}
return M{
b.Key: M{"$bitsAllSet": b.Bits},
}
}
// BitsAllClearMatch
type BitsAllClearMatch struct {
Key string
Bits []int
}
// *******************************************//
func NewBitsAllClearMatch(key string, bits []int) Matcher {
return &BitsAllClearMatch{
Key: key,
Bits: bits,
}
}
func (b *BitsAllClearMatch) Filter() M {
if len(b.Bits) == 0 {
return M{}
}
return M{
b.Key: M{"$bitsAllClear": b.Bits},
}
}
// *******************************************//
// UIDMatch
type UIDMatch struct {
UID *int64
}
func (u *UIDMatch) New() Matcher {
return NewAssignMatch("userId", u.UID)
}
// UIDInMatch
type UIDInMatch struct {
UIDS []int64
}
func (u *UIDInMatch) New() Matcher {
return NewInMatch("userId", u.UIDS)
}
// *******************************************//
// UIDMatch
type UserUIDMatch struct {
UID *int64
}
func (u *UserUIDMatch) New() Matcher {
return NewAssignMatch("id", u.UID)
}
// UIDInMatch
type UserUIDInMatch struct {
UIDS []int64
}
func (u *UserUIDInMatch) New() Matcher {
return NewInMatch("id", u.UIDS)
}
// *******************************************//
// IDMatch
type IDMatch struct {
ID *primitive.ObjectID
}
func (i *IDMatch) New() Matcher {
return NewAssignMatch("_id", i.ID)
}
// IDMatch
type IDint64Match struct {
Id *int64
}
func (i *IDint64Match) New() Matcher {
return NewAssignMatch("id", i.Id)
}
// *******************************************//
// IDInMatch
type IDInMatch struct {
IDs []primitive.ObjectID
}
func (c *IDInMatch) New() Matcher {
return NewInMatch("_id", c.IDs)
}
// *******************************************//
// CreatedAtGTEMatch
type CreatedAtGTEMatch struct {
GTE *time.Time
}
func (c *CreatedAtGTEMatch) New() Matcher {
return NewGTEMatch("createdAt", c.GTE)
}
// *******************************************//
// CreatedAtLTMatch
type CreatedAtLTMatch struct {
LT *time.Time
}
func (c *CreatedAtLTMatch) New() Matcher {
return NewLTMatch("createdAt", c.LT)
}
// *******************************************//
// CreatedAtGTEAndLTMatch
type CreatedAtGTEAndLTMatch struct {
GTE *time.Time
LT *time.Time
}
func (c *CreatedAtGTEAndLTMatch) New() Matcher {
return NewGTEAndLTMatch("createdAt", c.GTE, c.LT)
}
// // *******************************************//
// type Int32Match struct {
// key string //值
// value *int32
// }
//
// func (i *Int32Match) New() Matcher {
// return NewAssignMatch("_id", i.ID)
// }
//
// type Int32InMatch struct {
// key string //值
// values []int32
// }
//
// func (c *Int32InMatch) New() Matcher {
// return NewInMatch("_id", c.IDs)
// }
+83
View File
@@ -0,0 +1,83 @@
package pointers
import "time"
func Bool(v bool) *bool { return &v }
func Int(v int) *int { return &v }
func Int32(v int32) *int32 { return &v }
func Int64(v int64) *int64 { return &v }
func Uint32(v uint32) *uint32 { return &v }
func Uint64(v uint64) *uint64 { return &v }
func Float32(v float32) *float32 { return &v }
func Float64(v float64) *float64 { return &v }
func String(v string) *string { return &v }
func Time(v time.Time) *time.Time { return &v }
func StringSlice(v []string) *[]string { return &v }
func Int64Slice(v []int64) *[]int64 { return &v }
func GetBool(v *bool) bool {
if v != nil {
return *v
}
return false
}
func GetInt(v *int) int {
if v != nil {
return *v
}
return 0
}
func GetInt32(v *int32) int32 {
if v != nil {
return *v
}
return 0
}
func GetInt64(v *int64) int64 {
if v != nil {
return *v
}
return 0
}
func GetInt64Slice(v *[]int64) []int64 {
if v != nil {
return *v
}
return []int64{}
}
func GetUint32(v *uint32) uint32 {
if v != nil {
return *v
}
return 0
}
func GetUint64(v *uint64) uint64 {
if v != nil {
return *v
}
return 0
}
func GetFloat32(v *float32) float32 {
if v != nil {
return *v
}
return 0
}
func GetFloat64(v *float64) float64 {
if v != nil {
return *v
}
return 0
}
func GetString(v *string) string {
if v != nil {
return *v
}
return ""
}
func GetStringSlice(v *[]string) []string {
if v != nil {
return *v
}
return []string{}
}
+53
View File
@@ -0,0 +1,53 @@
package random
import (
"fmt"
"math/rand"
"time"
)
const letterBytesAll = "0123456789abcdefghijklmnopqrstvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
func init() {
rand.Seed(time.Now().Unix())
}
func Int(n int) int {
return rand.Intn(n)
}
func SliceRand(ids []int64) (result []int64) {
result = make([]int64, 0)
if len(ids) <= 0 {
return
}
copy := make([]int64, 0)
for _, id := range ids {
copy = append(copy, id)
}
num := Int(len(copy)) + 1
for i := 0; i < num; i++ {
index := Int(len(copy))
result = append(result, copy[index])
copy = append(copy[:index], copy[index+1:]...)
}
return
}
func RandDigits(n uint) string {
s := ""
for n != 0 {
s += fmt.Sprintf("%d", rand.Intn(10))
n--
}
return s
}
// RandStrRandrAll 生成指定位数的随机数
func RandStrRandrAll(n int) string {
b := make([]byte, n)
for i := range b {
b[i] = letterBytesAll[rand.Int63()%int64(len(letterBytesAll))]
}
return string(b)
}
+42
View File
@@ -0,0 +1,42 @@
package recomapi
import "time"
const (
FBT_Star string = "star"
FBT_Like string = "like"
FBT_Read string = "read"
)
type RSItemInfo struct {
Comment string `json:"Comment"`
ItemId string `json:"ItemId"`
Labels []string `json:"Labels"`
Timestamp time.Time `json:"Timestamp"`
}
type RSItemInfoList []*RSItemInfo
type RSUserInfo struct {
Comment string `json:"Comment"`
UserId string `json:"UserId"`
Labels []string `json:"Labels"`
Subscribe []string `json:"Subscribe"`
Timestamp time.Time `json:"Timestamp"`
}
type RSUserInfoList []*RSUserInfo
type APIResult struct {
RowAffected int64 `json:"RowAffected"`
}
type FeedbackInfo struct {
Comment string `json:"Comment"`
FeedbackType string `json:"FeedbackType"`
ItemId string `json:"ItemId"`
Timestamp time.Time `json:"Timestamp"`
UserId string `json:"UserId"`
}
type FeedbackInfoList []*FeedbackInfo
+45
View File
@@ -0,0 +1,45 @@
package recomapi
import (
"encoding/json"
"net/http"
"strconv"
"time"
"videoweb/pub/common/log"
"videoweb/pub/common/srvdef"
"videoweb/pub/common/utils"
)
// AddOneFeedback 添加一个推荐反馈
func AddOneFeedback(url string, key string,
userId int64, mediaId int64, fbType string, comment string) (
int64, *srvdef.SrvErr) {
feedBack := &FeedbackInfo{
UserId: strconv.FormatInt(userId, 10),
ItemId: strconv.FormatInt(mediaId, 10),
FeedbackType: fbType,
Comment: comment,
Timestamp: time.Now(),
}
return AddManyFeedbacks(url, key, FeedbackInfoList{feedBack})
}
func AddManyFeedbacks(url string, key string, feedbacks FeedbackInfoList) (
int64, *srvdef.SrvErr) {
reqUrl := url + "/api/feedback"
resp := &APIResult{}
reqBody, _ := json.Marshal(feedbacks)
header := map[string]string{"X-API-Key": key}
code, err := utils.POSTJsonWithJResp(resp, reqUrl, header, reqBody)
if err != nil {
log.Error("recomapi:AddManyFeedbacks POSTJsonWithJResp failed",
log.Any("reqUrl", reqUrl), log.E(err))
return 0, srvdef.New(srvdef.Err, "", "")
}
if code != http.StatusOK {
log.Error("recomapi:AddManyFeedbacks POSTJsonWithJResp code error.",
log.Any("reqUrl", reqUrl), log.Any("code", code))
return 0, srvdef.New(srvdef.Err, "", "")
}
return resp.RowAffected, nil
}
+106
View File
@@ -0,0 +1,106 @@
package recomapi
import (
"encoding/json"
"fmt"
"net/http"
"videoweb/pub/common/log"
"videoweb/pub/common/srvdef"
"videoweb/pub/common/utils"
)
func InsertOneItem(url string, key string, itemInfo *RSItemInfo) *srvdef.SrvErr {
reqUrl := url + "/api/item"
resp := &APIResult{}
reqBody, _ := json.Marshal(itemInfo)
header := map[string]string{"X-API-Key": key}
code, err := utils.POSTJsonWithJResp(resp, reqUrl, header, reqBody)
if err != nil {
log.Error("recomapi:InsertOneItem POSTJsonWithJResp failed",
log.Any("reqUrl", reqUrl), log.E(err))
return srvdef.New(srvdef.Err, "", "")
}
if code != http.StatusOK {
log.Error("recomapi:InsertOneItem POSTJsonWithJResp code error.",
log.Any("reqUrl", reqUrl), log.Any("code", code))
return srvdef.New(srvdef.Err, "", "")
}
return nil
}
func InsertManyItems(url string, key string, itemInfos RSItemInfoList) *srvdef.SrvErr {
reqUrl := url + "/api/items"
reqBody, _ := json.Marshal(itemInfos)
header := map[string]string{"X-API-Key": key}
code, err := utils.POSTJsonWithJResp(nil, reqUrl, header, reqBody)
if err != nil {
log.Error("recomapi:InsertManyItems POSTJsonWithJResp failed",
log.Any("reqUrl", reqUrl), log.E(err))
return srvdef.New(srvdef.Err, "", "")
}
if code != http.StatusOK {
log.Error("recomapi:InsertManyItems POSTJsonWithJResp code error.",
log.Any("reqUrl", reqUrl), log.Any("code", code))
return srvdef.New(srvdef.Err, "", "")
}
return nil
}
func GetOneItem(url string, key string, itemId string) (*RSItemInfo, *srvdef.SrvErr) {
reqUrl := fmt.Sprintf("%s/api/item/%s", url, itemId)
resp := &RSItemInfo{}
header := map[string]string{"X-API-Key": key}
code, err := utils.GetWithJResp(resp, reqUrl, header, nil)
if err != nil {
log.Error("recomapi:GetOneItem GetWithJResp failed",
log.Any("reqUrl", reqUrl), log.E(err))
return nil, srvdef.New(srvdef.Err, "", "")
}
if code != http.StatusOK {
log.Error("recomapi:GetOneItem GetWithJResp code error.",
log.Any("reqUrl", reqUrl), log.Any("code", code))
return nil, srvdef.New(srvdef.Err, "", "")
}
log.Info("Test", log.Any("resp", resp))
return resp, nil
}
func GetManyItems(url string, key string, cursor string, size int64) (RSItemInfoList, string, *srvdef.SrvErr) {
reqUrl := fmt.Sprintf("%s/api/items?n=%d&cursor=%s", url, size, cursor)
type Resp struct {
Cursor string `json:"Cursor"`
Items RSItemInfoList `json:"Items"`
}
var resp Resp
header := map[string]string{"X-API-Key": key}
code, err := utils.GetWithJResp(&resp, reqUrl, header, nil)
if err != nil {
log.Error("recomapi:GetOneItem GetWithJResp failed",
log.Any("reqUrl", reqUrl), log.E(err))
return nil, "", srvdef.New(srvdef.Err, "", "")
}
if code != http.StatusOK {
log.Error("recomapi:GetOneItem GetWithJResp code error.",
log.Any("reqUrl", reqUrl), log.Any("code", code))
return nil, "", srvdef.New(srvdef.Err, "", "")
}
return resp.Items, resp.Cursor, nil
}
func DeleteOneItem(url string, key string, itemId string) (int64, *srvdef.SrvErr) {
reqUrl := fmt.Sprintf("%s/api/item/%s", url, itemId)
resp := &APIResult{}
header := map[string]string{"X-API-Key": key}
code, err := utils.DeleteWithJResp(resp, reqUrl, header, nil)
if err != nil {
log.Error("recomapi:DeleteOneItem DeleteWithJResp failed",
log.Any("reqUrl", reqUrl), log.E(err))
return 0, srvdef.New(srvdef.Err, "", "")
}
if code != http.StatusOK {
log.Error("recomapi:DeleteOneItem DeleteWithJResp code error.",
log.Any("reqUrl", reqUrl), log.Any("code", code))
return 0, srvdef.New(srvdef.Err, "", "")
}
return resp.RowAffected, nil
}
+36
View File
@@ -0,0 +1,36 @@
package recomapi
import (
"fmt"
"net/http"
"strconv"
"videoweb/pub/common/log"
"videoweb/pub/common/srvdef"
"videoweb/pub/common/utils"
)
func GetRecomItems(url string, key string, userId int64, writeBack string, size int64) (
[]int64, *srvdef.SrvErr) {
reqUrl := fmt.Sprintf("%s/api/recommend/%d?n=%d&write-back=%s", url, userId, size, writeBack)
var resp []string
header := map[string]string{"X-API-Key": key}
code, err := utils.GetWithJResp(&resp, reqUrl, header, nil)
if err != nil {
log.Error("recomapi:GetRecomItems GetWithJResp failed",
log.Any("reqUrl", reqUrl), log.E(err))
return nil, srvdef.New(srvdef.Err, "", "")
}
if code != http.StatusOK {
log.Error("recomapi:GetRecomItems GetWithJResp code error.",
log.Any("reqUrl", reqUrl), log.Any("code", code))
return nil, srvdef.New(srvdef.Err, "", "")
}
itemIds := make([]int64, 0)
for _, v := range resp {
id, err := strconv.ParseInt(v, 10, 64)
if err == nil {
itemIds = append(itemIds, id)
}
}
return itemIds, nil
}
+77
View File
@@ -0,0 +1,77 @@
package recomapi
import (
"fmt"
"testing"
"time"
"videoweb/pub/common/log"
)
var (
testUrl = "http://139.177.187.219:8087"
testKey = ""
)
func Test_InsertOneItem(t *testing.T) {
itemInfo := &RSItemInfo{
Comment: "Comment1",
ItemId: "10004",
Labels: []string{"lable1", "lable2", "lable3"},
Timestamp: time.Now(),
}
serr := InsertOneItem(testUrl, testKey, itemInfo)
fmt.Printf("\nTest_pingReq %s %v\n", itemInfo, serr)
}
func Test_InsertManyItems(t *testing.T) {
list := make(RSItemInfoList, 0)
itemInfo1 := &RSItemInfo{
Comment: "Comment1",
ItemId: "10005",
Labels: []string{"lable1", "lable2", "lable3"},
Timestamp: time.Now(),
}
list = append(list, itemInfo1)
itemInfo2 := &RSItemInfo{
Comment: "Comment1",
ItemId: "10006",
Labels: []string{"lable1", "lable2", "lable3"},
Timestamp: time.Now(),
}
list = append(list, itemInfo2)
serr := InsertManyItems(testUrl, testKey, list)
fmt.Printf("\nTest_InsertManyItems %v\n", serr)
}
func Test_GetOneItem(t *testing.T) {
itemInfo, serr := GetOneItem(testUrl, testKey, "10004")
fmt.Printf("\nTest_GetOneItem %v %v\n", itemInfo, serr)
}
func Test_GetManyItem(t *testing.T) {
itemInfos, cursor, serr := GetManyItems(testUrl, testKey, "10003", 2)
log.Info("Test_GetManyItem", log.Any("itemInfos", itemInfos),
log.Any("cursor", cursor), log.E(serr))
//fmt.Printf("\nTest_GetOneItem %v %v\n", itemInfos, serr)
}
func Test_DeleteOneItem(t *testing.T) {
count, serr := DeleteOneItem(testUrl, testKey, "10004")
log.Info("Test_DeleteOneItem", log.Any("count", count), log.E(serr))
//fmt.Printf("\nTest_GetOneItem %v %v\n", itemInfos, serr)
}
//////////////////////////////////////////////////////////////
//feedback
func Test_AddOneFeedback(t *testing.T) {
count, serr := AddOneFeedback(testUrl, testKey, 18531, 16346, FBT_Star, "feedback1")
fmt.Printf("\nTest_pingReq %d %v\n", count, serr)
}
// /reccommend
func Test_GetManyRecomItems(t *testing.T) {
itemIds, serr := GetRecomItems(testUrl, testKey, 18531, "test", 10)
log.Info("Test_GetManyRecomItems", log.Any("itemIds", itemIds), log.E(serr))
//fmt.Printf("\nTest_GetOneItem %v %v\n", itemInfos, serr)
}
+1
View File
@@ -0,0 +1 @@
package recomapi
+26
View File
@@ -0,0 +1,26 @@
package common
import (
"github.com/gin-gonic/gin"
)
// type FunHandle func(*gin.Context) (proto.Message, string, string, pkt_pub.EType)
type ApiOpType int
const (
ApiGet ApiOpType = 1
ApiPost ApiOpType = 2
ApiPut ApiOpType = 3
)
// API接口设置
type ApiSet struct {
Path string // 路径
OpType ApiOpType // 操作类型
Fun gin.HandlerFunc // 函数句柄
IsWhite bool // 是否白名单
Desc string // 描述
}
var ApiSetMaps = map[string]ApiSet{}
+76
View File
@@ -0,0 +1,76 @@
package fengniao
import (
"crypto/sha1"
"errors"
"fmt"
"net/http"
"videoweb/pub/common/httputil"
"videoweb/pub/common/log"
)
type requestBody struct {
MercId string `json:"mercId"`
Secret string `json:"secret"`
TemplateId string `json:"templateId"`
Code 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 {
content := fmt.Sprintf(f.Tmpl, code)
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("content", content), log.E(err))
return err
}
body := &requestBody{
MercId: f.MercId,
Secret: f.Secret,
TemplateId: f.Tmpl,
Code: code,
Mobile: mobile,
SignType: "sha1",
Sign: fmt.Sprintf("%x", sha.Sum(nil)),
}
rp := responseBody{}
statusCode, err := httputil.Client().POSTJsonWithJResp(&rp, f.Url, nil, body)
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
}
+45
View File
@@ -0,0 +1,45 @@
package sms
import (
"strings"
"videoweb/pub/common/sms/fengniao"
)
type Provider = int
const (
tmplID1 = "6029f19a99fd8b404062e4d2" // 大陆
tmplID2 = "5f8412c39396d16f787e561b" // 非大陆
)
const (
FengNiao Provider = 1 //蜂鸟
YunPian Provider = 2 //云片
)
func sendByFengNiao(mobile string, code string) error {
var tmplID string
if strings.HasPrefix(mobile, "+86") {
tmplID = tmplID1
return (&fengniao.FengNiao{
Url: "https://t9x6k.cc/api/sms/sendSms",
MercId: "10177",
Secret: "2dfb7ec44ed59ee3c1fe8dc533384e16",
Tmpl: tmplID,
}).Send(mobile, code)
} else {
tmplID = tmplID2
return (&fengniao.FengNiao{
Url: "https://t9x6k.cc/api/sms/sendSms",
MercId: "10228",
Secret: "3ea58a27a162dad4ed3e18c076648742",
Tmpl: tmplID,
}).Send(mobile, code)
}
// return
}
// Send 发送短信
func Send(mobile string, code string) error {
return sendByFengNiao(mobile, code)
}
+8
View File
@@ -0,0 +1,8 @@
package sms
import "testing"
func TestSend(t *testing.T) {
err := Send("15008279990", "654321")
t.Log(err)
}
+328
View File
@@ -0,0 +1,328 @@
package srvdef
import "fmt"
type CodeType int
const NotFind = "mongo: no documents in result"
// 错误级别
const (
ParamLevel = 1000 // 参数级
StoreLevel = 3000 // 存储级
DBLevel = 5000 // 数据库
ServerLevel = 4000 // 服务级
BusinessLevel = 6000 // 业务逻辑级
NewErrorLevelV2 = 7000 // 新版错误
NewErrorLevelV3 = 8000 // 新版错误
)
// 通用code
const (
Success CodeType = 200 //Success 通用成功
Failure CodeType = 400 //Failure 通用失败
)
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 ""
}
func (c CodeType) Error() string {
return fmt.Sprintf("code=%d,msg=%v,tip=%v", c, c.Msg(), c.Tip())
}
type ErrInfoer interface {
GetMsg() string
GetTip() string
GetCode() CodeType
}
// GetMsg alias of Msg()
func (c CodeType) GetMsg() string { return c.Msg() }
// GetTip alias of Tip()
func (c CodeType) GetTip() string { return c.Tip() }
func (c CodeType) GetCode() CodeType { return c }
const (
InvalidToken CodeType = ParamLevel + iota // 框架错误
ErrParamError // 参数错误
NoToken // 无token
RoleErr // 角色错误
ReLogin // 重复登陆
UploadToFileSrvFailed // 文件上传失败
ErrCaptcha // 验证码为空
ErrCaptchaInvalid // 验证码非法
ErrCaptchaGet // 验证码获取失败
ErrCaptchaResend // 验证码重复发送
ErrCaptchaSendFail // 验证码发送失败
InvalidateToken // 非法token
NoUA // 没有UA信息
UploadFile // 文件上传失败
AlreadyRegisered // 已经组册
TokenRemoved // 你已经挤下线,请重新登陆
PostCommentNotUse //发送失败,服务器错误,请稍后再试
DBTransaction // 事务错误
ErrCrawlerUrl // 爬虫URL错误
ErrCrawler // 爬取数据错误
)
const (
DBQuery CodeType = StoreLevel + iota
DBInsert
DBUpdate
DBDelete
RedisSet // 设置redis错误
RedisGet // 获取redis值错误
RedisDel // 删除redis值错误
EtcdGet // 获取etcd错误
MongoDBQueryErr // 数据库查询失败
)
const (
ServerError CodeType = ServerLevel + iota // 用户相关错误
)
const (
_ CodeType = DBLevel + iota
ErrDbQueryError //ErrDbQueryError 数据库查询错误 5001
ErrDbInsertError //ErrDbInsertError 数据库插入错误 5002
ErrDbUpdateError //ErrDbUpdateError 数据库修改错误 5003
ErrDbDeleteError //ErrDbDeleteError 数据库删除错误 5004
ErrDbInputExist //ErrDbInputExist 数据库输入值已经存在 5005
ErrRdsOperError //ErrRdsOperError redis操作错误 5006
ErrElsOperError //ErrElsOperError redis操作错误 5007
ErrTransactionsError //ErrTransactionsError 数据库事务操作错误
ErrNoData //无数据
)
const (
ErrUserPermission CodeType = BusinessLevel + iota // 用户权限
ErrUserNotExsit // 用户不存在
ErrUserTypeNotUP // 用户不是UP主
AccountIsNotExists // 账户不存在
AccountErrAccessForbid // 账户禁止登陆
AccountErrPasswordError // 账户密码错误
GenerateTokenFailed // 生成二维码错误
ErrCardNotExists // 没有会员卡信息
ErrMadouCoinNotExists // 没有钻石信息
ErrMediaNotExsit // 视频不存在
ErrTopicInfoNotExsit // 视频专题不存在
ErrData // 数据错误
Err // 一般错误
ErrRchgOrderNotExist // 充值订单不存在
ErrSubmitRchgOrder // 发起充值订单错误
InvalidRequestReplayAttack // 非法请求
ErrBuyVIPCardFail // 购买会员卡失败
ErrOperationTooFrequently // 用户操作太频繁
ErrMediaSyncServer // 视频同步服务器错误
ErrUserType // 用户无改权限
DownloadM3u8File // 文件服务器访问失败
MediaSyncing // 视频同步中
MediaConnectServerFaild // 媒资库链接失败
AtressNotExist // 演员不存在
ErrAnnouncementNotExsit // 公告不存在
ErrContactDetailsNotExsit // 联系方式不存在
ErrPostCommentNotExsit // 评论不存在
HistoryNotExist // 记录不存在
ErrHotsearchNotExsit // 热搜不存在
ActivityDoesNotExist // 活动不存在
UserWatchNumNotEnough // 免费观看次数不足
MediaIsVip // 用户不是会员
MediaIsNeedPay // 视频需要付费
RepeatBuy // 重复购买
MovieTicketsNotExits // 观影券不足
MediaIsNotNeedPay // 视频不需要支付
PostNoNeed2Pay // 帖子不需要支付
SystemMsgNotExist // 消息不存在
ForbidlogNotExist // 封禁消息不存在
AttachmentDoesNotExist // 导出记录
UserDownloadNotEnough // 下载次数不足
UserDownloadNeedPay // 付费视频需要先购买
RedeemCodeNotExists // 兑换码不存在
RedeemCodeTimesUsedUp // 兑换码次数使用完了
RedeemCodeAlreadyUsed // 兑换码你已经使用过兑换码了
RedeemCodeRecordNotExists // 兑换记录不存在
MediaIsNotNeedPreSale // 视频不支持预售
MediaIsPreSale // 预售视频
QRcodeError // 二维码错误
ModifyUserNameError // 修改
ErrSkdRecordNotExsit // 任务不存在
ErrContainIllegalWord // 包含非法字符
DatingOrderNotExist // 约会购买记录不存在
DatingHasBuy // 重复购买
DatingCollectNotExist // 收藏记录不存在
DatingBossNotExist // 经纪人不存在
DatingReportNotExist // 验证报告不存在
DatingReportDouble // 重复验证
DatingAlreadyComplaint // 已经提交举报
SortNotExist // 视频排序不存在
MessageDialogNotExist // 私信对话框不存在
VideoUploadFaild // 文件上传失败
PosoStatusFaild // 帖子状态错误
WtdrFrequencyLimits // 提现频率限制
WtdrErroTip1 // 提现不是100整数倍
WtdrErroTip2 // 赠送3元金额要满500才能提现
WtdrErroTip3 // 请填写银行名字或升级App版本
GameWtdrErroTip7 // 银行卡必须与您以前的提现卡一致,如需更改请联系客服
ProxyUserNotExist // 代理不存在
ErrPostReportNotExsit // 举报信息不存在
FileUploadDouble // 文件重复上传
UserRepeatSubmitRchgOrder // 未支付订单太多,有问题请联系客服
Limiting // 限流
ErrIpForbid // Ip禁止
DeviceIdInvalid // 设备id无效
UserScoreNotEnough // 用户积分不足
UserSocreLVLimit // 用户积分等级限制
UploaderNeedActive // Up主没有激活
UploaderNeedBind // 后台用户没有up主绑定
PostCollectionNotExists // 图文专辑不存在
MediaCollectionNotExist // 该合集或选集不存在
ContestNotExists // 该比赛不存在
CannotJoinContest // 该比赛无法参加
ContestIsVip // 该比赛只限VIP参加
AppInfoNotExist // 该App不存在
MediaNotVerified // 视频未上架
PCCategoryUnmatch // 专辑的分类和作品的分类不一致
ErrMediaRecommendNotExsit // 推荐不存在
DatingBuyHistoryNotExist // 购买记录不存在
DatingWatchHistoryNotExist // 观看记录不存在
CollectIsExist // 收藏已经存在
CollectNotExist // 收藏不存在
DiscussRankDoesNotExist // 话题视频排名
DiscussActivityDoesNotExist // 话题活动不存在
UpDayStatDoesNotExist // 创作者收益
DiscussObjectRankDoesNotExist // 话题视频
DiscussCategoryDoesNotExist // 话题分类不存在
ErrCareNotExist // 取消关注对象不存在
ErrUpMasterTaskNotCompleted // up主申请任务未完成
HGameNotExist // 游戏不存在
HGameIsVip // 游戏需要会员
HGameIsNeedPay // 游戏需要付费
ErrApplyUpIsVip // UP主申请需要vip
UserLevelCardNotExist // 用户等级卡不存在
UserCheckinSetupNotExist // 签到配置不存在
PriceMediaNotCancel // 钻石视频不能下架
PriceMediaHasBuy // 钻石视频已经被购买
GiftWeekIsExist // 礼包已领取
UserLevelPointTooLow // 用户等级积分太低
UserGiftHaveReceive // 任务奖励已经领取
UserGiftExistNoReceive // 没有领取资格
DatingUpMasterNotExist // up主不存在
UserIsUpMaster // 用户已经是up主
UserUpCerting // up主认证中
MissionSetupNotExist // 任务配置不存在
UserIsUpMasterBind // 用户已被up主绑定
ErrCareIsExist // 不可重复关注
ObjectDayStatDoesNotExist // 作品统计记录不存在
ObjectDayViewIncStatDoesNotExist // 作品增量记录不存在
UserNotEnoughFreeLotteryCount // 免费抽奖次数不足
PrizeOutOfStore // 奖品库存不足
MediaSubscribedNotNeedPay // 订阅的视频不需要支付
DatingComplaintNotExist // 举报帖子不存在
DatingUserNotExist // 楼凤不存在
UserDauInfoNotExist // 搜寻日期没有资料
ParseJsonError // 数据解析错误
AlreadyRefund // 已退款
AiCountTypeNotSupport // ai内容不支持
TodayPontNotExist // 积分记录不存在
RedeemPrizeHistoryNotExist // 奖品兑换记录不存在
PrizeNotExist // 奖品不存在
ErrUserPointsNotExist // 积分不足
AIFreeCountNotEnough // ai免费次使用数不足
AIFreeCountNOtSupportRefund // AI免费使用次数不支持退款
CrowdfundStatusFail // 众筹女神的状态不在可修改的范围内
CrowdfundNotOpenPrizeFail // 众筹女神的订单开奖失败
CrowdfundPrizeNumberError // 众筹女神的开奖号码不对
RedeemFail // 积分兑换失败
CrowdfundNotBuy // 当前女神状态不能购买
CrowdfundNumberHaveBuy // 这个号码已经被其他人购买了
UserCheckinDisabled // 用户签到功能已关闭
PrizeTypeUnsupported // 奖品类型不支持自动发放
CouponExpired // 卡券已过期
CouponUsed // 卡券已被使用
CouponNotFound // 没有卡券信息
JumpTabNotExist // 首页跳转tab不存在
CouponCanNotUse // 卡券无法使用
SysConfigError //系统配置错误
ComicsCateNotExist // 漫画分类不存在
ComicsTagNotExist // 漫画标签不存在
ComicsTopicNotExist // 漫画主题不存在
ErrComicsChapterNotExist // 漫画章节不存在
ComicsNotExist // 漫画不存在
ComicsCVNotExist // 漫画CV不存在
ComicsMediaNotExist // 漫画视频不存在
ComicsRankNotExist // 排行榜不存在
VomicsTopicNotExist // 有声漫不存在
ComicsIsBlack // 漫画在黑名单中
CommentTextIsEmpty // 评论内容为空
ComicsIsVip // 漫画需要会员
ComicsIsNeedPay // 漫画需要付费
ComicsChapterExist // 漫画章节已存在
ObjectNotExist // 数据不存在
GameWtdrErroTip3 // 请填写银行名字或升级App版本
UserDoTaskErr // 用户任务出错
GetPrizeFail // 获取奖品失败
NovelIsNeedPay // 小说需要付费
NovelIsVip // 小说需要vip
LotteryActivityNotEnable // 抽奖活动没有启用
CurrTimeNotActivity // 当前时间没有活动
AIOrderStripsCountLimit // 今天AI脱衣提交次数达到上限
NotWtdrChannel // 没有匹配到提现通过
ExcelExportErr // 文件导出错误
ComicsNotJoinActivity // 这本漫画没有参与活动
NoGiftToReceive // 没有奖品可领取
UserRepeatDrawDiscountCard // 用户重复翻牌
NoUseDiscountCard // 不可使用折扣卡
AIOrderNotDel // 未完成的订单不能删除
AIWaitOrderTooMuch // 排队中的AI订单太多,请稍后再试
ActorNotExist // 演员不存在
MediaFSServerHandleIng // 文件服务器处理中
VideoRepeat // 视频重复
ErrReqForbidden // 请求被拒
APPIDError // APPID异常
)
const (
ErrRecharge CodeType = iota + 18000 // 充值相关错误
ErrGetRechargeType // 充值方式获取错误
UserPhoneNotBind // 手机号码未注册
PostCommentPermissionDenied // 禁止评论
UserLoginPasswordErr // 密码错误
ErrMediaRecordNotExsit // 视频记录不存在
ErrThressServerHttpErr // 第三方服务错误
UserPhoneBeenBound // 手机号码已被绑定
WeekCommentNumNotEnough // 评论条数不足
ErrAccessForbid //ErrAccessForbid 没有访问权限 1000
MediaAlreadyInEpisode // 该视频已经有选集
MediaNotInEpisode // 选集中不包含该视频
MediaCollectNotNull // 合集视频不为空
)
const (
//
CircleErrCode CodeType = iota + 20000 // 圈子错误码 20000
PublistNotVIPPermissionDenied // 不是会员禁止发布
PublistContentErrDenied // 包含敏感词汇
PostRepeatBuy // 帖子重复购买
UserNotEnoughMoney // 用户的钻石数量不足
PostStatusError // 订单状态错误
PostNotAllowComplaint // 不允许举报
UserNotEnoughScore // 用户的钻石数量不足
ErrUserAccExist // 账号已存在
UserAccountNotBind // 账号未注册
VerifyOKPostCountLimit //上架的帖子数量达到限制
ErrUserBeBanned //ErrUserBeBanned 用户已经禁言 1004
ErrCustomerBanned //ErrCustomerBanned 客服已休息 1005
ErrVersionUpdate //ErrVersionUpdate 版本强制更新 1006
)
+58
View File
@@ -0,0 +1,58 @@
package srvdef
// error tips
const (
Ok = "成功"
//基础错误提示
ET_Store = "存储错误"
ET_DBOp = "DB操作错误"
ET_DBUpdate = "DB更新失败"
ET_RdsOp = "Rds操作错误"
ET_ESOp = "ES操作错误"
ET_Param = "参数错误"
ET_NoDocment = "无数据不存在"
ET_NoCache = "缓存数据不存在"
ET_MoneyNotEnough = "余额不足"
//----------------------------------------------
ET_UserNoExist = "用户不存在"
ET_IsntBlogger = "用户不是博主"
ET_BloggerApply = "博主申请条件不满足"
ET_BloggerAlreadyIs = "用户已经是博主"
ET_VipLevel2Low = "用户的VIP等级不够"
//----------------------care------------------------
ET_StatusEqual = "已经是请求状态"
ET_AlreadyCare = "已关注"
ET_AlreadyJoinFG = "您已加入了该粉丝团"
)
var codeAndMsg = map[CodeType]string{
Success: "success",
Failure: "Failed",
ErrDbQueryError: "database query error",
ErrDbInsertError: "database insert error",
ErrDbUpdateError: "database update error",
ErrDbDeleteError: "database delete error",
ErrDbInputExist: "current input has already exists",
ErrRdsOperError: "redis operate error",
ErrElsOperError: "elastic operate error",
BusinessLevel: "business error",
ErrCardNotExists: "card does not exist",
ErrMadouCoinNotExists: "madou coin does not exist",
ErrGetRechargeType: "get recharge type error",
ErrRchgOrderNotExist: "recharge order does not exist",
ErrBuyVIPCardFail: "buy vip card failed",
ErrOperationTooFrequently: "user operation is too frequent",
UserNotEnoughMoney: "user's coin is not enough",
RedeemCodeNotExists: "code does not exist",
RedeemCodeTimesUsedUp: "code times used up",
RedeemCodeAlreadyUsed: "you have used this code",
RedeemCodeRecordNotExists: "code use record does not exist",
ErrNoData: "no data",
UserPhoneBeenBound: "phone number has been bound",
WtdrFrequencyLimits: "wtdr frequency limits,please try again later",
MediaIsNeedPay: "You have to buy the video.",
UserWatchNumNotEnough: "More than three times",
UserScoreNotEnough: "user score is not enough",
UserSocreLVLimit: "user score level limit",
AlreadyRefund: "already refund",
}
+67
View File
@@ -0,0 +1,67 @@
package srvdef
import (
"fmt"
)
// SrvErr 代替/common/error中的定义,逐渐在代码中弃掉(common/error模块)
type SrvErr struct {
Code CodeType `json:"code"`
Msg string `json:"msg"`
Tips string `json:"tips"`
}
// New create an error
func New(code CodeType, msgAndTips ...string) *SrvErr {
msg := code.Msg()
tips := code.Tip()
if len(msgAndTips) == 1 {
msg = msgAndTips[0]
} else if len(msgAndTips) == 2 {
msg, tips = msgAndTips[0], msgAndTips[1]
}
return &SrvErr{
Code: code,
Msg: msg,
Tips: tips,
}
}
// SrvErr 返回字符串
func (s *SrvErr) Error() string {
if s == nil {
return ""
}
return fmt.Sprintf("code = %d, msg = %s ,tips = %v", s.Code, s.Msg, s.Tips)
}
// SrvErr 返回error
func (s *SrvErr) ToError() error {
if s == nil {
return nil
}
return fmt.Errorf("code = %d, msg = %s ,tips = %v", s.Code, s.Msg, s.Tips)
}
func (c *SrvErr) GetMsg() string {
// 有自定义的msg就用自定义的msg
if c.Msg != "" {
return c.Msg
}
return c.Code.Msg()
}
func (c *SrvErr) GetTip() string {
// 有自定义的tip就用自定义的tip
if c.Tips != "" {
return c.Tips
}
return c.Code.Tip()
}
func (c *SrvErr) GetCode() CodeType { return c.Code }
var (
ErrDbInsert = New(ErrDbInsertError, "", "")
ErrDbQuery = New(ErrDbQueryError, "", "")
ErrDbUpdate = New(ErrDbUpdateError, "", "")
ErrDbDelete = New(ErrDbDeleteError, "", "")
)
+162
View File
@@ -0,0 +1,162 @@
package srvdef
var codeAndTip = map[CodeType]string{
Success: "成功",
Failure: "失敗",
ErrCardNotExists: "沒有會員卡信息",
ErrMadouCoinNotExists: "沒有金幣信息",
ErrGetRechargeType: "充值方式獲取錯誤",
ErrRchgOrderNotExist: "充值訂單不存在",
ErrBuyVIPCardFail: "購買會員卡失敗",
ErrOperationTooFrequently: "用戶操作太頻繁",
UserNotEnoughMoney: "用户余额不足",
UserPhoneNotBind: "手机号码未注册",
PostCommentPermissionDenied: "非会员禁止发贴和评论",
UserLoginPasswordErr: "密码错误",
ErrUserTypeNotUP: "用户不是UP主",
AccountIsNotExists: "账户不存在",
AccountErrAccessForbid: "账户禁止登陆",
AccountErrPasswordError: "账户密码错误",
GenerateTokenFailed: "生成二维码错误",
ErrMediaNotExsit: "视频不存在",
ErrTopicInfoNotExsit: "视频专题不存在",
ErrData: "数据错误",
Err: "一般错误",
ErrSubmitRchgOrder: "发起充值订单错误",
InvalidRequestReplayAttack: "非法请求",
ErrMediaSyncServer: "视频同步服务器错误",
ErrUserType: "用户无改权限",
DownloadM3u8File: "文件服务器访问失败",
MediaSyncing: "视频同步中",
MediaConnectServerFaild: "媒资库链接失败",
AtressNotExist: "演员不存在",
ErrAnnouncementNotExsit: "公告不存在",
ErrContactDetailsNotExsit: "联系方式不存在",
ErrPostCommentNotExsit: "评论不存在",
HistoryNotExist: "记录不存在",
ErrHotsearchNotExsit: "热搜不存在",
ActivityDoesNotExist: "活动不存在",
UserWatchNumNotEnough: "免费观看次数不足",
MediaIsVip: "用户不是会员",
MediaIsNeedPay: "视频需要付费",
RepeatBuy: "重复购买",
MovieTicketsNotExits: "观影券不足",
MediaIsNotNeedPay: "视频不需要支付",
PostNoNeed2Pay: "帖子不需要支付",
SystemMsgNotExist: "消息不存在",
ForbidlogNotExist: "封禁消息不存在",
AttachmentDoesNotExist: "导出记录",
UserDownloadNotEnough: "下载次数不足",
UserDownloadNeedPay: "付费视频需要先购买",
ErrNoData: "无数据",
ModifyUserNameError: "昵称!每月只能修改一次",
RedeemCodeTimesUsedUp: "該兌換碼已使用",
RedeemCodeNotExists: "兌換碼已使用不存在",
RedeemCodeAlreadyUsed: "該兌換碼已使用",
InvalidToken: "框架错误",
ErrParamError: "参数错误",
NoToken: "无token",
RoleErr: "角色错误",
ReLogin: "重复登陆",
UploadToFileSrvFailed: "文件上传失败",
ErrCaptcha: "验证码为空",
ErrCaptchaInvalid: "验证码非法",
ErrCaptchaGet: "验证码获取失败",
ErrCaptchaResend: "验证码重复发送",
ErrCaptchaSendFail: "验证码发送失败",
InvalidateToken: "非法token",
NoUA: "没有UA信息",
UploadFile: "文件上传失败",
RedisSet: "设置redis错误",
RedisGet: "获取redis值错误",
RedisDel: "删除redis值错误",
EtcdGet: "获取etcd错误",
MongoDBQueryErr: "数据库查询失败",
ErrDbQueryError: "数据库查询错误",
ErrDbInsertError: "数据库插入错误",
ErrDbUpdateError: "数据库修改错误",
ErrDbDeleteError: "数据库删除错误",
ErrDbInputExist: "数据库输入值已经存在",
ErrRdsOperError: "redis操作错误",
ErrElsOperError: "redis操作错误",
ErrTransactionsError: "数据库事务操作错误",
ErrUserPermission: "用户权限",
ErrUserNotExsit: "用户不存在",
RedeemCodeRecordNotExists: "兑换记录不存在",
MediaIsNotNeedPreSale: "视频不支持预售",
MediaIsPreSale: "预售视频",
QRcodeError: "二维码错误",
ErrRecharge: "充值相关错误",
ErrMediaRecordNotExsit: "视频记录不存在",
UserPhoneBeenBound: "手机号码已被绑定",
DatingAlreadyComplaint: "已经举报过",
WtdrFrequencyLimits: "提现操作频率限制,请稍后再试",
WtdrErroTip1: "提现数额必须是100的整数倍",
WtdrErroTip2: "赠送6元体验金,满500可兑换钻石",
WtdrErroTip3: "请填写银行名字或升级App版本",
GameWtdrErroTip7: "银行卡必须与您以前的提现卡一致,如需更改请联系客服",
FileUploadDouble: "文件重复上传",
UserRepeatSubmitRchgOrder: "未支付订单太多,有问题请联系客服",
ErrIpForbid: "IP被禁止",
UserScoreNotEnough: "用户积分不足",
UserSocreLVLimit: "用户积分等级限制",
UploaderNeedBind: "后台用户没有up主绑定",
UploaderNeedActive: "Up主没有激活",
PostCollectionNotExists: "图文专辑不存在",
MediaCollectionNotExist: "该合集或选集不存在",
ContestNotExists: "该比赛不存在",
CannotJoinContest: "该比赛无法参加",
ContestIsVip: "该比赛只限VIP参加",
MediaNotVerified: "视频未上架",
PCCategoryUnmatch: "专辑的分类和作品的分类不一致",
ErrCareNotExist: "取消关注对象不存在",
ErrUpMasterTaskNotCompleted: "up主申请任务未完成",
HGameIsVip: "游戏需要会员",
HGameIsNeedPay: "游戏需要付费",
HGameNotExist: "游戏不存在",
ErrApplyUpIsVip: "UP主申请需要vip",
UserLevelCardNotExist: "用户等级卡不存在",
UserCheckinSetupNotExist: "签到配置不存在",
GiftWeekIsExist: "礼包已领取",
UserLevelPointTooLow: "用户等级积分太低",
UserGiftHaveReceive: "任务奖励已领取",
UserGiftExistNoReceive: "没有领取资格",
DatingUpMasterNotExist: "up主不存在",
UserIsUpMaster: "用户已经是up主",
UserUpCerting: "up主认证中",
MissionSetupNotExist: "任务配置不存在",
UserIsUpMasterBind: "用户已被up主绑定",
ErrCareIsExist: "不可重复关注",
ObjectDayStatDoesNotExist: "作品统计记录不存在",
ObjectDayViewIncStatDoesNotExist: "作品增量记录不存在",
UserNotEnoughFreeLotteryCount: "免费抽奖次数不足",
PrizeOutOfStore: "奖品库存不足",
MediaSubscribedNotNeedPay: "订阅的视频无需支付",
DatingComplaintNotExist: "举报帖子不存在",
DatingUserNotExist: "楼凤不存在",
WeekCommentNumNotEnough: "评论条数不足",
UserDauInfoNotExist: "搜寻日期没有资料",
PostCommentNotUse: "发送失败,服务器错误,请稍后再试",
AlreadyRefund: "已退款",
ErrUserPointsNotExist: "积分不足",
RedeemFail: "积分兑换失败",
CrowdfundNumberHaveBuy: "购买的号码已经出售",
CouponExpired: "卡券已过期",
CouponUsed: "卡券已使用",
CouponNotFound: "没有卡券信息",
VerifyOKPostCountLimit: "上架的帖子数量达到限制",
CouponCanNotUse: "卡券无法使用",
LotteryActivityNotEnable: "抽奖活动没有启用",
CurrTimeNotActivity: "当前时间没有活动",
AIOrderStripsCountLimit: "今天AI脱衣提交次数达到上限",
NotWtdrChannel: "没有匹配到提现通过",
ComicsNotJoinActivity: "这本漫画没有参与活动",
UserRepeatDrawDiscountCard: "重复翻牌",
NoUseDiscountCard: "不可使用折扣卡",
ErrUserAccExist: "账号已存在",
AIOrderNotDel: "未完成的订单不能删除",
AIWaitOrderTooMuch: "排队中的AI订单太多,请稍后再试",
ActorNotExist: "演员不存在",
VideoRepeat: "视频重复",
ErrReqForbidden: "请求被拒",
}
+46
View File
@@ -0,0 +1,46 @@
package srvversion
import (
"fmt"
"os"
//"os"
)
var (
//每次编译时需要手动修改本字段,否则每次编译得到的服务版本号都会是默认值
SERVICE_VERSION = "V1.001.T"
//以下几个字段编译期间自动替换,无需手动操作
GIT_BRANCH = "GIT_BRANCH_FLAG"
GIT_VERSION = "GIT_VERSION_FLAG"
BUILD_DATE = "BUILD_DATE_FLAG"
BUILD_IP = "BUILD_IP_FLAG"
)
func Info() {
fmt.Printf("version:%s\n", SERVICE_VERSION)
fmt.Printf("git branch:%s\n", GIT_BRANCH)
fmt.Printf("git version:%s\n", GIT_VERSION)
fmt.Printf("build date:%s\n", BUILD_DATE)
fmt.Printf("build ip:%s\n", BUILD_IP)
os.Exit(0) //退出进程
}
func GetVersion() *Version {
return &Version{
ServiceVer: SERVICE_VERSION,
GitVer: GIT_VERSION,
BuildDate: BUILD_DATE,
BuildIp: BUILD_IP,
Branch: GIT_BRANCH,
}
}
type Version struct {
ServiceVer string `json:"serviceVer"`
GitVer string `json:"gitVer"`
BuildDate string `json:"buildDate"`
BuildIp string `json:"buildIp"`
Branch string `json:"branch"`
}
+27
View File
@@ -0,0 +1,27 @@
package swaggerutil
import (
"fmt"
"videoweb/pub/common/netutil"
"github.com/gin-gonic/gin"
swaggerFiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"
)
func isDevHost(runMode string, svcListenIp string) bool {
localIp := netutil.GetLocalIP()
return (runMode == "dev" || runMode == "debug") && localIp != svcListenIp
}
func SetupGinSwagger(runMode, svcListenIp, svcListenPort string) func(r *gin.Engine) {
return func(r *gin.Engine) {
if isDevHost(runMode, svcListenIp) {
url := ginSwagger.URL(fmt.Sprintf("http://%s:%s/swagger/doc.json", netutil.GetLocalIP(), svcListenPort))
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, url))
} else {
url := ginSwagger.URL(fmt.Sprintf("http://%s:%s/swagger/doc.json", svcListenIp, svcListenPort))
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, url))
}
}
}
+102
View File
@@ -0,0 +1,102 @@
package synclock
import (
"fmt"
"time"
"videoweb/pub/common/errors"
"videoweb/pub/redis"
)
const (
UserExtSyncLockFmt = "userExtSyncLock:%d" // 用户账户扩展信息
UserExtSyncLockExpire = 5 * time.Minute // 用户账户扩展信息 过期时间
UserAccountLockFmt = "UserAccountLockFmt:%d" // 用户账户
UserAccountLockExpire = 5 * time.Minute // 用户账户 过期时间
TraderAccountLockFmt = "TraderAccountLockFmt:%s" //商人账户
SpinLockExpire = 3 * time.Second
)
// UserExtSyncLock 通过redis按照用户ID加锁
func UserExtcLock(userId int64) bool {
v, e := redis.Setnx_NewOK(fmt.Sprintf(UserExtSyncLockFmt, userId), true, UserExtSyncLockExpire)
if !v || e != nil {
return false
}
return true
}
// UserExtSpinLock 通过redis按照用户ID加锁
func UserExtSpinLock(userId int64, lockTimeOut time.Duration) (bool, error) {
endTime := time.Now().Add(lockTimeOut)
for time.Now().Before(endTime) {
value, err := redis.Setnx_NewOK(fmt.Sprintf(UserExtSyncLockFmt, userId), true, UserExtSyncLockExpire)
if err != nil {
return false, errors.MakeWithMsg(errors.RedisError, "redis未启动")
}
if value {
return true, nil
}
time.Sleep(time.Millisecond)
}
return false, errors.MakeWithMsg(errors.RedisError, "用户操作繁忙")
}
// UserExtUnlock 按照用户ID释放锁
func UserExtUnlock(userId int64) {
redis.Del(fmt.Sprintf(UserExtSyncLockFmt, userId))
}
// UserAccountLock 通过redis按照用户ID加锁
func UserAccountLock(userId int64) bool {
v, e := redis.Setnx_NewOK(fmt.Sprintf(UserAccountLockFmt, userId), true, UserExtSyncLockExpire)
if !v || e != nil {
return false
}
return true
}
// UserAccountSpinLock 通过redis按照用户ID加锁
func UserAccountSpinLock(userId int64, lockTimeOut time.Duration) (bool, error) {
endTime := time.Now().Add(lockTimeOut)
for time.Now().Before(endTime) {
value, err := redis.Setnx_NewOK(fmt.Sprintf(UserAccountLockFmt, userId), true, UserExtSyncLockExpire)
if err != nil {
return false, errors.MakeWithMsg(errors.RedisError, "redis未启动")
}
if value {
return true, nil
}
time.Sleep(time.Millisecond)
}
return false, errors.MakeWithMsg(errors.RedisError, "用户操作繁忙")
}
// 通过redis按照商人ID加锁
func TraderAccountSpinLock(userId string, lockTimeOut time.Duration) (bool, error) {
endTime := time.Now().Add(lockTimeOut)
for time.Now().Before(endTime) {
value, err := redis.Setnx_NewOK(fmt.Sprintf(TraderAccountLockFmt, userId), true, SpinLockExpire)
if err != nil {
return false, errors.MakeWithMsg(errors.RedisError, "redis未启动")
}
if value {
return true, nil
}
time.Sleep(time.Millisecond)
}
return false, errors.MakeWithMsg(errors.RedisError, "用户操作繁忙")
}
// 按照商人ID释放锁
func TraderAccountUnlock(userId string) {
redis.Del(fmt.Sprintf(TraderAccountLockFmt, userId))
}
// UserAccountUnlock 按照用户ID释放锁
func UserAccountUnlock(userId int64) {
redis.Del(fmt.Sprintf(UserAccountLockFmt, userId))
}
+94
View File
@@ -0,0 +1,94 @@
package tgbot
import (
"videoweb/pub/common/srvdef"
)
//获取群聊id
//https://api.telegram.org/bot1803190372:AAGmrrN6xUoh7IKiv4qCVJYdCs8wLt1ffCw/getUpdates
var (
//配置
//zhihu app机器人 2048234434:AAF3XoW4iE6vAOmttTT_bofgan_PleKuaEk
//zhihu web机器人 1973289619:AAHjCZ8KQOewVfr20foHKlKVwJoulrf_OFc
tgToken = "2048234434:AAF3XoW4iE6vAOmttTT_bofgan_PleKuaEk"
//程序支付监控群tgids
programTgGroupIds = []int64{-778146822}
//天行监控组tgids
monitorTgGroupIds = []int64{-1001373963166}
//管理人员tgid(yl:1652126945)
adminTgIds = []int64{1652126945}
prefix = "[汁乎App]"
tgBot *TgBot = nil
)
func InitTgBot(token string, prefixStr string) *srvdef.SrvErr {
if tgBot != nil {
return nil
}
if prefixStr != "" {
prefix = prefixStr
}
tgBot = NewTelegramBot(token)
if serr := tgBot.Init(); nil != serr {
return serr
}
//SendToAll("支付监控测试", "Test")
return nil
}
func ReleaseTgBot() {
if tgBot == nil {
return
}
tgBot.Release()
}
// SendToAdmins 发到管理人员
func SendToAdmins(title string, info string) {
sendTo(adminTgIds, title, info)
}
// SendToProgram 发到程序
func SendToProgram(title string, info string) {
sendTo(programTgGroupIds, title, info)
}
// SendToMonitor 发到监控群组
func SendToMonitor(title string, info string) {
sendTo(monitorTgGroupIds, title, info)
}
// SendToProgram 发到所有
func SendToAll(title string, info string) {
groupIds := programTgGroupIds
groupIds = append(groupIds, monitorTgGroupIds...)
//groupIds = append(groupIds, adminTgIds...)
sendTo(groupIds, title, info)
}
func sendTo(ids []int64, title string, info string) {
if tgBot == nil {
return
}
//异步发送
go func() {
msg := prefix + "\n" + title + "\n" + info
tgBot.Send(ids, msg)
}()
}
func RegisterCommands(commands []*BotCmd) {
if tgBot == nil {
return
}
tgBot.RegisterCommands(commands)
}
func Receive() {
if tgBot == nil {
return
}
tgBot.Receive()
}
+169
View File
@@ -0,0 +1,169 @@
package tgbot
import (
"fmt"
"time"
"videoweb/pub/common/log"
"videoweb/pub/common/srvdef"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api"
)
type RSP_TYPE int
const (
TextType RSP_TYPE = 0
FileType RSP_TYPE = 1
)
type TgCmdRsp struct {
Buf []byte
BufType RSP_TYPE
}
// TgBot Telegram机器人.
type TgBot struct {
// 机器人标示;
token string
// // 管理员分组;
// admins []int64
// // 管理群分组;
// groups []int64
// 机器人引擎;
engine *tgbotapi.BotAPI
// 命令存储集;
command map[string]func(string, int64) TgCmdRsp
help string
exitRcv chan interface{}
}
// BotCmd 命令.
type BotCmd struct {
// 指令;
Key string
// 摘要;
Summary string
// 处理;
Handle func(string, int64) TgCmdRsp
}
type BotCmdList []*BotCmd
// NewBotCmd 新的命令.
func NewBotCmd(key, summary string, handle func(string, int64) TgCmdRsp) *BotCmd {
return &BotCmd{
Key: key,
Summary: summary,
Handle: handle,
}
}
// NewTelegramBot 新的Telegram机器人.
func NewTelegramBot(token string) *TgBot {
return &TgBot{
token: token,
exitRcv: make(chan interface{}),
}
}
// Init 机器人初始化.
func (tb *TgBot) Init() *srvdef.SrvErr {
var err error
tb.engine, err = tgbotapi.NewBotAPI(tb.token)
if err != nil {
log.Warn("(tgbot:Init) tgbotapi.NewBotAPI failed", log.E(err))
return srvdef.New(srvdef.BusinessLevel, "", "")
}
tb.command = make(map[string]func(string, int64) TgCmdRsp)
tb.command["help"] = tb.menu
return nil
}
// Release 机器人退出释放
func (tb *TgBot) Release() *srvdef.SrvErr {
tb.engine.StopReceivingUpdates()
close(tb.exitRcv)
return nil
}
func (tb *TgBot) menu(string, int64) TgCmdRsp {
return TgCmdRsp{Buf: []byte(tb.help), BufType: TextType}
}
// RegisterCommands 注册命令并且自动生成help.
func (tb *TgBot) RegisterCommands(commands []*BotCmd) {
for _, cmd := range commands {
tb.command[cmd.Key] = cmd.Handle
tb.help += fmt.Sprintln("/" + cmd.Key + ": " + cmd.Summary)
}
}
// Receive 接收消息(阻塞).
func (tb *TgBot) Receive() error {
log.Info("(tgbot:Receive) start")
// 设置更新频率;
cfg := tgbotapi.NewUpdate(0)
//cfg.Timeout = 1
// 得到更新管道;
updates, err := tb.engine.GetUpdatesChan(cfg)
if err != nil {
log.Warn("(tgbot:Receive) tb.engine.GetUpdatesChan failed", log.E(err))
return srvdef.New(srvdef.BusinessLevel, "", "")
}
for {
select {
case update := <-updates:
tb.ProcessMessage(&update)
case <-tb.exitRcv:
log.Info("(tgbot:Receive) exist")
break
default:
}
time.Sleep(time.Second * 1)
}
return nil
}
func (tb *TgBot) ProcessMessage(update *tgbotapi.Update) {
// 忽略非消息更新;
if update.Message == nil {
log.Info("(tgbot:ProcessMessage) message is nil")
return
}
info := fmt.Sprintf("[Name: %s%s][ID: %d][Text: %s]\n",
update.Message.From.FirstName, update.Message.From.LastName,
update.Message.Chat.ID, update.Message.Text)
log.Info(info)
if update.Message.IsCommand() {
message := tgbotapi.NewMessage(update.Message.Chat.ID, "")
handle, ok := tb.command[update.Message.Command()]
if !ok {
message.Text = "unknown command"
} else {
param := update.Message.CommandArguments()
rsp := handle(param, update.Message.Chat.ID)
switch rsp.BufType {
case FileType:
default:
message.Text = string(rsp.Buf)
}
//message.Text = handle(param)
}
tb.engine.Send(message)
}
}
func (tb *TgBot) StopReceive() {
tb.engine.StopReceivingUpdates()
close(tb.exitRcv)
}
// Send 发送消息.
func (tb *TgBot) Send(ids []int64, text string) {
for _, id := range ids {
message := tgbotapi.NewMessage(id, text)
tb.engine.Send(message)
}
}
+35
View File
@@ -0,0 +1,35 @@
package tgbot
import (
"testing"
)
func create(t *testing.T) *TgBot {
token := "1014745091:AAFkw5j3Fw4ytvWgIll6iRH1q5WooS6JO74"
robot := NewTelegramBot(token)
if err := robot.Init(); err != nil {
t.Error(err)
t.Fail()
}
// 注册命令.
cmds := BotCmdList{NewBotCmd("test", "return testfunc result", testfunc)}
robot.RegisterCommands(cmds)
return robot
}
func testfunc(string, int64) TgCmdRsp {
return TgCmdRsp{Buf: []byte("help"), BufType: TextType}
}
func TestReceive(t *testing.T) {
robot := create(t)
if err := robot.Receive(); err != nil {
t.Error(err)
t.Fail()
}
}
func TestSend(t *testing.T) {
robot := create(t)
robot.Send([]int64{790603940}, "test send")
}
+55
View File
@@ -0,0 +1,55 @@
package common
import (
"path/filepath"
"strings"
"time"
)
func BindUrl(hostname string, path ...string) string {
if hostname == "" {
return strings.TrimLeft(filepath.Join(path...), "/")
}
if len(path) == 0 {
return hostname
}
su := strings.TrimLeft(filepath.Join(path...), "/")
pr := strings.TrimRight(hostname, "/")
return pr + "/" + su
}
// 将time.Time转换成int64
func Time2Stamp(t time.Time) int64 {
return t.UnixNano() / 1e9
}
/**时间对象->字符串*/
func Time2Str() string {
const shortForm = "2006-01-01 15:04:05"
t := time.Now()
temp := time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), time.Local)
str := temp.Format(shortForm)
return str
}
/*时间戳->字符串*/
func Stamp2Str(stamp int64) string {
timeLayout := "2006-01-02 15:04:05"
str := time.Unix(stamp/1000, 0).Format(timeLayout)
return str
}
/*时间戳->时间对象*/
func Stamp2Time(stamp int64) time.Time {
stampStr := Stamp2Str(stamp)
timer := Str2Time(stampStr)
return timer
}
/**字符串->时间对象*/
func Str2Time(formatTimeStr string) time.Time {
timeLayout := "2006-01-02 15:04:05"
loc, _ := time.LoadLocation("Local")
theTime, _ := time.ParseInLocation(timeLayout, formatTimeStr, loc) //使用模板在对应时区转化为time.time类型
return theTime
}
+79
View File
@@ -0,0 +1,79 @@
package topicmessage
import (
"time"
)
/* 通信协议,第一版*/
// 用户用户注册消息
type UserRegisterMsg struct {
UserId int64 `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"` // 设备类型
Name string `json:"name" bson:"name"` // 名称
IsDirect bool `json:"isDirect" bson:"isDirect"` // true:是直推用户
DeviceId string `json:"deviceId" bson:"deviceId"` // 设备Id
IP string `json:"ip,omitempty" bson:"ip"` // IP
DistrictCode string `json:"districtCode" bson:"districtCode"` // 渠道码
InviteCode string `json:"inviteCode" bson:"inviteCode"` // 被邀请码
RegisterAt time.Time `json:"registerAt,omitempty" bson:"registerAt"` // 注册时间
UserAgent string `json:"userAgent" bson:"userAgent"` // User-Agent识别
}
// 用户第一次访问
type UserVisitMsg struct {
UserId int64 `bson:"userId" json:"userId"` // 用户Id
SumDate time.Time `bson:"sumDate" json:"sumDate"` // 记录时间
AppId int32 `json:"appId" bson:"appId"` // appID
SysType string `json:"sysType" bson:"sysType"` // 操作系统类型 安卓 IOS
DevType string `json:"devType" bson:"devType"` // 设备类型
IP string `json:"ip,omitempty" bson:"ip"` // IP
Version string `json:"version,omitempty" bson:"version"` // APP版本号
VisitAt time.Time `json:"visitAt,omitempty" bson:"visitAt"` // 访问时间
DistrictCode string `json:"districtCode" bson:"districtCode"` // 渠道码
}
type UserOrderMsg struct {
UserId int64 `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"` // 充值方式
RchgUse int64 `json:"rchgUse" bson:"rchgUse"` // 充值用途 1:金币 2:VIP
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付款成功(目前只有成功才发送)
DistrictCode string `json:"districtCode" bson:"districtCode"` // 渠道码
Rate string `json:"rate" bson:"rate"` // 渠道费率
SuccessAt time.Time `json:"successAt" bson:"successAt"` // 成功时间
CreatedAt time.Time `json:"createdAt" bson:"createdAt"` // 创建时间
}
type UserChannelEvent struct {
UserId int64 `bson:"userId" json:"userId"` // 用户Id
AppId int32 `json:"appId" bson:"appId"` // appID
DistrictCode string `json:"districtCode" bson:"districtCode"` // 渠道码
WatchCount int64 `json:"watchCount,omitempty" bson:"watchCount"` // 观看次数
WatchTime int64 `json:"watchTime,omitempty" bson:"watchTime"` // 观看时长
AdClick int64 `json:"adClick,omitempty" bson:"adClick"` // 广告点击
AppClick int64 `json:"appClick,omitempty" bson:"appClick"` // APP点击
RequestCount int64 `json:"requestCount,omitempty" bson:"requestCount"` // 请求次数
}
type MediaPlayEvent struct {
MediaId int64 `json:"mediaId" bson:"mediaId"` // 视频Id
Title string `json:"title" bson:"title"` // 标题
FileId string `json:"fileId" bson:"fileId"` // 媒资库ID
ActionType int64 `json:"actionType" bson:"actionType"` // 类型:0:播放 1:点赞 2:购买
UserId int64 `json:"userId" bson:"userId"` // 用户Id
AppId int32 `json:"appId" bson:"appId"` // appID
WatchAt time.Time `json:"watchAt" bson:"watchAt"` // 时间
}
+64
View File
@@ -0,0 +1,64 @@
package common
import (
"reflect"
"unicode"
)
// StructToMap 对象转map,去掉无效值
func StructToMap(u interface{}) (m map[string]interface{}) {
t := reflect.TypeOf(u)
v := reflect.ValueOf(u)
p := make(map[string]interface{})
for i := 0; i < t.NumField(); i++ {
fv := v.Field(i)
ft := t.Field(i)
jsonTag, _ := ft.Tag.Lookup("json")
switch fv.Kind() {
case reflect.String:
if fv.String() != "" {
p[jsonTag] = fv.Interface()
}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if fv.Int() != 0 {
p[jsonTag] = fv.Interface()
}
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
if fv.Uint() != 0 {
p[jsonTag] = fv.Interface()
}
case reflect.Float32, reflect.Float64:
if fv.Float() != 0 {
p[jsonTag] = fv.Interface()
}
default:
p[jsonTag] = fv.Interface()
}
}
return p
}
// Ucfirst () 字符串首字母大写
func Ucfirst(str string) string {
for i, v := range str {
return string(unicode.ToUpper(v)) + str[i+1:]
}
return ""
}
// Lcfirst () 字符串首字母小写
func Lcfirst(str string) string {
for i, v := range str {
return string(unicode.ToLower(v)) + str[i+1:]
}
return ""
}
// MobileBlurry ()
func MobileBlurry(str string) string {
return str[0:3] + "****" + str[7:]
}
+340
View File
@@ -0,0 +1,340 @@
package useragent
import (
"fmt"
"net/http"
"net/url"
"regexp"
"strings"
"github.com/gin-gonic/gin"
)
const DefaultVer = "1.0.0"
// Info 与前端 X-User-Agent 字段对齐,供 SSR 组装上游请求头。
type Info struct {
RawUA string
DevType string
SysType string
Ver string
BuildID string
DeviceBrand string
DeviceModel string
SystemName string
SystemVersion string
SID string
}
// BuildOptions SSR / 代理侧可选覆盖项。
type BuildOptions struct {
Ver string
SID string
// RawUA 为空时从 Request.UserAgent 读取
RawUA string
}
var (
reIOSVer = regexp.MustCompile(`OS (\d+[_\d]*) like Mac OS X`)
reAndroidVer = regexp.MustCompile(`Android ([\d.]+)`)
reAndroidModel = regexp.MustCompile(`Android\s+[\d.]+;\s*([^;)]+?)(?:\s+Build\/|\))`)
reWinNT = regexp.MustCompile(`Windows NT ([\d.]+)`)
reMacVer = regexp.MustCompile(`Mac OS X ([\d_]+)`)
reEdge = regexp.MustCompile(`(?i)Edg/([\d.]+)`)
reOpera = regexp.MustCompile(`(?i)(?:OPR|Opera)/([\d.]+)`)
reChrome = regexp.MustCompile(`(?i)Chrome/([\d.]+)`)
reSafari = regexp.MustCompile(`(?i)Version/([\d.]+).*Safari`)
reFirefox = regexp.MustCompile(`(?i)Firefox/([\d.]+)`)
reIE = regexp.MustCompile(`(?i)(?:MSIE\s|Trident.*rv:)([\d.]+)`)
)
// ParseDeviceFromUA 从浏览器原生 User-Agent 解析设备信息(服务端无 navigator)。
func ParseDeviceFromUA(ua string) (brand, model, deviceType, systemName, systemVersion string) {
ua = strings.TrimSpace(ua)
switch {
case strings.Contains(ua, "iPhone") || strings.Contains(ua, "iPad") || strings.Contains(ua, "iPod"):
systemName = "iOS"
brand = "Apple"
if m := reIOSVer.FindStringSubmatch(ua); len(m) > 1 {
systemVersion = strings.ReplaceAll(m[1], "_", ".")
}
switch {
case strings.Contains(ua, "iPad"):
model, deviceType = "iPad", "tablet"
case strings.Contains(ua, "iPod"):
model, deviceType = "iPod", "mobile"
default:
model, deviceType = "iPhone", "mobile"
}
case strings.Contains(ua, "Android"):
systemName = "Android"
if m := reAndroidVer.FindStringSubmatch(ua); len(m) > 1 {
systemVersion = m[1]
}
if m := reAndroidModel.FindStringSubmatch(ua); len(m) > 1 {
model = strings.TrimSpace(m[1])
}
brand = guessAndroidBrand(model, ua)
if strings.Contains(ua, "Mobile") {
deviceType = "mobile"
} else {
deviceType = "tablet"
}
case strings.Contains(ua, "Windows"):
systemName = "Windows"
deviceType = "desktop"
if m := reWinNT.FindStringSubmatch(ua); len(m) > 1 {
systemVersion = m[1]
}
case strings.Contains(ua, "Mac OS X") || strings.Contains(ua, "Macintosh"):
systemName = "macOS"
brand = "Apple"
model = "Mac"
deviceType = "desktop"
if m := reMacVer.FindStringSubmatch(ua); len(m) > 1 {
systemVersion = strings.ReplaceAll(m[1], "_", ".")
}
case strings.Contains(ua, "Linux"):
systemName = "Linux"
deviceType = "desktop"
default:
systemName = "Other"
deviceType = "desktop"
}
return
}
func guessAndroidBrand(model, ua string) string {
s := model + " " + ua
rules := []struct {
re *regexp.Regexp
brand string
}{
{regexp.MustCompile(`(?i)Build/HONOR|\bHONOR\b`), "Honor"},
{regexp.MustCompile(`(?i)Build/HUAWEI|HUAWEI|HW-`), "Huawei"},
{regexp.MustCompile(`(?i)SM-|samsung|Galaxy`), "Samsung"},
{regexp.MustCompile(`(?i)Pixel`), "Google"},
{regexp.MustCompile(`(?i)Redmi|POCO|XiaoMi|\bMi\b`), "Xiaomi"},
{regexp.MustCompile(`(?i)OPPO|\bCPH\d{3,4}`), "OPPO"},
{regexp.MustCompile(`(?i)vivo|\bV\d{4}`), "vivo"},
{regexp.MustCompile(`(?i)OnePlus|ONEPLUS`), "OnePlus"},
{regexp.MustCompile(`(?i)realme|\bRMX\d{3,4}`), "realme"},
}
for _, r := range rules {
if r.re.MatchString(s) {
return r.brand
}
}
return ""
}
func detectSysType(ua string) string {
uaLower := strings.ToLower(ua)
switch {
case strings.Contains(uaLower, "android"):
return "h5_android"
case strings.Contains(uaLower, "iphone"), strings.Contains(uaLower, "ipad"), strings.Contains(uaLower, "ipod"):
return "h5_ios"
default:
return "h5_pc"
}
}
func parseBrowser(ua string) (name, version string) {
switch {
case reEdge.MatchString(ua):
m := reEdge.FindStringSubmatch(ua)
return "Edge", m[1]
case reOpera.MatchString(ua):
m := reOpera.FindStringSubmatch(ua)
return "Opera", m[1]
case reChrome.MatchString(ua) && !reEdge.MatchString(ua):
m := reChrome.FindStringSubmatch(ua)
return "Chrome", m[1]
case reSafari.MatchString(ua):
m := reSafari.FindStringSubmatch(ua)
return "Safari", m[1]
case reFirefox.MatchString(ua):
m := reFirefox.FindStringSubmatch(ua)
return "Firefox", m[1]
case reIE.MatchString(ua):
m := reIE.FindStringSubmatch(ua)
return "IE", m[1]
default:
return "", ""
}
}
func buildDevType(brand, model, deviceType string) string {
parts := make([]string, 0, 3)
for _, v := range []string{brand, model, deviceType} {
v = strings.TrimSpace(v)
if v != "" {
parts = append(parts, v)
}
}
if len(parts) < 3 {
return "Apple iPhone mobile"
}
return strings.Join(parts, " ")
}
// BuildInfo 根据原始 UA 组装结构化信息。
func BuildInfo(opts BuildOptions) Info {
ua := strings.TrimSpace(opts.RawUA)
ver := strings.TrimSpace(opts.Ver)
if ver == "" {
ver = DefaultVer
}
brand, model, deviceType, systemName, systemVersion := ParseDeviceFromUA(ua)
browserName, browserVer := parseBrowser(ua)
buildID := strings.TrimSpace(strings.TrimSpace(browserName + " " + browserVer))
return Info{
RawUA: ua,
DevType: buildDevType(brand, model, deviceType),
SysType: detectSysType(ua),
Ver: ver,
BuildID: buildID,
DeviceBrand: brand,
DeviceModel: model,
SystemName: systemName,
SystemVersion: systemVersion,
SID: strings.TrimSpace(opts.SID),
}
}
// String 输出 X-User-Agent Header 值。
func (info Info) String() string {
return fmt.Sprintf(
"DevType=%s;SysType=%s;Ver=%s;BuildID=%s;device_brand=%s;device_model=%s;system_name=%s;system_version=%s;sid=%s",
info.DevType,
info.SysType,
info.Ver,
info.BuildID,
info.DeviceBrand,
info.DeviceModel,
info.SystemName,
info.SystemVersion,
info.SID,
)
}
// BuildXUserAgent 直接返回 Header 字符串。
func BuildXUserAgent(opts BuildOptions) string {
return BuildInfo(opts).String()
}
// SIDFromRequest 从 cookie 读取 sid(与前端约定:sid / _s_token_)。
func SIDFromRequest(r *http.Request) string {
if r == nil {
return ""
}
if c, err := r.Cookie("sid"); err == nil && c != nil {
if v := strings.TrimSpace(c.Value); v != "" {
return v
}
}
if c, err := r.Cookie("_s_token_"); err == nil && c != nil {
v := strings.TrimSpace(c.Value)
// 可能是 JSON:{"sid":"..."} 或纯字符串
if strings.HasPrefix(v, "{") {
const key = `"sid"`
if i := strings.Index(v, key); i >= 0 {
rest := v[i+len(key):]
if j := strings.Index(rest, `"`); j >= 0 {
rest = rest[j+1:]
if k := strings.Index(rest, `"`); k >= 0 {
return rest[:k]
}
}
}
}
return v
}
return ""
}
// BuildXUserAgentFromRequest 供 SSR 反向代理上游 API 使用。
// 优先透传客户端已带的 X-User-Agent;否则用浏览器 UA 组装。
func BuildXUserAgentFromRequest(c *gin.Context, ver string) string {
if c == nil || c.Request == nil {
return BuildXUserAgent(BuildOptions{Ver: ver})
}
if xua := strings.TrimSpace(c.GetHeader("X-User-Agent")); xua != "" {
return xua
}
return BuildXUserAgent(BuildOptions{
Ver: ver,
SID: SIDFromRequest(c.Request),
RawUA: c.Request.UserAgent(),
})
}
// TokenFromRequest 从 cookie 读取登录 token(与前端约定:token / _token_)。
func TokenFromRequest(r *http.Request) string {
if r == nil {
return ""
}
for _, name := range []string{"token", "_token_"} {
if c, err := r.Cookie(name); err == nil && c != nil {
if v := cookieUnescape(c.Value); v != "" {
return v
}
}
}
return ""
}
func cookieUnescape(raw string) string {
raw = strings.TrimSpace(raw)
if raw == "" {
return ""
}
if decoded, err := url.QueryUnescape(raw); err == nil {
return strings.TrimSpace(decoded)
}
return raw
}
// ClientIPFromRequest 优先透传已有 X-Real-IP / X-Forwarded-For,否则用远端地址。
func ClientIPFromRequest(c *gin.Context) string {
if c == nil || c.Request == nil {
return ""
}
if ip := strings.TrimSpace(c.GetHeader("X-Real-IP")); ip != "" {
return ip
}
if xff := strings.TrimSpace(c.GetHeader("X-Forwarded-For")); xff != "" {
if i := strings.Index(xff, ","); i >= 0 {
return strings.TrimSpace(xff[:i])
}
return xff
}
return strings.TrimSpace(c.ClientIP())
}
// BuildUpstreamHeader 组装上游 API 请求头:X-Real-IP、User-Agent、X-User-Agent。
// 登录 token / sid 已在 cookie 中时由调用方按需写入 Authorization 等字段。
func BuildUpstreamHeader(c *gin.Context, ver string) map[string]string {
header := make(map[string]string, 8)
ApplyToHeader(header, c, ver)
return header
}
// ApplyToHeader 将上游必需头写入 header map:
// X-Real-IP、User-Agent、X-User-Agent。
func ApplyToHeader(header map[string]string, c *gin.Context, ver string) {
if header == nil {
return
}
if c != nil && c.Request != nil {
if ip := ClientIPFromRequest(c); ip != "" {
header["X-Real-IP"] = ip
}
if ua := strings.TrimSpace(c.Request.UserAgent()); ua != "" {
header["User-Agent"] = ua
}
}
header["X-User-Agent"] = BuildXUserAgentFromRequest(c, ver)
}
+53
View File
@@ -0,0 +1,53 @@
package useragent
import (
"strings"
"testing"
)
func TestBuildXUserAgent_iPhone(t *testing.T) {
ua := "Mozilla/5.0 (iPhone; CPU iPhone OS 17_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Mobile/15E148 Safari/604.1"
got := BuildXUserAgent(BuildOptions{RawUA: ua, Ver: "1.0.0", SID: "abc"})
wantParts := []string{
"DevType=Apple iPhone mobile",
"SysType=h5_ios",
"Ver=1.0.0",
"BuildID=Safari 17.2",
"device_brand=Apple",
"device_model=iPhone",
"system_name=iOS",
"system_version=17.2",
"sid=abc",
}
for _, p := range wantParts {
if !strings.Contains(got, p) {
t.Fatalf("missing %q in %s", p, got)
}
}
}
func TestBuildXUserAgent_Android(t *testing.T) {
ua := "Mozilla/5.0 (Linux; Android 13; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36"
got := BuildXUserAgent(BuildOptions{RawUA: ua, Ver: "1.0.0", SID: "s1"})
wantParts := []string{
"SysType=h5_android",
"device_brand=Google",
"device_model=Pixel 7",
"system_name=Android",
"system_version=13",
"BuildID=Chrome 120.0.0.0",
"sid=s1",
}
for _, p := range wantParts {
if !strings.Contains(got, p) {
t.Fatalf("missing %q in %s", p, got)
}
}
}
func TestBuildXUserAgent_EmptyFallsBackDevType(t *testing.T) {
got := BuildXUserAgent(BuildOptions{RawUA: "", Ver: "1.0.0"})
if !strings.Contains(got, "DevType=Apple iPhone mobile") || !strings.Contains(got, "SysType=h5_pc") {
t.Fatalf("unexpected fallback: %s", got)
}
}
+97
View File
@@ -0,0 +1,97 @@
package useragent
import (
"net/url"
"strings"
"videoweb/pub/common/enum/consts"
"videoweb/pub/common/log"
"github.com/gin-gonic/gin"
)
type UA struct {
DevID string
DevType string
SysType string
Ver string
BuildID string
}
func IsH5(sysType string) bool {
if sysType == consts.H5IOS || sysType == consts.H5Android || sysType == consts.H5PC || sysType == consts.H5 || sysType == consts.Light {
return true
} else {
return false
}
}
func (s UA) IsH5() bool {
return IsH5(s.SysType)
}
func UserAgent(c *gin.Context) {
var uastr string
ua := UA{}
userAgent := c.GetHeader("X-User-Agent") //兼容H5 UserAgent
if userAgent == "" {
userAgent = c.Request.UserAgent()
}
if userAgent != "" {
// "BuildID=com.abc.Butterfly;SysType=h5;DevID=B9A00938-FE68-4686-9461-A365C7E5110D;Ver=0.0.1;DevType=iPhone;"
decodeUa, err := url.QueryUnescape(userAgent)
if err != nil {
log.Warn("URLDecode user-agent error", log.Any("ua", userAgent))
uastr = userAgent
} else {
uastr = decodeUa
}
uas := strings.Split(uastr, ";")
for _, v := range uas {
vss := strings.Split(strings.TrimSpace(v), "=")
if len(vss) < 2 {
c.Set(consts.CtxUAH5, vss[0])
// log.Warn("user-agent miss", log.Any("ua", userAgent), log.Any("v", v))
continue
}
switch strings.TrimSpace(vss[0]) {
case "DevID":
ua.DevID = strings.TrimSpace(vss[1])
c.Set(consts.CtxDevId, ua.DevID)
case "DevType":
ua.DevType = strings.TrimSpace(vss[1])
c.Set(consts.CtxDevType, ua.DevType)
case "SysType":
IntputSysType := strings.TrimSpace(vss[1])
if strings.Contains(IntputSysType, "h5_android") {
ua.SysType = consts.H5Android
} else if strings.Contains(IntputSysType, "h5_ios") {
ua.SysType = consts.H5IOS
} else if strings.Contains(IntputSysType, "h5_pc") {
ua.SysType = consts.H5PC
} else if strings.Contains(IntputSysType, "android") {
ua.SysType = consts.Android
} else if strings.Contains(IntputSysType, "ios") {
ua.SysType = consts.IOS
} else if strings.Contains(IntputSysType, "h5") {
ua.SysType = consts.H5
} else if strings.Contains(IntputSysType, "pc") {
ua.SysType = consts.PC
} else if strings.Contains(IntputSysType, "light") {
ua.SysType = consts.Light
}
c.Set(consts.CtxSysType, ua.SysType)
case "Ver":
ua.Ver = strings.TrimSpace(vss[1])
c.Set(consts.CtxVersion, ua.Ver)
case "BuildID":
ua.BuildID = strings.TrimSpace(vss[1])
c.Set(consts.CtxBuildId, ua.BuildID)
default:
log.Warn("user-agent unknown", log.Any("ua", userAgent))
}
}
} else {
log.Warn("user-agent empty")
}
c.Set(consts.CtxUA, ua)
}
+292
View File
@@ -0,0 +1,292 @@
package utils
import (
"math/rand"
"reflect"
)
//数组求交集,差集,并集
/*求并集**/
func Union(slice1, slice2 []string) []string {
m := make(map[string]int)
for _, v := range slice1 {
m[v]++
}
for _, v := range slice2 {
times, _ := m[v]
if times == 0 {
slice1 = append(slice1, v)
}
}
return slice1
}
// 求交集
func Intersect(slice1, slice2 []string) []string {
m := make(map[string]int)
nn := make([]string, 0)
for _, v := range slice1 {
m[v]++
}
for _, v := range slice2 {
times, _ := m[v]
if times == 1 {
nn = append(nn, v)
}
}
return nn
}
// 求交集
func IntersectInt64(slice1, slice2 []int64) []int64 {
m := make(map[int64]int)
nn := make([]int64, 0)
for _, v := range slice1 {
m[v]++
}
for _, v := range slice2 {
times, _ := m[v]
if times == 1 {
nn = append(nn, v)
}
}
return nn
}
// 求差集 slice1-并集
func Difference(slice1, slice2 []string) []string {
m := make(map[string]int)
nn := make([]string, 0)
inter := Intersect(slice1, slice2)
for _, v := range inter {
m[v]++
}
for _, value := range slice1 {
times, _ := m[value]
if times == 0 {
nn = append(nn, value)
}
}
return nn
}
// 求差集 slice1-并集
func DifferenceInt64(slice1, slice2 []int64) []int64 {
m := make(map[int64]int)
nn := make([]int64, 0)
inter := IntersectInt64(slice1, slice2)
for _, v := range inter {
m[v]++
}
for _, value := range slice1 {
times, _ := m[value]
if times == 0 {
nn = append(nn, value)
}
}
return nn
}
// 去重
func RemoveRepeat(slc []int64) []int64 {
result := []int64{} // 存放结果
for i := range slc {
flag := true
for j := range result {
if slc[i] == result[j] {
flag = false // 存在重复元素,标识为false
break
}
}
if flag { // 标识为false,不添加进结果
result = append(result, slc[i])
}
}
return result
}
func IncludeInt32(slc []int32, n int32) bool {
for _, v := range slc {
if v == n {
return true
}
}
return false
}
func IncludeInt64(slc []int64, n int64) bool {
for _, v := range slc {
if v == n {
return true
}
}
return false
}
func RemoveInt64(slc []int64, n int64) []int64 {
for i := 0; i < len(slc); {
if slc[i] == n {
slc = append(slc[:i], slc[i+1:]...)
} else {
i++
}
}
return slc
}
func RemoveString(slc []string, n string) []string {
for i := 0; i < len(slc); {
if slc[i] == n {
slc = append(slc[:i], slc[i+1:]...)
} else {
i++
}
}
return slc
}
// RemoveRepeatString 去重string
func RemoveRepeatString(slc []string) []string {
result := []string{} // 存放结果
for i := range slc {
flag := true
for j := range result {
if slc[i] == result[j] {
flag = false // 存在重复元素,标识为false
break
}
}
if flag { // 标识为false,不添加进结果
result = append(result, slc[i])
}
}
return result
}
func IncludeString(slc []string, s string) bool {
for _, v := range slc {
if v == s {
return true
}
}
return false
}
func EqualInt64s(slice1, slice2 []int64) bool {
if len(slice1) != len(slice2) {
return false
}
for _, v1 := range slice1 {
find := false
for _, v2 := range slice2 {
if v1 == v2 {
find = true
break
}
}
if !find {
return false
}
}
return true
}
func EqualInt64WithOrder(slice1, slice2 []int64) bool {
if len(slice1) != len(slice2) {
return false
}
for k, v1 := range slice1 {
if slice2[k] != v1 {
return false
}
}
return true
}
func EqualString(slice1, slice2 []string) bool {
if len(slice1) != len(slice2) {
return false
}
for _, v1 := range slice1 {
find := false
for _, v2 := range slice2 {
if v1 == v2 {
find = true
break
}
}
if find == false {
return false
}
}
return true
}
// 切片乱序
func ShuffleSlice(slice interface{}) {
rv := reflect.ValueOf(slice)
if rv.Type().Kind() != reflect.Slice {
return
}
length := rv.Len()
if length < 2 {
return
}
swap := reflect.Swapper(slice)
//rand.Seed(time.Now().Unix())
for i := length - 1; i >= 0; i-- {
j := rand.Intn(length)
swap(i, j)
}
return
}
func RemoveOneInt64(slc []int64, n int64) []int64 {
for i := 0; i < len(slc); {
if slc[i] == n {
slc = append(slc[:i], slc[i+1:]...)
break
} else {
i++
}
}
return slc
}
// 求最大值
func SliceMaxInt64(slc []int64) int64 {
maxVal := slc[0]
for i := 1; i < len(slc); i++ {
//从第二个 元素开始循环比较,如果发现有更大的,则交换
if maxVal < slc[i] {
maxVal = slc[i]
}
}
return maxVal
}
// 求最大值
func SliceMinInt64(slc []int64) int64 {
minVal := slc[0]
for i := 1; i < len(slc); i++ {
//从第二个 元素开始循环比较,如果发现有更小的,则交换
if minVal > slc[i] {
minVal = slc[i]
}
}
return minVal
}
func ReverseAny(s interface{}) {
n := reflect.ValueOf(s).Len()
swap := reflect.Swapper(s)
for i, j := 0, n-1; i < j; i, j = i+1, j-1 {
swap(i, j)
}
}
+184
View File
@@ -0,0 +1,184 @@
package utils
import (
"encoding/json"
"path/filepath"
"strings"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
)
type ID = primitive.ObjectID
// ToJsonM 将struct转换为 Json Map
func ToJsonM(obj interface{}) (map[string]interface{}, error) {
j, err := json.Marshal(obj)
if err != nil {
return nil, err
}
m := make(map[string]interface{})
if err := json.Unmarshal(j, &m); err != nil {
return nil, err
}
return m, err
}
// 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
}
func MapToJsonString(m map[string]string) (string, error) {
if data, err := json.Marshal(m); err != nil {
return "", err
} else {
return string(data), nil
}
}
func StructToJsonString(s interface{}) (string, error) {
j, err := json.Marshal(s)
if err != nil {
return "", err
}
m := make(map[string]interface{})
if err := json.Unmarshal(j, &m); err != nil {
return "", err
}
if data, err := json.Marshal(m); err != nil {
return "", err
} else {
return string(data), nil
}
}
// BMapToStruct s is struct ptr
func BMapToStruct(s interface{}, m map[string]interface{}) error {
bsonBytes, err := bson.Marshal(m)
if err != nil {
return err
}
return bson.Unmarshal(bsonBytes, s)
}
// JMapToStruct s is struct ptr
func JMapToStruct(s interface{}, m map[string]interface{}) error {
bsonBytes, err := json.Marshal(m)
if err != nil {
return err
}
return json.Unmarshal(bsonBytes, s)
}
// 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
}
func IDArray(ids []string) ([]ID, error) {
var idArray []ID
for _, id := range ids {
_id, err := primitive.ObjectIDFromHex(id)
if err != nil {
return nil, err
}
idArray = append(idArray, _id)
}
return idArray, nil
}
// 拼接https://
func BindHttpSch(path string) string {
if strings.HasPrefix(path, "http") {
return path
} else {
return "https://" + path
}
}
func BindUrl(hostname string, path ...string) string {
if hostname == "" {
return strings.TrimLeft(filepath.Join(path...), "/")
}
if len(path) == 0 {
return hostname
}
su := strings.TrimLeft(filepath.Join(path...), "/")
pr := strings.TrimRight(hostname, "/")
return pr + "/" + su
}
func ObjectIDs2String(obj []primitive.ObjectID) []string {
arr := []string{}
if len(obj) == 0 {
return arr
}
for _, o := range obj {
arr = append(arr, o.Hex())
}
return arr
}
func String2ObjectID(obj []string) []primitive.ObjectID {
arr := []primitive.ObjectID{}
for _, o := range obj {
oid, err := primitive.ObjectIDFromHex(o)
if err != nil {
continue
}
arr = append(arr, oid)
}
return arr
}
// PercentOf - calculate what percent [number1] is of [number2].
// ex. 300 is 12.5% of 2400
func PercentOf(part int, total int) float64 {
return (float64(part) * float64(100)) / float64(total)
}
func MergeMap(src, dest map[string]interface{}) map[string]interface{} {
if len(src) == 0 {
return dest
}
if len(dest) == 0 {
return src
}
if len(src) == 0 && len(dest) == 0 {
return make(map[string]interface{})
}
for k, v := range dest {
src[k] = v
}
return src
}
+130
View File
@@ -0,0 +1,130 @@
package utils
import (
"bytes"
"encoding/gob"
"reflect"
"time"
)
// DeepCopy 深copy
func DeepCopy(dst, src interface{}) error {
var buf bytes.Buffer
if err := gob.NewEncoder(&buf).Encode(src); err != nil {
return err
}
return gob.NewDecoder(bytes.NewBuffer(buf.Bytes())).Decode(dst)
}
// Interface for delegating copy process to type
type Interface interface {
DeepCopy() interface{}
}
// Iface is an alias to Copy; this exists for backwards compatibility reasons.
func Iface(iface interface{}) interface{} {
return Copy(iface)
}
// Copy creates a deep copy of whatever is passed to it and returns the copy
// in an interface{}. The returned value will need to be asserted to the
// correct type.
func Copy(src interface{}) interface{} {
if src == nil {
return nil
}
// Make the interface a reflect.Value
original := reflect.ValueOf(src)
// Make a copy of the same type as the original.
cpy := reflect.New(original.Type()).Elem()
// Recursively copy the original.
copyRecursive(original, cpy)
// Return the copy as an interface.
return cpy.Interface()
}
// copyRecursive does the actual copying of the interface. It currently has
// limited support for what it can handle. Add as needed.
func copyRecursive(original, cpy reflect.Value) {
// check for implement deepcopy.Interface
if original.CanInterface() {
if copier, ok := original.Interface().(Interface); ok {
cpy.Set(reflect.ValueOf(copier.DeepCopy()))
return
}
}
// handle according to original's Kind
switch original.Kind() {
case reflect.Ptr:
// Get the actual value being pointed to.
originalValue := original.Elem()
// if it isn't valid, return.
if !originalValue.IsValid() {
return
}
cpy.Set(reflect.New(originalValue.Type()))
copyRecursive(originalValue, cpy.Elem())
case reflect.Interface:
// If this is a nil, don't do anything
if original.IsNil() {
return
}
// Get the value for the interface, not the pointer.
originalValue := original.Elem()
// Get the value by calling Elem().
copyValue := reflect.New(originalValue.Type()).Elem()
copyRecursive(originalValue, copyValue)
cpy.Set(copyValue)
case reflect.Struct:
t, ok := original.Interface().(time.Time)
if ok {
cpy.Set(reflect.ValueOf(t))
return
}
// Go through each field of the struct and copy it.
for i := 0; i < original.NumField(); i++ {
// The Type's StructField for a given field is checked to see if StructField.PkgPath
// is set to determine if the field is exported or not because CanSet() returns false
// for settable fields. I'm not sure why. -mohae
if original.Type().Field(i).PkgPath != "" {
continue
}
copyRecursive(original.Field(i), cpy.Field(i))
}
case reflect.Slice:
if original.IsNil() {
return
}
// Make a new slice and copy each element.
cpy.Set(reflect.MakeSlice(original.Type(), original.Len(), original.Cap()))
for i := 0; i < original.Len(); i++ {
copyRecursive(original.Index(i), cpy.Index(i))
}
case reflect.Map:
if original.IsNil() {
return
}
cpy.Set(reflect.MakeMap(original.Type()))
for _, key := range original.MapKeys() {
originalValue := original.MapIndex(key)
copyValue := reflect.New(originalValue.Type()).Elem()
copyRecursive(originalValue, copyValue)
copyKey := Copy(key.Interface())
cpy.SetMapIndex(reflect.ValueOf(copyKey), copyValue)
}
default:
cpy.Set(original)
}
}
+56
View File
@@ -0,0 +1,56 @@
package utils
import "os"
// CreateEmptyFile 创建一个固定size大小的空文件
// 如果seek文件失败,则删除文件,并返回错误信息
func CreateEmptyFile(name string, size int64) (f *os.File, err error) {
defer func() {
if err != nil && f != nil {
f.Close()
os.Remove(name)
}
}()
f, err = os.OpenFile(name, os.O_APPEND|os.O_CREATE|os.O_RDWR, 0666)
if err != nil {
return
}
if _, err = f.Seek(size-1, 0); err != nil {
return
}
_, err = f.Write([]byte{0})
return
}
func WriteFile(data []byte, filename string) (err error) {
defer func() {
if err != nil {
os.Remove(filename)
}
}()
pi := PathForInfo(filename)
if pi.Dir != "" {
if err = os.MkdirAll(pi.Dir, 0755); err != nil {
return
}
}
f, err := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_RDWR, 0666)
if err != nil {
return
}
_, err = f.Write(data)
f.Close()
return
}
func PathExists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
}
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
+12
View File
@@ -0,0 +1,12 @@
package utils
// // MakeCrc
// func MakeCrc(s string) uint32 {
// return crc32.ChecksumIEEE([]byte(s))
// }
// func Md5V(str string) string {
// h := md5.New()
// h.Write([]byte(str))
// return hex.EncodeToString(h.Sum(nil))
// }
+283
View File
@@ -0,0 +1,283 @@
package utils
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net"
"net/http"
"net/url"
"strings"
"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: 60 * time.Second, //限制读取response header的时间,默认 timeout + 5*time.Second
ExpectContinueTimeout: 1 * time.Second, //限制client在发送包含 Expect: 100-continue的header到收到继续发送body的response之间的时间等待。
MaxIdleConns: 100, //所有host的连接池最大连接数量,默认无穷大
MaxIdleConnsPerHost: 300, //每个host的连接池最大空闲连接数,默认2
MaxConnsPerHost: 100, //每个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 HttpDelete(url string, header http.Header, body io.Reader, onResponse OnHttpResponse) error {
return HttpMethod(url, http.MethodDelete, 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)
})
}
func POSTJson(url string, headers map[string]string, data interface{}) (*http.Response, error) {
header := make(http.Header)
for k, v := range headers {
header.Set(k, v)
}
body, err := getBytes(data)
if err != nil {
return nil, err
}
req, err := http.NewRequest("POST", url, bytes.NewReader(body))
if err != nil {
return nil, err
}
req.Header = header
client := GetHttpClient()
return client.Do(req)
}
func GetWithParam(url string, headers map[string]string, params ...interface{}) (*http.Response, error) {
header := make(http.Header)
for k, v := range headers {
header.Set(k, v)
}
for _, p := range params {
url = addParams(url, toUrlValues(p))
}
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
req.Header = header
client := GetHttpClient()
return client.Do(req)
}
func GetWithJResp(bind interface{}, url string, headers map[string]string, params ...interface{}) (int, error) {
header := make(http.Header)
for k, v := range headers {
header.Set(k, v)
}
for _, p := range params {
url = addParams(url, toUrlValues(p))
}
code := http.StatusBadRequest
err := HttpGet(url, header, nil, func(res *http.Response) error {
code = res.StatusCode
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, bind)
})
return code, err
}
func POSTWithJResp(bind interface{}, url string, headers map[string]string, params interface{}) (int, error) {
header := make(http.Header)
for k, v := range headers {
header.Set(k, v)
}
body, err := getBytes(params)
if err != nil {
return http.StatusBadRequest, err
}
code := http.StatusBadRequest
err = HttpPost(url, header, bytes.NewReader(body), func(res *http.Response) error {
code = res.StatusCode
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, bind)
})
return code, err
}
func POSTJsonWithJResp(bind interface{}, url string, headers map[string]string, data interface{}) (int, error) {
header := make(http.Header)
for k, v := range headers {
header.Set(k, v)
}
header.Set("Content-Type", "application/json")
body, err := getBytes(data)
if err != nil {
return http.StatusBadRequest, err
}
code := http.StatusBadRequest
err = HttpPost(url, header, bytes.NewReader(body), func(res *http.Response) error {
code = res.StatusCode
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, bind)
})
return code, err
}
func DeleteWithJResp(bind interface{}, url string, headers map[string]string, params interface{}) (int, error) {
header := make(http.Header)
for k, v := range headers {
header.Set(k, v)
}
body, err := getBytes(params)
if err != nil {
return http.StatusBadRequest, err
}
code := http.StatusBadRequest
err = HttpDelete(url, header, bytes.NewReader(body), func(res *http.Response) error {
code = res.StatusCode
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, bind)
})
return code, err
}
// Add params to a url string.
func addParams(url_ string, params url.Values) string {
if len(params) == 0 {
return url_
}
if !strings.Contains(url_, "?") {
url_ += "?"
}
if strings.HasSuffix(url_, "?") || strings.HasSuffix(url_, "&") {
url_ += params.Encode()
} else {
url_ += "&" + params.Encode()
}
return url_
}
func toUrlValues(v interface{}) url.Values {
switch t := v.(type) {
case url.Values:
return t
case map[string][]string:
return url.Values(t)
case map[string]string:
rst := make(url.Values)
for k, v := range t {
rst.Add(k, v)
}
return rst
case nil:
return make(url.Values)
default:
panic("Invalid value")
}
}
+127
View File
@@ -0,0 +1,127 @@
package utils
import (
"errors"
"strings"
"time"
"github.com/dgrijalva/jwt-go"
)
const (
userIDKey = "userId"
ipKey = "ip"
uaKey = "ua"
)
type IPClaims struct {
Ip string
Expire time.Duration
UA string
}
func GenerateIpToken(claims IPClaims, secret string) (string, error) {
return generateToken(jwt.MapClaims{
ipKey: claims.Ip,
uaKey: claims.UA,
"exp": Now().Add(claims.Expire).Unix(),
"issuer": "com.butterfly",
"sub": "asign",
"aud": "public",
}, []byte(secret))
}
func ParseIpToken(tokenString string, secret string) (pass bool, ip, ua string, err error) {
t, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
return []byte(secret), nil
})
var isExpire *bool
if vErr, ok := err.(*jwt.ValidationError); ok {
e := vErr.Errors&jwt.ValidationErrorExpired > 0
isExpire = &e
}
if err != nil || isExpire == nil {
return
}
var ok bool
var jc jwt.MapClaims
jc, ok = t.Claims.(jwt.MapClaims)
if !ok {
err = errors.New("parse token fail")
return
}
ip, ok = jc[ipKey].(string)
if !ok {
err = errors.New("parse token fail")
return
}
ua, ok = jc[uaKey].(string)
if !ok {
err = errors.New("parse token fail")
return
}
pass = true
return
}
type UserClaims struct {
UserID int64
Expire time.Duration
}
func generateToken(claims jwt.MapClaims, secret []byte) (string, error) {
return jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString(secret)
}
func GenerateUserToken(claims UserClaims, secret string) (string, error) {
return generateToken(jwt.MapClaims{
userIDKey: claims.UserID,
"exp": Now().Add(claims.Expire).Unix(),
"issuer": "com.butterfly",
"sub": "asign",
"aud": "public",
}, []byte(secret))
}
// ParseToken 解析Token
func ParseUserToken(token string, secret string) (userId int64, newToken string, err error) {
t, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) {
return []byte(secret), nil
})
var isExpire *bool
if vErr, ok := err.(jwt.ValidationError); ok {
e := vErr.Errors&jwt.ValidationErrorExpired > 0
isExpire = &e
}
if err != nil && isExpire == nil {
return
}
jc, ok := t.Claims.(jwt.MapClaims)
if !ok {
err = errors.New("unknown token error1")
return
}
id, ok := jc[userIDKey].(float64)
if !ok {
err = errors.New("unknown token error1")
return
}
userId = int64(id)
if isExpire != nil && *isExpire {
newToken, err = GenerateUserToken(UserClaims{UserID: userId, Expire: 30 * 24 * time.Hour}, secret)
return
}
err = nil
return
}
// Strips 'Bearer ' prefix from bearer token string
func StripBearerPrefixFromTokenString(tok string) string {
if len(tok) > 6 && strings.ToUpper(tok[0:7]) == "BEARER " {
return tok[7:]
} else {
return tok
}
}
+20
View File
@@ -0,0 +1,20 @@
package utils
import (
"math/rand"
"time"
)
// CountDigits 有几位数字
func CountDigits(i int) (count int) {
for i != 0 {
i /= 10
count++
}
return
}
func RandInt(min int, max int) int {
rand.Seed(time.Now().UnixNano())
return min + rand.Intn(max-min)
}
+19
View File
@@ -0,0 +1,19 @@
package utils
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
}
+72
View File
@@ -0,0 +1,72 @@
package utils
import (
"path/filepath"
"strings"
"github.com/PuerkitoBio/purell"
)
// 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
}
// JoinUrlPath 域名与uri拼接(走 purell 标准化,会剥掉尾部斜杠)。
// 适用于 CDN 文件链接(m3u8 分段、vtt、静态图等)这类"不需要尾部斜杠"的场景。
// 需要保留尾部斜杠的页面 URL(sitemap/rss/indexnow)请使用 JoinUrlPathKeepSlash。
func JoinUrlPath(host string, uri string) (urlpath string) {
urlpath = joinRawUrlPath(host, uri)
if urlpath == "" {
return
}
// 标准化 URL
normalized, err := purell.NormalizeURLString(urlpath, purell.FlagsUsuallySafeGreedy)
if err != nil {
return urlpath
}
return normalized
}
// JoinUrlPathKeepSlash 域名与uri拼接,保留 uri 原始的尾部斜杠,不做任何标准化。
// sitemap/rss/indexnow 的页面 URL 必须与路由注册的 /archives/:id/ 等完全一致,
// 否则爬虫每次访问都会被 gin 的 RedirectTrailingSlash 301 到带 / 的地址,
// 浪费抓取预算并造成重定向链。
func JoinUrlPathKeepSlash(host string, uri string) string {
return joinRawUrlPath(host, uri)
}
// joinRawUrlPath 纯拼接,不做任何标准化。
func joinRawUrlPath(host string, uri string) (urlpath string) {
if uri == "" {
return
}
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
}
+56
View File
@@ -0,0 +1,56 @@
package utils
import (
"bytes"
"time"
"videoweb/pub/common/enum/redisconst"
sErr "videoweb/pub/common/errors"
"videoweb/pub/redis"
)
const (
expireTime = 30 * 24 * time.Hour
)
type TokenRedis struct {
}
func NewTokenRedis() *TokenRedis {
return &TokenRedis{}
}
func (tr *TokenRedis) Save(userId int64, token string) (err error) {
if err = tr.Delete(userId); err != nil {
return
}
err = redis.Set(redisconst.AppUserTokenKey(userId), token, expireTime)
if err != nil {
err = sErr.MakeWithErr(sErr.RedisSet, err)
}
return
}
func (tr *TokenRedis) Exists(userId int64, token string) (has bool, err error) {
tokenbytes, err := tr.Get(userId)
if err != nil {
return
}
has = bytes.Compare(tokenbytes, []byte(token)) == 0
return
}
func (tr *TokenRedis) Get(userId int64) (token []byte, err error) {
token, err = redis.GetBytes(redisconst.AppUserTokenKey(userId))
if err != nil {
err = sErr.MakeWithErr(sErr.RedisGet, err)
}
return
}
func (tr *TokenRedis) Delete(userId int64) (err error) {
err = redis.Del(redisconst.AppUserTokenKey(userId))
if err != nil {
err = sErr.MakeWithErr(sErr.RedisDel, err)
}
return
}
+15
View File
@@ -0,0 +1,15 @@
package utils
import (
"bytes"
"os/exec"
)
func ShellCmd(cmdStr string) (string, error) {
cmd := exec.Command("/bin/bash", "-c", cmdStr)
var out bytes.Buffer
cmd.Stdout = &out
ec := cmd.Run()
return out.String(), ec
}
+116
View File
@@ -0,0 +1,116 @@
package utils
import (
"golang.org/x/exp/constraints"
"sort"
)
func SliceConstruct[T any](fn func() T) T {
return fn()
}
type SliceWalkCallback func(index int) bool // true 继续
func SliceWalk(length int, cb SliceWalkCallback) {
for i := 0; i < length; i++ {
if ok := cb(i); ok {
continue
} else {
break
}
}
}
type SliceIncludeFilter func(index int) bool // 返回 true 继续, false 中断
func SliceInclude(length int, filter SliceIncludeFilter) (int, bool) {
for i := 0; i < length; i++ {
if ok := filter(i); ok {
return i, true
}
}
return -1, false
}
type SliceConstructNFilter[T any] func(index int) (T, bool) // 返回 是否保留,项值
func SliceConstructN[T any](length int, filter SliceConstructNFilter[T]) []T {
ret := make([]T, 0)
for i := 0; i < length; i++ {
if item, keep := filter(i); keep {
ret = append(ret, item)
}
}
return ret
}
// SliceUnique 使用泛型实现切片去重
func SliceUnique[T comparable](s []T) []T {
seen := make(map[T]bool)
j := 0
for _, v := range s {
if _, ok := seen[v]; !ok {
seen[v] = true
s[j] = v
j++
}
}
return s[:j]
}
// SlicesDifference 求两个集合的差集
func SlicesDifference[T comparable](a, b []T) []T {
// 使用 map 存储集合 b 的元素,方便快速判断元素是否存在
seen := make(map[T]struct{}, len(b))
for _, v := range b {
seen[v] = struct{}{}
}
// 遍历集合 a,将不在集合 b 中的元素放入新的切片
diff := make([]T, 0)
for _, v := range a {
if _, ok := seen[v]; !ok {
diff = append(diff, v)
}
}
return diff
}
func RemoveSlicesElement[T comparable](slice []T, element T) []T {
// 使用两个指针,一个遍历,一个用来记录有效元素的位置
j := 0
for i := range slice {
if slice[i] != element {
slice[j] = slice[i]
j++
}
}
// 返回截取后的切片
return slice[:j]
}
func SlicesIntersect[T constraints.Ordered | ~string](slice1, slice2 []T) []T {
dict := make(map[T]int)
ret := make([]T, 0)
for _, v1 := range slice1 {
dict[v1]++
}
for _, v2 := range slice2 {
if _, exists := dict[v2]; exists {
ret = append(ret, v2)
}
}
return ret
}
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
}
+27
View File
@@ -0,0 +1,27 @@
package utils
// BinarySearch BinarySearch
func BinarySearch(s []string, t string, n int) (idx int) {
idx = -1
if n == 0 {
return
}
start, end := 0, n-1
for start <= end {
mid := (start + end) >> 1
if s[mid] < t {
start = mid + 1
} else if s[mid] > t {
end = mid - 1
} else {
idx = mid
break
}
}
return
}
var YesOrNo = map[bool]string{
true: "是",
false: "否",
}
+52
View File
@@ -0,0 +1,52 @@
package utils
import (
"fmt"
"github.com/mitchellh/mapstructure"
"reflect"
)
func Struct2Map(obj interface{}) map[string]interface{} {
t := reflect.TypeOf(obj)
v := reflect.ValueOf(obj)
if t.Kind() == reflect.Ptr {
t = t.Elem()
v = reflect.Indirect(v)
}
var data = make(map[string]interface{})
//func (v Value) NumField() int, 返回v持有的结构体类型值的字段数,如果v的Kind不是Struct会panic
for i := 0; i < t.NumField(); i++ {
n := t.Field(i).Name
vv := v.Field(i).Interface()
data[Capitalize(n)] = vv
}
return data
}
func Map2Struct(obj interface{}, mapInfo map[string]interface{}) interface{} {
err := mapstructure.Decode(mapInfo, obj)
if err != nil {
fmt.Println(err)
}
return obj
}
// Capitalize 字符首字母大写
func Capitalize(str string) string {
var upperStr string
vv := []rune(str) // 后文有介绍
for i := 0; i < len(vv); i++ {
if i == 0 {
if vv[i] >= 65 && vv[i] <= 96 { // 后文有介绍
vv[i] += 32 // string的码表相差32位
upperStr += string(vv[i])
} else {
fmt.Println("Not begins with uppercase letter,")
return str
}
} else {
upperStr += string(vv[i])
}
}
return upperStr
}
+276
View File
@@ -0,0 +1,276 @@
package utils
import (
"fmt"
"time"
"videoweb/pub/pubdef"
)
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 BeginOfToday() time.Time {
t := Now()
return BeginOfTime(t)
}
func BeginOfTime(t time.Time) time.Time {
year, month, day := t.Date()
return time.Date(year, month, day, 0, 0, 0, 0, t.Location())
}
func GetNextMonthTime(t time.Time) time.Time {
year, month, day := t.Date()
return time.Date(year, month+1, day, 0, 0, 0, 0, t.Location())
}
func GetNextQuarterTime(t time.Time) time.Time {
year, month, day := t.Date()
return time.Date(year, month+3, day, 0, 0, 0, 0, t.Location())
}
func GetNextYearTime(t time.Time) time.Time {
year, month, day := t.Date()
return time.Date(year+1, month, day, 0, 0, 0, 0, t.Location())
}
func GetForeverTime() time.Time {
t := time.Now()
year, month, day := t.Date()
return time.Date(year+999, month, day, 0, 0, 0, 0, t.Location())
}
func IsoWeek() string {
year, week := time.Now().ISOWeek()
return fmt.Sprintf("%d%02d", year, week)
}
// Deprecated
func EndOfToday() time.Time {
t := Now()
return EndOfTime(t)
}
func EndOfTime(t time.Time) time.Time {
year, month, day := t.Date()
return time.Date(year, month, day, 23, 59, 59, 0, t.Location())
}
func GetDateString(t time.Time) string {
return t.In(LocBJ).Format(pubdef.DateISO)
}
func GetTodayDate(now time.Time) string {
return GetDateString(now.In(time.Local))
}
func GetYesterdayDate(now time.Time) string {
return GetDateString(now.Local().AddDate(0, 0, -1))
}
func GetDayBeforeYesterdayDate(now time.Time) string {
return GetDateString(now.Local().AddDate(0, 0, -2))
}
func GetDayRange(t time.Time) (dayStart time.Time, dayEnd time.Time) {
dayStart = BeginOfTime(t)
dayEnd = EndOfTime(t)
return
}
// GetDayRangeByDate 返回一天的开始和结束时间, 00:00:00 - 23:59:59
func GetDayRangeByDate(date string) (dayStart time.Time, dayEnd time.Time, err error) {
var d time.Time
d, err = time.ParseInLocation(pubdef.DateISO, date, LocBJ)
if err != nil {
return
}
dayStart, dayEnd = GetDayRange(d)
return
}
func GetDayEnd(date string) time.Time {
d, _ := time.ParseInLocation(pubdef.DateISO, date, LocBJ)
return EndOfTime(d)
}
func GetDayBegin(date string) time.Time {
d, _ := time.ParseInLocation(pubdef.DateISO, date, LocBJ)
return BeginOfTime(d)
}
func BeginWithThisMonth() time.Time {
t := Now()
year, month, _ := t.Date()
return time.Date(year, month, 1, 0, 0, 0, 0, t.Location())
}
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 EndOfLastMonth(t time.Time) time.Time {
return BeginOfMonth(t).Add(-time.Nanosecond)
}
func EndOfLastMonthDate(t time.Time) string {
return GetDateString(EndOfLastMonth(t))
}
func EndOfLastWeek(t time.Time) time.Time {
return BeginOfWeekByTime(t).Add(-time.Nanosecond)
}
func EndOfLastWeekDate(t time.Time) string {
return GetDateString(EndOfLastWeek(t))
}
// Deprecated
func BeginOfWeek() time.Time {
return BeginOfWeekByTime(Now())
}
func WeekDateRange(t time.Time) (string, string) {
b := BeginOfWeekByTime(t)
e := EndOfWeekByTime(t)
return GetDateString(b), GetDateString(e)
}
func MonthDateRange(t time.Time) (string, string) {
b := BeginOfMonth(t)
e := EndOfMonth(t)
return GetDateString(b), GetDateString(e)
}
func BeginOfWeekByTime(t time.Time) time.Time {
bt := BeginOfTime(t)
weekday := int(t.Weekday())
weekStartDayInt := int(time.Monday)
if weekday < weekStartDayInt {
weekday = weekday + 7 - weekStartDayInt
} else {
weekday = weekday - weekStartDayInt
}
return bt.AddDate(0, 0, -weekday)
}
func EndOfWeekByTime(t time.Time) time.Time {
return BeginOfWeekByTime(t).AddDate(0, 0, 7).Add(-time.Nanosecond)
}
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 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()
}
func IsSameYearAndMonth(t1, t2 time.Time) bool {
return t1.Year() == t2.Year() &&
t1.Month() == t2.Month()
}
// 获取月的第一天
func GetFirstDateOfMonth(d time.Time) time.Time {
d = d.AddDate(0, 0, -d.Day()+1)
return GetZeroTime(d)
}
// 获取月的最后一天
func GetLastDateOfMonth(d time.Time) time.Time {
return GetFirstDateOfMonth(d).AddDate(0, 1, -1)
}
// 获取周的星期一(星期一~星期日)
func GetMondayOfWeek(d time.Time) time.Time {
offset := (int(d.Weekday()) + 6) % 7
d = d.AddDate(0, 0, -offset)
return GetZeroTime(d)
}
// 获取周的星期日(星期一~星期日)
func GetSundayOfWeek(d time.Time) time.Time {
offset := 6 - (int(d.Weekday())+6)%7
d = d.AddDate(0, 0, offset)
return GetZeroTime(d)
}
// 获取某一天的0点时间
func GetZeroTime(d time.Time) time.Time {
return time.Date(d.Year(), d.Month(), d.Day(), 0, 0, 0, 0, d.Location())
}
// 获取当前整点时间
func GetWholeTime(d time.Time) time.Time {
return time.Date(d.Year(), d.Month(), d.Day(), d.Hour(), 0, 0, 0, d.Location())
}
func MustParseTime(s string) time.Time {
layouts := []string{
time.RFC3339,
pubdef.DateISO,
pubdef.TimeISO,
}
for _, layout := range layouts {
t, err := time.ParseInLocation(layout, s, time.Local)
if err != nil {
continue
}
return t
}
panic(fmt.Errorf("cannot parse %s as time.Time", s))
}
// 查看两个时间差了多少天,差一秒都算一天
func TimeSub(t1, t2 time.Time) int64 {
t1 = time.Date(t1.Year(), t1.Month(), t1.Day(), 0, 0, 0, 0, time.Local)
t2 = time.Date(t2.Year(), t2.Month(), t2.Day(), 0, 0, 0, 0, time.Local)
return int64(t1.Sub(t2).Hours() / 24)
}
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())
}
+130
View File
@@ -0,0 +1,130 @@
package utils
import (
"fmt"
"reflect"
"testing"
"time"
)
func Test_GetDayRangeByDate(t *testing.T) {
begin, end, _ := GetDayRangeByDate("2022-04-08")
fmt.Println(begin, end)
}
func Test_GetDayRange(t *testing.T) {
now := time.Now()
fmt.Println(GetDayRange(now))
}
func Test_GetDateString(t *testing.T) {
fmt.Println(GetDateString(time.Now()))
}
func Test_GetTodayDate(t *testing.T) {
fmt.Println(GetTodayDate(time.Now()))
}
func Test_GetYesterdayDate(t *testing.T) {
fmt.Println(GetYesterdayDate(time.Now()))
}
func Test_GetDayBeforeYesterdayDate(t *testing.T) {
fmt.Println(GetDayBeforeYesterdayDate(time.Now()))
}
func Test_BeginOfWeekByTime(t *testing.T) {
{
// 如果当前是周日,获取到的结果应该是这周周一而不是下周周一
now := time.Date(2022, 4, 3, 1, 0, 0, 0, time.Local)
actual := BeginOfWeekByTime(now)
expect := time.Date(2022, 3, 28, 0, 0, 0, 0, time.Local)
if !reflect.DeepEqual(expect, actual) {
t.Fatalf("fail")
}
}
{
now := time.Date(2022, 3, 28, 0, 0, 0, 0, time.Local)
actual := BeginOfWeekByTime(now)
expect := time.Date(2022, 3, 28, 0, 0, 0, 0, time.Local)
if !reflect.DeepEqual(expect, actual) {
t.Fatalf("fail")
}
}
}
func Test_GetDayEnd(t *testing.T) {
fmt.Println(GetDayEnd("2020-04-09"))
}
func Test_GetDayBegin(t *testing.T) {
fmt.Println(GetDayBegin("2020-04-09"))
}
func Test_EndOfWeekByTime(t *testing.T) {
now := time.Now()
fmt.Println(EndOfWeekByTime(now))
}
func Test_EndOfMonth(t *testing.T) {
now := time.Now()
fmt.Println(BeginOfMonth(now))
fmt.Println(EndOfMonth(now))
}
func Test_WeekDateRange(t *testing.T) {
fmt.Println(WeekDateRange(time.Now()))
}
func Test_MonthDateRange(t *testing.T) {
fmt.Println(MonthDateRange(time.Now()))
}
func Test_EndOfLastWeek(t *testing.T) {
fmt.Println(EndOfLastWeek(time.Now()))
}
func Test_EndOfLastMonth(t *testing.T) {
fmt.Println(EndOfLastMonth(time.Now()))
}
func Test_EndOfLastWeekDate(t *testing.T) {
fmt.Println(EndOfLastWeekDate(time.Now()))
}
func Test_EndOfLastMonthDate(t *testing.T) {
fmt.Println(EndOfLastMonthDate(time.Now()))
}
func TestMustParseTime(t *testing.T) {
type args struct {
s string
}
tests := []struct {
name string
args args
want time.Time
}{
{
name: "case1",
args: args{
s: "2022-04-25",
},
want: time.Date(2022, 4, 25, 0, 0, 0, 0, time.Local),
},
{
name: "case2",
args: args{
s: "2022-04-25 11:12:13",
},
want: time.Date(2022, 4, 25, 11, 12, 13, 0, time.Local),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := MustParseTime(tt.args.s); !reflect.DeepEqual(got, tt.want) {
t.Errorf("MustParseTime() = %v, want %v", got, tt.want)
}
})
}
}
+30
View File
@@ -0,0 +1,30 @@
package utils
import (
"strings"
"videoweb/pub/common/enum/consts"
)
func UASplit(useragent string) (phoneModel string, phoneSys string, appVer string) {
if useragent == "" {
return
}
infos := strings.Split(useragent, ";")
if len(infos) != 3 {
return
}
phoneinfos := strings.Split(strings.Trim(infos[1], " "), " ")
if len(phoneinfos) == 2 {
phoneModel = strings.Trim(phoneinfos[0], " ")
phoneSys = strings.Trim(phoneinfos[1], " ")
}
appVer = strings.Trim(infos[2], " ")
return
}
func UADeviceType(ua string) string {
if strings.Contains(ua, "iPhone") {
return consts.IOS
}
return consts.Android
}
+63
View File
@@ -0,0 +1,63 @@
package utils
import (
"fmt"
"math/rand"
"runtime/debug"
"strings"
"time"
"videoweb/pub/common/log"
"golang.org/x/exp/constraints"
)
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 GetRandom(min, max int) int64 {
rand.Seed(time.Now().UnixNano())
return int64(rand.Intn(max-min+1) + min)
}
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
}
// Shuffle 使用 Fisher-Yates 算法对切片进行随机排序
func Shuffle[T any](slice []T) {
rand.Seed(time.Now().UnixNano())
n := len(slice)
for i := n - 1; i > 0; i-- {
j := rand.Intn(i + 1)
slice[i], slice[j] = slice[j], slice[i]
}
}
func GetRandomMacAddress() string {
buf := make([]byte, 6)
rand.Read(buf)
buf[0] |= 2
mac := fmt.Sprintf("%02x:%02x:%02x:%02x:%02x:%02x", buf[0], buf[1], buf[2], buf[3], buf[4], buf[5])
return strings.ToUpper(mac)
}
+10
View File
@@ -0,0 +1,10 @@
package utils
import "regexp"
// mobile verify
func VerifyMobileFormat(mobileNum string) bool {
regular := "^((13[0-9])|(14[5,7])|(15[0-3,5-9])|(17[0,3,5-8])|(18[0-9])|166|198|199|(147))\\d{8}$"
reg := regexp.MustCompile(regular)
return reg.MatchString(mobileNum)
}
+43
View File
@@ -0,0 +1,43 @@
package utils
import (
"strconv"
"strings"
)
func getSubVersions(ver string) []int {
var vers [4]int
vv := strings.Split(ver, ".")
for i, v := range vv {
vers[i], _ = strconv.Atoi(v)
}
return vers[:]
}
// VersionCompare
func VersionCompare(a, b string) int {
aa := getSubVersions(a)
bb := getSubVersions(b)
for i := 0; i < len(aa) && i < len(bb); i++ {
if aa[i] == bb[i] {
continue
}
if aa[i] < bb[i] {
return -1
}
return 1
}
if len(aa) == len(bb) {
return 0
}
if len(aa) < len(bb) {
return -1
}
return 1
}
+19
View File
@@ -0,0 +1,19 @@
package utils
import (
"fmt"
"testing"
)
func TestVersionCompare(t *testing.T) {
fmt.Printf("2.10.9 vs 2.11 = %d\n", VersionCompare("2.10.9", "2.11"))
fmt.Printf("2.11.9 vs 2.11 = %d\n", VersionCompare("2.11.9", "2.11"))
fmt.Printf("1.10.9 vs 2.11 = %d\n", VersionCompare("1.10.9", "2.11"))
fmt.Printf("2.10.9 vs 2.10.1 = %d\n", VersionCompare("2.10.9", "2.11.1"))
fmt.Printf("2.10.9 vs 2.10.10 = %d\n", VersionCompare("2.10.9", "2.10.10"))
fmt.Printf("2.10.9 vs 2.10.9.0 = %d\n", VersionCompare("2.10.9", "2.10.9.0"))
fmt.Printf("1 vs 1.0.0.0 = %d\n", VersionCompare("2.10.9", "2.10.9.0"))
fmt.Printf("2.10.9 vs 2.10.9.-1 = %d\n", VersionCompare("2.10.9", "2.10.9.-1"))
}
+55
View File
@@ -0,0 +1,55 @@
package v10
import (
"github.com/go-playground/validator/v10"
"reflect"
"sync"
"github.com/gin-gonic/gin/binding"
)
type DefaultValidator struct {
once sync.Once
validate *validator.Validate
}
var _ binding.StructValidator = &DefaultValidator{}
func (v *DefaultValidator) ValidateStruct(obj interface{}) error {
if kindOfData(obj) == reflect.Struct {
v.lazyinit()
if err := v.validate.Struct(obj); err != nil {
return error(err)
}
}
return nil
}
func (v *DefaultValidator) Engine() interface{} {
v.lazyinit()
return v.validate
}
func (v *DefaultValidator) lazyinit() {
v.once.Do(func() {
v.validate = validator.New()
v.validate.SetTagName("binding")
// add any custom validations etc. here
})
}
func kindOfData(data interface{}) reflect.Kind {
value := reflect.ValueOf(data)
valueType := value.Kind()
if valueType == reflect.Ptr {
valueType = value.Elem().Kind()
}
return valueType
}
+171
View File
@@ -0,0 +1,171 @@
package v10
import (
"github.com/nyaruka/phonenumbers"
"reflect"
"regexp"
"strconv"
"strings"
"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)
}
// IsPureChineseChar 匹配中文
func IsPureChineseChar(src string) bool {
return regexp.MustCompile("[\u4e00-\u9fa5]").MatchString(src)
}
// 提取文字 包括数字 英文 不包含符号
func ExtractPureEngChar(src string) string {
pureArray := regexp.MustCompile("[a-z0-9A-Z\u005f]").FindAllString(src, -1)
return strings.Join(pureArray, "")
}
// 提取文字 包括中文 数字 英文 不包含符号
func ExtractPureChar(src string) string {
src = strings.TrimSpace(src)
pureArray := regexp.MustCompile("[a-z0-9A-Z\u4e00-\u9fa5]+").FindAllString(src, -1)
return strings.Join(pureArray, "")
}
// 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