@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -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"}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package crypt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
)
|
||||
|
||||
func PKCS7PaddingEx(ciphertext []byte, blockSize int) []byte {
|
||||
padding := blockSize - len(ciphertext)%blockSize
|
||||
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
|
||||
return append(ciphertext, padtext...)
|
||||
}
|
||||
|
||||
func PKCS7UnPaddingEx(origData []byte) []byte {
|
||||
length := len(origData)
|
||||
unpadding := int(origData[length-1])
|
||||
return origData[:(length - unpadding)]
|
||||
}
|
||||
|
||||
func EcbDecrypt(data, key []byte) []byte {
|
||||
block, _ := aes.NewCipher(key)
|
||||
decrypted := make([]byte, len(data))
|
||||
size := block.BlockSize()
|
||||
|
||||
for bs, be := 0, size; bs < len(data); bs, be = bs+size, be+size {
|
||||
block.Decrypt(decrypted[bs:be], data[bs:be])
|
||||
}
|
||||
|
||||
return PKCS7UnPaddingEx(decrypted)
|
||||
}
|
||||
|
||||
func EcbEncrypt(data, key []byte) []byte {
|
||||
block, _ := aes.NewCipher(key)
|
||||
data = PKCS7PaddingEx(data, block.BlockSize())
|
||||
decrypted := make([]byte, len(data))
|
||||
size := block.BlockSize()
|
||||
|
||||
for bs, be := 0, size; bs < len(data); bs, be = bs+size, be+size {
|
||||
block.Encrypt(decrypted[bs:be], data[bs:be])
|
||||
}
|
||||
|
||||
return decrypted
|
||||
}
|
||||
@@ -0,0 +1,566 @@
|
||||
package crypt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/hmac"
|
||||
"crypto/md5"
|
||||
"crypto/rand"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"hash/crc32"
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
)
|
||||
|
||||
// CreateToken 生成Token算法
|
||||
func CreateToken(secret string, tokenClaims map[string]interface{}) (tokeness string, err error) {
|
||||
if secret == "" {
|
||||
return "", errors.New("secret is empty")
|
||||
}
|
||||
claims := jwt.MapClaims(tokenClaims)
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
tokeness, err = token.SignedString([]byte(secret))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return tokeness, nil
|
||||
}
|
||||
|
||||
func keyFunc(secret string) jwt.Keyfunc {
|
||||
return func(token *jwt.Token) (interface{}, error) {
|
||||
return []byte(secret), nil
|
||||
}
|
||||
}
|
||||
|
||||
// ParseToken 解析Token
|
||||
func ParseToken(secret string, tokeness string) (map[string]interface{}, error) {
|
||||
if secret == "" || tokeness == "" {
|
||||
return nil, errors.New("secret or tokeness is empty")
|
||||
}
|
||||
token, err := jwt.Parse(tokeness, keyFunc(secret))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
//验证token,如果token被修改过则为false
|
||||
if !token.Valid {
|
||||
return nil, errors.New("token is invalid")
|
||||
}
|
||||
claimsToken, ok := token.Claims.(jwt.MapClaims)
|
||||
if !ok {
|
||||
return nil, errors.New("cannot convert claim to MapClaim")
|
||||
}
|
||||
return claimsToken, nil
|
||||
}
|
||||
|
||||
// StructToStr 结构体转json 先对结构体按照字典顺序排序 并返回字符串
|
||||
func StructToStr(obj interface{}) (string, error) {
|
||||
if obj == nil {
|
||||
return "", errors.New("obj is not nil")
|
||||
}
|
||||
data := make(map[string]interface{})
|
||||
bytes, err := json.Marshal(obj)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = json.Unmarshal(bytes, &data)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
str, err := MapToStr(data)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return str, nil
|
||||
}
|
||||
|
||||
// MapToStr 对map按照字典顺序排序 并返回字符串
|
||||
func MapToStr(data map[string]interface{}) (string, error) {
|
||||
if data == nil || len(data) == 0 {
|
||||
return "", errors.New("data is nil or empty")
|
||||
}
|
||||
newData := make(map[string]interface{})
|
||||
keys := make([]string, 0, len(data))
|
||||
for k := range data {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for _, k := range keys {
|
||||
newData[k] = data[k]
|
||||
}
|
||||
b, err := json.Marshal(newData)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
str := string(b)
|
||||
return str, nil
|
||||
}
|
||||
|
||||
// JSON2Struct json转struct
|
||||
func JSON2Struct(str string, obj interface{}) {
|
||||
if err := json.Unmarshal([]byte(str), &obj); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// JSONArray2Struct json转struct
|
||||
func JSONArray2Struct(str []string, obj interface{}) []interface{} {
|
||||
var data []interface{}
|
||||
for _, v := range str {
|
||||
JSON2Struct(v, &obj)
|
||||
data = append(data, obj)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// JSON2Map json转map
|
||||
func JSON2Map(str string) (map[string]interface{}, error) {
|
||||
var mapResult map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(str), &mapResult); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return mapResult, nil
|
||||
}
|
||||
|
||||
// StructToStrNormal 结构体转str
|
||||
func StructToStrNormal(obj interface{}) (string, error) {
|
||||
str, err := json.Marshal(obj)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(str), nil
|
||||
}
|
||||
|
||||
func UrlValueToStr(values url.Values, exclude string) (string, string) {
|
||||
if values == nil {
|
||||
return "", ""
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
var sign string
|
||||
keys := make([]string, 0, len(values))
|
||||
for k, _ := range values {
|
||||
if k == exclude {
|
||||
sign = strings.Join(values[k], "")
|
||||
continue
|
||||
}
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for i, k := range keys {
|
||||
if i != 0 {
|
||||
buf.WriteString("&")
|
||||
}
|
||||
buf.WriteString(k)
|
||||
buf.WriteString("=")
|
||||
buf.WriteString(strings.Join(values[k], ""))
|
||||
}
|
||||
return buf.String(), sign
|
||||
}
|
||||
|
||||
// 对参数进行排序后 拼接成url字符串 只对一级字符串类型的参数做处理
|
||||
func MapToUrlOnlyStr(data map[string]interface{}) string {
|
||||
if data == nil || len(data) == 0 {
|
||||
return ""
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
keys := make([]string, 0, len(data))
|
||||
for k := range data {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for i, k := range keys {
|
||||
if i != 0 {
|
||||
buf.WriteString("&")
|
||||
}
|
||||
buf.WriteString(k)
|
||||
buf.WriteString("=")
|
||||
if v, ok := data[k].(string); ok {
|
||||
buf.WriteString(v)
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// MapToStr 对map按照字典顺序排序 并返回字符串url 拼接的字段
|
||||
func MapToUrlStr(data map[string]interface{}) string {
|
||||
if data == nil || len(data) == 0 {
|
||||
return ""
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
keys := make([]string, 0, len(data))
|
||||
for k := range data {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for i, k := range keys {
|
||||
if i != 0 {
|
||||
buf.WriteString("&")
|
||||
}
|
||||
buf.WriteString(k)
|
||||
buf.WriteString("=")
|
||||
if v, ok := data[k].(string); ok {
|
||||
buf.WriteString(v)
|
||||
} else {
|
||||
t := reflect.TypeOf(data[k])
|
||||
if t != nil && t.Kind() == reflect.Map {
|
||||
if v, ok := data[k].(map[string]interface{}); ok {
|
||||
buf.WriteString("{")
|
||||
str := MapToUrlStr(v)
|
||||
buf.WriteString(str)
|
||||
buf.WriteString("}")
|
||||
}
|
||||
}
|
||||
if t != nil && (t.Kind() == reflect.Slice || t.Kind() == reflect.Array) {
|
||||
arraybyte, _ := json.Marshal(data[k])
|
||||
buf.WriteString(string(arraybyte))
|
||||
}
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// MapToURL 集合转URL
|
||||
func MapToURL(m map[string]string) string {
|
||||
if m == nil || len(m) == 0 {
|
||||
return ""
|
||||
}
|
||||
v := url.Values{}
|
||||
for k := range m {
|
||||
v.Set(k, string(m[k]))
|
||||
}
|
||||
return v.Encode()
|
||||
}
|
||||
|
||||
func Strcut2UrlValue(obj interface{}) (string, error) {
|
||||
t := reflect.TypeOf(obj)
|
||||
v := reflect.ValueOf(obj)
|
||||
if t.Kind() != reflect.Struct {
|
||||
return "", errors.New("obj must be struct type")
|
||||
}
|
||||
u := url.Values{}
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
var value string
|
||||
tag := t.Field(i).Tag.Get("json")
|
||||
switch t.Field(i).Type.Kind() {
|
||||
case reflect.String:
|
||||
value = v.Field(i).String()
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
value = strconv.Itoa(int(v.Field(i).Int()))
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
value = strconv.FormatUint(v.Field(i).Uint(), 10)
|
||||
case reflect.Slice, reflect.Array:
|
||||
buf := bytes.Buffer{}
|
||||
b, _ := json.Marshal(v.Field(i).Interface())
|
||||
t := strings.TrimRight(strings.TrimLeft(string(b), "["), "]")
|
||||
array := strings.Split(t, ",")
|
||||
if len(array) == 0 {
|
||||
value = ""
|
||||
}
|
||||
if len(array) == 1 {
|
||||
value = array[0]
|
||||
} else {
|
||||
for i, v := range array {
|
||||
if i == 0 {
|
||||
buf.WriteString(v)
|
||||
continue
|
||||
}
|
||||
buf.WriteString("&")
|
||||
buf.WriteString(tag)
|
||||
buf.WriteString("=")
|
||||
buf.WriteString(v)
|
||||
value = buf.String()
|
||||
}
|
||||
}
|
||||
}
|
||||
u.Add(tag, value)
|
||||
}
|
||||
dcodeurl, err := url.QueryUnescape(u.Encode())
|
||||
return dcodeurl, err
|
||||
}
|
||||
|
||||
// StrToMd5 字符串转md5
|
||||
func StrToMd5(str string) string {
|
||||
data := []byte(str)
|
||||
return ByteToMd5(data)
|
||||
}
|
||||
|
||||
// ByteToMd5 数组转md5
|
||||
func ByteToMd5(data []byte) string {
|
||||
md5Ctx := md5.New()
|
||||
md5Ctx.Write(data)
|
||||
cipherStr := md5Ctx.Sum(nil)
|
||||
return hex.EncodeToString(cipherStr)
|
||||
}
|
||||
|
||||
// FileToMd5 文件转MD5
|
||||
func FileToMd5(fileName string) string {
|
||||
file, err := os.Open(fileName)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
defer file.Close()
|
||||
md5Ctx := md5.New()
|
||||
if _, err := io.Copy(md5Ctx, file); err != nil {
|
||||
return ""
|
||||
}
|
||||
cipherStr := md5Ctx.Sum(nil)
|
||||
return hex.EncodeToString(cipherStr)
|
||||
}
|
||||
|
||||
// HashCode 对一个字符串生成唯一的hasHcode 码
|
||||
func HashCode(src string) int {
|
||||
v := int(crc32.ChecksumIEEE([]byte(src)))
|
||||
if v > 0 {
|
||||
return v
|
||||
}
|
||||
if -v > 0 {
|
||||
return -v
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// HexToString converts
|
||||
func HexToString(data []byte) string {
|
||||
return hex.EncodeToString(data)
|
||||
}
|
||||
|
||||
// StrToSha256 字符串转sha256
|
||||
func StrToSha256(str string) string {
|
||||
shaCtx := sha256.New()
|
||||
shaCtx.Write([]byte(str))
|
||||
shaBytes := shaCtx.Sum(nil)
|
||||
cipherStr := hex.EncodeToString(shaBytes[:])
|
||||
return cipherStr
|
||||
}
|
||||
|
||||
// StrToSha1 字符串转sha1
|
||||
func StrToSha1(str string, secret string) []byte {
|
||||
h := hmac.New(sha1.New, []byte(secret))
|
||||
h.Write([]byte(str))
|
||||
return h.Sum(nil)
|
||||
}
|
||||
|
||||
// StrToSha1 字符串转sha1 转16进制后
|
||||
func StrToHmacSha1(str, secret string) string {
|
||||
return hex.EncodeToString(StrToSha1(str, secret))
|
||||
}
|
||||
|
||||
// StrToHmacSha256 加密 字符串转sha256加密字符串
|
||||
func StrToHmacSha256(str string, secret string) string {
|
||||
h := hmac.New(sha256.New, []byte(secret))
|
||||
h.Write([]byte(str))
|
||||
cipherStr := hex.EncodeToString(h.Sum(nil))
|
||||
return cipherStr
|
||||
}
|
||||
|
||||
func PKCS7Padding(ciphertext []byte, blockSize int) []byte {
|
||||
padding := blockSize - len(ciphertext)%blockSize
|
||||
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
|
||||
return append(ciphertext, padtext...)
|
||||
}
|
||||
|
||||
func PKCS7UnPadding(origData []byte) []byte {
|
||||
length := len(origData)
|
||||
unpadding := int(origData[length-1])
|
||||
return origData[:(length - unpadding)]
|
||||
}
|
||||
|
||||
// AesEncrypt AES加密
|
||||
func AesEncrypt(origData, key string) ([]byte, error) {
|
||||
origDataByte := []byte(origData)
|
||||
return CoreAesEncrypt(origDataByte, key)
|
||||
}
|
||||
|
||||
// AesDecrypt AES解密
|
||||
func AesDecrypt(crypted, key string) (string, error) {
|
||||
cryptedByte := []byte(crypted)
|
||||
return CoreAesDecrypt(cryptedByte, key)
|
||||
}
|
||||
|
||||
// AesDecrypt AES解密
|
||||
func AesDecryptByte(cryptedByte []byte, key string) (string, error) {
|
||||
return CoreAesDecrypt(cryptedByte, key)
|
||||
}
|
||||
|
||||
// CoreAesEncrypt AES加密
|
||||
func CoreAesEncrypt(origData []byte, key string) ([]byte, error) {
|
||||
keybyte := []byte(key)
|
||||
block, err := aes.NewCipher(keybyte)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
blockSize := block.BlockSize()
|
||||
origData = PKCS7Padding(origData, blockSize)
|
||||
blockMode := cipher.NewCBCEncrypter(block, keybyte[:blockSize])
|
||||
crypted := make([]byte, len(origData))
|
||||
blockMode.CryptBlocks(crypted, origData)
|
||||
return crypted, nil
|
||||
}
|
||||
|
||||
// CoreAesDecrypt AES解密
|
||||
func CoreAesDecrypt(crypted []byte, key string) (string, error) {
|
||||
keybyte := []byte(key)
|
||||
block, err := aes.NewCipher(keybyte)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
blockSize := block.BlockSize()
|
||||
blockMode := cipher.NewCBCDecrypter(block, keybyte[:blockSize])
|
||||
origData := make([]byte, len(crypted))
|
||||
blockMode.CryptBlocks(origData, crypted)
|
||||
origData = PKCS7UnPadding(origData)
|
||||
return string(origData), nil
|
||||
}
|
||||
|
||||
// XorEnc
|
||||
func XorEnc(src string, xorKey string) string {
|
||||
var result string
|
||||
j := 0
|
||||
bt := []rune(src)
|
||||
xor := []rune(xorKey)
|
||||
for i := 0; i < len(bt); i++ {
|
||||
s := strconv.FormatInt(int64(bt[i]^xor[j]), 10)
|
||||
result = result + s
|
||||
j = 1 % len(xor)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// XorDec
|
||||
func XorDec(src string, xorKey string) string {
|
||||
var result string
|
||||
j := 0
|
||||
bt := []rune(src)
|
||||
xor := []rune(xorKey)
|
||||
for i := 0; i < len(bt); i++ {
|
||||
s := strconv.FormatInt(int64(bt[i]^xor[j]), 10)
|
||||
result = result + s
|
||||
j = i % len(xor)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func XorDecode(src, key string) string {
|
||||
srcByte, _ := base64.StdEncoding.DecodeString(src)
|
||||
keyByte, _ := base64.StdEncoding.DecodeString(key)
|
||||
srcByte = xor(srcByte, keyByte)
|
||||
return base64.StdEncoding.EncodeToString(srcByte)
|
||||
}
|
||||
|
||||
func XorEncode(src, key string) string {
|
||||
return XorDecode(src, key)
|
||||
}
|
||||
|
||||
func xor(src []byte, key []byte) []byte {
|
||||
for i := 0; i < len(src); i++ {
|
||||
src[i] ^= key[i%len(key)]
|
||||
}
|
||||
return src
|
||||
}
|
||||
|
||||
func XORLength(src []byte, key []byte, length int) []byte {
|
||||
for i := 0; i < length; i++ {
|
||||
src[i] ^= key[i%len(key)]
|
||||
}
|
||||
return src[:length]
|
||||
}
|
||||
|
||||
// https://core.telegram.org/api/end-to-end#sending-and-receiving-messages-in-a-secret-chat
|
||||
func CoreAesEncryptEx(plain []byte, nonceLen int, key string) ([]byte, error) {
|
||||
nonce := make([]byte, nonceLen)
|
||||
_, err := io.ReadFull(rand.Reader, nonce)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var largeShaRaw []byte
|
||||
largeShaRaw = append(largeShaRaw, []byte(key)...)
|
||||
largeShaRaw = append(largeShaRaw, nonce...)
|
||||
largeShaRawMid := len(largeShaRaw) / 2
|
||||
msgKeyLarge := sha256.Sum256(largeShaRaw)
|
||||
msgKey := msgKeyLarge[8:24] //16 bytes
|
||||
|
||||
var shaRawA []byte
|
||||
shaRawA = append(shaRawA, msgKey...)
|
||||
shaRawA = append(shaRawA, largeShaRaw[:largeShaRawMid]...)
|
||||
sha256a := sha256.Sum256(shaRawA) //32 bytes
|
||||
|
||||
var shaRawB []byte
|
||||
shaRawB = append(shaRawB, largeShaRaw[largeShaRawMid:]...)
|
||||
shaRawB = append(shaRawB, msgKey...)
|
||||
sha256b := sha256.Sum256(shaRawB) //32 bytes
|
||||
|
||||
var aesKey []byte //32 bytes AES-256
|
||||
aesKey = append(aesKey, sha256a[:8]...) //a: 8 bytes
|
||||
aesKey = append(aesKey, sha256b[8:24]...) //b: 16 bytes
|
||||
aesKey = append(aesKey, sha256a[24:32]...) //a: 8 bytes
|
||||
|
||||
var aesIV []byte // 16 bytes
|
||||
aesIV = append(aesIV, sha256b[:4]...) //b: 4 bytes
|
||||
aesIV = append(aesIV, sha256a[12:20]...) //a: 8 bytes
|
||||
aesIV = append(aesIV, sha256b[28:]...) //b: 4 bytes
|
||||
|
||||
block, err := aes.NewCipher(aesKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
blockSize := block.BlockSize()
|
||||
ciphertext := PKCS7Padding(plain, blockSize)
|
||||
mode := cipher.NewCBCEncrypter(block, aesIV)
|
||||
cipher := make([]byte, nonceLen+len(ciphertext))
|
||||
copy(cipher, nonce)
|
||||
mode.CryptBlocks(cipher[nonceLen:], ciphertext)
|
||||
return cipher, nil
|
||||
}
|
||||
|
||||
func CoreAesDecryptEx(crypt []byte, nonceLen int, key string) ([]byte, error) {
|
||||
nonce := make([]byte, nonceLen)
|
||||
copy(nonce, crypt)
|
||||
var largeShaRaw []byte
|
||||
largeShaRaw = append(largeShaRaw, []byte(key)...)
|
||||
largeShaRaw = append(largeShaRaw, nonce...)
|
||||
largeShaRawMid := len(largeShaRaw) / 2
|
||||
msgKeyLarge := sha256.Sum256(largeShaRaw)
|
||||
msgKey := msgKeyLarge[8:24] //16 bytes
|
||||
|
||||
var shaRawA []byte
|
||||
shaRawA = append(shaRawA, msgKey...)
|
||||
shaRawA = append(shaRawA, largeShaRaw[:largeShaRawMid]...)
|
||||
sha256a := sha256.Sum256(shaRawA) //32 bytes
|
||||
|
||||
var shaRawB []byte
|
||||
shaRawB = append(shaRawB, largeShaRaw[largeShaRawMid:]...)
|
||||
shaRawB = append(shaRawB, msgKey...)
|
||||
sha256b := sha256.Sum256(shaRawB) //32 bytes
|
||||
|
||||
var aesKey []byte //32 bytes AES-256
|
||||
aesKey = append(aesKey, sha256a[:8]...) //a: 8 bytes
|
||||
aesKey = append(aesKey, sha256b[8:24]...) //b: 16 bytes
|
||||
aesKey = append(aesKey, sha256a[24:32]...) //a: 8 bytes
|
||||
|
||||
var aesIV []byte // 16 bytes
|
||||
aesIV = append(aesIV, sha256b[:4]...) //b: 4 bytes
|
||||
aesIV = append(aesIV, sha256a[12:20]...) //a: 8 bytes
|
||||
aesIV = append(aesIV, sha256b[28:]...) //b: 4 bytes
|
||||
|
||||
block, err := aes.NewCipher(aesKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
realData := crypt[nonceLen:]
|
||||
blockMode := cipher.NewCBCDecrypter(block, aesIV)
|
||||
origData := make([]byte, len(realData))
|
||||
blockMode.CryptBlocks(origData, realData)
|
||||
return PKCS7UnPadding(origData), nil
|
||||
}
|
||||
@@ -0,0 +1,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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
@@ -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
|
||||
)
|
||||
@@ -0,0 +1,39 @@
|
||||
package errors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// SrvErr 错误定义
|
||||
type SrvErr struct {
|
||||
Code CodeType `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Tips string `json:"tip,omitempty"`
|
||||
Cause string `json:"-"`
|
||||
}
|
||||
|
||||
// New create an error
|
||||
func New(code CodeType, msg string, cause string) *SrvErr {
|
||||
return &SrvErr{
|
||||
Code: code,
|
||||
Msg: msg,
|
||||
Cause: cause,
|
||||
}
|
||||
}
|
||||
|
||||
// New create an error
|
||||
func NewTips(code CodeType, tips string, cause string) *SrvErr {
|
||||
return &SrvErr{
|
||||
Code: code,
|
||||
Msg: tips,
|
||||
Tips: tips,
|
||||
Cause: cause,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SrvErr) Error() string {
|
||||
if s == nil {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("code = %d, msg = %s ,case = %v", s.Code, s.Msg, s.Cause)
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package errors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
func MakeWithErr(code CodeType, err error) error {
|
||||
pc, fn, line, _ := runtime.Caller(1)
|
||||
cause := fmt.Sprintf("[error] in %s[%s:%d] %v", runtime.FuncForPC(pc).Name(), fn, line, err.Error())
|
||||
return New(code, err.Error(), cause)
|
||||
}
|
||||
|
||||
func MakeWithMsg(code CodeType, msg string) error {
|
||||
pc, fn, line, _ := runtime.Caller(1)
|
||||
cause := fmt.Sprintf("[error] in %s[%s:%d] %v", runtime.FuncForPC(pc).Name(), fn, line, msg)
|
||||
return New(code, msg, cause)
|
||||
}
|
||||
|
||||
func MakeWithTips(code CodeType, tips string) error {
|
||||
pc, fn, line, _ := runtime.Caller(1)
|
||||
cause := fmt.Sprintf("[error] in %s[%s:%d] %v", runtime.FuncForPC(pc).Name(), fn, line, tips)
|
||||
return NewTips(code, tips, cause)
|
||||
}
|
||||
@@ -0,0 +1,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 = "余额不足"
|
||||
)
|
||||
@@ -0,0 +1,6 @@
|
||||
package fconst
|
||||
|
||||
const (
|
||||
FileDir = "file-tmp"
|
||||
FsDir = "/fs"
|
||||
)
|
||||
@@ -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()
|
||||
}()
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package googauth
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"encoding/binary"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// HOTP implementation
|
||||
type HOTP struct {
|
||||
secret []byte
|
||||
digits int
|
||||
}
|
||||
|
||||
// At generate code
|
||||
func (h HOTP) At(counter uint64) string {
|
||||
counterBytes := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(counterBytes, counter)
|
||||
hash := hmac.New(sha1.New, h.secret)
|
||||
hash.Write(counterBytes)
|
||||
hs := hash.Sum(nil)
|
||||
offset := hs[19] & 0x0f
|
||||
binCodeBytes := make([]byte, 4)
|
||||
binCodeBytes[0] = hs[offset] & 0x7f
|
||||
binCodeBytes[1] = hs[offset+1] & 0xff
|
||||
binCodeBytes[2] = hs[offset+2] & 0xff
|
||||
binCodeBytes[3] = hs[offset+3] & 0xff
|
||||
binCode := binary.BigEndian.Uint32(binCodeBytes)
|
||||
mod := uint32(1)
|
||||
for i := 0; i < h.digits; i++ {
|
||||
mod *= 10
|
||||
}
|
||||
code := binCode % mod
|
||||
codeString := strconv.FormatUint(uint64(code), 10)
|
||||
if len(codeString) < h.digits {
|
||||
paddingByteLength := h.digits - len(codeString)
|
||||
paddingBytes := make([]byte, paddingByteLength)
|
||||
for i := 0; i < paddingByteLength; i++ {
|
||||
paddingBytes[i] = '0'
|
||||
}
|
||||
codeString = string(paddingBytes) + codeString
|
||||
}
|
||||
return codeString
|
||||
}
|
||||
|
||||
// Verify verify OTP code
|
||||
func (h HOTP) VerifyCode(code string, counter uint64) bool {
|
||||
realCode := h.At(counter)
|
||||
if realCode == code {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Verify verify OTP code
|
||||
func (h HOTP) GenerateCode(counter uint64) string {
|
||||
return h.At(counter)
|
||||
}
|
||||
|
||||
// NewHOTP generate new HOTP instance
|
||||
func NewHOTP(secret []byte, digits int) (h *HOTP) {
|
||||
h = new(HOTP)
|
||||
h.secret = secret
|
||||
h.digits = digits
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,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
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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) }
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package m3u8
|
||||
|
||||
type Req struct {
|
||||
Token string `json:"token" form:"token"`
|
||||
VId int64 `json:"vId" form:"vId"`
|
||||
Name string `json:"name" form:"name"`
|
||||
NeedHeader bool `json:"needHeader" form:"needHeader"`
|
||||
}
|
||||
|
||||
type PostVideoReq struct {
|
||||
Token string `json:"token" form:"token"`
|
||||
Name string `json:"name" form:"name" binding:"required" comment:"must be set"`
|
||||
}
|
||||
@@ -0,0 +1,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
|
||||
// }
|
||||
@@ -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
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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 >EMatch{
|
||||
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 >Match{
|
||||
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 <Match{
|
||||
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 <EMatch{
|
||||
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 >EAndLTEMatch{
|
||||
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 >EAndLTMatch{
|
||||
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)
|
||||
// }
|
||||
@@ -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{}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
package recomapi
|
||||
@@ -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{}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
@@ -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",
|
||||
}
|
||||
@@ -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, "", "")
|
||||
)
|
||||
@@ -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: "请求被拒",
|
||||
}
|
||||
@@ -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"`
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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"` // 时间
|
||||
}
|
||||
@@ -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:]
|
||||
|
||||
}
|
||||
@@ -0,0 +1,542 @@
|
||||
package useragent
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
"videoweb/app/appg"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultVer = "1.0.0"
|
||||
SDKCommonHeader = "X-SDK-Common"
|
||||
)
|
||||
|
||||
// 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.]+)`)
|
||||
reTrackerRequestID = regexp.MustCompile(`^[a-fA-F0-9]{32}$`)
|
||||
reTrackerCtx = regexp.MustCompile(`^[a-zA-Z0-9_-]+$`)
|
||||
reTrackerEvent = regexp.MustCompile(`^drama_(like|comment|collect|purchase)$`)
|
||||
reTrackerAppID = regexp.MustCompile(`^[A-Z]{1,6}-[0-9]{1,6}$`)
|
||||
reTrackerUID = regexp.MustCompile(`^[A-Za-z0-9_]+$`)
|
||||
reTrackerChannel = regexp.MustCompile(`^[A-Za-z0-9_.-]+$`)
|
||||
)
|
||||
|
||||
// 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) == 0 {
|
||||
return "OTHER"
|
||||
}
|
||||
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, 40)
|
||||
ApplyToHeader(header, c, ver)
|
||||
return header
|
||||
}
|
||||
|
||||
var trackerEventRoutes = map[string]string{
|
||||
"/api/playlet/chapter/like/set": "drama_like",
|
||||
"/api/playlet/like/set": "drama_like",
|
||||
"/api/playlet/chapter/collect/set": "drama_collect",
|
||||
"/api/playlet/collect/set": "drama_collect",
|
||||
"/api/comment/send": "drama_comment",
|
||||
"/api/playlet/chapter/unlock": "drama_purchase",
|
||||
}
|
||||
|
||||
var trackerCommonFieldKeys = map[string]struct{}{
|
||||
"app_id": {}, "uid": {}, "channel": {}, "sid": {}, "client_ts": {},
|
||||
"device": {}, "device_id": {}, "device_brand": {}, "device_model": {},
|
||||
"device_fingerprint": {}, "fp_version": {}, "user_agent": {},
|
||||
"sdk_version": {}, "app_version": {},
|
||||
}
|
||||
|
||||
var trackerDramaFieldKeys = map[string]struct{}{
|
||||
"media_id": {}, "drama_id": {}, "episode": {}, "video_id": {}, "recommend_trace_id": {},
|
||||
}
|
||||
|
||||
type trackerContextEnvelope struct {
|
||||
Version int `json:"v"`
|
||||
RequestID string `json:"request_id"`
|
||||
EventName string `json:"event_name"`
|
||||
Common map[string]json.RawMessage `json:"common"`
|
||||
Drama map[string]json.RawMessage `json:"drama"`
|
||||
}
|
||||
|
||||
// ApplyToHeader 将来源请求的网络信息与 SDK 原始公共字段快照写入上游 header。
|
||||
// 埋点上下文只通过 canonical envelope 白名单透传,不在 BFF 重新生成或覆盖。
|
||||
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)
|
||||
if c == nil || c.Request == nil {
|
||||
return
|
||||
}
|
||||
if sdk := strings.TrimSpace(c.GetHeader(SDKCommonHeader)); sdk != "" {
|
||||
header[SDKCommonHeader] = sdk
|
||||
}
|
||||
requestID := strings.TrimSpace(c.GetHeader("X-Tracker-Request-Id"))
|
||||
eventName := strings.TrimSpace(c.GetHeader("X-Tracker-Event-Name"))
|
||||
packedContext := strings.TrimSpace(c.GetHeader("X-DC-Request-Context"))
|
||||
expectedEvent := trackerEventRoutes[c.FullPath()]
|
||||
if expectedEvent == "" {
|
||||
expectedEvent = trackerEventRoutes[c.Request.URL.Path]
|
||||
}
|
||||
if c.Request.Method != http.MethodPost || expectedEvent == "" || eventName != expectedEvent ||
|
||||
!reTrackerRequestID.MatchString(requestID) || !reTrackerEvent.MatchString(eventName) {
|
||||
return
|
||||
}
|
||||
canonicalContext, ok := canonicalPackedTrackerContext(packedContext, requestID, eventName)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
header["X-DC-Request-Context"] = canonicalContext
|
||||
header["X-Tracker-Request-Id"] = requestID
|
||||
header["X-Tracker-Event-Name"] = eventName
|
||||
}
|
||||
|
||||
func canonicalPackedTrackerContext(value, requestID, eventName string) (string, bool) {
|
||||
if value == "" || len(value) > 8192 || !reTrackerCtx.MatchString(value) {
|
||||
return "", false
|
||||
}
|
||||
raw, err := base64.RawURLEncoding.DecodeString(value)
|
||||
if err != nil || len(raw) > 6144 {
|
||||
return "", false
|
||||
}
|
||||
var envelope trackerContextEnvelope
|
||||
if err := json.Unmarshal(raw, &envelope); err != nil {
|
||||
return "", false
|
||||
}
|
||||
if envelope.Version != 1 || envelope.RequestID != requestID ||
|
||||
envelope.EventName != eventName || !reTrackerRequestID.MatchString(envelope.RequestID) ||
|
||||
!reTrackerEvent.MatchString(envelope.EventName) ||
|
||||
!hasOnlyTrackerFields(envelope.Common, trackerCommonFieldKeys) ||
|
||||
!hasOnlyTrackerFields(envelope.Drama, trackerDramaFieldKeys) ||
|
||||
!validTrackerCommonFields(envelope.Common) || !validTrackerDramaFields(envelope.Drama) {
|
||||
return "", false
|
||||
}
|
||||
// 公共字段的值保持 SDK 原值;重新编码仅移除重复 JSON key,防止上下游解析差异。
|
||||
canonical, err := json.Marshal(envelope)
|
||||
if err != nil || len(canonical) > 6144 {
|
||||
return "", false
|
||||
}
|
||||
return base64.RawURLEncoding.EncodeToString(canonical), true
|
||||
}
|
||||
|
||||
func hasOnlyTrackerFields(values map[string]json.RawMessage, allowed map[string]struct{}) bool {
|
||||
if len(values) != len(allowed) {
|
||||
return false
|
||||
}
|
||||
for key := range values {
|
||||
if _, ok := allowed[key]; !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func validTrackerCommonFields(values map[string]json.RawMessage) bool {
|
||||
appID, ok := trackerStringField(values, "app_id", 50, false)
|
||||
if !ok || !reTrackerAppID.MatchString(appID) || appID != appg.TrackerAppID() {
|
||||
return false
|
||||
}
|
||||
uid, ok := trackerStringField(values, "uid", 32, true)
|
||||
if !ok || (uid != "" && (utf8.RuneCountInString(uid) < 4 || !reTrackerUID.MatchString(uid) ||
|
||||
uid == "u_1001" || uid == "stress_user" || uid == "u_lab")) {
|
||||
return false
|
||||
}
|
||||
channel, ok := trackerStringField(values, "channel", 50, true)
|
||||
if !ok || (channel != "" && (!reTrackerChannel.MatchString(channel) ||
|
||||
channel == "{}" || channel == "0" || channel == "-" || strings.EqualFold(channel, "unknown"))) {
|
||||
return false
|
||||
}
|
||||
if _, ok = trackerStringField(values, "sid", 50, true); !ok {
|
||||
return false
|
||||
}
|
||||
clientTS, ok := trackerIntegerField(values, "client_ts")
|
||||
if !ok || clientTS < 1767196800 || clientTS > 9999999999 {
|
||||
return false
|
||||
}
|
||||
device, ok := trackerStringField(values, "device", 30, true)
|
||||
if !ok || (device != "" && device != "Android" && device != "iOS" && device != "PC" && device != "OTHER") {
|
||||
return false
|
||||
}
|
||||
if _, ok = trackerStringField(values, "device_id", 50, false); !ok {
|
||||
return false
|
||||
}
|
||||
if _, ok = trackerStringField(values, "device_brand", 100, true); !ok {
|
||||
return false
|
||||
}
|
||||
if _, ok = trackerStringField(values, "device_model", 100, true); !ok {
|
||||
return false
|
||||
}
|
||||
if _, ok = trackerStringField(values, "device_fingerprint", 128, true); !ok {
|
||||
return false
|
||||
}
|
||||
if _, ok = trackerStringField(values, "fp_version", 128, true); !ok {
|
||||
return false
|
||||
}
|
||||
if _, ok = trackerStringField(values, "user_agent", 4096, true); !ok {
|
||||
return false
|
||||
}
|
||||
if _, ok = trackerStringField(values, "sdk_version", 128, true); !ok {
|
||||
return false
|
||||
}
|
||||
_, ok = trackerStringField(values, "app_version", 128, true)
|
||||
return ok
|
||||
}
|
||||
|
||||
func validTrackerDramaFields(values map[string]json.RawMessage) bool {
|
||||
if _, ok := trackerStringField(values, "media_id", 512, true); !ok {
|
||||
return false
|
||||
}
|
||||
if _, ok := trackerStringField(values, "drama_id", 512, false); !ok {
|
||||
return false
|
||||
}
|
||||
episode, ok := trackerIntegerField(values, "episode")
|
||||
if !ok || episode <= 0 {
|
||||
return false
|
||||
}
|
||||
if _, ok = trackerStringField(values, "video_id", 512, true); !ok {
|
||||
return false
|
||||
}
|
||||
_, ok = trackerStringField(values, "recommend_trace_id", 2048, true)
|
||||
return ok
|
||||
}
|
||||
|
||||
func trackerStringField(values map[string]json.RawMessage, key string, maxLen int, allowEmpty bool) (string, bool) {
|
||||
raw, ok := values[key]
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
var decoded *string
|
||||
if err := json.Unmarshal(raw, &decoded); err != nil || decoded == nil {
|
||||
return "", false
|
||||
}
|
||||
value := *decoded
|
||||
if utf8.RuneCountInString(value) > maxLen || (!allowEmpty && value == "") {
|
||||
return "", false
|
||||
}
|
||||
return value, true
|
||||
}
|
||||
|
||||
func trackerIntegerField(values map[string]json.RawMessage, key string) (int64, bool) {
|
||||
raw, ok := values[key]
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
var decoded *int64
|
||||
if err := json.Unmarshal(raw, &decoded); err != nil || decoded == nil {
|
||||
return 0, false
|
||||
}
|
||||
return *decoded, true
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package useragent
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// 限制解析开销;正常 SDK 公共参数远小于此大小。
|
||||
const maxSDKCommonHeaderSize = 16 * 1024
|
||||
|
||||
// SDKCommonFields 接收五项 SDK 公共字段及客户端 app_version,完整 fields 中的其他字段自动忽略。
|
||||
// 业务 DevID、用户 ID、渠道等继续走原有业务参数,不能被 SDK 对象覆盖。
|
||||
type SDKCommonFields struct {
|
||||
SID string `json:"sid" bson:"sid"`
|
||||
DeviceID string `json:"device_id" bson:"device_id"`
|
||||
Device string `json:"device" bson:"device"`
|
||||
DeviceFingerprint string `json:"device_fingerprint" bson:"device_fingerprint"`
|
||||
FPVersion string `json:"fp_version" bson:"fp_version"`
|
||||
AppVersion string `json:"app_version" bson:"app_version"` // 客户端版本随下单快照保存,支付回调不使用当前版本补值。
|
||||
}
|
||||
|
||||
// ParseSDKCommonHeader 缺少请求头返回 nil;格式错误交给调用方记录,不阻断业务请求。
|
||||
// 不修剪、截断或推算字段值,SDK 指纹生成失败时也保留其空值。
|
||||
func ParseSDKCommonHeader(header string) (*SDKCommonFields, error) {
|
||||
header = strings.TrimSpace(header)
|
||||
if header == "" {
|
||||
return nil, nil
|
||||
}
|
||||
if len(header) > maxSDKCommonHeaderSize {
|
||||
return nil, fmt.Errorf("SDK 公共参数请求头超过 %d 字节", maxSDKCommonHeaderSize)
|
||||
}
|
||||
data, err := base64.StdEncoding.DecodeString(header)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("SDK 公共参数 Base64 解码失败: %w", err)
|
||||
}
|
||||
if !utf8.Valid(data) {
|
||||
return nil, fmt.Errorf("SDK 公共参数不是有效的 UTF-8")
|
||||
}
|
||||
var fields *SDKCommonFields
|
||||
if err := json.Unmarshal(data, &fields); err != nil {
|
||||
return nil, fmt.Errorf("SDK 公共参数 JSON 解析失败: %w", err)
|
||||
}
|
||||
if fields == nil {
|
||||
return nil, fmt.Errorf("SDK 公共参数必须是 JSON 对象,不能为 null")
|
||||
}
|
||||
return fields, nil
|
||||
}
|
||||
|
||||
// Clone 下单时保存独立快照,避免请求对象后续变化影响支付事件。
|
||||
func (fields *SDKCommonFields) Clone() *SDKCommonFields {
|
||||
if fields == nil {
|
||||
return nil
|
||||
}
|
||||
snapshot := *fields
|
||||
return &snapshot
|
||||
}
|
||||
|
||||
// MissingFields 仅用于缺失观测;合法空值仍原样上报,不作为业务失败。
|
||||
func (fields *SDKCommonFields) MissingFields() []string {
|
||||
if fields == nil {
|
||||
fields = &SDKCommonFields{}
|
||||
}
|
||||
var missing []string
|
||||
for _, field := range []struct{ name, value string }{
|
||||
{"sid", fields.SID},
|
||||
{"device_id", fields.DeviceID},
|
||||
{"device", fields.Device},
|
||||
{"device_fingerprint", fields.DeviceFingerprint},
|
||||
{"fp_version", fields.FPVersion},
|
||||
{"app_version", fields.AppVersion},
|
||||
} {
|
||||
if strings.TrimSpace(field.value) == "" {
|
||||
missing = append(missing, field.name)
|
||||
}
|
||||
}
|
||||
return missing
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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))
|
||||
// }
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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: "否",
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package v10
|
||||
|
||||
import (
|
||||
"github.com/go-playground/validator/v10"
|
||||
"reflect"
|
||||
"sync"
|
||||
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
)
|
||||
|
||||
type DefaultValidator struct {
|
||||
once sync.Once
|
||||
validate *validator.Validate
|
||||
}
|
||||
|
||||
var _ binding.StructValidator = &DefaultValidator{}
|
||||
|
||||
func (v *DefaultValidator) ValidateStruct(obj interface{}) error {
|
||||
|
||||
if kindOfData(obj) == reflect.Struct {
|
||||
|
||||
v.lazyinit()
|
||||
|
||||
if err := v.validate.Struct(obj); err != nil {
|
||||
return error(err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *DefaultValidator) Engine() interface{} {
|
||||
v.lazyinit()
|
||||
return v.validate
|
||||
}
|
||||
|
||||
func (v *DefaultValidator) lazyinit() {
|
||||
v.once.Do(func() {
|
||||
v.validate = validator.New()
|
||||
v.validate.SetTagName("binding")
|
||||
|
||||
// add any custom validations etc. here
|
||||
})
|
||||
}
|
||||
|
||||
func kindOfData(data interface{}) reflect.Kind {
|
||||
|
||||
value := reflect.ValueOf(data)
|
||||
valueType := value.Kind()
|
||||
|
||||
if valueType == reflect.Ptr {
|
||||
valueType = value.Elem().Kind()
|
||||
}
|
||||
return valueType
|
||||
}
|
||||
@@ -0,0 +1,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()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package wordsfilter
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"regexp"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Filter 敏感词过滤器
|
||||
type Filter struct {
|
||||
trie *Trie
|
||||
noise *regexp.Regexp
|
||||
}
|
||||
|
||||
// New 返回一个敏感词过滤器
|
||||
func New() *Filter {
|
||||
return &Filter{
|
||||
trie: NewTrie(),
|
||||
noise: regexp.MustCompile(`[\|\s&%$@*]+`),
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateNoisePattern 更新去噪模式
|
||||
func (filter *Filter) UpdateNoisePattern(pattern string) {
|
||||
filter.noise = regexp.MustCompile(pattern)
|
||||
}
|
||||
|
||||
// LoadWordDict 加载敏感词字典
|
||||
func (filter *Filter) LoadWordDict(path string) error {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
return filter.Load(f)
|
||||
}
|
||||
|
||||
// LoadNetWordDict 加载网络敏感词字典
|
||||
func (filter *Filter) LoadNetWordDict(url string) error {
|
||||
c := http.Client{
|
||||
Timeout: 5 * time.Second,
|
||||
}
|
||||
rsp, err := c.Get(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rsp.Body.Close()
|
||||
|
||||
return filter.Load(rsp.Body)
|
||||
}
|
||||
|
||||
// Load common method to add words
|
||||
func (filter *Filter) Load(rd io.Reader) error {
|
||||
buf := bufio.NewReader(rd)
|
||||
for {
|
||||
line, _, err := buf.ReadLine()
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
return err
|
||||
}
|
||||
break
|
||||
}
|
||||
filter.trie.Add(string(line))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddWord 添加敏感词
|
||||
func (filter *Filter) AddWord(words ...string) {
|
||||
filter.trie.Add(words...)
|
||||
}
|
||||
|
||||
// DelWord 删除敏感词
|
||||
func (filter *Filter) DelWord(words ...string) {
|
||||
filter.trie.Del(words...)
|
||||
}
|
||||
|
||||
// Filter 过滤敏感词
|
||||
func (filter *Filter) Filter(text string) string {
|
||||
return filter.trie.Filter(text)
|
||||
}
|
||||
|
||||
// Replace 和谐敏感词
|
||||
func (filter *Filter) Replace(text string, repl rune) string {
|
||||
return filter.trie.Replace(text, repl)
|
||||
}
|
||||
|
||||
// FindIn 检测敏感词
|
||||
func (filter *Filter) FindIn(text string) (bool, string) {
|
||||
text = filter.RemoveNoise(text)
|
||||
return filter.trie.FindIn(text)
|
||||
}
|
||||
|
||||
// FindAll 找到所有匹配词
|
||||
func (filter *Filter) FindAll(text string) []string {
|
||||
return filter.trie.FindAll(text)
|
||||
}
|
||||
|
||||
// Validate 检测字符串是否合法
|
||||
func (filter *Filter) Validate(text string) (bool, string) {
|
||||
text = filter.RemoveNoise(text)
|
||||
return filter.trie.Validate(text)
|
||||
}
|
||||
|
||||
// RemoveNoise 去除空格等噪音
|
||||
func (filter *Filter) RemoveNoise(text string) string {
|
||||
return filter.noise.ReplaceAllString(text, "")
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
package wordsfilter
|
||||
|
||||
// Trie 短语组成的Trie树.
|
||||
type Trie struct {
|
||||
Root *Node
|
||||
}
|
||||
|
||||
// Node Trie树上的一个节点.
|
||||
type Node struct {
|
||||
isRootNode bool
|
||||
isPathEnd bool
|
||||
Character rune
|
||||
Children map[rune]*Node
|
||||
}
|
||||
|
||||
// NewTrie 新建一棵Trie
|
||||
func NewTrie() *Trie {
|
||||
return &Trie{
|
||||
Root: NewRootNode(0),
|
||||
}
|
||||
}
|
||||
|
||||
// Add 添加若干个词
|
||||
func (tree *Trie) Add(words ...string) {
|
||||
for _, word := range words {
|
||||
tree.add(word)
|
||||
}
|
||||
}
|
||||
|
||||
func (tree *Trie) add(word string) {
|
||||
var current = tree.Root
|
||||
var runes = []rune(word)
|
||||
for position := 0; position < len(runes); position++ {
|
||||
r := runes[position]
|
||||
if next, ok := current.Children[r]; ok {
|
||||
current = next
|
||||
} else {
|
||||
newNode := NewNode(r)
|
||||
current.Children[r] = newNode
|
||||
current = newNode
|
||||
}
|
||||
if position == len(runes)-1 {
|
||||
current.isPathEnd = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (tree *Trie) Del(words ...string) {
|
||||
for _, word := range words {
|
||||
tree.del(word)
|
||||
}
|
||||
}
|
||||
|
||||
func (tree *Trie) del(word string) {
|
||||
var current = tree.Root
|
||||
var runes = []rune(word)
|
||||
for position := 0; position < len(runes); position++ {
|
||||
r := runes[position]
|
||||
if next, ok := current.Children[r]; !ok {
|
||||
return
|
||||
} else {
|
||||
current = next
|
||||
}
|
||||
|
||||
if position == len(runes)-1 {
|
||||
current.SoftDel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Replace 词语替换
|
||||
func (tree *Trie) Replace(text string, character rune) string {
|
||||
var (
|
||||
parent = tree.Root
|
||||
current *Node
|
||||
runes = []rune(text)
|
||||
length = len(runes)
|
||||
left = 0
|
||||
found bool
|
||||
)
|
||||
|
||||
for position := 0; position < len(runes); position++ {
|
||||
current, found = parent.Children[runes[position]]
|
||||
|
||||
if !found || (!current.IsPathEnd() && position == length-1) {
|
||||
parent = tree.Root
|
||||
position = left
|
||||
left++
|
||||
continue
|
||||
}
|
||||
|
||||
// println(string(current.Character), current.IsPathEnd(), left)
|
||||
if current.IsPathEnd() && left <= position {
|
||||
for i := left; i <= position; i++ {
|
||||
runes[i] = character
|
||||
}
|
||||
}
|
||||
|
||||
parent = current
|
||||
}
|
||||
|
||||
return string(runes)
|
||||
}
|
||||
|
||||
// Filter 直接过滤掉字符串中的敏感词
|
||||
func (tree *Trie) Filter(text string) string {
|
||||
var (
|
||||
parent = tree.Root
|
||||
current *Node
|
||||
left = 0
|
||||
found bool
|
||||
runes = []rune(text)
|
||||
length = len(runes)
|
||||
resultRunes = make([]rune, 0, length)
|
||||
)
|
||||
|
||||
for position := 0; position < length; position++ {
|
||||
current, found = parent.Children[runes[position]]
|
||||
|
||||
if !found || (!current.IsPathEnd() && position == length-1) {
|
||||
resultRunes = append(resultRunes, runes[left])
|
||||
parent = tree.Root
|
||||
position = left
|
||||
left++
|
||||
continue
|
||||
}
|
||||
|
||||
if current.IsPathEnd() {
|
||||
left = position + 1
|
||||
parent = tree.Root
|
||||
} else {
|
||||
parent = current
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
resultRunes = append(resultRunes, runes[left:]...)
|
||||
return string(resultRunes)
|
||||
}
|
||||
|
||||
// Validate 验证字符串是否合法,如不合法则返回false和检测到
|
||||
// 的第一个敏感词
|
||||
func (tree *Trie) Validate(text string) (bool, string) {
|
||||
const (
|
||||
Empty = ""
|
||||
)
|
||||
var (
|
||||
parent = tree.Root
|
||||
current *Node
|
||||
runes = []rune(text)
|
||||
length = len(runes)
|
||||
left = 0
|
||||
found bool
|
||||
)
|
||||
|
||||
for position := 0; position < len(runes); position++ {
|
||||
current, found = parent.Children[runes[position]]
|
||||
|
||||
if !found || (!current.IsPathEnd() && position == length-1) {
|
||||
parent = tree.Root
|
||||
position = left
|
||||
left++
|
||||
continue
|
||||
}
|
||||
|
||||
if current.IsPathEnd() && left <= position {
|
||||
return false, string(runes[left : position+1])
|
||||
}
|
||||
|
||||
parent = current
|
||||
}
|
||||
|
||||
return true, Empty
|
||||
}
|
||||
|
||||
// FindIn 判断text中是否含有词库中的词
|
||||
func (tree *Trie) FindIn(text string) (bool, string) {
|
||||
validated, first := tree.Validate(text)
|
||||
return !validated, first
|
||||
}
|
||||
|
||||
// FindAll 找有所有包含在词库中的词
|
||||
func (tree *Trie) FindAll(text string) []string {
|
||||
var matches []string
|
||||
var (
|
||||
parent = tree.Root
|
||||
current *Node
|
||||
runes = []rune(text)
|
||||
length = len(runes)
|
||||
left = 0
|
||||
found bool
|
||||
)
|
||||
|
||||
for position := 0; position < length; position++ {
|
||||
current, found = parent.Children[runes[position]]
|
||||
|
||||
if !found {
|
||||
parent = tree.Root
|
||||
position = left
|
||||
left++
|
||||
continue
|
||||
}
|
||||
|
||||
if current.IsPathEnd() && left <= position {
|
||||
matches = append(matches, string(runes[left:position+1]))
|
||||
}
|
||||
|
||||
if position == length-1 {
|
||||
parent = tree.Root
|
||||
position = left
|
||||
left++
|
||||
continue
|
||||
}
|
||||
|
||||
parent = current
|
||||
}
|
||||
|
||||
var i = 0
|
||||
if count := len(matches); count > 0 {
|
||||
set := make(map[string]struct{})
|
||||
for i < count {
|
||||
_, ok := set[matches[i]]
|
||||
if !ok {
|
||||
set[matches[i]] = struct{}{}
|
||||
i++
|
||||
continue
|
||||
}
|
||||
count--
|
||||
copy(matches[i:], matches[i+1:])
|
||||
}
|
||||
return matches[:count]
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewNode 新建子节点
|
||||
func NewNode(character rune) *Node {
|
||||
return &Node{
|
||||
Character: character,
|
||||
Children: make(map[rune]*Node, 0),
|
||||
}
|
||||
}
|
||||
|
||||
// NewRootNode 新建根节点
|
||||
func NewRootNode(character rune) *Node {
|
||||
return &Node{
|
||||
isRootNode: true,
|
||||
Character: character,
|
||||
Children: make(map[rune]*Node, 0),
|
||||
}
|
||||
}
|
||||
|
||||
// IsLeafNode 判断是否叶子节点
|
||||
func (node *Node) IsLeafNode() bool {
|
||||
return len(node.Children) == 0
|
||||
}
|
||||
|
||||
// IsRootNode 判断是否为根节点
|
||||
func (node *Node) IsRootNode() bool {
|
||||
return node.isRootNode
|
||||
}
|
||||
|
||||
// IsPathEnd 判断是否为某个路径的结束
|
||||
func (node *Node) IsPathEnd() bool {
|
||||
return node.isPathEnd
|
||||
}
|
||||
|
||||
// SoftDel 置软删除状态
|
||||
func (node *Node) SoftDel() {
|
||||
node.isPathEnd = false
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
package esmanager
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/elastic/go-elasticsearch/v7/esapi"
|
||||
)
|
||||
|
||||
const (
|
||||
AnalyzerIkSmart = "ik_smart"
|
||||
AnalyzerIkMaxWord = "ik_max_word"
|
||||
NumberOfShards = 1
|
||||
NumberOfReplicas = 1
|
||||
)
|
||||
|
||||
func (m *Manager) InitIndices(indexName string, setting M) error {
|
||||
res, err := m.client.Indices.Create(indexName, func(request *esapi.IndicesCreateRequest) {
|
||||
request.Body = marshalSearchM(setting)
|
||||
})
|
||||
defer res.Body.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
// GetTokens 获取分词
|
||||
func (m *Manager) Tokens(index string, bind interface{}, query M, o ...func(*esapi.IndicesAnalyzeRequest)) error {
|
||||
o = append(o,
|
||||
m.client.Indices.Analyze.WithContext(context.Background()),
|
||||
m.client.Indices.Analyze.WithIndex(index),
|
||||
m.client.Indices.Analyze.WithBody(marshalSearchM(query)),
|
||||
m.client.Indices.Analyze.WithPretty(),
|
||||
)
|
||||
|
||||
res, err := m.client.Indices.Analyze(o...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
err = unmarshalAnalyzeBody(bind, res)
|
||||
return err
|
||||
}
|
||||
|
||||
// search 搜索
|
||||
func (m *Manager) Search(index string, bind interface{}, query M, o ...func(*esapi.SearchRequest)) error {
|
||||
marshalSearchM(query)
|
||||
o = append(o,
|
||||
m.client.Search.WithContext(context.Background()),
|
||||
m.client.Search.WithIndex(index),
|
||||
m.client.Search.WithBody(marshalSearchM(query)),
|
||||
m.client.Search.WithTrackTotalHits(true),
|
||||
m.client.Search.WithPretty(),
|
||||
)
|
||||
res, err := m.client.Search(o...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
err = unmarshalBody(bind, res)
|
||||
return err
|
||||
}
|
||||
|
||||
// 批量插入
|
||||
func (m *Manager) Bulk(index string, source M, o ...func(*esapi.BulkRequest)) error {
|
||||
o = append(o,
|
||||
m.client.Bulk.WithIndex(index),
|
||||
m.client.Bulk.WithContext(context.Background()),
|
||||
m.client.Bulk.WithRefresh("true"),
|
||||
)
|
||||
res, err := m.client.Bulk(marshalBulkM(source), o...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return errors.New(fmt.Sprintf("%+v", res))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Count 获取数量
|
||||
func (m *Manager) Count(index string, query M, o ...func(*esapi.CountRequest)) (int64, error) {
|
||||
o = append(o,
|
||||
m.client.Count.WithIndex(index),
|
||||
m.client.Count.WithBody(marshalSearchM(query)),
|
||||
m.client.Count.WithContext(context.Background()),
|
||||
)
|
||||
res, err := m.client.Count(o...)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return 0, errors.New(fmt.Sprintf("%+v", res))
|
||||
}
|
||||
type Bind struct {
|
||||
Count int64 `json:"count"`
|
||||
}
|
||||
var bind = Bind{}
|
||||
err = unmarshalStruct(&bind, res)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return bind.Count, err
|
||||
}
|
||||
|
||||
func (m *Manager) Get(index string, id string, bind interface{}, o ...func(*esapi.GetRequest)) error {
|
||||
o = append(o,
|
||||
m.client.Get.WithContext(context.Background()),
|
||||
m.client.Get.WithPretty(),
|
||||
)
|
||||
res, err := m.client.Get(index, id, o...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return errors.New(fmt.Sprintf("%+v", res))
|
||||
}
|
||||
return unmarshalBody(bind, res)
|
||||
}
|
||||
|
||||
func (m *Manager) Add(index string, id string, doc interface{}, o ...func(*esapi.IndexRequest)) error {
|
||||
o = append(o,
|
||||
m.client.Index.WithDocumentID(id),
|
||||
m.client.Index.WithContext(context.Background()),
|
||||
m.client.Index.WithPretty(),
|
||||
m.client.Index.WithRefresh("true"),
|
||||
)
|
||||
res, err := m.client.Index(index, marshalStruct(doc), o...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) Del(index string, id string, o ...func(*esapi.DeleteRequest)) error {
|
||||
o = append(o,
|
||||
m.client.Delete.WithContext(context.Background()),
|
||||
m.client.Delete.WithPretty(),
|
||||
m.client.Delete.WithRefresh("true"),
|
||||
)
|
||||
res, err := m.client.Delete(index, id, o...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) DelMany(indexs []string, query M, o ...func(*esapi.DeleteByQueryRequest)) error {
|
||||
o = append(o,
|
||||
m.client.DeleteByQuery.WithContext(context.Background()),
|
||||
m.client.DeleteByQuery.WithPretty(),
|
||||
m.client.DeleteByQuery.WithRefresh(true),
|
||||
)
|
||||
res, err := m.client.DeleteByQuery(indexs, marshalSearchM(query), o...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
package esmanager
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/elastic/go-elasticsearch/v7/esapi"
|
||||
)
|
||||
|
||||
type M map[string]interface{}
|
||||
|
||||
type A []map[string]interface{}
|
||||
|
||||
type MGetBody struct {
|
||||
ID string `json:"_id"`
|
||||
}
|
||||
|
||||
// ElasticResp 响应
|
||||
type Response = esapi.Response
|
||||
|
||||
type Options struct {
|
||||
Address []string
|
||||
MaxIdleConnPerHost int
|
||||
IdleConnTimeout time.Duration
|
||||
}
|
||||
|
||||
func unmarshalAggregateBody(bind interface{}, resp *Response) error {
|
||||
var v map[string]interface{}
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
if bind == nil { //传入空表示只执行 defer
|
||||
return nil
|
||||
}
|
||||
err := json.NewDecoder(resp.Body).Decode(&v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if v != nil && len(v) > 0 {
|
||||
if v, ok := v["aggregations"].(map[string]interface{}); ok {
|
||||
b, _ := json.Marshal(v)
|
||||
_ = json.Unmarshal(b, &bind)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func unmarshalAnalyzeBody(bind interface{}, resp *Response) error {
|
||||
var v map[string]interface{}
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
if bind == nil { //传入空表示只执行 defer
|
||||
return nil
|
||||
}
|
||||
err := json.NewDecoder(resp.Body).Decode(&v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if v != nil && len(v) > 0 {
|
||||
b, _ := json.Marshal(v)
|
||||
_ = json.Unmarshal(b, &bind)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func unmarshalBody(bind interface{}, resp *Response) error {
|
||||
var data map[string]interface{}
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
if bind == nil { //传入空表示只执行 defer
|
||||
return nil
|
||||
}
|
||||
err := json.NewDecoder(resp.Body).Decode(&data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if data != nil && len(data) > 0 {
|
||||
if hits1, ok := data["hits"].(map[string]interface{}); ok {
|
||||
b, _ := json.Marshal(hits1)
|
||||
_ = json.Unmarshal(b, &bind)
|
||||
} else if _, ok := data["docs"].([]interface{}); ok {
|
||||
b, _ := json.Marshal(data["docs"])
|
||||
_ = json.Unmarshal(b, &bind)
|
||||
} else if _, ok := data["_source"].(map[string]interface{}); ok {
|
||||
b, _ := json.Marshal(data)
|
||||
_ = json.Unmarshal(b, &bind)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func marshalSearchM(query M) io.Reader {
|
||||
var buf bytes.Buffer
|
||||
if query == nil || len(query) == 0 {
|
||||
return nil
|
||||
}
|
||||
err := json.NewEncoder(&buf).Encode(query)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return bytes.NewReader(buf.Bytes())
|
||||
}
|
||||
|
||||
func marshalBulkM(source M) io.Reader {
|
||||
var buf bytes.Buffer
|
||||
for k, v := range source {
|
||||
var meta = []byte(fmt.Sprintf(`{ "index" : { "_id" : "%s" } }%s`, k, "\n"))
|
||||
var data, _ = json.Marshal(v)
|
||||
data = append(data, "\n"...)
|
||||
buf.Grow(len(meta) + len(data))
|
||||
buf.Write(meta)
|
||||
buf.Write(data)
|
||||
}
|
||||
return bytes.NewReader(buf.Bytes())
|
||||
}
|
||||
|
||||
func marshalStruct(v interface{}) io.Reader {
|
||||
var buf bytes.Buffer
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
err := json.NewEncoder(&buf).Encode(v)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return bytes.NewReader(buf.Bytes())
|
||||
}
|
||||
|
||||
func unmarshalStruct(bind interface{}, resp *Response) error {
|
||||
var v map[string]interface{}
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
if bind == nil { //传入空表示只执行 defer
|
||||
return nil
|
||||
}
|
||||
err := json.NewDecoder(resp.Body).Decode(&v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if v != nil && len(v) > 0 {
|
||||
b, _ := json.Marshal(v)
|
||||
_ = json.Unmarshal(b, &bind)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package esmanager
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/elastic/go-elasticsearch/v7"
|
||||
)
|
||||
|
||||
var Instance *Manager
|
||||
|
||||
type Manager struct {
|
||||
client *elasticsearch.Client
|
||||
conf *elasticsearch.Config
|
||||
}
|
||||
|
||||
func NewManager(url string, user string, pw string) (*Manager, error) {
|
||||
if url == "" {
|
||||
return nil, errors.New(fmt.Sprintf("Elastic NewManager url is empty!"))
|
||||
}
|
||||
cfg := elasticsearch.Config{
|
||||
Addresses: []string{url},
|
||||
Transport: &http.Transport{
|
||||
MaxIdleConnsPerHost: 10,
|
||||
IdleConnTimeout: 30,
|
||||
},
|
||||
Username: user,
|
||||
Password: pw,
|
||||
}
|
||||
return &Manager{conf: &cfg}, nil
|
||||
}
|
||||
|
||||
func (m *Manager) Client() *elasticsearch.Client {
|
||||
return m.client
|
||||
}
|
||||
|
||||
func (m *Manager) ClientConnect() error {
|
||||
c, err := elasticsearch.NewClient(*m.conf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp, err := c.Ping(c.Ping.WithContext(context.Background()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return errors.New(fmt.Sprintf("%+v", resp))
|
||||
}
|
||||
m.client = c
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package google
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"encoding/base32"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type GoogleAuth struct {
|
||||
}
|
||||
|
||||
func NewGoogleAuth() *GoogleAuth {
|
||||
return &GoogleAuth{}
|
||||
}
|
||||
|
||||
func (ga *GoogleAuth) un() int64 {
|
||||
return time.Now().UnixNano() / 1000 / 30
|
||||
}
|
||||
|
||||
func (ga *GoogleAuth) hmacSha1(key, data []byte) []byte {
|
||||
h := hmac.New(sha1.New, key)
|
||||
if total := len(data); total > 0 {
|
||||
h.Write(data)
|
||||
}
|
||||
return h.Sum(nil)
|
||||
}
|
||||
|
||||
func (ga *GoogleAuth) base32encode(src []byte) string {
|
||||
return base32.StdEncoding.EncodeToString(src)
|
||||
}
|
||||
|
||||
func (ga *GoogleAuth) base32decode(s string) ([]byte, error) {
|
||||
return base32.StdEncoding.DecodeString(s)
|
||||
}
|
||||
|
||||
func (ga *GoogleAuth) toBytes(value int64) []byte {
|
||||
var result []byte
|
||||
mask := int64(0xFF)
|
||||
shifts := [8]uint16{56, 48, 40, 32, 24, 16, 8, 0}
|
||||
for _, shift := range shifts {
|
||||
result = append(result, byte((value>>shift)&mask))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (ga *GoogleAuth) toUint32(bts []byte) uint32 {
|
||||
return (uint32(bts[0]) << 24) + (uint32(bts[1]) << 16) +
|
||||
(uint32(bts[2]) << 8) + uint32(bts[3])
|
||||
}
|
||||
|
||||
func (ga *GoogleAuth) oneTimePassword(key []byte, data []byte) uint32 {
|
||||
hash := ga.hmacSha1(key, data)
|
||||
offset := hash[len(hash)-1] & 0x0F
|
||||
hashParts := hash[offset : offset+4]
|
||||
hashParts[0] = hashParts[0] & 0x7F
|
||||
number := ga.toUint32(hashParts)
|
||||
return number % 1000000
|
||||
}
|
||||
|
||||
func (ga *GoogleAuth) GetSecret() string {
|
||||
var buf bytes.Buffer
|
||||
binary.Write(&buf, binary.BigEndian, ga.un())
|
||||
return strings.ToUpper(ga.base32encode(ga.hmacSha1(buf.Bytes(), nil)))
|
||||
}
|
||||
|
||||
func (ga *GoogleAuth) GetCode(secret string) (string, error) {
|
||||
secretUpper := strings.ToUpper(secret)
|
||||
secretKey, err := ga.base32decode(secretUpper)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
number := ga.oneTimePassword(secretKey, ga.toBytes(time.Now().Unix()/30))
|
||||
return fmt.Sprintf("%06d", number), nil
|
||||
}
|
||||
|
||||
func (ga *GoogleAuth) GetQrcode(user, secret string) string {
|
||||
return fmt.Sprintf("otpauth://totp/%s?secret=%s", user, secret)
|
||||
}
|
||||
|
||||
func (ga *GoogleAuth) VerifyCode(secret, code string) (bool, error) {
|
||||
_code, err := ga.GetCode(secret)
|
||||
fmt.Println(_code, code, err)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return _code == code, nil
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
package analyticsdailystat
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"videoweb/pub/common/log"
|
||||
"videoweb/pub/common/srvdef"
|
||||
"videoweb/pub/mongos"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
// 表名
|
||||
|
||||
var db *mongos.Collection
|
||||
|
||||
func Init(ctx context.Context) {
|
||||
// 获取数据库表
|
||||
db = mongos.Shop.Coll(TN.AnalyticsDailyInfo)
|
||||
// 获取数据库表
|
||||
indexes := []mongo.IndexModel{
|
||||
{
|
||||
Keys: bson.D{
|
||||
{Key: FN.AnalyticsDailyInfo.AppId, Value: 1},
|
||||
{Key: FN.AnalyticsDailyInfo.CreatedAt, Value: -1},
|
||||
},
|
||||
},
|
||||
{
|
||||
Keys: bson.D{
|
||||
{Key: FN.AnalyticsDailyInfo.SumDate, Value: 1},
|
||||
{Key: FN.AnalyticsDailyInfo.AppId, Value: 1},
|
||||
},
|
||||
Options: options.Index().SetUnique(true),
|
||||
},
|
||||
{
|
||||
Keys: bson.D{
|
||||
{Key: FN.AnalyticsDailyInfo.CreatedAt, Value: -1},
|
||||
},
|
||||
},
|
||||
}
|
||||
if _, err := db.CreateIndex(ctx, indexes); err != nil {
|
||||
panic(fmt.Sprintf("(AnalyticsDailyInfo:initAnalyticsDailyInfoIndex) Create AnalyticsDailyInfo Index failed! err:%+v:", err))
|
||||
}
|
||||
}
|
||||
|
||||
// InsertOneAnalyticsDailyInfo 插入一个评论
|
||||
func InsertOneAnalyticsDailyInfo(ctx context.Context, AnalyticsDailyInfo *AnalyticsDailyInfo) *srvdef.SrvErr {
|
||||
if _, err := db.InsertOne(ctx, AnalyticsDailyInfo); err != nil {
|
||||
log.Warn("AnalyticsDailyInfo:InsertOneAnalyticsDailyInfo db.InsertOne failed", log.E(err))
|
||||
return srvdef.New(srvdef.ErrDbInsertError, "", "")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DelOneAnalyticsDailyInfo 删除一个评论
|
||||
func DelOneAnalyticsDailyInfo(ctx context.Context, filter bson.M) (
|
||||
int64, *srvdef.SrvErr) {
|
||||
count, err := db.DeleteOne(ctx, filter)
|
||||
if err != nil {
|
||||
log.Warn("AnalyticsDailyInfo:DelOneAnalyticsDailyInfo db.DeleteOne failed",
|
||||
log.E(err), log.Any("filter", filter))
|
||||
return 0, srvdef.New(srvdef.ErrDbDeleteError, "", "")
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// DelManyAnalyticsDailyInfo 删除一个评论评论
|
||||
func DelManyAnalyticsDailyInfo(ctx context.Context, filter bson.M) (
|
||||
int64, *srvdef.SrvErr) {
|
||||
count, err := db.DeleteMany(ctx, filter)
|
||||
if err != nil {
|
||||
log.Warn("AnalyticsDailyInfo:DelOneAnalyticsDailyInfo db.DeleteOne failed",
|
||||
log.E(err), log.Any("filter", filter))
|
||||
return 0, srvdef.New(srvdef.ErrDbDeleteError, "", "")
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// FindOneAnalyticsDailyInfo 查找一个评论
|
||||
func FindOneAnalyticsDailyInfo(ctx context.Context, filter bson.M, opts ...*options.FindOneOptions) (
|
||||
*AnalyticsDailyInfo, *srvdef.SrvErr) {
|
||||
var AnalyticsDailyInfo AnalyticsDailyInfo
|
||||
if err := db.FindOneEx(
|
||||
ctx, &AnalyticsDailyInfo, filter, opts...); err != nil {
|
||||
if err != mongo.ErrNoDocuments {
|
||||
log.Info("AnalyticsDailyInfo:FindOneAnalyticsDailyInfo db.FindOne failed",
|
||||
log.E(err), log.Any("filter", filter))
|
||||
return nil, srvdef.New(srvdef.ErrDbQueryError, "", "")
|
||||
}
|
||||
return nil, srvdef.New(srvdef.ErrNoData, "", "")
|
||||
}
|
||||
return &AnalyticsDailyInfo, nil
|
||||
}
|
||||
|
||||
// CountAnalyticsDailyInfos 统计评论视频评论数量
|
||||
func CountAnalyticsDailyInfos(ctx context.Context, filter bson.M) (
|
||||
int64, *srvdef.SrvErr) {
|
||||
count, err := db.Count(ctx, filter)
|
||||
if err != nil {
|
||||
log.Warn("AnalyticsDailyInfo:CountAnalyticsDailyInfos db.Count failed",
|
||||
log.E(err), log.Any("filter", filter))
|
||||
return 0, srvdef.New(srvdef.ErrDbQueryError, "", "")
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// UpdateOneAnalyticsDailyInfo 更新一个评论
|
||||
func UpdateOneAnalyticsDailyInfo(ctx context.Context, filter bson.M, update bson.M) *srvdef.SrvErr {
|
||||
result, err := db.UpdateOne(ctx, filter, update)
|
||||
if err != nil {
|
||||
log.Warn("AnalyticsDailyInfo:UpdateOneAnalyticsDailyInfo db.UpdateOne failed",
|
||||
log.E(err), log.Any("filter", filter))
|
||||
return srvdef.New(srvdef.ErrDbUpdateError, "", "")
|
||||
}
|
||||
if result.MatchedCount != 1 {
|
||||
log.Warn("AnalyticsDailyInfo:UpdateOneAnalyticsDailyInfo db.UpdateOne failed",
|
||||
log.E(err), log.Any("filter", filter))
|
||||
return srvdef.New(srvdef.ErrNoData, "", "")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateManyAnalyticsDailyInfo 更新一批评论
|
||||
func UpdateManyAnalyticsDailyInfo(ctx context.Context, filter bson.M, update bson.M) *srvdef.SrvErr {
|
||||
result, err := db.UpdateMMany(ctx, filter, update)
|
||||
if err != nil {
|
||||
log.Warn("AnalyticsDailyInfo:UpdateManyAnalyticsDailyInfo db.UpdateMMany failed",
|
||||
log.E(err), log.Any("filter", filter))
|
||||
return srvdef.New(srvdef.ErrDbUpdateError, "", "")
|
||||
}
|
||||
if result.MatchedCount <= 0 {
|
||||
log.Warn("AnalyticsDailyInfo:UpdateManyAnalyticsDailyInfo db.UpdateMMany failed",
|
||||
log.E(err), log.Any("filter", filter))
|
||||
//return srvdef.New(srvdef.ErrAnalyticsDailyInfoNotExsit, "", "")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FindManyAnalyticsDailyInfo 查找一批评论
|
||||
func FindManyAnalyticsDailyInfo(ctx context.Context, filter bson.M,
|
||||
opts ...*options.FindOptions) (AnalyticsDailyInfoList, *srvdef.SrvErr) {
|
||||
data := make(AnalyticsDailyInfoList, 0)
|
||||
if err := db.Find(ctx, &data, filter, opts...); err != nil {
|
||||
log.Warn("AnalyticsDailyInfo:FindManyAnalyticsDailyInfo db.Find failed",
|
||||
log.E(err), log.Any("filter", filter))
|
||||
return nil, srvdef.New(srvdef.ErrDbQueryError, "", "")
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// InsertManyAnalyticsDailyInfo 插入一批用户
|
||||
func InsertManyAnalyticsDailyInfo(ctx context.Context, histrory *AnalyticsDailyInfoList) *srvdef.SrvErr {
|
||||
if _, err := db.InsertMany(ctx, histrory); err != nil {
|
||||
log.Warn("AnalyticsDailyInfo:InsertManyAnalyticsDailyInfo db.InsertMany failed", log.E(err))
|
||||
return srvdef.New(srvdef.ErrDbInsertError, "", "")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AggregateAnalyticsDailyInfo 聚合查找一批数据
|
||||
func AggregateAnalyticsDailyInfo(ctx context.Context,
|
||||
bind interface{}, pipeline []bson.M) *srvdef.SrvErr {
|
||||
if err := db.Aggregate(ctx, bind, pipeline); err != nil {
|
||||
log.Warn("AnalyticsDailyInfo:AggregateAnalyticsDailyInfo db.Aggregate failed",
|
||||
log.E(err), log.Any("pipeline", pipeline))
|
||||
return srvdef.New(srvdef.ErrDbQueryError, "", "")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ObjectAggregateAnalyticsDailyInfo 聚合查找一批数据
|
||||
func ObjectAggregateAnalyticsDailyInfo(ctx context.Context,
|
||||
bind interface{}, pipeline []bson.M) *srvdef.SrvErr {
|
||||
if err := db.ObjectAggregate(ctx, bind, pipeline); err != nil {
|
||||
log.Warn("AnalyticsDailyInfo:AggregateAnalyticsDailyInfo db.Aggregate failed",
|
||||
log.E(err), log.Any("pipeline", pipeline))
|
||||
return srvdef.New(srvdef.ErrDbQueryError, "", "")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Executable
+42
@@ -0,0 +1,42 @@
|
||||
package analyticsdailystat
|
||||
|
||||
type TagAnalyticsDailyInfo struct {
|
||||
AppId string
|
||||
AppName string
|
||||
CreatedAt string
|
||||
Id string
|
||||
LeadAmount string
|
||||
LeadNewCount string
|
||||
SumDate string
|
||||
UpdatedAt string
|
||||
YestodayActiveUsers string
|
||||
YestodayNewUsers string
|
||||
YestodayPV string
|
||||
YestodaySessions string
|
||||
YestodayUV string
|
||||
}
|
||||
|
||||
var FN struct {
|
||||
AnalyticsDailyInfo TagAnalyticsDailyInfo
|
||||
}
|
||||
|
||||
var TN struct {
|
||||
AnalyticsDailyInfo string
|
||||
}
|
||||
|
||||
func init() {
|
||||
FN.AnalyticsDailyInfo.AppId = "appId"
|
||||
FN.AnalyticsDailyInfo.AppName = "appName"
|
||||
FN.AnalyticsDailyInfo.CreatedAt = "createdAt"
|
||||
FN.AnalyticsDailyInfo.Id = "_id"
|
||||
FN.AnalyticsDailyInfo.LeadAmount = "leadAmount"
|
||||
FN.AnalyticsDailyInfo.LeadNewCount = "leadNewCount"
|
||||
FN.AnalyticsDailyInfo.SumDate = "sumDate"
|
||||
FN.AnalyticsDailyInfo.UpdatedAt = "updatedAt"
|
||||
FN.AnalyticsDailyInfo.YestodayActiveUsers = "yestodayActiveUsers"
|
||||
FN.AnalyticsDailyInfo.YestodayNewUsers = "yestodayNewUsers"
|
||||
FN.AnalyticsDailyInfo.YestodayPV = "yestodayPV"
|
||||
FN.AnalyticsDailyInfo.YestodaySessions = "yestodaySessions"
|
||||
FN.AnalyticsDailyInfo.YestodayUV = "yestodayUV"
|
||||
TN.AnalyticsDailyInfo = "analytics_daily_info"
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package analyticsdailystat
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
)
|
||||
|
||||
// 数据统计
|
||||
// @table:analytics_daily_info
|
||||
type AnalyticsDailyInfo struct {
|
||||
Id primitive.ObjectID `bson:"_id,omitempty"`
|
||||
SumDate time.Time `json:"sumDate" bson:"sumDate"` // 时间半小时
|
||||
AppId int64 `json:"appId" bson:"appId"` // appId
|
||||
AppName string `json:"appName" bson:"appName"` // APP名称
|
||||
YestodayPV int64 `json:"yestodayPV" bson:"yestodayPV"` // 页面浏览量 (PV)
|
||||
YestodayUV int64 `json:"yestodayUV" bson:"yestodayUV"` // 独立用户数 (UV)
|
||||
YestodayActiveUsers int64 `json:"yestodayActiveUsers" bson:"yestodayActiveUsers"` // 活跃用户数
|
||||
YestodaySessions int64 `json:"yestodaySessions" bson:"yestodaySessions"` // 会话数
|
||||
YestodayNewUsers int64 `json:"yestodayNewUsers" bson:"yestodayNewUsers"` // 新增数量
|
||||
LeadNewCount float64 `json:"leadNewCount" bson:"leadNewCount"` // 日导量新增
|
||||
LeadAmount float64 `json:"leadAmount" bson:"leadAmount"` // 日导量充值
|
||||
UpdatedAt time.Time `json:"updatedAt" bson:"updatedAt"` // 更新时间
|
||||
CreatedAt time.Time `json:"createdAt" bson:"createdAt"` // 创建时间
|
||||
}
|
||||
|
||||
type AnalyticsDailyInfoList []*AnalyticsDailyInfo
|
||||
@@ -0,0 +1,182 @@
|
||||
package analyticsstat
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"videoweb/pub/common/log"
|
||||
"videoweb/pub/common/srvdef"
|
||||
"videoweb/pub/mongos"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
// 表名
|
||||
|
||||
var db *mongos.Collection
|
||||
|
||||
func Init(ctx context.Context) {
|
||||
// 获取数据库表
|
||||
db = mongos.Shop.Coll(TN.AnalyticsInfo)
|
||||
// 获取数据库表
|
||||
indexes := []mongo.IndexModel{
|
||||
{
|
||||
Keys: bson.D{
|
||||
{Key: FN.AnalyticsInfo.AppId, Value: 1},
|
||||
{Key: FN.AnalyticsInfo.CreatedAt, Value: -1},
|
||||
},
|
||||
},
|
||||
{
|
||||
Keys: bson.D{
|
||||
{Key: FN.AnalyticsInfo.SumDate, Value: 1},
|
||||
{Key: FN.AnalyticsInfo.AppId, Value: 1},
|
||||
},
|
||||
Options: options.Index().SetUnique(true),
|
||||
},
|
||||
{
|
||||
Keys: bson.D{
|
||||
{Key: FN.AnalyticsInfo.CreatedAt, Value: -1},
|
||||
},
|
||||
},
|
||||
}
|
||||
if _, err := db.CreateIndex(ctx, indexes); err != nil {
|
||||
panic(fmt.Sprintf("(AnalyticsInfo:initAnalyticsInfoIndex) Create AnalyticsInfo Index failed! err:%+v:", err))
|
||||
}
|
||||
}
|
||||
|
||||
// InsertOneAnalyticsInfo 插入一个评论
|
||||
func InsertOneAnalyticsInfo(ctx context.Context, AnalyticsInfo *AnalyticsInfo) *srvdef.SrvErr {
|
||||
if _, err := db.InsertOne(ctx, AnalyticsInfo); err != nil {
|
||||
log.Warn("AnalyticsInfo:InsertOneAnalyticsInfo db.InsertOne failed", log.E(err))
|
||||
return srvdef.New(srvdef.ErrDbInsertError, "", "")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DelOneAnalyticsInfo 删除一个评论
|
||||
func DelOneAnalyticsInfo(ctx context.Context, filter bson.M) (
|
||||
int64, *srvdef.SrvErr) {
|
||||
count, err := db.DeleteOne(ctx, filter)
|
||||
if err != nil {
|
||||
log.Warn("AnalyticsInfo:DelOneAnalyticsInfo db.DeleteOne failed",
|
||||
log.E(err), log.Any("filter", filter))
|
||||
return 0, srvdef.New(srvdef.ErrDbDeleteError, "", "")
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// DelManyAnalyticsInfo 删除一个评论评论
|
||||
func DelManyAnalyticsInfo(ctx context.Context, filter bson.M) (
|
||||
int64, *srvdef.SrvErr) {
|
||||
count, err := db.DeleteMany(ctx, filter)
|
||||
if err != nil {
|
||||
log.Warn("AnalyticsInfo:DelOneAnalyticsInfo db.DeleteOne failed",
|
||||
log.E(err), log.Any("filter", filter))
|
||||
return 0, srvdef.New(srvdef.ErrDbDeleteError, "", "")
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// FindOneAnalyticsInfo 查找一个评论
|
||||
func FindOneAnalyticsInfo(ctx context.Context, filter bson.M, opts ...*options.FindOneOptions) (
|
||||
*AnalyticsInfo, *srvdef.SrvErr) {
|
||||
var AnalyticsInfo AnalyticsInfo
|
||||
if err := db.FindOneEx(
|
||||
ctx, &AnalyticsInfo, filter, opts...); err != nil {
|
||||
if err != mongo.ErrNoDocuments {
|
||||
log.Info("AnalyticsInfo:FindOneAnalyticsInfo db.FindOne failed",
|
||||
log.E(err), log.Any("filter", filter))
|
||||
return nil, srvdef.New(srvdef.ErrDbQueryError, "", "")
|
||||
}
|
||||
return nil, srvdef.New(srvdef.ErrNoData, "", "")
|
||||
}
|
||||
return &AnalyticsInfo, nil
|
||||
}
|
||||
|
||||
// CountAnalyticsInfos 统计评论视频评论数量
|
||||
func CountAnalyticsInfos(ctx context.Context, filter bson.M) (
|
||||
int64, *srvdef.SrvErr) {
|
||||
count, err := db.Count(ctx, filter)
|
||||
if err != nil {
|
||||
log.Warn("AnalyticsInfo:CountAnalyticsInfos db.Count failed",
|
||||
log.E(err), log.Any("filter", filter))
|
||||
return 0, srvdef.New(srvdef.ErrDbQueryError, "", "")
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// UpdateOneAnalyticsInfo 更新一个评论
|
||||
func UpdateOneAnalyticsInfo(ctx context.Context, filter bson.M, update bson.M) *srvdef.SrvErr {
|
||||
result, err := db.UpdateOne(ctx, filter, update)
|
||||
if err != nil {
|
||||
log.Warn("AnalyticsInfo:UpdateOneAnalyticsInfo db.UpdateOne failed",
|
||||
log.E(err), log.Any("filter", filter))
|
||||
return srvdef.New(srvdef.ErrDbUpdateError, "", "")
|
||||
}
|
||||
if result.MatchedCount != 1 {
|
||||
log.Warn("AnalyticsInfo:UpdateOneAnalyticsInfo db.UpdateOne failed",
|
||||
log.E(err), log.Any("filter", filter))
|
||||
return srvdef.New(srvdef.ErrNoData, "", "")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateManyAnalyticsInfo 更新一批评论
|
||||
func UpdateManyAnalyticsInfo(ctx context.Context, filter bson.M, update bson.M) *srvdef.SrvErr {
|
||||
result, err := db.UpdateMMany(ctx, filter, update)
|
||||
if err != nil {
|
||||
log.Warn("AnalyticsInfo:UpdateManyAnalyticsInfo db.UpdateMMany failed",
|
||||
log.E(err), log.Any("filter", filter))
|
||||
return srvdef.New(srvdef.ErrDbUpdateError, "", "")
|
||||
}
|
||||
if result.MatchedCount <= 0 {
|
||||
log.Warn("AnalyticsInfo:UpdateManyAnalyticsInfo db.UpdateMMany failed",
|
||||
log.E(err), log.Any("filter", filter))
|
||||
//return srvdef.New(srvdef.ErrAnalyticsInfoNotExsit, "", "")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FindManyAnalyticsInfo 查找一批评论
|
||||
func FindManyAnalyticsInfo(ctx context.Context, filter bson.M,
|
||||
opts ...*options.FindOptions) (AnalyticsInfoList, *srvdef.SrvErr) {
|
||||
data := make(AnalyticsInfoList, 0)
|
||||
if err := db.Find(ctx, &data, filter, opts...); err != nil {
|
||||
log.Warn("AnalyticsInfo:FindManyAnalyticsInfo db.Find failed",
|
||||
log.E(err), log.Any("filter", filter))
|
||||
return nil, srvdef.New(srvdef.ErrDbQueryError, "", "")
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// InsertManyAnalyticsInfo 插入一批用户
|
||||
func InsertManyAnalyticsInfo(ctx context.Context, histrory *AnalyticsInfoList) *srvdef.SrvErr {
|
||||
if _, err := db.InsertMany(ctx, histrory); err != nil {
|
||||
log.Warn("AnalyticsInfo:InsertManyAnalyticsInfo db.InsertMany failed", log.E(err))
|
||||
return srvdef.New(srvdef.ErrDbInsertError, "", "")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AggregateAnalyticsInfo 聚合查找一批数据
|
||||
func AggregateAnalyticsInfo(ctx context.Context,
|
||||
bind interface{}, pipeline []bson.M) *srvdef.SrvErr {
|
||||
if err := db.Aggregate(ctx, bind, pipeline); err != nil {
|
||||
log.Warn("AnalyticsInfo:AggregateAnalyticsInfo db.Aggregate failed",
|
||||
log.E(err), log.Any("pipeline", pipeline))
|
||||
return srvdef.New(srvdef.ErrDbQueryError, "", "")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ObjectAggregateAnalyticsInfo 聚合查找一批数据
|
||||
func ObjectAggregateAnalyticsInfo(ctx context.Context,
|
||||
bind interface{}, pipeline []bson.M) *srvdef.SrvErr {
|
||||
if err := db.ObjectAggregate(ctx, bind, pipeline); err != nil {
|
||||
log.Warn("AnalyticsInfo:AggregateAnalyticsInfo db.Aggregate failed",
|
||||
log.E(err), log.Any("pipeline", pipeline))
|
||||
return srvdef.New(srvdef.ErrDbQueryError, "", "")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user