init: hgmj server initial commit

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-10-08 16:50:13 +08:00
co-authored by Claude Opus 4.7
commit 625e89c15d
1870 changed files with 254090 additions and 0 deletions
+261
View File
@@ -0,0 +1,261 @@
package reportser
import (
"91jd-server/common/dataReport"
"91jd-server/common/db"
"91jd-server/middleware/ua"
"91jd-server/models/v/eventmod"
"91jd-server/models/v/mediamod"
"91jd-server/models/v/moduleconfmod"
"91jd-server/models/v/usermod"
"encoding/json"
"time"
)
//
// 漫画事件
//
// ReportComicPurchaseEvent 购买事件
func ReportComicPurchaseEvent(t *db.MongoTool, user *usermod.User, ua ua.UA, ip string,
media *mediamod.Media, orderId string) (err error) {
mediaTypeId, mediaTypeName := extractMediaModule(media)
event := dataReport.NewCommonField(dataReport.GenerateGlobalEventID(), dataReport.EventTypeComicPurchase, user.UID, user.DistrictCode, ua, ip)
event.Payload = jsonRawMessage(&dataReport.ComicPurchaseEvent{
ComicEventPub: dataReport.ComicEventPub{
MediaId: media.LsjId,
ComicId: media.ID.Hex(),
ComicTitle: media.Title,
ComicTypeId: mediaTypeId,
ComicTypeName: mediaTypeName,
RecommendTraceId: "",
},
CoinQuantity: jsonNumber(media.Price),
OrderId: orderId,
PageNo: jsonNumber(0),
})
b, _ := json.Marshal(event)
// 写入数据库
return eventmod.Insert(t, &eventmod.Event{
EventId: event.EventID,
EventType: event.Event,
Data: string(b),
Status: eventmod.Default,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
})
}
// ReportComicCollectOrLikeEvent 收藏或者点赞事件上报
func ReportComicCollectOrLikeEvent(t *db.MongoTool, user *usermod.User, ua ua.UA, ip string,
media *mediamod.Media, isCollect bool, cancel bool) (err error) {
mediaTypeId, mediaTypeName := extractMediaModule(media)
var flag int64 = 1 // 点赞/收藏
if cancel {
flag = 2 // 取消点赞/取消收藏
}
eventKey := dataReport.EventTypeComicLike
if isCollect {
eventKey = dataReport.EventTypeComicCollect
}
event := dataReport.NewCommonField(dataReport.GenerateGlobalEventID(), eventKey, user.UID, user.DistrictCode, ua, ip)
event.Payload = jsonRawMessage(&dataReport.ComicCollectOrLikeEvent{
ComicEventPub: dataReport.ComicEventPub{
MediaId: media.LsjId,
ComicId: media.ID.Hex(),
ComicTitle: media.Title,
ComicTypeId: mediaTypeId,
ComicTypeName: mediaTypeName,
RecommendTraceId: "",
},
Flag: jsonNumber(flag),
PageNo: jsonNumber(0),
})
b, _ := json.Marshal(event)
// 写入数据库
return eventmod.Insert(t, &eventmod.Event{
EventId: event.EventID,
EventType: event.Event,
Data: string(b),
Status: eventmod.Default,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
})
}
// ReportComicCommentEvent 评论事件
func ReportComicCommentEvent(t *db.MongoTool, user *usermod.User, ua ua.UA, ip string,
media *mediamod.Media, content string) (err error) {
mediaTypeId, mediaTypeName := extractMediaModule(media)
event := dataReport.NewCommonField(dataReport.GenerateGlobalEventID(), dataReport.EventTypeComicComment, user.UID, user.DistrictCode, ua, ip)
event.Payload = jsonRawMessage(&dataReport.ComicCommentEvent{
ComicEventPub: dataReport.ComicEventPub{
MediaId: media.LsjId,
ComicId: media.ID.Hex(),
ComicTitle: media.Title,
ComicTypeId: mediaTypeId,
ComicTypeName: mediaTypeName,
RecommendTraceId: "",
},
CommentContent: content,
PageNo: jsonNumber(0),
})
b, _ := json.Marshal(event)
// 写入数据库
return eventmod.Insert(t, &eventmod.Event{
EventId: event.EventID,
EventType: event.Event,
Data: string(b),
Status: eventmod.Default,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
})
}
//
// 小说事件
//
// ReportNovelPurchaseEvent 购买事件
func ReportNovelPurchaseEvent(t *db.MongoTool, user *usermod.User, ua ua.UA, ip string,
media *mediamod.Media, orderId string) (err error) {
mediaTypeId, mediaTypeName := extractMediaModule(media)
event := dataReport.NewCommonField(dataReport.GenerateGlobalEventID(), dataReport.EventTypeNovelPurchase, user.UID, user.DistrictCode, ua, ip)
event.Payload = jsonRawMessage(&dataReport.NovelPurchaseEvent{
NovelEventPub: dataReport.NovelEventPub{
MediaId: media.LsjId,
NovelId: media.ID.Hex(),
NovelTitle: media.Title,
NovelTypeId: mediaTypeId,
NovelTypeName: mediaTypeName,
RecommendTraceId: "",
},
CoinQuantity: jsonNumber(media.Price),
OrderId: orderId,
PageNo: jsonNumber(0),
})
b, _ := json.Marshal(event)
// 写入数据库
return eventmod.Insert(t, &eventmod.Event{
EventId: event.EventID,
EventType: event.Event,
Data: string(b),
Status: eventmod.Default,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
})
}
// ReportNovelCollectOrLikeEvent 收藏或者点赞事件上报
func ReportNovelCollectOrLikeEvent(t *db.MongoTool, user *usermod.User, ua ua.UA, ip string,
media *mediamod.Media, isCollect bool, cancel bool) (err error) {
mediaTypeId, mediaTypeName := extractMediaModule(media)
var flag int64 = 1 // 点赞/收藏
if cancel {
flag = 2 // 取消点赞/取消收藏
}
eventKey := dataReport.EventTypeNovelLike //点赞
if isCollect {
eventKey = dataReport.EventTypeNovelCollect // 收藏
}
event := dataReport.NewCommonField(dataReport.GenerateGlobalEventID(), eventKey, user.UID, user.DistrictCode, ua, ip)
event.Payload = jsonRawMessage(&dataReport.NovelCollectOrLikeEvent{
NovelEventPub: dataReport.NovelEventPub{
MediaId: media.LsjId,
NovelId: media.ID.Hex(),
NovelTitle: media.Title,
NovelTypeId: mediaTypeId,
NovelTypeName: mediaTypeName,
RecommendTraceId: "",
},
Flag: jsonNumber(flag),
PageNo: jsonNumber(0),
})
b, _ := json.Marshal(event)
// 写入数据库
return eventmod.Insert(t, &eventmod.Event{
EventId: event.EventID,
EventType: event.Event,
Data: string(b),
Status: eventmod.Default,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
})
}
// ReportNovelCommentEvent 评论事件
func ReportNovelCommentEvent(t *db.MongoTool, user *usermod.User, ua ua.UA, ip string,
media *mediamod.Media, content string) (err error) {
mediaTypeId, mediaTypeName := extractMediaModule(media)
event := dataReport.NewCommonField(dataReport.GenerateGlobalEventID(), dataReport.EventTypeNovelComment, user.UID, user.DistrictCode, ua, ip)
event.Payload = jsonRawMessage(&dataReport.NovelCommentEvent{
NovelEventPub: dataReport.NovelEventPub{
MediaId: media.LsjId,
NovelId: media.ID.Hex(),
NovelTitle: media.Title,
NovelTypeId: mediaTypeId,
NovelTypeName: mediaTypeName,
RecommendTraceId: "",
},
CommentContent: content,
PageNo: jsonNumber(0),
})
b, _ := json.Marshal(event)
// 写入数据库
return eventmod.Insert(t, &eventmod.Event{
EventId: event.EventID,
EventType: event.Event,
Data: string(b),
Status: eventmod.Default,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
})
}
// extractMediaModule 通过media的MID获取模块ID和名称
func extractMediaModule(media *mediamod.Media) (typeID, typeName string) {
if media == nil {
return
}
if !media.MID.IsZero() {
module, _ := moduleconfmod.GetByID(media.MID)
if !module.ID.IsZero() {
typeID = module.ID.Hex()
typeName = module.ModuleName
return
}
}
typeID = "default"
typeName = "默认分类"
return
}
+22
View File
@@ -0,0 +1,22 @@
package reportser
import (
"encoding/json"
"time"
"91jd-server/common/dataReport"
"91jd-server/common/db"
"91jd-server/models/v/eventmod"
)
func persistEvent(t *db.MongoTool, event dataReport.CommonField) error {
b, _ := json.Marshal(event)
return eventmod.Insert(t, &eventmod.Event{
EventId: event.EventID,
EventType: event.Event,
Data: string(b),
Status: eventmod.Default,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
})
}
+58
View File
@@ -0,0 +1,58 @@
package reportser
import (
"strings"
"time"
"91jd-server/common/dataReport"
"91jd-server/common/db"
"91jd-server/middleware/ua"
"91jd-server/models/v/usermod"
)
const (
LoginTypeDeviceID = "deviceid"
LoginTypePhone = "phone"
LoginTypeEmail = "email"
LoginTypeAccount = "username"
)
func ReportUserLogin(t *db.MongoTool, user *usermod.User, ua ua.UA, ip string, loginType string) error {
if user == nil {
return nil
}
if loginType == "" {
loginType = LoginTypeDeviceID
}
createTime := time.Now().Unix()
eventID := newEventId(dataReport.ReportAppId(), user.UID, "", createTime)
event := dataReport.NewCommonField(eventID, dataReport.EventTypeUserLogin, user.UID, user.DistrictCode, ua, ip)
event.Payload = jsonRawMessage(&dataReport.UserLoginEvent{
Type: loginType,
})
return persistEvent(t, event)
}
func LoginTypeEventValue(loginType int) string {
switch loginType {
case usermod.MobileLogin:
return LoginTypePhone
case usermod.DevIDLogin, usermod.TokenLogin, usermod.QrCodeLogin:
return LoginTypeDeviceID
default:
return LoginTypeDeviceID
}
}
func LoginTypeFromH5(p usermod.LoginWithH5) string {
switch {
case strings.TrimSpace(p.Mobile) != "":
return LoginTypePhone
case strings.TrimSpace(p.Email) != "":
return LoginTypeEmail
case strings.TrimSpace(p.Account) != "":
return LoginTypeAccount
default:
return LoginTypeDeviceID
}
}
+204
View File
@@ -0,0 +1,204 @@
package reportser
import (
"91jd-server/common/dataReport"
"91jd-server/common/db"
"91jd-server/middleware/ua"
"91jd-server/models/v/eventmod"
"91jd-server/models/v/rchgordmod"
"91jd-server/models/v/usermod"
"91jd-server/models/v/vidmod"
"crypto/md5"
"encoding/hex"
"encoding/json"
"fmt"
"strconv"
"time"
)
// ReportOrderCreatedEvent 订单创建事件
func ReportOrderCreatedEvent(t *db.MongoTool, user *usermod.User, ua ua.UA, ip string, order *rchgordmod.RechargeOrder) (err error) {
orderType := "coin_purchase"
page := KeyCoinPage
coin := order.Amount
if order.Category == 1 {
orderType = "vip_subscription"
page = KeyVipPage
coin = 0
}
eventId := newEventId(dataReport.ReportAppId(), user.UID, order.ID.Hex(), order.CreatedAt.Unix())
event := dataReport.NewCommonField(eventId, dataReport.EventTypeOrderCreated, user.UID, user.DistrictCode, ua, ip)
var vipDurationID string
var vipDurationName string
if order.Category == 1 {
vipDurationID = order.ProductID.Hex()
vipDurationName = order.ProductName
}
event.Payload = jsonRawMessage(&dataReport.OrderCreatedEvent{
OrderID: order.ID.Hex(), // 订单号(唯一标识)
OrderType: orderType, // 订单类型
ProductID: order.ProductID.Hex(), // 商品ID
ProductName: order.ProductName, // 商品名称
Amount: jsonNumber(order.Money), // 订单金额(分)
Currency: "CNY", // 货币类型
CoinQuantity: jsonNumber(coin), // 金币数量
VIPDurationID: vipDurationID,
VIPDurationName: vipDurationName,
SourcePageKey: page.Key(), // 来源页面标识
SourcePageName: page.Name(), // 来源页面名称
CreateTime: jsonNumber(order.CreatedAt.Unix()),
})
b, _ := json.Marshal(event)
// 写入数据库
return eventmod.Insert(t, &eventmod.Event{
EventId: event.EventID,
EventType: event.Event,
Data: string(b),
Status: eventmod.Default,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
})
}
// ReportOrderPaidEvent 订单支付事件
func ReportOrderPaidEvent(t *db.MongoTool, order rchgordmod.RechargeOrder, transactionID string, amount int64, coin int64, vipExpirationTime, successAt time.Time) (err error) {
user, err := usermod.FindUserByUID(order.UID)
if err != nil || user == nil {
return
}
orderType := "coin_purchase"
var vipExpirationUnix int64
if order.Category == 1 {
orderType = "vip_subscription"
coin = 0
if vipExpirationTime.Unix() > 0 {
vipExpirationUnix = vipExpirationTime.Unix()
}
}
eventId := newEventId(dataReport.ReportAppId(), user.UID, order.ID.Hex(), successAt.Unix())
event := dataReport.NewCommonField(eventId, dataReport.EventTypeOrderPaid, user.UID, user.DistrictCode, order.Ua, order.UserIP)
event.Payload = jsonRawMessage(&dataReport.OrderPaidEvent{
OrderID: order.ID.Hex(), // 订单号(关联创建事件)
OrderType: orderType, // 订单类型:coin_purchase, vip_subscription
ProductID: order.ProductID.Hex(), // 商品ID
Amount: jsonNumber(amount), // 实际支付金额(分)
Currency: "CNY", // 货币类型:CNY, USD
CoinQuantity: jsonNumber(coin), // 金币数量(仅金币订单)
VIPExpirationTime: jsonNumber(vipExpirationUnix), // VIP过期时间(10位时间戳)
PayType: order.RechargeType, // 支付方式:wechat, alipay, bank_card
PayChannel: "", // 支付渠道:银行名称
TransactionID: transactionID, // 第三方支付交易号
CreateTime: jsonNumber(successAt.Unix()),
})
b, _ := json.Marshal(event)
// 写入数据库
return eventmod.Insert(t, &eventmod.Event{
EventId: event.EventID,
EventType: event.Event,
Data: string(b),
Status: eventmod.Default,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
})
}
// ReportCoinConsumeEvent 金币消耗事件
func ReportCoinConsumeEvent(t *db.MongoTool, orderId string, orderCreatedAt time.Time, user *usermod.User, ua ua.UA, ip string, productId, productName string, amount, balanceBefore, balanceAfter int64, reason Key) (err error) {
if amount == 0 {
return
}
if productId == "" {
if productName == "" {
productName = "金币消耗"
}
productId = Md5String(productName)
}
eventId := newEventId(dataReport.ReportAppId(), user.UID, orderId, orderCreatedAt.Unix())
event := dataReport.NewCommonField(eventId, dataReport.EventTypeCoinConsume, user.UID, user.DistrictCode, ua, ip)
event.Payload = jsonRawMessage(&dataReport.CoinConsumeEvent{
ProductID: productId, // 商品ID
ProductName: productName, // 商品名称
CoinConsumeAmount: jsonNumber(amount), // 金币消耗数量
CoinBalanceBefore: jsonNumber(balanceBefore), // 消耗前金币余额
CoinBalanceAfter: jsonNumber(balanceAfter), // 消耗后金币余额
ConsumeReasonKey: reason.Key(), // 消耗原因
ConsumeReasonName: reason.Name(), // 消耗原因名称
CreateTime: jsonNumber(orderCreatedAt.Unix()),
OrderId: orderId,
})
b, _ := json.Marshal(event)
// 写入数据库
return eventmod.Insert(t, &eventmod.Event{
EventId: event.EventID,
EventType: event.Event,
Data: string(b),
Status: eventmod.Default,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
})
}
// ReportVideoPurchase 视频购买事件
func ReportVideoPurchase(t *db.MongoTool, user *usermod.User, ua ua.UA, ip string, video *vidmod.VideoModel, orderId string) (err error) {
meta := extractVideoMeta(video)
event := dataReport.NewCommonField(dataReport.GenerateGlobalEventID(), dataReport.EventTypeVideoPurchase, user.UID, user.DistrictCode, ua, ip)
event.Payload = jsonRawMessage(&dataReport.VideoPurchaseEvent{
VideoEventCore: meta.toEventCore(video.ID.Hex(), video.Title, ""),
CoinQuantity: jsonNumber(video.Coins), // 金币数量
OrderID: orderId, // 订单号
})
b, _ := json.Marshal(event)
// 写入数据库
return eventmod.Insert(t, &eventmod.Event{
EventId: event.EventID,
EventType: event.Event,
Data: string(b),
Status: eventmod.Default,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
})
}
// ReportUserRegister 用户注册事件
func ReportUserRegister(t *db.MongoTool, user *usermod.User, ua ua.UA, ip string) (err error) {
registerType := "deviceid"
eventId := newEventId(dataReport.ReportAppId(), user.UID, "", user.CreatedAt.Unix())
event := dataReport.NewCommonField(eventId, dataReport.EventTypeUserRegister, user.UID, user.DistrictCode, ua, ip)
event.Payload = jsonRawMessage(&dataReport.UserRegisterEvent{
Type: registerType,
TraceID: user.Tid,
CreateTime: jsonNumber(user.CreatedAt.Unix()),
})
b, _ := json.Marshal(event)
// 写入数据库
return eventmod.Insert(t, &eventmod.Event{
EventId: event.EventID,
EventType: event.Event,
Data: string(b),
Status: eventmod.Default,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
})
}
func newEventId(appId string, uid uint64, orderId string, createTime int64) string {
input := fmt.Sprintf("%v%v%v%v", appId, uid, orderId, createTime)
return Md5String(input)
}
func jsonNumber(v int64) json.Number {
return json.Number(strconv.FormatInt(v, 10))
}
func jsonRawMessage(v interface{}) json.RawMessage {
b, _ := json.Marshal(v)
return b
}
func Md5String[T string | []byte](input T) string {
h := md5.New()
h.Write([]byte(input))
return hex.EncodeToString(h.Sum(nil))
}
+141
View File
@@ -0,0 +1,141 @@
package reportser
import (
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
"sync"
"time"
"91jd-server/app/appg"
"91jd-server/common/log"
"github.com/robfig/cron/v3"
)
const (
sdkConfigPath = "/sdk/getAvailableDomainList"
sdkConfigCacheKey = "report:sdk:domain_config"
sdkConfigCacheTTL = 30 * time.Minute
sdkConfigFetchInterval = "0 */5 * * * *"
)
var (
sdkConfigCron *cron.Cron
sdkConfigOnce sync.Once
sdkConfigMu sync.RWMutex
sdkDomainConfig string
sdkConfigUpdatedAt time.Time
)
type sdkConfigResponse struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data string `json:"data"`
}
// StartSDKConfigCron 定时拉取 SDK 域名配置串,服务端只缓存并原样透传。
func StartSDKConfigCron() {
sdkConfigOnce.Do(func() {
if err := RefreshSDKDomainConfig(); err != nil {
log.Error("RefreshSDKDomainConfig first run fail", log.E(err))
}
sdkConfigCron = cron.New(cron.WithSeconds())
if _, err := sdkConfigCron.AddFunc(sdkConfigFetchInterval, func() {
if err := RefreshSDKDomainConfig(); err != nil {
log.Error("RefreshSDKDomainConfig cron fail", log.E(err))
}
}); err != nil {
log.Error("StartSDKConfigCron AddFunc fail", log.E(err))
return
}
sdkConfigCron.Start()
})
}
func StopSDKConfigCron() {
if sdkConfigCron == nil {
return
}
ctx := sdkConfigCron.Stop()
<-ctx.Done()
}
func RefreshSDKDomainConfig() error {
apiURL := strings.TrimRight(appg.Conf.DataReport.Url, "/") + sdkConfigPath
data, err := fetchSDKDomainConfig(apiURL, &http.Client{Timeout: 5 * time.Second})
if err != nil {
return err
}
setSDKDomainConfig(data)
if appg.Redis == nil {
return nil
}
return appg.Redis.Set(sdkConfigCacheKey, data, sdkConfigCacheTTL)
}
func GetSDKDomainConfig() string {
sdkConfigMu.RLock()
cached := sdkDomainConfig
updatedAt := sdkConfigUpdatedAt
sdkConfigMu.RUnlock()
if cached != "" && time.Since(updatedAt) < time.Minute {
return cached
}
if appg.Redis != nil {
data, err := appg.Redis.Get(sdkConfigCacheKey)
if err != nil {
log.Error("GetSDKDomainConfig redis get fail", log.E(err))
} else if data != nil && *data != "" {
setSDKDomainConfig(*data)
return *data
}
}
return cached
}
func setSDKDomainConfig(data string) {
sdkConfigMu.Lock()
defer sdkConfigMu.Unlock()
sdkDomainConfig = data
sdkConfigUpdatedAt = time.Now()
}
func fetchSDKDomainConfig(apiURL string, client *http.Client) (string, error) {
req, err := http.NewRequest(http.MethodGet, apiURL, nil)
if err != nil {
return "", err
}
req.Header.Set("Accept", "application/json")
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("sdk config http status: %d", resp.StatusCode)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
}
var result sdkConfigResponse
if err := json.Unmarshal(body, &result); err != nil {
return "", err
}
if result.Code != 0 {
return "", fmt.Errorf("sdk config code: %d, msg: %s", result.Code, result.Msg)
}
if strings.TrimSpace(result.Data) == "" {
return "", fmt.Errorf("sdk config data is empty")
}
return result.Data, nil
}
+48
View File
@@ -0,0 +1,48 @@
package reportser
import (
"fmt"
"net/http"
"net/http/httptest"
"testing"
)
func TestFetchSDKDomainConfig(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
_, _ = fmt.Fprint(w, `{"code":0,"msg":"success","data":"domain-config"}`)
}))
defer server.Close()
got, err := fetchSDKDomainConfig(server.URL, server.Client())
if err != nil {
t.Fatalf("fetchSDKDomainConfig error = %v", err)
}
if got != "domain-config" {
t.Fatalf("data = %q, want domain-config", got)
}
}
func TestFetchSDKDomainConfigRejectsFailedResponse(t *testing.T) {
for _, tc := range []struct {
name string
body string
}{
{name: "non-zero code", body: `{"code":1,"msg":"fail","data":"domain-config"}`},
{name: "empty data", body: `{"code":0,"msg":"success","data":""}`},
} {
t.Run(tc.name, func(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, _ = fmt.Fprint(w, tc.body)
}))
defer server.Close()
if got, err := fetchSDKDomainConfig(server.URL, server.Client()); err == nil {
t.Fatalf("fetchSDKDomainConfig = %q, want error", got)
}
})
}
}
+48
View File
@@ -0,0 +1,48 @@
package reportser
type Key string
type ConsumeReasonKey = Key
const (
KeyBuyVip Key = "content_purchase" // 购买会员卡
KeyVideoUnlock Key = "video_unlock" // 视频解锁
KeyPostUnlock Key = "content_purchase" // 帖子解锁
KeyAcgUnlock Key = "content_purchase" // acg解锁
KeyAcgChapterUnlock Key = "content_purchase" // acg章节解锁
KeyGenerateAiImageChangeFace Key = "content_purchase" // 生成ai图片换脸
KeyGenerateAiVideoChangeFace Key = "content_purchase" // 生成ai视频换脸
KeyGenerateAiNovel Key = "content_purchase" // 生成ai小说
KeyGenerateAiImageToVideo Key = "content_purchase" // 生成ai图生视频
KeyGenerateAiTextToImage Key = "content_purchase" // 生成ai文生图
KeyGenerateAiUndress Key = "content_purchase" // 生成ai脱衣
KeyAiGirlFriendExchangeIntegral Key = "content_purchase" // ai女友兑换积分
KeyLottery Key = "content_purchase" // 抽奖
KeyGiftSend Key = "gift_send" // 礼物赠送
ConsumeReasonVideoUnlock ConsumeReasonKey = KeyVideoUnlock
ConsumeReasonGiftSend ConsumeReasonKey = KeyGiftSend
ConsumeReasonContentPurchase ConsumeReasonKey = KeyBuyVip
KeyHomePage Key = "homePage" // 首页
KeyVipPage Key = "vipPage" // 会员卡页面
KeyCoinPage Key = "coinPage" // 金币充值页面
)
var keyMap = map[Key]string{
// 购买解锁相关
KeyBuyVip: "内容购买",
KeyVideoUnlock: "视频解锁",
KeyGiftSend: "礼物赠送",
// 页面相关
KeyHomePage: "首页",
KeyVipPage: "会员卡页面",
KeyCoinPage: "金币充值页面",
}
func (p Key) Name() string {
return keyMap[p]
}
func (p Key) Key() string {
return string(p)
}
+191
View File
@@ -0,0 +1,191 @@
package reportser
import (
"strings"
"91jd-server/common/dataReport"
"91jd-server/common/db"
"91jd-server/common/log"
"91jd-server/middleware/ua"
"91jd-server/models/v/moduleconfmod"
"91jd-server/models/v/usermod"
"91jd-server/models/v/vidmod"
"go.mongodb.org/mongo-driver/bson/primitive"
)
type videoMeta struct {
mediaID string
videoTypeID string
videoTypeName string
videoContentType string
}
func (m videoMeta) toEventCore(videoID, title string, recommendTraceID string) dataReport.VideoEventCore {
return dataReport.VideoEventCore{
MediaID: m.mediaID,
VideoID: videoID,
VideoTitle: title,
VideoTypeID: m.videoTypeID,
VideoTypeName: m.videoTypeName,
VideoContentType: m.videoContentType,
RecommendTraceID: recommendTraceID,
}
}
func ReportVideoLikeEvent(t *db.MongoTool, user *usermod.User, ua ua.UA, ip string, video *vidmod.VideoModel, cancel bool) error {
if user == nil || video == nil || video.ID.IsZero() {
return nil
}
flag := int64(1)
if cancel {
flag = 2
}
meta := extractVideoMeta(video)
event := dataReport.NewCommonField(dataReport.GenerateGlobalEventID(), dataReport.EventTypeVideoLike, user.UID, user.DistrictCode, ua, ip)
event.Payload = jsonRawMessage(&dataReport.VideoLikeEvent{
VideoEventCore: meta.toEventCore(video.ID.Hex(), video.Title, ""),
Flag: jsonNumber(flag),
})
return persistEvent(t, event)
}
func ReportVideoCollectEvent(t *db.MongoTool, user *usermod.User, ua ua.UA, ip string, video *vidmod.VideoModel, cancel bool) error {
if user == nil || video == nil || video.ID.IsZero() {
return nil
}
flag := int64(1)
if cancel {
flag = 2
}
meta := extractVideoMeta(video)
event := dataReport.NewCommonField(dataReport.GenerateGlobalEventID(), dataReport.EventTypeVideoCollect, user.UID, user.DistrictCode, ua, ip)
event.Payload = jsonRawMessage(&dataReport.VideoCollectEvent{
VideoEventCore: meta.toEventCore(video.ID.Hex(), video.Title, ""),
Flag: jsonNumber(flag),
})
return persistEvent(t, event)
}
func ReportVideoCommentEvent(t *db.MongoTool, user *usermod.User, ua ua.UA, ip string, video *vidmod.VideoModel, comment string) error {
if user == nil || video == nil || video.ID.IsZero() {
return nil
}
meta := extractVideoMeta(video)
event := dataReport.NewCommonField(dataReport.GenerateGlobalEventID(), dataReport.EventTypeVideoComment, user.UID, user.DistrictCode, ua, ip)
event.Payload = jsonRawMessage(&dataReport.VideoCommentEvent{
VideoEventCore: meta.toEventCore(video.ID.Hex(), video.Title, ""),
CommentContent: comment,
})
return persistEvent(t, event)
}
func ReportVideoStatusChangeEvent(video *vidmod.VideoModel, flag int64) {
if video == nil || video.ID.IsZero() || flag == 0 {
return
}
user, err := usermod.FindUserByUID(video.PublisherID)
if err != nil || user == nil {
return
}
meta := extractVideoMeta(video)
uaInfo := ua.UA{SysType: "PC"}
event := dataReport.NewCommonField(dataReport.GenerateGlobalEventID(), dataReport.EventTypeVideoStatusChange, user.UID, user.DistrictCode, uaInfo, "")
event.Payload = jsonRawMessage(&dataReport.VideoStatusChangeEvent{
MediaID: meta.mediaID,
VideoID: video.ID.Hex(),
VideoTitle: video.Title,
VideoTypeID: meta.videoTypeID,
VideoTypeName: meta.videoTypeName,
Flag: jsonNumber(flag),
})
if err := persistEvent(nil, event); err != nil {
log.Error("ReportVideoStatusChangeEvent fail", log.Any("video", video.ID.Hex()), log.E(err))
}
}
func ReportVideoStatusChangeByIDs(ids []primitive.ObjectID, flag int64) {
if len(ids) == 0 || flag == 0 {
return
}
videos, err := vidmod.GetVideoListByIDsNoStatus(ids)
if err != nil {
log.Error("ReportVideoStatusChangeByIDs fail", log.E(err))
return
}
for _, video := range videos {
if video == nil || video.ID.IsZero() {
continue
}
videoCopy := *video
ReportVideoStatusChangeEvent(&videoCopy, flag)
}
}
func ReportVideoStatusByStatus(ids []primitive.ObjectID, status int) {
if flag := VideoStatusFlag(status); flag != 0 {
ReportVideoStatusChangeByIDs(ids, flag)
}
}
func extractVideoMeta(video *vidmod.VideoModel) videoMeta {
if video == nil {
return videoMeta{}
}
meta := videoMeta{
mediaID: videoMediaID(video),
videoContentType: resolveVideoContentType(video),
}
if strings.TrimSpace(video.MID) != "" {
oid, err := primitive.ObjectIDFromHex(video.MID)
if err == nil {
module, _ := moduleconfmod.GetByID(oid)
if !module.ID.IsZero() {
meta.videoTypeID = module.ID.Hex()
meta.videoTypeName = module.ModuleName
}
}
}
if meta.videoTypeID == "" || meta.videoTypeName == "" {
meta.videoTypeID = "default"
meta.videoTypeName = "默认分类"
}
return meta
}
func videoMediaID(video *vidmod.VideoModel) string {
if video == nil {
return ""
}
if strings.EqualFold(strings.TrimSpace(video.Via), "laosiji") {
return strings.TrimSpace(video.SourceID)
}
return ""
}
func resolveVideoContentType(video *vidmod.VideoModel) string {
if video == nil {
return ""
}
switch video.NewsType {
case vidmod.SHORT:
return "short_video"
default:
return "video"
}
}
func VideoStatusFlag(status int) int64 {
switch status {
case vidmod.OnlinePass:
return 1
case vidmod.OffShelf, vidmod.IsDeleted:
return 2
case vidmod.CheckPass, vidmod.Free:
return 3
case vidmod.CheckFailure:
return 4
default:
return 0
}
}