package reportser import ( "context" "crypto/md5" "encoding/hex" "encoding/json" "fmt" "strconv" "strings" "time" "videoweb/app/service/playletcache" "videoweb/pub/common" "videoweb/pub/common/enum/consts" "videoweb/pub/common/ipaddr" "videoweb/pub/common/log" "videoweb/pub/common/useragent" "videoweb/pub/dataReport" "videoweb/pub/models/eventmod" "videoweb/pub/models/playletchaptermod" "videoweb/pub/models/playletmod" "videoweb/pub/models/usermod" "videoweb/pub/pubdef" "github.com/gin-gonic/gin" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo/options" ) // ReportUserRegister 用户注册事件 func ReportUserRegister(user *usermod.User, ua useragent.UA, ip, registerType string) { if user == nil { return } if registerType == "" { registerType = "deviceid" } ua = fillUserUA(user, ua) createAt := user.CreatedAt if !user.RegisterTime.IsZero() { createAt = user.RegisterTime } eventId := newEventId(pubdef.TrackerAppId(), user.Id, "", createAt.Unix()) event := dataReport.NewCommonField(eventId, dataReport.EventTypeUserRegister, user.DistrictCode, ua, strconv.FormatInt(user.Id, 10), ip) event.Payload = dataReport.JsonRawMessage(&dataReport.UserRegisterEvent{ Type: registerType, CreateTime: dataReport.JsonNumber(createAt.Unix()), }) insertEvent(event) } // ReportUserLogin 用户登录事件 func ReportUserLogin(user *usermod.User, ua useragent.UA, ip string, loginType pubdef.LoginType) { if user == nil { return } ua = fillUserUA(user, ua) createTime := time.Now().Unix() eventId := newEventId(pubdef.TrackerAppId(), user.Id, "", createTime) event := dataReport.NewCommonField(eventId, dataReport.EventTypeUserLogin, user.DistrictCode, ua, strconv.FormatInt(user.Id, 10), ip) event.Payload = dataReport.JsonRawMessage(&dataReport.UserLoginEvent{ Type: loginTypeEventValue(loginType), }) insertEvent(event) } // ReportPlayletLike 短剧点赞 func ReportPlayletLike(ctx context.Context, userId, playletId, flag int64) { reportDramaEvent(ctx, userId, playletId, 0, dataReport.EventTypeDramaLike, flag, "") } // ReportPlayletCollect 短剧收藏 func ReportPlayletCollect(ctx context.Context, userId, playletId, flag int64) { reportDramaEvent(ctx, userId, playletId, 0, dataReport.EventTypeDramaCollect, flag, "") } // ReportPlayletChapterLike 短剧剧集点赞 func ReportPlayletChapterLike(ctx context.Context, userId, playletId, chapterId, flag int64) { reportDramaEvent(ctx, userId, playletId, chapterId, dataReport.EventTypeDramaLike, flag, "") } // ReportPlayletChapterCollect 短剧剧集收藏 func ReportPlayletChapterCollect(ctx context.Context, userId, playletId, chapterId, flag int64) { reportDramaEvent(ctx, userId, playletId, chapterId, dataReport.EventTypeDramaCollect, flag, "") } // ReportPlayletComment 短剧/剧集评论 func ReportPlayletComment(ctx context.Context, user *usermod.User, playletId, chapterId int64, content string) { if user == nil || playletId <= 0 { return } pub := buildDramaPub(ctx, playletId, chapterId) if pub.DramaID == "" { return } ua, ip := uaAndIPFromCtx(ctx) ua = fillUserUA(user, ua) event := dataReport.NewCommonField(dataReport.GenerateGlobalEventID(), dataReport.EventTypeDramaComment, user.DistrictCode, ua, strconv.FormatInt(user.Id, 10), ip) event.Payload = dataReport.JsonRawMessage(&dataReport.DramaCommentEvent{ DramaEventPub: pub, CommentContent: content, }) insertEvent(event) } // ReportPlayletUnlock 短剧剧集金币解锁:同时上报金币消耗和购买 func ReportPlayletUnlock(ctx context.Context, user *usermod.User, playlet *playletmod.Playlet, chapter *playletchaptermod.PlayletChapter, amount, balanceBefore, balanceAfter int64, orderNo string) { if user == nil || playlet == nil || chapter == nil || amount <= 0 { return } if full, err := usermod.FindOneUser(ctx, bson.M{usermod.FN.User.Id: user.Id}); err == nil && full != nil { user = full } ua, ip := uaAndIPFromCtx(ctx) ua = fillUserUA(user, ua) productId := strconv.FormatInt(chapter.Id, 10) productName := playletUnlockProductName(playlet, chapter) reportCoinConsume(user, ua, ip, productId, productName, amount, balanceBefore, balanceAfter, KeyPlayletUnlock, orderNo) reportDramaPurchase(ctx, user, ua, ip, playlet, chapter, amount, orderNo) } // ReportPlayletStatusChange 短剧上下架,复用 video_status_change func ReportPlayletStatusChange(playlet *playletmod.Playlet, flag int64) { if playlet == nil || playlet.Id == 0 || flag == 0 { return } typeID, typeName := resolvePlayletType(playlet) event := dataReport.NewCommonField("", dataReport.EventTypeVideoStatusChange, "", useragent.UA{}, "", "") event.Payload = dataReport.JsonRawMessage(&dataReport.VideoStatusChangeEvent{ MediaId: playletMediaId(playlet), VideoID: strconv.FormatInt(playlet.Id, 10), VideoTitle: playlet.Title, VideoTypeID: typeID, VideoTypeName: typeName, Flag: dataReport.JsonNumber(flag), }) insertEvent(event) } // PlayletStatusFlag 短剧上下架映射上报 flag:1上架 2下架 func PlayletStatusFlag(status playletmod.Status) int64 { switch status { case playletmod.StatusUp: return 1 case playletmod.StatusDown: return 2 default: return 0 } } func reportDramaEvent(ctx context.Context, userId, playletId, chapterId int64, eventType string, flag int64, content string) { if userId <= 0 || playletId <= 0 { return } user, err := usermod.FindOneUser(ctx, bson.M{usermod.FN.User.Id: userId}) if err != nil || user == nil { return } pub := buildDramaPub(ctx, playletId, chapterId) if pub.DramaID == "" { return } ua, ip := uaAndIPFromCtx(ctx) ua = fillUserUA(user, ua) event := dataReport.NewCommonField(dataReport.GenerateGlobalEventID(), eventType, user.DistrictCode, ua, strconv.FormatInt(user.Id, 10), ip) switch eventType { case dataReport.EventTypeDramaLike: event.Payload = dataReport.JsonRawMessage(&dataReport.DramaLikeEvent{ DramaEventPub: pub, Flag: dataReport.JsonNumber(flag), }) case dataReport.EventTypeDramaCollect: event.Payload = dataReport.JsonRawMessage(&dataReport.DramaCollectEvent{ DramaEventPub: pub, Flag: dataReport.JsonNumber(flag), }) case dataReport.EventTypeDramaComment: event.Payload = dataReport.JsonRawMessage(&dataReport.DramaCommentEvent{ DramaEventPub: pub, CommentContent: content, }) default: return } insertEvent(event) } func reportCoinConsume(user *usermod.User, ua useragent.UA, ip, productId, productName string, amount, balanceBefore, balanceAfter int64, reason Key, orderId string) { if user == nil || amount == 0 { return } if productId == "" { if productName == "" { productName = "金币消耗" } productId = md5String(productName) } createTime := time.Now().Unix() eventId := newEventId(pubdef.TrackerAppId(), user.Id, orderId, createTime) event := dataReport.NewCommonField(eventId, dataReport.EventTypeCoinConsume, user.DistrictCode, ua, strconv.FormatInt(user.Id, 10), ip) event.Payload = dataReport.JsonRawMessage(&dataReport.CoinConsumeEvent{ ProductID: productId, ProductName: productName, CoinConsumeAmount: dataReport.JsonNumber(amount), CoinBalanceBefore: dataReport.JsonNumber(balanceBefore), CoinBalanceAfter: dataReport.JsonNumber(balanceAfter), ConsumeReasonKey: reason.Key(), ConsumeReasonName: reason.Name(), CreateTime: dataReport.JsonNumber(createTime), OrderId: orderId, }) insertEvent(event) } func reportDramaPurchase(ctx context.Context, user *usermod.User, ua useragent.UA, ip string, playlet *playletmod.Playlet, chapter *playletchaptermod.PlayletChapter, amount int64, orderId string) { pub := dataReport.DramaEventPub{} if playlet != nil && chapter != nil && chapter.Id > 0 { pub = buildDramaPub(ctx, playlet.Id, chapter.Id) } if pub.DramaID == "" { pub = playletToDramaPub(playlet, chapter) } if pub.DramaID == "" { return } event := dataReport.NewCommonField(dataReport.GenerateGlobalEventID(), dataReport.EventTypeDramaPurchase, user.DistrictCode, ua, strconv.FormatInt(user.Id, 10), ip) event.Payload = dataReport.JsonRawMessage(&dataReport.DramaPurchaseEvent{ DramaEventPub: pub, CoinQuantity: dataReport.JsonNumber(amount), OrderID: orderId, UnlockType: dataReport.DramaUnlockTypeCoin, }) insertEvent(event) } func buildDramaPub(ctx context.Context, playletId, chapterId int64) dataReport.DramaEventPub { playlet, err := playletmod.FindOnePlaylet(ctx, bson.M{playletmod.FN.Playlet.Id: playletId}, options.FindOne().SetProjection(bson.D{ {Key: playletmod.FN.Playlet.Id, Value: 1}, {Key: playletmod.FN.Playlet.Title, Value: 1}, {Key: playletmod.FN.Playlet.ThemeIds, Value: 1}, {Key: playletmod.FN.Playlet.TagIds, Value: 1}, {Key: playletmod.FN.Playlet.TotalEpisode, Value: 1}, })) if err != nil || playlet == nil { return dataReport.DramaEventPub{} } chapter := findDramaReportChapter(ctx, playletId, chapterId) return playletToDramaPub(playlet, chapter) } // findDramaReportChapter 取上报用剧集。指定了剧集就用该集,短剧维度事件用第一集,保证 drama_id、video_id 都来自剧集。 func findDramaReportChapter(ctx context.Context, playletId, chapterId int64) *playletchaptermod.PlayletChapter { projection := options.FindOne().SetProjection(bson.D{ {Key: playletchaptermod.FN.PlayletChapter.Id, Value: 1}, {Key: playletchaptermod.FN.PlayletChapter.PlayletId, Value: 1}, {Key: playletchaptermod.FN.PlayletChapter.MediaContentId, Value: 1}, {Key: playletchaptermod.FN.PlayletChapter.Title, Value: 1}, {Key: playletchaptermod.FN.PlayletChapter.CurrentEpisode, Value: 1}, {Key: playletchaptermod.FN.PlayletChapter.MediaTime, Value: 1}, }) if chapterId > 0 { chapter, _ := playletchaptermod.FindOnePlayletChapter(ctx, bson.M{playletchaptermod.FN.PlayletChapter.Id: chapterId}, projection) return chapter } chapter, _ := playletchaptermod.FindOnePlayletChapter(ctx, bson.M{playletchaptermod.FN.PlayletChapter.PlayletId: playletId}, projection.SetSort(bson.D{ {Key: playletchaptermod.FN.PlayletChapter.CurrentEpisode, Value: 1}, {Key: playletchaptermod.FN.PlayletChapter.Id, Value: 1}, })) return chapter } func playletToDramaPub(playlet *playletmod.Playlet, chapter *playletchaptermod.PlayletChapter) dataReport.DramaEventPub { if playlet == nil { return dataReport.DramaEventPub{} } typeID, typeName := resolvePlayletType(playlet) tagKey, tagName := resolvePlayletTags(playlet.TagIds) episode := int64(1) duration := int64(0) dramaID := "" videoID := "" if chapter != nil && chapter.Id > 0 { if chapter.CurrentEpisode > 0 { episode = chapter.CurrentEpisode } duration = chapter.MediaTime dramaID = strconv.FormatInt(chapter.Id, 10) if chapter.MediaContentId > 0 { videoID = strconv.FormatUint(uint64(chapter.MediaContentId), 10) } } return dataReport.DramaEventPub{ DramaID: dramaID, DramaTitle: playlet.Title, Episode: dataReport.JsonNumber(episode), VideoID: videoID, TotalEpisode: dataReport.JsonNumber(playlet.TotalEpisode), DramaTypeID: typeID, DramaTypeName: typeName, DramaContentType: dataReport.DramaContentTypeShortDrama, DramaTagKey: tagKey, DramaTagName: tagName, DramaDuration: dataReport.JsonNumber(duration), } } func playletUnlockProductName(playlet *playletmod.Playlet, chapter *playletchaptermod.PlayletChapter) string { title := "" if playlet != nil { title = playlet.Title } if chapter == nil { return title } if chapter.Title != "" { if title == "" { return chapter.Title } return fmt.Sprintf("%s %s", title, chapter.Title) } if chapter.CurrentEpisode > 0 { if title == "" { return fmt.Sprintf("第%d集", chapter.CurrentEpisode) } return fmt.Sprintf("%s 第%d集", title, chapter.CurrentEpisode) } return title } func playletMediaId(playlet *playletmod.Playlet) string { if playlet == nil || playlet.MediaCenterId == 0 { return "" } return strconv.FormatUint(uint64(playlet.MediaCenterId), 10) } func resolvePlayletType(playlet *playletmod.Playlet) (string, string) { if playlet == nil || len(playlet.ThemeIds) == 0 { return "default", "默认分类" } id := playlet.ThemeIds[0] if id <= 0 { return "default", "默认分类" } name := strings.TrimSpace(playletcache.ThemeNameMap([]int64{id})[id]) if name == "" { name = "默认分类" } return strconv.FormatInt(id, 10), name } func resolvePlayletTags(ids []int64) (string, string) { keys := make([]string, 0, len(ids)) for _, id := range ids { if id > 0 { keys = append(keys, strconv.FormatInt(id, 10)) } } if len(keys) == 0 { return "default", "默认标签" } nameMap := playletcache.TagNameMap(ids) names := make([]string, 0, len(keys)) for _, id := range ids { if id <= 0 { continue } name := strings.TrimSpace(nameMap[id]) if name == "" { name = strconv.FormatInt(id, 10) } names = append(names, name) } return strings.Join(keys, ","), strings.Join(names, ",") } func uaAndIPFromCtx(ctx context.Context) (useragent.UA, string) { gc, ok := ctx.(*gin.Context) if !ok { if v := ctx.Value(consts.CtxUA); v != nil { if ua, ok := v.(useragent.UA); ok { return ua, "" } } return useragent.UA{}, "" } ua, _ := common.GetUA(gc) return ua, ipaddr.ResolveClientIP(gc) } func fillUserUA(user *usermod.User, ua useragent.UA) useragent.UA { if user == nil { return ua } if ua.DevID == "" { ua.DevID = user.DevID } if ua.DevType == "" { ua.DevType = user.Device } if ua.SysType == "" { ua.SysType = user.SysType } return ua } func loginTypeEventValue(loginType pubdef.LoginType) string { switch loginType { case pubdef.LoginTypeAccount: return "username" case pubdef.LoginTypeEmail: return "email" default: return "deviceid" } } func newEventId(appId string, uid int64, orderId interface{}, createTime int64) string { input := fmt.Sprintf("%v%v%v%v", appId, uid, orderId, createTime) return md5String(input) } func md5String(input string) string { h := md5.New() h.Write([]byte(input)) return hex.EncodeToString(h.Sum(nil)) } func insertEvent(event dataReport.CommonField) { b, _ := json.Marshal(event) if err := eventmod.InsertOneEvent(context.Background(), &eventmod.Event{ EventId: event.EventID, EventType: event.Event, Data: string(b), Status: eventmod.Default, CreatedAt: time.Now(), UpdatedAt: time.Now(), }); err != nil { log.Error("reportser insertEvent failed", log.Any("eventType", event.Event), log.Any("eventId", event.EventID), log.E(err)) } }