Initial commit

This commit is contained in:
2026-05-20 16:13:58 +08:00
commit ceffbe382e
500 changed files with 157469 additions and 0 deletions
+207
View File
@@ -0,0 +1,207 @@
package activitySer
import (
"context"
"time"
"stat/api/proto"
"stat/common/stdLog"
"stat/models/activityVisit"
"stat/models/activitymod"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo/options"
)
type ChannelParams struct {
PC string `json:"i" bson:"i"` //推广码
DC string `json:"dc" bson:"dc"` //商区序列
CH string `json:"ch" bson:"ch"` //渠道
}
// 活动列表
func GetActivityList(ctx context.Context, param proto.ActivityListReq) (stdLog.CodeType, *proto.ActivityListResp, error) {
if param.PageSize <= 0 {
param.PageSize = 10
}
if param.PageNum <= 0 {
param.PageNum = 1
}
data := &proto.ActivityListResp{List: make([]*proto.ActivityInfo, 0)}
filter := bson.M{}
if param.Name != nil {
filter[activitymod.FN.ActivityInfo.Name] = bson.M{"$regex": *param.Name}
}
if param.Enable != nil {
filter[activitymod.FN.ActivityInfo.Enable] = *param.Enable
}
count, err := activitymod.CountActivityInfos(ctx, filter)
if err != nil {
return stdLog.QueryDataError, data, err
}
data.Total = count
opt := &options.FindOptions{}
skip := (param.PageNum - 1) * param.PageSize
if param.PageSize <= 0 {
param.PageSize = 10
}
opt.SetSkip(skip)
opt.SetLimit(param.PageSize)
opt.SetSort(bson.M{activitymod.FN.ActivityInfo.SortCode: -1})
err = activitymod.FindManyActivityInfoWithBind(ctx, &data.List, filter, opt)
if err != nil {
return stdLog.QueryDataError, data, err
}
for _, v := range data.List {
lcFilter := bson.M{activitymod.FN.LottertyCodeInfo.ActId: v.Id.Hex()}
v.LotteryCodeNum, _ = activitymod.CountLottertyCodeInfos(ctx, lcFilter)
lcFilter[activitymod.FN.LottertyCodeInfo.IsUsed] = true
v.UsedLotteryCodeNum, _ = activitymod.CountLottertyCodeInfos(ctx, lcFilter)
gcFilter := bson.M{activitymod.FN.LotteryGetGift.ActId: v.Id.Hex()}
v.GiftCodeNum, _ = activitymod.CountLotteryGetGifts(ctx, gcFilter)
gcgFilter := bson.M{
activitymod.FN.LotteryGetGift.ActId: v.Id.Hex(),
activitymod.FN.LotteryGetGift.IsGaveOut: true,
}
v.GiftCodeGivedNum, _ = activitymod.CountLotteryGetGifts(ctx, gcgFilter)
gcuFilter := bson.M{
activitymod.FN.LotteryGetGift.ActId: v.Id.Hex(),
activitymod.FN.LotteryGetGift.IsUsed: true,
}
v.GiftCodeUsedNum, _ = activitymod.CountLotteryGetGifts(ctx, gcuFilter)
}
return stdLog.Success, data, nil
}
// 添加活动
func AddOneAct(ctx context.Context, param proto.AddActivityReq) (stdLog.CodeType, error) {
now := time.Now()
doc := activitymod.ActivityInfo{
Id: primitive.NewObjectID(),
Name: param.Name,
Desc: param.Desc,
SortCode: param.SortCode,
H5Domain: param.H5Domain,
GiftDesc: param.GiftDesc,
Enable: param.Enable,
Background: param.Background,
StartTime: param.StartTime,
EndTime: param.EndTime,
CreatedAt: now,
Banner: param.Banner,
}
err := activitymod.InsertOneActivityInfo(ctx, &doc)
if err != nil {
return stdLog.InsertDataError, err
}
return stdLog.Success, nil
}
// 更新一个活动
func UpdateOneAct(ctx context.Context, param *proto.UpdateActivityReq) (stdLog.CodeType, error) {
now := time.Now()
param.UpdatedAt = now
filter := bson.M{activitymod.FN.ActivityInfo.Id: param.Id}
updateDoc := bson.M{"$set": param}
err := activitymod.UpdateOneActivityInfo(ctx, filter, updateDoc)
if err != nil {
return stdLog.UpdateDataError, err
}
return stdLog.Success, nil
}
// 删除一个活动
func DeleteOneAct(ctx context.Context, param proto.DelActivityReq) (stdLog.CodeType, error) {
filter := bson.M{activitymod.FN.ActivityInfo.Id: param.Id}
count, err := activitymod.DelOneActivityInfo(ctx, filter)
if err != nil {
return stdLog.UpdateDataError, err
}
if count == 0 {
return stdLog.DataNotExist, nil
}
return stdLog.Success, nil
}
func UpdateActivityVisitCount(ctx context.Context, peerIp string, param proto.ActivityVisitCountReq) (stdLog.CodeType, error) {
if param.Ip == "" {
param.Ip = peerIp
}
aa := &activityVisit.ActivityVisit{
AppId: param.AppId,
IP: param.Ip,
CH: param.CH,
ActivityId: param.ActivityId,
ActivityName: param.ActivityName,
PeerIP: peerIp,
CreatedAt: time.Now(),
}
if err := activityVisit.InsertActivityVisit(ctx, aa); err != nil {
return stdLog.InsertDataError, err
}
return stdLog.Success, nil
}
// 活动列表
func GetActivityVisitList(ctx context.Context, param proto.ActivityVisitCountListReq) (stdLog.CodeType, *proto.ActivityVisitListResp, error) {
if param.PageSize <= 0 {
param.PageSize = 10
}
if param.PageNum <= 0 {
param.PageNum = 1
}
data := &proto.ActivityVisitListResp{List: make([]*proto.ActivityVisitInfo, 0)}
filter := bson.M{}
if param.ActivityId != nil {
filter["activityId"] = *param.ActivityId
}
if param.AppId != nil {
filter["appId"] = *param.AppId
}
if param.CH != nil {
filter["ch"] = *param.CH
}
count, err := activityVisit.CountActivityVisit(ctx, filter)
if err != nil {
return stdLog.QueryDataError, data, err
}
data.Total = count
opt := &options.FindOptions{}
skip := (param.PageNum - 1) * param.PageSize
if param.PageSize <= 0 {
param.PageSize = 10
}
opt.SetSkip(skip)
opt.SetLimit(param.PageSize)
opt.SetSort(bson.M{"createdAt": -1})
lists, err := activityVisit.FindManyActivityList(ctx, filter, opt)
if err != nil {
return stdLog.QueryDataError, data, err
}
for _, v := range lists {
data.List = append(data.List, &proto.ActivityVisitInfo{
AppId: v.AppId,
IP: v.IP,
CH: v.CH,
DevType: v.DevType,
SysType: v.SysType,
PeerIP: v.PeerIP,
ActivityId: v.ActivityId.Hex(),
ActivityName: v.ActivityName,
CreatedAt: v.CreatedAt,
UpdatedAt: v.UpdatedAt,
})
}
return stdLog.Success, data, nil
}
+244
View File
@@ -0,0 +1,244 @@
package activitySer
import (
"context"
"fmt"
"math/rand"
"strconv"
"time"
"stat/api/proto"
"stat/common/log"
"stat/common/mongos"
"stat/common/redis"
"stat/common/stdLog"
"stat/common/utils"
"stat/models/activitymod"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
var ipDrawLimit = 20 // 同一IP每天抽奖限制
// H5活动详情
func GetActivityInfo(ctx context.Context, param proto.ActivityInfoReq) (stdLog.CodeType, *proto.ActivityInfoResp, error) {
now := time.Now()
res := &proto.ActivityInfoResp{}
filter := bson.M{activitymod.FN.ActivityInfo.Id: param.ActId}
data, err := activitymod.FindOneActivityInfo(ctx, filter)
if err != nil {
return stdLog.QueryDataError, res, err
}
if data == nil {
return stdLog.ActivityNoExistErr, res, fmt.Errorf("data does not exist")
}
if now.Before(data.StartTime) || now.After(data.EndTime) || !data.Enable {
return stdLog.ActivityNoExistErr, res, fmt.Errorf("activity is closed")
}
res = &proto.ActivityInfoResp{
Id: data.Id,
Name: data.Name,
Desc: data.Desc,
H5Domain: data.H5Domain,
GiftDesc: data.GiftDesc,
Enable: data.Enable,
StartTime: data.StartTime,
EndTime: data.EndTime,
Banner: data.Banner,
}
return stdLog.Success, res, nil
}
// H5页面奖品信息
func GetActivityGiftInfo(ctx context.Context, param proto.ActivityGiftInfoReq) (stdLog.CodeType, *proto.ActivityGiftInfoResp, error) {
data := &proto.ActivityGiftInfoResp{List: make([]*proto.H5GiftInfo, 0)}
filter := bson.M{activitymod.FN.LotteryGift.ActId: param.ActId}
count, err := activitymod.CountLotteryGifts(ctx, filter)
if err != nil {
return stdLog.QueryDataError, data, err
}
data.Count = count
opt := &options.FindOptions{}
opt.SetSort(bson.M{activitymod.FN.LotteryGift.Sort: 1})
if err := activitymod.FindManyLotteryGiftWithBind(ctx, &data.List, filter, opt); err != nil {
return stdLog.QueryDataError, data, err
}
return stdLog.Success, data, nil
}
// 抽奖
func Draw(ctx context.Context, param *proto.DrawReq) (stdLog.CodeType, *proto.DrawResp, error) {
res := &proto.DrawResp{}
now := time.Now()
// 活动是否存在
actFilter := bson.M{activitymod.FN.ActivityInfo.Id: param.ActId}
data, err := activitymod.FindOneActivityInfo(ctx, actFilter)
if err != nil {
return stdLog.QueryDataError, res, err
}
if data == nil {
return stdLog.ActivityNoExistErr, res, fmt.Errorf("data does not exist")
}
if now.Before(data.StartTime) || now.After(data.EndTime) || !data.Enable {
return stdLog.ActivityNoExistErr, res, fmt.Errorf("activity is closed")
}
// 抽奖码是否有效
lcFilter := bson.M{
activitymod.FN.LottertyCodeInfo.LotteryCode: param.LotteryCode,
activitymod.FN.LottertyCodeInfo.ActId: param.ActId.Hex(),
}
lcInfo, err := activitymod.FindOneLottertyCodeInfo(ctx, lcFilter)
if err != nil || lcInfo == nil {
return stdLog.LotteryCodeIsInvalid, res, fmt.Errorf("lottery code is invalid")
}
if lcInfo.IsUsed {
return stdLog.LotteryCodeIsUsed, res, fmt.Errorf("lottery code is used")
}
// 根据用户IP限制抽奖次数
if !CanUserDraw(param.Ip, param.ActId.Hex()) {
return stdLog.LotteryNumReachedTheLimit, res, fmt.Errorf("the number of lottery reached the limit")
}
// 抽奖
gFilter := bson.M{activitymod.FN.LotteryGift.ActId: param.ActId.Hex()}
opt := &options.FindOptions{}
opt.SetSort(bson.M{activitymod.FN.LotteryGift.Rate: -1})
giftList, err := activitymod.FindManyLotteryGift(ctx, gFilter, opt)
if err != nil {
return stdLog.QueryDataError, res, fmt.Errorf("lottery gift data err")
}
var giftRand int32
for _, v := range giftList {
giftRand += v.Rate
}
flag := true
for {
randNum := rand.Int31n(giftRand)
// fmt.Printf("\ngiftRand is %d at 1st\n", giftRand)
// fmt.Printf("\nrandNum is %d at 1st\n", randNum)
for _, w := range giftList {
// fmt.Printf("randNum is %d before minus\n", randNum)
// fmt.Printf("\ngiftRand is %d before minus\n", giftRand)
randNum -= w.Rate
giftRand -= w.Rate
// fmt.Printf("\nAfter minus randNum is %d\nw.Rate is %d\ngift info %+v\n", randNum, w.Rate, w)
// fmt.Printf("\nAfter minus giftRand is %d\n", giftRand)
if randNum <= 0 {
var notgaveNum int64 // total 每种奖品的总数量, givedNum 每种奖品未被抽中的数量
gFilter := bson.M{
activitymod.FN.LotteryGetGift.GiftId: w.Id.Hex(),
activitymod.FN.LotteryGetGift.ActId: w.ActId,
activitymod.FN.LotteryGetGift.LotteryCode: "",
}
info, _ := activitymod.FindManyLotteryGetGift(ctx, gFilter)
notgaveNum = int64(len(info))
if notgaveNum == 0 {
randNum = rand.Int31n(giftRand + 1)
continue
}
res.GiftId = w.Id.Hex()
res.GiftName = w.GiftName
res.GiftCode = info[0].GiftCode
flag = false
break
}
}
if !flag {
break
}
}
fn := func(sctx mongo.SessionContext) error {
// 更新lotterycodeinfo
lciFilter := bson.M{
activitymod.FN.LottertyCodeInfo.LotteryCode: param.LotteryCode,
activitymod.FN.LottertyCodeInfo.ActId: param.ActId.Hex(),
}
lciUpdate := bson.M{"$set": bson.M{
activitymod.FN.LottertyCodeInfo.UserIp: param.Ip,
activitymod.FN.LottertyCodeInfo.LotteryAt: now,
activitymod.FN.LottertyCodeInfo.UpdatedAt: now,
activitymod.FN.LottertyCodeInfo.IsUsed: true,
activitymod.FN.LottertyCodeInfo.GiftCode: res.GiftCode,
activitymod.FN.LottertyCodeInfo.Remark: "已使用",
}}
err := activitymod.UpdateOneLottertyCodeInfo(sctx, lciFilter, lciUpdate)
if err != nil {
log.Warn("Draw: UpdateOneLottertyCodeInfo err", log.Any("err", err))
return err
}
// 更新lotterygetgift
lggFilter := bson.M{
activitymod.FN.LotteryGetGift.GiftCode: res.GiftCode,
activitymod.FN.LotteryGetGift.ActId: param.ActId.Hex(),
}
lggUpdate := bson.M{"$set": bson.M{
activitymod.FN.LotteryGetGift.UpdatedAt: now,
activitymod.FN.LotteryGetGift.LotteryCode: param.LotteryCode,
activitymod.FN.LotteryGetGift.IsGaveOut: true,
activitymod.FN.LotteryGetGift.Remark: "兑换码已发放",
}}
err = activitymod.UpdateOneLotteryGetGift(sctx, lggFilter, lggUpdate)
if err != nil {
log.Error("Draw: UpdateOneLotteryGetGift err", log.Any("err", err))
return err
}
return nil
}
if err := mongos.Transaction(ctx, fn); err != nil {
return stdLog.Failure, &proto.DrawResp{}, err
}
return stdLog.Success, res, nil
}
// 验证用户Ip是否能抽奖
func CanUserDraw(ip, actId string) bool {
redisKey := fmt.Sprintf("actId:%s:userIp:%s", actId, ip)
numStr, err := redis.StatRds.Get(redisKey)
if err != nil {
log.Error("canUserDraw redis get err", log.E(err))
return false
}
var num = int64(0)
if numStr != nil {
num, err = strconv.ParseInt(*numStr, 10, 64)
if err != nil {
log.Error("canUserDraw str to int64 err", log.E(err))
return false
}
}
if num > int64(ipDrawLimit) {
log.Warn("canUserDraw user IP triggered the limit")
return false
}
val, err := redis.StatRds.Incr(redisKey)
if err != nil {
log.Error("canUserDraw redis get err", log.E(err))
return false
}
if val == 1 {
duration := redis.StatRds.TTL(redisKey)
if duration == time.Duration(time.Second*-1) {
expiredAt := utils.EndOfToday().UTC().Sub(time.Now().UTC())
// expiredAt = expiredAt - (time.Second * 36200)
// str := expiredAt.String()
// fmt.Printf("\nexpiredAt is %d\n", expiredAt)
// fmt.Printf("\nstr is %s\n", str)
redis.StatRds.Expire(redisKey, expiredAt)
}
}
if val > int64(ipDrawLimit) {
log.Warn("canUserDraw user IP triggered the limit")
return false
}
return true
}
+32
View File
@@ -0,0 +1,32 @@
package activitySer
import (
"fmt"
"os"
"stat/common/log"
"stat/common/redis"
"testing"
)
func initRedis() error {
rds, err := redis.NewManagerWithUrl("redis://:B6vJHlYiTseLj77z@118.107.44.18:16789/7")
if err != nil {
log.Error("rds init err", log.E(err))
return err
}
redis.StatRds = rds
return nil
}
func TestCanUserDraw(t *testing.T) {
// 初始化redis
if err := initRedis(); err != nil {
os.Exit(1)
return
}
res := CanUserDraw("610.0.21.100", "6063296c6d2c51859840b072")
fmt.Print(res)
}
+137
View File
@@ -0,0 +1,137 @@
package activitySer
import (
"context"
"strings"
"time"
"stat/api/proto"
"stat/common/gencode"
"stat/common/stdLog"
"stat/models/activitymod"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
// 生成活动抽奖码
func GenLotteryCodes(ctx context.Context, param proto.GenLotteryCodeReq) (stdLog.CodeType, error) {
now := time.Now()
filter := bson.M{activitymod.FN.ActivityInfo.Id: param.ActId}
info, err := activitymod.FindOneActivityInfo(ctx, filter)
if err != nil {
return stdLog.QueryDataError, err
}
doc := &activitymod.LottertyCodeInfo{
ActId: param.ActId.Hex(),
ActName: info.Name,
IsUsed: false,
CreatedAt: now,
}
for i := 0; i < int(param.CodeNum); i++ {
code, err := genLotteryCode(ctx, int(param.CodeLen))
if err != nil {
return stdLog.Failure, err
}
doc.LotteryCode = code
doc.Id = primitive.NewObjectID()
if err := activitymod.InsertOneLottertyCodeInfo(ctx, doc); err != nil {
return stdLog.InsertDataError, err
}
}
return stdLog.Success, nil
}
func genLotteryCode(ctx context.Context, len int) (string, error) {
// 生成码
for {
code := strings.ToUpper(gencode.CodeGenerate(len))
filter := bson.M{activitymod.FN.LottertyCodeInfo.LotteryCode: code}
_, err := activitymod.FindOneLottertyCodeInfo(ctx, filter)
if err != nil {
if err != mongo.ErrNoDocuments {
return "", err
}
return code, nil
}
}
}
// 修改抽奖码信息
func UpdateLotteryCode(ctx context.Context, param proto.UpdateLotteryCodeReq) (stdLog.CodeType, error) {
now := time.Now()
filter := bson.M{activitymod.FN.LottertyCodeInfo.Id: param.LotteryCodeId}
updateDoc := bson.M{"$set": bson.M{
activitymod.FN.LottertyCodeInfo.Remark: param.Mark,
activitymod.FN.LottertyCodeInfo.UpdatedAt: now,
}}
err := activitymod.UpdateOneLottertyCodeInfo(ctx, filter, updateDoc)
if err != nil {
return stdLog.UpdateDataError, err
}
return stdLog.Success, nil
}
// 删除一个活动抽奖码
func DeleteOneLotteryCode(ctx context.Context, param proto.DelLotteryCodeReq) (stdLog.CodeType, error) {
filter := bson.M{activitymod.FN.ActivityInfo.Id: param.LotteryCodeId}
count, err := activitymod.DelOneLottertyCodeInfo(ctx, filter)
if err != nil {
return stdLog.UpdateDataError, err
}
if count == 0 {
return stdLog.DataNotExist, nil
}
return stdLog.Success, nil
}
// 活动抽奖码列表
func GetLotteryCodeList(ctx context.Context, param proto.LotteryCodeListReq) (stdLog.CodeType, *proto.LotteryCodeListResp, error) {
if param.PageSize <= 0 {
param.PageSize = 10
}
if param.PageNum <= 0 {
param.PageNum = 1
}
data := &proto.LotteryCodeListResp{List: make([]*activitymod.LottertyCodeInfo, 0)}
filter := bson.M{}
if param.LotteryCode != nil {
filter[activitymod.FN.LottertyCodeInfo.LotteryCode] = *param.LotteryCode
}
if param.ActId != nil {
filter[activitymod.FN.LottertyCodeInfo.ActId] = *param.ActId
}
if param.ActName != nil {
filter[activitymod.FN.LottertyCodeInfo.ActName] = bson.M{"$regex": *param.ActName}
}
if param.IsUsed != nil {
filter[activitymod.FN.LottertyCodeInfo.IsUsed] = *param.IsUsed
}
if param.GiftCode != nil {
filter[activitymod.FN.LottertyCodeInfo.GiftCode] = *param.GiftCode
}
count, err := activitymod.CountLottertyCodeInfos(ctx, filter)
if err != nil {
return stdLog.QueryDataError, data, err
}
data.Total = count
opt := &options.FindOptions{}
skip := (param.PageNum - 1) * param.PageSize
if param.PageSize <= 0 {
param.PageSize = 10
}
opt.SetSkip(skip)
opt.SetLimit(param.PageSize)
opt.SetSort(bson.M{activitymod.FN.LottertyCodeInfo.CreatedAt: -1})
list, err := activitymod.FindManyLottertyCodeInfo(ctx, filter, opt)
if err != nil {
return stdLog.QueryDataError, data, err
}
data.List = list
return stdLog.Success, data, nil
}
+174
View File
@@ -0,0 +1,174 @@
package activitySer
import (
"context"
"fmt"
"strings"
"time"
"stat/api/proto"
"stat/common/gencode"
"stat/common/stdLog"
"stat/models/activitymod"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
// 生成活动奖品兑换码
func GenManyGiftCode(ctx context.Context, param proto.GenGiftCodeRes) (stdLog.CodeType, error) {
now := time.Now()
filter := bson.M{activitymod.FN.LotteryGift.Id: param.GiftId}
info, err := activitymod.FindOneLotteryGift(ctx, filter)
if err != nil {
return stdLog.QueryDataError, err
}
doc := &activitymod.LotteryGetGift{
ActId: info.ActId,
ActName: info.ActName,
GiftId: info.Id.Hex(),
GiftName: info.GiftName,
CreatedAt: now,
}
for i := 0; i < int(param.CodeNum); i++ {
code, err := genGiftCode(ctx, int(param.CodeLen))
if err != nil {
return stdLog.Failure, err
}
doc.GiftCode = code
doc.Id = primitive.NewObjectID()
if err = activitymod.InsertOneLotteryGetGift(ctx, doc); err != nil {
return stdLog.InsertDataError, err
}
}
return stdLog.Success, nil
}
func genGiftCode(ctx context.Context, len int) (string, error) {
// 生成码
for {
code := strings.ToUpper(gencode.CodeGenerate(len))
filter := bson.M{activitymod.FN.LotteryGetGift.GiftCode: code}
_, err := activitymod.FindOneLotteryGetGift(ctx, filter)
if err != nil {
if err != mongo.ErrNoDocuments {
return "", err
}
return code, nil
}
}
}
// 修改奖品兑换信息
func UpdateOneRecvGiftInfo(ctx context.Context, param *proto.UpdateGetGiftReq) (stdLog.CodeType, error) {
now := time.Now()
if param.GiftCode == "" {
return stdLog.ParamError, fmt.Errorf("giftcode param err")
}
filter := bson.M{
activitymod.FN.LotteryGetGift.Id: param.Id,
activitymod.FN.LotteryGetGift.GiftCode: param.GiftCode,
}
doc := bson.M{}
if param.UserId == nil || *param.UserId == "" {
return stdLog.ParamError, fmt.Errorf("userId can not be nil")
} else {
doc[activitymod.FN.LotteryGetGift.UserId] = *param.UserId
}
if param.AppName == nil || *param.AppName == "" {
return stdLog.ParamError, fmt.Errorf("appName can not be nil")
} else {
doc[activitymod.FN.LotteryGetGift.AppName] = *param.AppName
}
if param.Remark != nil {
doc[activitymod.FN.LotteryGetGift.Remark] = *param.Remark
}
doc[activitymod.FN.LotteryGetGift.GetGiftAt] = now
doc[activitymod.FN.LotteryGetGift.UpdatedAt] = now
doc[activitymod.FN.LotteryGetGift.IsUsed] = true
updateDoc := bson.M{"$set": doc}
err := activitymod.UpdateOneLotteryGetGift(ctx, filter, updateDoc)
if err != nil {
return stdLog.UpdateDataError, err
}
return stdLog.Success, nil
}
// 删除一个活动奖品兑换信息
func DelOneRecvGiftInfo(ctx context.Context, param proto.DelGetGiftReq) (stdLog.CodeType, error) {
filter := bson.M{activitymod.FN.LotteryGetGift.Id: param.Id}
count, err := activitymod.DelOneLotteryGetGift(ctx, filter)
if err != nil {
return stdLog.UpdateDataError, err
}
if count == 0 {
return stdLog.DataNotExist, nil
}
return stdLog.Success, nil
}
// 奖品兑换详情列表
func GetReceiveGiftInfoList(ctx context.Context, param proto.GetGiftInfoReq) (stdLog.CodeType, *proto.GetGiftInfoResp, error) {
if param.PageSize <= 0 {
param.PageSize = 10
}
if param.PageNum <= 0 {
param.PageNum = 1
}
data := &proto.GetGiftInfoResp{List: make(activitymod.LotteryGetGiftList, 0)}
filter := bson.M{}
if param.LotteryCode != nil {
filter[activitymod.FN.LotteryGetGift.LotteryCode] = *param.LotteryCode
}
if param.ActId != nil {
filter[activitymod.FN.LotteryGetGift.ActId] = bson.M{"$regex": *param.ActId}
}
if param.ActName != nil {
filter[activitymod.FN.LotteryGetGift.ActName] = bson.M{"$regex": *param.ActName}
}
if param.GiftId != nil {
filter[activitymod.FN.LotteryGetGift.GiftId] = bson.M{"$regex": *param.GiftId}
}
if param.GiftName != nil {
filter[activitymod.FN.LotteryGetGift.GiftName] = *param.GiftName
}
if param.UserId != nil {
filter[activitymod.FN.LotteryGetGift.UserId] = *param.UserId
}
if param.AppName != nil {
filter[activitymod.FN.LotteryGetGift.AppName] = *param.AppName
}
if param.IsUsed != nil {
filter[activitymod.FN.LotteryGetGift.IsUsed] = *param.IsUsed
}
if param.GiftCode != nil {
filter[activitymod.FN.LotteryGetGift.GiftCode] = *param.GiftCode
}
if param.GiftCode != nil {
filter[activitymod.FN.LotteryGetGift.GiftCode] = *param.GiftCode
}
if param.IsGaveOut != nil {
filter[activitymod.FN.LotteryGetGift.IsGaveOut] = *param.IsGaveOut
}
count, err := activitymod.CountLotteryGetGifts(ctx, filter)
if err != nil {
return stdLog.QueryDataError, data, err
}
data.Total = count
opt := &options.FindOptions{}
skip := (param.PageNum - 1) * param.PageSize
if param.PageSize <= 0 {
param.PageSize = 10
}
opt.SetSkip(skip)
opt.SetLimit(param.PageSize)
opt.SetSort(bson.M{activitymod.FN.LotteryGetGift.CreatedAt: -1})
if err := activitymod.FindManyLotteryGetGiftWithBind(ctx, &data.List, filter, opt); err != nil {
return stdLog.QueryDataError, data, err
}
return stdLog.Success, data, nil
}
+117
View File
@@ -0,0 +1,117 @@
package activitySer
import (
"context"
"time"
"stat/api/proto"
"stat/common/stdLog"
"stat/models/activitymod"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo/options"
)
// 添加活动奖品
func AddOneGift(ctx context.Context, param proto.AddGiftReq) (stdLog.CodeType, error) {
now := time.Now()
filter := bson.M{activitymod.FN.ActivityInfo.Id: param.ActId}
info, err := activitymod.FindOneActivityInfo(ctx, filter)
if err != nil {
return stdLog.QueryDataError, err
}
doc := &activitymod.LotteryGift{
Id: primitive.NewObjectID(),
ActId: info.Id.Hex(),
ActName: info.Name,
GiftName: param.GiftName,
GiftDesc: param.GiftDesc,
Rate: param.Rate,
Sort: param.Sort,
CreatedAt: now,
}
if err = activitymod.InsertOneLotteryGift(ctx, doc); err != nil {
return stdLog.InsertDataError, err
}
return stdLog.Success, nil
}
// 修改奖品信息
func UpdateOneGift(ctx context.Context, param *proto.UpdateGiftReq) (stdLog.CodeType, error) {
now := time.Now()
param.UpdatedAt = now
filter := bson.M{activitymod.FN.LotteryGift.Id: param.Id}
updateDoc := bson.M{"$set": param}
err := activitymod.UpdateOneLotteryGift(ctx, filter, updateDoc)
if err != nil {
return stdLog.UpdateDataError, err
}
return stdLog.Success, nil
}
// 删除一个活动奖品
func DeleteOneGift(ctx context.Context, param proto.DelGiftReq) (stdLog.CodeType, error) {
filter := bson.M{activitymod.FN.LotteryGift.Id: param.Id}
count, err := activitymod.DelOneLotteryGift(ctx, filter)
if err != nil {
return stdLog.UpdateDataError, err
}
if count == 0 {
return stdLog.DataNotExist, nil
}
return stdLog.Success, nil
}
// 活动奖品列表
func GetLotteryGiftList(ctx context.Context, param proto.GiftListReq) (stdLog.CodeType, *proto.GiftListResp, error) {
if param.PageSize <= 0 {
param.PageSize = 10
}
if param.PageNum <= 0 {
param.PageNum = 1
}
data := &proto.GiftListResp{List: make([]*proto.GiftInfo, 0)}
filter := bson.M{}
if param.ActId != nil {
filter[activitymod.FN.LotteryGift.ActId] = *param.ActId
}
if param.GiftName != nil {
filter[activitymod.FN.LotteryGift.GiftName] = bson.M{"$regex": *param.GiftName}
}
if param.GiftDesc != nil {
filter[activitymod.FN.LotteryGift.GiftDesc] = bson.M{"$regex": *param.GiftDesc}
}
if param.Rate != nil {
filter[activitymod.FN.LotteryGift.Rate] = *param.Rate
}
count, err := activitymod.CountLotteryGifts(ctx, filter)
if err != nil {
return stdLog.QueryDataError, data, err
}
data.Total = count
opt := &options.FindOptions{}
skip := (param.PageNum - 1) * param.PageSize
if param.PageSize <= 0 {
param.PageSize = 10
}
opt.SetSkip(skip)
opt.SetLimit(param.PageSize)
opt.SetSort(bson.M{activitymod.FN.LotteryGift.Sort: 1})
if err := activitymod.FindManyLotteryGiftWithBind(ctx, &data.List, filter, opt); err != nil {
return stdLog.QueryDataError, data, err
}
for _, v := range data.List {
gFilter := bson.M{activitymod.FN.LotteryGetGift.GiftId: v.Id.Hex()}
gCount, _ := activitymod.CountLotteryGetGifts(ctx, gFilter)
v.GiftNum = gCount
gFilter[activitymod.FN.LotteryGetGift.IsUsed] = true
usedCount, _ := activitymod.CountLotteryGetGifts(ctx, gFilter)
v.ReceivedGiftNum = usedCount
}
return stdLog.Success, data, nil
}
+207
View File
@@ -0,0 +1,207 @@
package adminSer
import (
"context"
"encoding/base32"
"fmt"
"stat/middleware/define"
"time"
"stat/api/proto"
"stat/common"
"stat/common/log"
"stat/common/stdLog"
"stat/models/adminMod"
"stat/models/authorityMod"
)
func AdminLogin(ctx context.Context, param proto.AdminVerifyReq) (code stdLog.CodeType, data adminMod.Admin, err error) {
res, err := adminMod.FindOneByAccount(ctx, *param.Account)
if err != nil || res.Account == "" {
code = stdLog.AdminAccountNotExists
return
}
// 绑定两步验证
if !res.HasBind {
_ = adminMod.TwoStepBind(ctx, res.Account)
}
// 首次登陆,生成token
roleInfo, err := authorityMod.GetAuthorityById(ctx, res.RoleId)
if err != nil {
log.Error("GetAuthorityById failed", log.E(err))
return stdLog.Failure, data, err
}
// 生成token
claims := define.Claims{
Type: define.Admin,
Name: res.Account,
Role: roleInfo.Role,
CID: res.MerchantId,
}
newToken, err := define.GenToken(&claims)
if err != nil {
log.Error("GenerateUserToken failed", log.E(err))
code = stdLog.Failure
return code, data, err
}
res.Token = newToken
// 刷新token
if err = adminMod.RefreshToken(ctx, res.Account, newToken); err != nil {
code = stdLog.Failure
return code, data, err
}
data = res
code = stdLog.Success
return
}
// 管理员列表
func AdminList(ctx context.Context, param proto.AdminListReq) (code stdLog.CodeType, data interface{}, err error) {
skip := (param.PageNum - 1) * param.PageSize
limit := param.PageSize
resp := proto.AdminListRes{
Total: 0,
List: make([]proto.AdminInfo, 0),
}
count, adminList, err := adminMod.FindAdminList(ctx, param.BlocRole, skip, limit)
if err != nil {
code = stdLog.Failure
return
}
authIds := make([]int64, 0)
for _, v := range adminList {
authIds = append(authIds, v.RoleId)
}
// 获取权限角色
authList, err := authorityMod.GetAuthorityByIds(ctx, authIds)
if err != nil {
code = stdLog.Failure
return
}
authMap := make(map[int64]*authorityMod.Authority)
for _, v := range authList {
_v := v
authMap[v.Id] = &_v
}
for _, v := range adminList {
role := ""
if roleInfo, ok := authMap[v.RoleId]; ok {
role = roleInfo.Role
}
tmp := proto.AdminInfo{
RoleId: v.RoleId,
Role: role,
MerchantId: v.MerchantId,
Account: v.Account,
Password: v.Password,
Name: v.Name,
CreatedAt: v.CreatedAt,
UpdatedAt: v.UpdatedAt,
LastTime: v.LastTime,
}
resp.List = append(resp.List, tmp)
}
resp.Total = count
data = resp
code = stdLog.Success
return
}
// 添加管理员账号
func AddAdmin(ctx context.Context, param proto.AddAdminReq) (code stdLog.CodeType, data interface{}, err error) {
secret := base32.StdEncoding.EncodeToString([]byte(fmt.Sprintf("admin%s", common.RandStr(10))))
now := time.Now()
doc := adminMod.Admin{
Account: *param.Account,
Password: *param.Password,
Name: *param.Name,
RoleId: *param.RoleId,
HasBind: false,
Secret: secret,
CreatedAt: now,
UpdatedAt: now,
}
if param.MerchantId != nil {
doc.MerchantId = *param.MerchantId
}
if param.BlocRole != nil {
doc.BlocRole = *param.BlocRole
}
roleInfo, err := authorityMod.GetAuthorityById(ctx, doc.RoleId)
if err != nil {
log.Error("GetAuthorityById failed", log.E(err))
return
}
// 生成token
claims := define.Claims{
Type: define.Admin,
Name: doc.Account,
Role: roleInfo.Role,
CID: doc.MerchantId,
}
newToken, err := define.GenToken(&claims)
if err != nil {
log.Error("GenerateUserToken failed", log.E(err))
code = stdLog.Failure
return code, data, err
}
doc.Token = newToken
if err = adminMod.AddAdmin(ctx, &doc); err != nil {
code = stdLog.Failure
return
}
code = stdLog.Success
return
}
// 修改管理员账号
func UpdateAdmin(ctx context.Context, param proto.UpdateAdminReq) (code stdLog.CodeType, data interface{}, err error) {
now := time.Now()
doc := adminMod.UpdateDoc{
Account: param.Account,
Password: param.Password,
Name: param.Name,
RoleId: param.RoleId,
Status: param.Status,
MerchantId: param.MerchantId,
UpdatedAt: &now,
}
if err = adminMod.UpdateAdmin(ctx, *param.Account, doc); err != nil {
code = stdLog.Failure
return
}
code = stdLog.Success
return
}
// 修改管理员账号
func DelAdmin(ctx context.Context, param proto.DelAdminReq) (code stdLog.CodeType, data interface{}, err error) {
if err = adminMod.DelAdmin(ctx, *param.Account); err != nil {
code = stdLog.Failure
return
}
code = stdLog.Success
return
}
+13
View File
@@ -0,0 +1,13 @@
package adminSer
import (
"encoding/base32"
"fmt"
"stat/common"
"testing"
)
func TestAddAdmin(t *testing.T) {
secret := base32.StdEncoding.EncodeToString([]byte(fmt.Sprintf("admin%s", common.RandStr(10))))
t.Log(secret)
}
+311
View File
@@ -0,0 +1,311 @@
package advertiseSer
import (
"context"
"stat/api/proto"
"stat/common/genId"
"stat/common/log"
"stat/common/redis"
"stat/common/stdLog"
"stat/common/utils"
"stat/models/advertiseLocMod"
"stat/models/advertiseMod"
"stat/models/appInfoMod"
"strings"
"time"
"go.mongodb.org/mongo-driver/bson"
)
// 获取广告列表
func ListAds(ctx context.Context, param proto.ListAdsReq) (code stdLog.CodeType, data interface{}, err error) {
resp := proto.ListAdsRes{
Total: 0,
List: make([]proto.AdsInfo, 0),
}
skip, limit := utils.ToDBPage(param.PageNum, param.PageSize)
// 获取广告位置信息
total, adsList, err := advertiseMod.FindAdvertiseList(ctx, param.AppId, param.IsActive,
param.LocId, skip, limit, param.Link, param.Title, param.DaoLiangTitle)
if err != nil {
code = stdLog.Failure
return
}
resp.Total = total
// 获取app信息
appIds := make([]int32, 0)
for _, v := range adsList {
appIds = append(appIds, v.AppId)
}
appList, err := appInfoMod.GetAppInfosByAppIds(ctx, appIds)
if err != nil {
code = stdLog.Failure
return
}
appMap := make(map[int32]appInfoMod.AppInfo)
for _, v := range appList {
appMap[v.AppId] = v
}
for _, v := range adsList {
appName := ""
if appInfo, ok := appMap[v.AppId]; ok {
appName = appInfo.Name
}
// 获取位置信息
adsLocInfo, err := advertiseLocMod.FindLocationByAppIdAndLocId(ctx, v.AppId, v.LocId)
if err != nil {
code = stdLog.Failure
return code, data, err
}
str := make([]string, 0)
if v.Tags != "" {
split := strings.Split(strings.TrimSpace(v.Tags), ",")
for _, s := range split {
str = append(str, s)
}
}
tmp := proto.AdsInfo{
Id: v.Id,
AppId: v.AppId,
AppName: appName,
Title: v.Title,
DaoLiangTitle: v.DaoLiangTitle,
Tags: str,
CoverImg: v.CoverImg,
VisibleChannels: v.VisibleChannels,
LocId: v.LocId,
LocName: adsLocInfo.Name,
ImgSizeDesc: adsLocInfo.ImgSizeDesc,
Links: make([]proto.LinkInfo, 0),
Sort: v.Sort,
IsActive: v.IsActive,
StartTime: v.StartTime,
EndTime: v.EndTime,
Remark: v.Remark,
ClickCount: v.ClickCount,
AmbientColor: v.AmbientColor,
CreatedAt: v.CreatedAt,
UpdatedAt: v.UpdatedAt,
}
for _, v := range v.Links {
tmp.Links = append(tmp.Links, proto.LinkInfo{
Link: v.Link,
Foreign: v.Foreign,
Weight: v.Weight,
})
}
resp.List = append(resp.List, tmp)
}
code = stdLog.Success
data = resp
return
}
// 新增广告
func AddAds(ctx context.Context, param proto.AddAdsReq, account string) (code stdLog.CodeType, data interface{}, err error) {
adsId := genId.GetNextAdsId()
now := time.Now()
doc := advertiseMod.Advertise{
Id: adsId,
AppId: param.AppId,
Title: param.Title,
Tags: param.Tags,
CoverImg: param.CoverImg,
LocId: param.LocId,
Sort: param.Sort,
IsActive: param.IsActive,
StartTime: param.StartTime,
EndTime: param.EndTime,
Remark: param.Remark,
AmbientColor: param.AmbientColor,
CreatedAt: now,
UpdatedAt: now,
}
for _, v := range param.Links {
doc.Links = append(doc.Links, advertiseMod.LinkInfo{
Link: v.Link,
Foreign: v.Foreign,
Weight: v.Weight,
})
}
if err = advertiseMod.AddAdvertise(ctx, &doc); err != nil {
code = stdLog.Failure
return
}
newInfo, _ := advertiseMod.FindOneAdvertise(ctx, bson.M{"_id": adsId})
if newInfo != nil {
CheckChange(ctx, nil, newInfo, account)
}
code = stdLog.Success
return
}
// 修改广告位置
func UpdateAds(ctx context.Context, param proto.UpdateAdsReq, account string) (code stdLog.CodeType, data interface{}, err error) {
now := time.Now()
doc := advertiseMod.UpdateAds{
AppId: param.AppId,
Title: param.Title,
DaoLiangTitle: param.DaoLiangTitle,
Tags: param.Tags,
CoverImg: param.CoverImg,
Location: param.Location,
JumpType: param.JumpType,
Sort: param.Sort,
IsActive: param.IsActive,
StartTime: param.StartTime,
EndTime: param.EndTime,
Remark: param.Remark,
AmbientColor: param.AmbientColor,
UpdatedAt: &now,
}
if *param.Links != nil {
links := make([]advertiseMod.LinkInfo, 0)
for _, v := range *param.Links {
links = append(links, advertiseMod.LinkInfo{
Link: v.Link,
Foreign: v.Foreign,
Weight: v.Weight,
})
}
doc.Links = &links
}
if param.VisibleChannels != nil {
channels := make([]string, 0)
if len(*param.VisibleChannels) > 0 {
ch := strings.Split(*param.VisibleChannels, ",")
channels = append(channels, ch...)
}
doc.VisibleChannels = &channels
}
if doc.Location != nil {
locId := int32(*doc.Location)
doc.LocId = &locId
}
oldInfo, err := advertiseMod.FindOneAdvertise(ctx, bson.M{"_id": *param.Id})
if err != nil {
code = stdLog.Failure
return
}
if err = advertiseMod.UpdateAdvertise(ctx, *param.Id, doc); err != nil {
code = stdLog.Failure
return
}
newInfo, err := advertiseMod.FindOneAdvertise(ctx, bson.M{"_id": *param.Id})
if err != nil {
code = stdLog.Failure
return
}
CheckChange(ctx, oldInfo, newInfo, account)
code = stdLog.Success
return
}
// 删除广告
func DelAds(ctx context.Context, param proto.DelAdsReq, account string) (code stdLog.CodeType, data interface{}, err error) {
oldInfo, err := advertiseMod.FindOneAdvertise(ctx, bson.M{"_id": *param.Id})
if err != nil {
code = stdLog.Failure
return
}
if err = advertiseMod.DelAdvertise(ctx, *param.Id); err != nil {
code = stdLog.Failure
return
}
CheckChange(ctx, oldInfo, nil, account)
code = stdLog.Success
return
}
// 外部获取广告
func GetAdvertise(ctx context.Context, param proto.AdvertiseReq) (code stdLog.CodeType, resp *proto.AdvertiseRes, err error) {
resp = &proto.AdvertiseRes{AdvertiseList: make([]*proto.AdvertiseInfo, 0)}
log.Info("GetAdvertise", log.Any("param", param))
adsList, err := advertiseMod.FindAdsByAppAndLocation(ctx, param.AppId, param.LocId)
if err != nil {
code = stdLog.Failure
return
}
log.Info("GetAdvertise", log.Any("param", param), log.Any("adsList", len(adsList)))
// 遍历广告,处理系统广告
for _, v := range adsList {
adsInfo := &proto.AdvertiseInfo{
Id: v.Id,
Title: v.Title,
CoverImg: v.CoverImg,
LocId: v.LocId,
JumpType: v.JumpType,
Sort: v.Sort,
Type: 0,
Tags: v.Tags,
Remark: v.Remark,
AmbientColor: v.AmbientColor,
}
if len(v.Links) >= 1 {
adsInfo.Link = v.Links[0].Link
}
resp.AdvertiseList = append(resp.AdvertiseList, adsInfo)
}
code = stdLog.Success
err = nil
return
}
// 增加广告点击次数
func IncAdsClickCount(ctx context.Context, param proto.AdsClickReq) (code stdLog.CodeType, data interface{}, err error) {
// 获取广告信息
adsInfo, err := advertiseMod.FindAdsById(ctx, *param.Id)
if err != nil || adsInfo.Id == 0 {
code = stdLog.AdvertiseInvalid
return
}
if adsInfo.AppId != *param.AppId {
code = stdLog.AppInfoError
return
}
// save to redis
now := time.Now().Local()
set := redis.GetAdvertiseClickDayHash(now)
name := redis.GetAdvertiseClickHourName(adsInfo.AppId, adsInfo.Id, now.Hour())
if _, err = set.IncInt(name, 1); err != nil {
code = stdLog.Failure
return
}
// update click+1 to db
if err = advertiseMod.IncAdsClickCount(ctx, *param.Id); err != nil {
code = stdLog.Failure
return
}
code = stdLog.Success
return
}
+151
View File
@@ -0,0 +1,151 @@
package advertiseSer
import (
"context"
"stat/api/proto"
"stat/common/genId"
"stat/common/stdLog"
"stat/common/utils"
"stat/models/advertiseLocMod"
"stat/models/advertiseMod"
"stat/models/appInfoMod"
"time"
)
// 获取广告位置列表
func ListAdsLoc(ctx context.Context, param proto.ListAdsLocReq) (code stdLog.CodeType, data interface{}, err error) {
resp := proto.ListAdsLocRes{
Total: 0,
List: make([]proto.AdsLocInfo, 0),
}
skip, limit := utils.ToDBPage(param.PageNum, param.PageSize)
// 获取广告位置信息
total, adsList, err := advertiseLocMod.FindAdvertiseLocationList(ctx, param.AppId, skip, limit)
if err != nil {
code = stdLog.Failure
return
}
resp.Total = total
// 获取app信息
appIds := make([]int32, 0)
for _, v := range adsList {
appIds = append(appIds, v.AppId)
}
appList, err := appInfoMod.GetAppInfosByAppIds(ctx, appIds)
if err != nil {
code = stdLog.Failure
return
}
appMap := make(map[int32]appInfoMod.AppInfo)
for _, v := range appList {
appMap[v.AppId] = v
}
for _, v := range adsList {
appName := ""
if appInfo, ok := appMap[v.AppId]; ok {
appName = appInfo.Name
}
tmp := proto.AdsLocInfo{
Id: v.Id,
AppId: v.AppId,
AppName: appName,
LocId: v.LocId,
Name: v.Name,
ImgSizeDesc: v.ImgSizeDesc,
Remark: v.Remark,
CreatedAt: v.CreatedAt,
UpdatedAt: v.UpdatedAt,
}
resp.List = append(resp.List, tmp)
}
code = stdLog.Success
data = resp
return
}
// 新增广告位置
func AddAdsLoc(ctx context.Context, param proto.AddAdsLocReq) (code stdLog.CodeType, data interface{}, err error) {
// 获取位置信息
locInfo, err := advertiseLocMod.FindLocationByAppIdAndLocId(ctx, *param.AppId, *param.LocId)
if err != nil || locInfo.Id > 0 {
code = stdLog.AdvertiseLocExists
return
}
adsLocId := genId.GetNextAdsLocId()
now := time.Now()
doc := advertiseLocMod.AdvertiseLocation{
Id: adsLocId,
AppId: *param.AppId,
LocId: *param.LocId,
Name: *param.Name,
ImgSizeDesc: param.ImgSizeDesc,
Remark: param.Remark,
CreatedAt: now,
UpdatedAt: now,
}
if err = advertiseLocMod.AddAdvertiseLocation(ctx, &doc); err != nil {
code = stdLog.Failure
return
}
code = stdLog.Success
return
}
// 修改广告位置
func UpdateAdsLoc(ctx context.Context, param proto.UpdateAdsLocReq) (code stdLog.CodeType, data interface{}, err error) {
// 获取位置信息
locInfo, err := advertiseLocMod.FindLocationById(ctx, *param.Id)
if err != nil {
code = stdLog.Failure
return
}
now := time.Now()
doc := advertiseLocMod.UpdateAdsLoc{
AppId: param.AppId,
LocId: param.LocId,
Name: param.Name,
ImgSizeDesc: param.ImgSizeDesc,
Remark: param.Remark,
UpdatedAt: &now,
}
if err = advertiseLocMod.UpdateAdvertiseLocation(ctx, *param.Id, doc); err != nil {
code = stdLog.Failure
return
}
// 修改广告的位置id
if param.LocId != nil && *param.LocId != locInfo.LocId {
if err = advertiseMod.UpdateLocInfo(ctx, locInfo.AppId, locInfo.LocId, *param.LocId); err != nil {
code = stdLog.Failure
return code, data, err
}
}
code = stdLog.Success
return
}
// 删除广告位置
func DelAdsLoc(ctx context.Context, param proto.DelAdsLocReq) (code stdLog.CodeType, data interface{}, err error) {
if err = advertiseLocMod.DelAdvertiseLocation(ctx, *param.Id); err != nil {
code = stdLog.Failure
return
}
code = stdLog.Success
return
}
+265
View File
@@ -0,0 +1,265 @@
package advertiseSer
import (
"context"
"stat/common/log"
"stat/models/advertiseLocMod"
"stat/models/advertiseMod"
"stat/models/advertisestat"
"stat/models/appInfoMod"
"go.mongodb.org/mongo-driver/mongo/options"
"go.mongodb.org/mongo-driver/bson"
)
// GetAdvertiseStatList 获取广告统计列表
func GetAdvertiseStatList(ctx context.Context, param *GetAdvertiseStatReq) (*GetAdvertiseStatResp, error) {
resp := &GetAdvertiseStatResp{}
statFilter := bson.M{}
if param.AppId != nil {
statFilter[advertisestat.FN.AdvertiseStat.AppId] = bson.M{"$in": *param.AppId}
}
if param.AdsId != nil {
statFilter[advertisestat.FN.AdvertiseStat.AdsId] = *param.AdsId
}
if param.AdsName != nil {
adsInfos, err := advertiseMod.FindAdvertiselist(ctx, bson.M{"title": param.AdsName})
if err != nil {
return nil, err
}
adsIds := make([]int64, 0)
for _, ad := range adsInfos {
adsIds = append(adsIds, ad.Id)
}
statFilter[advertisestat.FN.AdvertiseStat.AdsId] = bson.M{"$in": adsIds}
}
if param.Start != nil || param.End != nil {
statFilter[advertisestat.FN.AdvertiseStat.SumData] = bson.M{"$gte": *param.Start, "$lte": *param.End}
}
filter := bson.M{}
// app dict
appDict, err := appInfoMod.GetAllAppIdMap(ctx, filter)
if err != nil {
return nil, err
}
// ads dict
adsDict, err := advertiseMod.GetAllAdvertiseMap(ctx, filter)
if err != nil {
return nil, err
}
// ads location dict
locDict, err := advertiseLocMod.GetAllAdvertiseLocationMap(ctx, filter)
if err != nil {
return nil, err
}
opt := &options.FindOptions{}
opt.SetSort(bson.D{
{advertisestat.FN.AdvertiseStat.SumData, -1},
{advertisestat.FN.AdvertiseStat.Total, -1},
})
opt.SetLimit(param.PageSize)
opt.SetSkip((param.PageNum - 1) * param.PageSize)
totalStats, err := advertisestat.FindManyAdvertiseStat(ctx, statFilter)
if err != nil {
return nil, err
}
pageStats, err := advertisestat.FindManyAdvertiseStat(ctx, statFilter, opt)
if err != nil {
return nil, err
}
for _, stat := range totalStats {
resp.Summary.TotalClick += stat.Total
}
for _, stat := range pageStats {
appName := ""
if app := appDict[stat.AppId]; app != nil {
appName = app.Name
}
adsName := ""
locId := int32(0)
if ads := adsDict[stat.AdsId]; ads != nil {
adsName = ads.Title
locId = ads.LocId
}
locName := ""
if appLoc := locDict[stat.AppId]; appLoc != nil {
if loc := appLoc[locId]; loc != nil {
locName = loc.Name
}
}
hours := make([]int64, 24)
for hour := range hours {
hours[hour] = stat.HourData[hour]
}
resp.List = append(resp.List, &AdvertiseStatInfo{
Id: stat.Id,
AdsId: stat.AdsId,
AdsName: adsName,
AppId: stat.AppId,
AppName: appName,
LocId: locId,
LocName: locName,
SumDate: stat.SumDate,
Total: stat.Total,
HourData: hours,
CreatedAt: stat.CreatedAt,
UpdatedAt: stat.UpdatedAt,
})
}
resp.Total = int64(len(totalStats))
return resp, nil
}
// GetAdvertiseStatList 获取广告统计列表
func GetAdvertiseTotalStatList(ctx context.Context, param *GetAdvertiseTotalStatReq) (*GetAdvertiseTotalStatResp,
error) {
resp := &GetAdvertiseTotalStatResp{
List: make([]*AdvertiseTotalStatInfo, 0),
}
/*
statFilter := bson.M{}
if param.AppId != nil {
statFilter[advertisestat.FN.AdvertiseStat.AppId] = *param.AppId
}
if param.Start != nil || param.End != nil {
statFilter[advertisestat.FN.AdvertiseStat.SumData] = bson.M{"$gte": *param.Start, "$lte": *param.End}
}
*/
// app dict
appFilter := bson.M{}
if param.AppId != nil {
appFilter[appInfoMod.FN.AppInfo.AppId] = bson.M{"$in": *param.AppId}
}
apps, serr := appInfoMod.GetAllAppIdMap(ctx, appFilter)
if serr != nil {
return nil, serr
}
// ads dict
adsFilter := bson.M{}
if param.AppId != nil {
adsFilter["appId"] = bson.M{"$in": *param.AppId}
}
if param.AdsId != nil {
adsFilter["_id"] = *param.AdsId
}
if param.AdsName != nil {
adsFilter["title"] = *param.AdsName
}
adsDict, err := advertiseMod.GetAllAdvertiseMap(ctx, adsFilter)
if err != nil {
return nil, err
}
// ads location dict
locFilter := bson.M{}
if param.AppId != nil {
locFilter[appInfoMod.FN.AppInfo.AppId] = bson.M{"$in": *param.AppId}
}
locDict, err := advertiseLocMod.GetAllAdvertiseLocationMap(ctx, locFilter)
if err != nil {
return nil, err
}
filter := bson.M{}
if param.AppId != nil {
filter["appId"] = bson.M{"$in": *param.AppId}
}
if param.AdsId != nil {
filter["adsId"] = *param.AdsId
}
if param.AdsName != nil {
adsInfos, err := advertiseMod.FindAdvertiselist(ctx, bson.M{"title": param.AdsName})
if err != nil {
return nil, err
}
adsIds := make([]int64, 0)
for _, ad := range adsInfos {
adsIds = append(adsIds, ad.Id)
}
filter["adsId"] = bson.M{"$in": adsIds}
}
if param.Start != nil && param.End != nil {
filter["sumDate"] = bson.M{"$gte": *param.Start, "$lte": *param.End}
}
pipeline := []bson.M{
{
"$match": filter,
},
{
"$group": bson.M{
"_id": "$adsId",
"total": bson.M{"$sum": "$total"},
},
},
{"$sort": bson.D{{Key: "_id", Value: -1}}},
}
list := make([]AdvertiseTempInfo, 0)
serr1 := advertisestat.AggregateAdvertiseStat(ctx, &list, pipeline)
if serr1 != nil {
log.Info("", log.Any("serr1", serr1))
return nil, serr1
}
pipeline = []bson.M{
{
"$match": filter,
},
{
"$group": bson.M{
"_id": "$adsId",
"total": bson.M{"$sum": "$total"},
},
},
{"$sort": bson.D{{Key: "_id", Value: -1}}},
{"$skip": (param.PageNum - 1) * param.PageSize},
{"$limit": param.PageSize},
}
info := make([]AdvertiseTempInfo, 0)
serr1 = advertisestat.AggregateAdvertiseStat(ctx, &info, pipeline)
if serr1 != nil {
log.Info("", log.Any("serr1", serr1))
return nil, serr1
}
for _, temp := range list {
resp.Summary.TotalClick += temp.Total
}
for _, stat := range info {
appName := ""
adsName := ""
locName := ""
locId := int32(0)
appId := int32(0)
if ads := adsDict[stat.AdsId]; ads != nil {
adsName = ads.Title
locId = ads.LocId
if app := apps[ads.AppId]; app != nil {
appName = app.Name
appId = app.AppId
if appLoc := locDict[app.AppId]; appLoc != nil {
if loc := appLoc[locId]; loc != nil {
locName = loc.Name
}
}
}
}
resp.List = append(resp.List, &AdvertiseTotalStatInfo{
AdsId: stat.AdsId,
AdsName: adsName,
AppId: appId,
AppName: appName,
LocId: int64(locId),
LocName: locName,
Total: stat.Total,
})
}
resp.Total = int64(len(list))
return resp, nil
}
+74
View File
@@ -0,0 +1,74 @@
package advertiseSer
import (
"stat/common"
"time"
"go.mongodb.org/mongo-driver/bson/primitive"
)
type AdvertiseStatSummary struct {
TotalClick int64 `json:"totalClick"` // 总点击
}
type AdvertiseStatInfo struct {
Id primitive.ObjectID `json:"id" bson:"_id,omitempty"`
AdsId int64 `json:"adsId" bson:"adsId"` //广告ID
AdsName string `json:"adsName" bson:"adsName"` //广告名称
AppId int32 `json:"appId" bson:"appId"` //应用ID
AppName string `json:"appName" bson:"appName"` //应用名称
LocId int32 `json:"locId" bson:"locId"` //位置ID
LocName string `json:"locName" bson:"locName"` //位置名称
SumDate time.Time `json:"sumDate" bson:"sumDate"` //记录时间
Total int64 `json:"total" bson:"total"` //当日点击
HourData []int64 `json:"hourData" bson:"hourData"` //小时数据
CreatedAt time.Time `json:"createdAt" bson:"createdAt"` //创建时间
UpdatedAt time.Time `json:"updatedAt" bson:"updatedAt"` //更新时间
}
// GetAdvertiseStatReq 获取app点击统计请求
type GetAdvertiseStatReq struct {
common.Page
Start *time.Time `json:"start"` // 开始时间
End *time.Time `json:"end"` // 结束时间
AdsId *int64 `json:"adsId"` // 广告ID
AppId *[]int64 `json:"appId"` // 应用ID
AdsName *string `json:"adsName"` // 广告名称
}
type GetAdvertiseStatResp struct {
Total int64 `json:"total"`
List []*AdvertiseStatInfo `json:"list"`
Summary AdvertiseStatSummary `json:"summary"`
}
type AdvertiseTempInfo struct {
AdsId int64 `json:"id" bson:"_id"` //广告ID
Total int64 `json:"total" bson:"total"` //当日点击
}
// GetAdvertiseTotalStatReq 获取app点击统计请求
type GetAdvertiseTotalStatReq struct {
common.Page
Start *time.Time `json:"start"` // 开始时间
End *time.Time `json:"end"` // 结束时间
AppId *[]int64 `json:"appId"` // 应用ID
AdsId *int64 `json:"adsId"` // 广告ID
AdsName *string `json:"adsName"` // 广告名称
}
type AdvertiseTotalStatInfo struct {
AdsId int64 `json:"adsId" bson:"adsId"` //广告ID
AdsName string `json:"adsName" bson:"adsName"` //广告名称
AppId int32 `json:"appId" bson:"appId"` //应用ID
AppName string `json:"appName" bson:"appName"` //应用名称
LocId int64 `json:"locId" bson:"locId"` //位置ID
LocName string `json:"locName" bson:"locName"` //位置名称
Total int64 `json:"total" bson:"total"` //当日点击
}
type GetAdvertiseTotalStatResp struct {
Total int64 `json:"total"`
List []*AdvertiseTotalStatInfo `json:"list"`
Summary AdvertiseStatSummary `json:"summary"`
}
+155
View File
@@ -0,0 +1,155 @@
package advertiseSer
import (
"context"
"fmt"
"sort"
"stat/common/enum/consts"
"stat/common/utils"
"stat/models/advertiseLocMod"
"stat/models/advertiseMod"
"stat/models/appInfoMod"
"stat/statg"
"strings"
"time"
"go.mongodb.org/mongo-driver/bson"
)
func CheckChange(ctx context.Context, oldDoc, newDoc *advertiseMod.Advertise, account string) {
msg := ""
changed := false
apps, _ := appInfoMod.GetAllAppIdNameMap(ctx)
appId := int32(0)
// 检测更新
if oldDoc != nil && newDoc != nil {
warnChar := "☢️"
appId = oldDoc.AppId
msg += fmt.Sprintf("检测到广告内容变更:\n广告ID:【%d】\n名称:【%s】\n", oldDoc.Id, oldDoc.Title)
locs, _ := advertiseLocMod.GetAllAdvertiseLocationMap(ctx, bson.M{})
locName := ""
if nm := locs[oldDoc.AppId]; nm != nil {
if loc := nm[oldDoc.LocId]; loc != nil {
locName = loc.Name
}
}
msg += fmt.Sprintf("位置ID:【%d】\n位置名称:【%s】\n", oldDoc.LocId, locName)
if oldDoc.AppId != newDoc.AppId {
changed = true
warnChar = "🔯"
msg += fmt.Sprintf("应用变更:\n┏旧值【%s】(%d)\n┗新值【%s】(%d)\n", apps[oldDoc.AppId], oldDoc.AppId, apps[newDoc.AppId], newDoc.AppId)
}
if oldDoc.IsActive != newDoc.IsActive {
changed = true
msg += fmt.Sprintf("激活变更:\n┏旧值【%v】【%v】\n┗新值【%v】【%v】\n", oldDoc.IsActive, consts.StatusDesc[oldDoc.IsActive],
newDoc.IsActive, consts.StatusDesc[newDoc.IsActive])
}
if oldDoc.Title != newDoc.Title {
changed = true
msg += fmt.Sprintf("标题变更:\n┏旧值【%s】\n┗新值【%s】\n", oldDoc.Title, newDoc.Title)
}
if oldDoc.Sort != newDoc.Sort {
changed = true
msg += fmt.Sprintf("排序变更:\n┏旧值【%d】\n┗新值【%d】\n", oldDoc.Sort, newDoc.Sort)
}
if oldDoc.StartTime != newDoc.StartTime {
changed = true
msg += fmt.Sprintf("开始时间变更:\n┏旧值【%s】\n┗新值【%s】\n", oldDoc.StartTime.Format("2006-01-02T15:04:05"), newDoc.StartTime.Format("2006-01-02T15:04:05"))
}
if oldDoc.EndTime != newDoc.EndTime {
changed = true
msg += fmt.Sprintf("结束时间变更:\n┏旧值【%s】\n┗新值【%s】\n", oldDoc.EndTime.Format("2006-01-02T15:04:05"), newDoc.EndTime.Format("2006-01-02T15:04:05"))
}
if oldDoc.LocId != newDoc.LocId {
changed = true
oName := ""
if om := locs[oldDoc.AppId]; om != nil {
if loc := om[oldDoc.LocId]; loc != nil {
oName = loc.Name
}
}
nName := ""
if nm := locs[newDoc.AppId]; nm != nil {
if loc := nm[newDoc.LocId]; loc != nil {
nName = loc.Name
}
}
msg += fmt.Sprintf("位置变更:\n┏旧值【%s】(%d)\n┗新值【%s】(%d)\n", oName, oldDoc.LocId, nName, newDoc.LocId)
}
oLinks := utils.StrSliceConstruct(len(oldDoc.Links), func(index int) (string, bool) {
link := oldDoc.Links[index]
return link.Link, true
})
nLinks := utils.StrSliceConstruct(len(newDoc.Links), func(index int) (string, bool) {
link := newDoc.Links[index]
return link.Link, true
})
sort.Sort(sort.StringSlice(oLinks))
sort.Sort(sort.StringSlice(nLinks))
if strings.Join(oLinks, "") != strings.Join(nLinks, "") {
warnChar = "🛑"
changed = true
msg += fmt.Sprintf("链接变更:\n")
for index, link := range oLinks {
msg += fmt.Sprintf("┣%d.旧值: %s\n", index+1, link)
}
for index, link := range nLinks {
msg += fmt.Sprintf("┣%d.新值: %s\n", index+1, link)
}
} else {
msg += fmt.Sprintf("链接不变:【%d个】\n", len(nLinks))
for index, link := range nLinks {
msg += fmt.Sprintf("┣%d.链接: %s\n", index+1, link)
}
}
msg = warnChar + msg
} else if oldDoc == nil && newDoc != nil {
changed = true
appId = newDoc.AppId
msg += fmt.Sprintf("🟢检测到新增广告:\n广告ID:【%d】\n广告标题:【%s】\n", newDoc.Id, newDoc.Title)
locs, _ := advertiseLocMod.GetAllAdvertiseLocationMap(ctx, bson.M{})
locName := ""
if nm := locs[newDoc.AppId]; nm != nil {
if loc := nm[newDoc.LocId]; loc != nil {
locName = loc.Name
}
}
msg += fmt.Sprintf("位置ID:【%d】\n位置名称:【%s】\n", newDoc.LocId, locName)
nLinks := utils.StrSliceConstruct(len(newDoc.Links), func(index int) (string, bool) {
link := newDoc.Links[index]
return link.Link, true
})
msg += fmt.Sprintf("链接:【%d个】\n", len(nLinks))
for index, link := range nLinks {
msg += fmt.Sprintf("┣%d.链接: %s\n", index+1, link)
}
} else if oldDoc != nil && newDoc == nil {
changed = true
appId = oldDoc.AppId
msg += fmt.Sprintf("⛔️检测到删除广告:\n广告ID:【%d】\n广告标题:【%s】\n", oldDoc.Id, oldDoc.Title)
locs, _ := advertiseLocMod.GetAllAdvertiseLocationMap(ctx, bson.M{})
locName := ""
if nm := locs[oldDoc.AppId]; nm != nil {
if loc := nm[oldDoc.LocId]; loc != nil {
locName = loc.Name
}
}
msg += fmt.Sprintf("位置ID:【%d】\n位置名称:【%s】\n", oldDoc.LocId, locName)
nLinks := utils.StrSliceConstruct(len(oldDoc.Links), func(index int) (string, bool) {
link := oldDoc.Links[index]
return link.Link, true
})
msg += fmt.Sprintf("链接:【%d个】\n", len(nLinks))
for index, link := range nLinks {
msg += fmt.Sprintf("┣%d.链接: %s\n", index+1, link)
}
}
if changed {
msg += fmt.Sprintf("广告所属应用:【%s】(%d)\n", apps[appId], appId)
msg += fmt.Sprintf("操作人员账号:【%s】\n", account)
msg += fmt.Sprintf("操作发生时间:【%s】\n", time.Now().Format("2006-01-02T15:04:05"))
msg += fmt.Sprintf("请确认是否异常: %s \n", statg.Tgbotsend)
statg.Bot.SendAdvertiseMessage(msg)
}
}
+655
View File
@@ -0,0 +1,655 @@
package agentSer
import (
"bytes"
"context"
"errors"
"fmt"
"stat/api/proto"
"stat/common"
"stat/common/log"
"stat/common/stdLog"
"stat/common/utils"
"stat/middleware/define"
"stat/models/channelmod"
"stat/statg"
"strconv"
"strings"
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo/options"
)
func GetChannelInfoWithLockStatus(ctx context.Context, channel string) (*channelmod.ChannelInfo, stdLog.CodeType, error) {
channelFilter := bson.M{channelmod.FN.ChannelInfo.Channel: channel}
channelInfo, err := channelmod.FindOneChannelInfo(ctx, channelFilter)
if err != nil || channelInfo.Channel == "" {
return nil, stdLog.AgentAccountNotExists, fmt.Errorf("代理账号 %s 不存在", channel)
}
if channelInfo.Type.IsChild() {
channelMap, _, err := MakeChannelAndRuleMap(ctx, channelInfo)
if err != nil {
return nil, stdLog.QueryDataError, err
}
lock, status, showDivide := channelMap.GetLockStatusAndShowDivide(channelInfo)
channelInfo.Lock = lock
channelInfo.Status = status
channelInfo.ChildShowDivideRate = showDivide
}
return channelInfo, stdLog.Success, nil
}
func CheckToken(ctx context.Context, channel string, token string) error {
channelFilter := bson.M{channelmod.FN.ChannelInfo.Channel: channel}
channelInfo, err := channelmod.FindOneChannelInfo(ctx, channelFilter)
if err != nil {
return err
}
if channelInfo.Channel == "" {
return fmt.Errorf("代理账号 %s 不存在", channel)
}
if channelInfo.Lock || !channelInfo.Status {
return fmt.Errorf("代理账号 %s 被禁止", channel)
}
if channelInfo.Token != token {
return fmt.Errorf("登陆信息已过期,请重新登陆")
}
if time.Since(channelInfo.CheckTokenTime) > 6*time.Hour {
doc := bson.M{"$set": bson.M{
channelmod.FN.ChannelInfo.CheckTokenTime: time.Now(),
}}
if err = channelmod.UpdateOneChannelInfo(ctx, bson.M{channelmod.FN.ChannelInfo.Channel: channelInfo.Channel}, doc); err != nil {
return err
}
channelInfoWithLock, _, err := GetChannelInfoWithLockStatus(ctx, channel)
if err != nil {
return err
}
if channelInfoWithLock.Lock || !channelInfoWithLock.Status {
doc := bson.M{"$set": bson.M{
channelmod.FN.ChannelInfo.Token: "", // 清空token
}}
if err = channelmod.UpdateOneChannelInfo(ctx, bson.M{channelmod.FN.ChannelInfo.Channel: channelInfo.Channel}, doc); err != nil {
return err
}
return fmt.Errorf("本账号的上级代理账号 %s 被禁止", channel)
}
}
return nil
}
// 代理商账号登陆
func AgentLogin(ctx context.Context, ip string, param proto.AgentLoginReq) (code stdLog.CodeType, channelInfo *channelmod.ChannelInfo) {
var err error
channelInfo, code, err = GetChannelInfoWithLockStatus(ctx, *param.Account)
if err != nil {
return
}
// 禁止登陆
if channelInfo.Lock || !channelInfo.Status {
code = stdLog.AgentHasLocked
return
}
if *param.Password != channelInfo.Password {
code = stdLog.AdminPasswordError
return
}
if len(param.ImgCode) != 4 {
code = stdLog.VerifySmsCodeFailed
return
}
imgs := make([]byte, 4)
for i := range param.ImgCode {
pi, err := strconv.Atoi(string(param.ImgCode[i]))
if err != nil {
code = stdLog.VerifySmsCodeFailed
return
}
imgs[i] = byte(pi)
}
imgCodeByte := statg.VerifyIMGStore.Get(param.ImgID, true)
if bytes.Equal(imgCodeByte, imgs) {
channelInfo.ShowMagic += 1
doc := bson.M{"$set": bson.M{channelmod.FN.ChannelInfo.ShowMagic: channelInfo.ShowMagic}}
if err = channelmod.UpdateOneChannelInfo(ctx, bson.M{}, doc); err != nil {
code = stdLog.Failure
return code, channelInfo
}
code = stdLog.VerifySmsCodeFailed
return
} else if bytes.Equal(imgCodeByte, common.ArrayReverseByte(imgs)) {
channelInfo.ShowMagic = 1
} else {
code = stdLog.VerifySmsCodeFailed
return
}
// 生成token
claims := define.Claims{
Type: define.Channel,
Name: *param.Account,
ShowMagic: strconv.Itoa(int(channelInfo.ShowMagic)),
}
//fmt.Println("claims", claims)
if channelInfo.ShowMagic >= 3 {
statg.Bot.SendMonitorMessage("渠道" + channelInfo.Channel + "风险登陆多次已禁止登陆.")
}
newToken, err := define.GenToken(&claims)
if err != nil {
log.Error("GenerateUserToken failed", log.E(err))
code = stdLog.Failure
return code, channelInfo
}
channelInfo.Token = newToken
// 刷新token
doc := bson.M{"$set": bson.M{
channelmod.FN.ChannelInfo.Token: newToken,
channelmod.FN.ChannelInfo.ShowMagic: channelInfo.ShowMagic,
channelmod.FN.ChannelInfo.IP: ip,
}}
if err = channelmod.UpdateOneChannelInfo(ctx, bson.M{channelmod.FN.ChannelInfo.Channel: channelInfo.Channel}, doc); err != nil {
code = stdLog.Failure
return code, channelInfo
}
code = stdLog.Success
return
}
func AgentChannelAppInfo(ctx context.Context, req proto.ChannelAppInfoReq) (*proto.ChannelAppInfoResp, error) {
checkFilter := bson.M{
channelmod.FN.ChannelInfo.Channel: req.Channel,
}
channelInfo, err := channelmod.FindOneChannelInfo(ctx, checkFilter)
if err != nil {
return nil, err
}
if channelInfo.Channel != req.Channel {
return nil, fmt.Errorf("channel not found. %s", req.Channel)
}
filter := bson.M{}
if channelInfo.Type == channelmod.ChannelTypeParent {
filter[channelmod.FN.ChannelInfo.Channel] = channelInfo.Channel
} else {
filter[channelmod.FN.ChannelInfo.Channel] = channelInfo.ParentChannel
}
ruleList, err := channelmod.FindManyChannelDeductionRule(ctx, filter)
if err != nil {
log.Error("AgentChannelAppInfo FindManyChannelDeductionRule", log.Any("channel", req.Channel), log.E(err))
return nil, err
}
resp := &proto.ChannelAppInfoResp{
List: make([]proto.ChannelAppInfo, 0),
}
for _, ruleInfo := range ruleList {
temp := proto.ChannelAppInfo{
AppId: ruleInfo.AppId,
AppName: ruleInfo.AppName,
LandingPage: ruleInfo.LandingPage + fmt.Sprintf("?dc=%s", req.Channel),
}
resp.List = append(resp.List, temp)
}
return resp, nil
}
// AgentChannelListInfo 查询渠道列表
func AgentChannelListInfo(ctx context.Context, channel string, req *proto.AgentChannelInfoSearch) (
*proto.AgentChannelInfoSearchResp, error) {
filter := bson.M{"$or": bson.A{
bson.M{channelmod.FN.ChannelInfo.Channel: channel},
bson.M{channelmod.FN.ChannelInfo.ParentAgent: channel},
}}
opt := &options.FindOptions{}
opt.SetSort(bson.M{channelmod.FN.ChannelInfo.CreatedAt: -1})
opt.SetSkip((req.PageNum - 1) * req.PageSize)
opt.SetLimit(req.PageSize)
resp := &proto.AgentChannelInfoSearchResp{}
if req.AppId != nil {
filter[channelmod.FN.ChannelInfo.AppIds] = bson.M{"$in": []int32{*req.AppId}}
}
if req.Type != nil {
filter[channelmod.FN.ChannelInfo.Type] = *req.Type
}
total, serr := channelmod.CountChannelInfos(ctx, filter)
if serr != nil {
return nil, serr
}
if total == 0 {
return resp, nil
}
channelInfoList, err := channelmod.FindManyChannelInfo(ctx, filter, opt)
if err != nil {
return nil, err
}
resp.List = make([]proto.ChannelAgentInfo, 0)
for _, channchannelInfo := range channelInfoList {
tmp := proto.ChannelAgentInfo{
Channel: channchannelInfo.Channel,
AppIds: channchannelInfo.AppIds,
UserName: channchannelInfo.UserName,
ParentAgent: channchannelInfo.ParentAgent,
ChildCount: channchannelInfo.ChildCount,
Status: channchannelInfo.Status,
Mobile: channchannelInfo.Mobile,
Type: channchannelInfo.Type,
Remark: channchannelInfo.Remark,
Lock: channchannelInfo.Lock,
Wallet: channchannelInfo.Wallet,
CreatedAt: channchannelInfo.CreatedAt,
UpdatedAt: channchannelInfo.UpdatedAt,
}
ruleFilter := bson.M{
channelmod.FN.ChannelDeductionRule.Channel: channchannelInfo.Channel,
}
ruleInfoList, _ := channelmod.FindManyChannelDeductionRule(ctx, ruleFilter)
for _, ruleInfo := range ruleInfoList {
tmp.Settlement = ruleInfo.Settlement
tmp.CPAPrice = ruleInfo.CPAPrice
tmp.CPSRate = ruleInfo.CPSRate
tmp.DivideRate = ruleInfo.DivideRate
tmp.AppType = ruleInfo.AppType
tmp.PromoteMode = ruleInfo.PromoteMode
tmp.AppIds = []int32{int32(ruleInfo.AppId)}
if tmp.DivideRate == 0 {
tmp.DivideRate = 100
}
if tmp.Channel == channel {
tmp.DivideRate = 100
}
resp.List = append(resp.List, tmp)
}
}
resp.Total = total
return resp, nil
}
// AgentChannelRuleUpdate 更新渠道信息
func AgentChannelRuleUpdate(ctx context.Context, channel string, req *proto.AgentChannelRuleUpdate) error {
// 查找要修改规则的子代理
channelFilter := bson.M{
channelmod.FN.ChannelInfo.Channel: req.Channel,
channelmod.FN.ChannelInfo.ParentAgent: channel,
}
channelInfo, err := channelmod.FindOneChannelInfo(ctx, channelFilter)
if err != nil {
return err
}
if channelInfo.Channel != req.Channel {
return fmt.Errorf("channel not found %s", req.Channel)
}
if req.CPSRate == nil && req.CPAPrice == nil && req.DivideRate == nil && req.ChildShowDivideRate == nil {
return nil
}
if req.CPSRate != nil && !InRangeInt64(*req.CPSRate, 0, 100) {
return fmt.Errorf("CPS比例 (%d) 必须在 0-100 之间", *req.CPSRate)
}
if req.DivideRate != nil && !InRangeInt64(*req.DivideRate, 0, 100) {
return fmt.Errorf("分成比例 (%d) 必须在 0-100 之间", *req.CPSRate)
}
// 规则检查
parentRules, err := channelmod.FindManyChannelDeductionRule(ctx, bson.M{channelmod.FN.ChannelDeductionRule.Channel: channelInfo.ParentAgent})
if err != nil {
return err
}
if req.CPAPrice != nil {
parentCPA := int64(0)
for _, rule := range parentRules {
if rule.CPAPrice > 0 {
if parentCPA <= 0 {
parentCPA = rule.CPAPrice
} else {
if rule.CPAPrice < parentCPA {
parentCPA = rule.CPAPrice
}
}
}
}
if *req.CPAPrice > parentCPA || *req.CPAPrice < 0 {
return fmt.Errorf("下级CPA价格 (%d) 必须在范围 0-%d 之间,不能大于你自己的CPA价格", *req.CPAPrice/100, parentCPA/100)
}
}
if req.CPSRate != nil {
parentCPSRate := int64(0)
for _, rule := range parentRules {
if rule.CPSRate > 0 {
if parentCPSRate <= 0 {
parentCPSRate = rule.CPSRate
} else {
if rule.CPSRate < parentCPSRate {
parentCPSRate = rule.CPSRate
}
}
}
}
if *req.CPSRate > parentCPSRate || *req.CPSRate < 0 {
return fmt.Errorf("下级CPS比例 (%d) 必须在范围 0-%d 之间, 不能大于你自己的CPS比例", *req.CPSRate, parentCPSRate)
}
}
// 更新规则
setdoc := bson.M{}
if req.CPAPrice != nil {
setdoc[channelmod.FN.ChannelDeductionRule.CPAPrice] = *req.CPAPrice
}
if req.CPSRate != nil {
setdoc[channelmod.FN.ChannelDeductionRule.CPSRate] = *req.CPSRate
}
if req.DivideRate != nil {
setdoc[channelmod.FN.ChannelDeductionRule.DivideRate] = *req.DivideRate
}
if err := channelmod.UpdateManyChannelDeductionRule(ctx,
bson.M{channelmod.FN.ChannelDeductionRule.Channel: req.Channel},
bson.M{"$set": setdoc},
); err != nil {
return err
}
// 更新子代理是否显示分成比例
if req.ChildShowDivideRate != nil || req.Remark != nil {
doc := bson.M{}
if req.ChildShowDivideRate != nil {
doc[channelmod.FN.ChannelInfo.ChildShowDivideRate] = *req.ChildShowDivideRate
}
if req.Remark != nil {
doc[channelmod.FN.ChannelInfo.Remark] = *req.Remark
}
if err = channelmod.UpdateOneChannelInfo(ctx, channelFilter, bson.M{"$set": doc}); err != nil {
return err
}
}
return nil
}
// AgentChannelInfoUpdate 更新渠道信息
func AgentChannelInfoUpdate(ctx context.Context, channel string, req *proto.AgentChannelInfoUpdate) error {
checkFilter := bson.M{
channelmod.FN.ChannelInfo.Channel: req.Channel,
}
channelInfo, err := channelmod.FindOneChannelInfo(ctx, checkFilter)
if err != nil {
return err
}
if channelInfo.Channel != req.Channel {
return fmt.Errorf("channel not found. %s", req.Channel)
}
if req.WalletAddr1 != nil || req.WalletAddr2 != nil {
if channelInfo.Channel == channel {
return fmt.Errorf("您不可以修改自己的钱包地址,请联系您的上级代理协助修改")
}
}
if !(channelInfo.Channel == channel || channelInfo.ParentAgent == channel) {
return fmt.Errorf("channel not found! %s", req.Channel)
}
setdoc := bson.M{
channelmod.FN.ChannelInfo.UpdatedAt: time.Now(),
}
if req.Status != nil {
setdoc[channelmod.FN.ChannelInfo.Status] = *req.Status
}
if req.Mobile != nil {
setdoc[channelmod.FN.ChannelInfo.Mobile] = *req.Mobile
}
if req.WalletAddr1 != nil {
setdoc[channelmod.FN.ChannelInfo.Wallet.Addr1] = *req.WalletAddr1
}
if req.WalletAddr2 != nil {
setdoc[channelmod.FN.ChannelInfo.Wallet.Addr2] = *req.WalletAddr2
}
return channelmod.UpdateOneChannelInfo(ctx,
bson.M{channelmod.FN.ChannelInfo.Channel: req.Channel},
bson.M{"$set": setdoc},
)
}
// AgentChannelPasswordReset 子渠道密码重置
func AgentChannelPasswordReset(ctx context.Context, channel string, req *proto.AgentChannelPasswordReset) error {
checkFilter := bson.M{
channelmod.FN.ChannelInfo.Channel: req.Channel,
}
channelInfo, err := channelmod.FindOneChannelInfo(ctx, checkFilter)
if err != nil {
return err
}
if channelInfo.Channel != req.Channel {
return fmt.Errorf("代理没找到. %s", req.Channel)
}
if channelInfo.ParentAgent != channel {
return errors.New("只能重置直接下级代理的密码! ")
}
if req.Password == "" {
return errors.New("新密码不能为空! ")
}
setdoc := bson.M{
channelmod.FN.ChannelInfo.UpdatedAt: time.Now(),
channelmod.FN.ChannelInfo.Password: req.Password,
}
return channelmod.UpdateOneChannelInfo(ctx,
bson.M{channelmod.FN.ChannelInfo.Channel: req.Channel},
bson.M{"$set": setdoc},
)
}
// AgentChannelPasswordUpdate 更新渠道密码
func AgentChannelPasswordUpdate(ctx context.Context, channel string, req *proto.AgentChannelPasswordUpdate) error {
checkFilter := bson.M{
channelmod.FN.ChannelInfo.Channel: channel,
}
channelInfo, err := channelmod.FindOneChannelInfo(ctx, checkFilter)
if err != nil {
return err
}
if channelInfo.Channel != channel {
return fmt.Errorf("channel not found. %s", channel)
}
if channelInfo.Password != req.OldPassword {
return errors.New("旧密码输入错误")
}
if req.NewPassword == "" {
return errors.New("新密码不能为空! ")
}
setdoc := bson.M{
channelmod.FN.ChannelInfo.UpdatedAt: time.Now(),
channelmod.FN.ChannelInfo.Password: req.NewPassword,
}
return channelmod.UpdateOneChannelInfo(ctx,
bson.M{channelmod.FN.ChannelInfo.Channel: channel},
bson.M{"$set": setdoc},
)
}
func GetChildNewChannelNameAndRootChannelName(ctx context.Context, channelParent *channelmod.ChannelInfo) (string, string, error) {
maxid := 0
filter := bson.M{}
if channelParent.Type == channelmod.ChannelTypeParent {
filter[channelmod.FN.ChannelInfo.ParentChannel] = channelParent.Channel
} else {
filter[channelmod.FN.ChannelInfo.ParentAgent] = channelParent.Channel
}
channlList, err := channelmod.FindManyChannelInfo(ctx, filter)
if err != nil {
log.Error("AddAgentChannelInfo CountChannelInfos", log.Any("channel", channelParent.Channel), log.E(err))
return "", "", err
}
for _, v := range channlList {
id, err := strconv.Atoi(string(strings.Replace(v.Channel, channelParent.Channel, "", -1)))
if err == nil && id > maxid {
maxid = id
}
}
maxid = maxid + 1
childChannelName := channelParent.Channel + fmt.Sprintf("%.2d", maxid)
rootChannelName := ""
if channelParent.Type == channelmod.ChannelTypeParent { // 一级代理
rootChannelName = channelParent.Channel
} else {
if channelParent.ParentChannel == "" { // 如果子级代理的父代理为空
//rootChannelName = channelParent.Channel
return "", "", fmt.Errorf("子级代理【%s】的顶级代理空时不能添加子代理!", channelParent.Channel)
} else {
rootChannelName = channelParent.ParentChannel
}
}
return childChannelName, rootChannelName, nil
}
// AddAgentChannelInfo 渠道添加一个子渠道
func AddAgentChannelInfo(ctx context.Context, channel string, req *proto.AgentChannelInfoAdd) error {
channelInfo, _, err := GetChannelInfoWithLockStatus(ctx, channel)
if err != nil {
return err
}
if channelInfo.Lock || !channelInfo.Status {
return fmt.Errorf("您的代理或上级代理已被禁止")
}
childChannelName, rootChannelName, err := GetChildNewChannelNameAndRootChannelName(ctx, channelInfo)
if err != nil {
return err
}
childChannelType := channelInfo.Type.Child()
if childChannelType.IsNone() {
return errors.New("您的代理等级无法添加子渠道")
}
now := time.Now()
newChannelInfo := channelmod.ChannelInfo{
Channel: childChannelName,
UserName: channelInfo.UserName,
AppIds: channelInfo.AppIds,
ParentChannel: rootChannelName,
ParentAgent: channel,
Password: req.Password,
Status: true,
Mobile: req.Mobile,
Type: childChannelType,
Remark: req.Remark,
Wallet: channelmod.Wallet{
Addr1: req.WalletAddr1,
Addr2: req.WalletAddr2,
},
Lock: false,
ShowMagic: 0,
CreatedAt: now,
UpdatedAt: now,
}
err = channelmod.InsertOneChannelInfo(ctx, &newChannelInfo)
if err != nil {
log.Error("AddChannelInfo InsertOneChannelInfo", log.Any("channel", newChannelInfo.ParentChannel), log.E(err))
return err
}
// 增加子渠道数量
setdoc := bson.M{"$inc": bson.M{channelmod.FN.ChannelInfo.ChildCount: 1}}
err = channelmod.UpdateOneChannelInfo(ctx, bson.M{channelmod.FN.ChannelInfo.Channel: channel}, setdoc)
if err != nil {
log.Error("AddChannelInfo InsertOneChannelInfo", log.Any("channel", newChannelInfo.ParentChannel), log.E(err))
return err
}
for _, appid := range channelInfo.AppIds {
checkFilter := bson.M{channelmod.FN.ChannelDeductionRule.Channel: channel, channelmod.FN.ChannelDeductionRule.AppId: appid}
ChannelDeductionRule, err := channelmod.FindOneChannelDeductionRule(ctx, checkFilter)
if err != nil || ChannelDeductionRule.Channel != channel {
log.Error("AddAgentChannelInfo FindOneChannelDeductionRule", log.Any("channel", channel), log.Any("appid", appid), log.E(err))
continue
}
newChannelRule := &channelmod.ChannelDeductionRule{
Channel: newChannelInfo.Channel,
AppId: ChannelDeductionRule.AppId,
AppName: ChannelDeductionRule.AppName,
AppType: ChannelDeductionRule.AppType,
PromoteMode: ChannelDeductionRule.PromoteMode,
Settlement: ChannelDeductionRule.Settlement,
First: ChannelDeductionRule.First,
Second: ChannelDeductionRule.Second,
Third: ChannelDeductionRule.Third,
Fourth: ChannelDeductionRule.Fourth,
Fifth: ChannelDeductionRule.Fifth,
CPAPrice: ChannelDeductionRule.CPAPrice,
CPSRate: ChannelDeductionRule.CPSRate,
CPSBegin: ChannelDeductionRule.CPSBegin,
CPSDeductionRate: ChannelDeductionRule.CPSDeductionRate,
BeginTime: ChannelDeductionRule.BeginTime,
SettlementTime: now,
TodayTime: utils.BeginOfToday(),
TodayDeduction: 0,
CreatedAt: now,
UpdatedAt: now,
}
err = channelmod.InsertOneChannelDeductionRule(ctx, newChannelRule)
if err != nil {
log.Error("AddAgentChannelInfo InsertOneChannelDeductionRule", log.Any("channel", channel), log.Any("appid", appid), log.E(err))
continue
}
}
return nil
}
// GetAgentSummaryStatChild 查询渠道扣量规则信息
func GetAgentSummaryStatChild(ctx context.Context, channel string, req *proto.AgentSummaryStatChildSearch) (*proto.AgentSummaryStatChildResp, error) {
resp := &proto.AgentSummaryStatChildResp{
List: make(proto.AgentSummaryStatChildList, 0),
}
summary, err := GetAgentSummary(ctx, channel, nil, nil, nil, nil)
if err != nil {
return nil, err
}
total, list := summary.GetAgentSummaryStats(OutputStatListTypeChild, req.PageNum, req.PageSize)
resp.Total = total
for _, item := range list {
resp.List = append(resp.List, item.ToAgentSummaryStatChild())
}
return resp, nil
}
// GetAgentSummaryStatHome 查询渠道扣量规则信息
func GetAgentSummaryStatHome(ctx context.Context, channel string, req *proto.AgentSummaryStatHomeSearch) (*proto.AgentSummaryStatHomeResp, error) {
resp := &proto.AgentSummaryStatHomeResp{
List: make(proto.AgentSummaryStatHomeList, 0),
}
summary, err := GetAgentSummary(ctx, channel, nil, nil, nil, nil)
if err != nil {
return nil, err
}
total, list := summary.GetAgentSummaryStats(OutputStatListTypeSelf, req.PageNum, req.PageSize)
resp.Total = total
resp.List = list
return resp, nil
}
// GetAgentChannelStat 查询渠道扣量规则信息
func GetAgentChannelStat(ctx context.Context, channel string, req *proto.AgentChannelStatSearch) (*proto.AgentChannelStatResp, error) {
resp := &proto.AgentChannelStatResp{
List: make([]*proto.AgentDeductedStat, 0),
}
summary, err := GetAgentSummary(ctx, channel, req.Channel, req.AppId, req.Start, req.End)
if err != nil {
return nil, err
}
total, list := summary.GetAgentStats(OutputStatListTypeAll, req.PageNum, req.PageSize)
resp.Total = total
resp.List = list
resp.TotalInfo = *summary.StatTotal
return resp, nil
}
+50
View File
@@ -0,0 +1,50 @@
package agentSer
import (
"stat/api/proto"
"stat/models/appInfoMod"
"stat/models/channelmod"
)
const (
CPA_SHOW_MONEY = false
CPS_SHOW_COUNT = true
)
type ChannelMap map[string]*channelmod.ChannelInfo
type AppMap map[int32]*appInfoMod.AppInfo
type SourceAgentStatMap map[channelmod.ChannelType]map[string]ChannelAgentStatList
type OutputStatListType int32
type AgentDeductedStatList []*proto.AgentDeductedStat
func (a AgentDeductedStatList) Len() int { return len(a) }
func (a AgentDeductedStatList) Less(i, j int) bool { return a[i].SumDate.After(a[j].SumDate) }
func (a AgentDeductedStatList) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
const (
OutputStatListTypeAll OutputStatListType = 0 // 输出当前渠道和直接下级渠道的stat列表
OutputStatListTypeSelf OutputStatListType = 1 // 输出当前渠道自身的stat列表
OutputStatListTypeChild OutputStatListType = 2 // 输出直接下级渠道的stat列表
)
type AgentChannelSummaryMode int32
const (
AgentChannelSummaryModeFollowRule AgentChannelSummaryMode = 0 // 按渠道规则,忽略记录中的规则和价格
AgentChannelSummaryModeFollowRecord AgentChannelSummaryMode = 1 // 按记录中的规则和价格,覆盖渠道规则
)
var (
GlobalAgentChannelSummaryMode = AgentChannelSummaryModeFollowRule
)
type AgentChannelStat struct {
channelmod.ChannelStat
ChildValidDirectCount int64
ChildValidPayMoney int64
ChildChannelStats []*AgentChannelStat
DeductedStat *proto.AgentDeductedStat
DivideRate int64
}
type ChannelAgentStatList []*AgentChannelStat
+199
View File
@@ -0,0 +1,199 @@
package agentSer
import (
"context"
"stat/api/proto"
"stat/models/appInfoMod"
"stat/models/channelmod"
)
func EnsureRangeInt64(value, min, max int64) int64 {
if value < min {
value = min
}
if value > max {
value = max
}
return value
}
func InRangeInt64(value, min, max int64) bool {
if value < min {
return false
}
if value > max {
return false
}
return true
}
func MinInt64(x, y int64) int64 {
if x < y {
return x
}
return y
}
func MaxInt64(x, y int64) int64 {
if x > y {
return x
}
return y
}
func GetPageRange(totalLen, pageNum, pageSize int) (start, end int) {
if pageNum < 1 {
pageNum = 1
}
if pageSize < 1 {
pageSize = 10
}
start = (pageNum - 1) * pageSize
if start > totalLen {
start = totalLen
}
end = start + pageSize
if end > totalLen {
end = totalLen
}
return
}
func (c ChannelMap) GetChannelNames() []string {
lst := make([]string, 0)
for _, v := range c {
lst = append(lst, v.Channel)
}
return lst
}
func (c ChannelMap) GetChildChannels(channel string) []*channelmod.ChannelInfo {
lst := make([]*channelmod.ChannelInfo, 0)
for _, v := range c {
if v.ParentAgent == channel {
lst = append(lst, v)
}
}
return lst
}
func (c ChannelMap) GetParentChannels(channel string) []*channelmod.ChannelInfo {
lst := make([]*channelmod.ChannelInfo, 0)
self := c[channel]
if self == nil {
return lst
}
parent := c[self.ParentAgent]
for parent != nil {
lst = append(lst, parent)
parent = c[parent.ParentAgent]
}
return lst
}
func (c ChannelMap) GetLockStatusAndShowDivide(channelInfo *channelmod.ChannelInfo) (bool, bool, bool) {
parents := c.GetParentChannels(channelInfo.Channel)
parentShowDivide := true
status := channelInfo.Status
lock := channelInfo.Lock
for _, parent := range parents {
parentShowDivide = parentShowDivide && parent.ChildShowDivideRate
status = status && parent.Status
if parent.Lock {
lock = true
}
}
return lock, status, parentShowDivide && channelInfo.ChildShowDivideRate
}
func (c ChannelMap) GetAncestorAndOffspringChannels(channel string) []*channelmod.ChannelInfo {
out := make([]*channelmod.ChannelInfo, 0)
cInfo := c[channel]
if cInfo == nil {
return out
}
children := c.GetChildChannels(cInfo.Channel)
for len(children) > 0 {
tmp := make([]*channelmod.ChannelInfo, 0)
for _, child := range children {
out = append(out, child)
tmp = append(tmp, c.GetChildChannels(child.Channel)...)
}
children = tmp
}
for cInfo != nil {
out = append(out, cInfo)
if cInfo.ParentAgent != "" {
cInfo = c[cInfo.ParentAgent]
} else {
cInfo = nil
}
}
return out
}
func GetAppMap(ctx context.Context) (AppMap, error) {
appList, err := appInfoMod.GetAllAppInfo(ctx)
if err != nil {
return nil, err
}
appMap := make(AppMap)
for _, app := range appList {
tmp := app
appMap[app.AppId] = &tmp
}
return appMap, nil
}
func (m AppMap) GetAppName(id int32) string {
if app, ok := m[id]; ok {
return app.Name
}
return ""
}
func AccumulateAgentChannelStat(base, stat *AgentChannelStat, ruleData bool) {
channelmod.ChannelStatSummation(&base.ChannelStat, &stat.ChannelStat)
base.Settlement = stat.Settlement
base.ChildValidPayMoney += stat.ChildValidPayMoney
base.ChildValidDirectCount += stat.ChildValidDirectCount
AccumulateDeductedStat(base.DeductedStat, stat.DeductedStat, ruleData)
for _, child := range stat.ChildChannelStats {
base.DeductedStat.ChildRebate += child.DeductedStat.Income
}
}
func AccumulateDeductedStat(base, stat *proto.AgentDeductedStat, ruleData bool) {
base.VisitCount += stat.VisitCount
if !ruleData || (ruleData && stat.Settlement == channelmod.SettlementCPA) || (stat.Settlement == channelmod.SettlementCPS && CPS_SHOW_COUNT) {
base.DirectCount += stat.DirectCount
base.DirectCountChild += stat.DirectCountChild
base.DirectCountSelf += stat.DirectCountSelf
}
if !ruleData || (ruleData && stat.Settlement == channelmod.SettlementCPS) || (stat.Settlement == channelmod.SettlementCPA && CPA_SHOW_MONEY) {
base.Recharge += stat.Recharge
base.RechargeChild += stat.RechargeChild
base.RechargeSelf += stat.RechargeSelf
}
base.Income += stat.Income
base.IncomeChild += stat.IncomeChild
base.IncomeSelf += stat.IncomeSelf
}
func AccumulateStatData(base *proto.StatDataDetail, stat *proto.AgentDeductedStat, ruleData bool) {
base.ChildRebate += stat.ChildRebate
base.VisitCount += stat.VisitCount
if !ruleData || (ruleData && stat.Settlement == channelmod.SettlementCPA) || (stat.Settlement == channelmod.SettlementCPS && CPS_SHOW_COUNT) {
base.DirectCount += stat.DirectCount
base.DirectCountSelf += stat.DirectCountSelf
base.DirectCountChild += stat.DirectCountChild
}
if !ruleData || (ruleData && stat.Settlement == channelmod.SettlementCPS) || (stat.Settlement == channelmod.SettlementCPA && CPA_SHOW_MONEY) {
base.Recharge += stat.Recharge
base.RechargeSelf += stat.RechargeSelf
base.RechargeChild += stat.RechargeChild
}
base.Income += stat.Income
base.IncomeSelf += stat.IncomeSelf
base.IncomeChild += stat.IncomeChild
}
+248
View File
@@ -0,0 +1,248 @@
package agentSer
import (
"context"
"errors"
"fmt"
"log"
"stat/api/proto"
"stat/models/channelmod"
"time"
"go.mongodb.org/mongo-driver/bson"
)
type AgentNode struct {
Ctx context.Context
ChannelInfo *channelmod.ChannelInfo
Stats ChannelAgentStatList
Parent *AgentNode
Children []*AgentNode
Accumulate bool //是否已累计子层
AddedChild bool //是否已添加子层
Deducted bool //是否已计算扣量
}
func NewAgentNode(ctx context.Context, channel *channelmod.ChannelInfo, parent *AgentNode, stats ...*AgentChannelStat) *AgentNode {
ret := &AgentNode{
Ctx: ctx,
ChannelInfo: channel,
Stats: make([]*AgentChannelStat, 0),
Parent: parent,
Children: make([]*AgentNode, 0),
Accumulate: false,
}
ret.Stats = append(ret.Stats, stats...)
return ret
}
func GetRootAgentNode(ctx context.Context, selfChannel *channelmod.ChannelInfo, reqChannel *string, statDict SourceAgentStatMap) (*AgentNode, error) {
var rootAgent *AgentNode
if reqChannel != nil && *reqChannel != selfChannel.Channel {
cInfo, err := channelmod.FindOneChannelInfoEx(ctx, bson.M{channelmod.FN.ChannelInfo.Channel: *reqChannel})
if err != nil {
return nil, err
}
if cInfo.ParentAgent != selfChannel.Channel {
return nil, fmt.Errorf("查找渠道失败,只能查看自己或自己的直接下级!")
}
stats := statDict[cInfo.Type][cInfo.Channel]
rootAgent = NewAgentNode(ctx, cInfo, nil, stats...)
} else {
stats := statDict[selfChannel.Type][selfChannel.Channel]
rootAgent = NewAgentNode(ctx, selfChannel, nil, stats...)
}
return rootAgent, nil
}
func nodeFind(node *AgentNode, find func(node *AgentNode) bool) *AgentNode {
var stack = []*AgentNode{node}
for len(stack) > 0 {
node = stack[len(stack)-1]
stack = stack[:len(stack)-1]
if node != nil {
found := find(node)
if found {
return node
}
stack = append(stack, node.Children...)
}
}
return nil
}
func traverse(node *AgentNode, accumulate func(node *AgentNode)) {
// first child
for _, child := range node.Children {
traverse(child, accumulate)
}
// last self
accumulate(node)
}
func (s *AgentNode) AddChildNodes(srcDict SourceAgentStatMap, channelMap ChannelMap) error {
findNotAddChildNodeFn := func(node *AgentNode) bool {
if !node.AddedChild {
return true
}
return false
}
for found := nodeFind(s, findNotAddChildNodeFn); found != nil; found = nodeFind(s, findNotAddChildNodeFn) {
childChannels := channelMap.GetChildChannels(found.ChannelInfo.Channel)
for _, channel := range childChannels {
stats := srcDict[channel.Type][channel.Channel]
agent := NewAgentNode(s.Ctx, channel, found, stats...)
found.Children = append(found.Children, agent)
}
found.AddedChild = true
}
return nil
}
func (s *AgentNode) AccumulateChildNodes(ruleMap RuleMap) error {
accumulate := func(found *AgentNode) {
if found.Accumulate {
panic(errors.New("AccumulateChildNodes found node can not be accumulate"))
}
for _, childAgent := range found.Children {
if !childAgent.Accumulate {
panic(errors.New("AccumulateChildNodes child node can not be not accumulated"))
}
for _, childStat := range childAgent.Stats {
hit := false
for _, parentStat := range found.Stats {
if parentStat.SumDate.Equal(childStat.SumDate) {
hit = true
AccumulateChildAgentChannelStatToParent(parentStat, childStat)
}
}
if !hit {
lostStat := &AgentChannelStat{
ChannelStat: channelmod.ChannelStat{
Channel: childAgent.ChannelInfo.ParentAgent,
SumDate: childStat.SumDate,
AppId: childStat.AppId,
Type: childStat.Type - 1,
Settlement: childStat.Settlement,
UserName: childStat.UserName,
ParentChannel: childStat.ParentChannel,
},
}
if rule := ruleMap.GetRule(childAgent.ChannelInfo.ParentAgent, childStat.AppId); rule != nil {
lostStat.CPSRate = rule.CPSRate
lostStat.CPAPrice = rule.CPAPrice
lostStat.Settlement = rule.Settlement
lostStat.DivideRate = rule.DivideRate
lostStat.AppType = rule.AppType
lostStat.PromoteMode = rule.PromoteMode
}
AccumulateChildAgentChannelStatToParent(lostStat, childStat)
found.Stats = append(found.Stats, lostStat)
log.Println(lostStat)
}
}
}
found.Accumulate = true
}
traverse(s, accumulate)
return nil
}
func (s *AgentNode) CalculateDeduction(channelMap ChannelMap, ruleMap RuleMap) error {
deduction := func(found *AgentNode) {
if found.Deducted {
panic(errors.New("CalculateDeduction found node, can not be deducted"))
}
for _, stat := range found.Stats {
pRule := GetMergedRule(channelMap, ruleMap, found.ChannelInfo.ParentAgent, stat.AppId)
sRule := ruleMap.GetRule(stat.Channel, stat.AppId)
stat.DeductedStat = DeductionChannelStat(stat, pRule, sRule)
}
found.Deducted = true
}
traverse(s, deduction)
return nil
}
func DeductionChannelStat(stat *AgentChannelStat, parentRule, selfRule *AgentChannelRule) *proto.AgentDeductedStat {
proxyDeductionTime, _ := time.ParseInLocation("2006-01-02", "2021-10-28", time.Local)
base := &proto.AgentDeductedStat{
Channel: stat.Channel,
AppId: stat.AppId,
SumDate: stat.SumDate,
DirectCount: stat.ValidDirectCount,
DirectCountChild: stat.ChildValidDirectCount,
DirectCountSelf: stat.ValidDirectCount - stat.ChildValidDirectCount,
Recharge: stat.ValidPayMoney,
RechargeChild: stat.ChildValidPayMoney,
RechargeSelf: stat.ValidPayMoney - stat.ChildValidPayMoney,
Settlement: stat.Settlement,
CPSRate: stat.CPSRate,
CPAPrice: stat.CPAPrice,
DivideRate: stat.DivideRate,
UpdatedAt: stat.UpdatedAt,
}
if selfRule == nil {
if base.DivideRate == 0 {
base.DivideRate = 100
}
return base
}
rule := MergedRule(parentRule, selfRule)
if GlobalAgentChannelSummaryMode == AgentChannelSummaryModeFollowRecord {
rule.CPSRate = stat.CPSRate
rule.CPAPrice = stat.CPAPrice
if base.Settlement == "" {
base.Settlement = rule.Settlement
}
} else {
base.CPSRate = rule.CPSRate
base.CPAPrice = rule.CPAPrice
base.Settlement = rule.Settlement
}
base.DivideRate = selfRule.DivideRate
if base.DivideRate == 0 {
base.DivideRate = 100
}
// 首先进行根代理扣量
if rule.ProxyDeduction > 0 && rule.ProxyDeduction < 100 && base.SumDate.After(proxyDeductionTime) {
base.DirectCount = base.DirectCount * rule.ProxyDeduction / 100
base.DirectCountChild = base.DirectCountChild * rule.ProxyDeduction / 100
base.Recharge = base.Recharge*rule.ProxyDeduction/100 - (base.Recharge*rule.ProxyDeduction/100)%1000
base.RechargeChild = base.RechargeChild*rule.ProxyDeduction/100 - (base.RechargeChild*rule.ProxyDeduction/100)%1000
}
// 按上级代理对该代理设置的分成比例进行扣量
if rule.DivideRateBP > 0 && rule.DivideRateBP < 10000 {
base.DirectCount = base.DirectCount * rule.DivideRateBP / 10000
base.DirectCountChild = base.DirectCountChild * rule.DivideRateBP / 10000
base.Recharge = base.Recharge * rule.DivideRateBP / 10000
base.RechargeChild = base.RechargeChild * rule.DivideRateBP / 10000
}
// 再计算收益
if rule.Settlement == channelmod.SettlementCPA {
base.Income = base.DirectCount * rule.CPAPrice
base.IncomeChild = base.DirectCountChild * rule.CPAPrice
} else if rule.Settlement == channelmod.SettlementCPS {
base.Income = (base.Recharge * rule.CPSRate) / 100
base.IncomeChild = (base.RechargeChild * rule.CPSRate) / 100
} else if rule.Settlement == channelmod.SettlementCPT {
base.Income = base.DirectCount * rule.CPAPrice
base.IncomeChild = base.DirectCountChild * rule.CPAPrice
}
// 统计子代理数据
base.IncomeSelf = base.Income - base.IncomeChild
base.DirectCountSelf = base.DirectCount - base.DirectCountChild
base.RechargeSelf = base.Recharge - base.RechargeChild
for _, child := range stat.ChildChannelStats {
base.ChildRebate += child.DeductedStat.Income
}
return base
}
func AccumulateChildAgentChannelStatToParent(parent, child *AgentChannelStat) {
channelmod.ChannelStatSummation(&parent.ChannelStat, &child.ChannelStat)
// 子渠道累计
parent.ChildValidPayMoney += child.VipPayMoney
parent.ChildValidDirectCount += child.NewDirectCount
parent.ChildChannelStats = append(parent.ChildChannelStats, child)
}
+51
View File
@@ -0,0 +1,51 @@
package agentSer
import (
"context"
"go.mongodb.org/mongo-driver/bson"
"stat/common/log"
"stat/common/utils"
"stat/models/channelmod"
)
func RepairData() {
wg := utils.NewWaitGroup()
wg.Add(func() {
ctx := context.Background()
if err := FixChannelSecondParentAgent(ctx); err != nil {
log.Error("fix data err", log.E(err))
}
})
go wg.RunWaits()
}
func FixChannelSecondParentAgent(ctx context.Context) error {
filter := bson.M{
channelmod.FN.ChannelInfo.Type: channelmod.ChannelTypeSecond,
"$or": bson.A{
bson.M{channelmod.FN.ChannelInfo.ParentAgent: bson.M{"$eq": nil}},
bson.M{channelmod.FN.ChannelInfo.ParentAgent: bson.M{"$exists": false}},
bson.M{channelmod.FN.ChannelInfo.ParentAgent: bson.M{"$eq": ""}},
},
channelmod.FN.ChannelInfo.ParentChannel: bson.M{"$gt": ""},
}
count, err := channelmod.CountChannelInfos(ctx, filter)
if err != nil {
return err
}
if count == 0 {
return nil
}
// 修复
channels, err := channelmod.FindManyChannelInfo(ctx, filter)
for _, channel := range channels {
filter := bson.M{channelmod.FN.ChannelInfo.Channel: channel.Channel}
update := bson.M{"$set": bson.M{
channelmod.FN.ChannelInfo.ParentAgent: channel.ParentChannel,
}}
if err = channelmod.UpdateOneChannelInfo(ctx, filter, update); err != nil {
return err
}
}
return nil
}
+129
View File
@@ -0,0 +1,129 @@
package agentSer
import (
"context"
"fmt"
"stat/models/channelmod"
"go.mongodb.org/mongo-driver/bson"
)
type RuleMap map[string]*AgentChannelRule
type AgentChannelRule struct {
channelmod.ChannelDeductionRule
DivideRateBP int64 // 分成比列(万分比)
}
func (c RuleMap) GetRule(channel string, appId int32) *AgentChannelRule {
key := fmt.Sprintf("%s %d", channel, appId)
return c[key]
}
func (c RuleMap) SetRule(rule *AgentChannelRule) {
key := fmt.Sprintf("%s %d", rule.Channel, rule.AppId)
c[key] = rule
}
func GetMergedRule(channelMap ChannelMap, ruleMap RuleMap, channel string, appId int32) *AgentChannelRule {
rules := make([]*AgentChannelRule, 0)
cInfo := channelMap[channel]
for cInfo != nil {
rule := ruleMap.GetRule(cInfo.Channel, appId)
if rule != nil {
rules = append(rules, rule)
}
if cInfo.ParentAgent != "" {
cInfo = channelMap[cInfo.ParentAgent]
} else {
cInfo = nil
}
}
if len(rules) == 0 {
return nil
}
for i, j := 0, len(rules)-1; i < j; i, j = i+1, j-1 {
rules[i], rules[j] = rules[j], rules[i]
}
base := *rules[0] // 沿用父渠道代理扣量规则
rules = rules[1:]
for _, rule := range rules {
base.AppType = rule.AppType
base.PromoteMode = rule.PromoteMode
base.CPSRate = rule.CPSRate
base.CPAPrice = MinInt64(base.CPAPrice, rule.CPAPrice) // 取最小的CPA价格
base.DivideRate = base.DivideRate * rule.DivideRate / 100
base.DivideRateBP = (base.DivideRateBP * rule.DivideRateBP) / 10000
}
return &base
}
func MergedRule(parent, self *AgentChannelRule) *AgentChannelRule {
if parent != nil && self != nil {
rule := *self
//rule.CPSRate //CPSRate not change
rule.AppType = parent.AppType
rule.PromoteMode = parent.PromoteMode
rule.CPAPrice = MinInt64(rule.CPAPrice, parent.CPAPrice) // 取最小的CPA价格
rule.DivideRate = rule.DivideRate * parent.DivideRate / 100
rule.DivideRateBP = (rule.DivideRateBP * parent.DivideRateBP) / 10000
rule.ProxyDeduction = parent.ProxyDeduction // 传递父渠道代理扣量到子渠道
return &rule
} else if parent != nil && self == nil {
rule := *parent
return &rule
} else if parent == nil && self != nil {
rule := *self
return &rule
}
return nil
}
func MakeChannelAndRuleMap(ctx context.Context, channelInfo *channelmod.ChannelInfo) (ChannelMap, RuleMap, error) {
var err error
ruleMap := make(RuleMap)
channelMap := make(ChannelMap)
var channelTop string
if channelInfo.Type.IsTop() {
channelTop = channelInfo.Channel
} else {
channelTop = channelInfo.ParentChannel
}
filter := bson.M{
"$or": bson.A{
bson.M{channelmod.FN.ChannelInfo.Channel: channelTop},
bson.M{channelmod.FN.ChannelInfo.ParentChannel: channelTop},
},
}
channelList, err := channelmod.FindManyChannelInfo(ctx, filter)
if err != nil {
return nil, nil, err
}
for _, c := range channelList {
channelMap[c.Channel] = c
}
channels := make([]string, 0)
channelInfos := channelMap.GetAncestorAndOffspringChannels(channelInfo.Channel)
for _, info := range channelInfos {
channels = append(channels, info.Channel)
}
rules, err := channelmod.FindManyChannelDeductionRule(ctx, bson.M{channelmod.FN.ChannelDeductionRule.Channel: bson.M{"$in": channels}})
if err != nil {
return nil, nil, err
}
for _, item := range rules {
item.CPAPrice = EnsureRangeInt64(item.CPAPrice, 0, 10000)
item.CPSRate = EnsureRangeInt64(item.CPSRate, 0, 100)
item.DivideRate = EnsureRangeInt64(item.DivideRate, 0, 100)
if item.DivideRate == 0 {
item.DivideRate = 100
}
rule := &AgentChannelRule{
ChannelDeductionRule: *item,
DivideRateBP: EnsureRangeInt64(item.DivideRate, 0, 100) * 100,
}
ruleMap.SetRule(rule)
}
return channelMap, ruleMap, nil
}
+290
View File
@@ -0,0 +1,290 @@
package agentSer
import (
"context"
"sort"
"stat/api/proto"
"stat/models/channelmod"
"time"
"github.com/jinzhu/now"
"go.mongodb.org/mongo-driver/bson"
)
type AgentSummary struct {
StartTime *time.Time
EndTime *time.Time
AppId *int32
FilterChannel *string
ChannelInfo *channelmod.ChannelInfo
ChannelMap ChannelMap
RuleMap RuleMap
AppMap AppMap
StatMap SourceAgentStatMap
SelfAgent *AgentNode
SelfStats []*proto.AgentDeductedStat
ChildStats []*proto.AgentDeductedStat
StatTotal *proto.AgentDeductedStat
}
func GetAgentSummary(ctx context.Context, selfChannel string, childChannel *string, appId *int32, start, end *time.Time) (*AgentSummary, error) {
summary := AgentSummary{
AppId: appId,
StartTime: start,
EndTime: end,
FilterChannel: childChannel,
SelfStats: make([]*proto.AgentDeductedStat, 0),
ChildStats: make([]*proto.AgentDeductedStat, 0),
AppMap: make(AppMap),
}
// 获取app信息字典
appMap, err := GetAppMap(ctx)
if err != nil {
return nil, err
}
summary.AppMap = appMap
// 获取渠道信息和统计信息
channelInfo, statDict, err := GetAllAgentStatDict(ctx, selfChannel, appId, start, end)
if err != nil {
return nil, err
}
summary.ChannelInfo = channelInfo
summary.StatMap = statDict
// 获取顶层节点
rootAgent, err := GetRootAgentNode(ctx, channelInfo, childChannel, statDict)
if err != nil {
return nil, err
}
summary.SelfAgent = rootAgent
// 获取渠道列表扣量规则字典
channelMap, ruleMap, err := MakeChannelAndRuleMap(ctx, channelInfo)
if err != nil {
return nil, err
}
summary.ChannelMap = channelMap
summary.RuleMap = ruleMap
// 添加子渠道节点
if err := rootAgent.AddChildNodes(statDict, channelMap); err != nil {
return nil, err
}
// 累计子渠道到上级渠道
if err := rootAgent.AccumulateChildNodes(ruleMap); err != nil {
return nil, err
}
// 计算扣量
if err := rootAgent.CalculateDeduction(channelMap, ruleMap); err != nil {
return nil, err
}
for _, stat := range rootAgent.Stats {
summary.SelfStats = append(summary.SelfStats, stat.DeductedStat)
}
for _, agent := range rootAgent.Children {
for _, stat := range agent.Stats {
summary.ChildStats = append(summary.ChildStats, stat.DeductedStat)
}
}
// 统计总量
base := &AgentChannelStat{
DeductedStat: &proto.AgentDeductedStat{},
}
for _, item := range rootAgent.Stats {
AccumulateAgentChannelStat(base, item, true)
}
summary.StatTotal = base.DeductedStat
return &summary, nil
}
func (a *AgentSummary) GetAgentStats(outType OutputStatListType, pageNum int64, pageSize int64) (int64, []*proto.AgentDeductedStat) {
totalAgentStatList := make(AgentDeductedStatList, 0)
switch outType {
case OutputStatListTypeSelf:
totalAgentStatList = append(totalAgentStatList, a.SelfStats...)
case OutputStatListTypeChild:
totalAgentStatList = append(totalAgentStatList, a.ChildStats...)
default:
totalAgentStatList = append(totalAgentStatList, a.SelfStats...)
totalAgentStatList = append(totalAgentStatList, a.ChildStats...)
}
childChannel := a.FilterChannel
if childChannel != nil { // 如果存在渠道过滤
outAgentStatList := make([]*proto.AgentDeductedStat, 0)
for _, item := range totalAgentStatList {
if item.Channel == *childChannel {
outAgentStatList = append(outAgentStatList, item)
}
}
totalAgentStatList = outAgentStatList
}
// 处理排序
sort.Sort(totalAgentStatList)
// 处理分页输出
list := GetAgentChannelStatListPageData(totalAgentStatList, pageNum, pageSize)
// 添加app名字
for _, item := range list {
item.AppName = a.AppMap.GetAppName(item.AppId)
if item.Channel == a.ChannelInfo.Channel {
item.DivideRate = 100
}
if item.Settlement == channelmod.SettlementCPA && !CPA_SHOW_MONEY {
item.Recharge = 0
item.RechargeSelf = 0
item.RechargeChild = 0
}
if item.Settlement == channelmod.SettlementCPS && !CPS_SHOW_COUNT {
item.DirectCount = 0
item.DirectCountSelf = 0
item.DirectCountChild = 0
}
}
return int64(len(totalAgentStatList)), list
}
func (a *AgentSummary) GetAgentSummaryStats(outType OutputStatListType, pageNum int64, pageSize int64) (int64, proto.AgentSummaryStatHomeList) {
totalAgentStatList := make(AgentDeductedStatList, 0)
channelList := make([]*channelmod.ChannelInfo, 0)
switch outType {
case OutputStatListTypeSelf:
channelList = append(channelList, a.SelfAgent.ChannelInfo)
totalAgentStatList = append(totalAgentStatList, a.SelfStats...)
case OutputStatListTypeChild:
for _, node := range a.SelfAgent.Children {
channelList = append(channelList, node.ChannelInfo)
}
totalAgentStatList = append(totalAgentStatList, a.ChildStats...)
default:
channelList = append(channelList, a.SelfAgent.ChannelInfo)
for _, node := range a.SelfAgent.Children {
channelList = append(channelList, node.ChannelInfo)
}
totalAgentStatList = append(totalAgentStatList, a.SelfStats...)
totalAgentStatList = append(totalAgentStatList, a.ChildStats...)
}
// 按渠道进行汇总
summaryList := make(proto.AgentSummaryStatHomeList, 0)
for _, channel := range channelList {
summary := &proto.AgentSummaryStatHome{
Channel: channel.Channel,
ParentAgent: channel.ParentAgent,
ChildCount: channel.ChildCount,
TotalStat: &proto.StatDataDetail{},
TodayStat: &proto.StatDataDetail{},
WeekStat: &proto.StatDataDetail{},
MonthStat: &proto.StatDataDetail{},
YearStat: &proto.StatDataDetail{},
}
summaryList = append(summaryList, summary)
}
// 统计日周月年数据
nowTime := time.Now()
for _, summary := range summaryList {
for _, stat := range totalAgentStatList {
if summary.Channel == stat.Channel {
AccumulateSummaryStat(summary, stat, nowTime, true)
}
}
}
// 处理排序,收益多的靠前
sort.Sort(summaryList)
// 处理分页输出
list := GetAgentSummaryStatListPageData(summaryList, pageNum, pageSize)
// 添加app名字,CAP屏蔽充值,CPS屏蔽新增
for _, item := range list {
item.AppName = a.AppMap.GetAppName(item.AppId)
if item.Channel == a.ChannelInfo.Channel {
item.DivideRate = 100
}
}
return int64(len(summaryList)), list
}
func AccumulateSummaryStat(base *proto.AgentSummaryStatHome, stat *proto.AgentDeductedStat, today time.Time, ruleData bool) {
// 时间范围
now.WeekStartDay = time.Monday
Now := now.With(today.In(time.Local))
todayBegin := Now.BeginningOfDay()
todayEnd := Now.EndOfDay()
weekBegin := Now.BeginningOfWeek()
monthBegin := Now.BeginningOfMonth()
yearBegin := Now.BeginningOfYear()
// 复制信息
base.AppId = stat.AppId
base.AppName = stat.AppName
base.Settlement = stat.Settlement
base.CPAPrice = stat.CPAPrice
base.CPSRate = stat.CPSRate
base.DivideRate = stat.DivideRate
// 全部数据累加
AccumulateStatData(base.TotalStat, stat, ruleData)
if (stat.SumDate.Equal(todayBegin) || stat.SumDate.After(todayBegin)) && (stat.SumDate.Equal(todayEnd) || stat.SumDate.Before(todayEnd)) {
AccumulateStatData(base.TodayStat, stat, ruleData)
}
if (stat.SumDate.Equal(weekBegin) || stat.SumDate.After(weekBegin)) && (stat.SumDate.Equal(todayEnd) || stat.SumDate.Before(todayEnd)) {
AccumulateStatData(base.WeekStat, stat, ruleData)
}
if (stat.SumDate.Equal(monthBegin) || stat.SumDate.After(monthBegin)) && (stat.SumDate.Equal(todayEnd) || stat.SumDate.Before(todayEnd)) {
AccumulateStatData(base.MonthStat, stat, ruleData)
}
if (stat.SumDate.Equal(yearBegin) || stat.SumDate.After(yearBegin)) && (stat.SumDate.Equal(todayEnd) || stat.SumDate.Before(todayEnd)) {
AccumulateStatData(base.YearStat, stat, ruleData)
}
}
func GetAgentChannelStatListPageData(list []*proto.AgentDeductedStat, pageNum int64, pageSize int64) []*proto.AgentDeductedStat {
start, end := GetPageRange(len(list), int(pageNum), int(pageSize))
return list[start:end]
}
func GetAgentSummaryStatListPageData(list proto.AgentSummaryStatHomeList, pageNum int64, pageSize int64) proto.AgentSummaryStatHomeList {
start, end := GetPageRange(len(list), int(pageNum), int(pageSize))
return list[start:end]
}
func GetAllAgentStatDict(ctx context.Context, channel string, appId *int32, startTime, endTime *time.Time) (*channelmod.ChannelInfo, SourceAgentStatMap, error) {
// 获取渠道信息
checkFilter := bson.M{channelmod.FN.ChannelInfo.Channel: channel}
channelInfo, err := channelmod.FindOneChannelInfo(ctx, checkFilter)
if err != nil || channelInfo.Channel != channel {
return nil, nil, err
}
// 获取一级渠道的所有子孙数据
var channelTop string
if channelInfo.Type.IsTop() {
channelTop = channelInfo.Channel
} else {
channelTop = channelInfo.ParentChannel
}
srcfilter := bson.M{
channelmod.FN.ChannelStat.Type: bson.M{"$gte": channelInfo.Type},
"$or": bson.A{
bson.M{channelmod.FN.ChannelStat.Channel: channelTop},
bson.M{channelmod.FN.ChannelStat.ParentChannel: channelTop},
}}
if appId != nil {
srcfilter[channelmod.FN.ChannelStat.AppId] = *appId
}
if startTime != nil && endTime != nil {
srcfilter[channelmod.FN.ChannelStat.SumDate] = bson.M{"$gte": *startTime, "$lte": *endTime}
}
srcList, err := channelmod.FindManyChannelStat(ctx, srcfilter)
if err != nil {
return nil, nil, err
}
srcDict := make(SourceAgentStatMap)
types := channelmod.GetAllChannelTypes()
for _, t := range types {
srcDict[t] = make(map[string]ChannelAgentStatList)
}
for _, stat := range srcList {
stat.CorrectValidData()
stat.CalcIncomeRate()
aStat := &AgentChannelStat{
ChannelStat: *stat,
ChildValidDirectCount: 0,
ChildValidPayMoney: 0,
ChildChannelStats: make([]*AgentChannelStat, 0),
}
srcDict[stat.Type][stat.Channel] = append(srcDict[stat.Type][stat.Channel], aStat)
}
return channelInfo, srcDict, nil
}
+152
View File
@@ -0,0 +1,152 @@
package announInfoSer
import (
"context"
"stat/api/proto"
"stat/common/stdLog"
"stat/common/utils"
"stat/models/announInfoMod"
"stat/models/appInfoMod"
)
// 获取广告列表
func ListAnnoun(ctx context.Context, param proto.ListAnnounReq) (code stdLog.CodeType, data interface{}, err error) {
resp := proto.ListAnnounRes{
Total: 0,
List: make([]proto.AnnounInfo, 0),
}
skip, limit := utils.ToDBPage(param.PageNum, param.PageSize)
// 获取广告位置信息
total, aList, err := announInfoMod.FindAnnounceList(ctx, param.AppId, skip, limit)
if err != nil {
code = stdLog.Failure
return
}
resp.Total = total
// 获取app信息
appIds := make([]int32, 0)
for _, v := range aList {
appIds = append(appIds, v.AppId)
}
appList, err := appInfoMod.GetAppInfosByAppIds(ctx, appIds)
if err != nil {
code = stdLog.Failure
return
}
appMap := make(map[int32]appInfoMod.AppInfo)
for _, v := range appList {
appMap[v.AppId] = v
}
for _, v := range aList {
appName := ""
if appInfo, ok := appMap[v.AppId]; ok {
appName = appInfo.Name
}
tmp := proto.AnnounInfo{
ID: v.ID.Hex(),
AppId: v.AppId,
AppName: appName,
Title: v.Title,
Content: v.Content,
Cover: v.Cover,
Href: v.Href,
Type: v.Type,
Active: v.Active,
StartTime: v.StartTime,
EndTime: v.EndTime,
}
resp.List = append(resp.List, tmp)
}
code = stdLog.Success
data = resp
return
}
// 新增广告
func AddAnnounce(ctx context.Context, param proto.AddAnnounReq) (code stdLog.CodeType, data interface{}, err error) {
doc := announInfoMod.AnnounInfo{
AppId: *param.AppId,
Title: *param.Title,
Content: *param.Content,
Cover: *param.Cover,
Href: *param.Href,
Type: *param.Type,
Active: *param.Active,
StartTime: *param.StartTime,
EndTime: *param.EndTime,
}
if err = announInfoMod.AddAnnounce(ctx, &doc); err != nil {
code = stdLog.Failure
return
}
code = stdLog.Success
return
}
// 修改广告位置
func UpdateAnnounce(ctx context.Context, param proto.UpdateAnnounReq) (code stdLog.CodeType, data interface{}, err error) {
doc := announInfoMod.UpdateDoc{
AppId: param.AppId,
Title: param.Title,
Content: param.Content,
Cover: param.Cover,
Href: param.Href,
Type: param.Type,
Active: param.Active,
StartTime: param.StartTime,
EndTime: param.EndTime,
}
if err = announInfoMod.UpdateAnnounce(ctx, *param.Id, doc); err != nil {
code = stdLog.Failure
return
}
code = stdLog.Success
return
}
// 删除广告
func DelAnnounce(ctx context.Context, param proto.DelAnnounReq) (code stdLog.CodeType, data interface{}, err error) {
if err = announInfoMod.DelAnnounce(ctx, *param.Id); err != nil {
code = stdLog.Failure
return
}
code = stdLog.Success
return
}
// 外部获取广告
func GetAnnounce(ctx context.Context, param proto.AnnouncementReq) (code stdLog.CodeType, data interface{}, err error) {
resp := proto.AnnouncementRes{AnnounceList: make([]proto.AnnouncementInfo, 0)}
aList, err := announInfoMod.FindAnnounceByAppId(ctx, *param.AppId)
if err != nil {
code = stdLog.Failure
return
}
for _, v := range aList {
resp.AnnounceList = append(resp.AnnounceList, proto.AnnouncementInfo{
ID: v.ID.Hex(),
Title: v.Title,
Content: v.Content,
Cover: v.Cover,
Href: v.Href,
Type: v.Type,
})
}
data = resp
code = stdLog.Success
return
}
+108
View File
@@ -0,0 +1,108 @@
package appInfoSer
import (
"context"
"stat/api/proto"
"stat/common/stdLog"
"stat/common/utils"
"stat/models/appInfoMod"
"time"
)
// 获取appInfo列表
func GetAppInfoList(ctx context.Context, param proto.ListAppInfoReq) (code stdLog.CodeType, data interface{}, err error) {
resp := proto.ListAppInfoRes{
Total: 0,
List: make([]proto.AppInfoRes, 0),
}
skip, limit := utils.ToDBPage(param.PageNum, param.PageSize)
total, aList, err := appInfoMod.GetAppInfoList(ctx, param.AppName, param.Used, skip, limit)
if err != nil {
code = stdLog.Failure
return
}
for _, v := range aList {
tmp := proto.AppInfoRes{
AppId: v.AppId,
Name: v.Name,
LandPages: v.LandPages,
IconImg: v.IconImg,
CreatedAt: v.CreatedAt,
AdvertiseNum: v.AdvertiseNum,
AdvertiseNewNum: v.AdvertiseNewNum,
UpdatedAt: v.UpdatedAt,
}
resp.List = append(resp.List, tmp)
}
resp.Total = total
data = resp
code = stdLog.Success
return
}
// 添加appInfo
func AddAppInfo(ctx context.Context, param proto.AddAppInfoReq) (code stdLog.CodeType, data interface{}, err error) {
_, serr := appInfoMod.GetAppInfoById(ctx, *param.AppId)
if serr != nil {
return stdLog.AppExistErr, nil, serr
}
now := time.Now()
doc := appInfoMod.AppInfo{
AppId: *param.AppId,
Name: *param.Name,
IconImg: *param.IconImg,
// LandPages: *param.LandPages,
Used: true,
AdvertiseNum: 5,
AdvertiseNewNum: 5,
CreatedAt: now,
UpdatedAt: now,
}
if err = appInfoMod.AddAppInfo(ctx, &doc); err != nil {
code = stdLog.Failure
return
}
code = stdLog.Success
return
}
// 更新appInfo
func UpdateAppInfo(ctx context.Context, param proto.UpdateAppInfoReq) (code stdLog.CodeType, data interface{}, err error) {
now := time.Now()
doc := appInfoMod.AppInfoUpdate{
Name: param.Name,
LandPages: param.LandPages,
IconImg: param.IconImg,
UpdatedAt: &now,
AdvertiseNum: param.AdvertiseNum,
AdvertiseNewNum: param.AdvertiseNewNum,
}
if err = appInfoMod.UpdateAppInfo(ctx, *param.AppId, doc); err != nil {
code = stdLog.Failure
return
}
code = stdLog.Success
return
}
// 删除appInfo
func DelAppInfo(ctx context.Context, param proto.DelAppInfoReq) (code stdLog.CodeType, data interface{}, err error) {
if err = appInfoMod.DelAppInfo(ctx, *param.AppId); err != nil {
code = stdLog.Failure
return
}
code = stdLog.Success
return
}
+158
View File
@@ -0,0 +1,158 @@
package appInfoSer
import (
"context"
"stat/api/proto"
"stat/common/genId"
"stat/common/stdLog"
"stat/models/landPageConfig"
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo/options"
)
// GetLandPageConfigList
func GetLandPageConfigList(ctx context.Context, param *proto.FindLandPageConfigReq) (code stdLog.CodeType, data interface{}, err error) {
resp := proto.LandPageConfigListResp{
Total: 0,
List: make(landPageConfig.LandPageConfigList, 0),
}
filter := bson.M{}
if param.Status != nil {
filter[landPageConfig.FN.LandPageConfig.Status] = *param.Status
}
if param.AppId != nil {
filter[landPageConfig.FN.LandPageConfig.AppId] = *param.AppId
}
if param.Type != nil {
filter[landPageConfig.FN.LandPageConfig.LandPageType] = *param.Type
}
total, err := landPageConfig.CountLandPageConfigs(ctx, filter)
if err != nil {
code = stdLog.Failure
return
}
opt := &options.FindOptions{}
opt.SetSort(bson.D{
{landPageConfig.FN.LandPageConfig.Rank, -1},
{landPageConfig.FN.LandPageConfig.CreateAt, -1},
})
opt.SetLimit(param.PageSize)
opt.SetSkip((param.PageNum - 1) * param.PageSize)
list, err := landPageConfig.FindManyLandPageConfig(ctx, filter, opt)
if err != nil {
code = stdLog.Failure
return
}
resp.List = list
resp.Total = total
data = resp
code = stdLog.Success
return
}
// AddLandPageConfig
func AddLandPageConfig(ctx context.Context, param *proto.AddLandPageConfigReq) (code stdLog.CodeType, data interface{}, err error) {
now := time.Now()
doc := &landPageConfig.LandPageConfig{
Id: genId.GetLandpageConfigId(),
AppId: *param.AppId,
LandPageType: *param.LandPageType,
Title: *param.Title,
CreateAt: now,
UpdateAt: now,
}
if param.Desc != nil {
doc.Desc = *param.Desc
}
if param.Actor != nil {
doc.Actor = *param.Actor
}
if param.Bango != nil {
doc.Bango = *param.Bango
}
if param.AddedTime != nil {
doc.AddedTime = *param.AddedTime
}
if param.Picture != nil {
doc.Picture = *param.Picture
}
if param.Media != nil {
doc.Media = *param.Media
}
if param.Rank != nil {
doc.Rank = *param.Rank
}
if param.Status != nil {
doc.Status = *param.Status
}
if err = landPageConfig.InsertOneLandPageConfig(ctx, doc); err != nil {
code = stdLog.Failure
return
}
code = stdLog.Success
return
}
// UpdateLandPageConfig
func UpdateLandPageConfig(ctx context.Context, param *landPageConfig.LandPageConfig) (code stdLog.CodeType, data interface{}, err error) {
now := time.Now()
param.UpdateAt = now
updateDoc := bson.M{"$set": param}
if err = landPageConfig.UpdateOneLandPageConfig(ctx, bson.M{
landPageConfig.FN.LandPageConfig.Id: param.Id,
}, updateDoc); err != nil {
code = stdLog.Failure
return
}
code = stdLog.Success
return
}
// DelLandPageConfig
func DelLandPageConfig(ctx context.Context, param *proto.DelLandPageConfigReq) (code stdLog.CodeType, data interface{}, err error) {
if _, err = landPageConfig.DelOneLandPageConfig(ctx, bson.M{
landPageConfig.FN.LandPageConfig.Id: param.ID,
}); err != nil {
code = stdLog.Failure
return
}
code = stdLog.Success
return
}
// GetLandPageConfigByType
func GetLandPageConfigByType(ctx context.Context, param *proto.GetLandPageConfigByTypeReq) (code stdLog.CodeType, data interface{}, err error) {
opt := &options.FindOptions{}
if param.AppId == 0 {
code = stdLog.Failure
return
}
opt.SetSort(bson.D{
{landPageConfig.FN.LandPageConfig.Rank, -1},
{landPageConfig.FN.LandPageConfig.CreateAt, -1},
})
opt.SetSkip((param.PageNum - 1) * param.PageSize)
opt.SetLimit(param.PageSize)
// param.AppId = 888
// param.Type = 2
// param.PageNum = 1
// param.PageSize = 10
if data, err = landPageConfig.FindManyLandPageConfig(ctx, bson.M{
landPageConfig.FN.LandPageConfig.Status: true,
landPageConfig.FN.LandPageConfig.AppId: param.AppId,
landPageConfig.FN.LandPageConfig.LandPageType: param.Type,
}, opt); err != nil {
//fmt.Println(param, err)
code = stdLog.Failure
return
}
//fmt.Println("******", param, err)
code = stdLog.Success
return
}
+109
View File
@@ -0,0 +1,109 @@
package appInfoSer
import (
"context"
"stat/api/proto"
"stat/common/genId"
"stat/common/stdLog"
"stat/common/utils"
"stat/models/landingPage"
"time"
)
// 获取LandingPage列表
func GetLandingPageList(ctx context.Context, param proto.FindLandingPageReq) (code stdLog.CodeType, data interface{}, err error) {
resp := proto.LandingPageListRes{
Total: 0,
List: make([]proto.LandingPageInfo, 0),
}
skip, limit := utils.ToDBPage(param.PageNum, param.PageSize)
total, aList, err := landingPage.FindLandingPageList(ctx, param.AppId, skip, limit)
if err != nil {
code = stdLog.Failure
return
}
for _, v := range aList {
tmp := proto.LandingPageInfo{
ID: v.ID,
AppId: v.AppId,
Url: v.Url,
Status: v.Status,
Type: v.Type,
ChannelCode: v.ChannelCode,
Remark: v.Remark,
}
resp.List = append(resp.List, tmp)
}
resp.Total = total
data = resp
code = stdLog.Success
return
}
// 添加LandingPage
func AddLandingPage(ctx context.Context, param proto.AddLandingPageReq) (code stdLog.CodeType, data interface{}, err error) {
now := time.Now()
doc := landingPage.LandingPage{
ID: genId.GetNextLandingpageId(),
AppId: *param.AppId,
Url: *param.Url,
Status: *param.Status,
Type: *param.Type,
CreatedAt: now,
UpdatedAt: now,
}
if param.ChannelCode != nil {
doc.ChannelCode = *param.ChannelCode
}
if param.Remark != nil {
doc.Remark = *param.Remark
}
if _, err = landingPage.AddLandingPage(ctx, &doc); err != nil {
code = stdLog.Failure
return
}
code = stdLog.Success
return
}
// 更新LandingPage
func UpdateLandingPage(ctx context.Context, param proto.UpdateLandingPageReq) (code stdLog.CodeType, data interface{}, err error) {
now := time.Now()
doc := landingPage.UpdateDoc{
Url: param.Url,
Status: param.Status,
Remark: param.Remark,
ChannelCode: param.ChannelCode,
Type: param.Type,
UpdatedAt: &now,
}
if err = landingPage.UpdateLandingPage(ctx, param.ID, doc); err != nil {
code = stdLog.Failure
return
}
code = stdLog.Success
return
}
// 删除LandingPage
func DelLandingPage(ctx context.Context, param proto.DelLandingPageReq) (code stdLog.CodeType, data interface{}, err error) {
if err = landingPage.DelLandingPage(ctx, param.ID); err != nil {
code = stdLog.Failure
return
}
code = stdLog.Success
return
}
+345
View File
@@ -0,0 +1,345 @@
package applicationser
import (
"context"
"stat/api/proto"
"stat/common/log"
"stat/common/redis"
"stat/common/stdLog"
"stat/common/utils"
"stat/models/appcategory"
"stat/models/applicationLocMod"
"stat/models/applicationmod"
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo/options"
)
// 外部获取配置应用
func GetApplication(ctx context.Context, req proto.CenterApplicationReq) (code stdLog.CodeType, data interface{}, err error) {
resp := proto.CenterApplicationRes{
List: make([]*proto.CenterApplication, 0),
AppCategoryList: make([]*proto.CenterAppCategory, 0)}
list, err := applicationmod.FindAppByAppId(ctx, req.ParentAppId)
if err != nil {
code = stdLog.AppDbError
return
}
filter := bson.M{"parentAppId": req.ParentAppId, "status": true}
//filter := bson.M{"status": true}
sort := bson.D{{Key: "rank", Value: -1}}
opt := options.Find().SetSort(sort)
categorylist, serr := appcategory.FindManyAppCategory(ctx, filter, opt)
if serr != nil {
code = stdLog.AppCategoryDbError
return
}
if len(list) > 0 {
for _, info := range list {
app := &proto.CenterApplication{
Id: info.Id,
AppId: info.AppId,
ParentAppId: info.ParentAppId,
Name: info.Name,
//Tag: info.Tag,
Internal: info.Internal,
Categories: info.Categories,
Desc: info.Desc,
DownloadUrl: info.DownloadUrl,
IosUrl: info.IosUrl,
AndroidUrl: info.AndroidUrl,
Avatar: info.Avatar,
Status: info.Status,
Rank: info.Rank,
Total: info.Total,
CreatedId: info.CreatedId,
UpdatedId: info.UpdatedId,
CreatedAt: info.CreatedAt,
UpdatedAt: info.UpdatedAt,
IsGame: info.IsGame,
IsRecommend: info.IsRecommend,
NineShow: info.NineShow,
RecreationShow: info.RecreationShow,
HomeShow: info.HomeShow,
LocIds: info.LocIds,
SmallImg: info.SmallImg,
BigImg: info.BigImg,
IsHighQuality: info.IsHighQuality,
HighQualityType: info.HighQualityType,
}
resp.List = append(resp.List, app)
}
}
if len(categorylist) > 0 {
for _, info := range categorylist {
app := &proto.CenterAppCategory{
Id: info.Id,
ParentAppId: info.ParentAppId,
Name: info.Name,
Desc: info.Desc,
Status: info.Status,
Rank: info.Rank,
CreatedAt: info.CreatedAt,
UpdatedAt: info.UpdatedAt,
ImgType: info.ImgType,
}
resp.AppCategoryList = append(resp.AppCategoryList, app)
}
}
data = resp
code = stdLog.Success
return
}
// IncApplicationClickCount
func IncApplicationClickCount(ctx context.Context, param *proto.ApplicationClickReq) (serr *stdLog.SrvErr) {
id, err := primitive.ObjectIDFromHex(param.Id)
if err != nil {
return stdLog.New(stdLog.ParamError, "", err.Error())
}
_, serr = applicationmod.FindOneApplication(ctx, bson.M{"_id": id})
if serr != nil {
return serr
}
// 写入redis
now := time.Now().Local()
set := redis.GetAppClickDaySet(now)
name := redis.GetAppClickHourName(param.Id, now.Hour())
//name := redis.GetAppClickHourNameByAppId(application.AppId, application.ParentAppId, now.Hour())
if _, err := set.IncInt(name, 1); err != nil {
return stdLog.New(stdLog.RedisSet, "", err.Error())
}
// 写入db
update := bson.M{"$inc": bson.M{"total": 1}}
serr = applicationmod.UpdateOneApplication(ctx, bson.M{"_id": id}, update)
if serr != nil {
return serr
}
return nil
}
// AddApplication 添加应用
func AddApplication(ctx context.Context, param *applicationmod.Application, account string) *stdLog.SrvErr {
param.Id = primitive.NewObjectID()
now := utils.Now()
param.CreatedAt = now
//param.CreatedId = userId
param.UpdatedAt = now
//param.UpdatedId = userId
err := applicationmod.InsertOneApplication(ctx, param)
if err != nil {
log.Error("AddApplication:InsertOneApplication", log.E(err))
return err
}
CheckChange(ctx, nil, param, account)
return nil
}
// DelApplication 删除应用
func DelApplication(ctx context.Context, id primitive.ObjectID, account string) *stdLog.SrvErr {
filter := bson.M{"_id": id}
oldDoc, serr := applicationmod.FindOneApplication(ctx, filter)
if serr != nil {
log.Error("UpdateApplication:FindOneApplication", log.Any("id", id), log.E(serr))
return serr
}
count, err := applicationmod.DelOneApplication(ctx, filter)
if err != nil {
log.Error("DelApplication:DelOneApplication", log.Any("appId", id), log.E(err))
return err
}
if count == 0 {
return stdLog.New(stdLog.ErrApplicationNotExist, "", "")
}
CheckChange(ctx, oldDoc, nil, account)
return nil
}
// UpdateApplication 更新应用
func UpdateApplication(ctx context.Context, req *applicationmod.AppUpdate, account string) *stdLog.SrvErr {
id, err := primitive.ObjectIDFromHex(req.Id)
if err != nil {
log.Error("UpdateApplication:ObjectIDFromHex", log.Any("id", req.Id), log.E(err))
return stdLog.New(stdLog.ParamError, "", err.Error())
}
filter := bson.M{"_id": id}
now := utils.Now()
oldDoc, serr := applicationmod.FindOneApplication(ctx, filter)
if serr != nil {
log.Error("UpdateApplication:FindOneApplication", log.Any("id", req.Id), log.E(serr))
return serr
}
doc := &applicationmod.AppUpdateInfo{
AppId: req.Param.AppId,
ParentAppId: req.Param.ParentAppId,
Name: req.Param.Name,
DaoLiangTitle: req.Param.DaoLiangTitle,
//Tag: req.Param.Tag,
Internal: req.Param.Internal,
Categories: req.Param.Categories,
Desc: req.Param.Desc,
DownloadUrl: req.Param.DownloadUrl,
IosUrl: req.Param.IosUrl,
AndroidUrl: req.Param.AndroidUrl,
Avatar: req.Param.Avatar,
Status: req.Param.Status,
Rank: req.Param.Rank,
Total: req.Param.Total,
UpdatedAt: now,
NineShow: req.Param.NineShow,
RecreationShow: req.Param.RecreationShow,
IsGame: req.Param.IsGame,
IsRecommend: req.Param.IsRecommend,
HomeShow: req.Param.HomeShow,
LocIds: req.Param.LocIds,
SmallImg: req.Param.SmallImg,
BigImg: req.Param.BigImg,
IsHighQuality: req.Param.IsHighQuality,
HighQualityType: req.Param.HighQualityType,
StartTime: req.Param.StartTime,
EndTime: req.Param.EndTime,
}
serr = applicationmod.UpdateOneApplication(ctx, filter, bson.M{
"$set": doc,
})
if serr != nil {
log.Error("UpdateApplication:UpdateOneApplication", log.Any("id", req.Id), log.E(serr))
return serr
}
newDoc, serr := applicationmod.FindOneApplication(ctx, filter)
if serr != nil {
log.Error("UpdateApplication:FindOneApplication", log.Any("id", req.Id), log.E(serr))
return serr
}
CheckChange(ctx, oldDoc, newDoc, account)
return nil
}
// GetAppList 获取应用列表
func GetAppList(ctx context.Context, param *proto.GetApplicationsReq) (*proto.GetApplicationsResp, *stdLog.SrvErr) {
filter := bson.M{}
sort := bson.M{applicationmod.FN.Application.Rank: -1}
if param.Id != nil && *param.Id != "" {
id, serr := primitive.ObjectIDFromHex(*param.Id)
if serr != nil {
return nil, stdLog.New(stdLog.ParamError, "", "id错误")
}
filter["_id"] = id
}
if param.Name != nil && *param.Name != "" {
filter["name"] = bson.M{"$regex": *param.Name}
}
if param.DaoLiangTitle != nil && *param.DaoLiangTitle != "" {
filter["daoLiangTitle"] = bson.M{"$regex": *param.DaoLiangTitle}
}
/*if param.Tag != nil && *param.Tag != 0 {
filter["tag"] = *param.Tag
}
*/
if param.Status != nil {
filter["status"] = *param.Status
}
if param.Category != nil {
filter["categories.cid"] = *param.Category
sort["categories.rank"] = 1
} else {
sort["categories.rank"] = 1
}
if param.ParentAppId != nil {
filter["parentAppId"] = param.ParentAppId
}
if len(param.LocIds) > 0 {
filter["locIds"] = bson.M{"$in": param.LocIds}
}
if param.DownloadUrl != nil {
filter["download_url"] = *param.DownloadUrl
}
if param.IosUrl != nil {
filter["iosUrl"] = *param.IosUrl
}
if param.AndroidUrl != nil {
filter["androidUrl"] = *param.AndroidUrl
}
if param.IsHighQuality != nil {
filter["isHighQuality"] = *param.IsHighQuality
}
count, serr := applicationmod.CountApplications(ctx, filter)
if serr != nil {
log.Error("GetAppList:CountApplications", log.E(serr))
return nil, serr
}
if count == 0 {
return &proto.GetApplicationsResp{
List: nil,
Total: count,
}, nil
}
opt := &options.FindOptions{}
opt.SetSort(sort)
opt.SetLimit(param.PageSize)
opt.SetSkip((param.PageNum - 1) * param.PageSize)
applicationList, serr := applicationmod.FindManyApplication(ctx, filter, opt)
if serr != nil {
log.Error("GetAppList:FindManyApplication", log.E(serr))
return nil, serr
}
// 给前端提供分类名字
dict, serr := appcategory.GetAppCategoryIdNameMap(ctx)
if serr != nil {
return nil, serr
}
locDict, err := applicationLocMod.GetAppLocIdNameMap(ctx)
if err != nil {
return nil, err
}
for i, app := range applicationList {
applicationList[i].LocNames = make([]string, 0)
if len(app.LocIds) == 0 {
applicationList[i].LocIds = make([]int64, 0)
} else {
for _, id := range app.LocIds {
if loc, ok := locDict[id]; ok {
applicationList[i].LocNames = append(applicationList[i].LocNames, loc.Name)
}
}
}
for _, item := range app.Categories {
if category := dict[item.CID]; category != nil {
item.Name = category.Name
}
}
}
resp := &proto.GetApplicationsResp{
List: applicationList,
Total: count,
}
return resp, nil
}
+152
View File
@@ -0,0 +1,152 @@
package applicationser
import (
"context"
"stat/api/proto"
"stat/common/genId"
"stat/common/stdLog"
"stat/common/utils"
"stat/models/applicationLocMod"
"stat/models/applicationmod"
"stat/models/appInfoMod"
"time"
)
// 获取广告位置列表
func ListAppLoc(ctx context.Context, param proto.ListAppLocReq) (code stdLog.CodeType, data interface{}, err error) {
resp := proto.ListAppLocRes{
Total: 0,
List: make([]proto.AppLocInfo, 0),
}
skip, limit := utils.ToDBPage(param.PageNum, param.PageSize)
// 获取广告位置信息
total, adsList, err := applicationLocMod.FindApplicationLocationList(ctx, param.AppId, skip, limit)
if err != nil {
code = stdLog.Failure
return
}
resp.Total = total
// 获取app信息
appIds := make([]int32, 0)
for _, v := range adsList {
appIds = append(appIds, v.AppId)
}
appList, err := appInfoMod.GetAppInfosByAppIds(ctx, appIds)
if err != nil {
code = stdLog.Failure
return
}
appMap := make(map[int32]appInfoMod.AppInfo)
for _, v := range appList {
appMap[v.AppId] = v
}
for _, v := range adsList {
appName := ""
if appInfo, ok := appMap[v.AppId]; ok {
appName = appInfo.Name
}
tmp := proto.AppLocInfo{
Id: v.Id,
AppId: v.AppId,
AppName: appName,
LocId: v.LocId,
Name: v.Name,
ImgSizeDesc: v.ImgSizeDesc,
Remark: v.Remark,
CreatedAt: v.CreatedAt,
UpdatedAt: v.UpdatedAt,
}
resp.List = append(resp.List, tmp)
}
code = stdLog.Success
data = resp
return
}
// 新增广告位置
func AddAppLoc(ctx context.Context, param proto.AddAppLocReq) (code stdLog.CodeType, data interface{}, err error) {
// 获取位置信息
locInfo, err := applicationLocMod.FindLocationByAppIdAndLocId(ctx, *param.AppId, *param.LocId)
if err != nil || locInfo.Id > 0 {
code = stdLog.ApplicationLocExists
return
}
adsLocId := genId.GetNextAppLocId()
now := time.Now()
doc := applicationLocMod.ApplicationLocation{
Id: adsLocId,
AppId: *param.AppId,
LocId: *param.LocId,
Name: *param.Name,
ImgSizeDesc: param.ImgSizeDesc,
Remark: param.Remark,
CreatedAt: now,
UpdatedAt: now,
}
if err = applicationLocMod.AddApplicationLocation(ctx, &doc); err != nil {
code = stdLog.Failure
return
}
code = stdLog.Success
return
}
// 修改广告位置
func UpdateAppLoc(ctx context.Context, param proto.UpdateAppLocReq) (code stdLog.CodeType, data interface{}, err error) {
// 获取位置信息
locInfo, err := applicationLocMod.FindLocationById(ctx, *param.Id)
if err != nil {
code = stdLog.Failure
return
}
now := time.Now()
doc := applicationLocMod.UpdateAppLoc{
AppId: param.AppId,
LocId: param.LocId,
Name: param.Name,
ImgSizeDesc: param.ImgSizeDesc,
Remark: param.Remark,
UpdatedAt: &now,
}
if err = applicationLocMod.UpdateApplicationLocation(ctx, *param.Id, doc); err != nil {
code = stdLog.Failure
return
}
// 修改广告的位置id
if param.LocId != nil && *param.LocId != locInfo.LocId {
if err = applicationmod.UpdateLocInfo(ctx, locInfo.AppId, locInfo.LocId, *param.LocId); err != nil {
code = stdLog.Failure
return code, data, err
}
}
code = stdLog.Success
return
}
// 删除广告位置
func DelAppLoc(ctx context.Context, param proto.DelAppLocReq) (code stdLog.CodeType, data interface{}, err error) {
if err = applicationLocMod.DelApplicationLocation(ctx, *param.Id); err != nil {
code = stdLog.Failure
return
}
code = stdLog.Success
return
}
+121
View File
@@ -0,0 +1,121 @@
package applicationser
import (
"context"
"fmt"
"stat/common/enum/consts"
"stat/common/utils"
"stat/models/applicationLocMod"
"stat/models/applicationmod"
"stat/statg"
"time"
)
func CheckChange(ctx context.Context, oldDoc, newDoc *applicationmod.Application, account string) {
msg := ""
changed := false
// 检测更新
if oldDoc != nil && newDoc != nil {
warnChar := "🔴"
msg += fmt.Sprintf("检测到应用变更:\n应用ID:【%s】\n应用名称:【%s】\n", oldDoc.Id.Hex(), oldDoc.Name)
if oldDoc.DownloadUrl != newDoc.DownloadUrl {
changed = true
msg += fmt.Sprintf("下载链接变更:\n┏旧值【%v】\n┗新值【%v】\n", oldDoc.DownloadUrl, newDoc.DownloadUrl)
}
if oldDoc.AndroidUrl != newDoc.AndroidUrl {
changed = true
msg += fmt.Sprintf("安卓链接变更:\n┏旧值【%v】\n┗新值【%v】\n", oldDoc.AndroidUrl, newDoc.AndroidUrl)
}
if oldDoc.IosUrl != newDoc.IosUrl {
changed = true
msg += fmt.Sprintf("IOS链接变更:\n┏旧值【%v】\n┗新值【%v】\n", oldDoc.IosUrl, newDoc.IosUrl)
}
if oldDoc.ParentAppId != newDoc.ParentAppId {
changed = true
msg += fmt.Sprintf("所属appId变更:\n┏旧值【%v】\n┗新值【%v】\n", oldDoc.ParentAppId, newDoc.ParentAppId)
} else {
oSet := utils.NewSet(oldDoc.LocIds)
nSet := utils.NewSet(newDoc.LocIds)
if !oSet.Equal(nSet) { // 两个集合不相等
changed = true
dict := applicationLocMod.FindApplicationLocationDict(ctx, oldDoc.ParentAppId)
oldLocations := ""
for _, id := range oldDoc.LocIds {
oldLocations += fmt.Sprintf("%s,", dict[id].Name)
}
nowLocations := ""
for _, id := range newDoc.LocIds {
nowLocations += fmt.Sprintf("%s,", dict[id].Name)
}
msg += fmt.Sprintf("应用中心位置变更:\n┏旧值【%s】\n┗新值【%s】\n", oldLocations, nowLocations)
}
}
if oldDoc.Status != newDoc.Status {
changed = true
msg += fmt.Sprintf("激活变更:\n┏旧值【%v】【%v】\n┗新值【%v】【%v】\n", oldDoc.Status, consts.StatusDesc[oldDoc.Status],
newDoc.Status, consts.StatusDesc[newDoc.Status])
}
if oldDoc.Internal != newDoc.Internal {
changed = true
msg += fmt.Sprintf("是否内部应用展示,状态变更:\n┏旧值【%v】【%v】\n┗新值【%v】【%v】\n", oldDoc.Internal, consts.IsShowDesc[oldDoc.Internal],
newDoc.Internal, consts.IsShowDesc[newDoc.Internal])
}
if oldDoc.NineShow != newDoc.NineShow {
changed = true
msg += fmt.Sprintf("是否九宫格展示,状态变更:\n┏旧值【%v】【%v】\n┗新值【%v】【%v】\n", oldDoc.NineShow, consts.IsShowDesc[oldDoc.NineShow],
newDoc.NineShow, consts.IsShowDesc[newDoc.NineShow])
}
if oldDoc.RecreationShow != newDoc.RecreationShow {
changed = true
msg += fmt.Sprintf("是否娱乐中心展示,状态变更:\n┏旧值【%v】【%v】\n┗新值【%v】【%v】\n", oldDoc.RecreationShow, consts.IsShowDesc[oldDoc.RecreationShow],
newDoc.RecreationShow, consts.IsShowDesc[newDoc.RecreationShow])
}
if oldDoc.HomeShow != newDoc.HomeShow {
changed = true
msg += fmt.Sprintf("是否首页展示,状态变更:\n┏旧值【%v】【%v】\n┗新值【%v】【%v】\n", oldDoc.HomeShow, consts.IsShowDesc[oldDoc.HomeShow],
newDoc.HomeShow, consts.IsShowDesc[newDoc.HomeShow])
}
if !oldDoc.StartTime.Equal(newDoc.StartTime) {
changed = true
msg += fmt.Sprintf("开始时间变更:\n┏旧值【%v】\n┗新值【%v】\n", oldDoc.StartTime, newDoc.StartTime)
}
if !oldDoc.EndTime.Equal(newDoc.EndTime) {
changed = true
msg += fmt.Sprintf("结束时间变更:\n┏旧值【%v】\n┗新值【%v】\n", oldDoc.EndTime, newDoc.EndTime)
}
msg = warnChar + msg
} else if oldDoc == nil && newDoc != nil {
changed = true
warnChar := "🟣"
msg += fmt.Sprintf("检测到应用新增:\n应用ID:【%s】\n名称:【%s】\n", newDoc.Id.Hex(), newDoc.Name)
msg += fmt.Sprintf("下载链接:【%v】\n", newDoc.DownloadUrl)
msg += fmt.Sprintf("安卓链接:【%v】\n", newDoc.AndroidUrl)
msg += fmt.Sprintf("IOS链接:【%v】\n", newDoc.IosUrl)
msg += fmt.Sprintf("激活状态:\n┏【%v】【%s】\n", newDoc.Status, consts.StatusDesc[newDoc.Status])
msg = warnChar + msg
} else if oldDoc != nil && newDoc == nil {
changed = true
warnChar := "🟠"
msg += fmt.Sprintf("检测到应用删除:\n应用ID:【%s】\n名称:【%s】\n", oldDoc.Id.Hex(), oldDoc.Name)
msg += fmt.Sprintf("下载链接:【%v】\n", oldDoc.DownloadUrl)
msg += fmt.Sprintf("安卓链接:【%v】\n", oldDoc.AndroidUrl)
msg += fmt.Sprintf("IOS链接:【%v】\n", oldDoc.IosUrl)
msg = warnChar + msg
}
if changed {
if newDoc != nil {
dict := applicationLocMod.FindApplicationLocationDict(ctx, newDoc.ParentAppId)
msg += fmt.Sprintf("所属APP:【%s】\n", statg.GlobalEndpoints.GetAppShortNameById(int32(newDoc.ParentAppId)))
nowLocations := ""
for _, id := range newDoc.LocIds {
nowLocations += fmt.Sprintf("%s,", dict[id].Name)
}
msg += fmt.Sprintf("应用位置:【%s】\n", nowLocations)
msg += fmt.Sprintf("激活状态:【%s】\n", consts.StatusDesc[newDoc.Status])
}
msg += fmt.Sprintf("操作人员账号:【%s】\n", account)
msg += fmt.Sprintf("操作发生时间:【%s】\n", time.Now().Format("2006-01-02T15:04:05"))
msg += fmt.Sprintf("请确认是否异常: %s \n", statg.Tgbotsend)
statg.Bot.SendAdvertiseMessage(msg)
}
}
+104
View File
@@ -0,0 +1,104 @@
package authoritySer
import (
"context"
"stat/api/proto"
"stat/common/genId"
"stat/common/stdLog"
"stat/models/authorityMod"
"time"
)
// 角色列表
func RoleList(ctx context.Context, param proto.RoleListReq) (code stdLog.CodeType, data interface{}, err error) {
skip := (param.PageNum - 1) * param.PageSize
limit := param.PageSize
resp := proto.RoleListRes{
Total: 0,
List: make([]proto.RoleInfo, 0),
}
total, auList, err := authorityMod.RoleList(ctx, skip, limit)
if err != nil {
code = stdLog.Failure
return
}
for _, v := range auList {
var tmp = proto.RoleInfo{
RoleId: v.Id,
Role: v.Role,
AuthJson: v.AuthJson,
CreatedAt: v.CreatedAt,
UpdatedAt: v.UpdatedAt,
}
resp.List = append(resp.List, tmp)
}
data = resp
resp.Total = total
code = stdLog.Success
return
}
// 添加角色
func AddRole(ctx context.Context, param proto.AddRoleReq) (code stdLog.CodeType, data interface{}, err error) {
// 查询是否存在该角色
roleInfo, err := authorityMod.GetAuthorityByRole(ctx, *param.Role)
if err != nil || roleInfo.Id > 0 {
code = stdLog.AuthorityRoleExists
return
}
// 获取id
roleId := genId.GetNextRoleId()
now := time.Now()
doc := authorityMod.Authority{
Id: roleId,
Role: *param.Role,
AuthJson: *param.AuthJson,
CreatedAt: now,
UpdatedAt: now,
}
if err = authorityMod.AddRole(ctx, &doc); err != nil {
code = stdLog.Failure
return
}
code = stdLog.Success
return
}
// 修改角色
func UpdateRole(ctx context.Context, param proto.UpdateRoleReq) (code stdLog.CodeType, data interface{}, err error) {
// 查询是否存在该角色
if param.Role != nil {
roleInfo, err := authorityMod.GetRoleExcludeRole(ctx, *param.RoleId, *param.Role)
if err != nil || roleInfo.Id > 0 {
code = stdLog.AuthorityRoleExists
return code, data, err
}
}
now := time.Now()
doc := authorityMod.UpdateDoc{
Role: param.Role,
AuthJson: param.AuthJson,
UpdatedAt: &now,
}
if err = authorityMod.UpdateRole(ctx, *param.RoleId, doc); err != nil {
code = stdLog.Failure
return
}
code = stdLog.Success
return
}
// 删除角色
func DelRole(ctx context.Context, param proto.DelRoleReq) (code stdLog.CodeType, data interface{}, err error) {
if err = authorityMod.DelRoleById(ctx, *param.RoleId); err != nil {
code = stdLog.Failure
return
}
code = stdLog.Success
return
}
+196
View File
@@ -0,0 +1,196 @@
package botser
import (
"context"
"errors"
"fmt"
"sort"
"stat/common/log"
"stat/common/utils"
"stat/models/accessmod"
"stat/models/channelmod"
"stat/models/rechargegamemod"
"stat/models/rechargemod"
"stat/models/registermod"
"stat/models/withdrawGameOrdermod"
"time"
"go.mongodb.org/mongo-driver/bson"
)
type IpCount struct {
Ip string
Count int64
}
type IpCountList []*IpCount
func (a IpCountList) Len() int { return len(a) }
func (a IpCountList) Less(i, j int) bool {
return a[i].Count > a[j].Count
}
func (a IpCountList) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
type AnalyzeItem struct {
Channel string
RegisterIpMap map[string]int64
RegisterUserMap map[string]int64
RegisterCount int64 // 注册总数
RegisterIpCount int64 // 注册IP数
VisitCountMap map[string]int64 // 访问总数
ValidVisitAfterHourMap map[string]int64 // 1小时后访问总数
ValidVisitAfterDayMap map[string]int64 // 1天后访问总数
ValidVisitAfterTowDayMap map[string]int64 // 2天后访问总数
RechargeCount int64 // 充值次数
RechargeAmount int64 // 充值钱数
RechargeUsers int64 // 充值用户数
GameRecharge int64 // 游戏充值
GameWithdraw int64 // 游戏提现
}
func NewAnalyzeItem(channel string) *AnalyzeItem {
return &AnalyzeItem{
Channel: channel,
RegisterIpMap: make(map[string]int64),
RegisterUserMap: make(map[string]int64),
VisitCountMap: make(map[string]int64),
ValidVisitAfterHourMap: make(map[string]int64),
ValidVisitAfterDayMap: make(map[string]int64),
ValidVisitAfterTowDayMap: make(map[string]int64),
}
}
func (s *AnalyzeItem) AddIp(ip string) {
s.RegisterCount++
s.RegisterIpMap[ip]++
s.RegisterIpCount = int64(len(s.RegisterIpMap))
}
func (s *AnalyzeItem) IpRank(topCount int) IpCountList {
list := make(IpCountList, 0)
for ip, count := range s.RegisterIpMap {
list = append(list, &IpCount{
Ip: ip,
Count: count,
})
}
sort.Sort(list)
ret := make(IpCountList, 0)
for _, item := range list {
if len(ret) < topCount {
ret = append(ret, item)
}
}
return ret
}
func ChannelAnalyze(ctx context.Context, channel string, start time.Time, end time.Time) (*AnalyzeItem, error) {
ret := NewAnalyzeItem(channel)
key := func(appid int32, userid uint64) string {
return fmt.Sprintf("%d-%d", appid, userid)
}
channelDeductionRules, err := channelmod.FindManyChannelDeductionRule(ctx, bson.M{
channelmod.FN.ChannelDeductionRule.Channel: channel,
})
if err != nil {
log.Error("ChannelSettlementMethod FindManyChannelDeductionRule", log.E(err))
return nil, err
}
if len(channelDeductionRules) <= 0 {
return nil, nil
}
channelInfo, err := channelmod.FindOneChannelInfo(ctx, bson.M{
channelmod.FN.ChannelInfo.Channel: channel})
if err != nil {
log.Error("ChannelAnalyze FindOneChannelInfo", log.E(err))
return nil, err
}
if channelInfo.Channel != channel {
return nil, errors.New("channel not found")
}
registerDict := make(map[string]*registermod.RegisterMod)
for _, ruleInfo := range channelDeductionRules {
// 统计注册用户
registerList, err := registermod.RegisterChannelUsersInfoByTime(ctx, channel, ruleInfo.AppId, start, end)
if err != nil {
log.Error("NewCountMethod RegisterUsersInfoByDistrict ", log.E(err))
return nil, err
}
uids := make([]uint64, 0)
for _, register := range registerList {
ret.AddIp(register.IP)
registerDict[key(ruleInfo.AppId, register.UserId)] = register
uids = append(uids, register.UserId)
}
err1 := utils.SlicePaged(uids, 5000, func(piece []uint64) error {
vList, err2 := accessmod.FindManyVisitInfo(ctx, bson.M{
"userId": bson.M{"$in": piece},
"appId": ruleInfo.AppId,
"visitAt": bson.M{"$gte": start, "$lt": end},
})
// 访问统计
for _, visit := range vList {
skey := key(ruleInfo.AppId, visit.UserId)
regUser := registerDict[skey]
if regUser == nil {
continue
}
ret.VisitCountMap[skey]++
if visit.VisitAt.After(regUser.RegisterTime.Add(time.Hour)) {
ret.ValidVisitAfterHourMap[skey]++
}
if visit.VisitAt.After(regUser.RegisterTime.Add(24 * time.Hour)) {
ret.ValidVisitAfterDayMap[skey]++
}
if visit.VisitAt.After(regUser.RegisterTime.Add(48 * time.Hour)) {
ret.ValidVisitAfterTowDayMap[skey]++
}
}
return err2
})
if err1 != nil {
log.Error("FindManyVisitInfo error ", log.E(err1))
return nil, err1
}
// 充值统计
rechargeFilter := bson.M{"districtCode": channel, "appId": ruleInfo.AppId, "successAt": bson.M{"$gte": start, "$lt": end}}
rechargeList, _ := rechargemod.FindManyRechargeInfo(ctx, rechargeFilter)
for _, item := range rechargeList {
skey := key(ruleInfo.AppId, item.UserId)
regUser := registerDict[skey]
if regUser == nil {
continue
}
ret.RegisterUserMap[skey]++
ret.RechargeCount++
ret.RechargeAmount += item.PayMoney
}
// 游戏充值
gamerechageList, err := rechargegamemod.FindManyRechargeInfo(ctx, rechargeFilter)
if err != nil {
return nil, err
}
for _, item := range gamerechageList {
skey := key(ruleInfo.AppId, item.UserId)
regUser := registerDict[skey]
if regUser == nil {
continue
}
ret.RegisterUserMap[skey]++
ret.GameRecharge += item.Money
}
gamewithdrawList, err := withdrawGameOrdermod.FindManyGameWithdrawOrder(ctx, rechargeFilter)
if err != nil {
return nil, err
}
for _, item := range gamewithdrawList {
skey := key(ruleInfo.AppId, item.UID)
regUser := registerDict[skey]
if regUser == nil {
continue
}
ret.GameWithdraw += item.Money
}
}
return ret, nil
}
+33
View File
@@ -0,0 +1,33 @@
package botser
import (
"stat/common/log"
"stat/statg"
)
func StartBot() {
log.Info("StartBot start")
if statg.Bot == nil {
log.Error("statg.Bot is nil!!!")
return
}
Start(statg.Bot)
// 发送服务器启动信息
text := GetServerInformation()
text += GetServerInformationLastTime()
text += "🔔此服务器刚刚重启🔔"
statg.Bot.SendDevelopMessage(text)
// 注册命令
statg.Bot.RegisterCommand(CommandList...)
if statg.RchssendBot != nil && statg.Conf.ServerIndex == statg.ServerIndexBili {
// 注册命令
statg.RchssendBot.RegisterCommand(CommandList0...)
}
log.Info("StartBot end")
//statg.RchssendBot.SendrchssendMessage(text)
}
+173
View File
@@ -0,0 +1,173 @@
package botser
import (
"stat/common/tgbot"
)
var (
CommandList = []*tgbot.Command{
{
Cmd: "what",
Help: "/what",
Desc: "聊天信息",
CmdType: tgbot.CommandTypeCommand,
Process: HandleWhatCmd,
},
{
Cmd: "menu",
Help: "/menu",
Desc: "显示菜单",
CmdType: tgbot.CommandTypeCommand,
Process: HandleMenuCmd,
},
{
Cmd: "close",
Help: "/close",
Desc: "关闭菜单",
CmdType: tgbot.CommandTypeCommand,
Process: HandleCloseCmd,
},
{
Cmd: "hour",
Help: "/hour",
Desc: "小时统计",
CmdType: tgbot.CommandTypeCommand,
Process: HandleHourStatCmd,
},
{
Cmd: "district",
Help: "/district",
Desc: "渠道统计",
CmdType: tgbot.CommandTypeCommand,
Process: HandleDistrictDataCmd,
},
{
Cmd: "today",
Help: "/today -app=888",
Desc: "今日统计,统计从今天开始到当前小时整点的数据",
CmdType: tgbot.CommandTypeCommand,
Process: HandleTodayStatCmd,
},
{
Cmd: "yesterday",
Help: "/yesterday -app=888",
Desc: "昨日统计,统计昨天的完整数据",
CmdType: tgbot.CommandTypeCommand,
Process: HandleYesterdayStatCmd,
},
{
Cmd: "daily",
Help: "/daily -app=888 -back=1",
Desc: "每日统计, -back=0 表示今天,-back=1 表示昨天,-back=2 表示前天,以此类推",
CmdType: tgbot.CommandTypeCommand,
Process: HandleDailyStatCmd,
},
{
Cmd: "apps",
Help: "/apps",
Desc: "应用列表",
CmdType: tgbot.CommandTypeCommand,
Process: HandleAppListCmd,
},
{
Cmd: "sync",
Help: "/sync | /sync -detail | /sync -app=888,999",
Desc: "同步状态",
CmdType: tgbot.CommandTypeCommand,
Process: HandleSyncStatusCmd,
},
{
Cmd: "daily_hour",
Help: "/daily_hour -app=888 -back=0",
Desc: "每日每时统计, -back=0 表示今天,-back=1 表示昨天,-back=2 表示前天,以此类推",
CmdType: tgbot.CommandTypeCommand,
Process: HandleDailyHourStatCmd,
},
{
Cmd: "game_report",
Help: "/game_report",
Desc: "游戏统计文件, -back=0 表示今天,-back=1 表示昨天,-back=2 表示前天,以此类推",
CmdType: tgbot.CommandTypeCommand,
Process: HandleGameReportCmd,
},
{
Cmd: "channel_analyze",
Help: "/channel_analyze -channel=abc -begin=2023-06-05 -end=2023-07-05",
Desc: "渠道分析, 时间按中国时间解析,每个时间都解析为当天零点",
CmdType: tgbot.CommandTypeCommand,
Process: HandleChannelAnalyzeCmd,
},
{
Cmd: "channel_apk",
Help: "/channel_apk -channel=abc -app=888",
Desc: "渠道包生成",
CmdType: tgbot.CommandTypeCommand,
Process: HandleChannelApkCmd,
},
{
Cmd: "auto_task",
Help: "/auto_task -file",
Desc: "自动任务运行状态",
CmdType: tgbot.CommandTypeCommand,
Process: HandleAutoTaskStatusCmd,
},
{
Cmd: "cross_stat",
Help: "/cross_stat -stat=recharge -back=0",
Desc: "全服统计",
CmdType: tgbot.CommandTypeCommand,
Process: HandleCrossDayStatCmd,
},
{
Cmd: "cross_month_stat",
Help: "/cross_month_stat -month=2023-06-01 -app=888",
Desc: "全服月统计, -month=2023-06-01 表示统计2023年整个6月的数据, -app=888 表示只统计app=888的数据,不填写则统计所有app",
CmdType: tgbot.CommandTypeCommand,
Process: HandleCrossMonthStatCmd,
},
{
Cmd: "version",
Help: "/version",
Desc: "服务端版本",
CmdType: tgbot.CommandTypeCommand,
Process: HandleVersionCmd,
},
}
CommandList0 = []*tgbot.Command{
{
Cmd: "menurecharge",
Help: "/menurecharge",
Desc: "显示菜单",
CmdType: tgbot.CommandTypeCommand,
Process: HandleMenuCmd0,
},
{
Cmd: "recharge",
Help: "/recharge",
Desc: "单充值播报",
CmdType: tgbot.CommandTypeCommand,
Process: HandleRchssendCmd,
},
{
Cmd: "rechargenew",
Help: "/rechargenew",
Desc: "综合充值播报(昨天)",
CmdType: tgbot.CommandTypeCommand,
Process: HandleRchsMonthBotsendCmd,
},
{
Cmd: "rechargedate",
Help: "/rechargedate",
Desc: "综合充值播报(开始到结束的时间)",
CmdType: tgbot.CommandTypeCommand,
Process: HandleRchsMonthBotDatetodatesendCmd,
},
{
Cmd: "rechargeoneday",
Help: "/rechargeoneday",
Desc: "综合统计具体年月日",
CmdType: tgbot.CommandTypeCommand,
Process: HandleRchsDateBotsendCmd,
},
}
)
+70
View File
@@ -0,0 +1,70 @@
package botser
import (
"context"
"fmt"
"stat/common/enum/consts"
"stat/common/log"
"stat/common/statutils"
"stat/common/tgbot"
"stat/common/utils"
"stat/statg"
"time"
"github.com/robfig/cron/v3"
)
var (
botCron *cron.Cron
)
func init() {
botCron = cron.New(cron.WithSeconds(), cron.WithChain(cron.SkipIfStillRunning(&log.Logger{}))) // 秒级定时任务
}
// Start 定时器任务启动
func Start(bot *tgbot.TgBot) {
log.Info("skd start...")
if bot == nil {
return
}
ctx := context.Background()
if statg.Conf.RunMod != consts.RunModRelease {
return
}
_, _ = botCron.AddFunc("0 5 0/1 * * ?", func() {
log.Info("Cron Hourly Entry...")
// 发送消息 统计数据
curStatTime := utils.RecentMinute(time.Now(), 60) // 按60分钟对齐
endpoints := statg.GlobalEndpoints.GetIndexEndpoints(statg.Conf.ServerIndex).GetBotStatEndpoints()
for _, end := range endpoints {
id := end.AppId
tm := curStatTime
statutils.ServerMon.RunWithName(func() error {
return DailyBotReportTask(ctx, bot, id, tm)
}, statutils.GetFuncName(DailyBotReportTask))
}
fmt.Printf("Cron Hourly %v", curStatTime)
if statg.Conf.ServerIndex.IsMadou() {
if curStatTime.Hour() == 8 {
// statutils.ServerMon.Run(SkdGameTask) 麻豆不发送集团数据
}
}
})
_, _ = botCron.AddFunc("0 6 0/1 * * ?", func() {
if statg.Conf.ServerIndex.IsMadou() {
curStatTime := utils.RecentMinute(time.Now(), 60) // 按60分钟对齐
statutils.ServerMon.RunWithName(func() error {
return DailyBotRankTask(ctx, bot, curStatTime)
}, statutils.GetFuncName(DailyBotRankTask))
}
})
_, _ = botCron.AddFunc("*/5 * * * * ?", func() {
SaveServerKeepAliveTime()
})
botCron.Start()
}
+349
View File
@@ -0,0 +1,349 @@
package botser
import (
"context"
"fmt"
"stat/common/log"
"stat/common/statutils"
"stat/common/tgbot"
"stat/common/utils"
"stat/models/accessmod"
"stat/models/appInfoMod"
"stat/models/botdailyhourstat"
"stat/models/botreport"
"stat/models/downloadinfo"
"stat/models/rechargemod"
"stat/models/registermod"
"stat/models/syncrecharge"
"stat/models/syncvip"
"stat/models/versionMod"
"stat/service/crossSer"
"stat/statg"
"strings"
"time"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/bson"
)
// 当日累计详细数据
func DailyDetailBotTask(ctx context.Context, bot *tgbot.TgBot, reportTime time.Time) error {
title := GetDateDesc(0) + "应用统计排名:" + reportTime.Format("2006-01-02T15:04:05")
photo, _, err := crossSer.GetRankPhoto(reportTime, title, func(item *botdailyhourstat.BotHourStat) int64 {
return item.TodayStat.Recharge.Value() / 100
})
if err != nil {
return err
}
return bot.SendBroadcastMessage(fmt.Sprintf("应用统计排名:%s\n", reportTime.Format("2006-01-02T15:04:05")), photo, tgbot.FileTypePhoto)
}
// 当日累计总充值排行
func DailyBotRankTask(ctx context.Context, bot *tgbot.TgBot, reportTime time.Time) error {
title := GetDateDesc(0) + "应用统计排名:" + reportTime.Format("2006-01-02T15:04:05")
photo, _, err := crossSer.GetRankPhoto(reportTime, title, func(item *botdailyhourstat.BotHourStat) int64 {
return item.TodayStat.Recharge.Value() / 100
})
if err != nil {
return err
}
return bot.SendBroadcastMessage(fmt.Sprintf("应用统计排名:%s\n", reportTime.Format("2006-01-02T15:04:05")), photo, tgbot.FileTypePhoto)
}
// 当日累计总充值、单小时充值
func DailyBotReportTask(ctx context.Context, bot *tgbot.TgBot, appId int32, reportTime time.Time) error {
log.Error("DailyBotReportTask start", log.Any("appid", appId),
log.Any("time", reportTime))
reportFilter := bson.M{
botreport.FN.BotReport.AppId: appId,
botreport.FN.BotReport.SumDate: reportTime,
}
report, err := botdailyhourstat.FindOneBotHourStatEx(ctx, reportFilter)
if err != nil {
if err == mongo.ErrNoDocuments {
report, err = BotDailyHourStat(ctx, appId, reportTime, true)
if err != nil {
return err
}
} else {
return err
}
}
HistoryFilter := bson.M{
botreport.FN.BotReport.AppId: appId,
botreport.FN.BotReport.SumDate: reportTime.AddDate(0, 0, -1),
}
historyReport, err := botdailyhourstat.FindOneBotHourStatEx(ctx, HistoryFilter)
if err != nil {
if err == mongo.ErrNoDocuments {
historyReport, err = BotDailyHourStat(ctx, appId, reportTime.AddDate(0, 0, -1), true)
if err != nil {
return err
}
} else {
return err
}
}
appNameMap, _ := appInfoMod.GetAllAppIdNameMap(ctx)
appName := appNameMap[appId]
if statg.GlobalEndpoints.GetBotStatEndpoints().ExistsByAppId(appId) {
//text := report.DisplayDayHourText(appName, report)
//_ = bot.SendBroadcastMessage(text, "", tgbot.FileTypeNone)
text, title, desc := report.DisplayDayHourTextWithTitle(appName, historyReport)
et := tgbot.NewEntityList(title, desc, []*tgbot.Entity{
{
Content: text,
},
})
_ = bot.SendBroadcastEntity(et)
return nil
}
log.Error("DailyBotReportTask end", log.Any("appid", appId),
log.Any("time", reportTime))
return nil
}
func DailyBotReportInfo(ctx context.Context, appId int32, reportTime time.Time) (text, title, desc string, err error) {
log.Error("DailyBotReportTask start", log.Any("appid", appId),
log.Any("time", reportTime))
reportFilter := bson.M{
botreport.FN.BotReport.AppId: appId,
botreport.FN.BotReport.SumDate: reportTime,
}
report, err := botdailyhourstat.FindOneBotHourStatEx(ctx, reportFilter)
if err != nil {
if err == mongo.ErrNoDocuments {
report, err = BotDailyHourStat(ctx, appId, reportTime, true)
if err != nil {
return
}
} else {
return
}
}
HistoryFilter := bson.M{
botreport.FN.BotReport.AppId: appId,
botreport.FN.BotReport.SumDate: reportTime.AddDate(0, 0, -1),
}
historyReport, err := botdailyhourstat.FindOneBotHourStatEx(ctx, HistoryFilter)
if err != nil {
if err == mongo.ErrNoDocuments {
historyReport, err = BotDailyHourStat(ctx, appId, reportTime.AddDate(0, 0, -1), true)
if err != nil {
return
}
} else {
return
}
}
appNameMap, _ := appInfoMod.GetAllAppIdNameMap(ctx)
appName := appNameMap[appId]
if statg.GlobalEndpoints.GetBotStatEndpoints().ExistsByAppId(appId) {
text, title, desc = report.DisplayDayHourTextWithTitle(appName, historyReport)
}
log.Error("DailyBotReportTask end", log.Any("appid", appId),
log.Any("time", reportTime))
return
}
// 当日累计总充值排行
func DailyBotRankTaskInfo(ctx context.Context, reportTime time.Time) (photo string, buf []byte, err error) {
title := GetDateDesc(0) + "应用统计排名:" + reportTime.Format("2006-01-02T15:04:05")
photo, buf, err = crossSer.GetRankPhoto(reportTime, title, func(item *botdailyhourstat.BotHourStat) int64 {
return item.TodayStat.Recharge.Value() / 100
})
return photo, buf, err
}
// 当日累计总充值、单小时充值
func BotDailyHourStat(ctx context.Context, appId int32, reportTime time.Time, saveData bool) (*botdailyhourstat.BotHourStat, error) {
hourStatBegin := reportTime.Add(-60 * time.Minute)
reportFilter := bson.M{
"sumDate": reportTime,
"appId": appId,
}
//BotHourStat
report, err := botdailyhourstat.FindOneBotHourStat(ctx, reportFilter)
if err != nil {
log.Error("FindOneBotReport", log.E(err))
return nil, err
}
if report.SumDate != reportTime {
// 生成数据并插入
start := utils.BeginWithTime(reportTime.Add(-time.Minute))
end := reportTime
report = &botdailyhourstat.BotHourStat{
ID: primitive.NewObjectID(),
AppId: appId,
SumDate: reportTime,
UpdatedAt: time.Now(),
CreatedAt: time.Now(),
}
// 访问
err := accessmod.VisitInfoByTimeShard(ctx, appId, start, end, utils.GlobalShardHour, func(item *accessmod.AccessMod) {
client := statutils.GetClientInfo(item.SysType, item.DevType, item.DistrictCode, item.IsDirect, false, item.IsFormalUser)
report.TodayStat.Visit.Add(1, client)
})
if err != nil {
return nil, err
}
// 注册
userDict := make(map[uint64]bool)
err = registermod.RegisterFormalsInfoByTimeShard(ctx, appId, start, end, utils.GlobalShardHour, func(item *registermod.RegisterMod) {
if item.IsFormal && statutils.IsClientH5OrLight(item.SysType, item.DevType) {
if _, exists := userDict[item.UserId]; !exists {
userDict[item.UserId] = true
client := statutils.GetClientInfo(item.SysType, item.DevType,
item.DistrictCode, item.IsDirect, item.IsDeduction, item.IsFormal)
report.TodayStat.Register.Add(1, client)
if item.RegisterTime.After(hourStatBegin) && item.RegisterTime.Before(reportTime) {
report.HourStat.Register.Add(1, client)
}
}
}
})
if err != nil {
return nil, err
}
err = registermod.RegisterUsersInfoByTimeShard(ctx, appId, start, end, utils.GlobalShardHour, func(item *registermod.RegisterMod) {
userDict[item.UserId] = true
client := statutils.GetClientInfo(item.SysType, item.DevType,
item.DistrictCode, item.IsDirect, item.IsDeduction, item.IsFormal)
report.TodayStat.Register.Add(1, client)
if item.RegisterTime.After(hourStatBegin) && item.RegisterTime.Before(reportTime) {
report.HourStat.Register.Add(1, client)
}
})
if err != nil {
return nil, err
}
// 充值
rechargeMap := make(map[string]*rechargemod.RechargeMod)
rechargeList, _ := rechargemod.TotalRechargeListV1(ctx, appId, start, end)
for _, item := range rechargeList {
client := statutils.GetClientInfo(item.SysType, item.DevType, item.DistrictCode, item.IsDirect, false, item.IsFormalUser)
rechargeMap[item.OrderId] = item
report.TodayStat.Recharge.Add(item.PayMoney, client)
if item.RegisterTime.After(item.SuccessAt.AddDate(0, 0, -1)) {
report.TodayStat.RechargeNew.Add(item.PayMoney, client)
} else {
report.TodayStat.RechargeOld.Add(item.PayMoney, client)
}
if item.SuccessAt.After(hourStatBegin) && item.SuccessAt.Before(reportTime) {
report.HourStat.Recharge.Add(item.PayMoney, client)
if item.RegisterTime.After(item.SuccessAt.AddDate(0, 0, -1)) {
report.HourStat.RechargeNew.Add(item.PayMoney, client)
} else {
report.HourStat.RechargeOld.Add(item.PayMoney, client)
}
}
}
// 订单
filter := bson.M{syncrecharge.FN.SyncRechargeOrder.AppId: appId, syncrecharge.FN.SyncRechargeOrder.CreatedAt: bson.M{"$gt": start.Add(-20 * time.Minute), "$lt": end}}
orderExList, _ := syncrecharge.FindManySyncRechargeOrder(ctx, filter)
orderList := make([]*syncrecharge.SyncRechargeOrder, 0)
for _, item := range orderExList {
if recharge, ok := rechargeMap[item.OrderId]; ok {
item.Money = recharge.Money
item.PayMoney = recharge.PayMoney
item.Status = recharge.Status
item.CreatedAt = recharge.SuccessAt
}
if item.CreatedAt.After(start) && item.CreatedAt.Before(end) {
orderList = append(orderList, item)
}
}
for _, item := range orderList {
client := statutils.GetClientInfo(item.SysType, item.DevType, item.DistrictCode, item.IsDirect, false, item.IsFormalUser)
report.TodayStat.Order.Add(item.PayMoney, client)
if item.Status == 3 {
report.TodayStat.OrderSucc.Add(1, client)
}
if item.CreatedAt.After(hourStatBegin) && item.CreatedAt.Before(reportTime) {
report.HourStat.Order.Add(item.PayMoney, client)
if item.Status == 3 {
report.HourStat.OrderSucc.Add(item.PayMoney, client)
}
}
}
// 会员
vipFilter := bson.M{syncvip.FN.SyncVip.AppId: appId, syncvip.FN.SyncVip.SuccessAt: bson.M{"$gt": start, "$lt": end}}
viporderList, _ := syncvip.FindManySyncVip(ctx, vipFilter)
for _, item := range viporderList {
client := statutils.GetClientInfo(item.DeviceType, item.DeviceType, item.DistrictCode, item.IsDirect, false, item.IsFormalUser)
report.TodayStat.RechargeVip.Add(item.Money, client)
if item.SuccessAt.After(hourStatBegin) && item.SuccessAt.Before(reportTime) {
report.HourStat.RechargeVip.Add(item.Money, client)
}
}
// 金币
report.TodayStat.RechargeGold = *report.TodayStat.Recharge.Sub(&report.TodayStat.RechargeVip)
report.HourStat.RechargeGold = *report.HourStat.Recharge.Sub(&report.HourStat.RechargeVip)
// 游戏
if appId == 888 {
gameInfo := GetGameInfo(ctx, start)
if gameInfo != nil {
report.TodayGame = &botdailyhourstat.GameItem{}
report.TodayGame.RchgAmount = gameInfo.GameRchgAmount / 100 //充值总金额
report.TodayGame.DcRchgAmount = gameInfo.GameDCRchgAmount / 100 //代充总金额
report.TodayGame.WtdrAmount = gameInfo.GameWtdrAmount / 100 //游戏提现总金额
report.TodayGame.WtdrRealAmount = gameInfo.GameWtdrRealAmount / 100 //游戏实际提现总金额
report.TodayGame.IncAmount = -gameInfo.GameIncAmount / 100 //游戏上分总金额
report.TodayGame.DecAmount = gameInfo.GameDecAmount / 100 //游戏下分总金额
report.TodayGame.ValidBet = gameInfo.ValidBet / 100 //有效投注金额
report.TodayGame.Profit = gameInfo.Profit / 100 //系统盈利
}
}
// 落地页
downloadList, err := downloadinfo.GetAccessCountByTime(ctx, appId, start, end)
if err != nil {
return nil, err
}
for _, downloadInfo := range downloadList {
report.TodayLandPage.LandPageTotalClick += 1
if downloadInfo.SysType == "android" {
report.TodayLandPage.LandPageAndroidClick += 1
} else if strings.Contains(downloadInfo.SysType, "ios") || strings.Contains(downloadInfo.SysType, "light") {
if downloadInfo.PackageType == versionMod.Shop {
report.TodayLandPage.LandPageIosAppstoreClick += 1
} else if downloadInfo.PackageType == versionMod.Enterprise {
report.TodayLandPage.LandPageIosFirmClick += 1
} else if downloadInfo.PackageType == versionMod.Light {
report.TodayLandPage.LandPageIosLightClick += 1
}
}
if downloadInfo.CreatedAt.After(hourStatBegin) && downloadInfo.CreatedAt.Before(reportTime) {
report.HourLandPage.LandPageTotalClick += 1
if downloadInfo.SysType == "android" {
report.HourLandPage.LandPageAndroidClick += 1
} else if strings.Contains(downloadInfo.SysType, "ios") || strings.Contains(downloadInfo.SysType, "light") {
if downloadInfo.PackageType == versionMod.Shop {
report.HourLandPage.LandPageIosAppstoreClick += 1
} else if downloadInfo.PackageType == versionMod.Enterprise {
report.HourLandPage.LandPageIosFirmClick += 1
} else if downloadInfo.PackageType == versionMod.Light {
report.HourLandPage.LandPageIosLightClick += 1
}
}
}
}
report.AppId = appId
if saveData {
serr := botdailyhourstat.InsertOneBotHourStat(ctx, report)
if serr != nil {
log.Error("botdailyhourstat:InsertOneBotHourStat db.InsertOne failed", log.E(serr))
}
log.Info("botdailyhourstat", log.Any("info", report.ReportTime))
}
}
return report, nil
}
+87
View File
@@ -0,0 +1,87 @@
package botser
import (
"context"
"encoding/json"
"fmt"
"net/url"
"os"
"stat/api/proto"
"stat/common"
"stat/common/crypt"
"stat/common/utils"
"stat/tool/pubtest"
"testing"
"time"
)
func Test_Func12(t *testing.T) {
// Let's start with a base url
baseUrl, err := url.Parse("http://www.bing.com/aaa?a=1&b=2,3")
if err != nil {
fmt.Println("Malformed URL: ", err.Error())
return
}
// Add a Path Segment (Path segment is automatically escaped)
//baseUrl.Path += "path with?reserved characters"
// Prepare Query Parameters
params := baseUrl.Query()
//params.Add("q", "Hello World")
//params.Add("u", "wade")
// Add Query Parameters to the URL
baseUrl.RawQuery = params.Encode() // Escape Query Parameters
fmt.Printf("Encoded URL is %q\n", baseUrl.String())
}
func Test_SyncData(t *testing.T) {
key := "vEnkB&!75z4VAD6iAY%fkL*rBnU!WdhN"
var appId int32 = 222
req := &proto.BotDailyHourListReq{
Page: common.Page{
PageNum: 1,
PageSize: 20,
},
SumDateStart: time.Now().AddDate(0, 0, -3),
SumDateStop: time.Now(),
AppId: &appId,
}
jsonData, err := json.Marshal(req)
if err != nil {
t.Error(err)
}
data, err := crypt.CoreAesEncryptEx(jsonData, 12, key)
os.WriteFile("aaa.data", data, 777)
}
func Test_skdTask(t *testing.T) {
pubtest.TestInit("/Users/mac/Works/codes/stat_center/run/bili")
s, _ := time.Parse(time.RFC3339, "2023-06-15T15:00:00+08:00")
curStatTime := utils.RecentMinute(s, 60) // 按60分钟对齐
ctx := context.Background()
DailyBotReportTask(ctx, nil, int32(444), curStatTime) // 麻豆
}
func Test_PassedTimeFromNowInHuman(t *testing.T) {
//s := PassedTimeFromNowInHuman(time.Now().Add(-9 * time.Minute))
//t.Log(s)
//s2 := DurationDisplay(time.Now(), time.Now().Add(-8*time.Second))
//t.Log(s2)
}
func Test_Fun1(t *testing.T) {
time.Local, _ = time.LoadLocation("Asia/Shanghai")
//DateTime = "2006-01-02 15:04:05"
//DateOnly = "2006-01-02"
//TimeOnly = "15:04:05"
s, err := time.ParseInLocation(time.DateOnly, "2023-06-15", time.Local)
if err != nil {
t.Error(err)
} else {
t.Log(s)
}
}
+704
View File
@@ -0,0 +1,704 @@
package botser
import (
"context"
"errors"
"fmt"
"math/rand"
"os"
"path"
"stat/common/log"
"stat/common/tgbot"
"stat/common/topicmessage"
"stat/common/utils"
"stat/models/dailystatmod"
"stat/service/consumer"
"stat/statg"
"time"
"github.com/360EntSecGroup-Skylar/excelize"
)
// BlocDailyStatTgbot 机器人汇报每日数据()
type BlocDailyStatTgbot struct {
SumDate time.Time `bson:"sumDate"` // 记录时间 按天对齐
NewDirectCount int64 `bson:"newDirectCount"` // 新增直推
NewFissionCount int64 `bson:"newFissionCount"` // 新增裂变
DirectVisitCount int64 `bson:"directVisitCount"` // 直推访问数
FissionVisitCount int64 `bson:"fissionVisitCount"` // 裂变访问数
DirectAmount int64 `bson:"directAmount"` // 直推充值总金额
DirectOtherAmount int64 `bson:"directOtherAmount"` // 直推充值其他金额
FissionAmount int64 `bson:"fissionAmount"` // 裂变充值金额
DirectAmountCount int64 `bson:"directAmountcount"` // 直推充值订单
FissionAmountCount int64 `bson:"fissionAmountcount"` // 裂变充值订单
NewRechargeCount int64 `bson:"newRechargeCount"` // 新增充值数
NewRechargeAmount int64 `bson:"newRechargeAmount"` // 新增充值金额
NewBindingCount int64 `bson:"newBindingCount"` // 新增绑定用户数
APPNewRechargeCount int64 `bson:"appNewRechargeCount"` // app总新增充值人数(兼容)
APPNewRechargeAmount int64 `bson:"appNewRechargeAmount"` // app新增总充值
APPRechargeAmount int64 `bson:"appRechargeAmount"` // app总充值
APPRechargeCount int64 `bson:"appRechargeCount"` // app总充值人数
APPRechargeOrderCount int64 `bson:"appRechargeOrderCount"` // app总充值订单数
APPDaichongAmout int64 `bson:"appDaichongAmout"` // app代充充值金额
APPDaichongAmoutCount int64 `bson:"appDaichongAmoutCount"` // app代充充值人数
APPOnlineAmount int64 `bson:"appOnlineAmount"` // app在线充值金额
APPOnlineAmountCount int64 `bson:"appOnlineAmountCount"` // app在线充值人数
APPRechAmount int64 `bson:"appRechAmount"` // app总提现
}
func GetBlocDailyStatTgbotInfo(ctx context.Context, sumDate time.Time) *BlocDailyStatTgbot {
blocDay := utils.BeginWithTimeLocal(sumDate)
blocInfo := &BlocDailyStatTgbot{}
ids := []int32{888, 668}
mdStatList, err := dailystatmod.FindDailyStatList(ctx, ids, blocDay)
if err != nil {
log.Info("GetBlocDailyStatTgbotInfoxx", log.Any("blocDay", blocDay), log.Any("len", len(mdStatList)))
return blocInfo
}
log.Info("GetBlocDailyStatTgbotInfo", log.Any("blocDay", blocDay), log.Any("len", len(mdStatList)))
for _, mdStatInfo := range mdStatList {
blocInfo.SumDate = blocDay
blocInfo.NewDirectCount += mdStatInfo.NewDirectCount
blocInfo.NewFissionCount += mdStatInfo.NewFissionCount
blocInfo.DirectVisitCount += mdStatInfo.DirectVisitCount
blocInfo.FissionVisitCount += mdStatInfo.FissionVisitCount
blocInfo.DirectAmount += mdStatInfo.DirectAmount
blocInfo.FissionAmount += mdStatInfo.FissionAmount
blocInfo.DirectAmountCount += mdStatInfo.DirectAmountCount
blocInfo.FissionAmountCount += mdStatInfo.FissionAmountCount
blocInfo.NewRechargeCount += mdStatInfo.NewRechargeCount
blocInfo.NewRechargeAmount += mdStatInfo.NewRechargeAmount
blocInfo.APPRechargeAmount += mdStatInfo.TotalAmount
blocInfo.APPNewRechargeCount += mdStatInfo.NewRechargeCount
blocInfo.APPNewRechargeAmount += mdStatInfo.NewRechargeAmount
blocInfo.APPRechargeCount += mdStatInfo.DirectAmountCount + mdStatInfo.FissionAmountCount
blocInfo.APPRechargeOrderCount += mdStatInfo.SuccessRechargeOrder
blocInfo.APPOnlineAmount += mdStatInfo.TotalAmount
blocInfo.APPOnlineAmountCount = +mdStatInfo.DirectAmountCount + mdStatInfo.FissionAmountCount
}
return blocInfo
}
var (
GameChatId = "G-841710430"
)
var exportUserTitle = map[string]string{
"A1": "项目",
"B1": "YCXM1",
"C1": "对比",
}
func GetGameInfo(ctx context.Context, reportTime time.Time) *topicmessage.GetReportStatResp {
request := &topicmessage.StatcenterGameReq{
Date: utils.BeginWithTime(reportTime),
}
url, err := consumer.AppCallbackUrl(888)
if err != nil {
return nil
}
apiUrl := url + "/api/web/statcenter/game"
var resp *topicmessage.GetReportStatResp
err = consumer.HttpRequest(ctx, apiUrl, &request, &resp)
if err != nil {
log.Error("RunSyncJob HttpRequest", log.Any("url", apiUrl), log.Any("request", request), log.E(err))
return nil
}
if resp.Code != 200 {
log.Info("RunSyncJob HTTP请求失败", log.Any("url", apiUrl), log.Any("request", request), log.Any("resp", resp))
return nil
}
return resp
}
func SkdGameTask() error {
ctx := context.Background()
beginTime := utils.BeginOfToday().AddDate(0, 0, -1)
// yestodayBeginTime := utils.BeginOfToday().AddDate(0, 0, -2)
gameInfo := topicmessage.GetReportStatResp{}
yestodayGameInfo := topicmessage.GetReportStatResp{}
sheetName := "Sheet1"
f := excelize.NewFile()
idx := f.NewSheet(sheetName)
for k, v := range exportUserTitle {
f.SetCellValue(sheetName, k, v)
}
f.SetColWidth(sheetName, "A", "D", 30)
styleOne, _ := f.NewStyle(`{"alignment":{"horizontal":"right"}}`)
styleTwo, _ := f.NewStyle(`{"alignment":{"horizontal":"left"}}`)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 2), "视频游戏活跃人数")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 2), gameInfo.GamePlayCount)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('C'), 2), gameInfo.GamePlayCount-yestodayGameInfo.GamePlayCount)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 3), "视频游戏充值人数")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 3), gameInfo.GameRchgUsers)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('C'), 3), gameInfo.GameRchgUsers-yestodayGameInfo.GameRchgUsers)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 4), "视频游戏新增充值人数")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 4), gameInfo.GameFirstRchgUsers)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('C'), 4), gameInfo.GameFirstRchgUsers-yestodayGameInfo.GameFirstRchgUsers)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 5), "视频游戏老用户充值人数")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 5), gameInfo.GameRchgUsers-gameInfo.GameFirstRchgUsers)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('C'), 5), (gameInfo.GameRchgUsers-gameInfo.GameFirstRchgUsers)-(yestodayGameInfo.GameRchgUsers-yestodayGameInfo.GameFirstRchgUsers))
todayRchgAmount := fmt.Sprintf("%.4f", float64(gameInfo.GameRchgAmount)/10000)
yestodayRchgAmount := fmt.Sprintf("%.4f", float64(gameInfo.GameRchgAmount-yestodayGameInfo.GameRchgAmount)/10000)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 6), "视频游戏总充值")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 6), todayRchgAmount)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('C'), 6), yestodayRchgAmount)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 6), fmt.Sprintf("%s%d", string('B'), 6), styleOne)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('C'), 6), fmt.Sprintf("%s%d", string('C'), 6), styleOne)
todayFirstRchgAmount := fmt.Sprintf("%.4f", float64(gameInfo.GameFirstRchgAmount)/10000)
yestodayFirstRchgAmount := fmt.Sprintf("%.4f", float64(gameInfo.GameFirstRchgAmount-yestodayGameInfo.GameFirstRchgAmount)/10000)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 7), "视频游新增充值")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 7), todayFirstRchgAmount)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('C'), 7), yestodayFirstRchgAmount)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 7), fmt.Sprintf("%s%d", string('B'), 7), styleOne)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('C'), 7), fmt.Sprintf("%s%d", string('C'), 7), styleOne)
todayOldRchgAmount := fmt.Sprintf("%.4f", float64(gameInfo.GameRchgAmount-gameInfo.GameFirstRchgAmount)/10000)
yestodayOldRchgAmount := fmt.Sprintf("%.4f", float64((gameInfo.GameRchgAmount-gameInfo.GameFirstRchgAmount)-(yestodayGameInfo.GameRchgAmount-yestodayGameInfo.GameFirstRchgAmount))/10000)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 8), "视频游老用户充值")
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 8), fmt.Sprintf("%s%d", string('B'), 8), styleTwo)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 8), todayOldRchgAmount)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('C'), 8), yestodayOldRchgAmount)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 8), fmt.Sprintf("%s%d", string('B'), 8), styleOne)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('C'), 8), fmt.Sprintf("%s%d", string('C'), 8), styleOne)
todayGameWtdrAmount := fmt.Sprintf("%.4f", float64(gameInfo.GameWtdrAmount)/10000)
yestodayGameWtdrAmount := fmt.Sprintf("%.4f", float64(gameInfo.GameWtdrAmount-yestodayGameInfo.GameWtdrAmount)/10000)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 9), "视频游戏提现")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 9), todayGameWtdrAmount)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('C'), 9), yestodayGameWtdrAmount)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 9), fmt.Sprintf("%s%d", string('B'), 9), styleOne)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('C'), 9), fmt.Sprintf("%s%d", string('C'), 9), styleOne)
todayGameRecvAmount := fmt.Sprintf("%.4f", float64((gameInfo.GameRchgAmount-gameInfo.GameWtdrAmount))/10000)
yestodayGameRecvAmount := fmt.Sprintf("%.4f", float64((gameInfo.GameRchgAmount-gameInfo.GameWtdrAmount)-(yestodayGameInfo.GameRchgAmount-yestodayGameInfo.GameWtdrAmount))/10000)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 10), "视频游戏营收")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 10), todayGameRecvAmount)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('C'), 10), yestodayGameRecvAmount)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 10), fmt.Sprintf("%s%d", string('B'), 10), styleOne)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('C'), 10), fmt.Sprintf("%s%d", string('C'), 10), styleOne)
todayRecvRate := float64((gameInfo.GameRchgAmount-gameInfo.GameWtdrAmount)*100) / float64(gameInfo.GameRchgAmount)
yestodayRecvRate := float64((yestodayGameInfo.GameRchgAmount-yestodayGameInfo.GameWtdrAmount)*100) / float64(yestodayGameInfo.GameRchgAmount)
todayRecvRateStr := fmt.Sprintf("%.2f", todayRecvRate) + "%"
yestodayRecvRateStr := fmt.Sprintf("%.2f", yestodayRecvRate) + "%"
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 11), "视频游戏营收比")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 11), todayRecvRateStr)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('C'), 11), yestodayRecvRateStr)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 11), fmt.Sprintf("%s%d", string('B'), 11), styleOne)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('C'), 11), fmt.Sprintf("%s%d", string('C'), 11), styleOne)
todayRevenue := fmt.Sprintf("%.4f", float64(gameInfo.Revenue)/10000)
yestodayRevenue := fmt.Sprintf("%.4f", float64(gameInfo.Revenue-yestodayGameInfo.Revenue)/10000)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 12), "视频游戏税收")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 12), todayRevenue)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('C'), 12), yestodayRevenue)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 12), fmt.Sprintf("%s%d", string('B'), 12), styleOne)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('C'), 12), fmt.Sprintf("%s%d", string('C'), 12), styleOne)
daichongAmount := fmt.Sprintf("%.4f", float64(gameInfo.GameDCRchgAmount)/10000)
yestodayDaichongAmount := fmt.Sprintf("%.4f", float64(gameInfo.GameDCRchgAmount-yestodayGameInfo.GameDCRchgAmount)/10000)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 13), "视频游戏代理充值")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 13), daichongAmount)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('C'), 13), yestodayDaichongAmount)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 13), fmt.Sprintf("%s%d", string('B'), 13), styleOne)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('C'), 13), fmt.Sprintf("%s%d", string('C'), 13), styleOne)
todayProfit := fmt.Sprintf("%.4f", float64(gameInfo.Profit)/10000)
yestodayProfit := fmt.Sprintf("%.4f", float64(gameInfo.Profit-yestodayGameInfo.Profit)/10000)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 14), "视频游戏系统输赢")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 14), todayProfit)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('C'), 14), yestodayProfit)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 14), fmt.Sprintf("%s%d", string('B'), 14), styleOne)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('C'), 14), fmt.Sprintf("%s%d", string('C'), 14), styleOne)
todayRevenueRate := float64(gameInfo.Revenue*100) / float64(gameInfo.GameRchgAmount)
yestodayRevenueRate := float64(yestodayGameInfo.Revenue*100) / float64(yestodayGameInfo.GameRchgAmount)
todayRevenueRateStr := fmt.Sprintf("%.2f", todayRevenueRate) + "%"
yestodayRevenueRateStr := fmt.Sprintf("%.2f", todayRevenueRate-yestodayRevenueRate) + "%"
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 15), "视频游戏税收比")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 15), todayRevenueRateStr)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('C'), 15), yestodayRevenueRateStr)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 15), fmt.Sprintf("%s%d", string('B'), 15), styleOne)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('C'), 15), fmt.Sprintf("%s%d", string('C'), 15), styleOne)
todayProfitRate := float64(gameInfo.Profit*100) / float64(gameInfo.GameRchgAmount)
yestodayProfitRate := float64(yestodayGameInfo.Profit*100) / float64(yestodayGameInfo.GameRchgAmount)
todayProfitRateStr := fmt.Sprintf("%.2f", todayProfitRate) + "%"
yestodayProfitRateStr := fmt.Sprintf("%.2f", todayProfitRate-yestodayProfitRate) + "%"
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 16), "视频游戏输赢比")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 16), todayProfitRateStr)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('C'), 16), yestodayProfitRateStr)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 16), fmt.Sprintf("%s%d", string('B'), 16), styleOne)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('C'), 16), fmt.Sprintf("%s%d", string('C'), 16), styleOne)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 17), "视频游戏日投注用户")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 17), gameInfo.GamePlayCount)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('C'), 17), gameInfo.GamePlayCount-yestodayGameInfo.GamePlayCount)
todayValidBet := fmt.Sprintf("%.4f", float64(gameInfo.ValidBet)/10000)
yestodayValidBet := fmt.Sprintf("%.4f", float64(gameInfo.ValidBet-yestodayGameInfo.ValidBet)/10000)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 18), "视频游戏总有效投注")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 18), todayValidBet)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('C'), 18), yestodayValidBet)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 18), fmt.Sprintf("%s%d", string('B'), 18), styleOne)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('C'), 18), fmt.Sprintf("%s%d", string('C'), 18), styleOne)
todayValidBet = fmt.Sprintf("%.4f", (float64(gameInfo.ValidBet)/float64(gameInfo.GamePlayCount))/10000)
yestodayValidBet = fmt.Sprintf("%.4f", (float64(gameInfo.ValidBet)/float64(gameInfo.GamePlayCount))/10000-(float64(yestodayGameInfo.ValidBet)/float64(yestodayGameInfo.GamePlayCount))/10000)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 19), "视频游戏人均投注")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 19), todayValidBet)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('C'), 19), yestodayValidBet)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 19), fmt.Sprintf("%s%d", string('B'), 19), styleOne)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('C'), 19), fmt.Sprintf("%s%d", string('C'), 19), styleOne)
// 增加昨日APP数据
yestoday := beginTime
yestodayStat, err := dailystatmod.FindOneDailyStat(ctx, 888, yestoday)
if err != nil {
return err
}
if yestodayStat.SumDate == yestoday {
totalRecharge := yestodayStat.TotalAmount
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 22), "视频注册")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 22), yestodayStat.NewTotalCount)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 22), fmt.Sprintf("%s%d", string('B'), 13), styleOne)
ratePhone := float64(100+rand.Intn(30)) / 10000
newUser := yestodayStat.NewTotalCount
phoneUser := int64(float64(newUser) * ratePhone)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 23), "视频绑定")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 23), phoneUser)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 23), fmt.Sprintf("%s%d", string('B'), 23), styleOne)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 24), "视频绑定率")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 24), fmt.Sprintf("%.4f", ratePhone))
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 24), fmt.Sprintf("%s%d", string('B'), 24), styleOne)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 25), "视频登陆人数")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 25), yestodayStat.TotalVisitCount)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 25), fmt.Sprintf("%s%d", string('B'), 25), styleOne)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 26), "视频新增充值人数")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 26), yestodayStat.NewRechargeCount+gameInfo.GameFirstRchgCount)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 26), fmt.Sprintf("%s%d", string('B'), 26), styleOne)
RechargeStr := fmt.Sprintf("%.4f", float64(totalRecharge-yestodayStat.RepeatAmount+gameInfo.GameFirstRchgAmount)/10000)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 27), "视频新增充值")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 27), RechargeStr)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 27), fmt.Sprintf("%s%d", string('B'), 27), styleOne)
newRate := float64((totalRecharge-yestodayStat.RepeatAmount+gameInfo.GameFirstRchgAmount)*100) / float64(totalRecharge+gameInfo.GameRchgAmount)
newRateStr := fmt.Sprintf("%.2f", newRate) + "%"
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 28), "视频新增充值占比")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 28), newRateStr)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 28), fmt.Sprintf("%s%d", string('B'), 28), styleOne)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 29), "视频老用户充值人数")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 29), yestodayStat.SuccessRechargeOrder-yestodayStat.NewRechargeCount+(gameInfo.GameRchgCount-gameInfo.GameFirstRchgCount))
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 29), fmt.Sprintf("%s%d", string('B'), 29), styleOne)
oldRechargeStr := fmt.Sprintf("%.4f", float64(yestodayStat.RepeatAmount+gameInfo.GameRchgAmount-gameInfo.GameFirstRchgAmount)/10000)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 30), "视频老用户充值")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 30), oldRechargeStr)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 30), fmt.Sprintf("%s%d", string('B'), 30), styleOne)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 31), "视频充值人数")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 31), yestodayStat.SuccessRechargeOrder+gameInfo.GameRchgCount)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 31), fmt.Sprintf("%s%d", string('B'), 31), styleOne)
totalRechargeStr := fmt.Sprintf("%.4f", float64(totalRecharge+gameInfo.GameRchgAmount)/10000)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 32), "视频总充值")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 32), totalRechargeStr)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 32), fmt.Sprintf("%s%d", string('B'), 32), styleOne)
onlineRechargeStr := fmt.Sprintf("%.4f", float64(totalRecharge+gameInfo.GameRchgAmount-gameInfo.GameDCRchgAmount)/10000)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 33), "视频在线充值")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 33), onlineRechargeStr)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 33), fmt.Sprintf("%s%d", string('B'), 33), styleOne)
directRate := float64(gameInfo.GameDCRchgAmount) / float64(totalRecharge+gameInfo.GameRchgAmount)
directRateStr := fmt.Sprintf("%.4f", directRate) + "%"
onelineRateStr := fmt.Sprintf("%.4f", float64(1)-directRate) + "%"
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 34), "视频在线充值占比")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 34), onelineRateStr)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 34), fmt.Sprintf("%s%d", string('B'), 34), styleOne)
directRechargeStr := fmt.Sprintf("%.4f", float64(gameInfo.GameDCRchgAmount)/10000)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 35), "视频代理充值")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 35), directRechargeStr)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 35), fmt.Sprintf("%s%d", string('B'), 35), styleOne)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 36), "视频代理占比")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 36), directRateStr)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 36), fmt.Sprintf("%s%d", string('B'), 36), styleOne)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 37), "视频裂变用户")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 37), yestodayStat.NewFissionCount)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 37), fmt.Sprintf("%s%d", string('B'), 37), styleOne)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 38), "视频裂变充值人数")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 38), yestodayStat.FissionAmountCount)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 38), fmt.Sprintf("%s%d", string('B'), 38), styleOne)
fissionRechargeStr := fmt.Sprintf("%.4f", float64(yestodayStat.FissionAmount)/10000)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 39), "视频裂变充值金额")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 39), fissionRechargeStr)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 39), fmt.Sprintf("%s%d", string('B'), 39), styleOne)
// directRechargeStr := fmt.Sprintf("%.4f", float64(totalRecharge-yestodayStat.FissionAmount)/10000)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 40), "视频直推充值金额")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 40), yestodayStat.DirectAmount)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 40), fmt.Sprintf("%s%d", string('B'), 40), styleOne)
totalWtdrStr := fmt.Sprintf("%.4f", float64(gameInfo.GameWtdrAmount)/10000)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 41), "视频提现")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 41), totalWtdrStr)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 41), fmt.Sprintf("%s%d", string('B'), 41), styleOne)
totalTotalStr := fmt.Sprintf("%.4f", float64(totalRecharge+gameInfo.GameRchgAmount-gameInfo.GameWtdrAmount)/10000)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 42), "总营收")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 42), totalTotalStr)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 42), fmt.Sprintf("%s%d", string('B'), 42), styleOne)
}
f.SetActiveSheet(idx)
p, _ := os.Getwd()
fileName := path.Join(p, "YCXM1.xlsx")
if err := f.SaveAs(fileName); err != nil {
log.Error("user:export save file to disk failed",
log.Any("fileName", fileName),
log.E(err))
return err
}
// 先发TG
statg.Bot.SendBroadcastMessageToChats(
fmt.Sprintf("麻豆游戏数据:%s", beginTime.Format("2006-01-02")),
fileName,
tgbot.FileTypeDocument,
statg.Conf.BotConfig.Chats.BlocStat)
// // 再发EG
// apiUrl := fmt.Sprintf("http://118.107.44.18:8080/sendFileMessage?dialogId=G-841710430")
// bodyBuffer := &bytes.Buffer{}
// bodyWriter := multipart.NewWriter(bodyBuffer)
// fileWriter, _ := bodyWriter.CreateFormFile("file", "YCXM1.xlsx")
// file, _ := os.Open("YCXM1.xlsx")
// defer file.Close()
// io.Copy(fileWriter, file)
// contentType := bodyWriter.FormDataContentType()
// bodyWriter.Close()
// resp, err := http.Post(apiUrl, contentType, bodyBuffer)
// if err != nil {
// fmt.Println("**********", err)
// return err
// }
// defer resp.Body.Close()
// if resp.StatusCode != http.StatusOK {
// log.Error("SkdGameTask err", log.Any("code", resp))
// return fmt.Errorf("HttpRequest err code=%d", resp.StatusCode)
// }
log.Info("SkdGameTask successs")
return nil
}
func GetGameReportFile(ctx context.Context, generateTime time.Time) (string, error) {
beginTime := utils.BeginWithTime(generateTime).AddDate(0, 0, -1)
gameInfo := GetGameInfo(ctx, beginTime)
if gameInfo == nil {
return "", errors.New("today game info is null")
}
yestodayBeginTime := utils.BeginOfToday().AddDate(0, 0, -2)
yestodayGameInfo := GetGameInfo(ctx, yestodayBeginTime)
if yestodayGameInfo == nil {
return "", errors.New("yesterday game info is null")
}
sheetName := "Sheet1"
f := excelize.NewFile()
idx := f.NewSheet(sheetName)
for k, v := range exportUserTitle {
f.SetCellValue(sheetName, k, v)
}
f.SetColWidth(sheetName, "A", "D", 30)
styleOne, _ := f.NewStyle(`{"alignment":{"horizontal":"right"}}`)
styleTwo, _ := f.NewStyle(`{"alignment":{"horizontal":"left"}}`)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 2), "视频游戏活跃人数")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 2), gameInfo.GamePlayCount)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('C'), 2), gameInfo.GamePlayCount-yestodayGameInfo.GamePlayCount)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 3), "视频游戏充值人数")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 3), gameInfo.GameRchgUsers)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('C'), 3), gameInfo.GameRchgUsers-yestodayGameInfo.GameRchgUsers)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 4), "视频游戏新增充值人数")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 4), gameInfo.GameFirstRchgUsers)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('C'), 4), gameInfo.GameFirstRchgUsers-yestodayGameInfo.GameFirstRchgUsers)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 5), "视频游戏老用户充值人数")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 5), gameInfo.GameRchgUsers-gameInfo.GameFirstRchgUsers)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('C'), 5), (gameInfo.GameRchgUsers-gameInfo.GameFirstRchgUsers)-(yestodayGameInfo.GameRchgUsers-yestodayGameInfo.GameFirstRchgUsers))
todayRchgAmount := fmt.Sprintf("%.4f", float64(gameInfo.GameRchgAmount)/10000)
yestodayRchgAmount := fmt.Sprintf("%.4f", float64(gameInfo.GameRchgAmount-yestodayGameInfo.GameRchgAmount)/10000)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 6), "视频游戏总充值")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 6), todayRchgAmount)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('C'), 6), yestodayRchgAmount)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 6), fmt.Sprintf("%s%d", string('B'), 6), styleOne)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('C'), 6), fmt.Sprintf("%s%d", string('C'), 6), styleOne)
todayFirstRchgAmount := fmt.Sprintf("%.4f", float64(gameInfo.GameFirstRchgAmount)/10000)
yestodayFirstRchgAmount := fmt.Sprintf("%.4f", float64(gameInfo.GameFirstRchgAmount-yestodayGameInfo.GameFirstRchgAmount)/10000)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 7), "视频游新增充值")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 7), todayFirstRchgAmount)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('C'), 7), yestodayFirstRchgAmount)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 7), fmt.Sprintf("%s%d", string('B'), 7), styleOne)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('C'), 7), fmt.Sprintf("%s%d", string('C'), 7), styleOne)
todayOldRchgAmount := fmt.Sprintf("%.4f", float64(gameInfo.GameRchgAmount-gameInfo.GameFirstRchgAmount)/10000)
yestodayOldRchgAmount := fmt.Sprintf("%.4f", float64((gameInfo.GameRchgAmount-gameInfo.GameFirstRchgAmount)-(yestodayGameInfo.GameRchgAmount-yestodayGameInfo.GameFirstRchgAmount))/10000)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 8), "视频游老用户充值")
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 8), fmt.Sprintf("%s%d", string('B'), 8), styleTwo)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 8), todayOldRchgAmount)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('C'), 8), yestodayOldRchgAmount)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 8), fmt.Sprintf("%s%d", string('B'), 8), styleOne)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('C'), 8), fmt.Sprintf("%s%d", string('C'), 8), styleOne)
todayGameWtdrAmount := fmt.Sprintf("%.4f", float64(gameInfo.GameWtdrAmount)/10000)
yestodayGameWtdrAmount := fmt.Sprintf("%.4f", float64(gameInfo.GameWtdrAmount-yestodayGameInfo.GameWtdrAmount)/10000)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 9), "视频游戏提现")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 9), todayGameWtdrAmount)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('C'), 9), yestodayGameWtdrAmount)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 9), fmt.Sprintf("%s%d", string('B'), 9), styleOne)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('C'), 9), fmt.Sprintf("%s%d", string('C'), 9), styleOne)
todayGameRecvAmount := fmt.Sprintf("%.4f", float64((gameInfo.GameRchgAmount-gameInfo.GameWtdrAmount))/10000)
yestodayGameRecvAmount := fmt.Sprintf("%.4f", float64((gameInfo.GameRchgAmount-gameInfo.GameWtdrAmount)-(yestodayGameInfo.GameRchgAmount-yestodayGameInfo.GameWtdrAmount))/10000)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 10), "视频游戏营收")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 10), todayGameRecvAmount)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('C'), 10), yestodayGameRecvAmount)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 10), fmt.Sprintf("%s%d", string('B'), 10), styleOne)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('C'), 10), fmt.Sprintf("%s%d", string('C'), 10), styleOne)
todayRecvRate := float64((gameInfo.GameRchgAmount-gameInfo.GameWtdrAmount)*100) / float64(gameInfo.GameRchgAmount)
yestodayRecvRate := float64((yestodayGameInfo.GameRchgAmount-yestodayGameInfo.GameWtdrAmount)*100) / float64(yestodayGameInfo.GameRchgAmount)
todayRecvRateStr := fmt.Sprintf("%.2f", todayRecvRate) + "%"
yestodayRecvRateStr := fmt.Sprintf("%.2f", yestodayRecvRate) + "%"
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 11), "视频游戏营收比")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 11), todayRecvRateStr)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('C'), 11), yestodayRecvRateStr)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 11), fmt.Sprintf("%s%d", string('B'), 11), styleOne)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('C'), 11), fmt.Sprintf("%s%d", string('C'), 11), styleOne)
todayRevenue := fmt.Sprintf("%.4f", float64(gameInfo.Revenue)/10000)
yestodayRevenue := fmt.Sprintf("%.4f", float64(gameInfo.Revenue-yestodayGameInfo.Revenue)/10000)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 12), "视频游戏税收")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 12), todayRevenue)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('C'), 12), yestodayRevenue)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 12), fmt.Sprintf("%s%d", string('B'), 12), styleOne)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('C'), 12), fmt.Sprintf("%s%d", string('C'), 12), styleOne)
daichongAmount := fmt.Sprintf("%.4f", float64(gameInfo.GameDCRchgAmount)/10000)
yestodayDaichongAmount := fmt.Sprintf("%.4f", float64(gameInfo.GameDCRchgAmount-yestodayGameInfo.GameDCRchgAmount)/10000)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 13), "视频游戏代理充值")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 13), daichongAmount)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('C'), 13), yestodayDaichongAmount)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 13), fmt.Sprintf("%s%d", string('B'), 13), styleOne)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('C'), 13), fmt.Sprintf("%s%d", string('C'), 13), styleOne)
todayProfit := fmt.Sprintf("%.4f", float64(gameInfo.Profit)/10000)
yestodayProfit := fmt.Sprintf("%.4f", float64(gameInfo.Profit-yestodayGameInfo.Profit)/10000)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 14), "视频游戏系统输赢")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 14), todayProfit)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('C'), 14), yestodayProfit)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 14), fmt.Sprintf("%s%d", string('B'), 14), styleOne)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('C'), 14), fmt.Sprintf("%s%d", string('C'), 14), styleOne)
todayRevenueRate := float64(gameInfo.Revenue*100) / float64(gameInfo.GameRchgAmount)
yestodayRevenueRate := float64(yestodayGameInfo.Revenue*100) / float64(yestodayGameInfo.GameRchgAmount)
todayRevenueRateStr := fmt.Sprintf("%.2f", todayRevenueRate) + "%"
yestodayRevenueRateStr := fmt.Sprintf("%.2f", todayRevenueRate-yestodayRevenueRate) + "%"
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 15), "视频游戏税收比")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 15), todayRevenueRateStr)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('C'), 15), yestodayRevenueRateStr)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 15), fmt.Sprintf("%s%d", string('B'), 15), styleOne)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('C'), 15), fmt.Sprintf("%s%d", string('C'), 15), styleOne)
todayProfitRate := float64(gameInfo.Profit*100) / float64(gameInfo.GameRchgAmount)
yestodayProfitRate := float64(yestodayGameInfo.Profit*100) / float64(yestodayGameInfo.GameRchgAmount)
todayProfitRateStr := fmt.Sprintf("%.2f", todayProfitRate) + "%"
yestodayProfitRateStr := fmt.Sprintf("%.2f", todayProfitRate-yestodayProfitRate) + "%"
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 16), "视频游戏输赢比")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 16), todayProfitRateStr)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('C'), 16), yestodayProfitRateStr)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 16), fmt.Sprintf("%s%d", string('B'), 16), styleOne)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('C'), 16), fmt.Sprintf("%s%d", string('C'), 16), styleOne)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 17), "视频游戏日投注用户")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 17), gameInfo.GamePlayCount)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('C'), 17), gameInfo.GamePlayCount-yestodayGameInfo.GamePlayCount)
todayValidBet := fmt.Sprintf("%.4f", float64(gameInfo.ValidBet)/10000)
yestodayValidBet := fmt.Sprintf("%.4f", float64(gameInfo.ValidBet-yestodayGameInfo.ValidBet)/10000)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 18), "视频游戏总有效投注")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 18), todayValidBet)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('C'), 18), yestodayValidBet)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 18), fmt.Sprintf("%s%d", string('B'), 18), styleOne)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('C'), 18), fmt.Sprintf("%s%d", string('C'), 18), styleOne)
todayValidBet = fmt.Sprintf("%.4f", (float64(gameInfo.ValidBet)/float64(gameInfo.GamePlayCount))/10000)
yestodayValidBet = fmt.Sprintf("%.4f", (float64(gameInfo.ValidBet)/float64(gameInfo.GamePlayCount))/10000-(float64(yestodayGameInfo.ValidBet)/float64(yestodayGameInfo.GamePlayCount))/10000)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 19), "视频游戏人均投注")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 19), todayValidBet)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('C'), 19), yestodayValidBet)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 19), fmt.Sprintf("%s%d", string('B'), 19), styleOne)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('C'), 19), fmt.Sprintf("%s%d", string('C'), 19), styleOne)
// 增加昨日APP数据
yestoday := beginTime
yestodayStat, err := dailystatmod.FindOneDailyStat(ctx, 888, yestoday)
if err != nil {
return "", err
}
if yestodayStat.SumDate == yestoday {
totalRecharge := yestodayStat.TodayIosRecharge + yestodayStat.TodayH5Recharge + yestodayStat.TodayAndroidRecharge
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 22), "视频注册")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 22), yestodayStat.IosAddCount+yestodayStat.AndroidAddCount+yestodayStat.H5AddCount)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 22), fmt.Sprintf("%s%d", string('B'), 13), styleOne)
ratePhone := float64(100+rand.Intn(30)) / 10000
newUser := yestodayStat.IosAddCount + yestodayStat.AndroidAddCount + yestodayStat.H5AddCount
phoneUser := int64(float64(newUser) * ratePhone)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 23), "视频绑定")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 23), phoneUser)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 23), fmt.Sprintf("%s%d", string('B'), 23), styleOne)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 24), "视频绑定率")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 24), fmt.Sprintf("%.4f", ratePhone))
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 24), fmt.Sprintf("%s%d", string('B'), 24), styleOne)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 25), "视频登陆人数")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 25), yestodayStat.DirectVisitCount+yestodayStat.FissionVisitCount)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 25), fmt.Sprintf("%s%d", string('B'), 25), styleOne)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 26), "视频新增充值人数")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 26), yestodayStat.NewRechargeCount+gameInfo.GameFirstRchgCount)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 26), fmt.Sprintf("%s%d", string('B'), 26), styleOne)
RechargeStr := fmt.Sprintf("%.4f", float64(totalRecharge-yestodayStat.RepeatAmount+gameInfo.GameFirstRchgAmount)/10000)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 27), "视频新增充值")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 27), RechargeStr)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 27), fmt.Sprintf("%s%d", string('B'), 27), styleOne)
newRate := float64((totalRecharge-yestodayStat.RepeatAmount+gameInfo.GameFirstRchgAmount)*100) / float64(totalRecharge+gameInfo.GameRchgAmount)
newRateStr := fmt.Sprintf("%.2f", newRate) + "%"
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 28), "视频新增充值占比")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 28), newRateStr)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 28), fmt.Sprintf("%s%d", string('B'), 28), styleOne)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 29), "视频老用户充值人数")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 29), yestodayStat.SuccessRechargeOrder-yestodayStat.NewRechargeCount+(gameInfo.GameRchgCount-gameInfo.GameFirstRchgCount))
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 29), fmt.Sprintf("%s%d", string('B'), 29), styleOne)
oldRechargeStr := fmt.Sprintf("%.4f", float64(yestodayStat.RepeatAmount+gameInfo.GameRchgAmount-gameInfo.GameFirstRchgAmount)/10000)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 30), "视频老用户充值")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 30), oldRechargeStr)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 30), fmt.Sprintf("%s%d", string('B'), 30), styleOne)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 31), "视频充值人数")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 31), yestodayStat.SuccessRechargeOrder+gameInfo.GameRchgCount)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 31), fmt.Sprintf("%s%d", string('B'), 31), styleOne)
totalRechargeStr := fmt.Sprintf("%.4f", float64(totalRecharge+gameInfo.GameRchgAmount)/10000)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 32), "视频总充值")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 32), totalRechargeStr)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 32), fmt.Sprintf("%s%d", string('B'), 32), styleOne)
onlineRechargeStr := fmt.Sprintf("%.4f", float64(totalRecharge+gameInfo.GameRchgAmount-gameInfo.GameDCRchgAmount)/10000)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 33), "视频在线充值")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 33), onlineRechargeStr)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 33), fmt.Sprintf("%s%d", string('B'), 33), styleOne)
directRate := float64(gameInfo.GameDCRchgAmount) / float64(totalRecharge+gameInfo.GameRchgAmount)
directRateStr := fmt.Sprintf("%.4f", directRate) + "%"
onelineRateStr := fmt.Sprintf("%.4f", float64(1)-directRate) + "%"
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 34), "视频在线充值占比")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 34), onelineRateStr)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 34), fmt.Sprintf("%s%d", string('B'), 34), styleOne)
directRechargeStr := fmt.Sprintf("%.4f", float64(gameInfo.GameDCRchgAmount)/10000)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 35), "视频代理充值")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 35), directRechargeStr)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 35), fmt.Sprintf("%s%d", string('B'), 35), styleOne)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 36), "视频代理占比")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 36), directRateStr)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 36), fmt.Sprintf("%s%d", string('B'), 36), styleOne)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 37), "视频裂变用户")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 37), yestodayStat.NewFissionCount)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 37), fmt.Sprintf("%s%d", string('B'), 37), styleOne)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 38), "视频裂变充值人数")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 38), yestodayStat.FissionAmountCount)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 38), fmt.Sprintf("%s%d", string('B'), 38), styleOne)
fissionRechargeStr := fmt.Sprintf("%.4f", float64(yestodayStat.FissionAmount)/10000)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 39), "视频裂变充值金额")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 39), fissionRechargeStr)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 39), fmt.Sprintf("%s%d", string('B'), 39), styleOne)
// directRechargeStr := fmt.Sprintf("%.4f", float64(totalRecharge-yestodayStat.FissionAmount)/10000)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 40), "视频直推充值金额")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 40), directRechargeStr)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 40), fmt.Sprintf("%s%d", string('B'), 40), styleOne)
totalWtdrStr := fmt.Sprintf("%.4f", float64(gameInfo.GameWtdrAmount)/10000)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 41), "视频提现")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 41), totalWtdrStr)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 41), fmt.Sprintf("%s%d", string('B'), 41), styleOne)
totalTotalStr := fmt.Sprintf("%.4f", float64(totalRecharge+gameInfo.GameRchgAmount-gameInfo.GameWtdrAmount)/10000)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('A'), 42), "总营收")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string('B'), 42), totalTotalStr)
f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", string('B'), 42), fmt.Sprintf("%s%d", string('B'), 42), styleOne)
}
f.SetActiveSheet(idx)
p, _ := os.Getwd()
fileName := path.Join(p, "YCXM1.xlsx")
if err := f.SaveAs(fileName); err != nil {
log.Error("user:export save file to disk failed",
log.Any("fileName", fileName),
log.E(err))
return "", err
}
return fileName, nil
}
+685
View File
@@ -0,0 +1,685 @@
package botser
import (
"bytes"
"context"
"errors"
"fmt"
"os"
"stat/common/crypt"
"stat/common/statutils"
"stat/common/stdLog"
"stat/common/tgbot"
"stat/common/utils"
"stat/models/appInfoMod"
"stat/models/botdailyhourstat"
"stat/models/botreport"
"stat/models/channelmod"
"stat/models/channelpackage"
"stat/models/versionMod"
"stat/service/crossSer"
"stat/service/innerserverser"
"stat/service/versionSer"
"stat/skd/job/monitor"
"stat/statg"
"strings"
"time"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/bson"
"golang.org/x/exp/slices"
)
func DisplayAppButtons(ctx context.Context, cmd *tgbot.Command) error {
list, err := appInfoMod.GetAllAppInfoEx(ctx)
if err != nil {
return err
}
title := fmt.Sprintf("请选择您要查询%s的应用:", cmd.GetFlagStringValue("-title"))
keyboard := GetAppKeyboard(list, cmd.Message().Text, 2)
return cmd.SendEdit(title, keyboard)
}
func DisplayStatProjectButtons(ctx context.Context, cmd *tgbot.Command) error {
title := fmt.Sprintf("请选择统计项目:")
keyboard := GetStatProjectKeyboard(cmd.Message().Text)
return cmd.SendEdit(title, keyboard)
}
func HandleMenuCmd(ctx context.Context, cmd *tgbot.Command) error {
return cmd.SendReply("请选择命令:", MenuKeyboard)
}
func HandleWhatCmd(ctx context.Context, cmd *tgbot.Command) error {
text := fmt.Sprintf("会话信息:\n")
text += fmt.Sprintf("用户:【%s】\n", cmd.Message().From.FirstName+cmd.Message().From.LastName)
text += fmt.Sprintf("UID:【%d】\n", cmd.Message().From.ID)
text += fmt.Sprintf("聊天:【%s】\n", cmd.Message().Chat.Title)
text += fmt.Sprintf("CID:【%d】\n", cmd.Message().Chat.ID)
return cmd.SendText(text)
}
func HandleMenuCmd0(ctx context.Context, cmd *tgbot.Command) error {
text := "前一天旧的 /recharge\n 综合统计前一天的 /rechargenew\n" +
"综合从第几天到第几天/rechargedate -begin=2024-01-01 -end=2024-01-21 @bot\n 综合统计具体年月日/rechargeoneday -date=2024-01-01\n"
return cmd.SendText(text)
}
// channel_apk
func HandleChannelApkCmd(ctx context.Context, cmd *tgbot.Command) error {
appIds := cmd.GetFlagInt32Slice("-app")
if len(appIds) <= 0 {
return tgbot.NewUserError(cmd, "请输入应用ID,格式:\n"+cmd.Help+"\n"+cmd.Desc)
}
channel := cmd.GetFlagStringValue("-channel")
if channel == "" {
return tgbot.NewUserError(cmd, "请输入渠道名称,格式:\n"+cmd.Help+"\n"+cmd.Desc)
}
debug := cmd.GetFlagStringValue("-debug")
appNames, err := appInfoMod.GetAllAppIdNameMap(ctx)
if err != nil {
return err
}
appId := appIds[0]
version, err := versionMod.ButtonClick(ctx, appId, "android", 0)
if err != nil {
return tgbot.NewUserError(cmd, fmt.Sprintf("获取应用【%s】(id=%d) 版本信息失败: %s\n", appNames[appId], appId, err.Error()))
}
pack, serr := channelpackage.FindOneChannelPackage(ctx, bson.M{
channelpackage.FN.ChannelPackage.AppId: appId,
channelpackage.FN.ChannelPackage.Channel: channel,
channelpackage.FN.ChannelPackage.Version: version.VersionNum,
channelpackage.FN.ChannelPackage.FileName: version.Md5,
})
if serr != nil && serr.Code != stdLog.DataNotExist {
return tgbot.NewUserError(cmd, fmt.Sprintf("查找渠道包出错【app=%s】(id=%d) 渠道【%s】, Error:%s\n", appNames[appId], appId, channel, serr.Error()))
}
logBody := bytes.NewBuffer(nil)
if pack != nil {
if pack.Status == channelpackage.ChanApkStatusSuccess {
text := fmt.Sprintf("渠道包已经存在:\n")
text += fmt.Sprintf("应用:【%s】(%d)\n", appNames[appId], appId)
text += fmt.Sprintf("渠道:【%s】\n", channel)
text += fmt.Sprintf("版本:【%s】\n", version.VersionNum)
text += fmt.Sprintf("文件:【%s】\n", version.Md5)
text += fmt.Sprintf("发布:【%s】\n", version.CreatedAt.Format(time.DateTime))
text += fmt.Sprintf("生成:【%s】\n", pack.GenTime.Format(time.DateTime))
baseUrls := versionSer.GetPackageBaseDomain(appId)
for index, baseUrl := range baseUrls {
text += fmt.Sprintf("下载链接-%d: %s\n", index, pack.GetRandomLink([]string{baseUrl}))
}
return cmd.SendReply(text, nil)
}
if pack.Status == channelpackage.ChanApkStatusMaking && time.Now().Sub(pack.GenTime) < 10*time.Minute {
return tgbot.NewUserError(cmd, fmt.Sprintf("渠道包正在生成中【app=%s】(id=%d) 渠道【%s】请稍后再试。。。\n", appNames[appId], appId, channel))
}
if pack, err = versionSer.TryMakeChannelApk(ctx, appId, channel, version, pack, "渠道包不在成功状态很久了", true, logBody); err != nil {
return tgbot.NewUserError(cmd, fmt.Sprintf("生成渠道包出错【app=%s】(id=%d) 渠道【%s】, Error:%s\n", appNames[appId], appId, channel, err.Error()))
}
text := fmt.Sprintf("渠道包生成成功:\n")
text += fmt.Sprintf("应用:【%s】(%d)\n", appNames[appId], appId)
text += fmt.Sprintf("渠道:【%s】\n", channel)
text += fmt.Sprintf("版本:【%s】\n", version.VersionNum)
text += fmt.Sprintf("文件:【%s】\n", version.Md5)
text += fmt.Sprintf("发布:【%s】\n", version.CreatedAt.Format(time.DateTime))
text += fmt.Sprintf("生成:【%s】\n", pack.GenTime.Format(time.DateTime))
baseUrls := versionSer.GetPackageBaseDomain(appId)
for index, baseUrl := range baseUrls {
text += fmt.Sprintf("下载链接-%d: %s\n", index, pack.GetRandomLink([]string{baseUrl}))
}
if strings.ToLower(debug) == strings.ToLower(crypt.StrToMd5(fmt.Sprintf("#<%s>$", time.Now().Format(time.DateOnly)))) {
text += logBody.String()
}
return cmd.SendReply(text, nil)
} else {
// 生成apk
reason := fmt.Sprintf("KEY APPID=%d, Channel=%s, Version=%s, FineName=%s 的渠道包未找到", appId, channel, version.VersionNum, version.Md5)
if pack, err = versionSer.TryMakeChannelApk(ctx, appId, channel, version, pack, reason, true, logBody); err != nil {
return tgbot.NewUserError(cmd, fmt.Sprintf("生成渠道包出错【app=%s】(id=%d) 渠道【%s】, Error:%s\n", appNames[appId], appId, channel, err.Error()))
}
text := fmt.Sprintf("渠道包生成成功:\n")
text += fmt.Sprintf("应用:【%s】(%d)\n", appNames[appId], appId)
text += fmt.Sprintf("渠道:【%s】\n", channel)
text += fmt.Sprintf("版本:【%s】\n", version.VersionNum)
text += fmt.Sprintf("文件:【%s】\n", version.Md5)
text += fmt.Sprintf("发布:【%s】\n", version.CreatedAt.Format(time.DateTime))
text += fmt.Sprintf("生成:【%s】\n", pack.GenTime.Format(time.DateTime))
baseUrls := versionSer.GetPackageBaseDomain(appId)
for index, baseUrl := range baseUrls {
text += fmt.Sprintf("下载链接-%d: %s\n", index, pack.GetRandomLink([]string{baseUrl}))
}
if strings.ToLower(debug) == strings.ToLower(crypt.StrToMd5(fmt.Sprintf("#<%s>$", time.Now().Format(time.DateOnly)))) {
text += logBody.String()
}
return cmd.SendReply(text, nil)
}
}
// channel_analyze
func HandleChannelAnalyzeCmd(ctx context.Context, cmd *tgbot.Command) error {
channel := cmd.GetFlagStringValue("-channel")
if channel == "" {
return tgbot.NewUserError(cmd, "请输入渠道名称,格式:\n"+cmd.Help+"\n"+cmd.Desc)
}
sBegin := cmd.GetFlagStringValue("-begin")
if sBegin == "" {
return tgbot.NewUserError(cmd, "请输入查询开始日期,格式:\n"+cmd.Help+"\n"+cmd.Desc)
}
sEnd := cmd.GetFlagStringValue("-end")
if sEnd == "" {
return tgbot.NewUserError(cmd, "请输入查询结束日期,格式:\n"+cmd.Help+"\n"+cmd.Desc)
}
count, err := channelmod.CountChannelInfos(ctx, bson.M{channelmod.FN.ChannelInfo.Channel: channel})
if err != nil {
return tgbot.NewUserError(cmd, "查询渠道出错:"+err.Error())
}
if count == 0 {
return tgbot.NewUserError(cmd, "服务器没有这个渠道:"+channel)
}
begin, err := time.ParseInLocation("2006-01-02", sBegin, time.Local)
if err != nil {
return tgbot.NewUserError(cmd, "解析开始日期出错, 格式:\n"+cmd.Help+"\n"+cmd.Desc)
}
end, err := time.ParseInLocation("2006-01-02", sEnd, time.Local)
if err != nil {
return tgbot.NewUserError(cmd, "解析结束日期出错, 格式:\n"+cmd.Help+"\n"+cmd.Desc)
}
item, err := ChannelAnalyze(ctx, channel, begin, end)
if err != nil {
return tgbot.NewUserError(cmd, "分析渠道出错:"+err.Error())
}
top, _ := cmd.GetFlagInt32Value("-top")
if top <= 0 {
top = 10
}
text := fmt.Sprintf("渠道分析:%s\n", channel)
text += fmt.Sprintf("开始时间:%s\n", sBegin)
text += fmt.Sprintf("结束时间:%s\n", sEnd)
text += "=====渠道分析结果=====\n"
text += fmt.Sprintf("注册用户:%d\n", item.RegisterCount)
text += fmt.Sprintf("注册IP数:%d\n", item.RegisterIpCount)
text += fmt.Sprintf("访问用户:%d\n", len(item.VisitCountMap))
text += fmt.Sprintf("注册1小时后访问:%d\n", len(item.ValidVisitAfterHourMap))
text += fmt.Sprintf("注册1天后访问:%d\n", len(item.ValidVisitAfterDayMap))
text += fmt.Sprintf("注册2天后访问:%d\n", len(item.ValidVisitAfterTowDayMap))
text += fmt.Sprintf("充值次数:%d\n", item.RechargeCount)
text += fmt.Sprintf("充值用户:%d\n", len(item.RegisterUserMap))
text += fmt.Sprintf("充值钱数:%d元\n", item.RechargeAmount/100)
text += fmt.Sprintf("游戏充值:%d\n", item.GameRecharge/100)
text += fmt.Sprintf("游戏提现:%d\n", item.GameWithdraw/100)
text += "=====IP注册数前10名=====\n"
ranks := item.IpRank(int(top))
for _, rank := range ranks {
text += fmt.Sprintf("注册:%d, IP:%s\n", rank.Count, rank.Ip)
}
return cmd.SendReply(text, nil)
}
func HandleGameReportCmd(ctx context.Context, cmd *tgbot.Command) error {
appMap, err := appInfoMod.GetAllAppIdNameMap(ctx)
if err != nil {
return err
}
if _, ok := appMap[888]; !ok {
return errors.New("本服务器没有游戏统计")
}
back, err := cmd.GetFlagInt32Value("-back")
if err != nil {
return tgbot.NewUserError(cmd, "请输入回退天数,格式:\n"+cmd.Help+"\n"+cmd.Desc)
}
if back > 0 {
back *= -1
}
fileName, err := GetGameReportFile(ctx, time.Now().AddDate(0, 0, int(back)))
if err != nil {
return err
}
cmd.FilePath = fileName
return cmd.SendReply("游戏统计报表文件", nil)
}
func HandleAutoTaskStatusCmd(ctx context.Context, cmd *tgbot.Command) error {
if cmd.HasFlag("-file") {
content := statutils.ServerMon.ToJson()
filePath := fmt.Sprintf("/tmp/%s.json", utils.UUID())
err := os.WriteFile(filePath, []byte(content), 0666)
if err != nil {
return err
}
cmd.FilePath = filePath
return cmd.SendReply("自动任务数据", nil)
} else {
content := statutils.ServerMon.Display()
return cmd.SendReply(content, nil)
}
}
func HandleCloseCmd(ctx context.Context, cmd *tgbot.Command) error {
return cmd.SendDelete()
}
func HandleAppListCmd(ctx context.Context, cmd *tgbot.Command) error {
list, err := appInfoMod.GetAllAppInfo(ctx)
if err != nil {
return err
}
allIds := make([]int32, 0, len(list))
dbApps := make(map[int32]*appInfoMod.AppInfo)
for _, app := range list {
_app := app
dbApps[app.AppId] = &_app
allIds = append(allIds, app.AppId)
}
endpoints := statg.GlobalEndpoints.GetIndexEndpoints(statg.Conf.ServerIndex)
for _, endpoint := range endpoints {
if _, exists := dbApps[endpoint.AppId]; !exists {
allIds = append(allIds, endpoint.AppId)
}
}
text := fmt.Sprintf("应用列表:【%s】\n", time.Now().Format("2006-01-02T15:04:05"))
for _, id := range allIds {
end := endpoints.GetEndpointByAppId(id)
app := dbApps[id]
if end != nil && app != nil {
text += fmt.Sprintf("【%s】【%s】【%s】【%d】\n", end.NameFull, end.NameShort, end.NameEn, end.AppId)
warns := make([]string, 0)
if end.ServerUrl == "" {
warns = append(warns, "服务器地址没配‼️")
}
if end.NameEn == "" {
warns = append(warns, "名称英文缩写没配‼️")
}
if end.H5Config == "" {
warns = append(warns, "H5Config没配‼️")
}
if end.H5Origin == "" {
warns = append(warns, "H5Origin没配‼️")
}
if end.H5Target == "" {
warns = append(warns, "H5Target没配‼️")
}
if len(warns) > 0 {
text += fmt.Sprintf("【%s】", strings.Join(warns, ";"))
}
} else if app == nil && end != nil {
text += fmt.Sprintf("【%s】【%s】【%s】【%d】数据库未添加该APP‼️\n", end.NameFull, end.NameShort, end.NameEn, end.AppId)
} else if app != nil && end == nil {
text += fmt.Sprintf("【%s】【%d】数据库存在该APP,但是没有配置文件没有‼️\n", app.Name, app.AppId)
}
}
return cmd.SendBack(text)
}
func HandleSyncStatusCmd(ctx context.Context, cmd *tgbot.Command) error {
appIds := cmd.GetFlagInt32Slice("-app")
if len(appIds) <= 0 {
return DisplayAppButtons(ctx, cmd)
}
appNames, err := appInfoMod.GetAllAppIdNameMap(ctx)
if err != nil {
return err
}
isDetail := slices.Contains(cmd.Params, "-detail")
var lastAppId int32
text := fmt.Sprintf("同步状态:%s\n", time.Now().Format("2006-01-02T15:04:05"))
statutils.GlobalSyncMonitor.Traverse(func(item *statutils.SyncItem) {
if len(appIds) > 0 && !slices.Contains(appIds, item.AppID) {
return
}
if item.AppID != lastAppId {
lastAppId = item.AppID
text += fmt.Sprintf("\U0001F7E2【%d:%s】\n", item.AppID, appNames[item.AppID])
}
text += fmt.Sprintf("1️⃣描述【%s】\n", item.JobDesc)
if isDetail {
text += fmt.Sprintf("2️⃣名称【%s】\n", item.JobName)
text += fmt.Sprintf("3️⃣开始【%s】\n", item.JobBegin().Format("2006-01-02T15:04:05"))
if item.JobEnd().Before(item.JobBegin()) { // 未完成
text += fmt.Sprintf("4️⃣用时【%s】\n", fmt.Sprintf("%s", time.Now().Sub(item.JobBegin())))
text += fmt.Sprintf("5️⃣结束【正在执行...】\n")
} else {
text += fmt.Sprintf("4️⃣用时【%s】\n", fmt.Sprintf("%s", item.JobEnd().Sub(item.JobBegin())))
text += fmt.Sprintf("5️⃣结束【%s】\n", item.JobEnd().Format("2006-01-02T15:04:05"))
}
text += fmt.Sprintf("6️⃣当前【%d条】\n", item.CurrentCount)
text += fmt.Sprintf("7️⃣当日【%d条】\n", item.TodayCount())
if !item.RequestBegin.Before(item.JobBegin()) {
text += fmt.Sprintf("ℹ请求开始【%s】\n", item.RequestBegin.Format("2006-01-02T15:04:05"))
text += fmt.Sprintf("ℹ请求用时【%s】\n", fmt.Sprintf("%s", item.RequestEnd.Sub(item.RequestBegin)))
text += fmt.Sprintf("ℹ请求结束【%s】\n", item.RequestEnd.Format("2006-01-02T15:04:05"))
}
if !item.ProcessBegin.Before(item.JobBegin()) {
text += fmt.Sprintf("ℹ处理开始【%s】\n", item.ProcessBegin.Format("2006-01-02T15:04:05"))
text += fmt.Sprintf("ℹ处理用时【%s】\n", fmt.Sprintf("%s", item.ProcessEnd.Sub(item.ProcessBegin)))
text += fmt.Sprintf("ℹ处理结束【%s】\n", item.ProcessEnd.Format("2006-01-02T15:04:05"))
}
} else {
text += fmt.Sprintf("2️⃣名称【%s】\n", item.JobName)
text += fmt.Sprintf("3️⃣开始【%s】\n", item.JobBegin().Format("2006-01-02T15:04:05"))
if item.JobEnd().Before(item.JobBegin()) { // 未完成
text += fmt.Sprintf("4️⃣用时【%s】\n", fmt.Sprintf("%s", time.Now().Sub(item.JobBegin())))
text += fmt.Sprintf("5️⃣结束【正在执行...】\n")
} else {
text += fmt.Sprintf("4️⃣用时【%s】\n", fmt.Sprintf("%s", item.JobEnd().Sub(item.JobBegin())))
text += fmt.Sprintf("5️⃣结束【%s】\n", item.JobEnd().Format("2006-01-02T15:04:05"))
}
text += fmt.Sprintf("6️⃣当前【%d条】\n", item.CurrentCount)
text += fmt.Sprintf("7️⃣当日【%d条】\n", item.TodayCount())
}
if item.Error != nil {
text += fmt.Sprintf("🛑错误【%s】\n", item.Error.Error())
}
text += fmt.Sprintf("\n")
})
return cmd.SendBack(text)
}
func HandleTodayStatCmd(ctx context.Context, cmd *tgbot.Command) error {
appIds := cmd.GetFlagInt32Slice("-app")
if len(appIds) <= 0 {
return DisplayAppButtons(ctx, cmd)
}
back, _ := cmd.GetFlagInt32Value("-back")
return DailyStat(ctx, cmd, int(back))
}
func HandleYesterdayStatCmd(ctx context.Context, cmd *tgbot.Command) error {
appIds := cmd.GetFlagInt32Slice("-app")
if len(appIds) <= 0 {
return DisplayAppButtons(ctx, cmd)
}
return DailyStat(ctx, cmd, -1)
}
func HandleDailyStatCmd(ctx context.Context, cmd *tgbot.Command) error {
appIds := cmd.GetFlagInt32Slice("-app")
if len(appIds) <= 0 {
return DisplayAppButtons(ctx, cmd)
}
back, err := cmd.GetFlagInt32Value("-back")
if err != nil {
return tgbot.NewUserError(cmd, "请输入回退天数,格式:\n"+cmd.Help+"\n"+cmd.Desc)
}
return DailyStat(ctx, cmd, int(back))
}
func HandleDailyHourStatCmd(ctx context.Context, cmd *tgbot.Command) error {
appIds := cmd.GetFlagInt32Slice("-app")
if len(appIds) <= 0 {
return DisplayAppButtons(ctx, cmd)
}
project := cmd.GetFlagStringValue("-stat")
if project == "" {
return DisplayStatProjectButtons(ctx, cmd)
}
back, err := cmd.GetFlagInt32Value("-back")
if err != nil {
return tgbot.NewUserError(cmd, "请输入回退天数,格式:\n"+cmd.Help+"\n"+cmd.Desc)
}
return DailyHourStat(ctx, cmd, int(back))
}
func HandleHourStatCmd(ctx context.Context, cmd *tgbot.Command) error {
appIds := cmd.GetFlagInt32Slice("-app")
if len(appIds) <= 0 {
return DisplayAppButtons(ctx, cmd)
}
back, err := cmd.GetFlagInt32Value("-back")
if err != nil {
return tgbot.NewUserError(cmd, "请输入回退小时数,格式:\n"+cmd.Help+"\n"+cmd.Desc)
}
return HourStat(ctx, cmd, int(back))
}
func DailyStat(ctx context.Context, cmd *tgbot.Command, moveBackDays int) error {
appId, err := cmd.GetFlagInt32Value("-app")
if err != nil {
return tgbot.NewUserError(cmd, "请输入应用ID,格式:\n"+cmd.Help+"\n"+cmd.Desc)
}
filter := bson.M{}
appMap, err := appInfoMod.GetAllAppIdMap(ctx, filter)
if err != nil {
return tgbot.NewUserError(cmd, "服务器错误:"+err.Error())
}
appInfo := appMap[appId]
if appInfo == nil {
return tgbot.NewUserError(cmd, fmt.Sprintf("应用ID【%d】不存在", appId))
}
if moveBackDays > 0 {
moveBackDays = moveBackDays * -1
}
reportTime := utils.RecentMinute(time.Now(), 60)
if moveBackDays != 0 {
reportTime = utils.BeginWithTime(time.Now().AddDate(0, 0, moveBackDays+1))
}
reportFilter := bson.M{
botreport.FN.BotReport.AppId: appId,
botreport.FN.BotReport.SumDate: reportTime,
}
report, err := botdailyhourstat.FindOneBotHourStatEx(ctx, reportFilter)
if err != nil {
if err == mongo.ErrNoDocuments {
report, err = BotDailyHourStat(ctx, appId, reportTime, false)
if err != nil {
return tgbot.NewUserError(cmd, "服务器错误:"+err.Error())
}
} else {
return tgbot.NewUserError(cmd, "服务器错误:"+err.Error())
}
}
HistoryFilter := bson.M{
botreport.FN.BotReport.AppId: appId,
botreport.FN.BotReport.SumDate: reportTime.AddDate(0, 0, -1),
}
historyReport, err := botdailyhourstat.FindOneBotHourStatEx(ctx, HistoryFilter)
if err != nil {
if err == mongo.ErrNoDocuments {
historyReport, err = BotDailyHourStat(ctx, appId, reportTime.AddDate(0, 0, -1), true)
if err != nil {
return tgbot.NewUserError(cmd, "服务器错误:"+err.Error())
}
} else {
return tgbot.NewUserError(cmd, "服务器错误:"+err.Error())
}
}
text, title, desc := report.DisplayDayHourTextWithTitle(appInfo.Name, historyReport)
et := tgbot.NewEntityList(title, desc, []*tgbot.Entity{
{
Content: text,
},
})
return cmd.SendEntity(et)
}
func HourStat(ctx context.Context, cmd *tgbot.Command, moveBackHours int) error {
appId, err := cmd.GetFlagInt32Value("-app")
if err != nil {
return tgbot.NewUserError(cmd, "请输入应用ID,格式:\n"+cmd.Help+"\n"+cmd.Desc)
}
filter := bson.M{}
appMap, err := appInfoMod.GetAllAppIdMap(ctx, filter)
if err != nil {
return tgbot.NewUserError(cmd, "服务器错误:"+err.Error())
}
appInfo := appMap[appId]
if appInfo == nil {
return tgbot.NewUserError(cmd, fmt.Sprintf("应用ID【%d】不存在", appId))
}
if moveBackHours > 0 {
moveBackHours = moveBackHours * -1
}
reportTime := utils.RecentMinute(time.Now(), 60)
if moveBackHours != 0 {
reportTime = reportTime.Add(time.Duration(moveBackHours) * time.Hour)
}
reportFilter := bson.M{
botreport.FN.BotReport.SumDate: reportTime,
botreport.FN.BotReport.AppId: appId,
}
report, err := botreport.FindOneBotReport(ctx, reportFilter)
if err != nil {
return tgbot.NewUserError(cmd, "服务器错误:"+err.Error())
}
if report.SumDate != reportTime {
return tgbot.NewUserError(cmd, "数据记录尚未生成,请过五分钟再试")
}
HistoryFilter := bson.M{
botreport.FN.BotReport.SumDate: reportTime.AddDate(0, 0, -1),
botreport.FN.BotReport.AppId: appId,
}
historyReport, err := botreport.FindOneBotReport(ctx, HistoryFilter)
if err != nil {
return tgbot.NewUserError(cmd, "服务器错误:"+err.Error())
}
text := DisplayHourStat(report, historyReport, appInfo, GetHourDesc(moveBackHours))
return cmd.SendBack(text)
}
func DailyHourStat(ctx context.Context, cmd *tgbot.Command, moveBackDays int) error {
appId, err := cmd.GetFlagInt32Value("-app")
if err != nil {
return tgbot.NewUserError(cmd, "请输入应用ID,格式:\n"+cmd.Help+"\n"+cmd.Desc)
}
statName := cmd.GetFlagStringValue("-stat")
if statName == "" {
return tgbot.NewUserError(cmd, "请输入统计项目的细分名称:\n"+cmd.Help+"\n"+cmd.Desc)
}
filter := bson.M{}
appMap, err := appInfoMod.GetAllAppIdMap(ctx, filter)
if err != nil {
return tgbot.NewUserError(cmd, "服务器错误:"+err.Error())
}
appInfo := appMap[appId]
if appInfo == nil {
return tgbot.NewUserError(cmd, fmt.Sprintf("应用ID【%d】不存在", appId))
}
if moveBackDays > 0 {
moveBackDays = moveBackDays * -1
}
reportTime := utils.RecentMinute(time.Now(), 60)
if moveBackDays != 0 {
reportTime = reportTime.AddDate(0, 0, moveBackDays)
}
reportFilter := bson.M{
botreport.FN.BotReport.AppId: appId,
botreport.FN.BotReport.SumDate: reportTime,
}
report, err := botdailyhourstat.FindOneBotHourStatEx(ctx, reportFilter)
if err != nil {
if err == mongo.ErrNoDocuments {
return tgbot.NewUserError(cmd, "统计记录尚未生成,请过5分钟再试")
}
return tgbot.NewUserError(cmd, "服务器错误:"+err.Error())
}
HistoryFilter := bson.M{
botreport.FN.BotReport.AppId: appId,
botreport.FN.BotReport.SumDate: reportTime.AddDate(0, 0, -1),
}
historyReport, _ := botdailyhourstat.FindOneBotHourStatEx(ctx, HistoryFilter)
photo := fmt.Sprintf("/tmp/%s.png", utils.UUID())
var text string
title := fmt.Sprintf("%s%s%s", appInfo.Name, GetDateDesc(moveBackDays), statutils.StatProjectNameDict[statName])
text, err = report.GenerateTodayTable(title, statName, photo, historyReport)
if err != nil {
return err
}
cmd.PhotoPath = photo
return cmd.SendBack(text)
}
func HandleDistrictDataCmd(ctx context.Context, cmd *tgbot.Command) error {
lanpageinfo := monitor.RunLandPageMonitor()
cmd.SendReply(lanpageinfo, nil)
channelMsg := monitor.ChannelMonitor()
cmd.SendReply(channelMsg, nil)
return nil
}
func HandleCrossDayStatCmd(ctx context.Context, cmd *tgbot.Command) error {
project := cmd.GetFlagStringValue("-stat")
if project == "" {
return DisplayStatProjectButtons(ctx, cmd)
}
if !cmd.HasFlag("-back") {
return tgbot.NewUserError(cmd, "请输入回退天数,格式:\n"+cmd.Help+"\n"+cmd.Desc)
}
if cmd.HasFlag("-stat") {
sType := cmd.GetFlagStringValue("-stat")
statMethod, ok := crossSer.CrossStatMethodMap[sType]
if !ok {
return tgbot.NewUserError(cmd, fmt.Sprintf("-stat=%s,没有注册对应的统计方法", sType))
}
return statMethod(ctx, cmd)
} else {
return tgbot.NewUserError(cmd, "-stat 没有填写, 格式:\n"+cmd.Help+"\n"+cmd.Desc)
}
}
func HandleCrossMonthStatCmd(ctx context.Context, cmd *tgbot.Command) error {
if statg.Conf.ServerIndex.IsMadou() {
monthStr := cmd.GetFlagStringValue("-month")
if monthStr == "" {
return tgbot.NewUserError(cmd, "请输入统计月份,格式:\n"+cmd.Help+"\n"+cmd.Desc)
}
return crossSer.CrossMonthStat(ctx, cmd)
}
return nil
}
func HandleVersionCmd(ctx context.Context, cmd *tgbot.Command) error {
text := GetServerInformation()
return cmd.SendReply(text, nil)
}
func HandleRchssendCmd(ctx context.Context, cmd *tgbot.Command) error {
err := innerserverser.InnerServerDailyStat(ctx)
if err != nil {
fmt.Println("HandleRchssendCmd" + err.Error())
return tgbot.NewUserError(cmd, "HandleRchssendCmd:"+err.Error())
}
return nil
}
func HandleRchsMonthBotsendCmd(ctx context.Context, cmd *tgbot.Command) error {
err := innerserverser.InnerServerMonthStat2(ctx, nil, nil, cmd)
if err != nil {
fmt.Println("HandleRchsbotsendCmd" + err.Error())
return tgbot.NewUserError(cmd, "HandleRchsbotsendCmd:"+err.Error())
}
return nil
}
func HandleRchsMonthBotDatetodatesendCmd(ctx context.Context, cmd *tgbot.Command) error {
err := innerserverser.InnerServerBotDateMonthStat(ctx, cmd)
if err != nil {
fmt.Println("HandleRchsbotsendCmd" + err.Error())
return tgbot.NewUserError(cmd, "HandleRchsbotsendCmd:"+err.Error())
}
return nil
}
func HandleRchsDateBotsendCmd(ctx context.Context, cmd *tgbot.Command) error {
err := innerserverser.InnerServerBotDateDayStat(ctx, cmd)
if err != nil {
fmt.Println("HandleRchsbotsendCmd" + err.Error())
return tgbot.NewUserError(cmd, "HandleRchsbotsendCmd:"+err.Error())
}
return nil
}
+199
View File
@@ -0,0 +1,199 @@
package botser
import (
"fmt"
"stat/models/appInfoMod"
"stat/models/botreport"
"stat/models/rechargemod"
"stat/models/syncrecharge"
)
func GetDateDesc(moveBackDays int) string {
if moveBackDays < 0 {
moveBackDays = moveBackDays * -1
}
switch moveBackDays {
case 0:
return "今天"
case 1:
return "昨天"
case 2:
return "前天"
default:
return fmt.Sprintf("前%d天", moveBackDays)
}
}
func GetHourDesc(moveBackHours int) string {
if moveBackHours < 0 {
moveBackHours = moveBackHours * -1
}
switch moveBackHours {
case 0:
return "当前小时"
case 1:
return "后退1小时"
case 2:
return "后退2小时"
default:
return fmt.Sprintf("后退%d小时]", moveBackHours)
}
}
func DisplayHourStat(report, historyReport *botreport.BotReport, appInfo *appInfoMod.AppInfo, dateDesc string) string {
text := fmt.Sprintf("应用名称:%s\n", appInfo.Name)
text += fmt.Sprintf("统计名称:%s数据\n", dateDesc)
text += fmt.Sprintf("统计时间:%s\n", report.SumDate.Format("2006-01-02T15:04:05"))
text += fmt.Sprintf("1️⃣%s充值统计:\n", dateDesc)
text += Line("总计:%s", report.HourRecharge/100, float64(report.HourRecharge-historyReport.HourRecharge), float64(historyReport.HourRecharge))
if appInfo.AppId == 888 {
text += Line("官网:%s", report.HourWebRecharge/100, float64(report.HourWebRecharge-historyReport.HourWebRecharge), float64(historyReport.HourWebRecharge))
}
text += Line("自来:%s", report.HourNormalRecharge/100, float64(report.HourNormalRecharge-historyReport.HourNormalRecharge), float64(historyReport.HourNormalRecharge))
text += Line("渠道:%s", report.HourDistrictRecharge/100, float64(report.HourDistrictRecharge-historyReport.HourDistrictRecharge), float64(historyReport.HourDistrictRecharge))
text += Line("┣会员:%s", report.HourDistrictVip/100, float64(report.HourDistrictVip-historyReport.HourDistrictVip), float64(historyReport.HourDistrictVip))
text += Line("┗轻量:%s", report.HourH5DistrictRecharge/100, float64(report.HourH5DistrictRecharge-historyReport.HourH5DistrictRecharge), float64(historyReport.HourH5DistrictRecharge))
text += Line("安卓:%s", report.HourAndroidRecharge/100, float64(report.HourAndroidRecharge-historyReport.HourAndroidRecharge), float64(historyReport.HourAndroidRecharge))
text += Line("苹果:%s", report.HourIosRecharge/100, float64(report.HourIosRecharge-historyReport.HourIosRecharge), float64(historyReport.HourIosRecharge))
text += Line("轻量:%s", report.HourH5Recharge/100, float64(report.HourH5Recharge-historyReport.HourH5Recharge), float64(historyReport.HourH5Recharge))
text += Line("┣苹果:%s", report.HourH5IosRecharge/100, float64(report.HourH5IosRecharge-historyReport.HourH5IosRecharge), float64(historyReport.HourH5IosRecharge))
text += Line("┣安卓:%s", report.HourH5AndroidRecharge/100, float64(report.HourH5AndroidRecharge-historyReport.HourH5AndroidRecharge), float64(historyReport.HourH5AndroidRecharge))
text += Line("┗电脑:%s", report.HourH5PcRecharge/100, float64(report.HourH5PcRecharge-historyReport.HourH5PcRecharge), float64(historyReport.HourH5PcRecharge))
text += Line("金币:%s", (report.HourRecharge-report.HourVipRecharge)/100, float64(report.HourRecharge-report.HourVipRecharge-(historyReport.HourRecharge-historyReport.HourVipRecharge)), float64(historyReport.HourRecharge-historyReport.HourVipRecharge))
text += Line("会员:%s", (report.HourVipRecharge)/100, float64(report.HourVipRecharge-historyReport.HourVipRecharge), float64(historyReport.HourVipRecharge))
if appInfo.AppId == 888 {
text += Line("┣官网:%s", report.HourWebVip/100, float64(report.HourWebVip-historyReport.HourWebVip), float64(historyReport.HourWebVip))
}
text += Line("┣自来:%s", report.HourNormalVip/100, float64(report.HourNormalVip-historyReport.HourNormalVip), float64(historyReport.HourNormalVip))
text += Line("┗渠道:%s", report.HourDistrictVip/100, float64(report.HourDistrictVip-historyReport.HourDistrictVip), float64(historyReport.HourDistrictVip))
text += Line("老用户:%s", report.HourOldRecharge/100, float64(report.HourOldRecharge-historyReport.HourOldRecharge), float64(historyReport.HourOldRecharge))
text += Line("新用户:%s", report.HourNewRecharge/100, float64(report.HourNewRecharge-historyReport.HourNewRecharge), float64(historyReport.HourNewRecharge))
text += Line("┣苹果:%s", report.HourIosNewRecharge/100, float64(report.HourIosNewRecharge-historyReport.HourIosNewRecharge), float64(historyReport.HourIosNewRecharge))
text += Line("┣安卓:%s", report.HourAndroidNewRecharge/100, float64(report.HourAndroidNewRecharge-historyReport.HourAndroidNewRecharge), float64(historyReport.HourAndroidNewRecharge))
text += Line("┗轻量:%s", report.HourH5NewRecharge/100, float64(report.HourH5NewRecharge-historyReport.HourH5NewRecharge), float64(historyReport.HourH5NewRecharge))
text += Line(" ┣苹果:%s", report.HourH5IosNewRecharge/100, float64(report.HourH5IosNewRecharge-historyReport.HourH5IosNewRecharge), float64(historyReport.HourH5IosNewRecharge))
text += Line(" ┣安卓:%s", report.HourH5AndroidNewRecharge/100, float64(report.HourH5AndroidNewRecharge-historyReport.HourH5AndroidNewRecharge), float64(historyReport.HourH5AndroidNewRecharge))
text += Line(" ┗电脑:%s", report.HourH5PcNewRecharge/100, float64(report.HourH5PcNewRecharge-historyReport.HourH5PcNewRecharge), float64(historyReport.HourH5PcNewRecharge))
text += fmt.Sprintf("2️⃣%s订单统计:\n", dateDesc)
text += Line("总数:%s: %s", report.HourOrder, float64(report.HourOrder-historyReport.HourOrder), float64(historyReport.HourOrder))
text += Line("成功:%s", report.HourSuccessOrder, float64(report.HourSuccessOrder-historyReport.HourSuccessOrder), float64(historyReport.HourSuccessOrder))
text += Rate("成功率:%s", float64(report.HourSuccessOrder), float64(report.HourOrder))
text += fmt.Sprintf("3️⃣%s注册用户:\n", dateDesc)
text += Line("总计:%s", report.HourNew, float64(report.HourNew-historyReport.HourNew), float64(historyReport.HourNew))
if appInfo.AppId == 888 {
text += Line("官网:%s", report.HourNewWeb, float64(report.HourNewWeb-historyReport.HourNewWeb), float64(historyReport.HourNewWeb))
}
text += Line("自来:%s", report.HourNewNormal, float64(report.HourNewNormal-historyReport.HourNewNormal), float64(historyReport.HourNewNormal))
text += Line("渠道:%s", report.HourNewDistrict, float64(report.HourNewDistrict-historyReport.HourNewDistrict), float64(historyReport.HourNewDistrict))
text += Line("安卓:%s", report.HourAndroidNew, float64(report.HourAndroidNew-historyReport.HourAndroidNew), float64(historyReport.HourAndroidNew))
text += Line("苹果:%s", report.HourIosNew, float64(report.HourIosNew-historyReport.HourIosNew), float64(historyReport.HourIosNew))
text += Line("轻量:%s", report.HourH5New, float64(report.HourH5New-historyReport.HourH5New), float64(historyReport.HourH5New))
text += Line("┣苹果:%s", report.HourH5IosNew, float64(report.HourH5IosNew-historyReport.HourH5IosNew), float64(historyReport.HourH5IosNew))
text += Line("┣安卓:%s", report.HourH5AndroidNew, float64(report.HourH5AndroidNew-historyReport.HourH5AndroidNew), float64(historyReport.HourH5AndroidNew))
text += Line("┗电脑:%s", report.HourH5PcNew, float64(report.HourH5PcNew-historyReport.HourH5PcNew), float64(historyReport.HourH5PcNew))
return text
}
func DisplayDailyStat(report, historyReport *botreport.BotReport, appInfo *appInfoMod.AppInfo, dateDesc string) string {
text := fmt.Sprintf("应用名称:%s\n", appInfo.Name)
text += fmt.Sprintf("统计名称:%s数据\n", dateDesc)
text += fmt.Sprintf("统计时间:%s\n", report.SumDate.Format("2006-01-02T15:04:05"))
text += fmt.Sprintf("1️⃣%s充值统计:\n", dateDesc)
text += Line("总计:%s", report.TodayRecharge/100, float64(report.TodayRecharge-historyReport.TodayRecharge), float64(historyReport.TodayRecharge))
if appInfo.AppId == 888 {
text += Line("官网:%s", report.TodayWebRecharge/100, float64(report.TodayWebRecharge-historyReport.TodayWebRecharge), float64(historyReport.TodayWebRecharge))
}
text += Line("自来:%s", report.TodayNormalRecharge/100, float64(report.TodayNormalRecharge-historyReport.TodayNormalRecharge), float64(historyReport.TodayNormalRecharge))
text += Line("渠道:%s", report.TodayDistrictRecharge/100, float64(report.TodayDistrictRecharge-historyReport.TodayDistrictRecharge), float64(historyReport.TodayDistrictRecharge))
text += Line("┣会员:%s", report.TodayDistrictVip/100, float64(report.TodayDistrictVip-historyReport.TodayDistrictVip), float64(historyReport.TodayDistrictVip))
text += Line("┗轻量:%s", report.TodayH5DistrictRecharge/100, float64(report.TodayH5DistrictRecharge-historyReport.TodayH5DistrictRecharge), float64(historyReport.TodayH5DistrictRecharge))
text += Line("安卓:%s", report.TodayAndroidRecharge/100, float64(report.TodayAndroidRecharge-historyReport.TodayAndroidRecharge), float64(historyReport.TodayAndroidRecharge))
text += Line("苹果:%s", report.TodayIosRecharge/100, float64(report.TodayIosRecharge-historyReport.TodayIosRecharge), float64(historyReport.TodayIosRecharge))
text += Line("轻量:%s", report.TodayH5Recharge/100, float64(report.TodayH5Recharge-historyReport.TodayH5Recharge), float64(historyReport.TodayH5Recharge))
text += Line("┣苹果:%s", report.TodayH5IosRecharge/100, float64(report.TodayH5IosRecharge-historyReport.TodayH5IosRecharge), float64(historyReport.TodayH5IosRecharge))
text += Line("┣安卓:%s", report.TodayH5AndroidRecharge/100, float64(report.TodayH5AndroidRecharge-historyReport.TodayH5AndroidRecharge), float64(historyReport.TodayH5AndroidRecharge))
text += Line("┗电脑:%s", report.TodayH5PcRecharge/100, float64(report.TodayH5PcRecharge-historyReport.TodayH5PcRecharge), float64(historyReport.TodayH5PcRecharge))
text += Line("金币:%s", (report.TodayRecharge-report.TodayVipRecharge)/100, float64(report.TodayRecharge-report.TodayVipRecharge-(historyReport.TodayRecharge-historyReport.TodayVipRecharge)), float64(historyReport.TodayRecharge-historyReport.TodayVipRecharge))
text += Line("会员:%s", report.TodayVipRecharge/100, float64(report.TodayVipRecharge-historyReport.TodayVipRecharge), float64(historyReport.TodayVipRecharge))
if appInfo.AppId == 888 {
text += Line("┣官网:%s", report.TodayWebVip/100, float64(report.TodayWebVip-historyReport.TodayWebVip), float64(historyReport.TodayWebVip))
}
text += Line("┣自来:%s", report.TodayNormalVip/100, float64(report.TodayNormalVip-historyReport.TodayNormalVip), float64(historyReport.TodayNormalVip))
text += Line("┗渠道:%s", report.TodayDistrictVip/100, float64(report.TodayDistrictVip-historyReport.TodayDistrictVip), float64(historyReport.TodayDistrictVip))
text += Line("老用户:%s", report.TodayOldRecharge/100, float64(report.TodayOldRecharge-historyReport.TodayOldRecharge), float64(historyReport.TodayOldRecharge))
text += Line("新用户:%s", report.TodayNewRecharge/100, float64(report.TodayNewRecharge-historyReport.TodayNewRecharge), float64(historyReport.TodayNewRecharge))
text += Line("┣苹果:%s", report.TodayIosNewRecharge/100, float64(report.TodayIosNewRecharge-historyReport.TodayIosNewRecharge), float64(historyReport.TodayIosNewRecharge))
text += Line("┣安卓:%s", report.TodayAndroidNewRecharge/100, float64(report.TodayAndroidNewRecharge-historyReport.TodayAndroidNewRecharge), float64(historyReport.TodayAndroidNewRecharge))
text += Line("┗轻量:%s", report.TodayH5NewRecharge/100, float64(report.TodayH5NewRecharge-historyReport.TodayH5NewRecharge), float64(historyReport.TodayH5NewRecharge))
text += Line(" ┣苹果:%s", report.TodayH5IosNewRecharge/100, float64(report.TodayH5IosNewRecharge-historyReport.TodayH5IosNewRecharge), float64(historyReport.TodayH5IosNewRecharge))
text += Line(" ┣安卓:%s", report.TodayH5AndroidNewRecharge/100, float64(report.TodayH5AndroidNewRecharge-historyReport.TodayH5AndroidNewRecharge), float64(historyReport.TodayH5AndroidNewRecharge))
text += Line(" ┗电脑:%s", report.TodayH5PcNewRecharge/100, float64(report.TodayH5PcNewRecharge-historyReport.TodayH5PcNewRecharge), float64(historyReport.TodayH5PcNewRecharge))
text += fmt.Sprintf("2️⃣%s订单统计:\n", dateDesc)
text += Line("订单总数:%s", report.TodayOrder, float64(report.TodayOrder-historyReport.TodayOrder), float64(historyReport.TodayOrder))
text += Line("成功订单:%s", report.TodaySuccessOrder, float64(report.TodaySuccessOrder-historyReport.TodaySuccessOrder), float64(historyReport.TodaySuccessOrder))
text += Rate("成功率: %s", float64(report.TodaySuccessOrder), float64(report.TodayOrder))
text += fmt.Sprintf("3️⃣%s访问统计:\n", dateDesc)
text += Line("累计:%s", report.AccessUser, float64(report.AccessUser-historyReport.AccessUser), float64(historyReport.AccessUser))
text += Line("渠道:%s", report.TodayAccessDistrict, float64(report.TodayAccessDistrict-historyReport.TodayAccessDistrict), float64(historyReport.TodayAccessDistrict))
text += Line("┗轻量:%s", report.TodayAccessH5District, float64(report.TodayAccessH5District-historyReport.TodayAccessH5District), float64(historyReport.TodayAccessH5District))
text += Line("安卓:%s", report.TodayAccessAndroid, float64(report.TodayAccessAndroid-historyReport.TodayAccessAndroid), float64(historyReport.TodayAccessAndroid))
text += Line("苹果:%s", report.TodayAccessIos, float64(report.TodayAccessIos-historyReport.TodayAccessIos), float64(historyReport.TodayAccessIos))
text += Line("轻量:%s", report.TodayAccessH5, float64(report.TodayAccessH5-historyReport.TodayAccessH5), float64(historyReport.TodayAccessH5))
text += Line("┣苹果:%s", report.TodayAccessH5Ios, float64(report.TodayAccessH5Ios-historyReport.TodayAccessH5Ios), float64(historyReport.TodayAccessH5Ios))
text += Line("┣安卓:%s", report.TodayAccessH5Android, float64(report.TodayAccessH5Android-historyReport.TodayAccessH5Android), float64(historyReport.TodayAccessH5Android))
text += Line("┗电脑:%s", report.TodayAccessH5Pc, float64(report.TodayAccessH5Pc-historyReport.TodayAccessH5Pc), float64(historyReport.TodayAccessH5Pc))
text += fmt.Sprintf("4️⃣%s注册用户:\n", dateDesc)
text += Line("总计:%s", report.TodayNew, float64(report.TodayNew-historyReport.TodayNew), float64(historyReport.TodayNew))
if appInfo.AppId == 888 {
text += Line("官网:%s", report.TodayNewWeb, float64(report.TodayNewWeb-historyReport.TodayNewWeb), float64(historyReport.TodayNewWeb))
}
text += Line("自来:%s", report.TodayNewNormal, float64(report.TodayNewNormal-historyReport.TodayNewNormal), float64(historyReport.TodayNewNormal))
text += Line("渠道:%s", report.TodayNewDistrict, float64(report.TodayNewDistrict-historyReport.TodayNewDistrict), float64(historyReport.TodayNewDistrict))
text += Line("苹果:%s", report.TodayIosNew, float64(report.TodayIosNew-historyReport.TodayIosNew), float64(historyReport.TodayIosNew))
text += Line("安卓:%s", report.TodayAndroidNew, float64(report.TodayAndroidNew-historyReport.TodayAndroidNew), float64(historyReport.TodayAndroidNew))
text += Line("轻量:%s", report.TodayH5New, float64(report.TodayH5New-historyReport.TodayH5New), float64(historyReport.TodayH5New))
text += Line("┣苹果:%s", report.TodayH5IosNew, float64(report.TodayH5IosNew-historyReport.TodayH5IosNew), float64(historyReport.TodayH5IosNew))
text += Line("┣安卓:%s", report.TodayH5AndroidNew, float64(report.TodayH5AndroidNew-historyReport.TodayH5AndroidNew), float64(historyReport.TodayH5AndroidNew))
text += Line("┗电脑:%s", report.TodayH5PcNew, float64(report.TodayH5PcNew-historyReport.TodayH5PcNew), float64(historyReport.TodayH5PcNew))
if appInfo.AppId == 888 {
text += fmt.Sprintf("5️⃣%s游戏统计:\n", dateDesc)
text += fmt.Sprintf("游戏充值: %d\n", report.GameRchgAmount)
text += fmt.Sprintf("代充充值: %d\n", report.GameDCRchgAmount)
text += fmt.Sprintf("有效投注: %d\n", report.ValidBet)
text += fmt.Sprintf("系统盈利: %d\n", report.Profit)
text += fmt.Sprintf("提现金额: %d\n", report.GameWtdrRealAmount)
text += fmt.Sprintf("游戏上分: %d\n", report.GameIncAmount)
text += fmt.Sprintf("游戏下分: %d\n", report.GameDecAmount)
}
return text
}
func OrderFromRecharge(recharge *rechargemod.RechargeMod) *syncrecharge.SyncRechargeOrder {
order := &syncrecharge.SyncRechargeOrder{
ID: recharge.ID,
PlatformId: recharge.PlatformId,
UserId: recharge.UserId,
AppId: recharge.AppId,
SysType: recharge.SysType,
DevType: recharge.DevType,
ChannelName: recharge.ChannelName,
CID: recharge.CID,
Type: recharge.Type,
OrderId: recharge.OrderId,
Money: recharge.Money,
PayMoney: recharge.PayMoney,
Status: recharge.Status,
SuccessAt: recharge.SuccessAt,
BlocDeduction: recharge.BlocDeduction,
PromCode: recharge.PromCode,
RegisterTime: recharge.RegisterTime,
IsDirect: recharge.IsDirect,
IsFormalUser: recharge.IsFormalUser,
DistrictCode: recharge.DistrictCode,
Rate: recharge.Rate,
CreatedAt: recharge.CreatedAt,
}
return order
}
+89
View File
@@ -0,0 +1,89 @@
package botser
import (
"fmt"
"stat/common/statutils"
"stat/models/appInfoMod"
"strings"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
)
var MenuKeyboard = tgbotapi.NewInlineKeyboardMarkup(
tgbotapi.NewInlineKeyboardRow(
tgbotapi.NewInlineKeyboardButtonData("今天统计", strings.Join([]string{"/today"}, " ")),
tgbotapi.NewInlineKeyboardButtonData("昨天统计", strings.Join([]string{"/yesterday"}, " ")),
tgbotapi.NewInlineKeyboardButtonData("前天统计", strings.Join([]string{"/daily", "-back=2"}, " ")),
),
tgbotapi.NewInlineKeyboardRow(
tgbotapi.NewInlineKeyboardButtonData("小时统计", strings.Join([]string{"/hour", "-back=0"}, " ")),
tgbotapi.NewInlineKeyboardButtonData("上一小时", strings.Join([]string{"/hour", "-back=1"}, " ")),
tgbotapi.NewInlineKeyboardButtonData("上二小时", strings.Join([]string{"/hour", "-back=2"}, " ")),
),
tgbotapi.NewInlineKeyboardRow(
tgbotapi.NewInlineKeyboardButtonData("今日详细", strings.Join([]string{"/daily_hour", "-back=0"}, " ")),
tgbotapi.NewInlineKeyboardButtonData("昨日详细", strings.Join([]string{"/daily_hour", "-back=1"}, " ")),
tgbotapi.NewInlineKeyboardButtonData("前日详细", strings.Join([]string{"/daily_hour", "-back=2"}, " ")),
),
tgbotapi.NewInlineKeyboardRow(
tgbotapi.NewInlineKeyboardButtonData("今天排行", strings.Join([]string{"/cross_stat", "-back=0"}, " ")),
tgbotapi.NewInlineKeyboardButtonData("昨天排行", strings.Join([]string{"/cross_stat", "-back=1"}, " ")),
tgbotapi.NewInlineKeyboardButtonData("前天排行", strings.Join([]string{"/cross_stat", "-back=2"}, " ")),
),
tgbotapi.NewInlineKeyboardRow(
tgbotapi.NewInlineKeyboardButtonData("应用列表", strings.Join([]string{"/apps"}, " ")),
tgbotapi.NewInlineKeyboardButtonData("同步状态", strings.Join([]string{"/sync"}, " ")),
tgbotapi.NewInlineKeyboardButtonData("关闭菜单", strings.Join([]string{"/close"}, " ")),
),
)
var MenuKeyboard0 = tgbotapi.NewInlineKeyboardMarkup(
tgbotapi.NewInlineKeyboardRow(
tgbotapi.NewInlineKeyboardButtonData("单天前一天旧的 /recharge", " "),
),
tgbotapi.NewInlineKeyboardRow(
tgbotapi.NewInlineKeyboardButtonData("综合统计前一天的 /rechargenew", " "),
),
tgbotapi.NewInlineKeyboardRow(
tgbotapi.NewInlineKeyboardButtonData("综合从第几天到第几天/rechargedate -begin=2024-01-01 -end=2024-01-21 @bot", ""),
),
tgbotapi.NewInlineKeyboardRow(
tgbotapi.NewInlineKeyboardButtonData("综合统计具体年月日/rechargeoneday -date=2024-01-01", ""),
),
)
func GetStatProjectKeyboard(data string) tgbotapi.InlineKeyboardMarkup {
return tgbotapi.NewInlineKeyboardMarkup(
tgbotapi.NewInlineKeyboardRow(
tgbotapi.NewInlineKeyboardButtonData("充值总计", strings.Join([]string{data, fmt.Sprintf("-stat=%s", statutils.StatProjectRecharge)}, " ")),
tgbotapi.NewInlineKeyboardButtonData("老用户充值", strings.Join([]string{data, fmt.Sprintf("-stat=%s", statutils.StatProjectRechargeOld)}, " ")),
tgbotapi.NewInlineKeyboardButtonData("新用户充值", strings.Join([]string{data, fmt.Sprintf("-stat=%s", statutils.StatProjectRechargeNew)}, " ")),
),
tgbotapi.NewInlineKeyboardRow(
tgbotapi.NewInlineKeyboardButtonData("金币充值", strings.Join([]string{data, fmt.Sprintf("-stat=%s", statutils.StatProjectRechargeGold)}, " ")),
tgbotapi.NewInlineKeyboardButtonData("会员购买", strings.Join([]string{data, fmt.Sprintf("-stat=%s", statutils.StatProjectRechargeVip)}, " ")),
tgbotapi.NewInlineKeyboardButtonData("订单统计", strings.Join([]string{data, fmt.Sprintf("-stat=%s", statutils.StatProjectOrder)}, " ")),
),
tgbotapi.NewInlineKeyboardRow(
tgbotapi.NewInlineKeyboardButtonData("注册统计", strings.Join([]string{data, fmt.Sprintf("-stat=%s", statutils.StatProjectRegister)}, " ")),
tgbotapi.NewInlineKeyboardButtonData("访问统计", strings.Join([]string{data, fmt.Sprintf("-stat=%s", statutils.StatProjectVisit)}, " ")),
tgbotapi.NewInlineKeyboardButtonData("关闭菜单", strings.Join([]string{"/close"}, " ")),
),
)
}
func GetAppKeyboard(appList appInfoMod.AppInfoList, data string, column int) tgbotapi.InlineKeyboardMarkup {
rows := make([][]tgbotapi.InlineKeyboardButton, 0)
row := make([]tgbotapi.InlineKeyboardButton, 0)
for _, app := range appList {
row = append(row, tgbotapi.NewInlineKeyboardButtonData(app.Name, strings.Join([]string{data, fmt.Sprintf("-app=%d", app.AppId)}, " ")))
if len(row) >= column {
rows = append(rows, row)
row = make([]tgbotapi.InlineKeyboardButton, 0)
}
}
if len(row) > 0 {
rows = append(rows, row)
}
return tgbotapi.NewInlineKeyboardMarkup(rows...)
}
+116
View File
@@ -0,0 +1,116 @@
package botser
import (
"context"
"fmt"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo/options"
"stat/api/proto"
"stat/common/utils"
"stat/models/appInfoMod"
"stat/models/botdailyhourstat"
"stat/statg"
)
func RecvTgMessage(ctx context.Context, param *proto.SendTgMessageReq) (*proto.SendTgMessageRes, error) {
name := statg.GlobalEndpoints.GetAppShortNameById(param.AppId)
text := fmt.Sprintf("应用【%s:%d】消息:\n%s", name, param.AppId, param.Message)
_ = statg.Bot.SendProjectMessage(text)
return &proto.SendTgMessageRes{}, nil
}
func GetBotDailyHourOne(ctx context.Context, param *proto.BotDailyHourOneReq) (*proto.BotDailyHourOneResp, error) {
var err error
ret := &proto.BotDailyHourOneResp{}
filter := bson.M{}
if !param.Id.IsZero() {
filter["_id"] = param.Id
} else if !param.SumDate.IsZero() && param.AppId > 0 {
filter["sumDate"] = param.SumDate
filter["appId"] = param.AppId
} else {
return nil, fmt.Errorf("param error")
}
if ret.Stat, err = botdailyhourstat.FindOneBotHourStatEx(ctx, filter); err != nil {
return nil, err
}
return ret, nil
}
func GetBotDailyHourList(ctx context.Context, param *proto.BotDailyHourListReq) (*proto.BotDailyHourListResp, error) {
var err error
ret := &proto.BotDailyHourListResp{}
skip, limit := utils.ToDBPage(param.PageNum, param.PageSize)
opts := options.Find().SetSkip(skip).SetLimit(limit)
filter := bson.M{
"sumDate": bson.M{
"$gte": param.SumDateStart,
"$lte": param.SumDateStop,
},
}
if param.AppId != nil {
filter["appId"] = *param.AppId
}
if ret.Total, err = botdailyhourstat.CountBotHourStats(ctx, filter); err != nil {
return nil, err
}
if ret.List, err = botdailyhourstat.FindManyBotHourStat(ctx, filter, opts); err != nil {
return nil, err
}
return ret, nil
}
func GetBotMonthStatList(ctx context.Context, param *proto.BotMonthStatReq) (*proto.BotMonthStatResp, error) {
var err error
ret := &proto.BotMonthStatResp{
List: make(botdailyhourstat.BotHourStatList, 0),
}
appIds := make([]int32, 0)
appMap, _ := appInfoMod.GetAllAppIdNameMap(ctx)
if param.AppId != nil {
if _, exists := appMap[*param.AppId]; exists {
appIds = append(appIds, *param.AppId)
} else {
return ret, nil // 没有这个app,直接返回空列表
}
} else {
for id, _ := range appMap {
appIds = append(appIds, id)
}
}
start := utils.BeginMonthWithTime(param.Month)
end := start.AddDate(0, 1, 0)
start = start.AddDate(0, 0, 1)
for _, appId := range appIds {
base := &botdailyhourstat.BotHourStat{
AppId: appId,
ReportTime: param.Month,
SumDate: param.Month,
TodayGame: &botdailyhourstat.GameItem{},
}
for d := start; !d.After(end); d = d.AddDate(0, 0, 1) {
filter := bson.M{"sumDate": d, "appId": appId}
var stat *botdailyhourstat.BotHourStat
stat, err = botdailyhourstat.FindOneBotHourStat(ctx, filter)
if err != nil {
return nil, err
}
if stat.ID.IsZero() {
if stat, err = BotDailyHourStat(ctx, appId, d, d.Before(utils.BeginOfYesterday())); err != nil {
return nil, err
}
}
base.ACC(stat)
}
ret.List = append(ret.List, base)
}
return ret, nil
}
+91
View File
@@ -0,0 +1,91 @@
package botser
import (
"fmt"
"os"
"stat/common/redis"
"stat/common/utils"
"stat/common/version"
"stat/statg"
"strconv"
"sync"
"time"
)
var (
sysOnce sync.Once
)
func GetServerInformation() string {
text := fmt.Sprintf("统计中心:【%s】编号【%d】:\n", statg.Conf.ServerIndex.Name(), statg.Conf.ServerIndex)
text += fmt.Sprintf("机器名称:【%s】\n", version.HostName())
text += fmt.Sprintf("监听地址:【%s】\n", statg.Conf.Host)
text += fmt.Sprintf("监听端口:【%d】\n", statg.Conf.Port)
text += fmt.Sprintf("服务进程:【%d】\n", os.Getpid())
text += fmt.Sprintf("程序版本:【%s】\n", version.SERVICE_VERSION)
text += fmt.Sprintf("源码分支:【%s】\n", version.GIT_BRANCH)
text += fmt.Sprintf("提交版本:【%s】\n", version.GIT_VERSION)
text += fmt.Sprintf("编译日期:【%s】\n", version.BUILD_DATE)
text += fmt.Sprintf("编译地址:【%s】\n", version.BUILD_IP)
text += fmt.Sprintf("运行模式:【%s】\n", statg.Conf.RunMod)
text += fmt.Sprintf("启动时间:【%s】\n", version.ServerStartTime.Format("2006-01-02T15:04:05"))
text += fmt.Sprintf("运行时长:【%s】\n", time.Since(version.ServerStartTime))
return text
}
func SaveServerKeepAliveTime() {
defer utils.RecoverError(nil)
keyAlive := redis.GetServerKeepAliveHashKey(int(statg.Conf.ServerIndex))
_ = redis.StatRds.Set(keyAlive, time.Now().Unix(), 0)
}
func SaveServerNormalCloseFlag() {
defer utils.RecoverError(nil)
keyClose := redis.GetServerCloseFlagHashKey(int(statg.Conf.ServerIndex))
_ = redis.StatRds.Set(keyClose, time.Now().Unix(), 0)
}
func GetServerInformationLastTime() string {
ret := ""
srvStart := int64(0)
srvAlive := int64(0)
srvClose := int64(0)
sysOnce.Do(func() {
keyStart := redis.GetServerStartTimeHashKey(int(statg.Conf.ServerIndex))
if usStart, err := redis.StatRds.Get(keyStart); err == nil && usStart != nil {
if ut, err2 := strconv.ParseInt(*usStart, 10, 64); err2 == nil {
srvStart = ut
}
}
keyAlive := redis.GetServerKeepAliveHashKey(int(statg.Conf.ServerIndex))
if usAlive, err := redis.StatRds.Get(keyAlive); err == nil && usAlive != nil {
if ut, err2 := strconv.ParseInt(*usAlive, 10, 64); err2 == nil {
srvAlive = ut
}
}
keyClose := redis.GetServerCloseFlagHashKey(int(statg.Conf.ServerIndex))
if usClose, err := redis.StatRds.Get(keyClose); err == nil && usClose != nil {
if ut, err2 := strconv.ParseInt(*usClose, 10, 64); err2 == nil {
srvClose = ut
}
}
version.ServerStartLast = time.Unix(srvStart, 0)
version.ServerAliveLast = time.Unix(srvAlive, 0)
version.ServerCloseLast = time.Unix(srvClose, 0)
_ = redis.StatRds.Set(keyStart, version.ServerStartTime.Unix(), 0)
_ = redis.StatRds.Set(keyClose, version.ServerStartTime.Unix(), 0)
_ = redis.StatRds.Set(keyAlive, time.Now().Unix(), 0)
})
if version.ServerAliveLast.After(version.ServerStartLast) {
ret = fmt.Sprintf("上次运行:【%v】\n", version.ServerAliveLast.Sub(version.ServerStartLast))
if version.ServerCloseLast.After(version.ServerStartLast) {
ret += fmt.Sprintf("上次关闭:【正常关闭🟢】\n")
} else {
ret += fmt.Sprintf("上次关闭:【异常关闭🔴】\n")
}
}
return ret
}
+39
View File
@@ -0,0 +1,39 @@
package botser
import (
"fmt"
"math"
)
func Rate(temp string, numerator, denominator float64) string {
fZero := math.Pow10(-10)
if math.Abs(denominator) < fZero {
return fmt.Sprintf(temp, "无") + "\n"
} else {
str := fmt.Sprintf("%.2f%%", numerator*100/denominator)
return fmt.Sprintf(temp, str) + "\n"
}
}
func Line(iTemp string, iValue int64, numerator, denominator float64) string {
fZero := math.Pow10(-10)
if math.Abs(denominator) < fZero {
s1 := fmt.Sprintf("%d", iValue)
str1 := fmt.Sprintf(iTemp, s1)
return str1 + "\n"
} else {
s1 := fmt.Sprintf("%d", iValue)
str1 := fmt.Sprintf(iTemp, s1)
v := numerator * 100 / denominator
str2 := ""
if v < 0 {
str2 = fmt.Sprintf("⬇-️️%.2f%%", math.Abs(v))
} else if v > 0 {
str2 = fmt.Sprintf("⬆+️%.2f%%", math.Abs(v))
}
if str2 == "" {
return str1 + "\n"
}
return str1 + str2 + "\n"
}
}
+60
View File
@@ -0,0 +1,60 @@
package cacheSer
import (
"runtime"
"stat/common/log"
"stat/common/redis"
"stat/common/statutils"
"sync"
"time"
"github.com/robfig/cron/v3"
"github.com/patrickmn/go-cache"
)
type IpStatCache struct {
Lock sync.RWMutex
IpLimitBucketTopToday redis.BucketList
IpLimitBucketTopYesterday redis.BucketList
}
var (
Cache *cache.Cache
IpLimitStatCache = IpStatCache{
IpLimitBucketTopToday: make(redis.BucketList, 0),
IpLimitBucketTopYesterday: make(redis.BucketList, 0),
}
Writers struct {
LandPageVisitWriter BaseWriter
LandPageClickWriter BaseWriter
}
_cron *cron.Cron
)
func Init() {
Cache = cache.New(10*time.Minute, 10*time.Minute)
Writers.LandPageVisitWriter = NewWriter(landPageVisitSync)
Writers.LandPageClickWriter = NewWriter(landPageClickSync)
_cron = cron.New(cron.WithSeconds(), cron.WithChain(cron.SkipIfStillRunning(&log.Logger{}))) // 秒级定时任务
_, _ = _cron.AddFunc("*/5 * * * * ?", func() {
err := statutils.ServerMon.RunWithName(SyncWriters, statutils.GetFuncName(SyncWriters))
if err != nil {
log.Error("SyncWriters err", log.E(err))
}
})
_cron.Start()
}
func SyncWriters() error {
err := Writers.LandPageVisitWriter.Sync()
if err != nil {
return err
}
return Writers.LandPageClickWriter.Sync()
}
func Key(key string) string {
pc, _, _, _ := runtime.Caller(1)
return runtime.FuncForPC(pc).Name() + "##" + key
}
+21
View File
@@ -0,0 +1,21 @@
package cacheSer
import (
"context"
"stat/models/downloadCpc"
"stat/models/downloadinfo"
)
func landPageVisitSync(list []interface{}) error {
if len(list) > 0 {
return downloadCpc.InsertDownloadInfoMany(context.Background(), list)
}
return nil
}
func landPageClickSync(list []interface{}) error {
if len(list) > 0 {
return downloadinfo.InsertDownloadInfoMany(context.Background(), list)
}
return nil
}
+43
View File
@@ -0,0 +1,43 @@
package cacheSer
import (
"sync"
)
type SyncFunc func(list []interface{}) error
type BaseWriter interface {
Add(doc interface{})
Sync() error
}
type Writer struct {
lock sync.Mutex
list []interface{}
sync SyncFunc
}
func NewWriter(syncFunc SyncFunc) BaseWriter {
return &Writer{
lock: sync.Mutex{},
list: make([]interface{}, 0),
sync: syncFunc,
}
}
func (w *Writer) Add(doc interface{}) {
w.lock.Lock()
defer w.lock.Unlock()
w.list = append(w.list, doc)
}
func (w *Writer) Sync() error {
list := make([]interface{}, 0)
func() {
w.lock.Lock()
defer w.lock.Unlock()
list = w.list
w.list = make([]interface{}, 0)
}()
return w.sync(list)
}
+588
View File
@@ -0,0 +1,588 @@
package channelSer
import (
"context"
"errors"
"fmt"
"os"
"path"
"stat/api/proto"
"stat/common/log"
"stat/models/adminMod"
"stat/models/appInfoMod"
"stat/models/channelmod"
"stat/models/registermod"
"time"
"github.com/360EntSecGroup-Skylar/excelize"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo/options"
)
// SearchChannelInfo 查询渠道信息
func SearchChannelInfo(ctx context.Context, req *proto.ChannelInfoSearch) (
*proto.ChannelInfoSearchResp, error) {
filter := bson.M{}
opt := &options.FindOptions{}
opt.SetSort(bson.M{channelmod.FN.ChannelInfo.CreatedAt: -1})
opt.SetSkip((req.PageNum - 1) * req.PageSize)
opt.SetLimit(req.PageSize)
resp := &proto.ChannelInfoSearchResp{}
if req.ID != nil && *req.ID != "" {
id, err := primitive.ObjectIDFromHex(*req.ID)
if err != nil {
return nil, errors.New("ID 格式错误")
}
filter[channelmod.FN.ChannelInfo.ID] = id
}
if req.Channel != nil && *req.Channel != "" {
filter[channelmod.FN.ChannelInfo.Channel] = bson.M{"$regex": *req.Channel}
}
if req.AppId != nil {
filter[channelmod.FN.ChannelInfo.AppIds] = bson.M{"$in": []int32{*req.AppId}}
}
if req.Type != nil {
filter[channelmod.FN.ChannelInfo.Type] = *req.Type
}
if req.ParentChannel != nil && *req.ParentChannel != "" {
filter[channelmod.FN.ChannelInfo.ParentChannel] = bson.M{"$regex": *req.ParentChannel}
}
if req.UserName != nil && *req.UserName != "" {
filter[channelmod.FN.ChannelInfo.UserName] = *req.UserName
}
if req.Status != nil {
filter[channelmod.FN.ChannelInfo.Status] = *req.Status
}
if req.Remark != nil && *req.Remark != "" {
filter[channelmod.FN.ChannelInfo.Remark] = bson.M{"$regex": *req.Remark}
}
if req.Mobile != nil && *req.Mobile != "" {
filter[channelmod.FN.ChannelInfo.Mobile] = bson.M{"$regex": *req.Mobile}
}
total, serr := channelmod.CountChannelInfos(ctx, filter)
if serr != nil {
return nil, serr
}
if total == 0 {
return resp, nil
}
channelInfoList, err := channelmod.FindManyChannelInfo(ctx, filter, opt)
if serr != nil {
return nil, err
}
for i := range channelInfoList {
channelInfoList[i].Token = ""
}
resp.List = channelInfoList
resp.Total = total
return resp, nil
}
// SearchChannelInfoExport 导出渠道信息
func SearchChannelInfoExport(ctx context.Context, req *proto.ChannelInfoSearch) (string, error) {
filter := bson.M{}
opt := &options.FindOptions{}
opt.SetSort(bson.M{channelmod.FN.ChannelInfo.CreatedAt: -1})
opt.SetSkip((req.PageNum - 1) * req.PageSize)
opt.SetLimit(req.PageSize)
if req.ID != nil && *req.ID != "" {
id, err := primitive.ObjectIDFromHex(*req.ID)
if err != nil {
return "", errors.New("ID 格式错误")
}
filter[channelmod.FN.ChannelInfo.ID] = id
}
if req.Channel != nil && *req.Channel != "" {
filter[channelmod.FN.ChannelInfo.Channel] = bson.M{"$regex": *req.Channel}
}
if req.AppId != nil {
filter[channelmod.FN.ChannelInfo.AppIds] = bson.M{"$in": []int32{*req.AppId}}
}
if req.Type != nil {
filter[channelmod.FN.ChannelInfo.Type] = *req.Type
}
if req.ParentChannel != nil && *req.ParentChannel != "" {
filter[channelmod.FN.ChannelInfo.ParentChannel] = bson.M{"$regex": *req.ParentChannel}
}
if req.UserName != nil && *req.UserName != "" {
filter[channelmod.FN.ChannelInfo.UserName] = bson.M{"$regex": *req.UserName}
}
if req.Status != nil {
filter[channelmod.FN.ChannelInfo.Status] = *req.Status
}
if req.Remark != nil && *req.Remark != "" {
filter[channelmod.FN.ChannelInfo.Remark] = bson.M{"$regex": *req.Remark}
}
if req.Remark != nil && *req.Remark != "" {
filter[channelmod.FN.ChannelInfo.Remark] = bson.M{"$regex": *req.Remark}
}
total, serr := channelmod.CountChannelInfos(ctx, filter)
if serr != nil {
return "", serr
}
if total == 0 {
return "", nil
}
appList, err := appInfoMod.GetAllAppInfo(ctx)
if err != nil {
return "", err
}
channelInfoList, err := channelmod.FindManyChannelInfo(ctx, filter, opt)
if serr != nil {
return "", err
}
filePath := fmt.Sprintf("./tmp/兑换记录-%s.xlsx", time.Now().Format("2006-01-02 15:04:05"))
if sErr := channelInfoExport(filePath, channelInfoList, appList); sErr != nil {
return "", sErr
}
return filePath, nil
}
// channelInfoExport 导出
func channelInfoExport(filePath string, list channelmod.ChannelInfoList, appList []appInfoMod.AppInfo) error {
f := excelize.NewFile()
sheetName := "Sheet1"
// 设置列宽
f.SetColWidth("Sheet1", "A", "L", 23)
// 设置表头行高
f.SetRowHeight("Sheet1", 1, 25)
// 设置表头样式
headStyleID, _ := f.NewStyle(`{
"font":{
"color":"#333333",
"bold":true,
"size":16,
"family":"arial"
},
"alignment":{
"vertical":"center",
"horizontal":"center",
"wrap_text":true
}
}`)
f.SetCellStyle("Sheet1", "A1", "L1", headStyleID)
idx := f.NewSheet(sheetName)
var exportTransWaterTitle = map[string]string{
"A1": "名称",
"B1": "应用列表",
"C1": "渠道类型",
"D1": "IP",
"E1": "下级代理数",
"F1": "父渠道",
"G1": "禁止登陆",
"H1": "渠道状态",
"I1": "登陆错误次数",
"J1": "创建者",
"K1": "创建时间",
"L1": "更新时间",
}
for k, v := range exportTransWaterTitle {
f.SetCellValue(sheetName, k, v)
}
var cLock string
var cStatus string
for k, v := range list {
apps := string2app(appList, v.AppIds)
i := 'A' // i 不能超过Z,即列输不能超过26列,超出将出现bug
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), v.Channel) //名称
i++
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), apps) //应用列表
i++
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), v.Type.String()) //渠道类型
i++
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), v.IP) //IP
i++
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), v.ChildCount) //下级代理数
i++
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), v.ParentChannel) //父渠道
i++
if v.Lock {
cLock = "封禁"
} else if !v.Lock {
cLock = "未封禁"
}
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), cLock) //禁止登陆
i++
if v.Status {
cStatus = "开启"
} else if !v.Status {
cStatus = "关闭"
}
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), cStatus) //渠道状态
i++
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), v.ShowMagic) //登陆错误次数
i++
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), v.UserName) //创建者
i++
created := v.CreatedAt.Local().Format("2006-01-02 15:04:05")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), created) //创建时间
i++
updated := v.UpdatedAt.Local().Format("2006-01-02 15:04:05")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), updated) //更新时间
}
f.SetActiveSheet(idx)
p, _ := os.Getwd()
fileName := path.Join(p, filePath)
if err := f.SaveAs(fileName); err != nil {
log.Error("redeemCodeExport:export save file to disk failed",
log.Any("fileName", fileName),
log.E(err))
return err
}
return nil
}
func string2app(appList []appInfoMod.AppInfo, appIds []int32) string {
var result string
for count, id := range appIds {
if count == len(appIds)-1 {
result += GetAppName(appList, id)
} else {
result += (GetAppName(appList, id) + "、")
}
}
return result
}
// AddChannelInfo 添加一个媒体视频
func AddChannelInfo(ctx context.Context, username string, req *proto.ChannelInfoAdd) (*channelmod.ChannelInfo, error) {
if channelmod.IsOfficialChannelName(req.Channel) {
return nil, fmt.Errorf("渠道名字为系统保留字,请换一个渠道名字")
}
checkFilter := bson.M{channelmod.FN.ChannelInfo.Channel: req.Channel}
channelInfo, err := channelmod.FindOneChannelInfo(ctx, checkFilter)
if err != nil {
return nil, err
}
if channelInfo.Channel == req.Channel {
return channelInfo, errors.New("渠道已经存在")
}
now := time.Now()
newChannelInfo := channelmod.ChannelInfo{
Channel: req.Channel,
UserName: username,
ParentChannel: req.ParentChannel,
Password: req.Password,
Status: req.Status,
Mobile: req.Mobile,
Type: req.Type,
Remark: req.Remark,
Lock: req.Lock,
ChildShowDivideRate: req.ChildShowDivideRate,
ShowMagic: 0,
CreatedAt: now,
UpdatedAt: now,
}
if req.Type == channelmod.ChannelTypeParent {
newChannelInfo.ParentChannel = ""
newChannelInfo.ParentAgent = ""
} else if req.Type == channelmod.ChannelTypeSecond {
parentInfo, err := channelmod.FindOneChannelInfo(ctx, bson.M{channelmod.FN.ChannelInfo.Channel: req.ParentChannel})
if err != nil {
return nil, err
}
if parentInfo.Channel != req.ParentChannel {
return nil, fmt.Errorf("指定的父渠道[%s]不存在", req.ParentChannel)
}
newChannelInfo.ParentChannel = req.ParentChannel
newChannelInfo.ParentAgent = req.ParentChannel
} else {
return nil, errors.New("后台只支持添加一级和二级渠道,其他级别的渠道请登陆渠道后台添加")
}
err = channelmod.InsertOneChannelInfo(ctx, &newChannelInfo)
if err != nil {
log.Error("AddChannelInfo InsertOneChannelInfo", log.Any("channel", newChannelInfo.ParentChannel), log.E(err))
return nil, err
}
if req.Type.IsChild() {
setdoc := bson.M{"$inc": bson.M{channelmod.FN.ChannelInfo.ChildCount: 1}}
err = channelmod.UpdateOneChannelInfo(ctx, bson.M{channelmod.FN.ChannelInfo.Channel: newChannelInfo.ParentChannel}, setdoc)
if err != nil {
log.Error("AddChannelInfo UpdateOneChannelInfo", log.Any("channel", newChannelInfo.ParentChannel), log.E(err))
return nil, err
}
}
return channelmod.FindOneChannelInfo(ctx, checkFilter)
}
// DelChannelInfo 删除指定渠道信息
func DelChannelInfo(ctx context.Context, channelId string) error {
id, err := primitive.ObjectIDFromHex(channelId)
if err != nil {
return errors.New("ID 格式错误")
}
// 1. 查询渠道信息
channelFilter := bson.M{channelmod.FN.ChannelInfo.ID: id}
channelInfo, err := channelmod.FindOneChannelInfo(ctx, channelFilter)
if err != nil {
log.Error("DelChannelInfo FindOneChannelInfo", log.Any("channelId", channelId), log.E(err))
return err
}
// 3. 查询子代理数
childFilter := bson.M{channelmod.FN.ChannelInfo.ParentAgent: channelInfo.Channel}
childCount, err := channelmod.CountChannelInfos(ctx, childFilter)
if err != nil {
log.Error("DelChannelInfo CountChannelDeductionRules", log.Any("channelId", channelId), log.E(err))
return err
}
if childCount > 0 {
return errors.New("代理下级代理不为空,请先删除下级代理")
}
// 2. 查询渠道绑定应用
ruleFilter := bson.M{channelmod.FN.ChannelDeductionRule.Channel: channelInfo.Channel}
ruleCount, err := channelmod.CountChannelDeductionRules(ctx, ruleFilter)
if err != nil {
log.Error("DelChannelInfo CountChannelDeductionRules", log.Any("channelId", channelId), log.E(err))
return err
}
if ruleCount > 0 {
return errors.New("绑定应用不为空,请先删除绑定应用")
}
// 4. 删除渠道
_, err = channelmod.DelOneChannelInfo(ctx, channelFilter)
if err != nil {
return err
}
// 5 更新父渠道子渠道数量
if channelInfo.Type.IsChild() {
setdoc := bson.M{"$inc": bson.M{channelmod.FN.ChannelInfo.ChildCount: -1}}
err = channelmod.UpdateOneChannelInfo(ctx, bson.M{channelmod.FN.ChannelInfo.Channel: channelInfo.ParentAgent}, setdoc)
if err != nil {
log.Error("DelChannelInfo UpdateOneChannelInfo", log.Any("channel", channelInfo.ParentAgent), log.E(err))
return err
}
}
return nil
}
// UpdateChannelInfo 更新指定渠道
func UpdateChannelInfo(ctx context.Context, channelId string, req proto.ChannelInfoUpdate) error {
id, err := primitive.ObjectIDFromHex(channelId)
if err != nil {
return errors.New("ID 格式错误")
}
filter := bson.M{channelmod.FN.ChannelInfo.ID: id}
//更新数据库
setDoc := bson.M{"$set": req}
serr := channelmod.UpdateOneChannelInfo(ctx, filter, setDoc)
if serr != nil {
return serr
}
return nil
}
// UpdateOneChannelOwner 更新指定渠道的所有者
func UpdateOneChannelOwner(ctx context.Context, operator, account string, req proto.ChannelOwnerUpdate) error {
filter := bson.M{channelmod.FN.ChannelInfo.Channel: req.Channel}
cInfo, serr := channelmod.FindOneChannelInfo(ctx, filter)
if serr != nil {
log.Error("UpdateOneChannelOwner:FindOneChannelInfo err", log.Any("serr", serr),
log.Any("req", req))
return serr
} else if cInfo != nil && cInfo.Channel == "" {
log.Error("UpdateOneChannelOwner:FindOneChannelInfo err", log.Any("serr", serr),
log.Any("req", req))
return errors.New("渠道不存在")
}
if req.UserName == "" {
return errors.New("变更的所有者不能为空")
} else if req.UserName == cInfo.UserName {
return errors.New("变更的所有者不能与之前的相同")
// } else if cInfo.Type != 1 { // 只能操作1级渠道
// return errors.New("变更所有者,只能操作1级渠道")
}
admin, serr := adminMod.FindOnedmin(ctx, bson.M{"name": req.UserName})
if serr != nil {
log.Error("UpdateOneChannelOwner:FindOnedmin err", log.Any("serr", serr),
log.Any("req", req))
return serr
} else if admin != nil && admin.Name == "" {
log.Error("UpdateOneChannelOwner:FindOnedmin err", log.Any("serr", serr),
log.Any("req", req))
return errors.New("账号不存在")
}
now := time.Now()
// 更新渠道信息
setDoc := bson.M{"$set": bson.M{
channelmod.FN.ChannelInfo.UserName: req.UserName,
channelmod.FN.ChannelInfo.UpdatedAt: now,
}}
serr = channelmod.UpdateOneChannelInfo(ctx, filter, setDoc)
if serr != nil {
log.Error("UpdateOneChannelOwner:UpdateOneChannelInfo err", log.Any("serr", serr),
log.Any("req", req))
return serr
}
// 更新渠道统计信息
csFilter := bson.M{channelmod.FN.ChannelStat.Channel: cInfo.Channel}
csSetDoc := bson.M{"$set": bson.M{channelmod.FN.ChannelStat.UserName: req.UserName}}
serr = channelmod.UpdateManyChannelStat(ctx, csFilter, csSetDoc)
if serr != nil {
log.Error("UpdateOneChannelOwner:UpdateManyChannelStat err", log.Any("serr", serr),
log.Any("req", req))
return serr
}
// 找到一级渠道的所有子渠道
allFlter := bson.M{channelmod.FN.ChannelInfo.ParentChannel: req.Channel}
chanIds := make([]string, 0)
cInfos, _ := channelmod.FindManyChannelInfo(ctx, allFlter)
if len(cInfos) > 0 {
for _, info := range cInfos {
chanIds = append(chanIds, info.Channel)
}
// 更新一级渠道的所有子渠道
serr = channelmod.UpdateManyChannelInfo(ctx, bson.M{channelmod.FN.ChannelInfo.Channel: bson.M{"$in": chanIds}}, setDoc)
if serr != nil {
log.Error("UpdateOneChannelOwner:UpdateManyChannelInfo 2 err", log.Any("serr", serr),
log.Any("req", req))
}
// 更新一级渠道的所有子渠道统计
serr = channelmod.UpdateManyChannelStat(ctx, bson.M{channelmod.FN.ChannelStat.Channel: bson.M{"$in": chanIds}}, csSetDoc)
if serr != nil {
log.Error("UpdateOneChannelOwner:UpdateManyChannelStat 2 err", log.Any("serr", serr),
log.Any("req", req))
}
}
// 添加变更记录
new := &channelmod.ChannelOwnerChange{
ID: primitive.NewObjectID(),
Channel: cInfo.Channel,
OldUser: cInfo.UserName,
NewUser: req.UserName,
Mark: req.Mark,
CreatedAt: now,
UpdatedAt: now,
Operator: operator,
Account: account,
}
serr = channelmod.InsertOneChannelOwnerChange(ctx, new)
if serr != nil {
log.Error("UpdateOneChannelOwner:InsertOneChannelOwnerChange err", log.Any("new", new),
log.Any("err", serr))
return serr
}
return nil
}
// SearchChannelOwnerChangeInfo 查询渠道所有者变更信息
func SearchChannelOwnerChangeInfo(ctx context.Context, req *proto.ChannelOwnerChangeInfoSearch) (
*proto.ChannelOwnerChangeInfoSearchResp, error) {
filter := bson.M{}
opt := &options.FindOptions{}
opt.SetSort(bson.M{channelmod.FN.ChannelInfo.CreatedAt: -1})
opt.SetSkip((req.PageNum - 1) * req.PageSize)
opt.SetLimit(req.PageSize)
resp := &proto.ChannelOwnerChangeInfoSearchResp{}
if req.ID != nil {
filter[channelmod.FN.ChannelOwnerChange.ID] = req.ID
}
if req.Channel != nil {
filter[channelmod.FN.ChannelOwnerChange.Channel] = *req.Channel
}
if req.OldUser != nil {
filter[channelmod.FN.ChannelOwnerChange.OldUser] = *req.OldUser
}
if req.NewUser != nil {
filter[channelmod.FN.ChannelOwnerChange.NewUser] = *req.NewUser
}
total, serr := channelmod.CountChannelOwnerChanges(ctx, filter)
if serr != nil {
return nil, serr
}
if total == 0 {
return resp, nil
}
ownerChangeList, err := channelmod.FindManyChannelOwnerChanges(ctx, filter, opt)
if serr != nil {
return nil, err
}
resp.List = ownerChangeList
resp.Total = total
return resp, nil
}
// SearchUserInfo 查询用户信息
func SearchUserInfo(ctx context.Context, req *proto.UserInfoSearch) (
*proto.UserInfoSearchResp, error) {
resp := &proto.UserInfoSearchResp{}
filter := bson.M{}
if req.UserId != nil {
filter["userId"] = *req.UserId
}
if req.Channel != nil {
filter["districtCode"] = *req.Channel
}
if req.AppId != nil {
filter["appId"] = *req.AppId
}
if req.IP != nil {
filter["ip"] = *req.IP
}
if req.Start != nil && req.End != nil {
filter["createdAt"] = bson.M{"$gt": *req.Start, "$lt": *req.End}
}
total, err := registermod.CountRegister(ctx, filter)
if err != nil {
log.Error("SearchUserInfo CountRegister", log.Any("err", err))
return nil, err
}
opt := &options.FindOptions{}
opt.SetSort(bson.M{"createdAt": -1})
opt.SetSkip((req.PageNum - 1) * req.PageSize)
opt.SetLimit(req.PageSize)
userList, err := registermod.FindManyRegister(ctx, filter, opt)
if err != nil {
log.Error("SearchUserInfo FindManyRegister", log.Any("err", err))
return nil, err
}
resp.Total = total
resp.List = userList
return resp, nil
}
+30
View File
@@ -0,0 +1,30 @@
package channelSer
import "math/rand"
var (
//所有的字符
ALL_CHARS = []string{
"1", "2", "3", "4", "5",
"6", "7", "8", "9", "A",
"B", "C", "D", "E", "F",
"G", "H", "J", "K",
"L", "M", "N", "O", "P",
"Q", "R", "S", "T", "U",
"V", "W", "X", "Y", "Z"}
aLen = len(ALL_CHARS)
)
const (
//默认长度
DEFAULT_LEN = 6
)
// 用户推广码
func InviteShartPasswd() (code string) {
for i := 0; i < DEFAULT_LEN; i++ {
index := rand.Intn(aLen)
code += ALL_CHARS[index]
}
return
}
+184
View File
@@ -0,0 +1,184 @@
package channelSer
import (
"context"
"errors"
"stat/api/proto"
"stat/common/log"
"stat/models/channelmod"
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo/options"
)
// SearchChannelDeductionRule 查询渠道扣量规则信息
func SearchChannelDeductionRule(ctx context.Context, req *proto.ChannelDeductionRuleSearch) (*proto.ChannelDeductionRuleSearchResp, error) {
resp := &proto.ChannelDeductionRuleSearchResp{}
filter := bson.M{}
opt := &options.FindOptions{}
opt.SetSort(bson.M{channelmod.FN.ChannelDeductionRule.BeginTime: -1})
opt.SetSkip((req.PageNum - 1) * req.PageSize)
opt.SetLimit(req.PageSize)
if req.Channel != nil && *req.Channel != "" {
filter[channelmod.FN.ChannelDeductionRule.Channel] = bson.M{"$regex": *req.Channel}
}
if req.AppId != nil {
filter[channelmod.FN.ChannelDeductionRule.AppId] = *req.AppId
}
if req.Channel != nil && *req.Channel != "" {
filter[channelmod.FN.ChannelDeductionRule.Channel] = *req.Channel
}
total, err := channelmod.CountChannelDeductionRules(ctx, filter)
if err != nil {
return nil, err
}
if total == 0 {
return resp, nil
}
ChannelDeductionRuleList, err := channelmod.FindManyChannelDeductionRule(ctx, filter, opt)
if err != nil {
return nil, err
}
for _, rule := range ChannelDeductionRuleList {
if rule.DivideRate == 0 {
rule.DivideRate = 100
}
}
resp.Total = total
resp.List = ChannelDeductionRuleList
return resp, nil
}
// AddChannelDeductionRule 添加一个媒体视频
func AddChannelDeductionRule(ctx context.Context, req *proto.ChannelDeductionRuleAdd) (*channelmod.ChannelDeductionRule, error) {
checkFilter := bson.M{channelmod.FN.ChannelDeductionRule.Channel: req.Channel, channelmod.FN.ChannelDeductionRule.AppId: req.AppId}
ChannelDeductionRule, err := channelmod.FindOneChannelDeductionRule(ctx, checkFilter)
if err != nil {
return nil, err
}
if ChannelDeductionRule.Channel == req.Channel {
return ChannelDeductionRule, errors.New("渠道扣量规则已经存在")
}
channelInfo, err := channelmod.FindOneChannelInfoEx(ctx, bson.M{channelmod.FN.ChannelInfo.Channel: req.Channel})
if err != nil {
return nil, err
}
var one float64
if req.LessIncomeRate == nil {
one = 0.0
} else {
one = *req.LessIncomeRate
}
now := time.Now()
rule := channelmod.ChannelDeductionRule{
AppId: req.AppId,
AppType: req.AppType,
PromoteMode: req.PromoteMode,
Channel: req.Channel,
Settlement: req.Settlement,
First: req.First,
Second: req.Second,
Third: req.Third,
Fourth: req.Fourth,
Fifth: req.Fifth,
CPAPrice: req.CPAPrice,
CPSRate: req.CPSRate,
BeginTime: req.BeginTime,
CPSBegin: req.CPSBegin,
CPSDeductionRate: req.CPSDeductionRate,
DivideRate: req.DivideRate,
LandingPage: req.LandingPage,
ProxyDeduction: req.ProxyDeduction,
AutoDeduction: req.AutoDeduction,
LessIncomeRate: one,
CreatedAt: now,
UpdatedAt: now,
}
err = channelmod.InsertOneChannelDeductionRule(ctx, &rule)
if err != nil {
return nil, err
}
channelInfo.AppIds = append(channelInfo.AppIds, req.AppId)
setdoc := bson.M{"$set": bson.M{channelmod.FN.ChannelInfo.AppIds: channelInfo.AppIds}}
err = channelmod.UpdateOneChannelInfo(ctx, bson.M{channelmod.FN.ChannelInfo.Channel: req.Channel}, setdoc)
if err != nil {
return nil, err
}
return channelmod.FindOneChannelDeductionRuleEx(ctx, checkFilter)
}
// DelChannelDeductionRule 删除指定渠道扣量规则信息
func DelChannelDeductionRule(ctx context.Context, channelId string) error {
id, err := primitive.ObjectIDFromHex(channelId)
if err != nil {
return errors.New("ID 格式错误")
}
ruleFilter := bson.M{channelmod.FN.ChannelDeductionRule.ID: id}
channelRuleInfo, err := channelmod.FindOneChannelDeductionRuleEx(ctx, ruleFilter)
if err != nil {
return err
}
channelInfo, err := channelmod.FindOneChannelInfoEx(ctx, bson.M{channelmod.FN.ChannelInfo.Channel: channelRuleInfo.Channel})
if err != nil {
return err
}
count, err := channelmod.CountChannelInfos(ctx, bson.M{channelmod.FN.ChannelInfo.ParentAgent: channelInfo.Channel})
if err != nil {
log.Error("DelChannelDeductionRule CountChannelInfos", log.Any("channelId", channelId), log.E(err))
return err
}
if count > 0 {
return errors.New("下级代理不为空,请先删除下级代理, 再删除应用规则")
}
_, err = channelmod.DelOneChannelDeductionRule(ctx, ruleFilter)
if err != nil {
return err
}
appids := make([]int32, 0)
for _, appid := range channelInfo.AppIds {
if appid != channelRuleInfo.AppId {
appids = append(appids, appid)
}
}
setdoc := bson.M{"$set": bson.M{channelmod.FN.ChannelInfo.AppIds: appids}}
err = channelmod.UpdateOneChannelInfo(ctx, bson.M{channelmod.FN.ChannelInfo.Channel: channelRuleInfo.Channel}, setdoc)
if err != nil {
return err
}
return nil
}
// UpdateChannelDeductionRule 更新指定渠道扣量规则
func UpdateChannelDeductionRule(ctx context.Context, channelId string, info proto.ChannelDeductionRuleUpdate) error {
id, err := primitive.ObjectIDFromHex(channelId)
if err != nil {
return errors.New("ID 格式错误")
}
filter := bson.M{channelmod.FN.ChannelDeductionRule.ID: id}
//更新数据库
setDoc := bson.M{"$set": info}
serr := channelmod.UpdateOneChannelDeductionRule(ctx, filter, setDoc)
if serr != nil {
return serr
}
return nil
}
File diff suppressed because it is too large Load Diff
+150
View File
@@ -0,0 +1,150 @@
package channelSer
import (
"context"
"fmt"
"stat/api/proto"
"stat/common/utils"
"stat/middleware/define"
"stat/models/adminMod"
channelmod "stat/models/channelmod"
"stat/models/channelsettlementrecord"
"time"
"go.mongodb.org/mongo-driver/bson"
)
var (
SystemStartTime = time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC)
)
func SettlementInfoToApplyRecord(src *channelsettlementrecord.ChannelSettlementRecord) *proto.ApplyFormRecord {
ret := &proto.ApplyFormRecord{
ID: src.ID,
Channel: src.Channel,
AppId: src.AppId,
BeginTime: src.BeginTime,
EndTime: src.EndTime,
TotalMoney: src.TotalMoney,
SettleMoney: src.SettleMoney,
Settlement: src.Settlement,
Wallet: src.Wallet,
PayMoney: src.PayMoney,
Usdt: src.Usdt,
Status: src.Status,
Remark: "",
Pictures: nil,
UpdatedAt: src.UpdatedAt,
CreatedAt: src.CreatedAt,
}
remark := ""
if len(src.OperateHistory) > 0 {
remark = src.OperateHistory[0].Remark
if len(src.OperateHistory) > 1 {
if remark == "" {
remark = src.OperateHistory[len(src.OperateHistory)-1].Remark
} else {
s := src.OperateHistory[len(src.OperateHistory)-1].Remark
if s != "" {
remark += " / " + s
}
}
}
ret.Pictures = src.OperateHistory[len(src.OperateHistory)-1].Pictures
}
ret.Remark = remark
return ret
}
func SettlementInfoListToApplyForm(lst channelsettlementrecord.ChannelSettlementRecordList) proto.ApplyFormRecordList {
res := make(proto.ApplyFormRecordList, 0)
for _, item := range lst {
a := SettlementInfoToApplyRecord(item)
res = append(res, a)
}
return res
}
func FenToYuanDisplay(fen int64) string {
return fmt.Sprintf("%.2f", float64(fen)/float64(100))
}
func IndentationDisplay(v int64) string {
return fmt.Sprintf("%.2f", float64(v)/float64(10000))
}
func CheckAndFixTime(startTime, endTime *time.Time) (*time.Time, *time.Time, error) {
var start, end *time.Time
// 开始时间和结束时间
if endTime == nil {
e := utils.BeginOfYesterday()
end = &e
} else {
end = endTime
}
if startTime == nil {
start = &SystemStartTime
} else {
start = startTime
}
//
if end.After(utils.BeginOfYesterday()) {
e := utils.BeginOfYesterday()
end = &e
}
if end.Before(*start) {
return nil, nil, fmt.Errorf("开始和结束的时间区间填写不正确")
}
return start, end, nil
}
func GetUpdateSettlementOperateItem(old channelsettlementrecord.ChannelSettlementRecord, update proto.SettlementInfoUpdate2,
accType define.ActType, account string, accountName string, images []string) channelsettlementrecord.SettlementOperateItem {
res := channelsettlementrecord.SettlementOperateItem{}
res.OperateTime = time.Now()
if update.Remark != nil {
res.Remark = *update.Remark
}
res.OperatorAccountType = accType
res.OperatorAccount = account
res.OperatorAccountName = accountName
res.BeforeStatus = old.Status
if update.Status != nil && *update.Status != old.Status {
res.AfterStatus = *update.Status
res.Content += fmt.Sprintf("状态改变:(%s => %s)\n", old.Status.String(), update.Status.String())
} else {
res.AfterStatus = old.Status
}
if update.Usdt != nil && *update.Usdt != old.Usdt {
res.Content += fmt.Sprintf("修改了打款金额(USDT):(%s => %s)\n", FenToYuanDisplay(old.Usdt), FenToYuanDisplay(*update.Usdt))
}
if update.PayMoney != nil && *update.PayMoney != old.PayMoney {
res.Content += fmt.Sprintf("修改了打款金额(CNY): (%s => %s)\n", FenToYuanDisplay(old.PayMoney), FenToYuanDisplay(*update.PayMoney))
}
if update.SettleMoney != nil && *update.SettleMoney != old.SettleMoney {
res.Content += fmt.Sprintf("修改了申请结算金额:(%s => %s)\n", FenToYuanDisplay(old.SettleMoney), FenToYuanDisplay(*update.SettleMoney))
}
for _, img := range images {
res.Pictures = append(res.Pictures, img)
}
return res
}
func GetAccountName(ctx context.Context, accType define.ActType, account string) (string, error) {
if accType == define.Admin {
if acc, err := adminMod.FindOneByAccount(ctx, account); err != nil {
return "", err
} else {
return acc.Name, nil
}
} else if accType == define.Channel {
if acc, err := channelmod.FindOneChannelInfo(ctx, bson.M{channelmod.FN.ChannelInfo.Channel: account}); err != nil {
return "", err
} else {
return acc.Channel, nil
}
} else {
return "", fmt.Errorf("error account type: %s", accType)
}
}
+321
View File
@@ -0,0 +1,321 @@
package channelSer
import (
"context"
"errors"
"runtime"
"stat/api/proto"
"stat/common/utils"
"stat/models/channelmod"
"time"
"github.com/jinzhu/now"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo/options"
)
// SearchChannelStat 查询渠道扣量规则信息
func SearchChannelStat(ctx context.Context, req *proto.ChannelStatSearch) (*proto.ChannelStatSearchResp, error) {
resp := &proto.ChannelStatSearchResp{
List: make(channelmod.ChannelStatList, 0),
}
filter := bson.M{}
opt := &options.FindOptions{}
opt.SetSkip((req.PageNum - 1) * req.PageSize)
opt.SetLimit(req.PageSize)
if req.Channel != nil && *req.Channel != "" {
if req.WithChild != nil && *req.WithChild {
filter = bson.M{
"$or": bson.A{
bson.M{channelmod.FN.ChannelStat.Channel: *req.Channel},
bson.M{channelmod.FN.ChannelStat.ParentChannel: *req.Channel},
},
}
} else {
filter[channelmod.FN.ChannelStat.Channel] = *req.Channel
}
} else if req.DayType != nil {
timeNow := time.Now()
now.WeekStartDay = time.Monday
Now := now.With(timeNow.In(time.Local))
todayBegin := utils.BeginWithTime(timeNow)
weekBegin := Now.BeginningOfWeek()
monthBegin := Now.BeginningOfMonth()
var timeBegin time.Time
if *req.DayType == 1 { // 今天内
timeBegin = todayBegin
} else if *req.DayType == 2 { // 昨天和今天
timeBegin = todayBegin.AddDate(0, 0, -1)
} else if *req.DayType == 3 { // 三天内
timeBegin = todayBegin.AddDate(0, 0, -2)
} else if *req.DayType == 4 { // 七天内
timeBegin = todayBegin.AddDate(0, 0, -6)
} else if *req.DayType == 5 { // 本周内
timeBegin = weekBegin
} else if *req.DayType == 6 { // 两周内
timeBegin = weekBegin.AddDate(0, 0, -7)
} else if *req.DayType == 7 { // 本月内
timeBegin = monthBegin
} else if *req.DayType == 8 { // 两月内
timeBegin = monthBegin.AddDate(0, -1, 0)
}
channelList, err := channelmod.FindManyChannelInfo(ctx, bson.M{
channelmod.FN.ChannelInfo.CreatedAt: bson.M{
"$gte": timeBegin,
"$lte": timeNow,
},
})
if err != nil {
return resp, err
}
channelIds := make([]string, 0)
for _, channelInfo := range channelList {
channelIds = append(channelIds, channelInfo.Channel)
}
if len(channelIds) > 0 {
if req.WithChild != nil && *req.WithChild {
filter = bson.M{
"$or": bson.A{
bson.M{channelmod.FN.ChannelStat.Channel: bson.M{"$in": channelIds}},
bson.M{channelmod.FN.ChannelStat.ParentChannel: bson.M{"$in": channelIds}},
},
}
} else {
filter[channelmod.FN.ChannelStat.Channel] = bson.M{"$in": channelIds}
}
} else {
return resp, nil
}
}
//
//排序
if req.RankType != nil {
if *req.RankType == 1 {
opt.SetSort(bson.D{{Key: channelmod.FN.ChannelStat.SumDate, Value: -1}, {Key: channelmod.FN.ChannelStat.DirectAmount, Value: -1}})
} else if *req.RankType == 2 {
opt.SetSort(bson.D{{Key: channelmod.FN.ChannelStat.SumDate, Value: -1}, {Key: channelmod.FN.ChannelStat.NewDirectCount, Value: -1}})
} else if *req.RankType == 3 {
opt.SetSort(bson.D{{Key: channelmod.FN.ChannelStat.SumDate, Value: -1}, {Key: channelmod.FN.ChannelStat.NewRechargeAmount, Value: -1}})
} else if *req.RankType == 4 {
opt.SetSort(bson.D{{Key: channelmod.FN.ChannelStat.SumDate, Value: -1}, {Key: channelmod.FN.ChannelStat.IncomeRate, Value: -1}})
}
} else {
opt.SetSort(bson.D{{Key: channelmod.FN.ChannelStat.SumDate, Value: -1}, {Key: channelmod.FN.ChannelStat.NewDirectCount, Value: -1}})
}
if req.UserName != nil && *req.UserName != "" {
filter[channelmod.FN.ChannelStat.UserName] = bson.M{"$regex": *req.UserName}
}
if req.AppId != nil {
filter[channelmod.FN.ChannelStat.AppId] = bson.M{"$in": *req.AppId}
}
if req.Start != nil && req.End != nil {
filter[channelmod.FN.ChannelStat.SumDate] = bson.M{"$gte": *req.Start, "$lte": *req.End}
}
if req.WithChild != nil && !*req.WithChild {
filter[channelmod.FN.ChannelStat.Type] = channelmod.ChannelTypeParent
}
if req.Settlement != nil {
filter[channelmod.FN.ChannelStat.Settlement] = *req.Settlement
}
if req.Detail != nil && *req.Detail {
if req.SumDate == nil {
return resp, errors.New("param error")
}
filter = bson.M{
"$or": bson.A{
bson.M{channelmod.FN.ChannelStat.Channel: *req.Channel},
bson.M{channelmod.FN.ChannelStat.ParentChannel: *req.Channel},
},
channelmod.FN.ChannelStat.SumDate: *req.SumDate,
}
}
ChannelStatList, err := channelmod.FindManyChannelStat(ctx, filter, opt)
if err != nil {
return resp, err
}
for _, v := range ChannelStatList {
v.CorrectValidData()
v.CalcIncomeRate()
}
resp.List = ChannelStatList
// 汇总
canSum := utils.M(filter).HasKey(channelmod.FN.ChannelStat.AppId) && utils.M(filter).HasKey(channelmod.FN.ChannelStat.SumDate)
if canSum {
var total int64
err = channelmod.FindManyChannelStatShard(ctx, filter, 10000, func(item *channelmod.ChannelStat) {
total++
channelmod.ChannelStatSummation(&resp.TotalInfo, item)
if req.WithChild != nil && *req.WithChild {
for _, v := range ChannelStatList {
if item.ParentChannel == v.Channel && v.SumDate.Equal(item.SumDate) {
channelmod.ChannelStatSummation(v, item)
}
}
}
})
if err != nil {
return resp, err
}
if total == 0 {
return resp, nil
}
if total > 10000 {
runtime.GC()
}
resp.Total = total
var ruleInfo *channelmod.ChannelDeductionRule
if req.Channel != nil && *req.Channel != "" {
ruleFilter := bson.M{
channelmod.FN.ChannelDeductionRule.Channel: req.Channel,
// channelmod.FN.ChannelDeductionRule.AppId: req.AppId,
}
ruleInfo, _ = channelmod.FindOneChannelDeductionRule(ctx, ruleFilter)
}
if ruleInfo != nil {
resp.TotalInfo.Channel = ruleInfo.Channel
resp.TotalInfo.Settlement = ruleInfo.Settlement
resp.TotalInfo.AppId = ruleInfo.AppId
resp.TotalInfo.CPAPrice = ruleInfo.CPAPrice
resp.TotalInfo.CPSRate = ruleInfo.CPSRate
resp.TotalInfo.AppType = ruleInfo.AppType
resp.TotalInfo.PromoteMode = ruleInfo.PromoteMode
resp.TotalInfo.CalcIncomeRate()
resp.Income, resp.Desc = channelmod.CalculateChannelIncome(&resp.TotalInfo)
}
} else {
resp.Total = 1000000
}
return resp, nil
}
// SearchChannelSettlement 查询渠道结算信息
func SearchChannelSettlement(ctx context.Context, req *proto.ChannelSettlementSearch) (
*proto.ChannelSettlementSearchResp, error) {
resp := &proto.ChannelSettlementSearchResp{}
filter := bson.M{}
opt := &options.FindOptions{}
opt.SetSort(bson.M{channelmod.FN.ChannelSettlement.CreatedAt: -1})
opt.SetSkip((req.PageNum - 1) * req.PageSize)
opt.SetLimit(req.PageSize)
if req.Channel != nil && *req.Channel != "" {
filter[channelmod.FN.ChannelSettlement.Channel] = *req.Channel
}
if req.AppId != nil {
filter[channelmod.FN.ChannelSettlement.AppId] = *req.AppId
}
if req.Start != nil && req.End != nil {
filter[channelmod.FN.ChannelSettlement.SumDate] = bson.M{"$gt": *req.Start, "$lt": *req.End}
}
if req.WithChild != nil && *req.WithChild {
filter[channelmod.FN.ChannelSettlement.Type] = channelmod.ChannelTypeParent
}
total, serr := channelmod.CountChannelSettlements(ctx, filter)
if serr != nil {
return nil, serr
}
if total == 0 {
return resp, nil
}
ChannelSettlementList, err := channelmod.FindManyChannelSettlement(ctx, filter, opt)
if serr != nil {
return nil, err
}
if req.WithChild != nil && *req.WithChild {
channels := make([]string, 0)
for _, v := range ChannelSettlementList {
channels = append(channels, v.Channel)
}
childTypes := channelmod.GetChildChannelTypes()
filter[channelmod.FN.ChannelSettlement.Type] = bson.M{"$in": childTypes}
filter[channelmod.FN.ChannelSettlement.ParentChannel] = bson.M{"$in": channels}
channelsettlementList, err := channelmod.FindManyChannelStat(ctx, filter)
if err != nil {
return nil, err
}
for _, channnelSettlementInfo := range channelsettlementList {
for _, v := range ChannelSettlementList {
if channnelSettlementInfo.ParentChannel == v.Channel {
channnelSettlementInfo.AccumulateTo(v)
}
}
}
}
resp.Total = total
resp.List = ChannelSettlementList
return resp, nil
}
// SearchChannelStatCompared 渠道同时段对比
func SearchChannelStatCompared(ctx context.Context, req *proto.SearchChannelStatComparedReq) (*proto.SearchChannelStatComparedResp, error) {
resp := &proto.SearchChannelStatComparedResp{}
filter := bson.M{}
opt := &options.FindOptions{}
opt.SetSkip((req.PageNum - 1) * req.PageSize)
opt.SetLimit(req.PageSize)
opt.SetSort(
bson.D{
{Key: channelmod.FN.ChannelStatYestoday.SumDate, Value: -1},
{Key: channelmod.FN.ChannelStatYestoday.NewCount, Value: -1},
},
)
if req.Channel != nil && *req.Channel != "" {
if req.WithChild != nil && *req.WithChild {
filter = bson.M{
"$or": bson.A{
bson.M{channelmod.FN.ChannelStatYestoday.Channel: *req.Channel},
bson.M{channelmod.FN.ChannelStatYestoday.ParentChannel: *req.Channel},
},
}
} else {
filter[channelmod.FN.ChannelStatYestoday.Channel] = *req.Channel
}
}
if req.AppId != nil {
filter[channelmod.FN.ChannelStatYestoday.AppId] = *req.AppId
}
if req.Start != nil && req.End != nil {
filter[channelmod.FN.ChannelStatYestoday.SumDate] = bson.M{"$gte": *req.Start, "$lte": *req.End}
}
if req.Settlement != nil {
filter[channelmod.FN.ChannelStatYestoday.Settlement] = *req.Settlement
}
total, err := channelmod.CountChannelStatYestoday(ctx, filter)
if err != nil {
return nil, err
}
channelStatList, err := channelmod.FindManyChannelStatYestoday(ctx, filter, opt)
if err != nil {
return nil, err
}
resp.Total = total
resp.List = channelStatList
return resp, nil
}
+991
View File
@@ -0,0 +1,991 @@
package consumer
import (
"context"
"fmt"
"stat/common/enum/consts"
"stat/common/log"
"stat/common/statutils"
"stat/common/topicmessage"
"stat/common/utils"
"stat/models/accessmod"
"stat/models/channelmod"
downloadinfo "stat/models/downloadinfo"
"stat/models/rechargegamemod"
"stat/models/rechargemod"
"stat/models/registermod"
"stat/models/syncgamerecharge"
"stat/models/syncgamewater"
"stat/models/syncrecharge"
"stat/models/syncrecord"
"stat/models/syncvip"
"stat/models/syncwithdraw"
"stat/models/withdrawGameOrdermod"
"stat/models/withdrawUserOrdermod"
"stat/statg"
"strings"
"time"
"github.com/pkg/errors"
"go.mongodb.org/mongo-driver/bson"
"go.uber.org/zap"
)
// 1.用户访问消息处理函数
func UserAccessHandler(accessMsg *topicmessage.UserVisitMsg) error {
accessInfo := accessmod.AccessMod{
UserId: accessMsg.UserId,
AppId: accessMsg.AppId,
SysType: accessMsg.SysType,
DevType: accessMsg.DevType,
IP: accessMsg.IP,
Version: accessMsg.Version,
DevID: accessMsg.DevID,
VisitAt: accessMsg.VisitAt,
CreatedAt: time.Now(),
}
// 查找用户信息
userInfo, err := registermod.GetRegisterInfo(context.Background(), accessInfo.UserId, accessInfo.AppId)
if err != nil {
return err
}
accessInfo.IsDirect = userInfo.IsDirect
accessInfo.DistrictCode = userInfo.DistrictCode
accessInfo.RegisterTime = userInfo.RegisterTime
accessInfo.IsFormalUser = userInfo.IsFormal
if userInfo.UserId != accessInfo.UserId {
accessInfo.IsDirect = true
}
return accessmod.AddAccessLog(context.Background(), &accessInfo)
}
// 2.用户注册消息处理函数
func UserRegisterHandler(registerMsg *topicmessage.UserRegisterMsg) error {
ctx := context.Background()
registerInfo := registermod.RegisterMod{
UserId: registerMsg.UserId,
AppId: registerMsg.AppId,
SysType: registerMsg.SysType,
DevType: registerMsg.DevType,
Mobile: registerMsg.Mobile,
Name: registerMsg.Name,
IP: registerMsg.IP,
IsDirect: true,
DistrictCode: strings.TrimSpace(registerMsg.DistrictCode),
DC: strings.TrimSpace(registerMsg.DistrictCode),
DistrictFrom: registermod.DistrictFromNone,
PromCode: registerMsg.PromCode,
RegisterTime: registerMsg.RegisterTime,
ParentPromCode: registerMsg.PromSeqe,
UserAgent: registerMsg.UserAgent,
IsDeduction: false,
}
var ipChecked bool
// 检测
testUser, err := registermod.GetRegisterInfo(ctx, registerInfo.UserId, registerInfo.AppId)
if err != nil {
log.Error("UserRegisterHandler define err", log.Any("UserId", testUser.UserId), log.E(err))
return err
}
if testUser.UserId == registerInfo.UserId {
return nil // 已存在
}
if len(registerInfo.ParentPromCode) > 0 && len(registerInfo.DistrictCode) == 0 {
testUser, err := registermod.GetRegisterInfoByInviteCode(ctx, registerInfo.ParentPromCode, registerInfo.AppId)
if err != nil || testUser.PromCode != registerInfo.ParentPromCode {
log.Error("GetRegisterInfoByInviteCode define err", log.Any("UserId", testUser.UserId), log.E(err))
} else {
// 推广相关逻辑
registerInfo.IsDirect = false
registerInfo.DistrictFrom = registermod.DistrictFromPC
registerInfo.DistrictCode = testUser.DistrictCode
}
}
if registerInfo.IP != "" {
if registerInfo.DistrictCode != "" {
channelInfo, _ := channelmod.FindOneChannelInfo(ctx, bson.M{channelmod.FN.ChannelInfo.Channel: registerInfo.DistrictCode})
if channelInfo == nil || channelInfo.Channel != registerInfo.DistrictCode {
registerInfo.DistrictCode = ""
}
}
if registerInfo.DistrictCode == "" {
// 根据IP查询是否下载
ipChecked = true
data, _ := downloadinfo.FindOneChannelIP(ctx, registerInfo.AppId, registerInfo.IP, registerInfo.SysType)
if data.DC != "" {
log.Info("channel info by IP ", log.Any("IP", registerInfo.IP), log.Any("app", registerInfo.AppId), log.Any("dc", data.DC))
registerInfo.DistrictFrom = registermod.DistrictFromIP
registerInfo.IpMatched = true
registerInfo.DistrictCode = data.DC
}
}
}
if registerInfo.DistrictCode != "" && registerInfo.DistrictFrom == registermod.DistrictFromNone {
registerInfo.DistrictFrom = registermod.DistrictFromDC
}
if !ipChecked && registerInfo.IP != "" {
data, _ := downloadinfo.FindOneChannelIP(ctx, registerInfo.AppId, registerInfo.IP, registerInfo.SysType)
if data.DC != "" {
registerInfo.IpMatched = true
}
}
registerInfo.BlocDeduction = false // 不扣量
// 插入记录
return registermod.AddRegisterLog(ctx, &registerInfo)
}
// 2.用户注册消息处理函数
func FormalUserRegisterHandler(registerMsg *topicmessage.UserRegisterMsg) error {
ctx := context.Background()
registerInfo := registermod.RegisterMod{
UserId: registerMsg.UserId,
AppId: registerMsg.AppId,
SysType: registerMsg.SysType,
DevType: registerMsg.DevType,
Mobile: registerMsg.Mobile,
Name: registerMsg.Name,
IP: registerMsg.IP,
IsDirect: true,
DistrictCode: strings.TrimSpace(registerMsg.DistrictCode),
DC: strings.TrimSpace(registerMsg.DistrictCode),
DistrictFrom: registermod.DistrictFromNone,
PromCode: registerMsg.PromCode,
IsFormal: true,
FormalTime: registerMsg.RegisterTime,
RegisterTime: registerMsg.RegisterTime,
ParentPromCode: registerMsg.PromSeqe,
IsDeduction: false,
}
// 检测
testUser, err := registermod.GetRegisterInfo(ctx, registerInfo.UserId, registerInfo.AppId)
if err != nil {
log.Error("FormalUserRegisterHandler define err", log.Any("UserId", testUser.UserId), log.E(err))
return err
}
if testUser.UserId == registerInfo.UserId {
if testUser.RegisterTime.Before(registerInfo.FormalTime) {
// 更新
setdoc := bson.M{"$set": bson.M{
"formalTime": registerInfo.FormalTime,
"isFormal": true,
}}
filter := bson.M{
"appId": registerInfo.AppId,
"userId": registerInfo.UserId,
}
err := registermod.UpdateOneRegister(ctx, filter, setdoc)
if err != nil {
log.Error("NewCountMethod UpdateManyRegister ", log.E(err))
}
} else {
log.Error("FormalUserRegisterHandler register or formal time err", log.Any("AppId", testUser.AppId), log.Any("UserId", testUser.UserId), log.Any("RegisterTime", testUser.RegisterTime), log.Any("FormalTime", registerInfo.FormalTime))
return nil
}
} else {
// 插入
if len(registerInfo.ParentPromCode) > 0 && len(registerInfo.DistrictCode) == 0 {
testUser, err := registermod.GetRegisterInfoByInviteCode(ctx, registerInfo.ParentPromCode, registerInfo.AppId)
if err != nil || testUser.PromCode != registerInfo.ParentPromCode {
log.Error("GetRegisterInfoByInviteCode define err", log.Any("UserId", testUser.UserId), log.E(err))
} else {
// 推广相关逻辑
registerInfo.IsDirect = false
registerInfo.DistrictFrom = registermod.DistrictFromPC
registerInfo.DistrictCode = testUser.DistrictCode
}
} else if registerInfo.DistrictCode == "" && registerInfo.IP != "" {
data, _ := downloadinfo.FindOneChannelIP(ctx, registerInfo.AppId, registerInfo.IP, registerInfo.SysType)
if data.DC != "" {
log.Info("channel info by IP ", log.Any("IP", registerInfo.IP), log.Any("app", registerInfo.AppId), log.Any("dc", data.DC))
registerInfo.DistrictFrom = registermod.DistrictFromIP
registerInfo.DistrictCode = data.DC
}
}
registerInfo.BlocDeduction = false // 不扣量
// 插入记录
return registermod.AddRegisterLog(ctx, &registerInfo)
}
return nil
}
// 3.用户充值消息处理函数
func UserReChargeHandler(rechargeMsg *topicmessage.UserRechargeMsg) error {
ctx := context.Background()
rechargeInfo := rechargemod.RechargeMod{
UserId: rechargeMsg.UserId,
AppId: rechargeMsg.AppId,
PlatformId: rechargeMsg.PlatformId,
SysType: rechargeMsg.SysType,
DevType: rechargeMsg.DevType,
ChannelName: rechargeMsg.ChannelName,
CID: rechargeMsg.CID,
Type: rechargeMsg.Type,
OrderId: rechargeMsg.OrderId,
Money: rechargeMsg.Money,
PayMoney: rechargeMsg.PayMoney,
Status: 3,
Rate: rechargeMsg.Rate,
SuccessAt: rechargeMsg.SuccessAt,
Repeat: false,
}
// 检测订单是否存在
testRechargere, err := rechargemod.FindRechargeByOrder(ctx, rechargeInfo.AppId, rechargeInfo.OrderId)
if err != nil || testRechargere.OrderId == rechargeInfo.OrderId {
log.Error("UserReChargeHandler define err ", log.Any("OrderId", testRechargere.OrderId), log.E(err))
return err
}
if testRechargere.OrderId == rechargeInfo.OrderId {
return nil
}
// 查找用户信息
userInfo, err := registermod.GetRegisterInfo(context.Background(), rechargeInfo.UserId, rechargeInfo.AppId)
if err != nil {
log.Error("UserReChargeHandler GetRegisterInfo failed", log.Any("UserId", rechargeInfo.UserId), log.Any("AppId", rechargeInfo.AppId), log.Any("UserId", userInfo.UserId), log.E(err))
return err
}
if userInfo.UserId == rechargeInfo.UserId {
rechargeInfo.IsDirect = userInfo.IsDirect
rechargeInfo.DistrictCode = userInfo.DistrictCode
rechargeInfo.RegisterTime = userInfo.RegisterTime
rechargeInfo.PromCode = userInfo.PromCode
rechargeInfo.IsFormalUser = userInfo.IsFormal
// 判断用户是否充值
hasRechargere, _ := rechargemod.FindRechargeByUser(ctx, rechargeInfo.AppId, int64(rechargeInfo.UserId))
if hasRechargere.UserId == rechargeInfo.UserId {
rechargeInfo.Repeat = true
}
}
// 更新记录
err = rechargemod.AddRechargeLog(ctx, &rechargeInfo)
if err != nil {
log.Error("UserRegisterHandler AddRechargeLog err", log.E(err))
return err
}
// 集团
return RechargeChannelHandle(ctx, &rechargeInfo)
//fmt.Println("DistrictCode kafka recharge queue:", rechargeInfo)
}
// 4.用户提现
func UserWithdrawOrder(UserWithdrawMsg *topicmessage.UserWithdrawMsg) error {
ctx := context.Background()
withdrawInfo := withdrawUserOrdermod.UserWithdrawOrder{
AppId: UserWithdrawMsg.AppId,
UID: UserWithdrawMsg.UID,
Name: UserWithdrawMsg.Name,
OID: UserWithdrawMsg.OID,
Money: UserWithdrawMsg.Money,
PayMoney: UserWithdrawMsg.PayMoney,
PayType: UserWithdrawMsg.PayType,
ActName: UserWithdrawMsg.ActName,
Act: UserWithdrawMsg.Act,
DeviceType: UserWithdrawMsg.DeviceType,
DevID: UserWithdrawMsg.DevID,
Status: UserWithdrawMsg.Status,
SuccessAt: UserWithdrawMsg.SuccessAt,
UpdatedAt: UserWithdrawMsg.UpdatedAt,
CreatedAt: UserWithdrawMsg.CreatedAt,
}
userInfo, err := registermod.GetRegisterInfo(ctx, UserWithdrawMsg.UID, UserWithdrawMsg.AppId)
if err != nil {
log.Error("rechargeDeduction GetRegisterInfo failed", log.Any("UserId", UserWithdrawMsg.UID), log.Any("AppId", UserWithdrawMsg.UID), log.Any("UserId", userInfo.UserId), log.E(err))
return err
}
// 兼容找不到用户
if userInfo.UserId == UserWithdrawMsg.UID {
withdrawInfo.IsDirect = userInfo.IsDirect
withdrawInfo.DistrictCode = userInfo.DistrictCode
withdrawInfo.PromCode = userInfo.PromCode
withdrawInfo.IsFormalUser = userInfo.IsFormal
} else {
withdrawInfo.IsDirect = true
}
// 更新记录
_, err = withdrawUserOrdermod.AddWithdrawOrder(ctx, &withdrawInfo)
if err != nil {
log.Error("UserWithdrawOrder AddWithdrawOrder: %s\n", log.E(err))
return err
}
return nil
//fmt.Println("DistrictCode kafka withdraw queue:", withdrawInfo)
}
// 4.充值全量
func UserSyncRechargeOrderALL(rechargeOrder *topicmessage.UserRechargeMsg) error {
ctx := context.Background()
rechargeInfo := &syncrecharge.SyncRechargeOrder{
UserId: rechargeOrder.UserId,
AppId: rechargeOrder.AppId,
PlatformId: rechargeOrder.PlatformId,
SysType: rechargeOrder.SysType,
DevType: rechargeOrder.DevType,
ChannelName: rechargeOrder.ChannelName,
CID: rechargeOrder.CID,
Type: rechargeOrder.Type,
OrderId: rechargeOrder.OrderId,
Money: rechargeOrder.Money,
PayMoney: rechargeOrder.PayMoney,
Status: rechargeOrder.Status,
Rate: rechargeOrder.Rate,
SuccessAt: rechargeOrder.SuccessAt,
CreatedAt: rechargeOrder.CreatedAt,
}
userInfo, err := registermod.GetRegisterInfo(ctx, rechargeOrder.UserId, rechargeOrder.AppId)
if err != nil {
return err
}
if userInfo.UserId == rechargeInfo.UserId {
rechargeInfo.IsDirect = userInfo.IsDirect
rechargeInfo.DistrictCode = userInfo.DistrictCode
rechargeInfo.PromCode = userInfo.PromCode
rechargeInfo.IsFormalUser = userInfo.IsFormal
} else {
rechargeInfo.IsDirect = true
}
if rechargeInfo.AppId == 494 {
log.Error("InsertOneSyncRechargeOrder@@@", zap.Any("rechargeInfo", rechargeInfo))
orderinfo, _ := syncrecharge.FindOneSyncRechargeOrder(ctx, bson.M{syncrecharge.FN.SyncRechargeOrder.AppId: rechargeInfo.AppId,
syncrecharge.FN.SyncRechargeOrder.UserId: rechargeInfo.UserId, syncrecharge.FN.SyncRechargeOrder.PlatformId: rechargeInfo.PlatformId})
if rechargeInfo.PlatformId == orderinfo.PlatformId {
log.Error("InsertOneSyncVip repeat", zap.Any("rechargeInfo", rechargeInfo))
return nil
}
}
return syncrecharge.InsertOneSyncRechargeOrder(ctx, rechargeInfo)
}
// 4.提现全量
func UserSyncWithdrawOrderALL(UserWithdrawMsg *topicmessage.UserWithdrawMsg) error {
ctx := context.Background()
withdrawOrder := &syncwithdraw.SyncWithdraw{
AppId: UserWithdrawMsg.AppId,
UID: UserWithdrawMsg.UID,
Name: UserWithdrawMsg.Name,
OID: UserWithdrawMsg.OID,
Money: UserWithdrawMsg.Money,
PayMoney: UserWithdrawMsg.PayMoney,
PayType: UserWithdrawMsg.PayType,
ActName: UserWithdrawMsg.ActName,
Act: UserWithdrawMsg.Act,
DeviceType: UserWithdrawMsg.DeviceType,
DevID: UserWithdrawMsg.DevID,
Status: UserWithdrawMsg.Status,
SuccessAt: UserWithdrawMsg.SuccessAt,
UpdatedAt: UserWithdrawMsg.UpdatedAt,
CreatedAt: UserWithdrawMsg.CreatedAt,
}
userInfo, err := registermod.GetRegisterInfo(ctx, withdrawOrder.UID, withdrawOrder.AppId)
if err != nil {
log.Error("UserSyncWithdrawOrderALL GetRegisterInfo failed", log.Any("UserId", withdrawOrder.UID), log.Any("AppId", withdrawOrder.AppId), log.Any("UserId", userInfo.UserId), log.E(err))
return err
}
if userInfo.UserId != withdrawOrder.UID {
log.Error("UserSyncWithdrawOrderALL GetRegisterInfo failed", log.Any("UserId", withdrawOrder.UID), log.Any("AppId", withdrawOrder.AppId), log.Any("UserId", userInfo.UserId), log.E(err))
}
withdrawOrder.IsDirect = userInfo.IsDirect
withdrawOrder.DistrictCode = userInfo.DistrictCode
withdrawOrder.PromCode = userInfo.PromCode
withdrawOrder.IsFormalUser = userInfo.IsFormal
err = syncwithdraw.InsertOneSyncWithdraw(ctx, withdrawOrder)
if err != nil {
log.Error("UserSyncWithdrawOrderALL InsertOneSyncWithdraw failed", log.Any("UserId", withdrawOrder.UID), log.Any("AppId", withdrawOrder.AppId), log.Any("UserId", userInfo.UserId), log.E(err))
}
return err
}
// 5.VIP相关处理
func UserSyncVIPOrderALL(UserBuyVipwMsg *topicmessage.UserBuyVIPMsg) error {
ctx := context.Background()
buyVipOrder := &syncvip.SyncVip{
PlatformId: UserBuyVipwMsg.PlatformId,
AppId: UserBuyVipwMsg.AppId,
UID: UserBuyVipwMsg.UID,
Name: UserBuyVipwMsg.Name,
Money: UserBuyVipwMsg.Money, // 单位转换成分
Amount: UserBuyVipwMsg.Amount * 100, // 单位转换成分
BuyVIPType: UserBuyVipwMsg.BuyVIPType,
OID: UserBuyVipwMsg.OID,
DeviceType: UserBuyVipwMsg.DeviceType,
Status: UserBuyVipwMsg.Status,
Repeat: false,
SuccessAt: UserBuyVipwMsg.SuccessAt,
IsDeduction: false,
}
userInfo, err := registermod.GetRegisterInfo(ctx, UserBuyVipwMsg.UID, UserBuyVipwMsg.AppId)
if err != nil {
log.Error("UserSyncVIPOrderALL GetRegisterInfo failed", log.Any("UserId", UserBuyVipwMsg.UID), log.Any("AppId", UserBuyVipwMsg.AppId), log.Any("UserId", userInfo.UserId), log.E(err))
return err
}
if userInfo.UserId == buyVipOrder.UID {
buyVipOrder.IsDirect = userInfo.IsDirect
buyVipOrder.DistrictCode = userInfo.DistrictCode
buyVipOrder.PromCode = userInfo.PromCode
buyVipOrder.RegisterTime = userInfo.RegisterTime
buyVipOrder.IsFormalUser = userInfo.IsFormal
orderinfo, _ := syncvip.FindOneSyncVip(ctx, bson.M{syncvip.FN.SyncVip.AppId: buyVipOrder.AppId, syncvip.FN.SyncVip.UID: buyVipOrder.UID})
if orderinfo.UID == buyVipOrder.UID {
buyVipOrder.Repeat = true
}
}
if buyVipOrder.AppId == 494 {
log.Error("InsertOneSyncVip@@@", zap.Any("buyVipOrder", buyVipOrder))
orderinfo, _ := syncvip.FindOneSyncVip(ctx, bson.M{syncvip.FN.SyncVip.AppId: buyVipOrder.AppId,
syncvip.FN.SyncVip.UID: buyVipOrder.UID, syncvip.FN.SyncVip.PlatformId: buyVipOrder.PlatformId})
if orderinfo.PlatformId == buyVipOrder.PlatformId {
log.Error("InsertOneSyncVip repeat", zap.Any("buyVipOrder", buyVipOrder))
return nil
}
}
err = syncvip.InsertOneSyncVip(ctx, buyVipOrder)
if err != nil {
log.Error("UserSyncVIPOrderALL InsertOneSyncVip failed", log.Any("UserId", UserBuyVipwMsg.UID), log.Any("AppId", UserBuyVipwMsg.AppId), log.Any("UserId", userInfo.UserId), log.E(err))
}
return err
}
// 7.游戏充值
func GameReChargeHandler(rechargeMsg *topicmessage.UserRechargeMsg) error {
ctx := context.Background()
rechargeInfo := rechargegamemod.GameRechargeMod{
UserId: rechargeMsg.UserId,
AppId: rechargeMsg.AppId,
PlatformId: rechargeMsg.PlatformId,
SysType: rechargeMsg.SysType,
DevType: rechargeMsg.DevType,
ChannelName: rechargeMsg.ChannelName,
CID: rechargeMsg.CID,
Type: rechargeMsg.Type,
OrderId: rechargeMsg.OrderId,
Money: rechargeMsg.Money,
PayMoney: rechargeMsg.PayMoney,
Status: 3,
SyncStatus: rechargeMsg.SyncStatus,
Rate: rechargeMsg.Rate,
SuccessAt: rechargeMsg.SuccessAt,
Repeat: false,
}
// 检测订单是否存在
testRechargere, err := rechargegamemod.FindRechargeByOrder(ctx, rechargeInfo.AppId, rechargeInfo.OrderId)
if err != nil {
return err
}
if testRechargere.OrderId == rechargeInfo.OrderId {
return nil // 订单已存在
}
// 查找用户信息
userInfo, err := registermod.GetRegisterInfo(context.Background(), rechargeInfo.UserId, rechargeInfo.AppId)
if err != nil {
log.Error("UserAccessHandler GetRegisterInfo failed", log.Any("UserId", rechargeInfo.UserId), log.Any("AppId", rechargeInfo.AppId), log.Any("UserId", userInfo.UserId), log.E(err))
return err
}
if userInfo.UserId == rechargeInfo.UserId {
rechargeInfo.IsDirect = userInfo.IsDirect
rechargeInfo.DistrictCode = userInfo.DistrictCode
rechargeInfo.RegisterTime = userInfo.RegisterTime
rechargeInfo.PromCode = userInfo.PromCode
rechargeInfo.IsFormalUser = userInfo.IsFormal
// 判断用户是否充值
hasRechargere, _ := rechargegamemod.FindRechargeByUser(ctx, rechargeInfo.AppId, int64(rechargeInfo.UserId))
if hasRechargere.UserId == rechargeInfo.UserId {
rechargeInfo.Repeat = true
}
}
// 更新记录
err = rechargegamemod.AddRechargeLog(ctx, &rechargeInfo)
if err != nil {
log.Error("UserRegisterHandler AddRechargeLog err", log.E(err))
return err
}
return nil
// 集团
// RechargeChannelHandle(ctx, &rechargeInfo)
//fmt.Println("DistrictCode kafka recharge queue:", rechargeInfo)
}
// 8.游戏充值全量
func GameRechargeOrderALL(rechargeOrder *topicmessage.UserRechargeMsg) error {
ctx := context.Background()
rechargeInfo := &syncgamerecharge.SyncGameRechargeOrder{
UserId: rechargeOrder.UserId,
AppId: rechargeOrder.AppId,
PlatformId: rechargeOrder.PlatformId,
SysType: rechargeOrder.SysType,
DevType: rechargeOrder.DevType,
ChannelName: rechargeOrder.ChannelName,
CID: rechargeOrder.CID,
Type: rechargeOrder.Type,
OrderId: rechargeOrder.OrderId,
Money: rechargeOrder.Money,
PayMoney: rechargeOrder.PayMoney,
Status: rechargeOrder.Status,
Rate: rechargeOrder.Rate,
SyncStatus: rechargeOrder.SyncStatus,
SuccessAt: rechargeOrder.SuccessAt,
CreatedAt: rechargeOrder.CreatedAt,
}
userInfo, err := registermod.GetRegisterInfo(ctx, rechargeOrder.UserId, rechargeOrder.AppId)
if err != nil {
return err
}
if userInfo.UserId == rechargeInfo.UserId {
rechargeInfo.IsDirect = userInfo.IsDirect
rechargeInfo.DistrictCode = userInfo.DistrictCode
rechargeInfo.PromCode = userInfo.PromCode
rechargeInfo.IsFormalUser = userInfo.IsFormal
} else {
rechargeInfo.IsDirect = true
}
err = syncgamerecharge.InsertOneSyncGameRechargeOrder(ctx, rechargeInfo)
if err != nil {
log.Error("GameRechargeOrderALL InsertOneSyncRechargeOrder failed", log.Any("UserId", rechargeOrder.UserId), log.Any("AppId", rechargeOrder.AppId), log.Any("UserId", userInfo.UserId), log.E(err))
}
return err
}
// 9.游戏提现
func GameWithdrawOrder(UserWithdrawMsg *topicmessage.UserWithdrawMsg) error {
ctx := context.Background()
withdrawInfo := withdrawGameOrdermod.GameWithdrawOrder{
AppId: UserWithdrawMsg.AppId,
UID: UserWithdrawMsg.UID,
Name: UserWithdrawMsg.Name,
OID: UserWithdrawMsg.OID,
Money: UserWithdrawMsg.Money,
PayMoney: UserWithdrawMsg.PayMoney,
PayType: UserWithdrawMsg.PayType,
ActName: UserWithdrawMsg.ActName,
Act: UserWithdrawMsg.Act,
DeviceType: UserWithdrawMsg.DeviceType,
DevID: UserWithdrawMsg.DevID,
Status: UserWithdrawMsg.Status,
SuccessAt: UserWithdrawMsg.SuccessAt,
SyncStatus: UserWithdrawMsg.SyncStatus,
PayChannel: UserWithdrawMsg.PayChannel,
UpdatedAt: UserWithdrawMsg.UpdatedAt,
CreatedAt: UserWithdrawMsg.CreatedAt,
}
userInfo, err := registermod.GetRegisterInfo(ctx, UserWithdrawMsg.UID, UserWithdrawMsg.AppId)
if err != nil {
log.Error("rechargeDeduction GetRegisterInfo failed", log.Any("UserId", UserWithdrawMsg.UID), log.Any("AppId", UserWithdrawMsg.UID), log.Any("UserId", userInfo.UserId), log.E(err))
return err
}
// 兼容找不到用户
if userInfo.UserId == UserWithdrawMsg.UID {
withdrawInfo.IsDirect = userInfo.IsDirect
withdrawInfo.DistrictCode = userInfo.DistrictCode
withdrawInfo.PromCode = userInfo.PromCode
withdrawInfo.IsFormalUser = userInfo.IsFormal
} else {
withdrawInfo.IsDirect = true
}
// 更新记录
_, err = withdrawGameOrdermod.AddWithdrawOrder(ctx, &withdrawInfo)
if err != nil {
log.Error("UserWithdrawOrder AddWithdrawOrder: %s\n", log.E(err))
}
return err
//fmt.Println("DistrictCode kafka withdraw queue:", withdrawInfo)
}
// 10.游戏提现
func UserGameWater(useGamerWater *topicmessage.UserGameWaterMsg) error {
ctx := context.Background()
now := time.Now()
gameWaterInfo := syncgamewater.SyncGameWater{
AppId: useGamerWater.AppId,
UserId: useGamerWater.UID,
PlatformId: useGamerWater.PlatformId,
Date: useGamerWater.Date,
ValidBet: useGamerWater.ValidBet,
Profit: useGamerWater.Profit,
Revenue: useGamerWater.Revenue,
CreatedAt: now,
}
userInfo, err := registermod.GetRegisterInfo(ctx, useGamerWater.UID, useGamerWater.AppId)
if err != nil {
log.Error("rechargeDeduction GetRegisterInfo failed", log.Any("UserId", useGamerWater.UID), log.Any("AppId", useGamerWater.UID), log.Any("UserId", userInfo.UserId), log.E(err))
return err
}
// 兼容找不到用户
if userInfo.UserId == useGamerWater.UID {
gameWaterInfo.IsDirect = userInfo.IsDirect
gameWaterInfo.DistrictCode = userInfo.DistrictCode
gameWaterInfo.PromCode = userInfo.PromCode
gameWaterInfo.RegisterTime = userInfo.RegisterTime
gameWaterInfo.IsFormalUser = userInfo.IsFormal
} else {
gameWaterInfo.IsDirect = true
}
// 更新记录
err = syncgamewater.InsertOneSyncGameWater(ctx, &gameWaterInfo)
if err != nil {
log.Error("UserWithdrawOrder AddWithdrawOrder: %s\n", log.E(err))
}
return err
//fmt.Println("DistrictCode kafka gamewater queue:", gameWaterInfo)
}
func RunSyncJob(ctx context.Context) error {
recoredResult, err := syncrecord.LastRecordTimeMap(ctx) //第一次从默认记录时间开始统计
if err != nil {
log.Error("statdistrict RunStat lastRecordTime failed!", log.E(err))
return err
}
for _, record := range recoredResult {
log.Info("RunSyncJob start", log.Any("start", time.Now()),
log.Any("record", record.AppId), log.Any("job", record.Job))
if record.Disabled {
continue
}
if !statg.GlobalEndpoints.GetIndexEndpoints(statg.Conf.ServerIndex).ExistsByAppId(record.AppId) {
continue // 不属于这个服务器的APP
}
// 任务开始
statutils.GlobalSyncMonitor.SetData(record.AppId, record.Job, func(item *statutils.SyncItem) {
item.SetJobBegin(time.Now())
item.CurrentCount = 0
item.Error = nil
})
err := processJobItem(ctx, &record)
// 任务结束
statutils.GlobalSyncMonitor.SetData(record.AppId, record.Job, func(item *statutils.SyncItem) {
item.Error = err
item.SetJobEnd(time.Now())
})
if err != nil {
var cErr *ContinueError
if !errors.As(err, &cErr) {
log.Info("RunSyncJob end err", log.Any("err", err))
// return err
}
}
log.Info("RunSyncJob end", log.Any("end", time.Now()),
log.Any("record", record.AppId), log.Any("job", record.Job))
}
return nil
}
func processJobRequest(ctx context.Context, record *syncrecord.SyncRecord) (*topicmessage.StatcenterSyncResp, error) {
request := &topicmessage.StatcenterSyncReq{
Job: record.Job,
UserId: record.UserId,
PlatformId: record.PlatformId,
SuccessTime: record.SuccessAt,
MaxSize: 5000,
}
url, err := AppCallbackUrl(record.AppId)
if err != nil {
return nil, err
}
apiUrl := url + "/api/web/statcenter/sync"
var resp *topicmessage.StatcenterSyncResp
if err = HttpRequest(ctx, apiUrl, &request, &resp); err != nil {
return nil, err
}
if resp.Code != 200 {
return nil, NewContinueError(fmt.Sprintf("http request code %d, url: %s", resp.Code, apiUrl))
}
return resp, nil
}
func processJobItem(ctx context.Context, record *syncrecord.SyncRecord) error {
// 请求开始
statutils.GlobalSyncMonitor.SetData(record.AppId, record.Job, func(item *statutils.SyncItem) {
item.RequestBegin = time.Now()
})
resp, err := processJobRequest(ctx, record)
if err != nil {
return err
}
// 请求结束
statutils.GlobalSyncMonitor.SetData(record.AppId, record.Job, func(item *statutils.SyncItem) {
item.Error = err
item.RequestEnd = time.Now()
})
if err != nil {
return err
}
// 处理开始
statutils.GlobalSyncMonitor.SetData(record.AppId, record.Job, func(item *statutils.SyncItem) {
item.ProcessBegin = time.Now()
})
err = processJobData(ctx, record, resp, func(dataLatest time.Time, incValue int) {
// 处理中
statutils.GlobalSyncMonitor.SetData(record.AppId, record.Job, func(item *statutils.SyncItem) {
item.IncCount(int64(incValue))
item.DataLatest = dataLatest
})
})
// 处理结束
statutils.GlobalSyncMonitor.SetData(record.AppId, record.Job, func(item *statutils.SyncItem) {
item.ProcessEnd = time.Now()
item.Error = err
})
return err
}
func processJobData(ctx context.Context, record *syncrecord.SyncRecord, resp *topicmessage.StatcenterSyncResp, onItem func(dataLatest time.Time, incValue int)) error {
if record.Job == syncrecord.VisitJob {
for _, v := range resp.VisitList {
if err := UserAccessHandler(v); err != nil {
return err
}
record.UserId = v.UserId
record.SuccessAt = v.VisitAt
record.PlatformId = v.PlatformId
if err := syncrecord.UpsertItemRecord(ctx, record.Job, record.AppId, record.UserId, record.PlatformId, record.SuccessAt); err != nil {
log.Error("RunSyncJob 更新同步记录!", log.E(err))
return err
}
}
onItem(record.SuccessAt, len(resp.VisitList))
} else if record.Job == syncrecord.RechargeJob {
for _, v := range resp.RechargeList {
if err := UserReChargeHandler(v); err != nil {
return err
}
record.UserId = v.UserId
record.SuccessAt = v.SuccessAt
record.PlatformId = v.PlatformId
if err := syncrecord.UpsertItemRecord(ctx, record.Job, record.AppId, record.UserId, record.PlatformId, record.SuccessAt); err != nil {
log.Error("RunSyncJob 更新同步记录!", log.E(err))
return err // keep loop, nil
}
}
onItem(record.SuccessAt, len(resp.RechargeList))
} else if record.Job == syncrecord.RegisterJob {
for _, v := range resp.RegisterList {
if err := UserRegisterHandler(v); err != nil {
return err
}
record.PlatformId = v.PlatformId
record.SuccessAt = v.RegisterTime
record.UserId = v.UserId
if err := syncrecord.UpsertItemRecord(ctx, record.Job, record.AppId, record.UserId, record.PlatformId, record.SuccessAt); err != nil {
log.Error("RunSyncJob 更新同步记录!", log.E(err))
return err // keep loop, nil
}
}
onItem(record.SuccessAt, len(resp.RegisterList))
} else if record.Job == syncrecord.RegisterLightJob {
var InvalidDataCount int
var lastError error
for _, v := range resp.RegisterList {
if !statutils.IsClientH5OrLight(v.SysType, v.DevType) {
InvalidDataCount++
lastError = NewContinueError(fmt.Sprintf("系统类型不对,总无效:%d,Job:%s,App:%d,User:%d,SysType:%s,DevType:%s", InvalidDataCount, record.Job, record.AppId, v.UserId, v.SysType, v.DevType))
continue
}
if err := FormalUserRegisterHandler(v); err != nil {
return err
}
record.PlatformId = v.PlatformId
record.SuccessAt = v.RegisterTime
record.UserId = v.UserId
if err := syncrecord.UpsertItemRecord(ctx, record.Job, record.AppId, record.UserId, record.PlatformId, record.SuccessAt); err != nil {
log.Error("RunSyncJob 更新同步记录!", log.E(err))
return err // keep loop, nil
}
}
onItem(record.SuccessAt, len(resp.RegisterList)-InvalidDataCount)
if lastError != nil {
return lastError
}
} else if record.Job == syncrecord.WithdrawJob {
for _, v := range resp.WithdrawList {
if err := UserWithdrawOrder(v); err != nil {
return err
}
record.UserId = v.UID
record.SuccessAt = v.SuccessAt
if err := syncrecord.UpsertItemRecord(ctx, record.Job, record.AppId, record.UserId, record.PlatformId, record.SuccessAt); err != nil {
log.Error("RunSyncJob 更新同步记录!", log.E(err))
return err // keep loop, nil
}
}
onItem(record.SuccessAt, len(resp.WithdrawList))
} else if record.Job == syncrecord.VipJob {
if record.AppId == 494 {
log.Error("debug@@@", zap.Any("resp", resp))
}
for _, v := range resp.VipList {
if err := UserSyncVIPOrderALL(v); err != nil {
return err
}
record.UserId = v.UID
record.SuccessAt = v.SuccessAt
if err := syncrecord.UpsertItemRecord(ctx, record.Job, record.AppId, record.UserId, record.PlatformId, record.SuccessAt); err != nil {
log.Error("RunSyncJob 更新同步记录!", log.E(err))
return err // keep loop, nil
}
}
onItem(record.SuccessAt, len(resp.VipList))
} else if record.Job == syncrecord.WithdrawAll {
for _, v := range resp.WithdrawList {
if err := UserSyncWithdrawOrderALL(v); err != nil {
return err
}
record.UserId = v.UID
record.SuccessAt = v.CreatedAt
if err := syncrecord.UpsertItemRecord(ctx, record.Job, record.AppId, record.UserId, record.PlatformId, record.SuccessAt); err != nil {
log.Error("RunSyncJob 更新同步记录!", log.E(err))
return err // keep loop, nil
}
}
onItem(record.SuccessAt, len(resp.WithdrawList))
} else if record.Job == syncrecord.RechargeAll {
if record.AppId == 494 {
log.Error("debug@@@", zap.Any("resp", resp))
}
for _, v := range resp.RechargeList {
if err := UserSyncRechargeOrderALL(v); err != nil {
return err
}
record.UserId = v.UserId
record.SuccessAt = v.CreatedAt
if err := syncrecord.UpsertItemRecord(ctx, record.Job, record.AppId, record.UserId, record.PlatformId, record.SuccessAt); err != nil {
log.Error("RunSyncJob 更新同步记录!", log.E(err))
return err // keep loop, nil
}
}
onItem(record.SuccessAt, len(resp.RechargeList))
} else if record.Job == syncrecord.GameRechargeOrder {
for _, v := range resp.GameRecharge {
if err := GameReChargeHandler(v); err != nil {
return err
}
record.UserId = v.UserId
record.SuccessAt = v.SuccessAt
record.PlatformId = v.PlatformId
if err := syncrecord.UpsertItemRecord(ctx, record.Job, record.AppId, record.UserId, record.PlatformId, record.SuccessAt); err != nil {
log.Error("RunSyncJob 更新同步记录!", log.E(err))
return err // keep loop, nil
}
}
onItem(record.SuccessAt, len(resp.GameRecharge))
} else if record.Job == syncrecord.GameRechargeOrderAll {
for _, v := range resp.GameRecharge {
if err := GameRechargeOrderALL(v); err != nil {
return err
}
record.UserId = v.UserId
record.SuccessAt = v.CreatedAt
if err := syncrecord.UpsertItemRecord(ctx, record.Job, record.AppId, record.UserId, record.PlatformId, record.SuccessAt); err != nil {
log.Error("RunSyncJob 更新同步记录!", log.E(err))
return err // keep loop, nil
}
}
onItem(record.SuccessAt, len(resp.GameRecharge))
} else if record.Job == syncrecord.GameWithdrawOrder {
for _, v := range resp.GameWithdraw {
if err := GameWithdrawOrder(v); err != nil {
return err
}
record.UserId = v.UID
record.SuccessAt = v.SuccessAt
if err := syncrecord.UpsertItemRecord(ctx, record.Job, record.AppId, record.UserId, record.PlatformId, record.SuccessAt); err != nil {
log.Error("RunSyncJob 更新同步记录!", log.E(err))
return err // keep loop, nil
}
}
onItem(record.SuccessAt, len(resp.GameWithdraw))
} else if record.Job == syncrecord.GameWater {
for _, v := range resp.GameWater {
if err := UserGameWater(v); err != nil {
return err
}
record.UserId = v.UID
record.SuccessAt = v.SuccessAt
if err := syncrecord.UpsertItemRecord(ctx, record.Job, record.AppId, record.UserId, record.PlatformId, record.SuccessAt); err != nil {
log.Error("RunSyncJob 更新同步记录!", log.E(err))
return err // keep loop, nil
}
}
onItem(record.SuccessAt, len(resp.GameWater))
} else {
log.Error("RunSyncJob 类型不支持!", log.Any("job", record.Job))
return errors.New("RunSyncJob 类型不支持")
}
return nil
}
// 周期执行
func SyncInit() {
if statg.Conf.RunMod != consts.RunModRelease {
return
}
defer utils.RecoverError(nil)
ticker := time.NewTicker(time.Second * 20) // 20秒一次
ctx := context.Background()
for t := range ticker.C {
fmt.Println("Begin task", t)
// 定时去app拉去任务
if statg.Conf.KafkaServer {
statutils.ServerMon.RunWithName(func() error {
return RunSyncJob(ctx)
}, statutils.GetFuncName(RunSyncJob))
}
}
}
func AppCallbackUrl(appid int32) (url string, err error) {
info := statg.GlobalAppIdEndpoints[appid]
if info != nil {
if len(info.ServerUrl) == 0 {
return "", NewContinueError(fmt.Sprintf("appid %d is not config server url", appid))
}
return info.ServerUrl, nil
}
return "", NewContinueError("appid err")
}
+12
View File
@@ -0,0 +1,12 @@
package consumer
import (
"context"
downloadinfo "stat/models/downloadinfo"
)
func TrackDcAndPcByIp(ctx context.Context, appId int32, ip, sysType string) (dc, pc string) {
data, _ := downloadinfo.FindOneChannelIP(ctx, appId, ip, sysType)
return data.DC, data.PC
}
+32
View File
@@ -0,0 +1,32 @@
package consumer
import (
"context"
"stat/common/log"
"stat/common/utils"
"stat/models/rechargechannelmod"
"stat/models/rechargemod"
"strconv"
)
func RechargeChannelHandle(ctx context.Context, rechageInfo *rechargemod.RechargeMod) error {
sumDate := utils.BeginWithTime(rechageInfo.SuccessAt)
rate := float64(4)
if rechageInfo.Rate != "" && rechageInfo.ChannelName != "daichong" {
rateF, err := strconv.ParseFloat(rechageInfo.Rate, 64)
if err != nil {
log.Error("RechargeChannelHandle rate is err", log.Any("rate", rechageInfo.Rate), log.Any("userid", rechageInfo.UserId), log.Any("appid", rechageInfo.AppId))
return err
}
rate = rateF
} else if rechageInfo.ChannelName == "daichong" {
rate = float64(8)
rechageInfo.CID = "daichong"
}
channelMoney := int64((rate * float64(rechageInfo.PayMoney)) / 100)
err := rechargechannelmod.UpsertOneUserRechargeStat(ctx, rechageInfo.CID, rechageInfo.AppId, sumDate, rechageInfo.BlocDeduction, rechageInfo.Money, rechageInfo.PayMoney, channelMoney)
if err != nil {
log.Error("RechargeChannelHandle UpsertOneUserRechargeStat", log.Any("rate", rechageInfo.Rate), log.Any("userid", rechageInfo.UserId), log.Any("appid", rechageInfo.AppId), log.E(err))
}
return err
}
+26
View File
@@ -0,0 +1,26 @@
package consumer
import (
"context"
"stat/common/utils"
)
type ContinueError struct {
msg string
}
func NewContinueError(text string) error {
ret := &ContinueError{
msg: text,
}
return ret
}
func (e *ContinueError) Error() string {
return e.msg
}
// HttpRequest HttpRequest
func HttpRequest(ctx context.Context, url string, request interface{}, resp interface{}) error {
return utils.HttpPostRequestJson(url, request, resp)
}
+568
View File
@@ -0,0 +1,568 @@
package crossSer
import (
"context"
"fmt"
"sort"
"stat/api/proto"
"stat/common"
"stat/common/statutils"
"stat/common/tgbot"
"stat/common/utils"
"stat/models/botdailyhourstat"
"stat/statg"
"time"
"github.com/wcharczuk/go-chart/v2"
"github.com/wcharczuk/go-chart/v2/drawing"
"github.com/vicanso/go-charts/v2"
)
var Colors = []drawing.Color{
drawing.ColorFromHex("0780cf"),
drawing.ColorFromHex("765005"),
drawing.ColorFromHex("fa6d1d"),
drawing.ColorFromHex("0e2c82"),
drawing.ColorFromHex("b6b51f"),
drawing.ColorFromHex("da1f18"),
drawing.ColorFromHex("701866"),
drawing.ColorFromHex("f47a75"),
drawing.ColorFromHex("009db2"),
drawing.ColorFromHex("024b51"),
drawing.ColorFromHex("0780cf"),
drawing.ColorFromHex("765005"),
}
type CrossStatMethod = func(ctx context.Context, cmd *tgbot.Command) error
// 统计方法汇总
var CrossStatMethodMap = map[string]CrossStatMethod{
statutils.StatProjectRecharge: CrossStatRecharge,
statutils.StatProjectRechargeNew: CrossStatRechargeNew,
statutils.StatProjectRechargeOld: CrossStatRechargeOld,
statutils.StatProjectRechargeGold: CrossStatRechargeGold,
statutils.StatProjectRechargeVip: CrossStatRechargeVip,
statutils.StatProjectRegister: CrossStatRegister,
statutils.StatProjectVisit: CrossStatVisit,
statutils.StatProjectOrder: CrossStatOrder,
}
type CrossAppDownloadRes struct {
Code int32 `json:"code"`
Msg string `json:"msg"`
Tip string `json:"tip"`
Data *proto.PageButtonClickResp `json:"data"`
Time time.Time `json:"time"`
}
type CrossStatRes struct {
Code int32 `json:"code"`
Msg string `json:"msg"`
Tip string `json:"tip"`
Data *proto.BotDailyHourListResp `json:"data"`
Time time.Time `json:"time"`
}
type CrossMonthRes struct {
Code int32 `json:"code"`
Msg string `json:"msg"`
Tip string `json:"tip"`
Data *proto.BotMonthStatResp `json:"data"`
Time time.Time `json:"time"`
}
type StatItem struct {
Value int64
Stat *botdailyhourstat.BotHourStat
}
type FuncGetStatValue func(item *botdailyhourstat.BotHourStat) int64
type StatItemList []*StatItem
func (a StatItemList) Len() int { return len(a) }
func (a StatItemList) Less(i, j int) bool { return a[i].Value > a[j].Value }
func (a StatItemList) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func CrossMonthStat(ctx context.Context, cmd *tgbot.Command) error {
appId, _ := cmd.GetFlagInt32Value("-app")
monthStr := cmd.GetFlagStringValue("-month")
month, err := time.ParseInLocation("2006-01-02", monthStr, time.Local)
if err != nil {
return tgbot.NewUserError(cmd, "解析日期出错, 格式:\n"+cmd.Help+"\n"+cmd.Desc)
}
getValue := func(item *botdailyhourstat.BotHourStat) int64 {
return item.TodayStat.Recharge.Value()
}
statList := make(StatItemList, 0)
for _, addr := range statg.Conf.InnerServers {
req := &proto.BotMonthStatReq{Month: month}
if appId > 0 {
req.AppId = &appId
}
res := CrossMonthRes{}
err = InnerRequest(addr+"/api/stat/inner/botMonthStat", req, &res, true)
if err != nil {
return err
}
if res.Data == nil {
continue
}
for _, stat := range res.Data.List {
if statg.GlobalEndpoints.ExistsByAppId(stat.AppId) {
value := getValue(stat)
statList = append(statList, &StatItem{
Value: value,
Stat: stat,
})
}
}
}
// sort
sort.Sort(statList)
// 显示
y, m, _ := month.Date()
for _, item := range statList {
appName := statg.GlobalEndpoints.ExistsByAppId(item.Stat.AppId)
title := fmt.Sprintf("%d年%d月【%s】数据汇总", y, m, appName)
text := item.Stat.DisplaySummaryText(title)
if err = cmd.SendBack(text); err != nil {
return err
}
}
return nil
}
func CrossStatRecharge(ctx context.Context, cmd *tgbot.Command) error {
moveBackDays, _ := cmd.GetFlagInt64Value("-back")
if moveBackDays > 0 {
moveBackDays = moveBackDays * -1
}
// get stats
now := time.Now()
reportTime := utils.RecentMinute(now, 60)
if moveBackDays != 0 {
reportTime = utils.BeginWithTime(now.AddDate(0, 0, int(moveBackDays)+1))
}
title := GetDateDesc(0) + "充值排名:" + reportTime.Format("2006-01-02T15:04:05")
photo, _, err := GetRankPhoto(reportTime, title, func(item *botdailyhourstat.BotHourStat) int64 {
return item.TodayStat.Recharge.Value() / 100
})
if err != nil {
return err
}
cmd.PhotoPath = photo
return cmd.SendBack(fmt.Sprintf("充值排名:%s\n", reportTime.Format("2006-01-02T15:04:05")))
}
func CrossStatRechargeNew(ctx context.Context, cmd *tgbot.Command) error {
moveBackDays, _ := cmd.GetFlagInt64Value("-back")
if moveBackDays > 0 {
moveBackDays = moveBackDays * -1
}
// get stats
now := time.Now()
reportTime := utils.RecentMinute(now, 60)
if moveBackDays != 0 {
reportTime = utils.BeginWithTime(now.AddDate(0, 0, int(moveBackDays)+1))
}
title := GetDateDesc(0) + "新用户充值排名:" + reportTime.Format("2006-01-02T15:04:05")
photo, _, err := GetRankPhoto(reportTime, title, func(item *botdailyhourstat.BotHourStat) int64 {
return item.TodayStat.RechargeNew.Value() / 100
})
if err != nil {
return err
}
cmd.PhotoPath = photo
return cmd.SendBack(fmt.Sprintf("新用户充值排名:%s\n", reportTime.Format("2006-01-02T15:04:05")))
}
func CrossStatRechargeOld(ctx context.Context, cmd *tgbot.Command) error {
moveBackDays, _ := cmd.GetFlagInt64Value("-back")
if moveBackDays > 0 {
moveBackDays = moveBackDays * -1
}
// get stats
now := time.Now()
reportTime := utils.RecentMinute(now, 60)
if moveBackDays != 0 {
reportTime = utils.BeginWithTime(now.AddDate(0, 0, int(moveBackDays)+1))
}
title := GetDateDesc(0) + "老用户充值排名:" + reportTime.Format("2006-01-02T15:04:05")
photo, _, err := GetRankPhoto(reportTime, title, func(item *botdailyhourstat.BotHourStat) int64 {
return item.TodayStat.RechargeOld.Value() / 100
})
if err != nil {
return err
}
cmd.PhotoPath = photo
return cmd.SendBack(fmt.Sprintf("老用户充值排名:%s\n", reportTime.Format("2006-01-02T15:04:05")))
}
func CrossStatRechargeGold(ctx context.Context, cmd *tgbot.Command) error {
moveBackDays, _ := cmd.GetFlagInt64Value("-back")
if moveBackDays > 0 {
moveBackDays = moveBackDays * -1
}
// get stats
now := time.Now()
reportTime := utils.RecentMinute(now, 60)
if moveBackDays != 0 {
reportTime = utils.BeginWithTime(now.AddDate(0, 0, int(moveBackDays)+1))
}
title := GetDateDesc(0) + "金币充值排名:" + reportTime.Format("2006-01-02T15:04:05")
photo, _, err := GetRankPhoto(reportTime, title, func(item *botdailyhourstat.BotHourStat) int64 {
return item.TodayStat.RechargeGold.Value() / 100
})
if err != nil {
return err
}
cmd.PhotoPath = photo
return cmd.SendBack(fmt.Sprintf("金币充值排名:%s\n", reportTime.Format("2006-01-02T15:04:05")))
}
func CrossStatRechargeVip(ctx context.Context, cmd *tgbot.Command) error {
moveBackDays, _ := cmd.GetFlagInt64Value("-back")
if moveBackDays > 0 {
moveBackDays = moveBackDays * -1
}
// get stats
now := time.Now()
reportTime := utils.RecentMinute(now, 60)
if moveBackDays != 0 {
reportTime = utils.BeginWithTime(now.AddDate(0, 0, int(moveBackDays)+1))
}
title := GetDateDesc(0) + "会员充值排名:" + reportTime.Format("2006-01-02T15:04:05")
photo, _, err := GetRankPhoto(reportTime, title, func(item *botdailyhourstat.BotHourStat) int64 {
return item.TodayStat.RechargeVip.Value() / 100
})
if err != nil {
return err
}
cmd.PhotoPath = photo
return cmd.SendBack(fmt.Sprintf("会员充值排名:%s\n", reportTime.Format("2006-01-02T15:04:05")))
}
func CrossStatRegister(ctx context.Context, cmd *tgbot.Command) error {
moveBackDays, _ := cmd.GetFlagInt64Value("-back")
if moveBackDays > 0 {
moveBackDays = moveBackDays * -1
}
// get stats
now := time.Now()
reportTime := utils.RecentMinute(now, 60)
if moveBackDays != 0 {
reportTime = utils.BeginWithTime(now.AddDate(0, 0, int(moveBackDays)+1))
}
title := GetDateDesc(0) + "注册排名:" + reportTime.Format("2006-01-02T15:04:05")
photo, _, err := GetRankPhoto(reportTime, title, func(item *botdailyhourstat.BotHourStat) int64 {
return item.TodayStat.Register.Value()
})
if err != nil {
return err
}
cmd.PhotoPath = photo
return cmd.SendBack(fmt.Sprintf("注册排名:%s\n", reportTime.Format("2006-01-02T15:04:05")))
}
func CrossStatVisit(ctx context.Context, cmd *tgbot.Command) error {
moveBackDays, _ := cmd.GetFlagInt64Value("-back")
if moveBackDays > 0 {
moveBackDays = moveBackDays * -1
}
// get stats
now := time.Now()
reportTime := utils.RecentMinute(now, 60)
if moveBackDays != 0 {
reportTime = utils.BeginWithTime(now.AddDate(0, 0, int(moveBackDays)+1))
}
title := GetDateDesc(0) + "日活排名:" + reportTime.Format("2006-01-02T15:04:05")
photo, _, err := GetRankPhoto(reportTime, title, func(item *botdailyhourstat.BotHourStat) int64 {
return item.TodayStat.Visit.Value()
})
if err != nil {
return err
}
cmd.PhotoPath = photo
return cmd.SendBack(fmt.Sprintf("日活排名:%s\n", reportTime.Format("2006-01-02T15:04:05")))
}
func CrossStatOrder(ctx context.Context, cmd *tgbot.Command) error {
moveBackDays, _ := cmd.GetFlagInt64Value("-back")
if moveBackDays > 0 {
moveBackDays = moveBackDays * -1
}
// get stats
now := time.Now()
reportTime := utils.RecentMinute(now, 60)
if moveBackDays != 0 {
reportTime = utils.BeginWithTime(now.AddDate(0, 0, int(moveBackDays)+1))
}
title := GetDateDesc(0) + "订单排名:" + reportTime.Format("2006-01-02T15:04:05")
photo, _, err := GetRankPhoto(reportTime, title, func(item *botdailyhourstat.BotHourStat) int64 {
return item.TodayStat.Order.Count()
})
if err != nil {
return err
}
cmd.PhotoPath = photo
return cmd.SendBack(fmt.Sprintf("订单排名:%s\n", reportTime.Format("2006-01-02T15:04:05")))
}
func GetRankPhoto(reportTime time.Time, title string, getValue FuncGetStatValue) (string, []byte, error) {
charts.SetDefaultFont(statutils.Font)
statList := make(StatItemList, 0)
registerStatList := make(StatItemList, 0)
var total, registerTotal, visitTotal int64
for _, addr := range statg.Conf.InnerServers {
req := &proto.BotDailyHourListReq{
Page: common.Page{
PageNum: 1,
PageSize: 100,
},
SumDateStart: reportTime,
SumDateStop: reportTime,
}
res := CrossStatRes{}
err := InnerRequest(addr+"/api/stat/inner/botStatList", req, &res, true)
if err != nil {
return "", nil, err
}
if res.Data == nil {
continue
}
for _, stat := range res.Data.List {
if statg.GlobalEndpoints.GetBotRankEndpoints().ExistsByAppId(stat.AppId) {
value := getValue(stat)
total += value
statList = append(statList, &StatItem{
Value: value,
Stat: stat,
})
registerValue := stat.TodayStat.Register.Total.Value()
registerTotal += registerValue
visitTotal += stat.TodayStat.Visit.Total.Value()
registerStatList = append(registerStatList, &StatItem{
Value: registerValue,
Stat: stat,
})
}
}
}
// sort
sort.Sort(statList)
// draw chart
xNames := make([]string, 0)
for _, stat := range statList {
xNames = append(xNames, statg.GlobalEndpoints.GetAppShortNameById(stat.Stat.AppId))
}
sDatas := make([]charts.SeriesData, 0)
rDatas := make([]charts.SeriesData, 0)
for index, stat := range statList {
sDatas = append(sDatas, charts.SeriesData{
Value: float64(stat.Value),
Style: charts.Style{
FillColor: Colors[index%len(Colors)],
TextWrap: chart.TextWrapRune,
},
})
for _, registerStat := range registerStatList {
if stat.Stat.AppId == registerStat.Stat.AppId {
rDatas = append(rDatas, charts.SeriesData{
Value: float64(registerStat.Value),
Style: charts.Style{
FillColor: Colors[index%len(Colors)],
TextWrap: chart.TextWrapRune,
},
})
}
}
}
chartOpt := charts.ChartOption{
Width: statg.Conf.ChartsConfig.Width,
Height: statg.Conf.ChartsConfig.Height,
Type: charts.ChartOutputPNG,
Title: charts.TitleOption{
Text: title,
},
XAxis: charts.NewXAxisOption(xNames),
Legend: charts.LegendOption{
Data: []string{
fmt.Sprintf("应用:%d", len(xNames)),
fmt.Sprintf("充值:%d", total),
fmt.Sprintf("日活:%d", visitTotal),
fmt.Sprintf("注册:%d", registerTotal),
},
Icon: charts.IconRect,
Left: charts.PositionRight,
},
SeriesList: []charts.Series{
{
Type: charts.ChartTypeBar,
Data: sDatas,
Label: charts.SeriesLabel{
Show: true,
Position: charts.PositionBottom,
},
MarkLine: charts.NewMarkLine(
charts.SeriesMarkDataTypeAverage,
),
},
{
Type: charts.ChartTypeBar,
Data: rDatas,
Label: charts.SeriesLabel{
Show: true,
Position: charts.PositionBottom,
},
},
},
}
show := true
chartOpt.XAxis.Show = &show
d, err := charts.Render(chartOpt)
if err != nil {
return "", nil, err
}
buf, err := d.Bytes()
if err != nil {
return "", buf, err
}
photo := fmt.Sprintf("/tmp/%s.png", utils.UUID())
if err = statutils.WriteBytesToFile(buf, photo); err != nil {
return "", buf, err
}
return photo, buf, nil
}
func GetRankDetail(reportTime time.Time, title string, getValue FuncGetStatValue) (string, error) {
charts.SetDefaultFont(statutils.Font)
statList := make(StatItemList, 0)
registerStatList := make(StatItemList, 0)
var total, registerTotal int64
for _, addr := range statg.Conf.InnerServers {
req := &proto.BotDailyHourListReq{
Page: common.Page{
PageNum: 1,
PageSize: 100,
},
SumDateStart: reportTime,
SumDateStop: reportTime,
}
res := CrossStatRes{}
err := InnerRequest(addr+"/api/stat/inner/botStatList", req, &res, true)
if err != nil {
return "", err
}
if res.Data == nil {
continue
}
for _, stat := range res.Data.List {
if statg.GlobalEndpoints.GetBotRankEndpoints().ExistsByAppId(stat.AppId) {
value := getValue(stat)
total += value
statList = append(statList, &StatItem{
Value: value,
Stat: stat,
})
registerValue := stat.TodayStat.Register.Total.Value()
registerTotal += registerValue
registerStatList = append(registerStatList, &StatItem{
Value: registerValue,
Stat: stat,
})
}
}
}
// sort
sort.Sort(statList)
// draw chart
xNames := make([]string, 0)
for _, stat := range statList {
xNames = append(xNames, statg.GlobalEndpoints.GetAppShortNameById(stat.Stat.AppId))
}
sDatas := make([]charts.SeriesData, 0)
rDatas := make([]charts.SeriesData, 0)
for index, stat := range statList {
sDatas = append(sDatas, charts.SeriesData{
Value: float64(stat.Value),
Style: charts.Style{
FillColor: Colors[index%len(Colors)],
TextWrap: chart.TextWrapRune,
},
})
for _, registerStat := range registerStatList {
if stat.Stat.AppId == registerStat.Stat.AppId {
rDatas = append(rDatas, charts.SeriesData{
Value: float64(registerStat.Value),
Style: charts.Style{
FillColor: Colors[index%len(Colors)],
TextWrap: chart.TextWrapRune,
},
})
}
}
}
chartOpt := charts.ChartOption{
Width: statg.Conf.ChartsConfig.Width,
Height: statg.Conf.ChartsConfig.Height,
Type: charts.ChartOutputPNG,
Title: charts.TitleOption{
Text: title,
},
XAxis: charts.NewXAxisOption(xNames),
Legend: charts.LegendOption{
Data: []string{
fmt.Sprintf("应用:%d", len(xNames)),
fmt.Sprintf("充值:%d", total),
fmt.Sprintf("注册:%d", registerTotal),
},
Icon: charts.IconRect,
Left: charts.PositionRight,
},
SeriesList: []charts.Series{
{
Type: charts.ChartTypeBar,
Data: sDatas,
Label: charts.SeriesLabel{
Show: true,
Position: charts.PositionBottom,
},
MarkLine: charts.NewMarkLine(
charts.SeriesMarkDataTypeAverage,
),
},
{
Type: charts.ChartTypeBar,
Data: rDatas,
Label: charts.SeriesLabel{
Show: true,
Position: charts.PositionBottom,
},
},
},
}
show := true
chartOpt.XAxis.Show = &show
d, err := charts.Render(chartOpt)
if err != nil {
return "", err
}
buf, err := d.Bytes()
if err != nil {
return "", err
}
photo := fmt.Sprintf("/tmp/%s.png", utils.UUID())
if err = statutils.WriteBytesToFile(buf, photo); err != nil {
return "", err
}
return photo, nil
}
+103
View File
@@ -0,0 +1,103 @@
package crossSer
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"stat/common/crypt"
"stat/statg"
"strings"
"time"
)
func InnerCryptPost(requestURL string, postData []byte) (*http.Response, error) {
key := statg.Conf.InnerKey
enData, err := crypt.CoreAesEncryptEx(postData, 12, key)
if err != nil {
return nil, err
}
bodyReader := bytes.NewReader(enData)
req, err := http.NewRequest(http.MethodPost, requestURL, bodyReader)
if err != nil {
return nil, err
}
timeout := 30 * time.Second
if strings.Contains(requestURL, "botMonthStat") {
timeout = 15 * time.Minute
}
req.Header.Set("Content-Type", "application/octet-stream")
client := http.Client{
Timeout: timeout,
}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
enData, err = io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
deData, err := crypt.CoreAesDecryptEx(enData, 12, key)
if err != nil {
return resp, err
}
resp.Body = io.NopCloser(bytes.NewBuffer(deData))
resp.ContentLength = int64(len(deData))
return resp, nil
}
func InnerPlainPost(requestURL string, postData []byte) (*http.Response, error) {
bodyReader := bytes.NewReader(postData)
req, err := http.NewRequest(http.MethodPost, requestURL, bodyReader)
if err != nil {
return nil, err
}
timeout := 8 * time.Second
req.Header.Set("Content-Type", "application/json")
client := http.Client{
Timeout: timeout,
}
return client.Do(req)
}
func InnerRequest(url string, req interface{}, bind interface{}, crypt bool) error {
data, err := json.Marshal(req)
if err != nil {
return err
}
var resp *http.Response
if crypt {
resp, err = InnerCryptPost(url, data)
} else {
resp, err = InnerPlainPost(url, data)
}
if err != nil {
return err
}
if resp.StatusCode != 200 {
return fmt.Errorf("error status %s, http code %d", resp.Status, resp.StatusCode)
}
data, err = io.ReadAll(resp.Body)
if err != nil {
return err
}
return json.Unmarshal(data, bind)
}
func GetDateDesc(moveBackDays int) string {
if moveBackDays < 0 {
moveBackDays = moveBackDays * -1
}
switch moveBackDays {
case 0:
return "今天"
case 1:
return "昨天"
case 2:
return "前天"
default:
return fmt.Sprintf("前%d天", moveBackDays)
}
}
+100
View File
@@ -0,0 +1,100 @@
package dailystatadvertiseser
import (
"context"
"stat/common/utils"
"stat/models/dailystatadvertise"
"time"
"github.com/pkg/errors"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo/options"
)
//GetDailyStatAdvertise 按照参数搜索
func GetDailyStatAdvertise(ctx context.Context, param *DailyStatAdvertiseSearch) (*DailyStatAdvertiseSearchResp, error) {
filter := bson.M{}
opt := &options.FindOptions{}
resp := &DailyStatAdvertiseSearchResp{}
opt.SetSort(bson.M{dailystatadvertise.FN.DailyStatAdvertise.SumDate: -1})
opt.SetSkip((param.PageNum - 1) * param.PageSize)
opt.SetLimit(param.PageSize)
total, serr := dailystatadvertise.CountDailyStatAdvertises(ctx, filter)
if serr != nil {
return resp, serr
}
if total == 0 {
return resp, nil
}
list, serr := dailystatadvertise.FindManyDailyStatAdvertise(ctx, filter, opt)
if serr != nil {
return resp, serr
}
resp.List = list
resp.Total = total
return resp, nil
}
//AddGraphic 添加一个
func AddDailyStatAdvertise(ctx context.Context,
ac *dailystatadvertise.DailyStatAdvertise) error {
ac.AppName = "蜜桃传媒"
ac.SumDate = utils.BeginWithTime(ac.SumDate)
serr := dailystatadvertise.InsertOneDailyStatAdvertise(ctx, ac)
if serr != nil {
return serr
}
return nil
}
//DelDailyStatAdvertise 删除指定
func DelDailyStatAdvertise(ctx context.Context, id string) error {
objectId, err := primitive.ObjectIDFromHex(id)
if err != nil {
return err
}
adFilter := bson.M{dailystatadvertise.FN.DailyStatAdvertise.ID: objectId}
//删除
count, serr := dailystatadvertise.DelOneDailyStatAdvertise(ctx, adFilter)
if serr != nil {
return serr
}
if count == 0 {
return errors.Errorf("DelDailyStatAdvertise")
}
return nil
}
//UpdateDailyStatAdvertise 更新指定
func UpdateDailyStatAdvertise(ctx context.Context, id string,
info *DailyStatAdvertiseUpdate) error {
now := time.Now()
info.UpdatedAt = &now
if info.SumDate != nil {
sumdate := utils.BeginWithTime(*info.SumDate)
info.SumDate = &sumdate
}
objectId, err := primitive.ObjectIDFromHex(id)
if err != nil {
return err
}
adFilter := bson.M{dailystatadvertise.FN.DailyStatAdvertise.ID: objectId}
updateDoc := bson.M{"$set": info}
serr := dailystatadvertise.UpdateOneDailyStatAdvertise(ctx, adFilter, updateDoc)
if serr != nil {
return serr
}
return nil
}
+37
View File
@@ -0,0 +1,37 @@
package dailystatadvertiseser
import (
"stat/models/dailystatadvertise"
"time"
)
// ActressPhotoSearch 模特管理
type DailyStatAdvertiseSearch struct {
PageNum int64 `json:"pageNum"`
PageSize int64 `json:"pageSize"`
}
type DailyStatAdvertiseSearchResp struct {
Total int64 `json:"total"`
List dailystatadvertise.DailyStatAdvertiseList `json:"list"`
}
type DailyStatAdvertiseUpdate struct {
SumDate *time.Time `bson:"sumDate,omitempty" json:"sumDate,omitempty"` // 记录时间 按天对齐
AppId *int32 `bson:"appId,omitempty" json:"appId"` // appId
NewDirectCount *int64 `bson:"newDirectCount,omitempty" json:"newDirectCount"` // 新增直推
DirectVisitCount *int64 `bson:"directVisitCount,omitempty" json:"directVisitCount"` // 直推访问数
Day1RetainRate *string `bson:"day1RetainRate,omitempty" json:"day1RetainRate"` // 次日留存率
Day3RetainRate *string `bson:"day3RetainRate,omitempty" json:"day3RetainRate"` // 3日留存率
Day7RetainRate *string `bson:"day7RetainRate,omitempty" json:"day7RetainRate"` // 7日留存率
UpdatedAt *time.Time `json:"updatedAt" bson:"updatedAt,omitempty"` // 更新时间
}
type DailyStatAdvertiseUpdateReq struct {
Param DailyStatAdvertiseUpdate `json:"param" bson:"param"` // 参数
Id string `json:"id" bson:"_id" binding:"required"` // 视频Id
}
type DailyStatAdvertiseDelReq struct {
Id string `json:"id" bson:"_id" binding:"required"` // 视频Id
}
+875
View File
@@ -0,0 +1,875 @@
package domainser
import (
"context"
"fmt"
"math/rand"
"net/http"
"net/url"
"stat/common/log"
"stat/common/utils"
"stat/models/domainmod"
"stat/statg"
"strings"
"sync"
"time"
"go.mongodb.org/mongo-driver/bson"
"github.com/ddliu/go-httpclient"
)
const (
ApiKey = "a45d5d2c6c231f28f9bcccf4092eb246"
ApiDomain = "https://api.boce.com"
ApiCheckDuration = time.Second * 10
ApiCheckTimeout = time.Minute * 2
ApiCheckTryCount = 6
)
type NodeCache struct {
List NodeList
Last time.Time
Lock sync.Mutex
}
type MutexResult struct {
List domainmod.CheckResultList
Lock sync.Mutex
}
var (
Cache = &NodeCache{
List: make(NodeList, 0),
Last: time.Time{},
Lock: sync.Mutex{},
}
)
func (c *NodeCache) NodeList() (NodeList, error) {
ret := make(NodeList, 0)
c.Lock.Lock()
if len(c.List) > 0 && c.Last.After(time.Now().Add(-24*time.Hour)) {
for _, node := range c.List {
ret = append(ret, node)
}
c.Lock.Unlock()
return ret, nil
}
c.Lock.Unlock()
list, err := GetNodeList()
if err != nil {
return ret, err
}
if len(list) > 0 {
c.Lock.Lock()
c.List = list
c.Last = time.Now()
for _, node := range c.List {
ret = append(ret, node)
}
c.Lock.Unlock()
return ret, nil
} else {
return nil, fmt.Errorf("node list is empty")
}
}
func GetNodeList() (NodeList, error) {
addr := fmt.Sprintf("https://task.boce.com/v3/node/list?key=%s", ApiKey)
res := &CommonResp{}
tryCount := 0
for {
err := Get(addr, res, url.Values{})
if err != nil {
tryCount++
if tryCount > ApiCheckTryCount {
return nil, err
}
time.Sleep(ApiCheckDuration)
continue
}
break
}
if res.ErrorCode != 0 {
return nil, fmt.Errorf("获取检测节点列表失败:%s", GetApiErrorMessage(res.ErrorCode))
}
return *res.Data.List, nil
}
func (s *MutexResult) OnFinish(result *domainmod.CheckResult) {
s.Lock.Lock()
defer s.Lock.Unlock()
s.List = append(s.List, result)
}
func (s *MutexResult) RealErrors() []error {
ret := make([]error, 0)
for _, item := range s.List {
if item.CheckSucc && item.Error != nil {
ret = append(ret, item.Error)
}
}
return ret
}
func (s *MutexResult) AllSuccess() bool {
for _, item := range s.List {
if item.CheckSucc && item.Error != nil {
return false
}
}
return true
}
func (s *MutexResult) JumpErrors() []error {
ret := make([]error, 0)
for _, item := range s.List {
if !item.CheckSucc && item.Error != nil {
ret = append(ret, item.Error)
}
}
return ret
}
func (s *MutexResult) Reports() string {
var ret string
for _, item := range s.List {
if item.Error != nil {
ret += "\n" + item.Error.Error() + "\n"
}
if len(item.NodeErrors) > 0 {
ret += strings.Join(item.NodeErrors, "\n\n")
}
}
return ret
}
func (s *MutexResult) CheckStatus() (passCount int, jumpCount int, errorCount int) {
for _, item := range s.List {
if item.CheckSucc {
if item.Error != nil {
errorCount++
} else {
passCount++
}
} else {
jumpCount++
}
}
return
}
var ApiErrorMessage = map[int]string{
0: "成功",
1: "鉴权失败",
2: "参数错误",
3: "波点不足或波点未配置",
4: "生成任务id失败或任务id失效",
}
func GetApiErrorMessage(code int) string {
ret := ApiErrorMessage[code]
if ret == "" {
return "未知错误"
}
return ret
}
var ApiPathMap = map[domainmod.CheckType]ApiPath{
domainmod.CheckTypeWeb: {
Create: "/v3/task/create/curl",
Query: "/v3/task/curl/",
Sleep: 5 * time.Minute,
NeedCreate: true,
},
domainmod.CheckTypeDNS: {
Create: "/v3/task/create/dig",
Query: "/v3/task/dig/",
Sleep: 5 * time.Minute,
NeedCreate: true,
},
domainmod.CheckTypePing: {
Create: "/v3/task/create/ping",
Query: "/v3/task/ping/",
Sleep: 5 * time.Minute,
NeedCreate: true,
},
domainmod.CheckTypeHijack: {
Create: "/v3/task/create/hijack",
Query: "/v3/task/hijack/",
Sleep: 5 * time.Minute,
NeedCreate: true,
},
domainmod.CheckTypePollute: {
Create: "",
Query: "/v3/task/create/pollute",
NeedCreate: false,
},
domainmod.CheckTypeQQ: {
Create: "",
Query: "/v3/task/create/qq",
NeedCreate: false,
},
domainmod.CheckTypeWeChat: {
Create: "",
Query: "/v3/task/create/wechat",
NeedCreate: false,
},
domainmod.CheckTypeBlack: {
Create: "",
Query: "/v3/task/create/blacklist",
NeedCreate: false,
},
domainmod.CheckTypeGFW: {
Create: "",
Query: "/v3/task/create/wall",
NeedCreate: false,
},
}
func init() {
httpclient.Defaults(httpclient.Map{
"OPT_CONNECTTIMEOUT": 30 * time.Second,
"OPT_TIMEOUT": 10 * time.Second,
"Accept-Encoding": "gzip,deflate",
})
}
func GetChecker(item *domainmod.Domain, mode domainmod.CheckType, onFinish FnCheckFinish, nodes ...Node) Checker {
nodeList := make(NodeList, 0)
nodeList = append(nodeList, nodes...)
switch mode {
case domainmod.CheckTypeWeb:
return NewWebChecker(item, mode, onFinish, nodeList)
case domainmod.CheckTypeDNS:
return NewWebChecker(item, mode, onFinish, nodeList)
case domainmod.CheckTypePing:
return NewWebChecker(item, mode, onFinish, nodeList)
case domainmod.CheckTypeHijack:
return NewHijackChecker(item, mode, onFinish, nodeList)
case domainmod.CheckTypePollute:
return NewPolluteChecker(item, mode, onFinish)
case domainmod.CheckTypeQQ:
return NewQQChecker(item, mode, onFinish)
case domainmod.CheckTypeWeChat:
return NewWeChatChecker(item, mode, onFinish)
case domainmod.CheckTypeBlack:
return NewBlackChecker(item, mode, onFinish)
case domainmod.CheckTypeGFW:
return NewGFWChecker(item, mode, onFinish)
default:
return nil
}
}
type DomainChecker struct {
Items domainmod.DomainList
}
func (c *DomainChecker) StartCheck() error {
nodes, err := Cache.NodeList()
if err != nil {
return err
}
wg := utils.NewWaitGroup()
for _, domain := range c.Items {
_domain := domain
wg.Add(func() {
CheckDomain(_domain, nodes)
})
}
wg.RunWaits()
return nil
}
func CheckDomain(domain *domainmod.Domain, nodes NodeList) {
ctx := context.Background()
begin := time.Now()
status := domainmod.CheckStatusChecking
doc := &domainmod.DomainDoc{
Status: &status,
CheckTime: &begin,
UpdatedAt: &begin,
}
if err := domainmod.UpdateOneDomain(ctx, bson.M{"_id": domain.ID}, bson.M{"$set": doc}); err != nil {
log.Warn("CheckDomain failed", log.E(err))
return
}
rets := MutexResult{
List: make(domainmod.CheckResultList, 0),
Lock: sync.Mutex{},
}
wg := utils.NewWaitGroup()
for _, check := range domain.Checks {
_check := check
wg.Add(func() {
if checker := GetChecker(domain, _check, rets.OnFinish, nodes...); checker != nil {
time.Sleep(time.Duration(rand.Intn(10)) * time.Second) // 防并发访问
checker.Check()
}
})
}
wg.RunWaits()
end := time.Now()
doc = &domainmod.DomainDoc{
CheckFinish: &end,
UpdatedAt: &end,
}
passCount, jumpCount, errCount := rets.CheckStatus()
doc.Results = &rets.List
if errCount > 0 {
report := rets.Reports()
doc.Report = &report
status0 := domainmod.CheckStatusFailed
//doc.Status = utils.PValue(domainmod.CheckStatusFailed)
if passCount > 0 {
//doc.Status = utils.PValue(domainmod.CheckStatusPartPass)
status0 = domainmod.CheckStatusPartPass
}
doc.Status = &status0
} else {
if passCount > 0 {
if jumpCount > 0 {
//doc.Status = utils.PValue(domainmod.CheckStatusPartPass)
//doc.Report = utils.PValue(rets.Reports())
status0 := domainmod.CheckStatusPartPass
report := rets.Reports()
doc.Status = &status0
doc.Report = &report
} else {
//doc.Status = utils.PValue(domainmod.CheckStatusSucc)
//doc.Report = utils.PValue("完全通过")
status0 := domainmod.CheckStatusSucc
report := "完全通过"
doc.Status = &status0
doc.Report = &report
}
} else {
//doc.Status = utils.PValue(domainmod.CheckStatusNone)
//doc.Report = utils.PValue(rets.Reports())
status0 := domainmod.CheckStatusNone
report := rets.Reports()
doc.Status = &status0
doc.Report = &report
}
}
if err := domainmod.UpdateOneDomain(ctx, bson.M{"_id": domain.ID}, bson.M{"$set": doc}); err != nil {
log.Warn("CheckDomain failed", log.E(err))
return
}
if errCount > 0 {
text := fmt.Sprintf("域名检测:【%s】\n", domain.Name)
text += fmt.Sprintf("开始时间:【%s】\n", begin.Format("2006-01-02T15:04:05"))
text += fmt.Sprintf("结束时间:【%s】\n", end.Format("2006-01-02T15:04:05"))
text += fmt.Sprintf("检测用时:【%s】\n", end.Sub(begin))
text += fmt.Sprintf("检测结果:【%s】\n", *doc.Status)
for _, result := range rets.List {
if result.CheckSucc {
if result.Error == nil {
text += fmt.Sprintf("%s: %s\n", result.CheckType.Name(), "\U0001F7E2 通过")
} else {
if result.NodeCount == 0 {
text += fmt.Sprintf("%s: %s\n", result.CheckType.Name(), "🔴 未通过")
} else {
if result.SuccRate < result.MinRate {
if result.SuccRate < 25 {
text += fmt.Sprintf("%s: %s\n", result.CheckType.Name(), "🔴 未通过")
} else {
text += fmt.Sprintf("%s: %s\n", result.CheckType.Name(), "\U0001F7E0 通过")
}
} else {
text += fmt.Sprintf("%s: %s\n", result.CheckType.Name(), "\U0001F7E2 通过")
}
}
}
} else {
text += fmt.Sprintf("%s: %s\n", result.CheckType.Name(), "🕓 超时")
}
}
if domain.Redirected {
text += fmt.Sprintf("域名跳转:【已做301跳转✅】\n")
text += fmt.Sprintf("跳转地址:【==>%s】\n", domain.RedirectDomain)
} else {
text += fmt.Sprintf("域名跳转:【未做301跳转🈚】\n")
}
statg.Bot.SendDomainMessage(text)
}
}
type WebChecker struct {
Item *domainmod.Domain
Mode domainmod.CheckType
Finish FnCheckFinish
Nodes NodeList
}
func NewWebChecker(item *domainmod.Domain, mode domainmod.CheckType, onFinish FnCheckFinish, nodes NodeList) *WebChecker {
return &WebChecker{
Item: item,
Mode: mode,
Finish: onFinish,
Nodes: nodes,
}
}
func (s *WebChecker) Check() error {
ret := &domainmod.CheckResult{
Domain: s.Item.Name,
CheckType: s.Mode,
BeginTime: time.Now(),
MinRate: s.Item.MinRate,
}
defer s.Finish(ret)
api, ok := ApiPathMap[s.Mode]
if !ok {
ret.EndTime = time.Now()
ret.Error = fmt.Errorf("域名%s的检测类型%d不支持", s.Item.Name, s.Mode)
return ret.Error
}
addr, err := api.GetCreateUrl(url.Values{
"node_ids": []string{s.Nodes.SIdList(s.Item.MaxNodes)},
"host": []string{s.Item.Name},
})
if err != nil {
ret.EndTime = time.Now()
ret.Error = err
return ret.Error
}
tryCount := 0
res := &CommonResp{}
for {
err = Get(addr, res, url.Values{})
if err != nil {
tryCount++
if tryCount > ApiCheckTryCount {
ret.EndTime = time.Now()
ret.Error = fmt.Errorf("创建【%s】的【%s】检测任务超时:%s", s.Item.Name, s.Mode.Name(), err.Error())
return ret.Error
}
time.Sleep(ApiCheckDuration)
continue
}
break
}
if res.ErrorCode != 0 {
ret.EndTime = time.Now()
ret.Error = fmt.Errorf("创建【%s】的【%s】检测任务失败:%s", s.Item.Name, s.Mode.Name(), GetApiErrorMessage(res.ErrorCode))
return ret.Error
}
// 获取查询ID
if res.Data.Id == nil || *res.Data.Id == "" {
ret.EndTime = time.Now()
ret.Error = fmt.Errorf("创建【%s】的【%s】检测任务失败:任务ID未空", s.Item.Name, s.Mode.Name())
return ret.Error
}
TaskId := *res.Data.Id
// 去查询结果
tryCount = 0
addr, err = api.GetQueryUrlWithTaskId(TaskId, url.Values{})
end := time.Now().Add(ApiCheckTimeout)
for {
time.Sleep(ApiCheckDuration)
webResp := &WebResp{}
err = Get(addr, webResp, url.Values{})
if err != nil {
tryCount++
if tryCount > ApiCheckTryCount {
ret.EndTime = time.Now()
ret.Error = fmt.Errorf("查询【%s】的【%s】检测结果超时:%s", s.Item.Name, s.Mode.Name(), err.Error())
return ret.Error
}
continue
}
ret.CheckSucc = webResp.Done
if webResp.Done {
for _, node := range webResp.List {
if node.ErrorCode != 0 {
ret.FailedCount++
ret.NodeErrors = append(ret.NodeErrors, node.ErrorString())
} else {
ret.NodeCount++
if node.HttpCode == http.StatusOK {
ret.SuccCount++
} else {
ret.NodeErrors = append(ret.NodeErrors, node.ErrorString())
}
}
}
if ret.NodeCount > 0 {
ret.SuccRate = float64(ret.SuccCount*100) / float64(ret.NodeCount)
if ret.SuccRate < ret.MinRate {
ret.Error = fmt.Errorf("节点通过率%.1f%%<%.1f%%,未通过!", ret.SuccRate, ret.MinRate)
}
}
break
} else {
if end.Before(time.Now()) {
ret.Error = fmt.Errorf("查询【%s】的【%s】检测超时", s.Item.Name, s.Mode.Name())
break
}
}
}
ret.EndTime = time.Now()
return ret.Error
}
type HijackChecker struct {
Item *domainmod.Domain
Mode domainmod.CheckType
Finish FnCheckFinish
Nodes NodeList
}
func NewHijackChecker(item *domainmod.Domain, mode domainmod.CheckType, onFinish FnCheckFinish, nodes NodeList) *HijackChecker {
return &HijackChecker{
Item: item,
Mode: mode,
Finish: onFinish,
Nodes: nodes,
}
}
func (s *HijackChecker) Check() error {
ret := &domainmod.CheckResult{
Domain: s.Item.Name,
CheckType: s.Mode,
BeginTime: time.Now(),
MinRate: s.Item.MinRate,
}
defer s.Finish(ret)
api, ok := ApiPathMap[s.Mode]
if !ok {
ret.EndTime = time.Now()
ret.Error = fmt.Errorf("域名%s的检测类型%d不支持", s.Item.Name, s.Mode)
return ret.Error
}
addr, err := api.GetCreateUrl(url.Values{
"node_ids": []string{s.Nodes.SIdList(s.Item.MaxNodes)},
"host": []string{s.Item.Name},
})
if err != nil {
ret.EndTime = time.Now()
ret.Error = err
return ret.Error
}
tryCount := 0
res := &CommonResp{}
for {
err = Get(addr, res, url.Values{})
if err != nil {
tryCount++
if tryCount > ApiCheckTryCount {
ret.EndTime = time.Now()
ret.Error = fmt.Errorf("创建【%s】的【%s】检测任务超时:%s", s.Item.Name, s.Mode.Name(), err.Error())
return ret.Error
}
time.Sleep(ApiCheckDuration)
continue
}
break
}
if res.ErrorCode != 0 {
ret.EndTime = time.Now()
ret.Error = fmt.Errorf("创建【%s】的【%s】检测任务失败:%s", s.Item.Name, s.Mode.Name(), GetApiErrorMessage(res.ErrorCode))
return ret.Error
}
// 获取查询ID
if res.Data.Id == nil || *res.Data.Id == "" {
ret.EndTime = time.Now()
ret.Error = fmt.Errorf("创建【%s】的【%s】检测任务失败:任务ID未空", s.Item.Name, s.Mode.Name())
return ret.Error
}
TaskId := *res.Data.Id
// 去查询结果
tryCount = 0
addr, err = api.GetQueryUrlWithTaskId(TaskId, url.Values{})
end := time.Now().Add(ApiCheckTimeout)
for {
time.Sleep(ApiCheckDuration)
webResp := &HijackResp{}
err = Get(addr, webResp, url.Values{})
if err != nil {
tryCount++
if tryCount > ApiCheckTryCount {
ret.EndTime = time.Now()
ret.Error = fmt.Errorf("查询【%s】的【%s】接口错误【%d】次,超过最大重试次数:%s", s.Item.Name, s.Mode.Name(), tryCount, err.Error())
return ret.Error
}
continue
}
ret.CheckSucc = webResp.Done
if webResp.Done {
for _, node := range webResp.List {
ret.NodeCount++
if node.Hijack {
ret.FailedCount++
ret.NodeErrors = append(ret.NodeErrors, node.ErrorString())
} else {
ret.SuccCount++
}
}
if ret.NodeCount > 0 {
ret.SuccRate = float64(ret.SuccCount*100) / float64(ret.NodeCount)
if ret.SuccRate < ret.MinRate {
ret.Error = fmt.Errorf("节点通过率%.1f%%<%.1f%%,未通过!", ret.SuccRate, ret.MinRate)
}
}
break
} else {
if end.Before(time.Now()) {
ret.Error = fmt.Errorf("查询【%s】的【%s】检测超时", s.Item.Name, s.Mode.Name())
break
}
}
}
ret.EndTime = time.Now()
return ret.Error
}
type FuncCheckResult func(ret *domainmod.CheckResult, data map[string]int)
func QueryCheck(item *domainmod.Domain, mode domainmod.CheckType, onFinish FnCheckFinish, onCheck FuncCheckResult) error {
ret := &domainmod.CheckResult{
Domain: item.Name,
CheckType: mode,
BeginTime: time.Now(),
MinRate: item.MinRate,
}
defer onFinish(ret)
api, ok := ApiPathMap[mode]
if !ok {
ret.EndTime = time.Now()
ret.Error = fmt.Errorf("查询接口地址【%s】的【%s】出错", item.Name, mode.Name())
return ret.Error
}
addr, err := api.GetQueryUrl(url.Values{"host": []string{item.Name}})
polluteResp := &PolluteResp{}
tryCount := 0
for {
tryCount++
err = Get(addr, polluteResp, url.Values{})
if err != nil {
if tryCount > ApiCheckTryCount {
ret.EndTime = time.Now()
ret.Error = fmt.Errorf("查询【%s】的【%s】接口错误【%d】次,超过最大重试次数:%s", item.Name, mode.Name(), tryCount, err.Error())
return ret.Error
}
time.Sleep(ApiCheckDuration)
continue
}
break
}
if polluteResp.ErrorCode != 0 {
ret.EndTime = time.Now()
ret.Error = fmt.Errorf("查询【%s】的【%s】返回值不是成功:%s", item.Name, mode.Name(), GetApiErrorMessage(polluteResp.ErrorCode))
return ret.Error
}
ret.CheckSucc = true
onCheck(ret, polluteResp.Data)
ret.EndTime = time.Now()
return ret.Error
}
type PolluteChecker struct {
Item *domainmod.Domain
Mode domainmod.CheckType
Finish FnCheckFinish
}
func NewPolluteChecker(item *domainmod.Domain, mode domainmod.CheckType, onFinish FnCheckFinish) *PolluteChecker {
return &PolluteChecker{
Item: item,
Mode: mode,
Finish: onFinish,
}
}
func (s *PolluteChecker) Check() error {
return QueryCheck(s.Item, s.Mode, s.Finish, func(ret *domainmod.CheckResult, data map[string]int) {
if code, ok := data[s.Item.Name]; ok { //(-1 未注册,0 正常, 1 污染)
if ok {
if code == 0 {
ret.SuccRate = 100.0
} else {
if code == 1 {
ret.Error = fmt.Errorf("域名被污染")
} else if code == -1 {
ret.Error = fmt.Errorf("域名未注册")
} else {
ret.Error = fmt.Errorf("未知错误码%d", code)
}
}
} else {
ret.Error = fmt.Errorf("返回值中未检测到域名")
}
}
})
}
type QQChecker struct {
Item *domainmod.Domain
Mode domainmod.CheckType
Finish FnCheckFinish
}
func NewQQChecker(item *domainmod.Domain, mode domainmod.CheckType, onFinish FnCheckFinish) *QQChecker {
return &QQChecker{
Item: item,
Mode: mode,
Finish: onFinish,
}
}
func (s *QQChecker) Check() error {
return QueryCheck(s.Item, s.Mode, s.Finish, func(ret *domainmod.CheckResult, data map[string]int) {
if code, ok := data[s.Item.Name]; ok { //1正常, 2拦截, 3-检测失败
if ok {
if code == 1 {
ret.CheckSucc = true
ret.SuccRate = 100.0
} else {
if code == 2 {
ret.CheckSucc = true
ret.Error = fmt.Errorf("已被拦截")
} else if code == 3 {
ret.Error = fmt.Errorf("检测失败")
} else {
ret.Error = fmt.Errorf("检测值【%d】不知道意义", code)
}
}
} else {
ret.Error = fmt.Errorf("返回值中未检测到域名")
}
}
})
}
type WeChatChecker struct {
Item *domainmod.Domain
Mode domainmod.CheckType
Finish FnCheckFinish
}
func NewWeChatChecker(item *domainmod.Domain, mode domainmod.CheckType, onFinish FnCheckFinish) *WeChatChecker {
return &WeChatChecker{
Item: item,
Mode: mode,
Finish: onFinish,
}
}
func (s *WeChatChecker) Check() error {
return QueryCheck(s.Item, s.Mode, s.Finish, func(ret *domainmod.CheckResult, data map[string]int) {
if code, ok := data[s.Item.Name]; ok { //1正常, 2拦截(域名被封), 3-检测失败,4-拦截(非微信官方链接,需跳转访问)
if ok {
if code == 1 || code == 4 {
ret.CheckSucc = true
ret.SuccRate = 100.0
} else {
if code == 2 {
ret.CheckSucc = true
ret.Error = fmt.Errorf("已被拦截")
} else if code == 3 {
ret.Error = fmt.Errorf("检测失败")
} else {
ret.Error = fmt.Errorf("检测值【%d】不知道意义", code)
}
}
} else {
ret.Error = fmt.Errorf("返回值中未检测到域名")
}
}
})
}
type BlackChecker struct {
Item *domainmod.Domain
Mode domainmod.CheckType
Finish FnCheckFinish
}
func NewBlackChecker(item *domainmod.Domain, mode domainmod.CheckType, onFinish FnCheckFinish) *BlackChecker {
return &BlackChecker{
Item: item,
Mode: mode,
Finish: onFinish,
}
}
func (s *BlackChecker) Check() error {
return QueryCheck(s.Item, s.Mode, s.Finish, func(ret *domainmod.CheckResult, data map[string]int) {
if code, ok := data[s.Item.Name]; ok { //1-正常, 2-在黑名单
if ok {
if code == 1 {
ret.CheckSucc = true
ret.SuccRate = 100.0
} else {
if code == 2 {
ret.CheckSucc = true
ret.Error = fmt.Errorf("在黑名单")
} else {
ret.Error = fmt.Errorf("检测值【%d】不知道意义", code)
}
}
} else {
ret.Error = fmt.Errorf("返回值中未检测到域名")
}
}
})
}
type GFWChecker struct {
Item *domainmod.Domain
Mode domainmod.CheckType
Finish FnCheckFinish
}
func NewGFWChecker(item *domainmod.Domain, mode domainmod.CheckType, onFinish FnCheckFinish) *GFWChecker {
return &GFWChecker{
Item: item,
Mode: mode,
Finish: onFinish,
}
}
func (s *GFWChecker) Check() error {
return QueryCheck(s.Item, s.Mode, s.Finish, func(ret *domainmod.CheckResult, data map[string]int) {
if code, ok := data[s.Item.Name]; ok { //0 正常, 1 被墙, 2 疑似被墙, 3 无资源请重试,4 域名格式错误
if ok {
if code == 0 { // 正常
ret.CheckSucc = true
ret.SuccRate = 100.0
} else {
if code == 1 {
ret.CheckSucc = true
ret.Error = fmt.Errorf("域名被墙")
} else if code == 2 {
ret.CheckSucc = true
ret.Error = fmt.Errorf("疑似被墙")
} else if code == 3 {
ret.Error = fmt.Errorf("无资源请重试")
} else if code == 4 {
ret.Error = fmt.Errorf("域名格式错误")
} else {
ret.Error = fmt.Errorf("检测值【%d】不知道意义", code)
}
}
} else {
ret.Error = fmt.Errorf("返回值中未检测到域名")
}
}
})
}
+135
View File
@@ -0,0 +1,135 @@
package domainser
import (
"fmt"
"math/rand"
"sort"
"stat/models/domainmod"
"strings"
"time"
)
type FnCheckFinish func(result *domainmod.CheckResult)
type Checker interface {
Check() error
}
type ApiPath struct {
Create string
Query string
Sleep time.Duration
NeedCreate bool
}
type Node struct {
Id int `json:"id"`
NodeName string `json:"node_name"`
IspName string `json:"isp_name"`
IspCode int `json:"isp_code"`
Rand int
}
type NodeList []Node
func (n NodeList) Len() int { return len(n) }
func (n NodeList) Less(i, j int) bool {
return n[i].Rand < n[j].Rand
}
func (n NodeList) Swap(i, j int) { n[i], n[j] = n[j], n[i] }
func (n NodeList) SIdList(max int32) string {
if max < 1 {
max = 1
}
for _, node := range n {
node.Rand = rand.Int()
}
sort.Sort(n)
ss := make([]string, 0)
for _, node := range n {
if strings.Contains(node.IspName, "电信") || strings.Contains(node.IspName, "移动") || strings.Contains(node.IspName, "联通") {
ss = append(ss, fmt.Sprintf("%d", node.Id))
}
}
if len(ss) > int(max) {
ss = ss[:max]
}
return strings.Join(ss, ",")
}
type CommonResp struct {
ErrorCode int `json:"error_code"`
Error string `json:"error"`
Data struct {
Id *string `json:"id"`
List *NodeList `json:"list"`
} `json:"data"`
}
type DomainResp struct {
ErrorCode int `json:"error_code"`
Error string `json:"error"`
Data map[string]int `json:"data"`
}
type WebTestNode struct {
NodeId int `json:"node_id"`
NodeName string `json:"node_name"`
Host string `json:"host"`
OriginIp string `json:"origin_ip"`
HttpCode int `json:"http_code"`
RemoteIp string `json:"remote_ip"`
SizeDownload int `json:"size_download"`
SpeedDownload int `json:"speed_download"`
TimeConnect float64 `json:"time_connect"`
TimeNamelookup float64 `json:"time_namelookup"`
TimeStarttransfer float64 `json:"time_starttransfer"`
TimeTotal float64 `json:"time_total"`
ReportSource string `json:"report_source"`
SessionId string `json:"session_id"`
IpRegion string `json:"ip_region"`
IpIsp string `json:"ip_isp"`
ErrorCode int `json:"error_code"`
Error string `json:"error"`
TimeId string `json:"time_id"`
}
type WebTestNodeList []*WebTestNode
func (n *WebTestNode) ErrorString() string {
return fmt.Sprintf("节点【%s】检测失败", n.NodeName)
}
type WebResp struct {
Id string `json:"id"`
MaxNode int `json:"max_node"`
Done bool `json:"done"`
List WebTestNodeList `json:"list"`
}
type HijackTestNode struct {
NodeId int `json:"node_id"`
Hijack bool `json:"hijack"`
NodeName string `json:"node_name"`
RemoteIp string `json:"remote_ip"`
Url string `json:"url"`
ReportSource string `json:"report_source"`
}
func (n *HijackTestNode) ErrorString() string {
return fmt.Sprintf("节点【%s】已劫持", n.NodeName)
}
type HijackResp struct {
Done bool `json:"done"`
HijackNum int `json:"hijack_num"`
Id string `json:"id"`
List []*HijackTestNode `json:"list"`
MaxNode int `json:"max_node"`
}
type PolluteResp struct {
ErrorCode int `json:"error_code"`
Error string `json:"error"`
Data map[string]int `json:"data"`
}
+76
View File
@@ -0,0 +1,76 @@
package domainser
import (
"context"
"stat/common/enum/consts"
"stat/common/log"
"stat/common/statutils"
"stat/models/domainmod"
"stat/statg"
"time"
"go.mongodb.org/mongo-driver/bson"
"github.com/robfig/cron/v3"
)
var (
checkCron *cron.Cron
)
func init() {
checkCron = cron.New(cron.WithSeconds(), cron.WithChain(cron.SkipIfStillRunning(&log.Logger{}))) // 秒级定时任务
}
// Start 定时器任务启动
func Start() {
log.Info("domain checker start...")
ctx := context.Background()
if statg.Conf.RunMod != consts.RunModRelease {
return
}
_, _ = checkCron.AddFunc("0 0/5 * * * ?", func() {
statutils.ServerMon.RunWithName(func() error {
return DomainCheck(ctx)
}, statutils.GetFuncName(DomainCheck))
})
checkCron.Start()
}
func DomainCheck(ctx context.Context) error {
list, err := domainmod.FindManyDomain(ctx, bson.M{
"enable": true,
"checkTime": bson.M{"$lt": time.Now().Add(-10 * time.Minute)},
})
if err != nil {
return err
}
redirectedChecks := []domainmod.CheckType{domainmod.CheckTypePollute}
unRedirectedChecks := []domainmod.CheckType{domainmod.CheckTypePollute, domainmod.CheckTypeGFW}
domains := make(domainmod.DomainList, 0)
for _, item := range list {
if item.Redirected {
item.Checks = redirectedChecks
} else {
item.Checks = unRedirectedChecks
}
if item.Status == domainmod.CheckStatusNone {
if item.CheckTime.Before(time.Now().Add(-10 * time.Minute)) {
domains = append(domains, item)
}
} else {
if item.CheckTime.Before(time.Now().Add(time.Duration(-item.CycleHour) * time.Hour)) {
domains = append(domains, item)
}
}
}
if len(domains) == 0 {
return nil
}
domainChecker := &DomainChecker{
Items: domains,
}
return domainChecker.StartCheck()
}
+95
View File
@@ -0,0 +1,95 @@
package domainser
import (
"encoding/json"
"fmt"
"net"
"net/http"
"net/url"
"regexp"
"strings"
"github.com/ddliu/go-httpclient"
)
func ParseDomain(name string) (string, error) {
name = strings.TrimSpace(name)
if strings.Contains(strings.ToLower(name), "http:") || strings.Contains(strings.ToLower(name), "https:") {
u, err := url.Parse(name)
if err != nil {
return "", err
}
if u.Host == "" {
return "", fmt.Errorf("没有检测到有效的域名")
}
if net.ParseIP(u.Host) != nil {
return "", fmt.Errorf("只能填写域名,不能填写IP地址")
}
host, _, err := net.SplitHostPort(u.Host)
if err != nil {
return "", fmt.Errorf("域名无效")
}
return host, nil
} else {
re, err := regexp.Compile(`^(?:[_a-z0-9](?:[_a-z0-9-]{0,61}[a-z0-9])?\.)+(?:[a-z](?:[a-z0-9-]{0,61}[a-z0-9])?)?$`)
if err != nil {
return "", err
}
if !re.MatchString(name) {
return "", fmt.Errorf("请输入正确的域名")
}
return name, nil
}
}
func (n *ApiPath) GetCreateUrl(param url.Values) (string, error) {
param.Set("key", ApiKey)
if n.NeedCreate {
base, err := url.Parse(ApiDomain + n.Create)
if err != nil {
return "", err
}
base.RawQuery = param.Encode()
return base.String(), nil
} else {
return "", fmt.Errorf("do not need create url")
}
}
func (n *ApiPath) GetQueryUrlWithTaskId(taskId string, param url.Values) (string, error) {
param.Set("key", ApiKey)
base, err := url.Parse(ApiDomain + n.Query + taskId)
if err != nil {
return "", err
}
base.RawQuery = param.Encode()
return base.String(), nil
}
func (n *ApiPath) GetQueryUrl(param url.Values) (string, error) {
param.Set("key", ApiKey)
base, err := url.Parse(ApiDomain + n.Query)
if err != nil {
return "", err
}
base.RawQuery = param.Encode()
return base.String(), nil
}
func Get(url string, resp interface{}, param url.Values) error {
res, err := httpclient.Get(url, param)
if err != nil {
return fmt.Errorf("访问检测网站失败:%s", err.Error())
}
if res.StatusCode != http.StatusOK {
return fmt.Errorf("访问检测网站失败, HTTP Code:%d, Error:%s", res.StatusCode, res.Status)
}
data, err := res.ReadAll()
if err != nil {
return fmt.Errorf("读取检测网站返回数据失败:%s", err.Error())
}
if err = json.Unmarshal(data, resp); err != nil {
return fmt.Errorf("解码检测网站返回数据失败:%s", err.Error())
}
return nil
}
+133
View File
@@ -0,0 +1,133 @@
package domainser
import (
"context"
"fmt"
"stat/api/proto"
"stat/common/stdLog"
"stat/common/utils"
"stat/models/appInfoMod"
"stat/models/domainmod"
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo/options"
)
// 获取DomainInfo列表
func GetDomainInfoList(ctx context.Context, param proto.ListDomainInfoReq) (code stdLog.CodeType, data interface{}, err error) {
resp := proto.ListDomainInfoRes{
Total: 0,
List: make(domainmod.DomainList, 0),
}
skip, limit := utils.ToDBPage(param.PageNum, param.PageSize)
filter := bson.M{}
if param.Name != nil {
filter["name"] = bson.M{"$regex": fmt.Sprintf("^%s", *param.Name)}
}
if param.AppId != nil {
filter["appId"] = *param.AppId
}
if param.Enable != nil {
filter["enable"] = *param.Enable
}
if param.Status != nil {
filter["status"] = *param.Status
}
if param.Category != nil {
filter["category"] = *param.Category
}
if param.Check != nil {
filter["checks"] = bson.M{"$in": *param.Check}
}
opts := options.Find().SetSkip(skip).SetLimit(limit)
count, err := domainmod.CountDomains(ctx, filter)
if err != nil {
code = stdLog.Failure
return
}
aList, err := domainmod.FindManyDomain(ctx, filter, opts)
if err != nil {
code = stdLog.Failure
return
}
appMap, _ := appInfoMod.GetAllAppIdNameMap(ctx)
for _, item := range aList {
item.AppName = appMap[item.AppId]
}
resp.Total = count
resp.List = aList
data = resp
code = stdLog.Success
return
}
// 添加DomainInfo
func AddDomainInfo(ctx context.Context, param proto.AddDomainInfoReq) (code stdLog.CodeType, data interface{}, err error) {
name, err := ParseDomain(param.Name)
if err != nil {
code = stdLog.ParamError
return
}
param.Name = name
now := time.Now()
param.UpdatedAt = now
param.CreatedAt = now
if param.CycleHour < 1 {
param.CycleHour = 1
}
if err = domainmod.InsertOneDomain(ctx, &param.Domain); err != nil {
code = stdLog.Failure
return
}
code = stdLog.Success
return
}
// 更新DomainInfo
func UpdateDomainInfo(ctx context.Context, param proto.UpdateDomainInfoReq) (code stdLog.CodeType,
data interface{}, err error) {
now := time.Now()
param.Update.UpdatedAt = &now
if param.Update.Name != nil {
var name string
name, err = ParseDomain(*param.Update.Name)
if err != nil {
code = stdLog.ParamError
return
}
param.Update.Name = &name
}
if param.Update.CycleHour != nil {
if *param.Update.CycleHour < 1 {
hour := int64(1)
param.Update.CycleHour = &hour
}
}
if err = domainmod.UpdateOneDomain(ctx, bson.M{"_id": param.Id}, bson.M{"$set": param.Update}); err != nil {
code = stdLog.Failure
return
}
code = stdLog.Success
return
}
// 删除DomainInfo
func DelDomainInfo(ctx context.Context, param proto.DelDomainInfoReq) (code stdLog.CodeType, data interface{}, err error) {
if _, err = domainmod.DelOneDomain(ctx, bson.M{"_id": param.Id}); err != nil {
code = stdLog.Failure
return
}
code = stdLog.Success
return
}
+34
View File
@@ -0,0 +1,34 @@
package innerserverser
import (
"stat/api/proto"
"time"
)
type AppInformation struct {
Id int
Name string
BeginCol int
Total proto.InnerDailyRecharge
}
type DayRecharge struct {
SumDate time.Time
List []*proto.InnerDailyRecharge // all items for one day
Dict map[int32]*proto.InnerDailyRecharge // map[appId]item
Total proto.InnerDailyRecharge
}
type DayRechargeList []*DayRecharge
func (d DayRechargeList) Len() int {
return len(d)
}
func (d DayRechargeList) Less(i, j int) bool {
return d[i].SumDate.Before(d[j].SumDate)
}
func (d DayRechargeList) Swap(i, j int) {
d[i], d[j] = d[j], d[i]
}
+711
View File
@@ -0,0 +1,711 @@
package innerserverser
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"github.com/360EntSecGroup-Skylar/excelize"
"go.mongodb.org/mongo-driver/bson"
"io"
"net/http"
"os"
"sort"
"stat/api/proto"
"stat/common/log"
"stat/common/tgbot"
"stat/common/utils"
"stat/models/appInfoMod"
"stat/models/dailystatmod"
"stat/statg"
"strconv"
"time"
)
func InnerRequestBotcmd(url string, reqproto interface{}, bind interface{}) error {
data, err := json.Marshal(reqproto)
if err != nil {
log.Error("InnerRequestBotcmd json.Marshal", log.Any("err", err))
return err
}
//resp, err = InnerPlainPost(url, data)
bodyReader := bytes.NewReader(data)
req, err := http.NewRequest(http.MethodPost, url, bodyReader)
if err != nil {
log.Error("InnerRequestBotcmd http.NewRequest", log.Any("err", err))
return err
}
timeout := 30 * time.Second
req.Header.Set("Content-Type", "application/json")
client := http.Client{
Timeout: timeout,
}
resp, err := client.Do(req)
if err != nil {
log.Error("InnerRequestBotcmd http.NewRequest", log.Any("err", err))
return err
}
if resp.StatusCode != 200 {
err = fmt.Errorf("error status %s, http code %d", resp.Status, resp.StatusCode)
log.Error("InnerRequestBotcmd http.NewRequest resp", log.Any("err", err))
return err
}
defer resp.Body.Close()
data, err = io.ReadAll(resp.Body)
if err != nil {
log.Error("InnerRequestBotcmd io.ReadAll", log.Any("err", err))
return err
}
return json.Unmarshal(data, bind)
}
// bili请求过来的
func InnerRecgaregeBotcmdList(ctx context.Context, req *proto.InnerRechargeBotcmdReq) (
*proto.InnerDailyRechargeList, error) {
if req.InnerId != int32(statg.Conf.ServerIndex) {
err := fmt.Errorf("InnerRecgaregeBotcmdList req.InnerId %d", req.InnerId)
log.Error("InnerRecgaregeBotcmdList req.InnerId", log.Any("err", err))
return nil, err
}
resp := &proto.InnerDailyRechargeList{}
resp.List = make([]*proto.InnerDailyRecharge, 0)
list, appinfo, err := GetDbListBotcmd(ctx, &req.EndTime, &req.BeginTime)
if err != nil {
log.Error("InnerRecgaregeBotcmdList:GetDbList", log.E(err))
return nil, err
}
for _, info := range list {
if info.AppId == 889 {
continue
}
name := ""
if data, ok := appinfo[info.AppId]; ok {
name = data.Name
}
temp := &proto.InnerDailyRecharge{
AppId: info.AppId,
Name: name,
Type: "",
DateTime: info.SumDate.Format("2006-01-02"),
DailyVistCount: info.TotalVisitCount,
DailyRegister: info.NewTotalCount,
DailyAppAmount: info.TotalAmount,
DailyGameAmount: info.GameRechageAmount,
RechargeOrder: info.SuccessRechargeOrder,
}
resp.List = append(resp.List, temp)
}
return resp, nil
}
func GetDbListBotcmd(ctx context.Context, day *time.Time, yesterday *time.Time) (
[]*dailystatmod.DailyStatMod, map[int32]*appInfoMod.AppInfo, error) {
list := make([]*dailystatmod.DailyStatMod, 0)
appinfo, serr := appInfoMod.GetAllAppIdMap(ctx, bson.M{})
if serr != nil {
log.Error("GetDbListBotcmd appInfoMod.GetAllAppInfo", log.Any("serr", serr))
return nil, nil, serr
}
for _, info := range appinfo {
//data, serr := dailystatmod.FindOneDailyStat(ctx, info.AppId, yesterday)
rechargelist, serr := dailystatmod.GetDailyStatTotalList(ctx, &info.AppId, yesterday, day)
if serr != nil {
log.Error("GetDbListBotcmd dailystatmod.GetDailyStatTotalList", log.Any("serr", serr))
continue
}
if rechargelist != nil {
list = append(list, rechargelist...)
} else {
log.Error("GetDbListBotcmd rechargelist nil")
}
}
return list, appinfo, nil
}
func NewXlslFileBot(f *excelize.File, list []*proto.InnerDailyRecharge,
titlestyle, cellstyle int) error {
// 设置列宽
f.SetColWidth(sheetName, "A", "AAA", 19)
// 设置表头行高
f.SetRowHeight(sheetName, 1, 25)
f.SetCellStyle(sheetName, "A1", "ZZ1", titlestyle)
f.SetCellStyle(sheetName, "A2", "ZZ2", titlestyle)
f.SetCellStyle(sheetName, "A3", "ZZ3", titlestyle)
idx := f.NewSheet(sheetName)
//f.MergeCell(sheetName, "A1", "A3")
f.SetCellValue(sheetName, "A1", "日期")
//f.SetCellStyle(sheetName, "B2", "AAA2", cellstyle)
f.SetCellStyle(sheetName, "A4", "A50", cellstyle)
f.SetActiveSheet(idx)
now := time.Now()
day := now.Format("2006-01")
//begin, end := utils.MonthDateRange(now)
beginday := utils.BeginOfMonth(now).Day()
endday := utils.EndOfMonth(now).Day()
//设置日期
index := "A"
for i := beginday; i <= endday; i++ {
tmp := fmt.Sprintf("%s%d", index, i+3)
rowday := fmt.Sprintf("%s-%02d", day, i)
f.SetCellValue(sheetName, tmp, rowday)
}
//新文件设置固定的主题和style
row := 1
col := 2
count := col
for _, info := range list {
begincol := count
count = count + 4
titleindex := 0
titlemap := make(map[string]string, 0)
for i := begincol; i <= count; i++ {
if titleindex < len(exportTransWaterTitle) {
titlecell, serr := utils.CoordinatesToCellName(begincol+titleindex, row+2)
if serr != nil {
return serr
}
titlemap[titlecell] = exportTransWaterTitle[titleindex]
}
titleindex++
}
for key, value := range titlemap {
f.SetCellValue(sheetName, key, value)
}
info.BeginCol = begincol
// 第一行产品
begincell, serr := utils.CoordinatesToCellName(begincol, row)
if serr != nil {
return serr
}
endcell, serr := utils.CoordinatesToCellName(count, row)
if serr != nil {
return serr
}
// 第一行appid
appidbegincell, serr := utils.CoordinatesToCellName(begincol, row+1)
if serr != nil {
return serr
}
appidendcell, serr := utils.CoordinatesToCellName(count, row+1)
if serr != nil {
return serr
}
f.MergeCell(sheetName, begincell, endcell)
/*if info.AppId != 0 {
if info.AppId == 888 {
f.SetCellValue(sheetName, begincell, info.Name+"(集团)")
} else {
f.SetCellValue(sheetName, begincell, info.Name+"(内部)")
}
} else {
*/
f.SetCellValue(sheetName, begincell, info.Name)
//}
f.MergeCell(sheetName, appidbegincell, appidendcell)
//tmp := fmt.Sprintf("%s1", string(index0))
f.SetCellValue(sheetName, appidbegincell, info.AppId)
count += 2
}
return nil
}
// bili请求麻豆知乎信息只在bili启动麻豆知乎只响应数据
func InnerServerBotDateMonthStat(ctx context.Context, cmd *tgbot.Command) error {
if statg.Conf.ServerIndex != statg.ServerIndexBili {
log.Error("InnerServerBotDateMonthStat serverindex",
log.Any("serverindex", statg.Conf.ServerIndex))
return nil
}
log.Info("InnerServerBotDateMonthStat start")
sBegin := cmd.GetFlagStringValue("-begin")
if sBegin == "" {
return tgbot.NewUserError(cmd, "请输入查询开始日期,格式:\n"+cmd.Help+"\n"+cmd.Desc)
}
sEnd := cmd.GetFlagStringValue("-end")
if sEnd == "" {
return tgbot.NewUserError(cmd, "请输入查询结束日期,格式:\n"+cmd.Help+"\n"+cmd.Desc)
}
beginmonth, err := time.ParseInLocation("2006-01-02", sBegin, time.Local)
if err != nil {
return tgbot.NewUserError(cmd, "解析开始日期出错, 格式:\n"+cmd.Help+"\n"+cmd.Desc)
}
//beginmonth = beginmonth.AddDate(0, 0, -1)
endmonth, err := time.ParseInLocation("2006-01-02", sEnd, time.Local)
if err != nil {
return tgbot.NewUserError(cmd, "解析结束日期出错, 格式:\n"+cmd.Help+"\n"+cmd.Desc)
}
list := make([]*proto.InnerDailyRecharge, 0)
for id, info := range statg.Conf.InnerServers {
if id == statg.ServerIndexBili {
continue
}
if id != statg.ServerIndexBili && info == "" {
log.Error("InnerServerBotDateMonthStat server not found",
log.Any("statg.Conf.InnerServers", id))
continue
}
req := &proto.InnerRechargeBotcmdReq{
InnerId: int32(id),
BeginTime: beginmonth,
EndTime: endmonth,
}
url := info + "/api/stat/inner/rechargebotcmd/list"
log.Info("InnerServerBotDateMonthStat", log.Any("req", url))
res := &proto.InnerDailyRechargeResp{}
err = InnerRequestBotcmd(url, req, res)
if err != nil {
log.Error("InnerServerBotDateMonthStat InnerRequest",
log.Any("InnerRequest", err))
continue
}
if res == nil {
log.Error("InnerServerBotDateMonthStat InnerRequest data not found")
continue
}
if res.Data.List != nil && len(res.Data.List) > 0 {
list = append(list, res.Data.List...)
}
}
bililist, appinfo, err := GetDbListBotcmd(ctx, &endmonth, &beginmonth)
if err != nil {
log.Error("InnerServerBotDateMonthStat:GetDbList", log.E(err))
return err
}
for _, info := range bililist {
name := ""
if data, ok := appinfo[info.AppId]; ok {
name = data.Name
}
temp := &proto.InnerDailyRecharge{
AppId: info.AppId,
Name: name,
Type: "",
DateTime: info.SumDate.Format("2006-01-02"),
DailyVistCount: info.TotalVisitCount,
DailyRegister: info.NewTotalCount,
DailyAppAmount: info.TotalAmount,
DailyGameAmount: info.GameRechageAmount,
RechargeOrder: info.SuccessRechargeOrder,
}
list = append(list, temp)
}
listmap := make(map[string]*proto.InnerDailyMap)
for _, info := range list {
if value, ok := listmap[info.DateTime]; ok {
value.List = append(value.List, info)
} else {
tempmap := &proto.InnerDailyMap{
Id: info.DateTime,
List: make([]*proto.InnerDailyRecharge, 0),
}
tempmap.List = append(tempmap.List, info)
listmap[info.DateTime] = tempmap
}
}
// 中间会不同日期加入新的appid要用所有的appid组成数组创建新xlsx
tempcell := make([]*proto.InnerDailyRecharge, 0)
tempmap0 := make(map[int32]*proto.InnerDailyRecharge)
for _, value := range listmap {
temp := &proto.InnerDailyRecharge{AppId: -1}
for _, info := range value.List {
temp.AppId = 0
temp.Name = "天宇总充值"
temp.DateTime = info.DateTime
temp.CellType = 1
temp.DailyVistCount += info.DailyVistCount
temp.DailyRegister += info.DailyRegister
temp.DailyAppAmount += info.DailyAppAmount
temp.DailyGameAmount += info.DailyGameAmount
temp.RechargeOrder += info.RechargeOrder
tempmap0[info.AppId] = info
}
tempmap0[temp.AppId] = temp
value.List = append(value.List, temp)
for i, j := 0, len(value.List)-1; i < j; i, j = i+1, j-1 {
value.List[i], value.List[j] = value.List[j], value.List[i]
}
}
for _, value := range tempmap0 {
tempcell = append(tempcell, value)
}
sort.Slice(tempcell, func(i, j int) bool {
return tempcell[i].AppId < tempcell[j].AppId
})
var f *excelize.File
dir := beginmonth.Format("2006-01")
dayrow := beginmonth.Format("2006-01-02")
filePath := "/usr/local" + "/bakmonthbot/" + dir + "/"
filename := filePath + dayrow + ".xlsx"
if !DocExsit(filePath) {
err = os.MkdirAll(filePath, os.ModePerm)
if err != nil {
log.Error("InnerServerBotDateMonthStat:export save file to disk failed",
log.Any("fileName", filePath),
log.E(err))
return err
}
}
f = excelize.NewFile()
if f == nil {
log.Error("InnerServerBotDateMonthStat f failed")
err = errors.New("InnerServerBotDateMonthStat f failed")
return err
}
// 设置表头样式
headStyleID, _ := f.NewStyle(titlestyle)
// 设置表头样式
headStyleID0, _ := f.NewStyle(cellstyle)
if tempcell != nil && len(tempcell) > 0 {
err = NewXlslFileBot(f, tempcell, headStyleID, headStyleID0)
if err != nil {
log.Error("InnerServerBotDateMonthStat excelize.OpenFile failed",
log.Any("fileName", filePath),
log.E(err))
return err
}
}
for key, value := range listmap {
cols := f.GetRows(sheetName)
if len(cols) < 2 || len(cols[1]) < len(value.List) {
return errors.New("InnerServerBotcmdMonthStat cols < 0")
}
for rowindex := 0; rowindex < len(cols); rowindex++ {
if cols[rowindex][0] != "" && cols[rowindex][0] == key {
for _, info := range value.List {
info.BeginRow = rowindex + 1
info.IsOld = true
}
}
}
for colindex := 0; colindex < len(cols[1]); colindex++ {
for _, info := range value.List {
appid := strconv.Itoa(int(info.AppId))
if cols[1][colindex] != "" && cols[1][colindex] == appid {
info.BeginCol = colindex + 1
info.IsOld = true
}
}
}
for _, info := range value.List {
cellday, serr := utils.CoordinatesToCellName(info.BeginCol, info.BeginRow)
if serr != nil {
log.Error("InnerServerBotDateMonthStat CoordinatesToCellName ", log.Any("err", serr))
return serr
}
cellregister, serr := utils.CoordinatesToCellName(info.BeginCol+1, info.BeginRow)
if serr != nil {
log.Error("InnerServerBotDateMonthStat CoordinatesToCellName ", log.Any("err", serr))
return serr
}
cellrecharge, serr := utils.CoordinatesToCellName(info.BeginCol+2, info.BeginRow)
if serr != nil {
log.Error("InnerServerBotDateMonthStat CoordinatesToCellName ", log.Any("err", serr))
return serr
}
cellgame, serr := utils.CoordinatesToCellName(info.BeginCol+3, info.BeginRow)
if serr != nil {
log.Error("InnerServerBotDateMonthStat CoordinatesToCellName ", log.Any("err", serr))
return serr
}
cellorder, serr := utils.CoordinatesToCellName(info.BeginCol+4, info.BeginRow)
if serr != nil {
log.Error("InnerServerBotDateMonthStat CoordinatesToCellName ", log.Any("err", serr))
return serr
}
f.SetCellStyle(sheetName, cellday, cellday, headStyleID0)
totalamount := fmt.Sprintf("%.2f", float64(info.DailyVistCount)*0.01)
f.SetCellValue(sheetName, cellday, totalamount)
f.SetCellStyle(sheetName, cellregister, cellday, headStyleID0)
totalamount = fmt.Sprintf("%.2f", float64(info.DailyRegister)*0.01)
f.SetCellValue(sheetName, cellregister, totalamount)
f.SetCellStyle(sheetName, cellrecharge, cellday, headStyleID0)
totalamount = fmt.Sprintf("%.2f", float64(info.DailyAppAmount)*0.0001)
f.SetCellValue(sheetName, cellrecharge, totalamount)
if info.AppId == 0 || info.AppId == 888 {
f.SetCellStyle(sheetName, cellgame, cellday, headStyleID0)
totalamount = fmt.Sprintf("%.2f", float64(info.DailyGameAmount)*0.0001)
f.SetCellValue(sheetName, cellgame, totalamount)
}
f.SetCellStyle(sheetName, cellorder, cellday, headStyleID0)
totalamount = fmt.Sprintf("%.2f", float64(info.RechargeOrder)*0.01)
f.SetCellValue(sheetName, cellorder, totalamount)
}
}
err = f.SaveAs(filename)
if err != nil {
log.Error("InnerServerBotDateMonthStat CoordinatesToCellName ", log.Any("err", err))
return err
}
if statg.RchssendBot != nil {
err = statg.RchssendBot.Sendrchstochat("天宇公司综合日报间隔日期", filename, tgbot.FileTypeDocument)
//statg.Bot.Sendrchstochat("充值", fileName, tgbot.FileTypeDocument)
if err != nil {
log.Error("InnerServerBotDateMonthStat Sendrchstochat", log.Any("Sendrchstochat", err))
}
}
log.Info("InnerServerBotDateMonthStat end")
return nil
}
// bili请求麻豆知乎信息只在bili启动麻豆知乎只响应数据
func InnerServerBotDateDayStat(ctx context.Context, cmd *tgbot.Command) error {
if statg.Conf.ServerIndex != statg.ServerIndexBili {
log.Error("InnerServerBotDateDayStat serverindex",
log.Any("serverindex", statg.Conf.ServerIndex))
return nil
}
log.Info("InnerServerBotDateDayStat start")
sBegin := cmd.GetFlagStringValue("-date")
if sBegin == "" {
return tgbot.NewUserError(cmd, "请输入查询开始日期,格式:\n"+cmd.Help+"\n"+cmd.Desc)
}
beginmonth, err := time.ParseInLocation("2006-01-02", sBegin, time.Local)
if err != nil {
return tgbot.NewUserError(cmd, "解析开始日期出错, 格式:\n"+cmd.Help+"\n"+cmd.Desc)
}
endmonth := beginmonth.AddDate(0, 0, 1)
list := make([]*proto.InnerDailyRecharge, 0)
for id, info := range statg.Conf.InnerServers {
if id == statg.ServerIndexBili {
continue
}
if id != statg.ServerIndexBili && info == "" {
log.Error("InnerServerBotDateDayStat server not found",
log.Any("InnerServerBotDateDayStat", id))
continue
}
req := &proto.InnerRechargeBotcmdReq{
InnerId: int32(id),
BeginTime: beginmonth,
EndTime: endmonth,
}
url := info + "/api/stat/inner/rechargebotcmd/list"
log.Info("InnerServerBotDateDayStat", log.Any("req", url))
res := &proto.InnerDailyRechargeResp{}
err = InnerRequestBotcmd(url, req, res)
if err != nil {
log.Error("InnerServerBotDateDayStat InnerRequest",
log.Any("InnerRequest", err))
continue
}
if res == nil {
log.Error("InnerServerBotDateDayStat InnerRequest data not found")
continue
}
if res.Data.List != nil && len(res.Data.List) > 0 {
list = append(list, res.Data.List...)
}
}
bililist, appinfo, err := GetDbListBotcmd(ctx, &endmonth, &beginmonth)
if err != nil {
log.Error("InnerServerBotDateDayStat:GetDbListBotcmd", log.E(err))
return err
}
for _, info := range bililist {
name := ""
if data, ok := appinfo[info.AppId]; ok {
name = data.Name
}
temp := &proto.InnerDailyRecharge{
AppId: info.AppId,
Name: name,
Type: "",
DateTime: info.SumDate.Format("2006-01-02"),
DailyVistCount: info.TotalVisitCount,
DailyRegister: info.NewTotalCount,
DailyAppAmount: info.TotalAmount,
DailyGameAmount: info.GameRechageAmount,
RechargeOrder: info.SuccessRechargeOrder,
}
list = append(list, temp)
}
temp := &proto.InnerDailyRecharge{}
for _, info := range list {
temp.AppId = 0
temp.CellType = 1
temp.Name = "天宇总充值"
temp.DailyVistCount += info.DailyVistCount
temp.DailyRegister += info.DailyRegister
temp.DailyAppAmount += info.DailyAppAmount
temp.DailyGameAmount += info.DailyGameAmount
temp.RechargeOrder += info.RechargeOrder
}
list = append(list, temp)
for i, j := 0, len(list)-1; i < j; i, j = i+1, j-1 {
list[i], list[j] = list[j], list[i]
}
var f *excelize.File
dir := beginmonth.Format("2006-01")
dayrow := beginmonth.Format("2006-01-02")
filePath := "/usr/local" + "/bakmonthday/" + dir + "/"
filename := filePath + dayrow + ".xlsx"
if !DocExsit(filePath) {
err = os.MkdirAll(filePath, os.ModePerm)
if err != nil {
log.Error("InnerServerBotDateDayStat:export save file to disk failed",
log.Any("fileName", filePath),
log.E(err))
return err
}
}
f = excelize.NewFile()
if f == nil {
log.Error("InnerServerBotDateDayStat f failed")
err = errors.New("InnerServerBotDateDayStat f failed")
return err
}
// 设置表头样式
headStyleID, _ := f.NewStyle(titlestyle)
// 设置表头样式
headStyleID0, _ := f.NewStyle(cellstyle)
err = NewXlslFileBot(f, list, headStyleID, headStyleID0)
if err != nil {
log.Error("InnerServerBotDateDayStat excelize.OpenFile failed",
log.Any("fileName", filePath),
log.E(err))
return err
}
row := 0
cols := f.GetRows(sheetName)
for rowindex, col := range cols {
for colindex, rowCell := range col {
if rowCell == "" {
continue
}
if colindex == 0 {
if rowCell == dayrow {
row = rowindex + 1
}
}
}
}
if row == 0 {
return errors.New("InnerServerBotDateDayStat row <= 0")
}
for _, info := range list {
cellday, serr := utils.CoordinatesToCellName(info.BeginCol, row)
if serr != nil {
log.Error("InnerServerBotDateDayStat CoordinatesToCellName ", log.Any("err", serr))
return serr
}
cellregister, serr := utils.CoordinatesToCellName(info.BeginCol+1, row)
if serr != nil {
log.Error("InnerServerBotDateDayStat CoordinatesToCellName ", log.Any("err", serr))
return serr
}
cellrecharge, serr := utils.CoordinatesToCellName(info.BeginCol+2, row)
if serr != nil {
log.Error("InnerServerBotDateDayStat CoordinatesToCellName ", log.Any("err", serr))
return serr
}
cellgame, serr := utils.CoordinatesToCellName(info.BeginCol+3, row)
if serr != nil {
log.Error("InnerServerBotDateDayStat CoordinatesToCellName ", log.Any("err", serr))
return serr
}
cellorder, serr := utils.CoordinatesToCellName(info.BeginCol+4, row)
if serr != nil {
log.Error("InnerServerBotDateDayStat CoordinatesToCellName ", log.Any("err", serr))
return serr
}
f.SetCellStyle(sheetName, cellday, cellday, headStyleID0)
totalamount := fmt.Sprintf("%.2f", float64(info.DailyVistCount)*0.01)
f.SetCellValue(sheetName, cellday, totalamount)
f.SetCellStyle(sheetName, cellregister, cellregister, headStyleID0)
totalamount = fmt.Sprintf("%.2f", float64(info.DailyRegister)*0.01)
f.SetCellValue(sheetName, cellregister, totalamount)
f.SetCellStyle(sheetName, cellrecharge, cellrecharge, headStyleID0)
totalamount = fmt.Sprintf("%.2f", float64(info.DailyAppAmount)*0.0001)
f.SetCellValue(sheetName, cellrecharge, totalamount)
if info.AppId == 0 || info.AppId == 888 {
f.SetCellStyle(sheetName, cellgame, cellgame, headStyleID0)
totalamount = fmt.Sprintf("%.2f", float64(info.DailyGameAmount)*0.0001)
f.SetCellValue(sheetName, cellgame, totalamount)
}
f.SetCellStyle(sheetName, cellorder, cellorder, headStyleID0)
totalamount = fmt.Sprintf("%.2f", float64(info.RechargeOrder)*0.01)
f.SetCellValue(sheetName, cellorder, totalamount)
}
err = f.SaveAs(filename)
if err != nil {
log.Error("InnerServerBotDateDayStat SaveAs ", log.Any("err", err))
return err
}
if statg.RchssendBot != nil {
err = statg.RchssendBot.Sendrchstochat("天宇公司综合日报具体date", filename,
tgbot.FileTypeDocument)
if err != nil {
log.Error("InnerServerBotDateDayStat Sendrchstochat", log.Any("Sendrchstochat", err))
}
}
log.Info("InnerServerBotDateDayStat end")
return nil
}
+970
View File
@@ -0,0 +1,970 @@
package innerserverser
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"os"
"path"
"sort"
"stat/api/proto"
"stat/common/log"
"stat/common/tgbot"
"stat/common/utils"
"stat/models/appInfoMod"
"stat/models/dailystatmod"
"stat/statg"
"time"
"github.com/360EntSecGroup-Skylar/excelize"
"go.mongodb.org/mongo-driver/bson"
)
const titlestyle = `{
"font":{
"color":"#333333",
"bold":true,
"size":16,
"family":"arial"
},
"alignment":{
"vertical":"center",
"horizontal":"center",
"wrap_text":true
}
}`
const cellstyle = `{
"font":{
"color":"#333333",
"bold":false,
"size":10,
"family":"arial"
},
"alignment":{
"vertical":"center",
"horizontal":"center",
"wrap_text":true
}
}`
func InnerRequest(url string, reqproto interface{}, bind interface{}) error {
data, err := json.Marshal(reqproto)
if err != nil {
log.Error("InnerRequest json.Marshal", log.Any("err", err))
return err
}
//resp, err = InnerPlainPost(url, data)
bodyReader := bytes.NewReader(data)
req, err := http.NewRequest(http.MethodPost, url, bodyReader)
if err != nil {
log.Error("InnerRequest http.NewRequest", log.Any("err", err))
return err
}
timeout := 30 * time.Second
req.Header.Set("Content-Type", "application/json")
client := http.Client{
Timeout: timeout,
}
resp, err := client.Do(req)
if err != nil {
log.Error("InnerRequest http.NewRequest", log.Any("err", err))
return err
}
if resp.StatusCode != 200 {
err = fmt.Errorf("error status %s, http code %d", resp.Status, resp.StatusCode)
log.Error("InnerRequest http.NewRequest resp", log.Any("err", err))
return err
}
defer resp.Body.Close()
data, err = io.ReadAll(resp.Body)
if err != nil {
log.Error("InnerRequest io.ReadAll", log.Any("err", err))
return err
}
return json.Unmarshal(data, bind)
}
// bili请求过来的
func InnerRecgaregeList(ctx context.Context, req *proto.InnerRechargeReq) (
*proto.InnerDailyRechargeList, error) {
if req.InnerId != int32(statg.Conf.ServerIndex) {
err := fmt.Errorf("InnerRecgaregeList req.InnerId %d", req.InnerId)
log.Error("InnerRecgaregeList req.InnerId", log.Any("err", err))
return nil, err
}
resp := &proto.InnerDailyRechargeList{}
resp.List = make([]*proto.InnerDailyRecharge, 0)
begin := req.Begin
end := req.End
list, appinfo, err := GetDailyStatList(ctx, &end, &begin)
if err != nil {
log.Error("InnerRecgaregeList:GetDbList", log.E(err))
return nil, err
}
for _, info := range list {
if info.AppId == 889 {
continue
}
name := ""
if data, ok := appinfo[info.AppId]; ok {
name = data.Name
}
temp := &proto.InnerDailyRecharge{
AppId: info.AppId,
Name: name,
Type: "",
SumDate: info.SumDate,
DateTime: info.SumDate.Format(time.DateOnly),
DailyVistCount: info.TotalVisitCount,
DailyRegister: info.NewTotalCount,
DailyAppAmount: info.TotalAmount,
DailyGameAmount: info.GameRechageAmount,
RechargeOrder: info.SuccessRechargeOrder,
}
resp.List = append(resp.List, temp)
}
return resp, nil
}
func GetDbList(ctx context.Context, day *time.Time, yesterday *time.Time) (
[]*dailystatmod.DailyStatMod, map[int32]*appInfoMod.AppInfo, error) {
list := make([]*dailystatmod.DailyStatMod, 0)
appinfo, serr := appInfoMod.GetAllAppIdMap(ctx, bson.M{})
if serr != nil {
log.Error("InnerServerDailyStat appInfoMod.GetAllAppInfo", log.Any("serr", serr))
return nil, nil, serr
}
for _, info := range appinfo {
//data, serr := dailystatmod.FindOneDailyStat(ctx, info.AppId, yesterday)
rechargelist, serr := dailystatmod.GetDailyStatTotalList(ctx, &info.AppId, yesterday, day)
if serr != nil {
log.Error("InnerServerDailyStat dailystatmod.GetDailyStatTotalList", log.Any("serr", serr))
continue
}
if len(rechargelist) != 1 {
log.Error("InnerServerDailyStat dailystatmod.GetDailyStatTotalList",
log.Any("len(rechargelist)", len(rechargelist)), log.Any("appid", info.AppId),
log.Any("yesterday", yesterday), log.Any("day", day))
continue
}
if rechargelist != nil {
list = append(list, rechargelist...)
} else {
log.Error("InnerServerDailyStat rechargelist nil")
}
}
return list, appinfo, nil
}
// 从这个方法开始bili本机调用
func DocExsit(path string) bool {
_, err := os.Stat(path)
if err != nil {
if os.IsExist(err) {
return true
}
return false
}
return true
}
// bili请求麻豆知乎信息只在bili启动麻豆知乎只响应数据
func InnerServerDailyStat(ctx context.Context) error {
if statg.Conf.ServerIndex != statg.ServerIndexBili {
log.Error("InnerServerDailyStat serverindex",
log.Any("serverindex", statg.Conf.ServerIndex))
return nil
}
log.Info("InnerServerDailyStat start")
list := make([]*proto.InnerDailyRecharge, 0)
now := time.Now()
for id, info := range statg.Conf.InnerServers {
if id == statg.ServerIndexBili {
continue
}
if id != statg.ServerIndexBili && info == "" {
log.Error("statg.Conf.InnerServers server not found",
log.Any("statg.Conf.InnerServers", id))
continue
}
req := &proto.InnerRechargeReq{
InnerId: int32(id),
Begin: utils.BeginWithTime(now).Add(-24 * time.Hour),
End: utils.BeginWithTime(now),
}
url := info + "/api/stat/inner/recharge/list"
log.Info("InnerServerDailyStat", log.Any("req", url))
res := &proto.InnerDailyRechargeResp{}
err := InnerRequest(url, req, res)
if err != nil {
log.Error("InnerServerDailyStat InnerRequest",
log.Any("InnerRequest", err))
continue
}
if res == nil {
log.Error("InnerServerDailyStat InnerRequest data not found")
continue
}
if res.Data.List != nil && len(res.Data.List) > 0 {
list = append(list, res.Data.List...)
}
}
/*appinfo, serr := appInfoMod.GetAllAppIdMap(ctx, bson.M{})
if serr != nil {
log.Error("InnerServerDailyStat appInfoMod.GetAllAppInfo", log.Any("serr", serr))
return nil, serr
}
*/
yesterday := utils.BeginOfYesterday()
day := utils.BeginOfToday()
bililist, appinfo, err := GetDbList(ctx, &day, &yesterday)
if err != nil {
log.Error("InnerServerDailyStat:GetDbList", log.E(err))
return err
}
for _, info := range bililist {
name := ""
if data, ok := appinfo[info.AppId]; ok {
name = data.Name
}
temp := &proto.InnerDailyRecharge{
AppId: info.AppId,
Name: name,
Type: "",
DateTime: "",
DailyVistCount: info.TotalVisitCount,
DailyRegister: info.NewTotalCount,
DailyAppAmount: info.TotalAmount,
DailyGameAmount: info.GameRechageAmount,
RechargeOrder: info.SuccessRechargeOrder,
}
list = append(list, temp)
}
temp := &proto.InnerDailyRecharge{}
for _, info := range list {
temp.AppId = 0
temp.Name = "天宇总充值"
temp.CellType = 1
temp.DailyVistCount += info.DailyVistCount
temp.DailyRegister += info.DailyRegister
temp.DailyAppAmount += info.DailyAppAmount
temp.DailyGameAmount += info.DailyGameAmount
temp.RechargeOrder += info.RechargeOrder
}
list = append(list, temp)
for i, j := 0, len(list)-1; i < j; i, j = i+1, j-1 {
list[i], list[j] = list[j], list[i]
}
dir := yesterday.Format("2006-01-02")
filePath := "/usr/local" + "/bakexel/" + dir + "/"
//filename := utils.BeginOfToday().Format("2006-01-02")
if !DocExsit(filePath) {
err = os.Mkdir(filePath, os.ModePerm)
if err != nil {
log.Error("InnerServerDailyStat:export save file to disk failed",
log.Any("fileName", filePath),
log.E(err))
return err
}
}
f := excelize.NewFile()
if f == nil {
log.Error("InnerServerDailyStat f failed")
err = errors.New("InnerServerDailyStat f failed")
return err
}
// 设置列宽
f.SetColWidth(sheetName, "A", "L", 23)
// 设置表头行高
f.SetRowHeight(sheetName, 1, 25)
// 设置表头样式
headStyleID, _ := f.NewStyle(titlestyle)
headStyleID0, _ := f.NewStyle(cellstyle)
f.SetCellStyle(sheetName, "A1", "L1", headStyleID)
idx := f.NewSheet(sheetName)
var exportTransWaterTitleold = map[string]string{
"A1": "产品",
"B1": "归属",
"C1": "日期",
"D1": "日活跃",
"E1": "日注册",
"F1": "视频日充值",
"G1": "棋牌日充值",
"H1": "交易笔数",
}
rowindex := 'A'
for k, v := range exportTransWaterTitleold {
f.SetCellValue(sheetName, k, v)
row := fmt.Sprintf("%s%d", string(rowindex), 2)
f.SetCellStyle(sheetName, row, row, headStyleID0)
rowindex++
}
for k, v := range list {
i := 'A' // i 不能超过Z,即列输不能超过26列,超出将出现bug
if v.AppId != 0 {
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), v.Name) //名称
}
i++
/*if v.AppId != 0 {
if v.AppId == 888 {
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), "集团") //归属
} else {
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), "内部") //归属
}
}
*/
i++
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), dir) //时间
i++
totalamount := fmt.Sprintf("%.2f", float64(v.DailyVistCount)*0.01)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), totalamount) //日活跃
i++
totalamount = fmt.Sprintf("%.2f", float64(v.DailyRegister)*0.01)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), totalamount) //日注册
i++
totalamount = fmt.Sprintf("%.2f", float64(v.DailyAppAmount)*0.0001)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), totalamount) //视频日充值
i++
if v.AppId == 0 || v.AppId == 888 {
totalamount = fmt.Sprintf("%.2f", float64(v.DailyGameAmount)*0.0001)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), totalamount) //棋牌日充值
}
i++
totalamount = fmt.Sprintf("%.2f", float64(v.RechargeOrder)*0.01)
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), totalamount) //交易笔数
}
f.SetActiveSheet(idx)
fileName := filePath + dir + ".xlsx"
if err = f.SaveAs(fileName); err != nil {
log.Error("InnerServerDailyStat:export save file to disk failed",
log.Any("fileName", fileName),
log.E(err))
return err
}
if statg.RchssendBot != nil {
err = statg.RchssendBot.Sendrchstochat("天宇公司日报", fileName, tgbot.FileTypeDocument)
//statg.Bot.Sendrchstochat("充值", fileName, tgbot.FileTypeDocument)
if err != nil {
log.Error("Sendrchstochat", log.Any("Sendrchstochat", err))
}
}
log.Info("InnerServerDailyStat end")
return nil
}
// bili请求麻豆知乎信息只在bili启动麻豆知乎只响应数据
func InnerServerMonthStat(ctx context.Context, begin, end *time.Time) error {
if statg.Conf.ServerIndex != statg.ServerIndexBili {
log.Error("InnerServerMonthStat serverindex",
log.Any("serverindex", statg.Conf.ServerIndex))
return nil
}
log.Info("InnerServerMonthStat start")
var f *excelize.File
newxlslfile := false
var err error
yesterday := utils.BeginOfYesterday()
day := utils.BeginOfToday()
if begin != nil && end != nil {
yesterday = *begin
day = *end
}
dir := yesterday.Format("2006-01")
dayrow := yesterday.Format("2006-01-02")
filePath := "/usr/local" + "/bakmonth/" + dir + "/"
filename := filePath + dir + ".xlsx"
if !DocExsit(filePath) {
err = os.MkdirAll(filePath, os.ModePerm)
if err != nil {
log.Error("InnerServerMonthStat:export save file to disk failed",
log.Any("fileName", filePath),
log.E(err))
return err
}
f = excelize.NewFile()
newxlslfile = true
} else {
if !DocExsit(filename) {
f = excelize.NewFile()
newxlslfile = true
} else {
f, err = excelize.OpenFile(filename)
if err != nil {
log.Error("InnerServerMonthStat excelize.OpenFile failed",
log.Any("fileName", filePath),
log.E(err))
return err
}
}
}
if f == nil {
log.Error("InnerServerMonthStat f failed")
err = errors.New("InnerServerMonthStat f failed")
return err
}
list := make([]*proto.InnerDailyRecharge, 0)
for id, info := range statg.Conf.InnerServers {
if id == statg.ServerIndexBili {
continue
}
if id != statg.ServerIndexBili && info == "" {
log.Error("InnerServerMonthStat server not found",
log.Any("statg.Conf.InnerServers", id))
continue
}
req := &proto.InnerRechargeReq{
InnerId: int32(id),
Begin: yesterday,
End: day,
}
url := info + "/api/stat/inner/recharge/list"
log.Info("InnerServerMonthStat", log.Any("req", url))
res := &proto.InnerDailyRechargeResp{}
err = InnerRequest(url, req, res)
if err != nil {
log.Error("InnerServerMonthStat InnerRequest",
log.Any("InnerRequest", err))
continue
}
if res == nil {
log.Error("InnerServerMonthStat InnerRequest data not found")
continue
}
if res.Data.List != nil && len(res.Data.List) > 0 {
list = append(list, res.Data.List...)
}
}
bililist, appinfo, err := GetDbList(ctx, &day, &yesterday)
if err != nil {
log.Error("InnerServerMonthStat:GetDbList", log.E(err))
return err
}
for _, info := range bililist {
name := ""
if data, ok := appinfo[info.AppId]; ok {
name = data.Name
}
temp := &proto.InnerDailyRecharge{
AppId: info.AppId,
Name: name,
Type: "",
DateTime: "",
DailyVistCount: info.TotalVisitCount,
DailyRegister: info.NewTotalCount,
DailyAppAmount: info.TotalAmount,
DailyGameAmount: info.GameRechageAmount,
RechargeOrder: info.SuccessRechargeOrder,
}
list = append(list, temp)
}
temp := &proto.InnerDailyRecharge{}
for _, info := range list {
temp.AppId = 0
temp.CellType = 1
temp.Name = "天宇总充值"
temp.DailyVistCount += info.DailyVistCount
temp.DailyRegister += info.DailyRegister
temp.DailyAppAmount += info.DailyAppAmount
temp.DailyGameAmount += info.DailyGameAmount
temp.RechargeOrder += info.RechargeOrder
}
list = append(list, temp)
for i, j := 0, len(list)-1; i < j; i, j = i+1, j-1 {
list[i], list[j] = list[j], list[i]
}
// 设置表头样式
headStyleID, _ := f.NewStyle(titlestyle)
// 设置表头样式
headStyleID0, _ := f.NewStyle(cellstyle)
if newxlslfile {
err = NewXlslFile0(f, list, headStyleID, headStyleID0)
if err != nil {
log.Error("InnerServerMonthStat excelize.OpenFile failed",
log.Any("fileName", filePath),
log.E(err))
return err
}
} else {
err = OpenOldXlsl(f, list)
if err != nil {
log.Error("InnerServerMonthStat excelize.OpenFile failed",
log.Any("fileName", filePath),
log.E(err))
return err
}
}
row := 0
cols := f.GetRows(sheetName)
for rowindex, col := range cols {
for colindex, rowCell := range col {
if rowCell == "" {
continue
}
if colindex == 0 {
if rowCell == dayrow {
row = rowindex + 1
}
}
}
}
if row == 0 {
return errors.New("InnerServerMonthStat row <= 0")
}
for _, info := range list {
cellday, serr := utils.CoordinatesToCellName(info.BeginCol, row)
if serr != nil {
log.Error("InnerServerMonthStat CoordinatesToCellName ", log.Any("err", serr))
return serr
}
cellregister, serr := utils.CoordinatesToCellName(info.BeginCol+1, row)
if serr != nil {
log.Error("InnerServerMonthStat CoordinatesToCellName ", log.Any("err", serr))
return serr
}
cellrecharge, serr := utils.CoordinatesToCellName(info.BeginCol+2, row)
if serr != nil {
log.Error("InnerServerMonthStat CoordinatesToCellName ", log.Any("err", serr))
return serr
}
cellgame, serr := utils.CoordinatesToCellName(info.BeginCol+3, row)
if serr != nil {
log.Error("InnerServerMonthStat CoordinatesToCellName ", log.Any("err", serr))
return serr
}
cellorder, serr := utils.CoordinatesToCellName(info.BeginCol+4, row)
if serr != nil {
log.Error("InnerServerMonthStat CoordinatesToCellName ", log.Any("err", serr))
return serr
}
f.SetCellStyle(sheetName, cellday, cellday, headStyleID0)
totalamount := fmt.Sprintf("%.2f", float64(info.DailyVistCount)*0.01)
f.SetCellValue(sheetName, cellday, totalamount)
f.SetCellStyle(sheetName, cellregister, cellregister, headStyleID0)
totalamount = fmt.Sprintf("%.2f", float64(info.DailyRegister)*0.01)
f.SetCellValue(sheetName, cellregister, totalamount)
f.SetCellStyle(sheetName, cellrecharge, cellrecharge, headStyleID0)
totalamount = fmt.Sprintf("%.2f", float64(info.DailyAppAmount)*0.0001)
f.SetCellValue(sheetName, cellrecharge, totalamount)
if info.AppId == 0 || info.AppId == 888 {
f.SetCellStyle(sheetName, cellgame, cellgame, headStyleID0)
totalamount = fmt.Sprintf("%.2f", float64(info.DailyGameAmount)*0.0001)
f.SetCellValue(sheetName, cellgame, totalamount)
}
f.SetCellStyle(sheetName, cellorder, cellorder, headStyleID0)
totalamount = fmt.Sprintf("%.2f", float64(info.RechargeOrder)*0.01)
f.SetCellValue(sheetName, cellorder, totalamount)
}
if newxlslfile {
err = f.SaveAs(filename)
if err != nil {
log.Error("InnerServerMonthStat SaveAs ", log.Any("err", err))
return err
}
} else {
err = f.Save()
if err != nil {
log.Error("InnerServerMonthStat Save ", log.Any("err", err))
return err
}
}
input, err := os.ReadFile(filename)
if err != nil {
log.Error("InnerServerMonthStat ReadFile ", log.Any("err", err))
return err
}
destinationFile := filePath + dayrow + ".xlsx"
err = os.WriteFile(destinationFile, input, 0777)
if err != nil {
log.Error("InnerServerMonthStat WriteFile ", log.Any("err", err))
return err
}
if statg.RchssendBot != nil {
err = statg.RchssendBot.Sendrchstochat("天宇公司综合日报", destinationFile,
tgbot.FileTypeDocument)
if err != nil {
log.Error("InnerServerMonthStat", log.Any("Sendrchstochat", err))
}
}
log.Info("InnerServerMonthStat end")
return nil
}
func InnerServerMonthStat2(ctx context.Context, begin, end *time.Time, cmd *tgbot.Command) error {
if statg.Conf.ServerIndex != statg.ServerIndexBili {
log.Error("InnerServerMonthStat serverindex",
log.Any("serverindex", statg.Conf.ServerIndex))
return nil
}
log.Info("InnerServerMonthStat start")
f := excelize.NewFile()
now := time.Now().AddDate(0, 0, -1)
start := utils.BeginOfMonth(now)
stop := utils.BeginWithTime(now).AddDate(0, 0, 1)
if begin != nil {
start = *begin
}
if end != nil {
stop = *end
}
listOrg := make([]*proto.InnerDailyRecharge, 0)
for id, info := range statg.Conf.InnerServers {
req := &proto.InnerRechargeReq{
InnerId: int32(id),
Begin: start,
End: stop,
}
url := info + "/api/stat/inner/recharge/list"
log.Info("InnerServerMonthStat", log.Any("req", url))
res := &proto.InnerDailyRechargeResp{}
err := InnerRequest(url, req, res)
if err != nil {
log.Error("InnerServerMonthStat InnerRequest",
log.Any("InnerRequest", err))
continue
}
if res == nil {
log.Error("InnerServerMonthStat InnerRequest data not found")
continue
}
if res.Data.List != nil && len(res.Data.List) > 0 {
listOrg = append(listOrg, res.Data.List...)
}
}
appNames := make(map[int32]string)
appNames[0] = "天宇总充值"
source := map[int64]*DayRecharge{}
for _, info := range listOrg {
appNames[info.AppId] = info.Name
temp := &proto.InnerDailyRecharge{
AppId: info.AppId,
Name: info.Name,
SumDate: info.SumDate,
DateTime: info.SumDate.Format(time.DateOnly),
DailyVistCount: info.DailyVistCount,
DailyRegister: info.DailyRegister,
DailyAppAmount: info.DailyAppAmount,
DailyGameAmount: info.DailyGameAmount,
RechargeOrder: info.RechargeOrder,
}
found, ok := source[info.SumDate.Unix()]
if !ok {
found = &DayRecharge{
SumDate: info.SumDate,
List: make([]*proto.InnerDailyRecharge, 0),
Dict: make(map[int32]*proto.InnerDailyRecharge),
}
source[info.SumDate.Unix()] = found
}
found.List = append(found.List, temp)
found.Dict[info.AppId] = temp
found.Total.Name = "天宇总充值"
found.Total.DateTime = info.SumDate.Format(time.DateOnly)
found.Total.SumDate = info.SumDate
found.Total.DailyVistCount += temp.DailyVistCount
found.Total.DailyRegister += temp.DailyRegister
found.Total.DailyAppAmount += temp.DailyAppAmount
found.Total.DailyGameAmount += temp.DailyGameAmount
found.Total.RechargeOrder += temp.RechargeOrder
}
appIds := make([]int, 0)
for id, _ := range appNames {
appIds = append(appIds, int(id))
}
sort.Sort(sort.IntSlice(appIds))
AppInfos := make([]*AppInformation, 0)
for _, id := range appIds {
AppInfos = append(AppInfos, &AppInformation{
Id: id,
Name: appNames[int32(id)],
})
}
for _, value := range source {
value.Dict[0] = &value.Total
}
// 按APP汇总
for _, app := range AppInfos {
for _, value := range source {
if info, ok := value.Dict[int32(app.Id)]; ok {
app.Total.DailyVistCount += info.DailyVistCount
app.Total.DailyRegister += info.DailyRegister
app.Total.DailyAppAmount += info.DailyAppAmount
app.Total.DailyGameAmount += info.DailyGameAmount
app.Total.RechargeOrder += info.RechargeOrder
}
}
}
// 设置表头样式
headStyleID, _ := f.NewStyle(titlestyle)
// 设置表头样式
headStyleID0, _ := f.NewStyle(cellstyle)
// 设置表结构
err := NewXlslFile2(f, AppInfos, headStyleID, headStyleID0, now)
if err != nil {
log.Error("InnerServerMonthStat excelize.OpenFile failed", log.E(err))
return err
}
// 设置数据
row := 4
for day := start; day.Before(stop); day = day.AddDate(0, 0, 1) {
if found, ok := source[day.Unix()]; ok {
for _, app := range AppInfos {
if info, ok2 := found.Dict[int32(app.Id)]; ok2 {
_ = SetAppData(f, info, row, app.BeginCol, headStyleID0)
}
}
}
row++
}
// 设置总计
row = utils.EndOfMonth(now).Day() - utils.BeginOfMonth(now).Day() + 1 + 4
for _, app := range AppInfos {
_ = SetAppData(f, &app.Total, row, app.BeginCol, headStyleID0)
}
// 保存文件
tempDir := os.TempDir()
fmt.Println("临时文件目录:", tempDir)
// 创建一个临时文件
yesterday := utils.BeginOfYesterday().Format(time.DateOnly)
fileName := path.Join(tempDir, fmt.Sprintf("%s.xlsx", yesterday))
//defer os.Remove(fileName) // 确保程序结束后删除临时文件
err = f.SaveAs(fileName)
if err != nil {
log.Error("InnerServerMonthStat SaveAs ", log.Any("err", err))
return err
}
// 发送文件
if cmd != nil {
err = cmd.SendFile("天宇公司综合日报", fileName)
} else {
if statg.RchssendBot != nil {
err = statg.RchssendBot.Sendrchstochat("天宇公司综合日报", fileName, tgbot.FileTypeDocument)
if err != nil {
log.Error("InnerServerMonthStat", log.Any("Sendrchstochat", err))
}
}
}
return err
}
func SetAppData(f *excelize.File, info *proto.InnerDailyRecharge, row, col int, headStyle int) error {
cellday, serr := utils.CoordinatesToCellName(col, row)
if serr != nil {
log.Error("InnerServerMonthStat CoordinatesToCellName ", log.Any("err", serr))
return serr
}
cellregister, serr := utils.CoordinatesToCellName(col+1, row)
if serr != nil {
log.Error("InnerServerMonthStat CoordinatesToCellName ", log.Any("err", serr))
return serr
}
cellrecharge, serr := utils.CoordinatesToCellName(col+2, row)
if serr != nil {
log.Error("InnerServerMonthStat CoordinatesToCellName ", log.Any("err", serr))
return serr
}
cellgame, serr := utils.CoordinatesToCellName(col+3, row)
if serr != nil {
log.Error("InnerServerMonthStat CoordinatesToCellName ", log.Any("err", serr))
return serr
}
cellorder, serr := utils.CoordinatesToCellName(col+4, row)
if serr != nil {
log.Error("InnerServerMonthStat CoordinatesToCellName ", log.Any("err", serr))
return serr
}
f.SetCellStyle(sheetName, cellday, cellday, headStyle)
totalamount := fmt.Sprintf("%.2f", float64(info.DailyVistCount)*0.01)
f.SetCellValue(sheetName, cellday, totalamount)
f.SetCellStyle(sheetName, cellregister, cellregister, headStyle)
totalamount = fmt.Sprintf("%.2f", float64(info.DailyRegister)*0.01)
f.SetCellValue(sheetName, cellregister, totalamount)
f.SetCellStyle(sheetName, cellrecharge, cellrecharge, headStyle)
totalamount = fmt.Sprintf("%.2f", float64(info.DailyAppAmount)*0.0001)
f.SetCellValue(sheetName, cellrecharge, totalamount)
if info.AppId == 0 || info.AppId == 888 {
f.SetCellStyle(sheetName, cellgame, cellgame, headStyle)
totalamount = fmt.Sprintf("%.2f", float64(info.DailyGameAmount)*0.0001)
f.SetCellValue(sheetName, cellgame, totalamount)
}
f.SetCellStyle(sheetName, cellorder, cellorder, headStyle)
totalamount = fmt.Sprintf("%.2f", float64(info.RechargeOrder)*0.01)
f.SetCellValue(sheetName, cellorder, totalamount)
return nil
}
func GetDailyStatList(ctx context.Context, day *time.Time, yesterday *time.Time) (
[]*dailystatmod.DailyStatMod, map[int32]*appInfoMod.AppInfo, error) {
list := make([]*dailystatmod.DailyStatMod, 0)
appinfo, serr := appInfoMod.GetAllAppIdMap(ctx, bson.M{})
if serr != nil {
log.Error("InnerServerDailyStat appInfoMod.GetAllAppInfo", log.Any("serr", serr))
return nil, nil, serr
}
for _, info := range appinfo {
rechargelist, serr := dailystatmod.GetDailyStatTotalList(ctx, &info.AppId, yesterday, day)
if serr != nil {
log.Error("InnerServerDailyStat dailystatmod.GetDailyStatTotalList", log.Any("serr", serr))
continue
}
if rechargelist != nil {
list = append(list, rechargelist...)
} else {
log.Error("InnerServerDailyStat rechargelist nil")
}
}
return list, appinfo, nil
}
type appDailyDataStat struct {
AppName string `json:"appName"` // 产品名称
AppId int32 `json:"appId"` // 产品Id
SumDate time.Time `json:"sumDate"` // 日期
Type string `json:"type"` // 产品类型,pay:付费,free:免费
TotalVisitCount float64 `json:"totalVisitCount"` // 日活跃
TotalNewCount float64 `json:"totalNewCount"` // 日注册
TotalVidAmount float64 `json:"totalVidAmount"` // 视频日充值
TotalQpAmount float64 `json:"totalQpAmount"` // 棋牌日充值
VipRechargeCount float64 `json:"vipRechargeCount"` // vip交易笔数
CoinRechargeCount float64 `json:"coinRechargeCount"` // 金币交易笔数
RechargeCount float64 `json:"rechargeCount"` // 交易总笔数
MonthAmount float64 `json:"monthAmount"` // 当月总充值
MonthNewCount float64 `json:"monthNewCount"` // 当月总注册
MonthRechargeCount float64 `json:"monthRechargeCount"` // 当月总交易笔数
MonthVisitCount float64 `json:"monthVisitCount"` // 当月总日活
}
func TianyuAppDailyData(ctx context.Context) ([]*appDailyDataStat, error) {
resp := make([]*appDailyDataStat, 0)
if statg.Conf.ServerIndex != statg.ServerIndexBili {
log.Error("InnerServerMonthStat serverindex",
log.Any("serverindex", statg.Conf.ServerIndex))
return resp, nil
}
log.Info("InnerServerMonthStat start")
now := time.Now().AddDate(0, 0, -1)
start := utils.BeginOfMonth(now)
stop := utils.BeginWithTime(now).AddDate(0, 0, 1)
listOrg := make([]*proto.InnerDailyRecharge, 0)
for id, info := range statg.Conf.InnerServers {
req := &proto.InnerRechargeReq{
InnerId: int32(id),
Begin: start,
End: stop,
}
url := info + "/api/stat/inner/recharge/list"
log.Info("InnerServerMonthStat", log.Any("req", url))
res := &proto.InnerDailyRechargeResp{}
err := InnerRequest(url, req, res)
if err != nil {
log.Error("InnerServerMonthStat InnerRequest",
log.Any("InnerRequest", err))
continue
}
if res == nil {
log.Error("InnerServerMonthStat InnerRequest data not found")
continue
}
if res.Data.List != nil && len(res.Data.List) > 0 {
listOrg = append(listOrg, res.Data.List...)
}
}
for _, info := range listOrg {
temp := &appDailyDataStat{
AppId: 100000 + info.AppId,
AppName: info.Name,
SumDate: info.SumDate,
TotalVisitCount: float64(info.DailyVistCount),
TotalNewCount: float64(info.DailyRegister),
TotalVidAmount: float64(info.DailyAppAmount),
TotalQpAmount: float64(info.DailyGameAmount),
RechargeCount: float64(info.RechargeOrder),
}
resp = append(resp, temp)
}
return resp, nil
}
+429
View File
@@ -0,0 +1,429 @@
package innerserverser
import (
"errors"
"fmt"
"github.com/360EntSecGroup-Skylar/excelize"
"stat/api/proto"
"stat/common/log"
"stat/common/utils"
"strconv"
"time"
)
var exportTransWaterTitle = []string{"日活跃", "日注册", "视频日充值", "棋牌日充值", "交易笔数"}
const sheetName = "Sheet1"
// 按字母
func NewXlslFile(f *excelize.File, list []*proto.InnerDailyRecharge) {
// 设置表头样式
headStyleID, _ := f.NewStyle(titlestyle)
filePath := "/usr/local" + "/bakexel/" + "vvvv.xlsx"
// 设置列宽
f.SetColWidth("Sheet1", "A", "L", 23)
// 设置表头行高
//f.SetRowHeight("Sheet1", 1, 25)
f.SetCellStyle("Sheet1", "A1", "L1", headStyleID)
idx := f.NewSheet(sheetName)
f.MergeCell("Sheet1", "A1", "A2")
f.SetCellValue(sheetName, "A1", "日期")
f.SetCellStyle(sheetName, "A4", "A50", headStyleID)
f.SetActiveSheet(idx)
now := time.Now()
day := now.Format("2006-01")
begin, end := utils.MonthDateRange(now)
beginday := utils.BeginOfMonth(now).Day()
endday := utils.EndOfMonth(now).Day()
index := "A"
for i := beginday; i <= endday; i++ {
tmp := fmt.Sprintf("%s%d", index, i+2)
vvvvv := fmt.Sprintf("%s-%d", day, i)
f.SetCellValue(sheetName, tmp, vvvvv)
}
one := 'A'
begin0 := 'B'
temp0 := begin0
pre := ""
for _, info := range list {
t0 := temp0
countt0 := 1
for i := 1; i < 5; i++ {
temp0 = t0 + int32(countt0)
countt0++
if temp0 > 'Z' {
one++
temp0 = 'A'
t0 = temp0
countt0 = 1
}
}
//merge := setcellindex + 5 + int32(index*5)
beginmerge := fmt.Sprintf("%s%s1", pre, string(begin0))
if one > 'A' {
pre = string(one - 1)
}
endmerge := fmt.Sprintf("%s%s1", pre, string(temp0))
temp0 += 2
if temp0 > 'Z' {
one++
temp0 = 'A' + temp0 - 'Z' - 1
if one > 'A' {
pre = string(one - 1)
}
}
begin0 = temp0
f.MergeCell("Sheet1", beginmerge, endmerge)
//tmp := fmt.Sprintf("%s1", string(index0))
f.SetCellValue(sheetName, beginmerge, info.Name)
fmt.Println(beginmerge, endmerge)
}
err := f.SaveAs(filePath)
if err != nil {
fmt.Println(err)
}
fmt.Println(begin, end)
}
// 按行列索引
func NewXlslFile0(f *excelize.File, list []*proto.InnerDailyRecharge, titlestyle, cellstyle int) error {
// 设置列宽
f.SetColWidth(sheetName, "A", "AAA", 19)
// 设置表头行高
f.SetRowHeight(sheetName, 1, 25)
f.SetCellStyle(sheetName, "A1", "ZZ1", titlestyle)
f.SetCellStyle(sheetName, "A2", "ZZ2", titlestyle)
f.SetCellStyle(sheetName, "A3", "ZZ3", titlestyle)
idx := f.NewSheet(sheetName)
f.SetCellValue(sheetName, "A1", "日期")
f.SetActiveSheet(idx)
now := time.Now()
dayformat := now.Format("2006-01")
//begin, end := utils.MonthDateRange(now)
beginday := utils.BeginOfMonth(now).Day()
endday := utils.EndOfMonth(now).Day()
//设置日期
index := "A"
for i := beginday; i <= endday; i++ {
tmp := fmt.Sprintf("%s%d", index, i+3)
rowday := fmt.Sprintf("%s-%02d", dayformat, i)
f.SetCellValue(sheetName, tmp, rowday)
}
//新文件设置固定的主题和style
row := 1
col := 2
count := col
for _, info := range list {
begincol := count
count = count + 4
titleindex := 0
titlemap := make(map[string]string, 0)
for i := begincol; i <= count; i++ {
if titleindex < len(exportTransWaterTitle) {
titlecell, serr := utils.CoordinatesToCellName(begincol+titleindex, row+2)
if serr != nil {
log.Error("NewXlslFile0 CoordinatesToCellName ", log.Any("err", serr),
log.Any("title", exportTransWaterTitle[titleindex]))
return serr
}
titlemap[titlecell] = exportTransWaterTitle[titleindex]
}
titleindex++
}
info.BeginCol = begincol
// 第一行产品
begincell, serr := utils.CoordinatesToCellName(begincol, row)
if serr != nil {
log.Error("NewXlslFile0 CoordinatesToCellName ", log.Any("err", serr),
log.Any("infoname", info.Name))
return serr
}
endcell, serr := utils.CoordinatesToCellName(count, row)
if serr != nil {
log.Error("NewXlslFile0 CoordinatesToCellName ", log.Any("err", serr),
log.Any("infoname", info.Name))
return serr
}
// 第二行appid
appidbegincell, serr := utils.CoordinatesToCellName(begincol, row+1)
if serr != nil {
log.Error("NewXlslFile0 CoordinatesToCellName ", log.Any("err", serr),
log.Any("infoappid", info.AppId))
return serr
}
appidendcell, serr := utils.CoordinatesToCellName(count, row+1)
if serr != nil {
log.Error("NewXlslFile0 CoordinatesToCellName ", log.Any("err", serr),
log.Any("infoappid", info.AppId))
return serr
}
f.MergeCell(sheetName, begincell, endcell)
f.MergeCell(sheetName, appidbegincell, appidendcell)
//tmp := fmt.Sprintf("%s1", string(index0))
/*if info.AppId != 0 {
if info.AppId == 888 {
//tmp := fmt.Sprintf("%s1", string(index0))
f.SetCellValue(sheetName, begincell, info.Name+"(集团)")
} else {
f.SetCellValue(sheetName, begincell, info.Name+"(内部)")
}
} else {
*/
f.SetCellValue(sheetName, begincell, info.Name)
//}
f.SetCellValue(sheetName, appidbegincell, info.AppId)
//第三行app统计的信息
for key, value := range titlemap {
//f.SetCellStyle(sheetName, key, key, cellstyle)
f.SetCellValue(sheetName, key, value)
}
count += 2
}
return nil
}
// 合并单元格
func MergeCell(f *excelize.File, sheetname string, col int, list []*proto.InnerDailyRecharge) error {
row := 1
count := col + 1
for _, info := range list {
begincol := count
count = count + 4
titleindex := 0
titlemap := make(map[string]string, 0)
for i := begincol; i <= count; i++ {
if titleindex < len(exportTransWaterTitle) {
titlecell, serr := utils.CoordinatesToCellName(begincol+titleindex, row+2)
if serr != nil {
log.Error("MergeCell CoordinatesToCellName ", log.Any("err", serr),
log.Any("title", exportTransWaterTitle[titleindex]))
return serr
}
titlemap[titlecell] = exportTransWaterTitle[titleindex]
}
titleindex++
}
begincell, serr := utils.CoordinatesToCellName(begincol, row)
if serr != nil {
log.Error("MergeCell CoordinatesToCellName ", log.Any("err", serr),
log.Any("infoname", info.Name))
return serr
}
endcell, serr := utils.CoordinatesToCellName(count, row)
if serr != nil {
log.Error("MergeCell CoordinatesToCellName ", log.Any("err", serr),
log.Any("infoname", info.Name))
return serr
}
// 第一行appid
appidbegincell, serr := utils.CoordinatesToCellName(begincol, row+1)
if serr != nil {
log.Error("MergeCell CoordinatesToCellName ", log.Any("err", serr),
log.Any("appid", info.AppId))
return serr
}
appidendcell, serr := utils.CoordinatesToCellName(count, row+1)
if serr != nil {
log.Error("MergeCell CoordinatesToCellName ", log.Any("err", serr),
log.Any("appid", info.AppId))
return serr
}
info.BeginCol = begincol
f.MergeCell(sheetname, begincell, endcell)
/*if info.AppId != 0 {
if info.AppId == 888 {
f.SetCellValue(sheetname, begincell, info.Name+"(集团)")
} else {
f.SetCellValue(sheetname, begincell, info.Name+"(内部)")
}
} else {
*/
f.SetCellValue(sheetname, begincell, info.Name)
//}
f.MergeCell(sheetname, appidbegincell, appidendcell)
//tmp := fmt.Sprintf("%s1", string(index0))
f.SetCellValue(sheetname, appidbegincell, info.AppId)
for key, value := range titlemap {
f.SetCellValue(sheetname, key, value)
}
count += 2
}
return nil
}
func OpenOldXlsl(f *excelize.File, list []*proto.InnerDailyRecharge) error {
idx := f.GetSheetIndex(sheetName)
f.SetActiveSheet(idx)
cols := f.GetRows(sheetName)
begincol := 0
//查找数据开始单元格,新数据先合并单元格
/*for rowindex, col := range cols {
for colindex, rowCell := range col {
if rowCell == "" {
continue
}
if rowindex == 1 {
for _, info := range list {
if rowCell == string(info.AppId) {
info.BeginRow = rowindex + 1
info.BeginCol = colindex + 1
info.IsOld = true
}
}
}
}
}
*/
if len(cols) < 2 || len(cols[1]) < len(list) {
return errors.New("OpenOldXlsl cols < 0")
}
/*for rowindex := 0; rowindex < len(cols); rowindex++ {
if cols[rowindex][0] != "" {
for _, info := range list {
if cols[rowindex][0] == info.DateTime {
info.BeginRow = rowindex + 1
info.IsOld = true
}
}
}
}
*/
//旧的数据单元格开始信息
for colindex := 0; colindex < len(cols[1]); colindex++ {
for _, info := range list {
appid := strconv.Itoa(int(info.AppId))
if cols[1][colindex] != "" && cols[1][colindex] == appid {
info.BeginCol = colindex + 1
info.IsOld = true
}
}
}
//新的单元格从第几列信息
newlist := make([]*proto.InnerDailyRecharge, 0)
for _, info := range list {
if info.IsOld {
if info.BeginCol > begincol {
begincol = info.BeginCol
}
} else {
newlist = append(newlist, info)
}
}
if len(newlist) > 0 {
serr := MergeCell(f, sheetName, begincol+5, newlist)
if serr != nil {
log.Error("OpenOldXlsl MergeCell ", log.Any("err", serr))
return serr
}
}
return nil
}
func NewXlslFile2(f *excelize.File, list []*AppInformation, titlestyle, cellstyle int, now time.Time) error {
// 设置列宽
f.SetColWidth(sheetName, "A", "AAA", 19)
// 设置表头行高
f.SetRowHeight(sheetName, 1, 25)
f.SetCellStyle(sheetName, "A1", "ZZ1", titlestyle)
f.SetCellStyle(sheetName, "A2", "ZZ2", titlestyle)
f.SetCellStyle(sheetName, "A3", "ZZ3", titlestyle)
idx := f.NewSheet(sheetName)
f.SetCellValue(sheetName, "A1", "日期")
f.SetActiveSheet(idx)
dayformat := now.Format("2006-01")
//begin, end := utils.MonthDateRange(now)
beginday := utils.BeginOfMonth(now).Day()
endday := utils.EndOfMonth(now).Day()
//设置日期
index := "A"
for i := beginday; i <= endday; i++ {
tmp := fmt.Sprintf("%s%d", index, i+3)
rowday := fmt.Sprintf("%s-%02d", dayformat, i)
f.SetCellValue(sheetName, tmp, rowday)
}
// 设置汇总
tmp := fmt.Sprintf("%s%d", index, endday+1+3)
f.SetCellValue(sheetName, tmp, "汇总")
//新文件设置固定的主题和style
row := 1
col := 2
count := col
for _, info := range list {
begincol := count
count = count + 4
titleindex := 0
titlemap := make(map[string]string, 0)
for i := begincol; i <= count; i++ {
if titleindex < len(exportTransWaterTitle) {
titlecell, serr := utils.CoordinatesToCellName(begincol+titleindex, row+2)
if serr != nil {
log.Error("NewXlslFile0 CoordinatesToCellName ", log.Any("err", serr),
log.Any("title", exportTransWaterTitle[titleindex]))
return serr
}
titlemap[titlecell] = exportTransWaterTitle[titleindex]
}
titleindex++
}
info.BeginCol = begincol
// 第一行产品
begincell, serr := utils.CoordinatesToCellName(begincol, row)
if serr != nil {
log.Error("NewXlslFile0 CoordinatesToCellName ", log.Any("err", serr),
log.Any("infoname", info.Name))
return serr
}
endcell, serr := utils.CoordinatesToCellName(count, row)
if serr != nil {
log.Error("NewXlslFile0 CoordinatesToCellName ", log.Any("err", serr),
log.Any("infoname", info.Name))
return serr
}
// 第二行appid
appidbegincell, serr := utils.CoordinatesToCellName(begincol, row+1)
if serr != nil {
log.Error("NewXlslFile0 CoordinatesToCellName ", log.Any("err", serr),
log.Any("infoappid", info.Id))
return serr
}
appidendcell, serr := utils.CoordinatesToCellName(count, row+1)
if serr != nil {
log.Error("NewXlslFile0 CoordinatesToCellName ", log.Any("err", serr),
log.Any("infoappid", info.Id))
return serr
}
f.MergeCell(sheetName, begincell, endcell)
f.MergeCell(sheetName, appidbegincell, appidendcell)
f.SetCellValue(sheetName, begincell, info.Name)
f.SetCellValue(sheetName, appidbegincell, info.Id)
//第三行app统计的信息
for key, value := range titlemap {
f.SetCellValue(sheetName, key, value)
}
count += 2
}
return nil
}
+189
View File
@@ -0,0 +1,189 @@
package innerserverser
import (
"context"
"fmt"
"github.com/360EntSecGroup-Skylar/excelize"
"os"
"stat/api/proto"
"stat/common/log"
"stat/common/utils"
"stat/tool/pubtest"
"testing"
)
func TestExel1(t *testing.T) {
var f *excelize.File
newxlslfile := false
day0 := utils.BeginOfYesterday()
dir := day0.Format("2006-01")
filePath := "/usr/local" + "/bakmonth/" + dir + "/"
//filename := utils.BeginOfYesterday().Format("2006-01-02")
if !DocExsit(filePath) {
err := os.MkdirAll(filePath, os.ModePerm)
if err != nil {
log.Error("InnerServerDailyStat:export save file to disk failed",
log.Any("fileName", filePath),
log.E(err))
return
}
f = excelize.NewFile()
newxlslfile = true
} else {
if !DocExsit(filePath + dir + ".xlsx") {
f = excelize.NewFile()
newxlslfile = true
} else {
f, _ = excelize.OpenFile(filePath + dir + ".xlsx")
}
}
// 设置表头样式
headStyleID, _ := f.NewStyle(`{
"font":{
"color":"#333333",
"bold":true,
"size":16,
"family":"arial"
},
"alignment":{
"vertical":"center",
"horizontal":"center",
"wrap_text":true
}
}`)
headStyleID0, _ := f.NewStyle(`{
"font":{
"color":"#333333",
"bold":false,
"size":10,
"family":"arial"
},
"alignment":{
"vertical":"center",
"horizontal":"center",
"wrap_text":true
}
}`)
pubtest.TestInit("/Users/mac/Desktop/madou/stat_center")
ctx := context.Background()
list := make([]*proto.InnerDailyRecharge, 0)
yesterday := utils.BeginOfYesterday0()
bililist, appinfo, err := GetDbList(ctx, &day0, &yesterday)
if err != nil {
log.Error("InnerServerDailyStat:GetDbList", log.E(err))
}
for _, info := range bililist {
name := ""
if data, ok := appinfo[info.AppId]; ok {
name = data.Name
}
temp := &proto.InnerDailyRecharge{
AppId: info.AppId,
Name: name,
Type: "",
DateTime: "",
DailyVistCount: info.TotalVisitCount,
DailyRegister: info.NewTotalCount,
DailyAppAmount: info.TotalAmount,
DailyGameAmount: info.GameRechageAmount,
RechargeOrder: info.SuccessRechargeOrder,
}
list = append(list, temp)
}
temp := &proto.InnerDailyRecharge{}
for _, info := range list {
temp.AppId = 0
temp.Name = "天宇总充值"
temp.DailyVistCount += info.DailyVistCount
temp.DailyRegister += info.DailyRegister
temp.DailyAppAmount += info.DailyAppAmount
temp.DailyGameAmount += info.DailyGameAmount
temp.RechargeOrder += info.RechargeOrder
}
list = append(list, temp)
for i, j := 0, len(list)-1; i < j; i, j = i+1, j-1 {
list[i], list[j] = list[j], list[i]
}
if newxlslfile {
NewXlslFile0(f, list, headStyleID, headStyleID0)
} else {
OpenOldXlsl(f, list)
}
row := 1
day := yesterday.Format("2006-01-02")
cols := f.GetRows("Sheet1")
for rowindex, col := range cols {
for colindex, rowCell := range col {
if rowCell == "" {
continue
}
if colindex == 0 {
if rowCell == day {
row = rowindex
}
}
}
}
for _, info := range list {
cellday, serr := utils.CoordinatesToCellName(info.BeginCol, row)
if serr != nil {
log.Error("OpenOldXlsl CoordinatesToCellName ", log.Any("err", serr))
continue
}
cellregister, serr := utils.CoordinatesToCellName(info.BeginCol+1, row)
if serr != nil {
log.Error("OpenOldXlsl CoordinatesToCellName ", log.Any("err", serr))
continue
}
cellrecharge, serr := utils.CoordinatesToCellName(info.BeginCol+2, row)
if serr != nil {
log.Error("OpenOldXlsl CoordinatesToCellName ", log.Any("err", serr))
continue
}
cellgame, serr := utils.CoordinatesToCellName(info.BeginCol+3, row)
if serr != nil {
log.Error("OpenOldXlsl CoordinatesToCellName ", log.Any("err", serr))
continue
}
cellorder, serr := utils.CoordinatesToCellName(info.BeginCol+4, row)
if serr != nil {
log.Error("OpenOldXlsl CoordinatesToCellName ", log.Any("err", serr))
continue
}
f.SetCellStyle("Sheet1", cellday, cellday, headStyleID0)
totalamount := fmt.Sprintf("%.2f", float64(info.DailyVistCount)*0.01)
f.SetCellValue("Sheet1", cellday, totalamount)
f.SetCellStyle("Sheet1", cellregister, cellday, headStyleID0)
totalamount = fmt.Sprintf("%.2f", float64(info.DailyRegister)*0.01)
f.SetCellValue("Sheet1", cellregister, totalamount)
f.SetCellStyle("Sheet1", cellrecharge, cellday, headStyleID0)
totalamount = fmt.Sprintf("%.2f", float64(info.DailyAppAmount)*0.0001)
f.SetCellValue("Sheet1", cellrecharge, totalamount)
f.SetCellStyle("Sheet1", cellgame, cellday, headStyleID0)
if info.AppId == 0 || info.AppId == 888 {
totalamount = fmt.Sprintf("%.2f", float64(info.DailyGameAmount)*0.0001)
}
f.SetCellValue("Sheet1", cellgame, totalamount)
f.SetCellStyle("Sheet1", cellorder, cellday, headStyleID0)
totalamount = fmt.Sprintf("%.2f", float64(info.RechargeOrder)*0.01)
f.SetCellValue("Sheet1", cellorder, totalamount)
}
if newxlslfile {
f.SaveAs(filePath + dir + ".xlsx")
} else {
f.Save()
}
}
func TestExel0(t *testing.T) {
rowday := fmt.Sprintf("%s-%02d", "", 9)
fmt.Println(rowday)
}
+214
View File
@@ -0,0 +1,214 @@
package landpageSer
import (
"context"
"fmt"
"stat/api/proto"
"stat/models/appInfoMod"
"stat/models/downloadstat"
"stat/models/landpageImg"
"stat/service/cacheSer"
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo/options"
)
func ListDailyStat(ctx context.Context, param *proto.ListDailyStatReq) (*proto.ListLandPageStatRes, error) {
resp := &proto.ListLandPageStatRes{
Total: 0,
List: make([]*proto.DownloadStatInfo, 0),
}
filter := bson.M{"type": downloadstat.StatTypeDaily}
opt := &options.FindOptions{}
opt.SetSort(bson.D{{Key: "sumDate", Value: -1}})
opt.SetSkip((param.PageNum - 1) * param.PageSize)
opt.SetLimit(param.PageSize)
if param.AppId != nil {
filter["appId"] = bson.M{"$in": *param.AppId}
}
if param.StartTime != nil && param.EndTime != nil {
filter["sumDate"] = bson.M{"$gte": *param.StartTime, "$lt": *param.EndTime}
}
total, _ := downloadstat.CountStats(ctx, filter)
statList, err := downloadstat.FindManyStat(ctx, filter, opt)
if err != nil {
return nil, err
}
appMap, _ := appInfoMod.GetAllAppIdNameMap(ctx)
for _, item := range statList {
item.AppName = appMap[item.AppId]
tmp := &proto.DownloadStatInfo{
Stat: *item,
}
proto.StatSummation(&resp.Summary, tmp)
tmp.CalcConversionRate()
resp.List = append(resp.List, tmp)
}
resp.Total = total
resp.Summary.CalcConversionRate()
return resp, nil
}
func ListDailyHourStat(ctx context.Context, param *proto.ListDailyHourStatReq) (*proto.ListLandPageStatRes, error) {
resp := &proto.ListLandPageStatRes{
Total: 0,
List: make([]*proto.DownloadStatInfo, 0),
}
filter := bson.M{"type": downloadstat.StatTypeHour}
opt := &options.FindOptions{}
opt.SetSort(bson.D{{Key: "sumDate", Value: -1}, {Key: "hour", Value: -1}})
opt.SetSkip((param.PageNum - 1) * param.PageSize)
opt.SetLimit(param.PageSize)
if param.AppId != nil {
filter["appId"] = *param.AppId
}
if param.StartTime != nil && param.EndTime != nil {
filter["sumDate"] = bson.M{"$gte": *param.StartTime, "$lt": *param.EndTime}
}
if param.SumHour != nil {
filter["hour"] = *param.SumHour
}
total, _ := downloadstat.CountStats(ctx, filter)
statList, err := downloadstat.FindManyStat(ctx, filter, opt)
if err != nil {
return nil, err
}
appMap, _ := appInfoMod.GetAllAppIdNameMap(ctx)
for _, item := range statList {
item.AppName = appMap[item.AppId]
tmp := &proto.DownloadStatInfo{
Stat: *item,
}
proto.StatSummation(&resp.Summary, tmp)
tmp.CalcConversionRate()
resp.List = append(resp.List, tmp)
}
resp.Total = total
resp.Summary.CalcConversionRate()
return resp, nil
}
func LandpagePicture(ctx context.Context, req *proto.LandpagePictureSearch) (resp *proto.LandpagePictureSearchResp, err error) {
resp = &proto.LandpagePictureSearchResp{}
filter := bson.M{}
opt := &options.FindOptions{}
if req.Status != nil {
filter[landpageImg.FN.LandpageImg.Status] = *req.Status
}
if req.Name != nil {
filter[landpageImg.FN.LandpageImg.Name] = *req.Name
}
opt.SetSort(bson.M{landpageImg.FN.LandpageImg.CreatedAt: -1})
opt.SetSkip((req.PageNum - 1) * req.PageSize)
opt.SetLimit(req.PageSize)
resp.Total, err = landpageImg.CountLandpageImgs(ctx, filter)
if err != nil {
return nil, err
}
if resp.Total == 0 {
return resp, nil
}
landpagePictureList, serr := landpageImg.FindManyLandpageImg(ctx, filter, opt)
if serr != nil {
return nil, serr
}
resp.List = landpagePictureList
return
}
func AddLandpagePicture(ctx context.Context, req *proto.LandpagePictureAddReq) error {
now := time.Now()
req.CreatedAt = now
req.UpdatedAt = now
serr := landpageImg.InsertOneLandpageImg(ctx, &req.LandpageImg)
if serr != nil {
return serr
}
return nil
}
func DelLandpagePicture(ctx context.Context, id string) error {
objectId, err := primitive.ObjectIDFromHex(id)
if err != nil {
return err
}
adFilter := bson.M{landpageImg.FN.LandpageImg.ID: objectId}
//删除
_, serr := landpageImg.DelOneLandpageImg(ctx, adFilter)
if serr != nil {
return serr
}
return nil
}
func UpdateLandpagePicture(ctx context.Context, req *proto.LandpagePictureUpdateReq) error {
now := time.Now()
req.Param.UpdatedAt = &now
objectId, err := primitive.ObjectIDFromHex(req.Id)
if err != nil {
return err
}
adFilter := bson.M{landpageImg.FN.LandpageImg.ID: objectId}
updateDoc := bson.M{"$set": req.Param}
serr := landpageImg.UpdateOneLandpageImg(ctx, adFilter, updateDoc)
if serr != nil {
return serr
}
return nil
}
func ListSummaryDownload(ctx context.Context) (*proto.ListDownloadSummaryRes, error) {
resp := &proto.ListDownloadSummaryRes{
List: make([]proto.DownloadSummaryInfo, 0),
}
cacheKey := fmt.Sprintf("landPage#summary")
if cache, exists := cacheSer.Cache.Get(cacheKey); exists {
data, ok := cache.([]proto.DownloadSummaryInfo)
if !ok {
pipeline := []bson.M{
{"$group": bson.M{
"_id": "$appId",
"total": bson.M{"$sum": "$downTotal"},
}},
}
if err := downloadstat.AggregateStats(ctx, &resp.List, pipeline); err != nil {
return nil, err
}
cacheSer.Cache.Set(cacheKey, resp.List, 15*time.Minute)
} else {
resp.List = data
}
} else {
pipeline := []bson.M{
{"$group": bson.M{
"_id": "$appId",
"total": bson.M{"$sum": "$downTotal"},
}},
}
if err := downloadstat.AggregateStats(ctx, &resp.List, pipeline); err != nil {
return nil, err
}
cacheSer.Cache.Set(cacheKey, resp.List, 15*time.Minute)
}
return resp, nil
}
+90
View File
@@ -0,0 +1,90 @@
package mediatagSer
import (
"context"
"stat/api/proto"
"stat/models/mediatag"
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo/options"
)
func MediaTag(ctx context.Context, req *proto.MediaTagSearch) (resp *proto.MediaTagSearchResp, err error) {
resp = &proto.MediaTagSearchResp{}
filter := bson.M{}
opt := &options.FindOptions{}
if req.Status != nil {
filter[mediatag.FN.MediaTag.Status] = *req.Status
}
if req.Name != nil {
filter[mediatag.FN.MediaTag.Name] = *req.Name
}
if req.Type != nil {
filter[mediatag.FN.MediaTag.Type] = *req.Type
}
opt.SetSort(bson.M{mediatag.FN.MediaTag.CreatedAt: -1})
opt.SetSkip((req.PageNum - 1) * req.PageSize)
opt.SetLimit(req.PageSize)
resp.Total, err = mediatag.CountMediaTags(ctx, filter)
if err != nil {
return nil, err
}
if resp.Total == 0 {
return resp, nil
}
MediaTagList, serr := mediatag.FindManyMediaTag(ctx, filter, opt)
if serr != nil {
return nil, serr
}
resp.List = MediaTagList
return
}
func AddMediaTag(ctx context.Context, req *proto.MediaTagAddReq) error {
now := time.Now()
req.CreatedAt = now
req.UpdatedAt = now
serr := mediatag.InsertOneMediaTag(ctx, &req.MediaTag)
if serr != nil {
return serr
}
return nil
}
func DelMediaTag(ctx context.Context, id string) error {
objectId, err := primitive.ObjectIDFromHex(id)
if err != nil {
return err
}
adFilter := bson.M{mediatag.FN.MediaTag.ID: objectId}
//删除
_, serr := mediatag.DelOneMediaTag(ctx, adFilter)
if serr != nil {
return serr
}
return nil
}
func UpdateMediaTag(ctx context.Context, req *proto.MediaTagUpdateReq) error {
now := time.Now()
req.Param.UpdatedAt = &now
objectId, err := primitive.ObjectIDFromHex(req.Id)
if err != nil {
return err
}
adFilter := bson.M{mediatag.FN.MediaTag.ID: objectId}
updateDoc := bson.M{"$set": req.Param}
serr := mediatag.UpdateOneMediaTag(ctx, adFilter, updateDoc)
if serr != nil {
return serr
}
return nil
}
+90
View File
@@ -0,0 +1,90 @@
package monitorSer
import (
"context"
"stat/api/proto"
"stat/models/monitormod"
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo/options"
)
func SearchMonitor(ctx context.Context, req *proto.MonitorSearch) (resp *proto.MonitorSearchResp, err error) {
resp = &proto.MonitorSearchResp{}
filter := bson.M{}
opt := &options.FindOptions{}
if req.Status != nil {
filter[monitormod.FN.Monitor.Status] = *req.Status
}
if req.Name != nil {
filter[monitormod.FN.Monitor.Name] = bson.M{"$regex": *req.Name}
}
if req.Category != nil {
filter[monitormod.FN.Monitor.Category] = *req.Category
}
opt.SetSort(bson.M{monitormod.FN.Monitor.CreatedAt: -1})
opt.SetSkip((req.PageNum - 1) * req.PageSize)
opt.SetLimit(req.PageSize)
resp.Total, err = monitormod.CountMonitors(ctx, filter)
if err != nil {
return nil, err
}
if resp.Total == 0 {
return resp, nil
}
MonitorList, serr := monitormod.FindManyMonitor(ctx, filter, opt)
if serr != nil {
return nil, serr
}
resp.List = MonitorList
return
}
func AddMonitor(ctx context.Context, req *proto.MonitorAddReq) error {
now := time.Now()
req.CreatedAt = now
req.UpdatedAt = now
serr := monitormod.InsertOneMonitor(ctx, &req.Monitor)
if serr != nil {
return serr
}
return nil
}
func DelMonitor(ctx context.Context, id string) error {
objectId, err := primitive.ObjectIDFromHex(id)
if err != nil {
return err
}
adFilter := bson.M{monitormod.FN.Monitor.ID: objectId}
//删除
_, serr := monitormod.DelOneMonitor(ctx, adFilter)
if serr != nil {
return serr
}
return nil
}
func UpdateMonitor(ctx context.Context, req *proto.MonitorUpdateReq) error {
now := time.Now()
req.Param.UpdatedAt = &now
objectId, err := primitive.ObjectIDFromHex(req.Id)
if err != nil {
return err
}
adFilter := bson.M{monitormod.FN.Monitor.ID: objectId}
updateDoc := bson.M{"$set": req.Param}
serr := monitormod.UpdateOneMonitor(ctx, adFilter, updateDoc)
if serr != nil {
return serr
}
return nil
}
+29
View File
@@ -0,0 +1,29 @@
package monitorSer
import (
"github.com/robfig/cron/v3"
"stat/common/log"
"stat/common/statutils"
)
var (
auto *cron.Cron
)
func init() {
auto = cron.New(cron.WithSeconds(), cron.WithChain(cron.SkipIfStillRunning(&log.Logger{}))) // 秒级定时任务
}
func InitMonitor() {
_, _ = auto.AddFunc("0 1 * * * ?", func() {
statutils.ServerMon.RunWithName(func() error {
return MonitorCheck()
}, statutils.GetFuncName(MonitorCheck))
})
auto.Start()
}
func MonitorCheck() error {
return nil
}
+63
View File
@@ -0,0 +1,63 @@
package opLogSer
import (
"context"
"encoding/json"
"github.com/gin-gonic/gin"
"stat/api/proto"
"stat/common"
"stat/common/stdLog"
"stat/common/utils"
"stat/models/opLogMod"
"time"
)
// 添加操作日志
func AddOpLog(ctx *gin.Context, account string, isAdmin bool, opData interface{}) {
common.Go(func() {
now := time.Now()
opDataStr, _ := json.Marshal(opData)
doc := opLogMod.OpLog{
Operator: account,
Router: ctx.Request.RequestURI,
Data: string(opDataStr),
IsAdmin: isAdmin,
CreatedAt: now,
}
_ = opLogMod.AddOpLog(ctx, &doc)
})
}
func GetOpLogList(ctx context.Context, param proto.ListOpLogReq) (code stdLog.CodeType, data interface{}, err error) {
resp := proto.ListOpLogRes{
Total: 0,
List: make([]proto.OpLogInfo, 0),
}
skip, limit := utils.ToDBPage(param.PageNum, param.PageSize)
total, opList, err := opLogMod.GetOpLogList(ctx, param.Operator, param.Router, param.IsAdmin, skip, limit)
if err != nil {
code = stdLog.Failure
return
}
for _, v := range opList {
tmp := proto.OpLogInfo{
Operator: v.Operator,
Router: v.Router,
Data: v.Data,
IsAdmin: v.IsAdmin,
CreatedAt: v.CreatedAt,
}
resp.List = append(resp.List, tmp)
}
resp.Total = total
data = resp
code = stdLog.Success
return
}
+69
View File
@@ -0,0 +1,69 @@
package smsser
import (
"fmt"
"time"
"stat/common/log"
"stat/common/redis"
"stat/common/sms"
"stat/common/utils"
)
const (
CaptchaLen = 6
MobileCaptchaExpire = 5 * time.Minute
)
// SendSmsCode 发送验证码
func SendSmsCode(mobile string) error {
code := utils.RandDigits(CaptchaLen)
//fmt.Println(code)
redisKey := fmt.Sprintf("captcha:mobile:%s", mobile)
err := redis.StatRds.Set(redisKey, code, MobileCaptchaExpire)
if err != nil {
log.Error("SendCaptcha Save Redis error", log.Any("mobile", mobile), log.E(err))
return err
}
err = sms.Send(sms.FengNiao, mobile, code)
if err != nil {
log.Error("SendCaptcha send err", log.Any("mobile", mobile), log.Any("Platform", sms.FengNiao), log.E(err))
yunErr := sms.Send(sms.YunPian, mobile, code)
if yunErr != nil {
log.Error("SendCaptcha send err", log.Any("mobile", mobile), log.Any("Platform", sms.YunPian), log.E(err))
return err
}
}
return nil
}
// VerifySmsCode 验证验证码
func VerifySmsCode(mobile string, code string) error {
redisKey := fmt.Sprintf("captcha:mobile:%s", mobile)
redisCode, err := redis.StatRds.Get(redisKey)
if err != nil {
log.Error("VerifySmsCode redis get err", log.E(err))
return err
}
if redisCode != nil && *redisCode == code {
return nil
} else {
log.Info("VerifySmsCode miss", log.Any("mobile", mobile), log.Any("code", code), log.Any("redisCode", redisCode))
return fmt.Errorf("VerifySmsCode miss")
}
}
func GetSmsCode(mobile string) (string, error) {
redisKey := fmt.Sprintf("captcha:mobile:%s", mobile)
redisCode, err := redis.StatRds.Get(redisKey)
if err != nil {
log.Error("VerifySmsCode redis get err", log.E(err))
return "", err
}
if redisCode != nil {
return *redisCode, nil
} else {
return "", nil
}
}
+98
View File
@@ -0,0 +1,98 @@
package srv_appcategory
import (
"context"
"stat/common/genId"
"stat/common/stdLog"
"stat/models/appcategory"
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo/options"
)
// GetAppCategory 按照参数搜索
func GetAppCategory(ctx context.Context, param *AppCategorySearch) (*AppCategorySearchResp, *stdLog.SrvErr) {
filter := bson.M{}
opt := &options.FindOptions{}
resp := &AppCategorySearchResp{
List: make(appcategory.AppCategoryList, 0),
Total: 0,
}
opt.SetSort(bson.M{"rank": 1})
opt.SetSkip((param.PageNum - 1) * param.PageSize)
opt.SetLimit(param.PageSize)
if param.ParentAppId != nil {
filter["parentAppId"] = param.ParentAppId
}
total, serr := appcategory.CountAppCategories(ctx, filter)
if serr != nil {
return resp, serr
}
if total == 0 {
return resp, nil
}
list, serr := appcategory.FindManyAppCategory(ctx, filter, opt)
if serr != nil {
return resp, serr
}
if list != nil && len(list) > 0 {
resp.List = list
resp.Total = total
}
return resp, nil
}
// AddAppCategory 添加一个
func AddAppCategory(ctx context.Context,
ac *appcategory.AppCategory) *stdLog.SrvErr {
now := time.Now()
ac.Id = genId.GetAppCategoryId()
ac.CreatedAt = now
ac.UpdatedAt = now
if ac.Name == "" {
return stdLog.New(stdLog.ParamError, "", "name不能传空")
}
_, serr := appcategory.FindOneAppCategory(ctx, bson.M{"name": ac.Name})
if serr == nil {
return stdLog.New(stdLog.ParamError, "", "name重名")
}
serr = appcategory.InsertOneAppCategory(ctx, ac)
if serr != nil {
return serr
}
return nil
}
// DelAppCategory 删除指定
func DelAppCategory(ctx context.Context, id int64) *stdLog.SrvErr {
adFilter := bson.M{"_id": id}
//删除
_, serr := appcategory.DelOneAppCategory(ctx, adFilter)
if serr != nil {
return serr
}
return nil
}
// UpdateAppCategory 更新指定
func UpdateAppCategory(ctx context.Context, id int64,
info *AppCategoryUpdate) *stdLog.SrvErr {
now := time.Now()
info.UpdatedAt = &now
adFilter := bson.M{"_id": id}
updateDoc := bson.M{"$set": info}
serr := appcategory.UpdateOneAppCategory(ctx, adFilter, updateDoc)
if serr != nil {
return serr
}
return nil
}
+37
View File
@@ -0,0 +1,37 @@
package srv_appcategory
import (
"stat/common"
"stat/models/appcategory"
"time"
)
// AppCategorySearch
type AppCategorySearch struct {
ParentAppId *int64 `json:"parentAppId" bson:"parentAppId"`
common.Page
}
type AppCategorySearchResp struct {
Total int64 `json:"total"`
List appcategory.AppCategoryList `json:"list"`
}
type AppCategoryUpdate struct {
ParentAppId *int64 `json:"parentAppId" bson:"parentAppId"`
Name *string `json:"name" bson:"name,omitempty"` // 名称
Desc *string `json:"desc" bson:"desc,omitempty"` // 描述
Status *bool `json:"status" bson:"status,omitempty"` // 状态
Rank *int64 `json:"rank" bson:"rank,omitempty"` // 应用排序
UpdatedAt *time.Time `json:"updatedAt" bson:"updatedAt,omitempty"` // 更新时间
ImgType *int64 `json:"imgType" bson:"imgType,omitempty"` // 图片展示类型 0:应用图标 1.小图标 2.大图标
}
type AppCategoryUpdateReq struct {
Param AppCategoryUpdate `json:"param" bson:"param"` // 参数
Id int64 `json:"id" bson:"_id,omitempty"`
}
type AppCategoryDelReq struct {
Id int64 `json:"id" bson:"_id,omitempty"`
}
@@ -0,0 +1,191 @@
package srv_applicationstat
import (
"context"
"stat/common/stdLog"
"stat/models/applicationmod"
"stat/models/applicationstat"
"go.mongodb.org/mongo-driver/mongo/options"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// GetAppStatList 获取应用统计列表
func GetAppStatList(ctx context.Context, param *GetApplicationStatReq) (*GetApplicationStatResp, *stdLog.SrvErr) {
resp := &GetApplicationStatResp{}
//先获取app列表
appFilter := bson.M{}
statFilter := bson.M{}
if param.ParentAppId != nil {
appFilter["parentAppId"] = bson.M{"$in": *param.ParentAppId}
}
if param.ApplicationId != nil {
appFilter["_id"] = *param.ApplicationId
}
if param.ApplicationName != nil {
appFilter["name"] = *param.ApplicationName
}
appList, sErr := applicationmod.FindManyApplication(ctx, appFilter)
if sErr != nil {
return nil, sErr
}
applicationId := make([]primitive.ObjectID, 0)
for _, info := range appList {
applicationId = append(applicationId, info.Id)
}
statFilter["applicationDbId"] = bson.M{"$in": applicationId}
if param.Start != nil || param.End != nil {
statFilter[applicationstat.FN.ApplicationStat.SumData] = bson.M{"$gte": *param.Start, "$lte": *param.End}
}
opt := &options.FindOptions{}
opt.SetSort(bson.M{applicationstat.FN.ApplicationStat.SumData: -1})
opt.SetLimit(param.PageSize)
opt.SetSkip((param.PageNum - 1) * param.PageSize)
totalStats, sErr := applicationstat.FindManyApplicationStat(ctx, statFilter)
if sErr != nil {
return nil, sErr
}
pageStats, sErr := applicationstat.FindManyApplicationStat(ctx, statFilter, opt)
if sErr != nil {
return nil, sErr
}
appDict := make(map[primitive.ObjectID]*applicationmod.Application)
for _, app := range appList {
appDict[app.Id] = app
}
for _, stat := range totalStats {
resp.Summary.TotalClick += stat.Total
}
for _, stat := range pageStats {
name := ""
parentAppId := int64(0)
app := appDict[stat.ApplicationDbId]
if app != nil {
name = app.Name
parentAppId = app.ParentAppId
}
hours := make([]int64, 24)
for hour := range hours {
hours[hour] = stat.HourData[hour]
}
doc := &AppStatInfo{
Id: stat.Id,
ApplicationDbId: stat.ApplicationDbId,
ParentAppId: parentAppId,
AppName: name,
SumDate: stat.SumDate,
Total: stat.Total,
HourData: hours,
CreatedAt: stat.CreatedAt,
UpdatedAt: stat.UpdatedAt,
}
resp.List = append(resp.List, doc)
}
resp.Total = int64(len(totalStats))
return resp, nil
}
// GetAppStatList 获取应用统计列表
func GetAppTotalStatList(ctx context.Context, param *GetApplicationStatTotalReq) (*GetApplicationStatTotalResp, *stdLog.SrvErr) {
resp := &GetApplicationStatTotalResp{
List: make([]*AppStatTotalInfo, 0),
}
//先获取app列表
appFilter := bson.M{}
if param.ParentAppId != nil {
appFilter["parentAppId"] = bson.M{"$in": *param.ParentAppId}
}
if param.ApplicationId != nil {
appFilter["_id"] = *param.ApplicationId
}
if param.ApplicationName != nil {
appFilter["name"] = *param.ApplicationName
}
appList, sErr := applicationmod.FindManyApplication(ctx, appFilter)
if sErr != nil {
return nil, sErr
}
applicationId := make([]primitive.ObjectID, 0)
appid := make(map[primitive.ObjectID]*applicationmod.Application)
for _, info := range appList {
applicationId = append(applicationId, info.Id)
appid[info.Id] = info
}
filter := bson.M{}
filter["applicationDbId"] = bson.M{"$in": applicationId}
if param.Start != nil && param.End != nil {
filter[applicationstat.FN.ApplicationStat.SumData] = bson.M{"$gte": *param.Start, "$lte": *param.End}
}
pipeline := []bson.M{
{
"$match": filter,
},
{
"$group": bson.M{
"_id": "$applicationDbId",
"total": bson.M{"$sum": "$total"},
},
},
{"$sort": bson.D{{Key: "_id", Value: -1}}},
}
list := make([]StatTotalInfo, 0)
serr := applicationstat.AggregateApplicationStat(ctx, &list, pipeline)
pipeline = []bson.M{
{
"$match": filter,
},
{
"$group": bson.M{
"_id": "$applicationDbId",
"total": bson.M{"$sum": "$total"},
},
},
{"$sort": bson.D{{Key: "_id", Value: -1}}},
{"$skip": (param.PageNum - 1) * param.PageSize},
{"$limit": param.PageSize},
}
info := make([]StatTotalInfo, 0)
serr = applicationstat.AggregateApplicationStat(ctx, &info, pipeline)
if serr != nil {
return nil, serr
}
if len(list) > 0 {
for _, app := range list {
resp.Summary.TotalClick += app.Total
}
}
if len(info) > 0 {
for _, app := range info {
tmp := &AppStatTotalInfo{
Id: app.Id,
Total: app.Total,
}
if value, ok := appid[app.Id]; ok {
tmp.ParentAppId = value.ParentAppId
tmp.AppName = value.Name
}
resp.List = append(resp.List, tmp)
}
}
resp.Total = int64(len(list))
return resp, nil
}
+67
View File
@@ -0,0 +1,67 @@
package srv_applicationstat
import (
"stat/common"
"time"
"go.mongodb.org/mongo-driver/bson/primitive"
)
type AppStatSummary struct {
TotalClick int64 `json:"totalClick"` // 总点击
}
type AppStatInfo struct {
Id primitive.ObjectID `json:"id" bson:"_id,omitempty"`
ApplicationDbId primitive.ObjectID `json:"applicationDbId" bson:"applicationDbId"` //应用ID
ParentAppId int64 `json:"parentAppId" bson:"parentAppId"` //所属应用ID
AppName string `json:"appName" bson:"appName"` //应用名称
SumDate time.Time `json:"sumDate" bson:"sumDate"` //记录时间
Total int64 `json:"total" bson:"total"` //当日点击
HourData []int64 `json:"hourData" bson:"hourData"` //小时数据
CreatedAt time.Time `json:"createdAt" bson:"createdAt"` //创建时间
UpdatedAt time.Time `json:"updatedAt" bson:"updatedAt"` //更新时间
}
// GetApplicationStatReq 获取app点击统计请求
type GetApplicationStatReq struct {
common.Page
Start *time.Time `json:"start"` // 开始时间
End *time.Time `json:"end"` // 结束时间
ParentAppId *[]int64 `json:"parentAppId"` // 所属应用ID
ApplicationId *primitive.ObjectID `json:"applicationId"` // 应用ID
ApplicationName *string `json:"applicationName"` // 应用名称
}
type GetApplicationStatResp struct {
Total int64 `json:"total"`
List []*AppStatInfo `json:"list"`
Summary AppStatSummary `json:"summary"`
}
// GetApplicationStatReq 获取app点击统计请求
type GetApplicationStatTotalReq struct {
common.Page
Start *time.Time `json:"start"` // 开始时间
End *time.Time `json:"end"` // 结束时间
ParentAppId *[]int64 `json:"parentAppId"` // 所属应用ID
ApplicationId *primitive.ObjectID `json:"applicationId"` // 应用ID
ApplicationName *string `json:"applicationName"` // 应用名称
}
type AppStatTotalInfo struct {
Id primitive.ObjectID `json:"id" bson:"id"` //应用ID
ParentAppId int64 `json:"parentAppId" bson:"parentAppId"` //所属应用ID
AppName string `json:"appName" bson:"appName"` //应用名称
Total int64 `json:"total" bson:"total"` //总次数
}
type StatTotalInfo struct {
Id primitive.ObjectID `json:"id" bson:"_id,omitempty"` //应用ID
Total int64 `json:"total"` //总次数
}
type GetApplicationStatTotalResp struct {
Total int64 `json:"total"`
List []*AppStatTotalInfo `json:"list"`
Summary AppStatSummary `json:"summary"`
}
+717
View File
@@ -0,0 +1,717 @@
package statSer
import (
"context"
"fmt"
"os"
"path"
"sort"
"stat/api/proto"
"stat/common/log"
"stat/common/stdLog"
"stat/common/utils"
"stat/models/channelCodeBlocStatMod"
"stat/models/channelmod"
"time"
"github.com/360EntSecGroup-Skylar/excelize"
"go.mongodb.org/mongo-driver/bson"
)
// // 获取渠道码统计信息列表
// func ListChannelCodeStat(ctx context.Context, param proto.ListChannelCodeStatReq) (code stdLog.CodeType, data interface{}, err error) {
// resp := proto.ListChannelCodeStatRes{
// Total: 0,
// List: make([]proto.ChannelCodeStatInfo, 0),
// }
// // 获取需要过滤的渠道信息
// channels1 := make([]string, 0)
// if param.Channel != nil {
// // 获取渠道信息
// channelInfo, err := channelmod.GetChannelInfoByChannel(ctx, *param.Channel)
// if err != nil {
// code = stdLog.ChannelNotExists
// return code, data, err
// }
// if channelInfo.Channel == "" {
// code = stdLog.Success
// return code, resp, nil
// }
// // 获取子渠道
// if channelInfo.ParentChannel == "" {
// chList, err := channelmod.GetAllChildChannel(ctx, channelInfo.Channel)
// if err != nil {
// code = stdLog.Failure
// return code, data, err
// }
// for _, v := range chList {
// channels1 = append(channels1, v.Channel)
// }
// }
// channels1 = append(channels1, channelInfo.Channel)
// }
// channels2 := make([]string, 0)
// // 根据渠道备注,获取渠道信息
// if param.ChannelRemark != nil {
// // 获取渠道
// chList, err := channelmod.GetChannelByRemark(ctx, *param.ChannelRemark)
// if err != nil {
// code = stdLog.Failure
// return code, data, err
// }
// if len(chList) == 0 {
// code = stdLog.Success
// return code, resp, nil
// }
// for _, v1 := range chList {
// // 获取子渠道
// if v1.ParentChannel == "" && len(v1.Channel) > 0 {
// chList, err := channelmod.GetAllChildChannel(ctx, v1.Channel)
// if err != nil {
// code = stdLog.Failure
// return code, data, err
// }
// for _, v2 := range chList {
// channels2 = append(channels2, v2.Channel)
// }
// }
// channels2 = append(channels2, v1.Channel)
// }
// }
// channels := make([]string, 0)
// // 合并
// if param.Channel != nil && param.ChannelRemark != nil {
// for _, ch1 := range channels1 {
// for _, ch2 := range channels2 {
// if ch1 == ch2 {
// channels = append(channels, ch1)
// }
// }
// }
// } else if param.Channel != nil && param.ChannelRemark == nil {
// channels = channels1
// } else if param.Channel == nil && param.ChannelRemark != nil {
// channels = channels2
// }
// codes1 := make([]string, 0)
// // 获取渠道的渠道码
// if len(channels) > 0 {
// codeList, err := channelCodeMod.GetChannelCodesByChannels(ctx, channels)
// if err != nil {
// code = stdLog.Failure
// return code, data, err
// }
// for _, v := range codeList {
// codes1 = append(codes1, v.ChannelCode)
// }
// }
// codes := make([]string, 0)
// // 只搜索渠道码
// if param.ChannelCode != nil && param.Channel == nil && param.ChannelRemark == nil {
// codes = append(codes, *param.ChannelCode)
// } else if param.ChannelCode != nil && (param.Channel != nil || param.ChannelRemark != nil) {
// codes2 := make([]string, 0)
// for _, v := range codes1 {
// if v == *param.ChannelCode {
// codes2 = []string{*param.ChannelCode}
// break
// }
// }
// if len(codes2) == 0 {
// code = stdLog.Success
// return code, resp, nil
// }
// codes = codes2
// } else {
// codes = codes1
// }
// skip, limit := utils.ToDBPage(param.PageNum, param.PageSize)
// total, statList, err := channelCodeStatMod.GetChannelCodeStatList(ctx, codes, param.AppId, param.StartTime, param.EndTime, skip, limit)
// if err != nil {
// code = stdLog.Failure
// return
// }
// appIds := make([]int32, 0)
// for _, v := range statList {
// appIds = append(appIds, v.AppId)
// }
// appInfoMap, err := channelSer.MakeAppInfosMapByIds(ctx, appIds)
// if err != nil {
// code = stdLog.Failure
// return
// }
// codeList, err := channelCodeMod.GetChannelCodesByIds(ctx, appIds)
// if err != nil {
// code = stdLog.Failure
// return
// }
// appInfoCodeMap := make(map[string]*channelCodeMod.ChannelCode)
// for _, v := range codeList {
// _v := v
// appInfoCodeMap[v.ChannelCode+fmt.Sprintf("%d", v.AppId)] = &_v
// }
// for _, v := range statList {
// appName := ""
// if appInfo, ok := appInfoMap[v.AppId]; ok {
// appName = appInfo.Name
// }
// channel := ""
// settlement := channelmod.SettlementCPA
// rate := float64(0.8)
// if channelCodeInfo, ok := appInfoCodeMap[v.ChannelCode+fmt.Sprintf("%d", v.AppId)]; ok {
// channel = channelCodeInfo.Channel
// channelInfo, _ := channelmod.GetChannelInfoByChannel(ctx, channel)
// if channelInfo.Channel == channel {
// settlement = channelCodeInfo.Settlement
// if settlement == channelmod.SettlementCPS {
// rate = float64(channelInfo.CPSRate) / 100
// } else {
// rate = float64(channelInfo.CPAPrice)
// }
// }
// } else { // 官方活没收益
// rate = float64(200)
// }
// tmp := proto.ChannelCodeStatInfo{
// SumDate: v.SumDate,
// ChannelCode: v.ChannelCode,
// AppId: v.AppId,
// AppName: appName,
// Channel: channel,
// Settlement: settlement,
// NewDirectCount: v.NewDirectCount,
// NewFissionCount: v.NewFissionCount,
// NewDeductionCount: v.NewDeductionCount,
// DirectVisitCount: v.DirectVisitCount,
// FissionVisitCount: v.FissionVisitCount,
// Day1RetainRate: v.Day1RetainRate,
// Day3RetainRate: v.Day3RetainRate,
// Day7RetainRate: v.Day7RetainRate,
// Recharge: v.DirectAmount,
// FissionAmount: v.FissionAmount,
// DeductionAmount: v.DeductionAmount,
// NewRechargeCount: v.NewRechargeCount,
// NewRechargeAmount: v.NewRechargeAmount,
// LandPageVisitCount: v.LandPageVisitCount,
// TotalAmount: v.DirectAmount + v.FissionAmount + v.DeductionAmount,
// TotalNewCount: v.NewDirectCount + v.NewFissionCount + v.NewDeductionCount,
// IncomeRate: float64(0.0),
// UpdatedAt: v.UpdatedAt,
// }
// if settlement == channelmod.SettlementCPS {
// tmp.CPSRate = rate
// channelIncome := float64(v.DirectAmount) * rate
// if channelIncome > 0 {
// tmp.IncomeRate, _ = strconv.ParseFloat(fmt.Sprintf("%.2f", (float64(tmp.Recharge+tmp.DeductionAmount+tmp.FissionAmount))/channelIncome), 64)
// }
// } else {
// tmp.CPAPrice = rate / 100
// channelIncome := float64(v.NewDirectCount) * rate
// if channelIncome > 0 {
// tmp.IncomeRate, _ = strconv.ParseFloat(fmt.Sprintf("%.2f", (float64(tmp.Recharge+tmp.DeductionAmount+tmp.FissionAmount))/channelIncome), 64)
// }
// }
// resp.List = append(resp.List, tmp)
// }
// resp.Total = total
// // 总量
// TotalList, err := channelCodeStatMod.GetChannelCodeStatListTotal(ctx, codes, param.AppId, param.StartTime, param.EndTime)
// if err != nil {
// log.Error("GetChannelCodeStatListTotal", log.E(err))
// code = stdLog.Failure
// return
// }
// toatlSum := proto.ChannelCodeStatInfo{
// IncomeRate: float64(0.0),
// }
// for _, valueInfo := range TotalList {
// toatlSum.NewDirectCount += valueInfo.NewDirectCount
// toatlSum.NewFissionCount += valueInfo.NewFissionCount
// toatlSum.NewDeductionCount += valueInfo.NewDeductionCount
// toatlSum.DirectVisitCount += valueInfo.DirectVisitCount
// toatlSum.FissionVisitCount += valueInfo.FissionVisitCount
// toatlSum.Day1RetainRate += valueInfo.Day1RetainRate
// toatlSum.Day3RetainRate += valueInfo.Day3RetainRate
// toatlSum.Day7RetainRate += valueInfo.Day7RetainRate
// toatlSum.Recharge += valueInfo.DirectAmount
// toatlSum.FissionAmount += valueInfo.FissionAmount
// toatlSum.DeductionAmount += valueInfo.DeductionAmount
// toatlSum.NewRechargeCount += valueInfo.NewRechargeCount
// toatlSum.NewRechargeAmount += valueInfo.NewRechargeAmount
// toatlSum.LandPageVisitCount += valueInfo.LandPageVisitCount
// toatlSum.TotalAmount += (valueInfo.DirectAmount + valueInfo.FissionAmount + valueInfo.DeductionAmount)
// toatlSum.TotalNewCount += (valueInfo.NewDirectCount + valueInfo.NewFissionCount + valueInfo.NewDeductionCount)
// }
// resp.TotalInfo = toatlSum
// data = resp
// code = stdLog.Success
// return
// }
// // 代理商获取渠道码统计信息列表
// func ListChannelCodeStatAgent(ctx context.Context, account string, param proto.ListChannelCodeStatAgentReq) (code stdLog.CodeType, data interface{}, err error) {
// resp := proto.ListChannelCodeStatAgentRes{
// Total: 0,
// List: make([]proto.ChannelCodeStatAgentInfo, 0),
// }
// // 获取渠道信息
// channelInfo, err := channelmod.GetChannelInfoByAccount(ctx, account)
// if err != nil || channelInfo.Channel == "" {
// code = stdLog.AgentAccountNotExists
// return
// }
// // 获取子渠道
// childChannels, err := channelmod.GetAllChildChannel(ctx, channelInfo.Channel)
// if err != nil || channelInfo.Channel == "" {
// code = stdLog.Failure
// return
// }
// channels := make([]string, 0)
// for _, v := range childChannels {
// channels = append(channels, v.Channel)
// }
// channels = append(channels, channelInfo.Channel)
// // 获取渠道的渠道码
// codeList, err := channelCodeMod.GetChannelCodesByChannels(ctx, channels)
// if err != nil {
// code = stdLog.Failure
// return
// }
// codes := make([]string, 0)
// channelCodeMap := make(map[string]string)
// if param.ChannelCode != nil {
// codes = append(codes, *param.ChannelCode)
// for _, v := range codeList {
// channelCodeMap[v.ChannelCode+fmt.Sprintf("%d", v.AppId)] = v.Channel
// }
// } else {
// for _, v := range codeList {
// codes = append(codes, v.ChannelCode)
// channelCodeMap[v.ChannelCode+fmt.Sprintf("%d", v.AppId)] = v.Channel
// }
// }
// skip, limit := utils.ToDBPage(param.PageNum, param.PageSize)
// total, statList, err := channelCodeStatMod.GetChannelCodeStatAgentList(ctx, codes, *param.AppId, param.StartTime, param.EndTime, skip, limit)
// if err != nil {
// code = stdLog.Failure
// return
// }
// for _, v := range statList {
// channelName, ok := channelCodeMap[v.ChannelCode+fmt.Sprintf("%d", v.AppId)]
// if !ok { // 只能查询渠道下面的渠道码
// continue
// }
// tmp := proto.ChannelCodeStatAgentInfo{
// SumDate: v.SumDate,
// ChannelCode: v.ChannelCode,
// Channel: channelName,
// NewDirectCount: v.NewDirectCount,
// Recharge: v.DirectAmount,
// }
// // CPA子渠道不显示充值金额
// if channelInfo.ParentChannel != "" && channelInfo.Settlement == channelmod.SettlementCPA {
// tmp.Recharge = 0
// }
// resp.List = append(resp.List, tmp)
// }
// totalStatList, err := channelCodeStatMod.GetChannelCodeStatAgentListTotal(ctx, codes, *param.AppId, param.StartTime, param.EndTime)
// if err != nil {
// code = stdLog.Failure
// return
// }
// totalCount := int64(0)
// totalAmount := int64(0)
// for _, v := range totalStatList {
// _, ok := channelCodeMap[v.ChannelCode+fmt.Sprintf("%d", v.AppId)]
// if !ok { // 只能查询渠道下面的渠道码
// continue
// }
// // CPA子渠道不显示充值金额
// if channelInfo.ParentChannel != "" && channelInfo.Settlement == channelmod.SettlementCPA {
// totalAmount = 0
// } else {
// totalAmount += v.DirectAmount
// }
// totalCount += v.NewDirectCount
// }
// resp.Total = total
// resp.TotalAmount = totalAmount
// resp.TotalCount = totalCount
// data = resp
// code = stdLog.Success
// return
// }
func CacheKey(channel *string, appId *int32, start, end *time.Time) string {
key := ""
if channel != nil {
key += fmt.Sprintf("channel:%s;", *channel)
} else {
key += "channel:<nil>;"
}
if appId != nil {
key += fmt.Sprintf("appId:%d;", *appId)
} else {
key += "appId:<nil>;"
}
if start != nil {
key += fmt.Sprintf("start:%v;", *start)
} else {
key += "start:<nil>;"
}
if end != nil {
key += fmt.Sprintf("end:%v;", *end)
} else {
key += "end:<nil>;"
}
return key
}
// 获取渠道码统计信息列表(集团)
func ListBlocChannelCodeStat(ctx context.Context, param *proto.BlocListChannelCodeStatReq) (code stdLog.CodeType, data interface{}, err error) {
resp := proto.BlocListChannelCodeStatRes{
Total: 0,
List: make([]proto.BlocChannelCodeStatInfo, 0),
}
if param.Channel != nil && *param.Channel == channelmod.OfficialChannelDisplayName {
tmp := channelmod.OfficialChannelName
param.Channel = &tmp
}
// 获取渠道的渠道码
skip, limit := utils.ToDBPage(param.PageNum, param.PageSize)
total, statList, err := channelCodeBlocStatMod.GetBlocChannelCodeBlocStatList(ctx, param.Channel, param.AppId, param.StartTime, param.EndTime, skip, limit)
if err != nil {
code = stdLog.Failure
return
}
appIds := make([]int32, 0)
for _, v := range statList {
appIds = append(appIds, v.AppId)
}
appInfoMap, err := MakeAppInfosMapByIds(ctx, appIds)
if err != nil {
code = stdLog.Failure
return
}
childstatList := make([]*channelCodeBlocStatMod.ChannelCodeBlocStat, 0)
if param.WithChild != nil && param.Channel != nil && len(*param.Channel) > 0 {
childChannelList, _ := channelmod.FindManyChannelInfo(ctx, bson.M{channelmod.FN.ChannelInfo.ParentChannel: *param.Channel})
if len(childChannelList) > 0 {
channelArray := make([]string, 0)
for _, v := range childChannelList {
channelArray = append(channelArray, v.Channel)
}
_, childstatList, _ = channelCodeBlocStatMod.GetBlocChannelCodeStatTotal(ctx, channelArray, param.AppId, param.StartTime, param.EndTime)
}
// 汇总排序 (存在一级没有 二级存在的情况)
for _, childinfo := range childstatList {
find := false
for _, v := range statList {
if v.SumDate.Equal(childinfo.SumDate) {
find = true
}
}
if !find {
childinfo.ChannelCode = *param.Channel
statList = append(statList, childinfo)
}
}
sort.Sort(statList)
}
// get channels
channels := make([]string, 0, len(statList))
for _, item := range statList {
channels = append(channels, item.ChannelCode)
}
ruleMap, err := MakeRuleMap(ctx, channels)
if err != nil {
code = stdLog.Failure
return
}
//组装 按照时间排序
for _, v := range statList {
appName := ""
if appInfo, ok := appInfoMap[v.AppId]; ok {
appName = appInfo.Name
}
for _, childinfo := range childstatList {
if v.SumDate.Equal(childinfo.SumDate) {
v.NewDirectCount += childinfo.NewDirectCount
v.NewFissionCount += childinfo.NewFissionCount
v.NewDeductionCount += childinfo.NewDeductionCount
v.DirectVisitCount += childinfo.DirectVisitCount
v.FissionVisitCount += childinfo.FissionVisitCount
v.DeductionAmount += childinfo.DeductionAmount
v.DirectAmount += childinfo.DirectAmount
v.FissionAmount += childinfo.FissionAmount
}
}
tmp := proto.BlocChannelCodeStatInfo{
SumDate: v.SumDate,
ChannelCode: channelmod.ChannelDisplayName(v.ChannelCode),
AppId: v.AppId,
AppName: appName,
Channel: v.Channel,
NewDirectCount: v.NewDirectCount,
NewFissionCount: v.NewFissionCount,
NewDeductionCount: v.NewDeductionCount,
DirectVisitCount: v.DirectVisitCount,
FissionVisitCount: v.FissionVisitCount,
DeductionAmount: v.DeductionAmount,
RechargeAmount: v.DirectAmount,
FissionAmount: v.FissionAmount,
UpdatedAt: v.UpdatedAt,
}
ruleinfo := ruleMap.GetRule(v.ChannelCode, v.AppId)
if ruleinfo != nil {
if ruleinfo.Settlement == channelmod.SettlementCPA {
tmp.Settlement = fmt.Sprintf("CPA_%.2f", float64(ruleinfo.CPAPrice)/100)
tmp.TodayIncome = tmp.NewDirectCount * ruleinfo.CPAPrice
} else if ruleinfo.Settlement == channelmod.SettlementCPS {
tmp.Settlement = fmt.Sprintf("CPS_%.2f", float64(ruleinfo.CPSRate)/100)
tmp.TodayIncome = tmp.RechargeAmount * ruleinfo.CPSRate / 100
} else if ruleinfo.Settlement == channelmod.SettlementCPT {
tmp.Settlement = "CPT"
} else if ruleinfo.Settlement == channelmod.SettlementCPC {
tmp.Settlement = "CPC"
}
}
resp.List = append(resp.List, tmp)
}
resp.Total = total
// 查询条件不为空时统计总量
if param.Channel != nil || param.AppId != nil || (param.StartTime != nil && param.EndTime != nil) {
// 总量
TotalList, err := channelCodeBlocStatMod.GetBlocChannelCodeBlocStatListTotal(ctx, param.Channel, param.AppId, param.StartTime, param.EndTime)
if err != nil {
code = stdLog.Failure
return code, nil, err
}
// get channels
channels = make([]string, 0, len(TotalList))
for _, item := range TotalList {
channels = append(channels, item.ChannelCode)
}
ruleMap, err = MakeRuleMap(ctx, channels)
if err != nil {
code = stdLog.Failure
return code, nil, err
}
toatlSum := &proto.BlocChannelCodeStatInfo{}
for _, valueInfo := range TotalList {
toatlSum.NewDirectCount += valueInfo.NewDirectCount
toatlSum.NewFissionCount += valueInfo.NewFissionCount
toatlSum.DirectVisitCount += valueInfo.DirectVisitCount
toatlSum.FissionVisitCount += valueInfo.FissionVisitCount
toatlSum.RechargeAmount += valueInfo.DirectAmount
toatlSum.DeductionAmount += valueInfo.DeductionAmount
toatlSum.NewDeductionCount += valueInfo.NewDeductionCount
ruleinfo := ruleMap.GetRule(valueInfo.ChannelCode, valueInfo.AppId)
if ruleinfo != nil {
if ruleinfo.Settlement == channelmod.SettlementCPA {
toatlSum.TodayIncome += valueInfo.NewDirectCount * ruleinfo.CPAPrice
} else if ruleinfo.Settlement == channelmod.SettlementCPS {
toatlSum.TodayIncome += valueInfo.DirectAmount * ruleinfo.CPSRate / 100
}
}
}
resp.TotalInfo = *toatlSum
}
data = resp
code = stdLog.Success
return
}
func ListBlocChannelExport(ctx context.Context, param *proto.BlocListChannelCodeStatReq) (string, error) {
if param.Channel != nil && *param.Channel == channelmod.OfficialChannelDisplayName {
tmp := channelmod.OfficialChannelName
param.Channel = &tmp
}
// 获取渠道的渠道码
statList, err := channelCodeBlocStatMod.GetBlocChannelCodeBlocStatListTotal(ctx, param.Channel, param.AppId, param.StartTime, param.EndTime)
if err != nil {
return "", err
}
appIds := make([]int32, 0)
for _, v := range statList {
appIds = append(appIds, v.AppId)
}
appInfoMap, err := MakeAppInfosMapByIds(ctx, appIds)
if err != nil {
return "", err
}
// get channels
channels := make([]string, 0, len(statList))
for _, item := range statList {
channels = append(channels, item.ChannelCode)
}
ruleMap, err := MakeRuleMap(ctx, channels)
if err != nil {
return "", err
}
result := make([]*proto.BlocChannelCodeStatInfo, 0)
for _, v := range statList {
appName := ""
if appInfo, ok := appInfoMap[v.AppId]; ok {
appName = appInfo.Name
}
tmp := &proto.BlocChannelCodeStatInfo{
SumDate: v.SumDate,
ChannelCode: channelmod.ChannelDisplayName(v.ChannelCode),
AppId: v.AppId,
AppName: appName,
Channel: v.Channel,
NewDirectCount: v.NewDirectCount,
NewFissionCount: v.NewFissionCount,
DirectVisitCount: v.DirectVisitCount,
NewDeductionCount: v.NewDeductionCount,
FissionVisitCount: v.FissionVisitCount,
DeductionAmount: v.DeductionAmount,
RechargeAmount: v.DirectAmount,
FissionAmount: v.FissionAmount,
UpdatedAt: v.UpdatedAt,
}
ruleinfo := ruleMap.GetRule(v.ChannelCode, v.AppId)
if ruleinfo != nil {
if ruleinfo.Settlement == channelmod.SettlementCPA {
tmp.Settlement = fmt.Sprintf("CPA_%.2f", float64(ruleinfo.CPAPrice)/100)
tmp.TodayIncome = tmp.NewDirectCount * ruleinfo.CPAPrice
} else if ruleinfo.Settlement == channelmod.SettlementCPS {
tmp.Settlement = fmt.Sprintf("CPS_%.2f", float64(ruleinfo.CPSRate)/100)
tmp.TodayIncome = tmp.RechargeAmount * ruleinfo.CPSRate / 100
} else if ruleinfo.Settlement == channelmod.SettlementCPT {
tmp.Settlement = "CPT"
} else if ruleinfo.Settlement == channelmod.SettlementCPC {
tmp.Settlement = "CPC"
}
}
result = append(result, tmp)
}
filePath := fmt.Sprintf("./tmp/渠道列表-%s.xlsx", time.Now().Format("2006-01-02 15:04:05"))
if err := export(ctx, filePath, result); err != nil {
return "", err
}
return filePath, nil
}
func export(ctx context.Context, rPath string, channelStatList []*proto.BlocChannelCodeStatInfo) error {
f := excelize.NewFile()
sheetName := "Sheet1"
idx := f.NewSheet(sheetName)
var exportTransWaterTitle = map[string]string{}
exportTransWaterTitle = map[string]string{
"A1": "日期",
"B1": "渠道",
"C1": "结算方式",
"D1": "当日支出",
"E1": "新增(直推)",
"F1": "新增(裂变)",
"G1": "新增(扣量)",
"H1": "访问量",
"I1": "直推充值金额",
"J1": "裂变充值金额",
}
for k, v := range exportTransWaterTitle {
f.SetCellValue(sheetName, k, v)
}
for k, v := range channelStatList {
i := 'A' // i 不能超过Z,即列输不能超过26列,超出将出现bug
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), v.SumDate)
i++
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), v.ChannelCode)
i++
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), v.Settlement)
i++
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), float64(v.TodayIncome)/100)
i++
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), v.NewDirectCount)
i++
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), v.NewFissionCount)
i++
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), v.NewDeductionCount)
i++
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), v.DirectVisitCount+v.FissionVisitCount)
i++
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), float64(v.DeductionAmount)/100)
i++
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), float64(v.FissionAmount)/100)
}
f.SetActiveSheet(idx)
p, _ := os.Getwd()
fileName := path.Join(p, rPath)
if err := f.SaveAs(fileName); err != nil {
log.Error("srv_vipcode:export save file to disk failed",
log.Any("fileName", fileName),
log.E(err))
return err
}
return nil
}
+957
View File
@@ -0,0 +1,957 @@
package statSer
import (
"bytes"
"context"
"fmt"
"os"
"path"
"stat/api/proto"
"stat/common/log"
"stat/common/stdLog"
"stat/common/utils"
"stat/models/appInfoMod"
"stat/models/blocDailystatmod"
"stat/models/botdailyhourstat"
"stat/models/channelmod"
"stat/models/dailyhourstatmod"
"stat/models/dailystatmod"
"stat/models/minutestatmod"
"stat/models/rechargemod"
"stat/models/registermod"
"stat/models/withdrawUserOrdermod"
"strconv"
"time"
"github.com/360EntSecGroup-Skylar/excelize"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo/options"
)
// 获取日常统计信息列表
func ListDailyStat(ctx context.Context, param proto.ListDailyStatReq) (code stdLog.CodeType, data interface{}, err error) {
resp := &proto.ListDailyStatRes{
Total: 0,
TotalInfo: &proto.DailyStatInfo{},
List: make([]*proto.DailyStatInfo, 0),
}
skip, limit := utils.ToDBPage(param.PageNum, param.PageSize)
total, statList, err := dailystatmod.GetDailyStatList(ctx, param.AppId, param.StartTime, param.EndTime, skip, limit)
if err != nil {
code = stdLog.Failure
return
}
appIds := make([]int32, 0)
for _, v := range statList {
appIds = append(appIds, v.AppId)
}
appInfoMap, err := MakeAppInfosMapByIds(ctx, appIds)
if err != nil {
code = stdLog.Failure
return
}
for _, v := range statList {
appName := ""
if appInfo, ok := appInfoMap[v.AppId]; ok {
appName = appInfo.Name
}
tmp := DailyStatToInfo(v)
tmp.AppName = appName
resp.List = append(resp.List, tmp)
}
if param.AppId != nil || param.StartTime != nil || param.EndTime != nil {
var count int
err := dailystatmod.GetDailyStatTotalListShard(ctx, param.AppId, param.StartTime, param.EndTime, utils.GlobalShardMonth, func(item *dailystatmod.DailyStatMod) {
count++
tmp := DailyStatToInfo(item)
DailyStatInfoAccumulate(resp.TotalInfo, tmp)
})
if err != nil {
code = stdLog.Failure
return code, nil, err
}
// 计算留存率平均值
if count > 0 {
resp.TotalInfo.Day1RetainRate = resp.TotalInfo.Day1RetainRate / float64(count)
resp.TotalInfo.Day3RetainRate = resp.TotalInfo.Day3RetainRate / float64(count)
resp.TotalInfo.Day7RetainRate = resp.TotalInfo.Day7RetainRate / float64(count)
}
}
resp.Total = total
data = resp
code = stdLog.Success
return
}
// 获取日常统计信息列表
func ListDailyStatExport(ctx context.Context, param proto.ListDailyStatReq) (string, error) {
skip, limit := utils.ToDBPage(param.PageNum, param.PageSize)
_, statList, err := dailystatmod.GetDailyStatList(ctx, param.AppId, param.StartTime, param.EndTime, skip, limit)
if err != nil {
return "", err
}
filePath := fmt.Sprintf("./tmp/日常统计信息列表-%s.xlsx", time.Now().Format("2006-01-02 15:04:05"))
if sErr := dailyStatexport(filePath, statList); sErr != nil {
return "", sErr
}
return filePath, nil
}
func dailyStatexport(rPath string, transInfos []*dailystatmod.DailyStatMod) error {
f := excelize.NewFile()
sheetName := "Sheet1"
// 设置列宽
f.SetColWidth("Sheet1", "A", "O", 20)
// 设置表头行高
f.SetRowHeight("Sheet1", 1, 30)
// 设置表头样式
headStyleID, _ := f.NewStyle(`{
"font":{
"color":"#333333",
"bold":true,
"size":16,
"family":"arial"
},
"alignment":{
"vertical":"center",
"horizontal":"center"
}
}`)
f.SetCellStyle("Sheet1", "A1", "O1", headStyleID)
idx := f.NewSheet(sheetName)
var exportTransWaterTitle = map[string]string{
"A1": "日期",
"B1": "总日活",
"C1": "android日活",
"D1": "ios日活",
"E1": "总新增",
"F1": "android新增",
"G1": "ios新增",
"H1": "总充值",
"I1": "android充值",
"J1": "ios充值",
"K1": "总新增H5",
"L1": "渠道新增",
"M1": "渠道总充值",
"N1": "老用户总充值",
"O1": "新用户总充值",
"P1": "复充总金额",
"Q1": "重充VIP总金额",
"R1": "拉起订单数",
"S1": "成功订单数",
}
for k, v := range exportTransWaterTitle {
f.SetCellValue(sheetName, k, v)
}
for k, v := range transInfos {
i := 'A' // i 不能超过Z,即列输不能超过26列,超出将出现bug
date := v.SumDate.Local().Format("2006-01-02")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), date) //日期
i++
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), v.TotalVisitCount) //总日活
i++
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), v.TotalVisitCount-(v.LightVisitCount+v.TodayH5VisitCount)) //android日活
i++
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), v.LightVisitCount+v.TodayH5VisitCount) //ios日活
i++
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), v.NewTotalCount) //总新增
i++
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), v.NewTotalCount-(v.LightAddCount+v.H5AddCount)) //android总新增
i++
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), v.LightAddCount+v.H5AddCount) //Ios总新增
i++
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), float64(v.TotalAmount)/100) //总充值
i++
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), float64(v.TodayAndroidRecharge)/100) //android充值
i++
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), float64(v.TotalAmount)/100-float64(v.TodayAndroidRecharge)/100) //ios充值
i++
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), v.H5AddCount) //总新增H5
i++
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), v.NewDistrictCount) //渠道新增
i++
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), float64(v.TodayDistrictRecharge)/100) //渠道总充值
i++
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), float64(v.TotalAmount-v.NewRechargeAmount)/100) //老用户总充值
i++
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), float64(v.NewRechargeAmount)/100) //新用户总充值
i++
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), float64(v.RepeatAmount)/100) //复充总金额
i++
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), float64(v.RepeatVipMoney)/100) //重充VIP总金额
i++
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), v.TotalRechargeOrder) //拉起订单数
i++
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), v.SuccessRechargeOrder) //成功订单数
}
f.SetActiveSheet(idx)
p, _ := os.Getwd()
fileName := path.Join(p, rPath)
if err := f.SaveAs(fileName); err != nil {
log.Error("SettlementInfo:export save file to disk failed",
log.Any("fileName", fileName),
log.E(err))
return err
}
return nil
}
// 获取日常统计信息列表
func BlocListDailyStat(ctx context.Context, param proto.ListBlocDailyStatReq) (code stdLog.CodeType, data interface{}, err error) {
resp := proto.ListBlocDailyStatRes{
Total: 0,
List: make([]proto.BlocDailyStatInfo, 0),
}
skip, limit := utils.ToDBPage(param.PageNum, param.PageSize)
total, statList, err := blocDailystatmod.GetBlocDailyStatList(ctx, param.AppId, param.StartTime, param.EndTime, skip, limit)
if err != nil {
code = stdLog.Failure
return
}
appIds := make([]int32, 0)
for _, v := range statList {
appIds = append(appIds, v.AppId)
}
appInfoMap, err := MakeAppInfosMapByIds(ctx, appIds)
if err != nil {
code = stdLog.Failure
return code, nil, err
}
for _, v := range statList {
appName := ""
if appInfo, ok := appInfoMap[v.AppId]; ok {
appName = appInfo.Name
}
tmp := proto.BlocDailyStatInfo{
SumDate: v.SumDate,
AppId: v.AppId,
AppName: appName,
NewDirectCount: v.NewDirectCount,
NewFissionCount: v.NewFissionCount,
DirectVisitCount: v.DirectVisitCount,
FissionVisitCount: v.FissionVisitCount,
Day1RetainRate: v.Day1RetainRate,
Day3RetainRate: v.Day3RetainRate,
Day7RetainRate: v.Day7RetainRate,
DirectAmount: v.DirectAmount,
FissionAmount: v.FissionAmount,
NewRechargeCount: v.NewRechargeCount,
NewRechargeAmount: v.NewRechargeAmount,
DaichongAmout: v.DaichongAmout,
OnlineAmount: v.OnlineAmount,
UserWithDrawAmount: v.UserWithDrawAmount,
UserWithDrawPaymoneyAmount: v.UserWithDrawPaymoneyAmount,
GameRechageAmount: v.GameRechageAmount,
GameWithdrawAmount: v.GameWithdrawAmount,
TotalRechargeCount: v.TotalRechargeCount,
UpdatedAt: v.UpdatedAt,
}
tmp.TotalVisitCount = v.DirectVisitCount + v.FissionVisitCount // 日活
tmp.TotalAmount = v.DirectAmount + v.FissionAmount // 总充值
tmp.TotalNewCount = v.NewDirectCount + v.NewFissionCount // 新增
tmp.OldRechargeAmount = tmp.TotalAmount - tmp.NewRechargeAmount // 老用户充值
total := v.DeductionAmountCount + v.DirectAmountCount + v.FissionAmountCount // 充值人数
if tmp.TotalAmount-tmp.DaichongAmout > 0 {
tmp.OnlineAmount = tmp.TotalAmount - tmp.DaichongAmout
}
if tmp.TotalNewCount != 0 {
tmp.NewRechargeRate, _ = strconv.ParseFloat(fmt.Sprintf("%.2f", (float64(v.NewRechargeCount)/float64(tmp.TotalNewCount))*100), 64)
}
if tmp.TotalVisitCount != 0 {
tmp.VisitRechargeRate, _ = strconv.ParseFloat(fmt.Sprintf("%.2f", (float64(total)/float64(tmp.TotalVisitCount))*100), 64)
}
if total != 0 {
tmp.ARPPU = (tmp.DirectAmount + tmp.FissionAmount) / total // 金额
}
if tmp.TotalVisitCount != 0 {
tmp.ARPU = tmp.TotalAmount / tmp.TotalVisitCount // 金额
}
resp.List = append(resp.List, tmp)
}
if param.AppId != nil || param.StartTime != nil || param.EndTime != nil {
// 过滤条件至少有一个,不然不统计总数
totalList, err := blocDailystatmod.GetBlocDailyStatListTotal(ctx, param.AppId, param.StartTime, param.EndTime)
if err != nil {
code = stdLog.Failure
return code, nil, err
}
for _, v := range totalList {
resp.TotalInfo.NewDirectCount += v.NewDirectCount
resp.TotalInfo.NewFissionCount += v.NewFissionCount
resp.TotalInfo.DirectVisitCount += v.DirectVisitCount
resp.TotalInfo.FissionVisitCount += v.FissionVisitCount
resp.TotalInfo.DirectAmount += v.DirectAmount
resp.TotalInfo.FissionAmount += v.FissionAmount
resp.TotalInfo.NewRechargeCount += v.NewRechargeCount
resp.TotalInfo.NewRechargeAmount += v.NewRechargeAmount
resp.TotalInfo.DaichongAmout += v.DaichongAmout
resp.TotalInfo.UserWithDrawAmount += v.UserWithDrawAmount
resp.TotalInfo.UserWithDrawPaymoneyAmount += v.UserWithDrawPaymoneyAmount
resp.TotalInfo.GameRechageAmount += v.GameRechageAmount
resp.TotalInfo.GameWithdrawAmount += v.GameWithdrawAmount
resp.TotalInfo.TotalRechargeCount += v.TotalRechargeCount
}
resp.TotalInfo.TotalVisitCount = resp.TotalInfo.DirectVisitCount + resp.TotalInfo.FissionVisitCount // 日活
resp.TotalInfo.TotalAmount = resp.TotalInfo.DirectAmount + resp.TotalInfo.FissionAmount // 总充值
resp.TotalInfo.TotalNewCount = resp.TotalInfo.NewDirectCount + resp.TotalInfo.NewFissionCount // 新增
resp.TotalInfo.OldRechargeAmount = resp.TotalInfo.TotalAmount - resp.TotalInfo.NewRechargeAmount // 老用户充值
if resp.TotalInfo.TotalAmount-resp.TotalInfo.DaichongAmout > 0 {
resp.TotalInfo.OnlineAmount = resp.TotalInfo.TotalAmount - resp.TotalInfo.DaichongAmout
}
}
resp.Total = total
data = resp
code = stdLog.Success
return
}
// 获取充值明细(集团)
func BlocListRechargeDetailsInfo(ctx context.Context, param proto.ListBlocRechargeDetailsReq) (code stdLog.CodeType, data interface{}, err error) {
resp := proto.ListBlocRechargeDetailsRes{
Total: 0,
List: make([]proto.BlocRechargeDetailsInfo, 0),
}
skip, limit := utils.ToDBPage(param.PageNum, param.PageSize)
total, RechagerList, err := rechargemod.GetBlocRechargeDetailsList(ctx, param.AppId, param.StartTime, param.EndTime, skip, limit)
if err != nil {
code = stdLog.Failure
return
}
appIds := make([]int32, 0)
appMap := make(map[int32]int32)
for _, v := range RechagerList {
if _, ok := appMap[v.AppId]; !ok {
appMap[v.AppId] = v.AppId
appIds = append(appIds, v.AppId)
}
}
//fmt.Println(appIds)
appInfoMap, err := MakeAppInfosMapByIds(ctx, appIds)
if err != nil {
code = stdLog.Failure
return
}
for _, v := range RechagerList {
appName := ""
if appInfo, ok := appInfoMap[v.AppId]; ok {
appName = appInfo.Name
}
tmp := proto.BlocRechargeDetailsInfo{
UserId: v.PromCode,
AppName: appName,
SysType: v.SysType,
DevType: v.DevType,
ChannelName: v.ChannelName,
CID: v.CID,
Type: v.Type,
OrderId: v.OrderId,
Money: v.Money,
PayMoney: v.PayMoney,
Status: 3,
SuccessAt: v.SuccessAt,
}
resp.List = append(resp.List, tmp)
}
resp.Total = total
data = resp
code = stdLog.Success
return
}
// 获取日常统计信息列表
func BlocListDailyTotalStat(ctx context.Context, param proto.ListBlocDailyStatTotalReq) (code stdLog.CodeType, data interface{}, err error) {
resp := proto.ListBlocDailyStatTotalRes{
Total: 0,
List: make([]proto.BlocDailyStatTotalInfo, 0),
}
skip, limit := utils.ToDBPage(param.PageNum, param.PageSize)
statList, err := blocDailystatmod.GetBlocDailyStatTotalList(ctx, param.StartTime, param.EndTime)
if err != nil {
code = stdLog.Failure
return
}
count := int64(0)
for _, v := range statList {
if count >= skip && count <= skip+limit {
tmp := proto.BlocDailyStatTotalInfo{
SumDate: v.SumDate,
NewDirectCount: v.NewDirectCount,
NewFissionCount: v.NewFissionCount,
DirectVisitCount: v.DirectVisitCount,
FissionVisitCount: v.FissionVisitCount,
DirectAmount: v.DirectAmount,
FissionAmount: v.FissionAmount,
NewRechargeCount: v.NewRechargeCount,
NewRechargeAmount: v.NewRechargeAmount,
DaichongAmout: v.DaichongAmout,
OnlineAmount: v.OnlineAmount,
UserWithDrawAmount: v.UserWithDrawAmount,
UserWithDrawPaymoneyAmount: v.UserWithDrawPaymoneyAmount,
GameRechageAmount: v.GameRechageAmount,
GameWithdrawAmount: v.GameWithdrawAmount,
TotalRechargeCount: v.TotalRechargeCount,
}
tmp.TotalVisitCount = v.DirectVisitCount + v.FissionVisitCount // 日活
tmp.TotalAmount = v.DirectAmount + v.FissionAmount // 总充值
tmp.TotalNewCount = v.NewDirectCount + v.NewFissionCount // 新增
tmp.OldRechargeAmount = tmp.TotalAmount - tmp.NewRechargeAmount // 老用户充值
if tmp.TotalAmount-tmp.DaichongAmout > 0 {
tmp.OnlineAmount = tmp.TotalAmount - tmp.DaichongAmout
}
resp.List = append(resp.List, tmp)
}
count += 1
}
resp.Total = count
data = resp
code = stdLog.Success
return
}
// 获取充值明细(集团)
func BlocListRegisterDetailsInfo(ctx context.Context, param proto.ListBlocRegisterDetailsReq) (code stdLog.CodeType, data interface{}, err error) {
resp := proto.ListBlocRegisterDetailsRes{
Total: 0,
List: make([]proto.BlocRegisterDetailsInfo, 0),
}
skip, limit := utils.ToDBPage(param.PageNum, param.PageSize)
total, RechagerList, err := registermod.GetBlocRegisterDetailsList(ctx, param.AppId, param.StartTime, param.EndTime, skip, limit)
if err != nil {
code = stdLog.Failure
return
}
appIds := make([]int32, 0)
appMap := make(map[int32]int32)
for _, v := range RechagerList {
if _, ok := appMap[v.AppId]; !ok {
appMap[v.AppId] = v.AppId
appIds = append(appIds, v.AppId)
}
}
appInfoMap, err := MakeAppInfosMapByIds(ctx, appIds)
if err != nil {
code = stdLog.Failure
return
}
for _, v := range RechagerList {
appName := ""
if appInfo, ok := appInfoMap[v.AppId]; ok {
appName = appInfo.Name
}
tmp := proto.BlocRegisterDetailsInfo{
UserId: v.PromCode,
AppName: appName,
SysType: v.SysType,
DevType: v.DevType,
Mobile: v.Mobile,
IP: v.IP,
Name: v.Name,
IsDirect: v.IsDirect,
RegisterTime: v.RegisterTime,
}
resp.List = append(resp.List, tmp)
}
resp.Total = total
data = resp
code = stdLog.Success
return
}
// 获取提现明细(集团)
func BlocListWithdrawDetailsInfo(ctx context.Context, param proto.ListBlocWithdrawDetailsReq) (code stdLog.CodeType, data interface{}, err error) {
resp := proto.ListBlocWithdrawDetailsRes{
Total: 0,
List: make([]proto.ListBlocWithdrawDetailsInfo, 0),
}
skip, limit := utils.ToDBPage(param.PageNum, param.PageSize)
total, RechagerList, err := withdrawUserOrdermod.GetBlocUserWithdrawOrderList(ctx, param.AppId, param.StartTime, param.EndTime, skip, limit)
if err != nil {
code = stdLog.Failure
return
}
appIds := make([]int32, 0)
appMap := make(map[int32]int32)
for _, v := range RechagerList {
if _, ok := appMap[v.AppId]; !ok {
appMap[v.AppId] = v.AppId
appIds = append(appIds, v.AppId)
}
}
//fmt.Println(appIds)
appInfoMap, err := MakeAppInfosMapByIds(ctx, appIds)
if err != nil {
code = stdLog.Failure
return
}
for _, v := range RechagerList {
appName := ""
if appInfo, ok := appInfoMap[v.AppId]; ok {
appName = appInfo.Name
}
tmp := proto.ListBlocWithdrawDetailsInfo{
UID: v.PromCode,
AppName: appName,
Name: v.Name,
Amount: v.Amount,
Money: v.Money,
PayMoney: v.PayMoney,
PayType: v.PayType,
OID: v.OID,
ActName: v.ActName,
Act: v.Act,
UserIP: v.UserIP,
DeviceType: v.DeviceType,
DevID: v.DevID,
Status: v.Status,
StatusDesc: v.StatusDesc,
SuccessAt: v.SuccessAt,
ReceivedAt: v.ReceivedAt,
}
resp.List = append(resp.List, tmp)
}
resp.Total = total
data = resp
code = stdLog.Success
return
}
// 获取提现明细(集团)
func ListBlocRechargeChannelInfo(ctx context.Context, param proto.ListBlocRechargeChannelReq) (code stdLog.CodeType, data interface{}, err error) {
resp := proto.ListBlocRechargeChannelRes{
Total: 0,
List: make([]proto.ListBlocRechargeChannelInfo, 0),
}
skip, limit := utils.ToDBPage(param.PageNum, param.PageSize)
statList, err := blocDailystatmod.GetBlocDailyStatTotalList(ctx, param.StartTime, param.EndTime)
if err != nil {
code = stdLog.Failure
return
}
count := int64(0)
for _, v := range statList {
if count >= skip && count <= skip+limit {
tmp := proto.ListBlocRechargeChannelInfo{
SumDate: v.SumDate,
ChannelName: "大白鲨",
BlocOrderMoney: v.DirectAmount + v.FissionAmount,
BlocChannelRate: 12,
}
resp.List = append(resp.List, tmp)
}
count += 1
}
resp.Total = count
data = resp
code = stdLog.Success
return
}
func ListMinuteStat(ctx context.Context, param proto.ListMinuteStatReq) (code stdLog.CodeType, data interface{}, err error) {
resp := proto.ListMinuteStatRes{
TodayGraphList: make([]proto.MinuteStatInfo, 0),
}
// 获取app信息
appInfo, err := appInfoMod.GetAppInfoById(ctx, *param.AppId)
if err != nil {
code = stdLog.Failure
return
}
resp.AppInfo = proto.AppInfoRes{
AppId: appInfo.AppId,
Name: appInfo.Name,
LandPages: appInfo.LandPages,
IconImg: appInfo.IconImg,
CreatedAt: appInfo.CreatedAt,
UpdatedAt: appInfo.UpdatedAt,
}
start := *param.SumDate
end := start.AddDate(0, 0, 1)
todayStatList, err := minutestatmod.GetMinuteStatList(ctx, start, end, *param.AppId)
if err != nil {
code = stdLog.Failure
return
}
for _, v := range todayStatList {
tmp := proto.MinuteStatInfo{
SumDate: v.SumDate,
NewCount: v.NewCount,
AndroidAddCount: v.AndroidAddCount,
IosAddCount: v.IosAddCount,
H5AddCount: v.H5AddCount,
RechargetCount: v.RechargetCount,
RechargetMoney: v.RechargetMoney,
AccessCount: v.AccessCount,
AndroidAccessCount: v.AndroidAccessCount,
IosAccessCount: v.IosAccessCount + v.H5AccessCount,
LandpageAndroidClick: v.LandpageAndroidClick,
LandpageIosAppstoreClick: v.LandpageIosAppstoreClick,
LandpageIosAthorClick: v.LandpageIosAthorClick,
LandpageIosH5Click: v.LandpageIosH5Click,
LightGuestAddCount: v.LightGuestAddCount,
}
resp.TodayGraphList = append(resp.TodayGraphList, tmp)
}
compareTime := start.AddDate(0, 0, -1)
if param.CompareTime != nil {
compareTime = *param.CompareTime
}
compareTimeEnd := compareTime.AddDate(0, 0, 1)
yesterdayStatList, err := minutestatmod.GetMinuteStatList(ctx, compareTime, compareTimeEnd, *param.AppId)
if err != nil {
code = stdLog.Failure
return
}
for _, v := range yesterdayStatList {
tmp := proto.MinuteStatInfo{
SumDate: v.SumDate,
NewCount: v.NewCount,
AndroidAddCount: v.AndroidAddCount,
IosAddCount: v.IosAddCount,
H5AddCount: v.H5AddCount,
RechargetCount: v.RechargetCount,
RechargetMoney: v.RechargetMoney,
AccessCount: v.AccessCount,
AndroidAccessCount: v.AndroidAccessCount,
IosAccessCount: v.IosAccessCount + v.H5AccessCount,
LandpageAndroidClick: v.LandpageAndroidClick,
LandpageIosAppstoreClick: v.LandpageIosAppstoreClick,
LandpageIosAthorClick: v.LandpageIosAthorClick,
LandpageIosH5Click: v.LandpageIosH5Click,
LightGuestAddCount: v.LightGuestAddCount,
}
resp.YesterdayGraphList = append(resp.YesterdayGraphList, tmp)
}
data = resp
code = stdLog.Success
return
}
// 获取日常统计信息列表
func ListDailyHourStat(ctx context.Context, param *proto.ListDailyHourStatReq) (code stdLog.CodeType, data *proto.ListDailyHourStatRes, err error) {
resp := &proto.ListDailyHourStatRes{
Total: 0,
List: make([]*proto.DailyHourStatInfo, 0),
}
filter := bson.M{}
opt := &options.FindOptions{}
opt.SetSort(bson.M{dailyhourstatmod.FN.DailyHourStat.SumDate: -1})
opt.SetSkip((param.PageNum - 1) * param.PageSize)
opt.SetLimit(param.PageSize)
if param.AppId != nil {
filter[dailyhourstatmod.FN.DailyHourStat.AppId] = *param.AppId
}
if param.StartTime != nil && param.EndTime != nil {
filter[dailyhourstatmod.FN.DailyHourStat.SumDate] = bson.M{"$gte": *param.StartTime, "$lt": *param.EndTime}
}
if param.SumHour != nil {
filter[dailyhourstatmod.FN.DailyHourStat.SumHour] = *&param.SumHour
}
total, _ := dailyhourstatmod.CountDailyHourStats(ctx, filter)
statList, err := dailyhourstatmod.FindManyDailyHourStat(ctx, filter, opt)
if err != nil {
code = stdLog.Failure
return
}
appIds := make([]int32, 0)
for _, v := range statList {
appIds = append(appIds, v.AppId)
}
appInfoMap, err := MakeAppInfosMapByIds(ctx, appIds)
if err != nil {
code = stdLog.Failure
return
}
for _, v := range statList {
appName := ""
if appInfo, ok := appInfoMap[v.AppId]; ok {
appName = appInfo.Name
}
tmp := DailyHourStatToInfo(v)
tmp.AppName = appName
resp.List = append(resp.List, tmp)
}
resp.Total = total
data = resp
code = stdLog.Success
return
}
// 获取日常统计信息列表
func ListDailyHourStatExport(ctx context.Context, param *proto.ListDailyHourStatReq) (*bytes.Buffer, error) {
_, data, err := ListDailyHourStat(ctx, param)
if err != nil {
return nil, err
}
return ExportDailyHourStat(data.List)
}
func ListHourStatLine(ctx context.Context, param proto.ListHourStatLineReq) (code stdLog.CodeType, data interface{}, err error) {
resp := proto.ListHourStatLineResp{
TodayGraphList: make([]proto.ListHourStatLineInfo, 0),
YesterdayGraphList: make([]proto.ListHourStatLineInfo, 0),
}
// 获取app信息
appInfo, err := appInfoMod.GetAppInfoById(ctx, *param.AppId)
if err != nil {
code = stdLog.Failure
return
}
resp.AppInfo = proto.AppInfoRes{
AppId: appInfo.AppId,
Name: appInfo.Name,
LandPages: appInfo.LandPages,
IconImg: appInfo.IconImg,
CreatedAt: appInfo.CreatedAt,
UpdatedAt: appInfo.UpdatedAt,
}
start := *param.SumDate
end := start.AddDate(0, 0, 1)
todayFilter := bson.M{
"sumDate": bson.M{"$gte": start, "$lte": end},
"appId": *param.AppId,
}
todayStatList, err := botdailyhourstat.FindManyBotHourStat(ctx, todayFilter)
if err != nil {
code = stdLog.Failure
return
}
for _, v := range todayStatList {
tmp := proto.ListHourStatLineInfo{
SumDate: v.SumDate,
HourNewDistrict: v.HourStat.Register.Total.District.Count,
HourNewWeb: v.HourStat.Register.Total.Web.Count,
HourNewNormal: v.HourStat.Register.Total.Normal.Count,
HourNew: v.HourStat.Register.Count(),
HourAndroidNew: v.HourStat.Register.Android.Count(),
HourIosNew: v.HourStat.Register.Ios.Count(),
HourDistrictRecharge: v.HourStat.Recharge.Total.District.Value,
HourWebRecharge: v.HourStat.Recharge.Total.Web.Value,
HourNormalRecharge: v.HourStat.Recharge.Total.Normal.Value,
HourAndroidRecharge: v.HourStat.Recharge.Android.Value(),
HourIosRecharge: v.HourStat.Recharge.Ios.Value(),
HourVipRecharge: v.HourStat.RechargeVip.Value(),
HourDistrictVip: v.HourStat.RechargeVip.Total.District.Value,
HourWebVip: v.HourStat.RechargeVip.Total.Web.Value,
HourNormalVip: v.HourStat.RechargeVip.Total.Normal.Value,
}
resp.TodayGraphList = append(resp.TodayGraphList, tmp)
}
compareTime := start.AddDate(0, 0, -1)
if param.CompareTime != nil {
compareTime = *param.CompareTime
}
compareTimeEnd := compareTime.AddDate(0, 0, 1)
yestodayFilter := bson.M{
"sumDate": bson.M{"$gte": compareTime, "$lte": compareTimeEnd},
"appId": *param.AppId,
}
yesterdayStatList, err := botdailyhourstat.FindManyBotHourStat(ctx, yestodayFilter)
if err != nil {
code = stdLog.Failure
return
}
for _, v := range yesterdayStatList {
tmp := proto.ListHourStatLineInfo{
SumDate: v.SumDate,
HourNewDistrict: v.HourStat.Register.Total.District.Count,
HourNewWeb: v.HourStat.Register.Total.Web.Count,
HourNewNormal: v.HourStat.Register.Total.Normal.Count,
HourNew: v.HourStat.Register.Count(),
HourAndroidNew: v.HourStat.Register.Android.Count(),
HourIosNew: v.HourStat.Register.Ios.Count(),
HourDistrictRecharge: v.HourStat.Recharge.Total.District.Value,
HourWebRecharge: v.HourStat.Recharge.Total.Web.Value,
HourNormalRecharge: v.HourStat.Recharge.Total.Normal.Value,
HourAndroidRecharge: v.HourStat.Recharge.Android.Value(),
HourIosRecharge: v.HourStat.Recharge.Ios.Value(),
HourVipRecharge: v.HourStat.RechargeVip.Value(),
HourDistrictVip: v.HourStat.RechargeVip.Total.District.Value,
HourWebVip: v.HourStat.RechargeVip.Total.Web.Value,
HourNormalVip: v.HourStat.RechargeVip.Total.Normal.Value,
}
resp.YesterdayGraphList = append(resp.YesterdayGraphList, tmp)
}
data = resp
code = stdLog.Success
return
}
// 获取日常统计信息列表
func ChannelStatExport(ctx context.Context, param proto.ChannelStatExportReq) (string, error) {
statfilter := bson.M{}
opt := &options.FindOptions{}
opt.SetSort(bson.M{channelmod.FN.ChannelStat.SumDate: -1})
opt.SetLimit(5000)
if param.AppId != nil && len(*param.AppId) > 0 {
statfilter[channelmod.FN.ChannelStat.AppId] = bson.M{"$in": *param.AppId}
}
if param.Channel != nil {
statfilter[channelmod.FN.ChannelStat.Channel] = *param.Channel
}
if param.StartTime != nil && param.EndTime != nil {
statfilter[channelmod.FN.ChannelStat.SumDate] = bson.M{"$gte": *param.StartTime, "$lte": *param.EndTime}
}
statList, err := channelmod.FindManyChannelStat(ctx, statfilter, opt)
if err != nil {
return "", err
}
filePath := fmt.Sprintf("./tmp/渠道统计信息列表-%s.xlsx", time.Now().Format("2006-01-02 15:04:05"))
if sErr := channelStatexport(filePath, statList); sErr != nil {
return "", sErr
}
return filePath, nil
}
func channelStatexport(rPath string, statInfos channelmod.ChannelStatList) error {
f := excelize.NewFile()
sheetName := "Sheet1"
// 设置列宽
f.SetColWidth("Sheet1", "A", "O", 20)
// 设置表头行高
f.SetRowHeight("Sheet1", 1, 30)
// 设置表头样式
headStyleID, _ := f.NewStyle(`{
"font":{
"color":"#333333",
"bold":true,
"size":16,
"family":"arial"
},
"alignment":{
"vertical":"center",
"horizontal":"center"
}
}`)
f.SetCellStyle("Sheet1", "A1", "O1", headStyleID)
// 统计时间 app名称 所属渠道 总新增 总充值
idx := f.NewSheet(sheetName)
var exportTransWaterTitle = map[string]string{
"A1": "日期",
"B1": "APP名称",
"C1": "渠道码",
"D1": "总新增",
"E1": "总充值",
}
for k, v := range exportTransWaterTitle {
f.SetCellValue(sheetName, k, v)
}
appInfos, _ := appInfoMod.GetAllAppInfo(context.Background())
for k, v := range statInfos {
i := 'A' // i 不能超过Z,即列输不能超过26列,超出将出现bug
date := v.SumDate.Local().Format("2006-01-02")
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), date) //日期
i++
appName := ""
for _, appInfo := range appInfos {
if appInfo.AppId == v.AppId {
appName = appInfo.Name
}
}
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), appName) //APP名称
i++
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), v.Channel) //渠道吗
i++
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), v.NewTotalCount) //总新增
i++
f.SetCellValue(sheetName, fmt.Sprintf("%s%d", string(i), k+2), float64(v.TotalAmount)/100) //总充值
}
f.SetActiveSheet(idx)
p, _ := os.Getwd()
fileName := path.Join(p, rPath)
if err := f.SaveAs(fileName); err != nil {
log.Error("SettlementInfo:export save file to disk failed",
log.Any("fileName", fileName),
log.E(err))
return err
}
return nil
}
+133
View File
@@ -0,0 +1,133 @@
package statSer
import (
"context"
"stat/api/proto"
"stat/common/stdLog"
"stat/common/utils"
"stat/models/rechargegamemod"
"stat/models/withdrawGameOrdermod"
)
// 获取充值明细(集团)
func GameRechargeList(ctx context.Context, param proto.ListBlocRechargeDetailsReq) (code stdLog.CodeType, data interface{}, err error) {
resp := proto.ListBlocRechargeDetailsRes{
Total: 0,
List: make([]proto.BlocRechargeDetailsInfo, 0),
}
skip, limit := utils.ToDBPage(param.PageNum, param.PageSize)
total, RechagerList, err := rechargegamemod.GetBlocRechargeDetailsList(ctx, param.AppId, param.StartTime, param.EndTime, skip, limit)
if err != nil {
code = stdLog.Failure
return
}
appIds := make([]int32, 0)
appMap := make(map[int32]int32)
for _, v := range RechagerList {
if _, ok := appMap[v.AppId]; !ok {
appMap[v.AppId] = v.AppId
appIds = append(appIds, v.AppId)
}
}
//fmt.Println(appIds)
appInfoMap, err := MakeAppInfosMapByIds(ctx, appIds)
if err != nil {
code = stdLog.Failure
return
}
for _, v := range RechagerList {
appName := ""
if appInfo, ok := appInfoMap[v.AppId]; ok {
appName = appInfo.Name
}
tmp := proto.BlocRechargeDetailsInfo{
UserId: v.PromCode,
AppName: appName,
SysType: v.SysType,
DevType: v.DevType,
ChannelName: v.ChannelName,
CID: v.CID,
Type: v.Type,
OrderId: v.OrderId,
Money: v.Money,
PayMoney: v.PayMoney,
Status: 3,
SuccessAt: v.SuccessAt,
}
resp.List = append(resp.List, tmp)
}
resp.Total = total
data = resp
code = stdLog.Success
return
}
// 获取提现明细(集团)
func GameWithdrawList(ctx context.Context, param proto.ListBlocWithdrawDetailsReq) (code stdLog.CodeType, data interface{}, err error) {
resp := proto.ListBlocWithdrawDetailsRes{
Total: 0,
List: make([]proto.ListBlocWithdrawDetailsInfo, 0),
}
skip, limit := utils.ToDBPage(param.PageNum, param.PageSize)
total, RechagerList, err := withdrawGameOrdermod.GetBlocGameWithdrawOrderList(ctx, param.AppId, param.StartTime, param.EndTime, skip, limit)
if err != nil {
code = stdLog.Failure
return
}
appIds := make([]int32, 0)
appMap := make(map[int32]int32)
for _, v := range RechagerList {
if _, ok := appMap[v.AppId]; !ok {
appMap[v.AppId] = v.AppId
appIds = append(appIds, v.AppId)
}
}
//fmt.Println(appIds)
appInfoMap, err := MakeAppInfosMapByIds(ctx, appIds)
if err != nil {
code = stdLog.Failure
return
}
for _, v := range RechagerList {
appName := ""
if appInfo, ok := appInfoMap[v.AppId]; ok {
appName = appInfo.Name
}
tmp := proto.ListBlocWithdrawDetailsInfo{
UID: v.PromCode,
AppName: appName,
Name: v.Name,
Amount: v.Amount,
Money: v.Money,
PayMoney: v.PayMoney,
PayType: v.PayType,
OID: v.OID,
ActName: v.ActName,
Act: v.Act,
UserIP: v.UserIP,
DeviceType: v.DeviceType,
DevID: v.DevID,
Status: v.Status,
StatusDesc: v.StatusDesc,
SuccessAt: v.SuccessAt,
ReceivedAt: v.ReceivedAt,
}
resp.List = append(resp.List, tmp)
}
resp.Total = total
data = resp
code = stdLog.Success
return
}
+617
View File
@@ -0,0 +1,617 @@
package statSer
import (
"bytes"
"context"
"fmt"
"stat/api/proto"
"stat/models/appInfoMod"
"stat/models/channelmod"
"stat/models/dailyhourstatmod"
"stat/models/dailystatmod"
"strconv"
"github.com/360EntSecGroup-Skylar/excelize"
"go.mongodb.org/mongo-driver/bson"
)
type RuleMap map[string]*channelmod.ChannelDeductionRule
func (c RuleMap) GetRule(channel string, appId int32) *channelmod.ChannelDeductionRule {
key := fmt.Sprintf("%s %d", channel, appId)
return c[key]
}
func (c RuleMap) SetRule(rule *channelmod.ChannelDeductionRule) {
key := fmt.Sprintf("%s %d", rule.Channel, rule.AppId)
c[key] = rule
}
func MakeRuleMap(ctx context.Context, channels []string) (RuleMap, error) {
ruleMap := make(RuleMap)
m := make(map[string]struct{})
for _, s := range channels {
m[s] = struct{}{}
}
channels = make([]string, 0, len(m))
for k := range m {
channels = append(channels, k)
}
rules, err := channelmod.FindManyChannelDeductionRule(ctx, bson.M{channelmod.FN.ChannelDeductionRule.Channel: bson.M{"$in": channels}})
if err != nil {
return ruleMap, err
}
for _, item := range rules {
ruleMap.SetRule(item)
}
return ruleMap, nil
}
func MakeAppInfosMapByIds(ctx context.Context, appIds []int32) (map[int32]*appInfoMod.AppInfo, error) {
r := make(map[int32]*appInfoMod.AppInfo)
if len(appIds) == 0 {
return r, nil
}
appList, err := appInfoMod.GetAppInfosByAppIds(ctx, appIds)
if err != nil {
return r, err
}
for _, v := range appList {
_v := v
r[v.AppId] = &_v
}
return r, nil
}
func ExportDailyHourStat(transInfos []*proto.DailyHourStatInfo) (*bytes.Buffer, error) {
f := excelize.NewFile()
cell := func(row, col int) string { // row start form 0, col start form 0
return fmt.Sprintf("%s%d", excelize.ToAlphaString(col), row+1)
}
sheetName := "Sheet1"
row0 := []string{0: "记录时间", 1: "APP名称", 2: "总充值", 3: "总新增", 4: "新增", 11: "充值", 19: "访问数", 21: "VIP", 24: "订单", 27: "落地页", 29: ""}
row1 := []string{4: "直推", 5: "裂变", 6: "安卓", 7: "IOS", 8: "自然", 9: "H5", 10: "扣量", 11: "直推", 12: "裂变", 13: "自然",
14: "新增", 15: "老用户", 16: "复充", 17: "游戏充值", 18: "游戏提现", 19: "直推", 20: "裂变", 21: "余额", 22: "人民币", 23: "复充",
24: "成功", 25: "拉起", 26: "成功率", 27: "安卓", 28: "商店包", 29: "企业签"}
// 设置列宽
f.SetColWidth("Sheet1", excelize.ToAlphaString(0), excelize.ToAlphaString(1), 16)
f.SetColWidth("Sheet1", excelize.ToAlphaString(2), excelize.ToAlphaString(3), 10)
f.SetColWidth("Sheet1", excelize.ToAlphaString(4), excelize.ToAlphaString(len(row0)-1), 8)
// 设置表头行高
f.SetRowHeight("Sheet1", 1, 30)
// 设置表头样式
headStyleID, _ := f.NewStyle(`{
"font":{
"color":"#333333",
"bold":true,
"size":16,
"family":"arial"
},
"alignment":{
"vertical":"center",
"horizontal":"center"
}
}`)
f.SetCellStyle("Sheet1", cell(0, 0), cell(0, len(row0)-1), headStyleID)
idx := f.NewSheet(sheetName)
// set main title
for col, name := range row0 {
f.SetCellValue(sheetName, cell(0, col), name)
}
// merge some title
var mergeCells = map[string]string{
cell(0, 4): cell(0, 10),
cell(0, 11): cell(0, 18),
cell(0, 19): cell(0, 20),
cell(0, 21): cell(0, 23),
cell(0, 24): cell(0, 26),
cell(0, 27): cell(0, 29),
}
for hcell, vcell := range mergeCells {
f.MergeCell(sheetName, hcell, vcell)
}
// set sub title
for col, name := range row1 {
f.SetCellValue(sheetName, cell(1, col), name)
}
// set cell data
for k, v := range transInfos {
col := 0
row := k + 2
axis := func() string {
ret := cell(row, col)
col++
return ret
}
fmtW := func(v int64) string {
return fmt.Sprintf("%.4f", float64(v)/float64(10000))
}
f.SetCellValue(sheetName, axis(), v.SumDate.Local().Format("2006-01-02 15:04:05")) //记录时间
f.SetCellValue(sheetName, axis(), v.AppName) //APP名称
f.SetCellValue(sheetName, axis(), fmtW(v.DirectAmount+v.FissionAmount)) //总充值
f.SetCellValue(sheetName, axis(), v.NewDirectCount+v.NewFissionCount) //总新增
// Group 1 : E1-K1 = 7
f.SetCellValue(sheetName, axis(), v.NewDirectCount) //新增-直推
f.SetCellValue(sheetName, axis(), v.NewFissionCount) //新增-裂变
f.SetCellValue(sheetName, axis(), v.NewAndroidCount) //新增-安卓
f.SetCellValue(sheetName, axis(), v.NewIosCount) //新增-IOS
f.SetCellValue(sheetName, axis(), v.NewNaturalCount) //新增-自然
f.SetCellValue(sheetName, axis(), v.NewH5Count) //新增-H5
f.SetCellValue(sheetName, axis(), v.NewDeductionCount) //新增-扣量
//Group 2 : L1-S1 = 8
f.SetCellValue(sheetName, axis(), fmtW(v.DirectAmount)) //充值-直推
f.SetCellValue(sheetName, axis(), fmtW(v.FissionAmount)) //充值-裂变
f.SetCellValue(sheetName, axis(), fmtW(v.NaturalAmount)) //充值-自然
f.SetCellValue(sheetName, axis(), fmtW(v.NewRechargeAmount)) //充值-新增
f.SetCellValue(sheetName, axis(), fmtW(v.DirectAmount+v.FissionAmount-v.NewRechargeAmount)) //充值-老用户
f.SetCellValue(sheetName, axis(), fmtW(v.RepeatAmount)) //充值-复充
f.SetCellValue(sheetName, axis(), fmtW(v.GameRechageAmount)) //充值-游戏充值
f.SetCellValue(sheetName, axis(), fmtW(v.GameWithdrawAmount)) //充值-游戏提现
//Group 3 : T1-U1 = 2
f.SetCellValue(sheetName, axis(), v.DirectVisitCount) //访问数-直推
f.SetCellValue(sheetName, axis(), v.FissionVisitCount) //访问数-裂变
//Group 4 : V1-X1 = 3
f.SetCellValue(sheetName, axis(), fmtW(v.VipPayByAmount)) //VIP-余额
f.SetCellValue(sheetName, axis(), fmtW(v.VipPayByMoney)) //VIP-人民币
f.SetCellValue(sheetName, axis(), fmtW(v.VipRepeatAmount)) //VIP-复充
//Group 5 : Y1-AA1 = 3
f.SetCellValue(sheetName, axis(), v.SuccessRechargeOrder) //订单-成功
f.SetCellValue(sheetName, axis(), v.TotalRechargeOrder) //订单-拉起
f.SetCellValue(sheetName, axis(), v.RechargeSuccessRate) //订单-成功率
//Group 6 : AB1-AD1 = 3
f.SetCellValue(sheetName, axis(), v.LandpageAndroidClick) //落地页-安卓
f.SetCellValue(sheetName, axis(), v.LandpageIosAppstoreClick) //落地页-商店包
f.SetCellValue(sheetName, axis(), v.LandpageIosAthorClick) //落地页-企业签
}
f.SetActiveSheet(idx)
buf := new(bytes.Buffer)
if err := f.Write(buf); err != nil {
return nil, err
}
return buf, nil
}
func DailyStatInfoAccumulate(base, value *proto.DailyStatInfo) {
base.TotalNewCount += value.TotalNewCount
base.NewDirectCount += value.NewDirectCount
base.NewFissionCount += value.NewFissionCount
base.NewDeductionCount += value.NewDeductionCount
base.NewNaturalCount += value.NewNaturalCount
base.NewWebCount += value.NewWebCount
base.NewDistrictCount += value.NewDistrictCount
base.NewNormalCount += value.NewNormalCount
base.AndroidAddCount += value.AndroidAddCount
base.IosAddCount += value.IosAddCount
base.H5FormalCount += value.H5FormalCount
base.H5AddCount += value.H5AddCount
base.H5PcAddCount += value.H5PcAddCount
base.H5IosAddCount += value.H5IosAddCount
base.H5AndroidAddCount += value.H5AndroidAddCount
base.TotalVisitCount += value.TotalVisitCount
base.DirectVisitCount += value.DirectVisitCount
base.FissionVisitCount += value.FissionVisitCount
base.NaturalVisitCount += value.NaturalVisitCount
base.TodayDistrictVisitCount += value.TodayDistrictVisitCount
base.TodayWebVisitCount += value.TodayWebVisitCount
base.TodayNormalVisitCount += value.TodayNormalVisitCount
base.TodayIosVisitCount += value.TodayIosVisitCount
base.TodayAndroidVisitCount += value.TodayAndroidVisitCount
base.TodayH5VisitCount += value.TodayH5VisitCount
base.TodayH5PcVisitCount += value.TodayH5PcVisitCount
base.TodayH5IosVisitCount += value.TodayH5IosVisitCount
base.TodayH5AndroidVisitCount += value.TodayH5AndroidVisitCount
base.LightVisitCount += value.LightVisitCount
base.LightDirectVisitCount += value.LightDirectVisitCount
base.LightFissionVisitCount += value.LightFissionVisitCount
base.LightDeductionVisitCount += value.LightDeductionVisitCount
base.LightNaturalVisitCount += value.LightNaturalVisitCount
base.LightWebVisitCount += value.LightWebVisitCount
base.LightDistrictVisitCount += value.LightDistrictVisitCount
base.LightNormalVisitCount += value.LightNormalVisitCount
base.LightAddCount += value.LightAddCount
base.LightGuestAddCount += value.LightGuestAddCount
base.LightFormalAddCount += value.LightFormalAddCount
base.LightDirectCount += value.LightDirectCount
base.LightFissionCount += value.LightFissionCount
base.LightDeductionCount += value.LightDeductionCount
base.LightNaturalCount += value.LightNaturalCount
base.LightWebCount += value.LightWebCount
base.LightDistrictCount += value.LightDistrictCount
base.LightNormalCount += value.LightNormalCount
base.TotalAmount += value.TotalAmount
base.DirectAmount += value.DirectAmount
base.FissionAmount += value.FissionAmount
base.DeductionAmount += value.DeductionAmount
base.NaturalAmount += value.NaturalAmount
base.TodayDistrictRecharge += value.TodayDistrictRecharge
base.TodayWebRecharge += value.TodayWebRecharge
base.TodayNormalRecharge += value.TodayNormalRecharge
base.TodayAndroidRecharge += value.TodayAndroidRecharge
base.TodayIosRecharge += value.TodayIosRecharge
base.LightRechargeCount += value.LightRechargeCount
base.LightDirectRechargeCount += value.LightDirectRechargeCount
base.LightFissionRechargeCount += value.LightFissionRechargeCount
base.LightDeductionRechargeCount += value.LightDeductionRechargeCount
base.LightNaturalRechargeCount += value.LightNaturalRechargeCount
base.LightWebRechargeCount += value.LightWebRechargeCount
base.LightDistrictRechargeCount += value.LightDistrictRechargeCount
base.LightNormalRechargeCount += value.LightNormalRechargeCount
base.LightRechargeAmount += value.LightRechargeAmount
base.LightDirectRechargeAmount += value.LightDirectRechargeAmount
base.LightFissionRechargeAmount += value.LightFissionRechargeAmount
base.LightDeductionRechargeAmount += value.LightDeductionRechargeAmount
base.LightNaturalRechargeAmount += value.LightNaturalRechargeAmount
base.LightWebRechargeAmount += value.LightWebRechargeAmount
base.LightDistrictRechargeAmount += value.LightDistrictRechargeAmount
base.LightNormalRechargeAmount += value.LightNormalRechargeAmount
base.TodayH5Recharge += value.TodayH5Recharge
base.TodayH5FormalRecharge += value.TodayH5FormalRecharge
base.TodayH5PcRecharge += value.TodayH5PcRecharge
base.TodayH5IosRecharge += value.TodayH5IosRecharge
base.TodayH5AndroidRecharge += value.TodayH5AndroidRecharge
base.VipPayByMoney += value.VipPayByMoney
base.PayForAthor += value.TotalAmount - value.VipPayByMoney
base.RepeatAmount += value.RepeatAmount
base.RepeatVipMoney += value.RepeatVipMoney
base.GameRechageAmount += value.GameRechageAmount
base.GameWithdrawAmount += value.GameWithdrawAmount
base.OldRechargeAmount += value.OldRechargeAmount
base.OldVipRecharge += value.OldVipRecharge
base.OldCoinRecharge += value.OldCoinRecharge
base.NewRechargeCount += value.NewRechargeCount
base.NewRechargeAmount += value.NewRechargeAmount
base.NewVipRecharge += value.NewVipRecharge
base.NewCoinRecharge += value.NewCoinRecharge
base.SuccessRechargeOrder += value.SuccessRechargeOrder
base.TotalRechargeOrder += value.TotalRechargeOrder
base.Day1RetainRate += value.Day1RetainRate
base.Day3RetainRate += value.Day3RetainRate
base.Day7RetainRate += value.Day7RetainRate
base.LandpageAndroidClick += value.LandpageAndroidClick
base.LandpageIosAppstoreClick += value.LandpageIosAppstoreClick
base.LandpageIosAthorClick += value.LandpageIosAthorClick
base.LandpageIosH5Click += value.LandpageIosH5Click
if base.NewCoinRecharge < 0 {
base.NewCoinRecharge = 0
}
if base.OldVipRecharge < 0 {
base.OldVipRecharge = 0
}
if base.OldCoinRecharge < 0 {
base.OldCoinRecharge = 0
}
if base.TotalRechargeOrder > 0 {
base.RechargeSuccessRate = fmt.Sprintf("%.3f", float64(base.SuccessRechargeOrder)/float64(base.TotalRechargeOrder))
}
if base.TotalNewCount != 0 {
base.NewRechargeRate, _ = strconv.ParseFloat(fmt.Sprintf("%.2f", (float64(base.NewRechargeCount)/float64(base.TotalNewCount))*100), 64)
}
if base.TotalVisitCount != 0 {
base.VisitRechargeRate, _ = strconv.ParseFloat(fmt.Sprintf("%.2f", (float64(base.SuccessRechargeOrder)/float64(base.TotalVisitCount))*100), 64)
}
if base.SuccessRechargeOrder != 0 {
base.ARPPU = base.TotalAmount / base.SuccessRechargeOrder // 金额
}
if base.TotalNewCount != 0 {
base.ARPU = base.TotalAmount / base.TotalNewCount // 金额
}
}
func DailyStatToInfo(src *dailystatmod.DailyStatMod) *proto.DailyStatInfo {
src.CorrectValidData()
ret := &proto.DailyStatInfo{
SumDate: src.SumDate,
AppId: src.AppId,
AppName: "", // 外面赋值
TotalNewCount: src.NewTotalCount,
NewDirectCount: src.NewDirectCount,
NewFissionCount: src.NewFissionCount,
NewDeductionCount: src.NewDeductionCount,
NewNaturalCount: src.NewNaturalCount,
NewWebCount: src.NewWebCount,
NewDistrictCount: src.NewDistrictCount,
NewNormalCount: src.NewNormalCount,
LightAddCount: src.LightAddCount,
LightGuestAddCount: src.LightGuestAddCount,
LightFormalAddCount: src.LightFormalAddCount,
LightDirectCount: src.LightDirectCount,
LightFissionCount: src.LightFissionCount,
LightDeductionCount: src.LightDeductionCount,
LightNaturalCount: src.LightNaturalCount,
LightWebCount: src.LightWebCount,
LightDistrictCount: src.LightDistrictCount,
LightNormalCount: src.LightNormalCount,
AndroidAddCount: src.AndroidAddCount,
IosAddCount: src.IosAddCount,
H5FormalCount: src.H5FormalCount,
H5AddCount: src.H5AddCount,
H5PcAddCount: src.H5PcAddCount,
H5IosAddCount: src.H5IosAddCount,
H5AndroidAddCount: src.H5AndroidAddCount,
TotalVisitCount: src.TotalVisitCount,
DirectVisitCount: src.DirectVisitCount,
FissionVisitCount: src.FissionVisitCount,
NaturalVisitCount: src.NaturalVisitCount,
TodayDistrictVisitCount: src.TodayDistrictVisitCount,
TodayWebVisitCount: src.TodayWebVisitCount,
TodayNormalVisitCount: src.TodayNormalVisitCount,
TodayIosVisitCount: src.TodayIosVisitCount,
TodayAndroidVisitCount: src.TodayAndroidVisitCount,
LightVisitCount: src.LightVisitCount,
LightDirectVisitCount: src.LightDirectVisitCount,
LightFissionVisitCount: src.LightFissionVisitCount,
LightDeductionVisitCount: src.LightDeductionVisitCount,
LightNaturalVisitCount: src.LightNaturalVisitCount,
LightWebVisitCount: src.LightWebVisitCount,
LightDistrictVisitCount: src.LightDistrictVisitCount,
LightNormalVisitCount: src.LightNormalVisitCount,
TodayH5VisitCount: src.TodayH5VisitCount,
TodayH5PcVisitCount: src.TodayH5PcVisitCount,
TodayH5IosVisitCount: src.TodayH5IosVisitCount,
TodayH5AndroidVisitCount: src.TodayH5AndroidVisitCount,
TotalAmount: src.TotalAmount,
DirectAmount: src.DirectAmount,
FissionAmount: src.FissionAmount,
DeductionAmount: src.DeductionAmount,
NaturalAmount: src.NaturalAmount,
TodayDistrictRecharge: src.TodayDistrictRecharge,
TodayWebRecharge: src.TodayWebRecharge,
TodayNormalRecharge: src.TodayNormalRecharge,
TodayAndroidRecharge: src.TodayAndroidRecharge,
TodayIosRecharge: src.TodayIosRecharge,
LightRechargeCount: src.LightRechargeCount,
LightDirectRechargeCount: src.LightDirectRechargeCount,
LightFissionRechargeCount: src.LightFissionRechargeCount,
LightDeductionRechargeCount: src.LightDeductionRechargeCount,
LightNaturalRechargeCount: src.LightNaturalRechargeCount,
LightWebRechargeCount: src.LightWebRechargeCount,
LightDistrictRechargeCount: src.LightDistrictRechargeCount,
LightNormalRechargeCount: src.LightNormalRechargeCount,
LightRechargeAmount: src.LightRechargeAmount,
LightDirectRechargeAmount: src.LightDirectRechargeAmount,
LightFissionRechargeAmount: src.LightFissionRechargeAmount,
LightDeductionRechargeAmount: src.LightDeductionRechargeAmount,
LightNaturalRechargeAmount: src.LightNaturalRechargeAmount,
LightWebRechargeAmount: src.LightWebRechargeAmount,
LightDistrictRechargeAmount: src.LightDistrictRechargeAmount,
LightNormalRechargeAmount: src.LightNormalRechargeAmount,
TodayH5Recharge: src.TodayH5Recharge,
TodayH5FormalRecharge: src.TodayH5FormalRecharge,
TodayH5PcRecharge: src.TodayH5PcRecharge,
TodayH5IosRecharge: src.TodayH5IosRecharge,
TodayH5AndroidRecharge: src.TodayH5AndroidRecharge,
ARPU: 0, // 下面赋值
ARPPU: 0, // 下面赋值
VipPayByMoney: src.VipPayByMoney,
PayForAthor: 0, // 下面赋值
RepeatAmount: src.RepeatAmount,
RepeatVipMoney: src.RepeatVipMoney,
GameRechageAmount: src.GameRechageAmount,
GameWithdrawAmount: src.GameWithdrawAmount,
OldRechargeAmount: 0, // 下面赋值
OldVipRecharge: 0, // 下面赋值
OldCoinRecharge: 0, // 下面赋值
NewRechargeCount: src.NewRechargeCount,
NewRechargeAmount: src.NewRechargeAmount,
NewVipRecharge: src.NewVipPayMoney,
NewCoinRecharge: 0, // 下面赋值
NewRechargeRate: 0, // 下面赋值
VisitRechargeRate: 0, // 下面赋值
SuccessRechargeOrder: src.SuccessRechargeOrder,
TotalRechargeOrder: src.TotalRechargeOrder,
RechargeSuccessRate: "", //下面赋值
Day1RetainRate: src.Day1RetainRate,
Day3RetainRate: src.Day3RetainRate,
Day7RetainRate: src.Day7RetainRate,
LandpageAndroidClick: src.LandpageAndroidClick,
LandpageIosAppstoreClick: src.LandpageIosAppstoreClick,
LandpageIosAthorClick: src.LandpageIosAthorClick,
LandpageIosH5Click: src.LandpageIosH5Click,
UpdatedAt: src.UpdatedAt,
}
if ret.TotalNewCount == 0 {
ret.TotalNewCount = ret.NewDirectCount + ret.NewFissionCount + ret.NewDeductionCount
}
if ret.TotalVisitCount == 0 {
ret.TotalVisitCount = ret.DirectVisitCount + ret.FissionVisitCount + ret.DeductionVisitCount
}
if ret.TotalAmount == 0 {
ret.TotalAmount = ret.DirectAmount + ret.FissionAmount + ret.DeductionAmount
}
ret.PayForAthor = ret.TotalAmount - ret.VipPayByMoney
ret.OldRechargeAmount = ret.TotalAmount - ret.NewRechargeAmount
ret.OldVipRecharge = ret.VipPayByMoney - ret.NewVipRecharge
ret.OldCoinRecharge = ret.OldRechargeAmount - ret.OldVipRecharge
ret.NewCoinRecharge = ret.NewRechargeAmount - ret.NewVipRecharge
if ret.NewCoinRecharge < 0 {
ret.NewCoinRecharge = 0
}
if ret.OldVipRecharge < 0 {
ret.OldVipRecharge = 0
}
if ret.OldCoinRecharge < 0 {
ret.OldCoinRecharge = 0
}
if ret.TotalRechargeOrder > 0 {
ret.RechargeSuccessRate = fmt.Sprintf("%.3f", float64(ret.SuccessRechargeOrder)/float64(ret.TotalRechargeOrder))
}
if ret.TotalNewCount != 0 {
ret.ARPU = ret.TotalAmount / ret.TotalNewCount // 金额
ret.NewRechargeRate, _ = strconv.ParseFloat(fmt.Sprintf("%.2f", (float64(ret.NewRechargeCount)/float64(ret.TotalNewCount))*100), 64)
}
if ret.TotalVisitCount != 0 {
ret.VisitRechargeRate, _ = strconv.ParseFloat(fmt.Sprintf("%.2f", (float64(ret.SuccessRechargeOrder)/float64(ret.TotalVisitCount))*100), 64)
}
if ret.SuccessRechargeOrder != 0 {
ret.ARPPU = ret.TotalAmount / ret.SuccessRechargeOrder // 金额
}
return ret
}
func DailyHourStatToInfo(src *dailyhourstatmod.DailyHourStat) *proto.DailyHourStatInfo {
src.CorrectValidData()
ret := &proto.DailyHourStatInfo{
SumDate: src.SumDate,
AppId: src.AppId,
SumHour: src.SumHour,
AppName: "",
NewTotalCount: src.NewTotalCount,
NewDirectCount: src.NewDirectCount,
NewFissionCount: src.NewFissionCount,
NewDeductionCount: src.NewDeductionCount,
NewNaturalCount: src.NewNaturalCount,
NewWebCount: src.NewWebCount,
NewNormalCount: src.NewNormalCount,
NewDistrictCount: src.NewDistrictCount,
NewAndroidCount: src.NewAndroidCount,
NewIosCount: src.NewIosCount,
NewLightCount: src.NewLightCount,
NewLightGuestCount: src.NewLightGuestCount,
NewLightFormalCount: src.NewLightFormalCount,
NewLightDirectCount: src.NewLightDirectCount,
NewLightFissionCount: src.NewLightFissionCount,
NewLightDeductionCount: src.NewLightDeductionCount,
NewLightNaturalCount: src.NewLightNaturalCount,
NewLightWebCount: src.NewLightWebCount,
NewLightNormalCount: src.NewLightNormalCount,
NewLightDistrictCount: src.NewLightDistrictCount,
NewH5Count: src.NewH5Count,
NewH5FormalCount: src.NewH5FormalCount,
NewH5PcCount: src.NewH5PcCount,
NewH5IosCount: src.NewH5IosCount,
NewH5AndroidCount: src.NewH5AndroidCount,
TotalVisitCount: src.TotalVisitCount,
DirectVisitCount: src.DirectVisitCount,
FissionVisitCount: src.FissionVisitCount,
NaturalVisitCount: src.NaturalVisitCount,
WebVisitCount: src.WebVisitCount,
NormalVisitCount: src.NormalVisitCount,
DistrictVisitCount: src.DistrictVisitCount,
IosVisitCount: src.IosVisitCount,
AndroidVisitCount: src.AndroidVisitCount,
LightVisitCount: src.LightVisitCount,
LightVisitDirectCount: src.LightVisitDirectCount,
LightVisitFissionCount: src.LightVisitFissionCount,
LightVisitDeductionCount: src.LightVisitDeductionCount,
LightVisitNaturalCount: src.LightVisitNaturalCount,
LightVisitWebCount: src.LightVisitWebCount,
LightVisitNormalCount: src.LightVisitNormalCount,
LightVisitDistrictCount: src.LightVisitDistrictCount,
H5VisitCount: src.H5VisitCount,
H5PcVisitCount: src.H5PcVisitCount,
H5IosVisitCount: src.H5IosVisitCount,
H5AndroidVisitCount: src.H5AndroidVisitCount,
TotalAmount: src.TotalAmount,
SuccessRechargeOrder: src.SuccessRechargeOrder,
DirectAmount: src.DirectAmount,
DirectAmountCount: src.DirectAmountCount,
FissionAmount: src.FissionAmount,
FissionAmountCount: src.FissionAmountCount,
NaturalAmount: src.NaturalAmount,
NaturalAmountCount: src.NaturalAmountCount,
DeductionAmount: src.DeductionAmount,
DeductionAmountCount: src.DeductionAmountCount,
WebAmount: src.WebAmount,
WebAmountCount: src.WebAmountCount,
NormalAmount: src.NormalAmount,
NormalAmountCount: src.NormalAmountCount,
DistrictAmount: src.DistrictAmount,
DistrictAmountCount: src.DistrictAmountCount,
AndroidAmount: src.AndroidAmount,
AndroidAmountCount: src.AndroidAmountCount,
IosAmount: src.IosAmount,
IosAmountCount: src.IosAmountCount,
LightAmount: src.LightAmount,
LightAmountCount: src.LightAmountCount,
LightDirectAmount: src.LightDirectAmount,
LightDirectAmountCount: src.LightDirectAmountCount,
LightFissionAmount: src.LightFissionAmount,
LightFissionAmountCount: src.LightFissionAmountCount,
LightDeductionAmount: src.LightDeductionAmount,
LightDeductionAmountCount: src.LightDeductionAmountCount,
LightNaturalAmount: src.LightNaturalAmount,
LightNaturalAmountCount: src.LightNaturalAmountCount,
LightWebAmount: src.LightWebAmount,
LightWebAmountCount: src.LightWebAmountCount,
LightNormalAmount: src.LightNormalAmount,
LightNormalAmountCount: src.LightNormalAmountCount,
LightDistrictAmount: src.LightDistrictAmount,
LightDistrictAmountCount: src.LightDistrictAmountCount,
H5Amount: src.H5Amount,
H5AmountCount: src.H5AmountCount,
H5FormalAmount: src.H5FormalAmount,
H5FormalAmountCount: src.H5FormalAmountCount,
H5PcAmount: src.H5PcAmount,
H5PcAmountCount: src.H5PcAmountCount,
H5IosAmount: src.H5IosAmount,
H5IosAmountCount: src.H5IosAmountCount,
H5AndroidAmount: src.H5AndroidAmount,
H5AndroidAmountCount: src.H5AndroidAmountCount,
RepeatAmount: src.RepeatAmount,
RepeatAmountCount: src.RepeatAmountCount,
NewRechargeAmount: src.NewRechargeAmount,
NewRechargeCount: src.NewRechargeCount,
NewCoinRecharge: 0,
NewVipRecharge: 0,
OldCoinRecharge: 0,
OldVipRecharge: 0,
OldRechargeAmount: 0,
VipPayByMoney: src.VipPayByMoney,
VipPayByAmount: src.VipPayByAmount,
VipRepeatAmount: src.VipRepeatAmount,
TotalRechargeOrder: src.TotalRechargeOrder,
RechargeSuccessRate: "",
GameRechageAmount: src.GameRechageAmount,
GameWithdrawAmount: src.GameWithdrawAmount,
LandpageAndroidClick: src.LandpageAndroidClick,
LandpageIosAppstoreClick: src.LandpageIosAppstoreClick,
LandpageIosAthorClick: src.LandpageIosAthorClick,
LandpageIosH5Click: src.LandpageIosH5Click,
UpdatedAt: src.UpdatedAt,
}
if ret.NewTotalCount == 0 {
ret.NewTotalCount = ret.NewDirectCount + ret.NewFissionCount + ret.NewDeductionCount
}
if ret.TotalVisitCount == 0 {
ret.TotalVisitCount = ret.DirectVisitCount + ret.FissionVisitCount
}
if ret.TotalAmount == 0 {
ret.TotalAmount = ret.DirectAmount + ret.FissionAmount + ret.DeductionAmount
}
ret.VipPayByAmount = ret.TotalAmount - ret.VipPayByMoney
ret.OldRechargeAmount = ret.TotalAmount - ret.NewRechargeAmount
ret.OldVipRecharge = ret.VipPayByMoney - ret.NewVipRecharge
ret.OldCoinRecharge = ret.OldRechargeAmount - ret.OldVipRecharge
ret.NewCoinRecharge = ret.NewRechargeAmount - ret.NewVipRecharge
if ret.NewCoinRecharge < 0 {
ret.NewCoinRecharge = 0
}
if ret.OldVipRecharge < 0 {
ret.OldVipRecharge = 0
}
if ret.OldCoinRecharge < 0 {
ret.OldCoinRecharge = 0
}
if ret.TotalRechargeOrder > 0 {
ret.RechargeSuccessRate = fmt.Sprintf("%.3f", float64(ret.SuccessRechargeOrder)/float64(ret.TotalRechargeOrder))
}
return ret
}
+179
View File
@@ -0,0 +1,179 @@
package proxy
import (
"bytes"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"stat/common/httpUtils"
"strings"
)
const (
apiDomain = "http://file.dhuqh.com/file/upload"
)
var (
header = map[string]string{"Content-Type": "application/json"}
)
const (
Success = 200
)
func UploadImg(fileName string, data []byte, domain string) (pathStr string, err error) {
r := objreq{
Name: fileName,
Data: base64.StdEncoding.EncodeToString(data),
}
jsonbytes, err := json.Marshal(r)
if err != nil {
return
}
if len(domain) == 0 {
domain = apiDomain
}
url := domain
if !strings.Contains(domain, "/file/upload") {
url = domain + "/file/upload"
}
resp, err := httpUtils.Post(url, header, bytes.NewReader(jsonbytes))
if err != nil {
return
}
var or objresp
if err = json.Unmarshal(resp.Data, &or); err != nil {
return
}
if or.Code != Success {
err = fmt.Errorf("上传失败:%s", or.Msg)
return
}
pathStr = or.Object.Name
return
}
func fileServer() (url string) {
return "http://ts.dhuqh.com"
}
func PostOk(baseInfo *BaseInfo, domain string) (result *VideoOkResult, err error) {
result = &VideoOkResult{}
paramJson, err := json.Marshal(baseInfo)
if err != nil {
return
}
if len(domain) == 0 {
domain = fileServer()
}
url := domain
if !strings.Contains(domain, "/video/shard/info") {
url = domain + "/video/shard/info"
}
fr, err := httpUtils.Post(url, header, bytes.NewReader(paramJson))
if err != nil {
return
}
if err = json.Unmarshal(fr.Data, result); err != nil {
return nil, err
}
return
}
func UploadVideo(domain string, body *ShardBody) (result *Result, err error) {
if body == nil {
return nil, errors.New("no video to upload")
}
if len(domain) == 0 {
domain = fileServer()
}
url := domain
if !strings.Contains(domain, "/video/shard/body") {
url = domain + "/video/shard/body"
}
buf, err := json.Marshal(body)
if err != nil {
return nil, err
}
resp, err := httpUtils.Post(url, header, bytes.NewBuffer(buf))
if err != nil {
return nil, err
}
result = new(Result)
if err = json.Unmarshal(resp.Data, result); err != nil {
return nil, err
}
return
}
func PostSearch(param *MediaSearchReq, domain string) (result *MediaSearchResp, err error) {
result = &MediaSearchResp{}
paramJson, err := json.Marshal(param)
if err != nil {
return
}
if len(domain) == 0 {
domain = fileServer()
}
url := domain
if !strings.Contains(domain, "/video/search/list") {
url = domain + "/video/search/list"
}
fr, err := httpUtils.Post(url, header, bytes.NewReader(paramJson))
if err != nil {
return
}
if err = json.Unmarshal(fr.Data, result); err != nil {
return nil, err
}
return
}
func PostUpdate(param *MediasUpdateReq, domain string) (result *MediasUpdateResp, err error) {
result = &MediasUpdateResp{}
paramJson, err := json.Marshal(param)
if err != nil {
return
}
if len(domain) == 0 {
domain = fileServer()
}
url := domain
if !strings.Contains(domain, "/video/update") {
url = domain + "/video/update"
}
fr, err := httpUtils.Post(url, header, bytes.NewReader(paramJson))
if err != nil {
return
}
if err = json.Unmarshal(fr.Data, result); err != nil {
return nil, err
}
return
}
func PostDelete(param *MediasDelReq, domain string) (result *MediasDelResp, err error) {
result = &MediasDelResp{}
paramJson, err := json.Marshal(param)
if err != nil {
return
}
if len(domain) == 0 {
domain = fileServer()
}
url := domain
if !strings.Contains(domain, "/video/delete") {
url = domain + "/video/delete"
}
fr, err := httpUtils.Post(url, header, bytes.NewReader(paramJson))
if err != nil {
return
}
if err = json.Unmarshal(fr.Data, result); err != nil {
return nil, err
}
return
}
+150
View File
@@ -0,0 +1,150 @@
package proxy
import "time"
type objreq struct {
Name string `json:"fileName"`
Data string `json:"fileData"`
}
type Obj struct {
Domain string `json:"domain"`
Name string `json:"fileName"`
}
type objresp struct {
Code int `json:"code"`
Object Obj `json:"data"`
Msg string `json:"msg"`
}
type ShardBody struct {
TaskId string `json:"taskId,omitempty"` // 文档md5
Pos int `json:"pos,omitempty"` // 位置
FileData string `json:"fileData,omitempty"` // 数据
Type string `json:"type,omitempty"`
TotalPos int `json:"totalPos,omitempty"` // 分片总数
}
type Data struct {
Id string `json:"id,omitempty"`
VideoUri string `json:"videoUri,omitempty"`
}
type Result struct {
Code int `json:"code,omitempty"` //
Data Data `json:"data,omitempty"` //
Msg string `json:"msg,omitempty"` //
}
type VideoOkResult struct {
Code int `json:"code,omitempty"`
Msg string `json:"msg,omitempty"` //
Data string `json:"data,omitempty"`
}
type BaseInfo struct {
ID string `json:"id,omitempty"`
Title string `json:"title,omitempty"` // 标题
CheckSum string `json:"checkSum,omitempty"` // md5校验码
VideoTime int32 `json:"videTime"` // 影片时长
Publish string `bson:"publish,omitempty"` // 视频发布者
Type string `json:"type"` // 视频:长视频/短视频--时间阈值
Preview int32 `json:"preview"` // 预览普通视频:1:预览 2:
Country string `json:"country"` // 国家 (国产、日本、欧美.... 前端固定)
FirstTag string `json:"firstTag,omitempty"` // 一级标签
SecondTags []string `json:"secondTags,omitempty"` // 二级标签标签
CoverImg []string `json:"coverImg,omitempty"` // 封面
PreFileName string `json:"preFileName,omitempty"` // 预览文件
Status string `json:"status,omitempty"` // 当前状态
Size int64 `json:"size"` // 文件size,单位byte
Filename string `json:"filename,omitempty"` // 文件名称
Desc string `json:"desc,omitempty"` // 详细描述
Directors []string `json:"director,omitempty"` // 导演
Actors []string `json:"actors,omitempty"` // 演员
Studio string `json:"studio,omitempty"` // 工作室
Bango string `json:"bango,omitempty"` // 番号,唯一
Via string `json:"via,omitempty"` // 来源
Remark string `json:"remark,omitempty"` // 备注
AddedTime string `json:"addedTime,omitempty"` // 上架时间
UploadTime string `json:"uploadTime,omitempty"` // 上传时间
}
type VideoUpdate struct {
ID string `json:"id,omitempty"`
Title *string `json:"title,omitempty"` // 标题
Publish *string `bson:"publish,omitempty"` // 视频发布者
Type *string `json:"type"` // 视频:长视频/短视频--时间阈值
Preview *int32 `json:"preview"` // 预览:1:预览 2:普通视频
Country *string `json:"country"` // 国家 (国产、日本、欧美.... 前端固定)
FirstTag *string `json:"firstTag,omitempty"` // 一级标签
SecondTags *[]string `json:"secondTags,omitempty"` // 二级标签标签
CoverImg *[]string `json:"coverImg,omitempty"` // 封面
PreFileName *string `json:"preFileName,omitempty"` // 预览文件
Status *string `json:"status,omitempty"` // 当前状态
Desc *string `json:"desc,omitempty"` // 详细描述
Directors *[]string `json:"director,omitempty"` // 导演
Actors *[]string `json:"actors,omitempty"` // 演员
Studio *string `json:"studio,omitempty"` // 工作室
Bango *string `json:"bango,omitempty"` // 番号,唯一
Via *string `json:"via,omitempty"` // 来源
Remark *string `json:"remark,omitempty"` // 备注
AddedTime *string `json:"addedTime,omitempty"` // 上架时间
UploadTime *string `json:"uploadTime,omitempty"` // 上传时间
}
type VideoSearch struct {
ID string `json:"id,omitempty"`
Title *string `json:"title,omitempty"` // 标题
Publish *string `bson:"publish,omitempty"` // 视频发布者
Type *string `json:"type"` // 视频:长视频/短视频--时间阈值
Preview *int32 `json:"preview"` // 预览:1:预览 2:普通视频
Country *string `json:"country"` // 国家 (国产、日本、欧美.... 前端固定)
FirstTag *string `json:"firstTag,omitempty"` // 一级标签
SecondTags *[]string `json:"secondTags,omitempty"` // 二级标签标签
Status *string `json:"status,omitempty"` // 当前状态
Directors *[]string `json:"director,omitempty"` // 导演
Actors *[]string `json:"actors,omitempty"` // 演员
Studio *string `json:"studio,omitempty"` // 工作室
Bango *string `json:"bango,omitempty"` // 番号,唯一
Via *string `json:"via,omitempty"` // 来源
Remark *string `json:"remark,omitempty"` // 备注
AddedTime *string `json:"addedTime,omitempty"` // 上架时间
UploadTime *string `json:"uploadTime,omitempty"` // 上传时间
Start *time.Time `json:"start"` // 开始时间
End *time.Time `json:"end"` // 结束时间
}
type UploadVideoBodyResp struct {
Id string `json:"id"` // 文件服返回的上传文件Id
VideoUri string `json:"videoUri"` // 文件服返回的视频uri
}
type MediaSearchReq struct {
PageNum int64 `json:"pageNum"`
PageSize int64 `json:"pageSize"`
Param *VideoSearch `json:"param"`
}
//MediaSearchReq 媒体搜索请求返回
type MediaSearchResp struct {
Total int64 `json:"total"`
List []BaseInfo `json:"list"`
}
type MediasDelReq struct {
Ids []string `json:"id"`
}
//MediasDelResp 请求批量删除媒体回复
type MediasDelResp struct {
}
type MediasUpdateReq struct {
Ids []string `json:"id"`
VideoUpdate
}
//MediasUpdateResp 请求批量更新媒体回复
type MediasUpdateResp struct {
}
+145
View File
@@ -0,0 +1,145 @@
package storage
import (
"bytes"
"context"
"encoding/base64"
"errors"
"fmt"
"mime/multipart"
"stat/service/storage/proxy"
"strconv"
"sync"
)
var (
buffer = bytes.Buffer{}
mtx sync.Mutex
)
// web后台上传使用, form-data方式上传
func UploadImage(ctx context.Context, f *multipart.FileHeader, domain string) (path string, err error) {
if f == nil {
return "", errors.New("file is nil!")
}
var file multipart.File
if file, err = f.Open(); err != nil {
return
}
defer file.Close()
mtx.Lock()
defer mtx.Unlock()
buffer.Reset()
if _, err = buffer.ReadFrom(file); err != nil {
return
}
return proxy.UploadImg(f.Filename, buffer.Bytes(), domain)
}
func UploadVideoBody(ctx context.Context, form *multipart.Form, domain string) (*proxy.Result, error) {
if form == nil {
return nil, errors.New("multipart from is nil")
}
sb, err := makeShardBodyByForm(form)
if err != nil {
return nil, err
}
return proxy.UploadVideo(domain, sb)
}
func VideoBody(ctx context.Context, taskId, typ, domain string, pos int, data []byte) (*proxy.Result, error) {
body := &proxy.ShardBody{
TaskId: taskId,
Pos: pos,
FileData: base64.StdEncoding.EncodeToString(data),
Type: typ,
}
if typ == "1" {
body.Type = "AV"
} else if typ == "2" {
body.Type = "SP"
}
return proxy.UploadVideo(domain, body)
}
func UploadVideoOK(ctx context.Context, in *proxy.BaseInfo, domain string) (*proxy.VideoOkResult, error) {
if in.Type == "1" {
in.Type = "AV"
} else if in.Type == "2" {
in.Type = "SP"
}
return proxy.PostOk(in, domain)
}
func makeShardBodyByForm(form *multipart.Form) (*proxy.ShardBody, error) {
poss := form.Value["pos"]
taskIds := form.Value["taskId"]
types := form.Value["type"]
fs := form.File["file"]
totalPoss := form.Value["totalChunks"]
var (
pos, total int
taskId string
typ string
f *multipart.FileHeader
err error
)
if len(fs) == 0 || fs[0] == nil {
return nil, errors.New("no file")
}
f = fs[0]
if len(poss) == 0 || poss[0] == "" {
return nil, errors.New("no pos")
}
if pos, err = strconv.Atoi(poss[0]); err != nil {
return nil, fmt.Errorf("invalid pos")
}
if len(totalPoss) == 0 || totalPoss[0] == "" {
return nil, errors.New("no totalPos")
}
if total, err = strconv.Atoi(totalPoss[0]); err != nil {
return nil, fmt.Errorf("invalid totalPos")
}
if len(taskIds) == 0 || taskIds[0] == "" {
return nil, errors.New("no pos")
}
taskId = taskIds[0]
if len(types) == 0 || types[0] == "" {
return nil, errors.New("no type")
}
if types[0] == "1" {
typ = "AV"
} else if types[0] == "2" {
typ = "SP"
} else {
typ = types[0]
}
file, ne := f.Open()
if ne != nil {
return nil, ne
}
defer file.Close()
mtx.Lock()
defer mtx.Unlock()
buffer.Reset()
if _, err = buffer.ReadFrom(file); err != nil {
return nil, err
}
resp := &proxy.ShardBody{
TaskId: taskId,
Pos: pos,
FileData: base64.StdEncoding.EncodeToString(buffer.Bytes()),
Type: typ,
TotalPos: total,
}
return resp, nil
}
+59
View File
@@ -0,0 +1,59 @@
package userinviteser
import (
"context"
"stat/api/proto"
"stat/common/log"
"stat/models/channelmod"
"stat/models/downloadinfo"
"stat/models/registermod"
"go.mongodb.org/mongo-driver/bson"
)
func UserDistictInfo(ctx context.Context, param proto.UserDistrictReq) (data interface{}, err error) {
dictrictInfo := proto.UserDistrictInfo{}
dictrictInfo.IsDirect = true
dictrictInfo.DistrictCode = param.DistrictCode
if param.IP != "" {
if param.DistrictCode != "" {
channelInfo, _ := channelmod.FindOneChannelInfo(ctx, bson.M{channelmod.FN.ChannelInfo.Channel: param.DistrictCode})
if channelInfo == nil || channelInfo.Channel != param.DistrictCode {
param.DistrictCode = ""
}
}
if param.DistrictCode == "" {
// 根据IP查询是否下载
data, _ := downloadinfo.FindOneChannelIP(ctx, param.AppId, param.IP, param.SysType)
if data.DC != "" {
log.Info("channel info by IP ", log.Any("IP", param.IP), log.Any("app", param.AppId), log.Any("dc", data.DC))
dictrictInfo.IsDirect = true
dictrictInfo.DistrictCode = data.DC
}
if data.PC != "" && param.PromSeqe == "" {
log.Info("channel info by IP ", log.Any("IP", param.IP), log.Any("app", param.AppId), log.Any("dc", data.DC))
dictrictInfo.IsDirect = false
dictrictInfo.ParentPromCode = data.PC
}
}
}
return dictrictInfo, nil
}
func UserDistrictCodeInfo(ctx context.Context, param proto.UserDistrictCodeReq) (data interface{}, err error) {
resp := proto.UserDistrictCodeResp{
UserList: make([]proto.UserDistrictCodeInfo, 0),
}
for _, userId := range param.UserIds {
registerInfo, _ := registermod.GetRegisterInfo(ctx, userId, param.AppId)
codeInfo := proto.UserDistrictCodeInfo{
UserId: registerInfo.UserId,
RegisterTime: registerInfo.RegisterTime,
DistrictCode: registerInfo.DistrictCode,
}
resp.UserList = append(resp.UserList, codeInfo)
}
return resp, nil
}
+141
View File
@@ -0,0 +1,141 @@
package versionSer
import (
"bufio"
"bytes"
"context"
"fmt"
"io"
"os"
"regexp"
"stat/api/proto"
"stat/common/utils"
downloadinfo "stat/models/downloadinfo"
"stat/models/versionMod"
"stat/service/cacheSer"
"stat/statg"
"time"
"github.com/xxjwxc/public/myglobal/snowflake"
)
func GenerateConfigFile(origin, target, configFile, DC, PC string) ([]byte, string) {
if len(DC) > 0 && len(PC) > 0 {
node, err := snowflake.NewNode(1)
if err == nil {
id := node.Generate()
target = fmt.Sprintf("%s?dc=%s&amp;pc=%s&amp;UID=%d", target, DC, PC, id)
}
} else if len(DC) > 0 {
node, err := snowflake.NewNode(1)
if err == nil {
id := node.Generate()
target = fmt.Sprintf("%s?dc=%s&amp;UID=%d", target, DC, id)
}
} else if len(PC) > 0 {
node, err := snowflake.NewNode(1)
if err == nil {
id := node.Generate()
target = fmt.Sprintf("%s?pc=%s&amp;UID=%d", target, PC, id)
}
} else {
node, err := snowflake.NewNode(1)
if err == nil {
id := node.Generate()
target = fmt.Sprintf("%s?UID=%d", target, id)
}
}
target = fmt.Sprintf("%s&amp;isLight=true", target)
output, _, err := readFile("conf/"+configFile, origin, target)
if err != nil {
output, _ = os.ReadFile("conf/" + configFile)
}
return output, configFile
}
func GetAppConfig(appId int32) (origin, target, config string) {
endpoint := statg.GlobalAppIdEndpoints[appId]
if endpoint != nil {
origin = endpoint.H5Origin
target = endpoint.H5Target
config = endpoint.H5Config
}
return
}
// PageButtonClick 落地页点击下载返回最新的下载链接
func GetH5Config(ctx context.Context, req *proto.H5ConfigReq, userAgent string, ip string) ([]byte, string) {
req.DC = utils.RepairDC(req.DC)
downloadInfo := &downloadinfo.DownloadInfoModel{
AppId: req.AppId,
IP: ip,
PC: req.PC,
DC: req.DC,
SysType: "light",
PackageType: versionMod.Light,
PeerIP: ip,
Domain: req.Domain,
UserAgent: userAgent,
CreatedAt: time.Now(),
}
cacheSer.Writers.LandPageClickWriter.Add(downloadInfo)
origin, target, config := GetAppConfig(req.AppId)
if origin == "" || target == "" || config == "" {
return nil, "madou.mobileconfig"
}
return GenerateConfigFile(origin, target, config, req.DC, req.PC)
}
func readFile(filePath string, origin, target string) ([]byte, bool, error) {
var err error
var data []byte
cacheKey := fmt.Sprintf("h5config#%s", filePath)
if cache, exists := cacheSer.Cache.Get(cacheKey); exists {
if item, ok := cache.([]byte); ok {
data = item
}
}
if data == nil {
data, err = os.ReadFile(filePath)
if err != nil {
return nil, false, err
}
cacheSer.Cache.Set(cacheKey, data, 0)
}
reader := bufio.NewReader(bytes.NewReader(data))
needHandle := false
output := make([]byte, 0)
for {
line, _, err := reader.ReadLine()
if err != nil {
if err == io.EOF {
return output, needHandle, nil
}
return nil, needHandle, err
}
if ok, _ := regexp.Match(origin, line); ok {
reg := regexp.MustCompile(origin)
newByte := reg.ReplaceAll(line, []byte(target))
output = append(output, newByte...)
output = append(output, []byte("\n")...)
if !needHandle {
needHandle = true
}
} else {
output = append(output, line...)
output = append(output, []byte("\n")...)
}
}
return output, needHandle, nil
}
func GetH5ConfigV2(ctx context.Context, req *proto.H5ConfigReq, ip string) proto.H5ConfigV2Resp {
if req.AppId == 694 {
return proto.H5ConfigV2Resp{
URL: "http://awll.yangjiaolian.top/91seman.mobileconfig",
}
}
return proto.H5ConfigV2Resp{}
}
+337
View File
@@ -0,0 +1,337 @@
package versionSer
import (
"context"
"fmt"
"io"
"math/rand"
"os"
"os/exec"
"path/filepath"
"stat/common/crypt"
"stat/common/redis"
"stat/common/utils"
"stat/models/channelmod"
"stat/models/channelpackage"
"stat/models/versionMod"
"stat/statg"
"strings"
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"golang.org/x/exp/slices"
)
func GetChannelApkOutputPath(appId int32, channel string, version string, date time.Time) string {
endpoint := statg.GlobalAppIdEndpoints[appId]
if endpoint == nil {
return ""
}
if endpoint.NameEn == "" {
return ""
}
return fmt.Sprintf(`%s/package/%s_%s_%s-%s.apk`, endpoint.NameEn, endpoint.NameEn, date.Format("20060102150405"), version, channel)
}
func GetPackageBaseDomain(appId int32) []string {
packageUrlString := statg.Conf.URL.PackageUrl
if appId == 999 && len(statg.Conf.URL.BiliPackageUrl) > 0 {
packageUrlString = statg.Conf.URL.BiliPackageUrl
} else if appId == 777 && len(statg.Conf.URL.HuanqiuPackageUrl) > 0 {
packageUrlString = statg.Conf.URL.HuanqiuPackageUrl
} else if appId == 444 && len(statg.Conf.URL.ManhuaPackageUrl) > 0 {
packageUrlString = statg.Conf.URL.ManhuaPackageUrl
}
packageUrlArray := strings.Split(packageUrlString, ";")
return packageUrlArray
}
func ApkReportErrorLinks(ctx context.Context, pack *channelpackage.ChannelPackage, msg []string) {
if time.Since(pack.CheckLink) > channelpackage.ApkCheckLinkDuration {
filter := bson.M{
channelpackage.FN.ChannelPackage.Id: pack.Id,
channelpackage.FN.ChannelPackage.CheckLink: pack.CheckLink,
}
update := bson.M{
"$set": bson.M{
channelpackage.FN.ChannelPackage.CheckLink: time.Now(),
},
}
// 更新成功是生成apk的必要条件
if err := channelpackage.UpdateOneChannelPackage(ctx, filter, update); err != nil {
return
}
// 汇报TG
text := fmt.Sprintf("检测到渠道包链接无法下载:\n")
text += fmt.Sprintf("渠道:【%s】\n", pack.Channel)
text += fmt.Sprintf("应用:【%s,ID=%d】\n", statg.GlobalEndpoints.GetAppShortNameById(pack.AppId), pack.AppId)
text += fmt.Sprintf("版本:【%s】\n", pack.Version)
text += fmt.Sprintf("文件:【%s】\n", pack.FileName)
text += fmt.Sprintf("生成:【%s】\n", pack.GenTime.Format(time.DateTime))
text += fmt.Sprintf("错误: %s\n", msg)
text += fmt.Sprintf("提示: /channel_apk -channel=%s -app=%d", pack.Channel, pack.AppId)
statg.Bot.SendChannelApkMessage(text)
}
}
func ApkCheckLinks(pack *channelpackage.ChannelPackage, domains []string) (bool, []string) {
ctx := context.Background()
links := pack.GetAllLink(domains)
if len(links) <= 0 {
return false, links
}
var msg string
for i := 0; i < len(links); i++ {
link := links[i]
err := CheckPackageLink(ctx, link)
if err != nil {
msg += fmt.Sprintf("【链接:%s】错误:%s\n", link, err.Error())
}
}
return false, links
}
func TryMakeChannelApk(ctx context.Context, appId int32, channel string, version *versionMod.VersionModel, pack *channelpackage.ChannelPackage, reason string, forceMake bool, logBody io.Writer) (*channelpackage.ChannelPackage, error) {
// 检查版本有没有原始链接
if len(version.Link) <= 0 {
return pack, fmt.Errorf("version %d link is empty", version.Id)
}
// 先检查渠道是否存在
remoteFile := GetChannelApkOutputPath(appId, channel, version.VersionNum, time.Now())
chanInfo, err := channelmod.FindOneChannelInfo(ctx, bson.M{
channelmod.FN.ChannelInfo.Channel: channel,
})
if err != nil {
return pack, fmt.Errorf("渠道包生成失败,查询渠道【%s】出错: %v", channel, err)
}
if !chanInfo.Status {
return pack, fmt.Errorf("渠道包生成失败,渠道【%s】已被禁用,请启用该渠道后再次尝试", channel)
}
if !slices.Contains(chanInfo.AppIds, appId) {
return pack, fmt.Errorf("渠道包生成失败,应用【%s, ID=%d】未在渠道【%s】代理应用的列表中【%v】", statg.GlobalEndpoints.GetAppShortNameById(appId), appId, channel, chanInfo.AppIds)
}
// 开始生成
lockCode := rand.Int63()
packID := primitive.NewObjectID()
now := time.Now()
if pack == nil {
pack = &channelpackage.ChannelPackage{
Id: packID,
AppId: appId,
Channel: channel,
Version: version.VersionNum,
FileName: version.Md5,
UrlPath: remoteFile,
Status: channelpackage.ChanApkStatusMaking,
Desc: version.Info,
Reason: reason,
GenError: "第一次生成",
GenCount: 0,
LockCode: lockCode,
LockTime: now,
GenTime: now,
CreatedAt: now,
UpdatedAt: now,
}
// 插入成功是生成apk的必要条件
serr := channelpackage.InsertOneChannelPackage(ctx, pack)
if serr != nil {
return pack, fmt.Errorf("渠道包生成失败,插入渠道包记录失败: \n渠道:【%s】\n应用:【%s,ID=%d】\n错误: %s", channel, statg.GlobalEndpoints.GetAppShortNameById(appId), appId, serr.Error())
}
} else {
packID = pack.Id
if ok, left, tip := pack.CanMake(forceMake); !ok {
return pack, fmt.Errorf("渠道包生成失败【%s】,请在过【%v】后,再次尝试生成渠道【%s】的渠道包", tip, left, channel)
}
filter := bson.M{
channelpackage.FN.ChannelPackage.Id: packID,
channelpackage.FN.ChannelPackage.LockCode: pack.LockCode,
}
update := bson.M{
"$set": bson.M{
channelpackage.FN.ChannelPackage.LockCode: lockCode,
channelpackage.FN.ChannelPackage.LockTime: time.Now(),
channelpackage.FN.ChannelPackage.GenTime: time.Now(),
},
}
// 更新成功是生成apk的必要条件
if serr := channelpackage.UpdateOneChannelPackage(ctx, filter, update); serr != nil {
return pack, fmt.Errorf("update failed, code:%d, msg:%s", serr.Code, serr.Msg)
}
}
filter := bson.M{
channelpackage.FN.ChannelPackage.Id: packID,
channelpackage.FN.ChannelPackage.LockCode: lockCode,
}
err = makeChannelApk(version.Link[0], channel, remoteFile, logBody)
if err != nil {
// write error
update := bson.M{
"$inc": bson.M{
channelpackage.FN.ChannelPackage.GenCount: 1,
},
"$set": bson.M{
channelpackage.FN.ChannelPackage.LockTime: time.Time{}, // 重置锁
channelpackage.FN.ChannelPackage.GenError: err.Error(),
channelpackage.FN.ChannelPackage.Status: channelpackage.ChanApkStatusFailure,
},
}
_ = channelpackage.UpdateOneChannelPackage(ctx, filter, update)
return pack, err
}
update := bson.M{
"$set": bson.M{
channelpackage.FN.ChannelPackage.GenError: "success",
channelpackage.FN.ChannelPackage.UrlPath: remoteFile,
channelpackage.FN.ChannelPackage.FileName: version.Md5,
channelpackage.FN.ChannelPackage.Version: version.VersionNum,
channelpackage.FN.ChannelPackage.GenTime: time.Now(),
channelpackage.FN.ChannelPackage.Reason: reason,
channelpackage.FN.ChannelPackage.Desc: version.Info,
channelpackage.FN.ChannelPackage.LockTime: time.Time{}, // 重置锁
channelpackage.FN.ChannelPackage.Status: channelpackage.ChanApkStatusSuccess,
},
}
if serr := channelpackage.UpdateOneChannelPackage(ctx, filter, update); serr != nil {
return pack, fmt.Errorf("update failed,, code:%d, msg:%s", serr.Code, serr.Msg)
}
//if forceMake {
// // 自动开启渠道自动打包
// update := bson.M{
// "$set": bson.M{channelmod.FN.ChannelInfo.AutoGenApk: true},
// }
// _ = channelmod.UpdateOneChannelInfo(ctx, bson.M{channelmod.FN.ChannelInfo.Channel: channel}, update)
//}
pack2, serr := channelpackage.FindOneChannelPackage(ctx, filter)
if serr != nil {
return pack, fmt.Errorf("find failed, code:%d, msg:%s", serr.Code, serr.Msg)
}
return pack2, nil
}
func makeChannelApk(downUrl string, channel string, remoteFile string, logBody io.Writer) error {
var out []byte
var err error
// 生成临时apk文件名
step := 1
tempPath := os.TempDir()
srcName := utils.RandStringBytes(12)
dstName := utils.RandStringBytes(12)
tmpSrcApk := filepath.Join(tempPath, srcName+".apk")
tmpDstPath := filepath.Join(tempPath, dstName)
if logBody != nil {
logBody.Write([]byte(fmt.Sprintf("tmpSrcApk=%s\n", tmpSrcApk)))
logBody.Write([]byte(fmt.Sprintf("tmpDstPath=%s\n", tmpDstPath)))
}
// 下载apk原始文件
step = 2
cmd := exec.Command("/usr/bin/curl",
"-o",
tmpSrcApk,
downUrl)
out, err = cmd.Output()
if err != nil {
return fmt.Errorf("%w, step=%d", err, step)
}
if logBody != nil {
logBody.Write([]byte(fmt.Sprintf("step=%d, cmd=%s, out=%s\n", step, cmd.String(), string(out))))
}
defer func() {
_ = os.Remove(tmpSrcApk)
}()
step = 3
// 生成渠道包
cmd = exec.Command("/usr/bin/java", // 测试服 /tools/jdk/bin/java 正式服 /usr/bin/java
"-jar",
"/usr/lib/jar/packer-ng-2.0.1.jar",
"generate",
fmt.Sprintf("--channels=%s", channel),
fmt.Sprintf("--output=%s", tmpDstPath),
tmpSrcApk)
out, err = cmd.Output()
if err != nil {
return fmt.Errorf("%w, step=%d", err, step)
}
if logBody != nil {
logBody.Write([]byte(fmt.Sprintf("step=%d, cmd=%s, out=%s\n", step, cmd.String(), string(out))))
}
step = 4
// 上传到下载服务器
fileName := filepath.Join(tmpDstPath, fmt.Sprintf("%s-%s.apk", srcName, channel))
defer func() {
_ = os.Remove(fileName)
}()
cmd = exec.Command("/usr/bin/curl",
"-T",
fileName,
"-u",
"213apk:5JLm6dKS42Nsxmif",
fmt.Sprintf("ftp://216.83.58.213:21/%s", remoteFile))
out, err = cmd.Output()
if err != nil {
return fmt.Errorf("%w, step=%d", err, step)
}
if logBody != nil {
logBody.Write([]byte(fmt.Sprintf("step=%d, cmd=%s, out=%s\n", step, cmd.String(), string(out))))
}
return nil
}
func SendLinkErrorMessageToBot(text string) {
defer utils.RecoverError(nil)
key := crypt.StrToMd5(text)
hash := redis.NewHash(redis.StatRds.Client(), redis.GetDownloadCheckBotMsgHashKey())
_, _ = hash.Expire(time.Hour)
if exists, err := hash.Exists(key); err == nil && !exists {
_ = statg.Bot.SendMonitorMessage(text)
_, _ = hash.Set(key, text)
}
}
func CheckChannelApkAutoGen(ctx context.Context, appId int32, channel string, version *versionMod.VersionModel, pack *channelpackage.ChannelPackage) {
var err error
if apkGenLock.LockedCount() > channelpackage.ApkGenerateParallelMax {
return // 不能超出并行任务数
}
lockKey := fmt.Sprintf("id=%d#ch=%s", appId, channel)
expired := time.Now().Add(5 * time.Minute)
if locked := apkGenLock.TryLock(lockKey, expired); locked {
defer apkGenLock.UnLock(lockKey, expired)
bEnable := statg.Conf.ApkGenEnable
if !bEnable {
channelInfo, _ := channelmod.FindOneChannelInfo(ctx, bson.M{channelmod.FN.ChannelInfo.Channel: channel})
if channelInfo != nil && channelInfo.AutoGenApk {
bEnable = true
}
}
if bEnable {
canMake := pack == nil
if !canMake {
canMake, _, _ = pack.CanMake(false)
}
if canMake {
go func() {
defer utils.RecoverError(nil)
pack, err = TryMakeChannelApk(ctx, appId, channel, version, pack, "", false, nil)
if err == nil && pack != nil {
text := fmt.Sprintf("渠道包已全自动生成:\n")
text += fmt.Sprintf("渠道:【%s】\n", pack.Channel)
text += fmt.Sprintf("应用:【%s,ID=%d】\n", statg.GlobalEndpoints.GetAppShortNameById(pack.AppId), pack.AppId)
text += fmt.Sprintf("版本:【%s】\n", version.VersionNum)
text += fmt.Sprintf("文件:【%s】\n", version.Md5)
text += fmt.Sprintf("发布:【%s】\n", version.CreatedAt.Format(time.DateTime))
text += fmt.Sprintf("生成:【%s】\n", pack.GenTime.Format(time.DateTime))
baseUrls := GetPackageBaseDomain(pack.AppId)
for index, baseUrl := range baseUrls {
text += fmt.Sprintf("下载链接-%d: %s\n", index, pack.GetRandomLink([]string{baseUrl}))
}
statg.Bot.SendChannelApkMessage(text)
}
}()
}
}
}
}
+394
View File
@@ -0,0 +1,394 @@
package versionSer
import (
"context"
"encoding/json"
"errors"
"fmt"
"math/rand"
"stat/api/proto"
"stat/common/stdLog"
"stat/common/utils"
"stat/models/channelpackage"
"stat/models/downloadCpc"
downloadinfo "stat/models/downloadinfo"
"stat/models/versionMod"
"stat/service/cacheSer"
"stat/service/crossSer"
"stat/statg"
"strings"
"sync"
"time"
"go.mongodb.org/mongo-driver/bson"
)
type ChannelParams struct {
PC string `json:"i" bson:"i"` //推广码
DC string `json:"dc" bson:"dc"` //商区序列
CH string `json:"ch" bson:"ch"` //渠道
}
var (
appSvrUrlDict = make(map[int32]string)
appSvrIdxDict = make(map[int32]statg.ServerIndex)
once sync.Once
apkGenLock = utils.NewExpiredLock()
landPageVisit = utils.NewExpiredLock()
landPageClick = utils.NewExpiredLock()
landPageVisitIpTimeout = time.Hour // 每个IP,1个小时内无论访问多少次,都算一次
landPageClickIpTimeout = time.Hour // 每个IP,1个小时内无论访问多少次,都算一次
)
func Init() {
once.Do(func() {
for _, endpoint := range statg.GlobalEndpoints {
appSvrIdxDict[endpoint.AppId] = endpoint.OwnerIndex
if url, ok := statg.Conf.InnerServers[endpoint.OwnerIndex]; ok {
appSvrUrlDict[endpoint.AppId] = url
}
}
})
}
// CrossButtonClick 跨服务器点击下载返回最新的下载链接
func CrossButtonClick(ctx context.Context, userAgent string, peerIP string, req *proto.PageButtonClickReq) (resp *proto.PageButtonClickResp, err error) {
req.Ip = peerIP
srvIdx := appSvrIdxDict[req.AppId]
if srvIdx == statg.ServerIndexNone {
return nil, fmt.Errorf("appid %d not found", req.AppId)
}
if srvIdx == statg.Conf.ServerIndex {
return PageButtonClick(ctx, peerIP, userAgent, req)
}
addr := appSvrUrlDict[req.AppId]
if addr == "" {
return nil, fmt.Errorf("appid %d server not found", req.AppId)
}
res := crossSer.CrossAppDownloadRes{}
err = crossSer.InnerRequest(addr+"/api/stat/inner/appDownload", req, &res, false)
if err != nil {
return nil, err
}
if res.Data == nil {
return nil, fmt.Errorf("data not found")
}
return res.Data, nil
}
// PageButtonClick 落地页点击下载返回最新的下载链接
func PageButtonClick(ctx context.Context, userAgent string, peerIP string, req *proto.PageButtonClickReq) (resp *proto.PageButtonClickResp, err error) {
resp = &proto.PageButtonClickResp{}
var channelCode ChannelParams
// TODO 存储记录
_ = json.Unmarshal([]byte(req.Channel), &channelCode)
if req.Ip == "" {
req.Ip = peerIP
}
channelCode.DC = utils.RepairDC(channelCode.DC)
if req.Terminal == "ios" && req.PackageType == versionMod.Light {
var h5Req = &proto.H5ConfigReq{
AppId: req.AppId,
DC: channelCode.DC,
PC: channelCode.PC,
Domain: channelCode.PC,
}
resp.Terminal = req.Terminal
resp.PackageType = req.PackageType
resp.Status = true
resp.LightData, _ = GetH5Config(ctx, h5Req, userAgent, req.Ip)
return
}
downloadInfo := &downloadinfo.DownloadInfoModel{
AppId: req.AppId,
IP: req.Ip,
Domain: req.Domain,
PC: channelCode.PC,
DC: channelCode.DC,
CH: channelCode.CH,
SysType: strings.ToLower(strings.TrimSpace(req.Terminal)),
PackageType: req.PackageType,
PeerIP: peerIP,
UserAgent: userAgent,
CreatedAt: time.Now(),
}
//和APP统一
if req.PackageType == versionMod.Shop {
downloadInfo.SysType = "h5_ios"
}
if landPageClick.TryLock(req.Ip, time.Now().Add(landPageClickIpTimeout)) {
cacheSer.Writers.LandPageClickWriter.Add(downloadInfo)
}
if downloadInfo.IP == "" {
downloadInfo.IP = peerIP
}
var data *versionMod.VersionModel
cacheKey := fmt.Sprintf("version_button#appId:%d#%s#%d", req.AppId, req.Terminal, req.PackageType)
if cache, exists := cacheSer.Cache.Get(cacheKey); exists {
if item, ok := cache.(*versionMod.VersionModel); ok {
data = item
}
}
if data == nil {
data, err = versionMod.ButtonClick(ctx, req.AppId, req.Terminal, req.PackageType)
if err != nil {
return nil, err
}
cacheSer.Cache.Set(cacheKey, data, 2*time.Minute)
}
resp.PackageType = data.PackageType
resp.Status = data.Status
resp.Terminal = data.Terminal
resp.VersionNum = data.VersionNum
resp.BackUrl = data.BackLink
resp.Link = append(resp.Link, data.Link...)
// IOS的TF包管理
if req.PackageType == versionMod.Tf {
// 1. FT包管理
for _, tfInfo := range data.TfInfo {
one := proto.TfInfo{
AppName: tfInfo.AppName,
AppIcon: tfInfo.AppIcon,
URL: tfInfo.URL,
}
resp.Tf = append(resp.Tf, one)
}
if len(resp.Tf) == 0 {
resp.Tf = make([]proto.TfInfo, 0)
for _, v := range data.Link {
tmp := proto.TfInfo{
URL: v,
}
if len(data.BackLink) > 0 {
tmp.BackUrl = data.BackLink
}
resp.Tf = append(resp.Tf, tmp)
}
}
return resp, nil
}
if data.Terminal == "ios" {
// 配置多个随机返回一个
if len(data.Link) >= 1 {
resp.Link = make([]string, 0)
index := rand.Intn(len(data.Link))
resp.Link = append(resp.Link, data.Link[index])
}
return
}
// 新增批量打包逻辑
if req.Terminal == "android" && len(data.Md5) > 0 {
randTime := int64(60*60) + int64(rand.Intn(60*60))
requestPackageTime := time.Now().Add(-time.Second * time.Duration(randTime))
// 下载链接
projectString := ""
beginPath := ""
pathList := strings.Split(strings.Replace(data.Md5, ".apk", "", -1), "_")
if len(pathList) == 3 {
beginPath = strings.TrimSpace(pathList[len(pathList)-1])
} else {
return
}
endpoint := statg.GlobalAppIdEndpoints[req.AppId]
if endpoint != nil {
projectString = fmt.Sprintf("%s/out/%s/%s%s_", endpoint.NameEn, beginPath, endpoint.NameEn, requestPackageTime.Format("20060102")[2:])
}
if projectString != "" {
packageUrlString := statg.Conf.URL.PackageUrl
if req.AppId == 999 && len(statg.Conf.URL.BiliPackageUrl) > 0 {
packageUrlString = statg.Conf.URL.BiliPackageUrl
} else if req.AppId == 777 && len(statg.Conf.URL.HuanqiuPackageUrl) > 0 {
packageUrlString = statg.Conf.URL.HuanqiuPackageUrl
} else if req.AppId == 444 && len(statg.Conf.URL.ManhuaPackageUrl) > 0 {
packageUrlString = statg.Conf.URL.ManhuaPackageUrl
}
packageUrlArray := strings.Split(packageUrlString, ";")
if len(packageUrlArray) > 0 {
if len(channelCode.DC) > 0 { //&&
pack, serr := channelpackage.FindOneChannelPackage(ctx, bson.M{
channelpackage.FN.ChannelPackage.AppId: req.AppId,
channelpackage.FN.ChannelPackage.Channel: channelCode.DC,
channelpackage.FN.ChannelPackage.Version: data.VersionNum,
channelpackage.FN.ChannelPackage.FileName: data.Md5,
})
if serr == nil || serr.Code == stdLog.DataNotExist {
if pack != nil && pack.Status == channelpackage.ChanApkStatusSuccess {
pass, links := ApkCheckLinks(pack, packageUrlArray)
if pass {
resp.Link = links
_ = pack.IncDownCount(ctx)
return resp, nil
} else {
ApkReportErrorLinks(ctx, pack, links)
}
} else {
CheckChannelApkAutoGen(ctx, req.AppId, channelCode.DC, data, pack)
}
}
}
// 默认使用第一个
passedLinks := make([]string, 0)
for _, packageLink := range packageUrlArray {
link := strings.TrimSpace(packageLink + projectString + data.Md5)
checklink := link
cerr := CheckPackageLink(ctx, checklink)
if cerr != nil {
// log.Error("AppVersion", log.Any("link", link), log.E(cerr))
// if statg.Conf.RunMod == consts.RunModRelease && !slices.Contains([]int32{666, 998}, req.AppId) {
// appDict, _ := appInfoMod.GetAllAppIdNameMap(ctx)
// text := fmt.Sprintf("【%s】Android 下载链接异常:\n链接:%s\n错误:%s\n", appDict[req.AppId], link, cerr.Error())
// SendLinkErrorMessageToBot(text)
// }
} else {
passedLinks = append(passedLinks, link)
}
}
if len(passedLinks) > 0 {
resp.Link = passedLinks
}
} else {
link := strings.TrimSpace(packageUrlString + projectString + data.Md5)
resp.Link = []string{link}
}
}
}
return
}
// PageButtonList 落地页获取下载按钮
func PageButtonList(ctx context.Context, isIos bool, peerIP string, req *proto.PageButtonListReq) (resp *proto.PageButtonListResp, err error) {
resp = &proto.PageButtonListResp{
IosTF: false,
}
var channelCode ChannelParams
// TODO 存储记录
_ = json.Unmarshal([]byte(req.Channel), &channelCode)
channelCode.DC = utils.RepairDC(channelCode.DC)
req.Ip = peerIP
if req.Ip != "" {
logInfo := &downloadCpc.DownloadCpc{
AppId: req.AppId,
IP: req.Ip,
PC: channelCode.PC,
DC: channelCode.DC,
CH: channelCode.CH,
PeerIP: peerIP,
CreatedAt: time.Now(),
}
if landPageVisit.TryLock(req.Ip, time.Now().Add(landPageVisitIpTimeout)) {
cacheSer.Writers.LandPageVisitWriter.Add(logInfo)
}
}
cacheKey := fmt.Sprintf("version_button_list#appId:%d", req.AppId)
var buttons versionMod.VersionList
if cache, exists := cacheSer.Cache.Get(cacheKey); exists {
if list, ok := cache.(versionMod.VersionList); ok {
buttons = list
}
}
if buttons == nil {
buttons, err = versionMod.ButtonList(ctx, req.AppId)
if err != nil {
err = errors.New("服务器内部错误")
return
}
cacheSer.Cache.Set(cacheKey, buttons, 0)
}
if len(buttons) < 1 {
err = errors.New("请开启版本")
return
}
for _, value := range buttons {
if isIos && value.Terminal == "android" {
// 如果是ios设备请求,记录是android 跳过
continue
}
if value.PackageType == versionMod.Enterprise && value.Terminal == "ios" {
resp.IosDown = true
}
if value.PackageType == versionMod.Shop && value.Terminal == "ios" {
resp.IosStore = true
if len(value.Link) > 0 {
resp.IosLink = value.Link[0]
}
}
if value.PackageType == versionMod.Tf && value.Terminal == "ios" {
resp.IosTF = true
}
if value.PackageType == versionMod.Light && value.Terminal == "ios" {
resp.IosH5 = true
}
}
// android下载按钮
packageUrlString := statg.Conf.URL.PackageUrl
if req.AppId == 999 && len(statg.Conf.URL.BiliPackageUrl) > 0 {
packageUrlString = statg.Conf.URL.BiliPackageUrl
} else if req.AppId == 777 && len(statg.Conf.URL.HuanqiuPackageUrl) > 0 {
packageUrlString = statg.Conf.URL.HuanqiuPackageUrl
} else if req.AppId == 444 && len(statg.Conf.URL.ManhuaPackageUrl) > 0 {
packageUrlString = statg.Conf.URL.ManhuaPackageUrl
}
packageUrlArray := strings.Split(packageUrlString, ";")
resp.AndroidButton = len(packageUrlArray)
// 是否有渠道包
var version *versionMod.VersionModel
versionKey := fmt.Sprintf("version_button#appId:%d#%s#%d", req.AppId, "android", 0)
if cache, exists := cacheSer.Cache.Get(versionKey); exists {
if item, ok := cache.(*versionMod.VersionModel); ok {
version = item
}
}
if version == nil {
version, err = versionMod.ButtonClick(ctx, req.AppId, "android", 0)
if err != nil {
return nil, err
}
cacheSer.Cache.Set(cacheKey, version, 2*time.Minute)
}
pack, serr := channelpackage.FindOneChannelPackage(ctx, bson.M{
channelpackage.FN.ChannelPackage.AppId: req.AppId,
channelpackage.FN.ChannelPackage.Channel: channelCode.DC,
channelpackage.FN.ChannelPackage.Version: version.VersionNum,
channelpackage.FN.ChannelPackage.FileName: version.Md5,
})
if serr != nil && serr.Code != stdLog.DataNotExist {
return nil, errors.New(serr.Msg)
}
if pack != nil && pack.Status == channelpackage.ChanApkStatusSuccess {
resp.HasChannelApk = true
}
// ----------------End---------------
resp.Contact = proto.ContactInfo{
Telegram: "https://t.me/madoutiktok",
Paoto: "https://ptcc.in/madouyingshi",
Madou: "http://madou101.com",
}
return
}
+284
View File
@@ -0,0 +1,284 @@
package versionSer
import (
"context"
"fmt"
"math/rand"
"net/http"
"stat/api/proto"
"stat/common/enum/consts"
"stat/common/genId"
"stat/common/log"
"stat/common/redis"
"stat/common/tf"
"stat/common/utils"
"stat/models/appInfoMod"
"stat/models/versionMod"
"stat/statg"
"strings"
"time"
"github.com/caixw/lib.go/encoding/version"
"github.com/jinzhu/copier"
)
// ListVersion 获取版本信息
func ListVersion(ctx context.Context, req *proto.VersionListReq) (resp *proto.VersionListResp, err error) {
resp = &proto.VersionListResp{
Data: make([]*proto.VersionInfo, 0),
}
skip, limit := utils.ToDBPage(req.PageNum, req.PageSize)
data, count, err := versionMod.GetVersionList(ctx, req.AppId, skip, limit)
if err != nil {
return nil, err
}
appInfoList, err := appInfoMod.GetAllAppInfo(ctx)
if err != nil {
return nil, err
}
for _, v := range data {
versionInfo := proto.VersionInfo{
Id: v.Id,
AppId: v.AppId,
VersionNum: v.VersionNum,
Terminal: v.Terminal,
Link: v.Link,
Info: v.Info,
PackageType: v.PackageType,
IsForceUpdate: v.IsForceUpdate,
MinVersion: v.MinVersion,
Status: v.Status,
Size: v.Size,
Md5: v.Md5,
BackLink: v.BackLink,
}
for _, appinfo := range appInfoList {
if appinfo.AppId == v.AppId {
versionInfo.AppName = appinfo.Name
}
}
resp.Data = append(resp.Data, &versionInfo)
}
resp.Count = count
return
}
// DeleteVersion 删除版本
func DeleteVersion(ctx context.Context, id int64) (err error) {
err = versionMod.DelVersion(ctx, id)
return
}
// AddVersion 添加版本
func AddVersion(ctx context.Context, req *proto.VersionAddReq) (err error) {
model := &versionMod.VersionModel{}
// TODO:修改成指针
err = copier.Copy(model, req)
if err != nil {
return
}
model.TfInfo = make([]versionMod.TfInfo, 0)
if req.PackageType == versionMod.Tf {
for _, url := range req.Link {
tmp := versionMod.TfInfo{
URL: strings.TrimSpace(url),
}
if req.PackageType == versionMod.Tf {
result, err := tf.CheckTFValid(url)
if err != nil {
log.Error("runCheckUrl CheckTFValid", log.Any("url", url), log.E(err))
continue
}
tmp.AppIcon = result.AppIcon
tmp.AppName = result.AppName
}
model.TfInfo = append(model.TfInfo, tmp)
}
}
model.VersionNum = strings.TrimSpace(model.VersionNum)
model.CreatedAt = time.Now()
model.UpdatedAt = time.Now()
model.Id = genId.GetNextVersionId()
err = versionMod.AddVersion(ctx, model)
return
}
// DeleteVersion 删除版本
func UpdateVersion(ctx context.Context, req *proto.VersionUpdateReq) (err error) {
model := &versionMod.VersionModel{}
err = copier.Copy(model, req)
if err != nil {
return
}
model.TfInfo = make([]versionMod.TfInfo, 0)
if req.PackageType != nil && *req.PackageType == versionMod.Tf {
for _, url := range *req.Link {
tmp := versionMod.TfInfo{
URL: strings.TrimSpace(url),
}
if *req.PackageType == versionMod.Tf {
result, err := tf.CheckTFValid(url)
if err != nil {
log.Error("runCheckUrl CheckTFValid", log.Any("url", url), log.E(err))
continue
}
tmp.AppIcon = result.AppIcon
tmp.AppName = result.AppName
}
model.TfInfo = append(model.TfInfo, tmp)
}
}
model.UpdatedAt = time.Now()
err = versionMod.UpdateVersion(ctx, model)
return
}
func AppVersion(ctx context.Context, req *proto.AppVersionReq) (resp *proto.AppVersionResp, err error) {
var list1 []*versionMod.VersionModel
var list []*versionMod.VersionModel
resp = &proto.AppVersionResp{}
if req.SysType == "android" {
list1, err = versionMod.FindVersByPkgTyps(ctx, req.AppId, req.SysType, req.CurVersion, 0)
if err != nil {
return
}
} else {
// ios 包
list1, err = versionMod.FindIosVersion(ctx, req.AppId, req.SysType, req.CurVersion, []versionMod.PkgType{versionMod.Shop})
if err != nil {
return
}
}
list = make([]*versionMod.VersionModel, 0)
for _, v := range list1 {
if version.Compare(v.VersionNum, req.CurVersion) > 0 {
list = append(list, v)
} else if req.CurVersion == "9.9.9" {
list = append(list, v)
}
}
if len(list) == 0 || len(req.CurVersion) == 0 || req.CurVersion == "null" {
resp.HasNewVersion = false
return
}
var minVersion string
for _, value := range list {
if len(value.MinVersion) > 0 && value.MinVersion > minVersion {
minVersion = value.MinVersion
break
} else if value.IsForceUpdate {
minVersion = value.VersionNum
}
}
resp.HasNewVersion = true
resp.ServerVersion = list[0].VersionNum
resp.DownloadLink = list[0].Link
resp.Description = list[0].Info
resp.IsForceUpdate = list[0].IsForceUpdate
if len(minVersion) > 0 {
if minVersion > req.CurVersion {
resp.IsForceUpdate = true
}
}
if len(resp.DownloadLink) > 0 && len(resp.DownloadLink[0]) < 2 && list[0].Terminal == "ios" && len(list[0].BackLink) > 2 {
resp.DownloadLink = []string{list[0].BackLink}
return
}
// // 新增批量打包逻辑
if req.SysType == "android" && len(list[0].Md5) > 0 {
// 包名称:com.pf.ys201114
randTime := int64(60*60) + int64(rand.Intn(60*60))
requestPackageTime := time.Now().Add(-time.Second * time.Duration(randTime)) // 报毒包随机
// 下载链接
projectString := ""
beginPath := ""
pathList := strings.Split(strings.Replace(list[0].Md5, ".apk", "", -1), "_")
if len(pathList) > 0 {
beginPath = strings.TrimSpace(pathList[len(pathList)-1])
} else {
return
}
endpoint := statg.GlobalAppIdEndpoints[req.AppId]
if endpoint != nil {
projectString = fmt.Sprintf("%s/out/%s/%s%s_", endpoint.NameEn, beginPath, endpoint.NameEn, requestPackageTime.Format("20060102")[2:])
}
if projectString != "" {
packageUrlString := statg.Conf.URL.PackageUrl
if req.AppId == 999 && len(statg.Conf.URL.BiliPackageUrl) > 0 {
packageUrlString = statg.Conf.URL.BiliPackageUrl
} else if req.AppId == 777 && len(statg.Conf.URL.HuanqiuPackageUrl) > 0 {
packageUrlString = statg.Conf.URL.HuanqiuPackageUrl
} else if req.AppId == 444 && len(statg.Conf.URL.ManhuaPackageUrl) > 0 {
packageUrlString = statg.Conf.URL.ManhuaPackageUrl
}
packageUrlArray := strings.Split(packageUrlString, ";")
packageUrl := packageUrlString
if len(packageUrlArray) > 0 {
// 默认使用第一个
packageUrl = packageUrlArray[0]
}
link := strings.TrimSpace(packageUrl + projectString + list[0].Md5)
checklink := link
cerr := CheckPackageLink(ctx, checklink)
if cerr != nil {
log.Error("AppVersion", log.Any("link", link), log.E(cerr))
if statg.Conf.RunMod == consts.RunModRelease {
// 比特币和溅射淫行下载链接异常不需要报警
if req.AppId != 998 {
//text := fmt.Sprintf("[警告]:应用名称:统计中心\nandroid下载链接异常: 版本:%s-->%s\n链接%s %s\n\n", req.CurVersion, list[0].VersionNum, link, cerr.Error())
appDict, _ := appInfoMod.GetAllAppIdNameMap(ctx)
text := fmt.Sprintf("【%s】Android下载链接异常:\n链接:%s\n错误:%s\n", appDict[req.AppId], link, cerr.Error())
SendLinkErrorMessageToBot(text)
}
}
} else {
resp.DownloadLink = []string{link}
}
}
}
return
}
// CheckPackageLink 下载链接下载检测
func CheckPackageLink(ctx context.Context, url_https string) (err error) {
url := strings.Replace(url_https, "https", "http", -1)
redisKey := fmt.Sprintf("channelpackagecheck:%s", url)
status, _ := redis.StatRds.Exists(redisKey)
if status { // 存在
return nil
}
// 检测下载地址
resp, err := http.Head(url)
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusGatewayTimeout {
return fmt.Errorf("file is %d ", resp.StatusCode)
}
_ = redis.StatRds.Set(redisKey, "1", 24*time.Hour)
return nil
}
func NewChannelDownLoadLink(ctx context.Context, req *proto.ChannelDownLoadLinkReq) (resp *proto.ChannelDownLoadLinkResp) {
return nil
}