diff --git a/app/api/adsapi/click.go b/app/api/adsapi/click.go new file mode 100644 index 0000000..f673027 --- /dev/null +++ b/app/api/adsapi/click.go @@ -0,0 +1,31 @@ +package adsapi + +import ( + "strconv" + + "videoweb/app/api/proto" + "videoweb/app/service/configser" + "videoweb/pub/common" + "videoweb/pub/common/srvdef" + + "github.com/gin-gonic/gin" +) + +// Click 广告点击上报 +// @Tags 广告 +// @Summary 广告点击上报 +// @Description 用户点击广告时异步上报到广告中心。请求可带 appId 覆盖默认,缺省时按本站 appId 上报。 +// @Accept json +// @Produce json +// @Param param body proto.AdsClickInfo true "参数" +// @Success 200 {string} json +// @Router /api/app/ads/click [POST] +func Click(c *gin.Context) { + var req proto.AdsClickInfo + if err := c.ShouldBindJSON(&req); err != nil { + common.ServerJSON(c, srvdef.ErrParamError, err.Error()) + return + } + configser.AsyncAdvertiseClick(strconv.FormatInt(req.AdsId, 10), int32(req.AppId)) + common.ServerJSON(c, srvdef.Success, nil) +} diff --git a/app/conf/app.yaml b/app/conf/app.yaml index 64cf544..739f0dc 100644 --- a/app/conf/app.yaml +++ b/app/conf/app.yaml @@ -26,6 +26,7 @@ attack: pprof: false host: "172.104.166.87" listenAddr: "20101" +versionUrl: "http://139.162.47.194:8089" origDomain: "https://yuan.bbaazz.me/" trustedProxies: "" bucketVideoAddressV2: "http://139.162.27.29:19996" diff --git a/app/router/ads.go b/app/router/ads.go new file mode 100644 index 0000000..9d132a0 --- /dev/null +++ b/app/router/ads.go @@ -0,0 +1,11 @@ +package router + +import ( + "videoweb/app/api/adsapi" + + "github.com/gin-gonic/gin" +) + +func adsRouter(r *gin.Engine) { + r.POST("/api/app/ads/click", adsapi.Click) +} diff --git a/app/router/gin.go b/app/router/gin.go index 8ea586f..ec43701 100644 --- a/app/router/gin.go +++ b/app/router/gin.go @@ -119,6 +119,7 @@ func registeRouter(r *gin.Engine) { appInfoRouter(r) translationRouter(r) statcenterRouter(r) + adsRouter(r) r.GET("/notpage", templatecache.WithCache(time.Minute*1), home.PageNotFound) } diff --git a/app/service/configser/advertise_click.go b/app/service/configser/advertise_click.go new file mode 100644 index 0000000..ab070ac --- /dev/null +++ b/app/service/configser/advertise_click.go @@ -0,0 +1,66 @@ +package configser + +import ( + "encoding/json" + "fmt" + "net/http" + "strconv" + "strings" + + "videoweb/app/appg" + "videoweb/pub/common/godef" + "videoweb/pub/common/httputil" + "videoweb/pub/common/log" +) + +type advertiseClickReq struct { + Id int64 `json:"id"` + AppId int32 `json:"appId"` +} + +type advertiseClickResp struct { + Code int64 `json:"code"` +} + +// resolveAdvertiseAppId 请求带了 AppId 用请求的,否则回落到默认 advertiseAppId +func resolveAdvertiseAppId(reqAppId int32) int32 { + if reqAppId > 0 { + return reqAppId + } + return advertiseAppId +} + +// AdvertiseClickCenter 同步上报单条广告点击到广告中心。 +// appId<=0 时回落到默认 advertiseAppId。 +func AdvertiseClickCenter(advertiseId int64, appId int32) error { + versionUrl := strings.TrimRight(strings.TrimSpace(appg.Conf.VersionUrl), "/") + if versionUrl == "" { + return fmt.Errorf("versionUrl 未配置") + } + url := versionUrl + "/api/stat/advitise/v2/click" + body, _ := json.Marshal(advertiseClickReq{Id: advertiseId, AppId: resolveAdvertiseAppId(appId)}) + var resp advertiseClickResp + code, err := httputil.Client().POSTJsonWithJResp(&resp, url, map[string]string{"Content-Type": "application/json"}, body) + if err != nil { + log.Error("AdvertiseClickCenter POST fail", log.Any("url", url), log.Any("id", advertiseId), log.E(err)) + return err + } + if code != http.StatusOK { + log.Error("AdvertiseClickCenter response status", log.Any("code", code), log.Any("id", advertiseId)) + return fmt.Errorf("response status %d", code) + } + return nil +} + +// AsyncAdvertiseClick 异步上报广告点击,非数字 id 忽略。 +// appId<=0 时回落到默认 advertiseAppId。 +func AsyncAdvertiseClick(id string, appId int32) { + advertiseId, err := strconv.ParseInt(strings.TrimSpace(id), 10, 64) + if err != nil { + log.Warn("AsyncAdvertiseClick invalid id", log.Any("id", id)) + return + } + godef.Go(func() { + _ = AdvertiseClickCenter(advertiseId, appId) + }) +} diff --git a/app/service/configser/announcement.go b/app/service/configser/announcement.go index f2f3002..5296ad1 100644 --- a/app/service/configser/announcement.go +++ b/app/service/configser/announcement.go @@ -2,60 +2,112 @@ package configser import ( "context" + "encoding/json" + "fmt" "net/http" + "sort" + "strconv" + "strings" + "videoweb/app/appg" + "videoweb/pub/common/httputil" "videoweb/pub/common/log" "videoweb/pub/common/netutil" - "videoweb/pub/pubdef" - "videoweb/pub/storage" "videoweb/pub/storage/proxy" ) -// UpdateAdvitiseData 从媒资中心按 AppId 拉取广告,图片拼接 CDN 后写入缓存 -func UpdateAdvitiseData(ctx context.Context) { - config := GetConfigInfo() - queryAdvitiseReq := &proxy.AppAdvitiseInfoReq{ - Id: pubdef.AppId, +// 广告中心应用ID(本站专用) +const advertiseAppId int32 = 12 + +// advertiseInfo 广告中心 /api/stat/advitise/v2/get 的广告条目 +type advertiseInfo struct { + Id int64 `json:"id"` // 广告id + Title string `json:"title"` // 广告标题 + CoverImg string `json:"coverImg"` // 封面 + LocId int32 `json:"locId"` // 位置id + JumpType int32 `json:"jumpType"` // 跳转方式 + Link string `json:"link"` // 链接地址 + Sort int64 `json:"sort"` // 排序 + Tags string `json:"tags"` // 标签 + Remark string `json:"remark"` // 备注描述 +} + +type advertiseReq struct { + AppId int32 `json:"appId"` +} + +type advertiseRes struct { + AdvertiseList []advertiseInfo `json:"advertiseList"` +} + +// fetchAdvertiseList 请求广告中心 /api/stat/advitise/v2/get +func fetchAdvertiseList() (*advertiseRes, error) { + versionUrl := strings.TrimRight(strings.TrimSpace(appg.Conf.VersionUrl), "/") + if versionUrl == "" { + return nil, fmt.Errorf("versionUrl 未配置") } - advitiseResult, err := storage.QueryAdvitiseList(ctx, queryAdvitiseReq, "") - if err != nil || advitiseResult.Code != http.StatusOK { - if advitiseResult != nil { - log.Warn("UpdateAdvitiseData.QueryAdvitiseList failed", log.Any("filter", queryAdvitiseReq), log.E(err), log.Any("respCode", advitiseResult.Code)) - } else { - log.Warn("UpdateAdvitiseData.QueryAdvitiseList failed", log.Any("filter", queryAdvitiseReq), log.E(err)) - } + url := versionUrl + "/api/stat/advitise/v2/get" + body, _ := json.Marshal(advertiseReq{AppId: advertiseAppId}) + var resp advertiseRes + code, err := httputil.Client().POSTJsonWithJResp(&resp, url, map[string]string{"Content-Type": "application/json"}, body) + if err != nil { + return nil, err + } + if code != http.StatusOK { + return nil, fmt.Errorf("response status %d", code) + } + return &resp, nil +} + +// UpdateAdvitiseData 从广告中心拉取广告并更新内存缓存,列表为空时重试一次 +func UpdateAdvitiseData(ctx context.Context) { + resp, err := fetchAdvertiseList() + if err != nil { + log.Error("UpdateAdvitiseData fetchAdvertiseList fail", log.E(err)) return } + if len(resp.AdvertiseList) == 0 { + resp, err = fetchAdvertiseList() + if err != nil { + log.Error("UpdateAdvitiseData fetchAdvertiseList retry fail", log.E(err)) + return + } + } + list := resp.AdvertiseList + sort.SliceStable(list, func(i, j int) bool { + if list[i].LocId != list[j].LocId { + return list[i].LocId < list[j].LocId + } + return list[i].Sort < list[j].Sort + }) - data := advitiseResult.Data - for _, list := range []*[]proxy.AdvitiseInfo{ - &data.DialogList, - &data.HomeList, - &data.VideoTopList, - &data.VideoDownList, - &data.HotAppList, - &data.NewAppList, - &data.MustAppList, - &data.TitleAppList, - &data.FriendList, - &data.NineGridList, - &data.VideoList, - &data.StopList, - &data.ForcedJumpList, - &data.AppInfoAList, - &data.AppInfoBList, - } { - mergeAdvitiseImages(*list, config.ImgCdn) + config := GetConfigInfo() + data := proxy.AdvitiseData{} + + for _, v := range list { + pos := int64(v.LocId) + info := proxy.AdvitiseInfo{ + Id: strconv.FormatInt(v.Id, 10), + Location: pos, + Title: v.Title, + Images: netutil.MergeURL(config.ImgCdn, strings.TrimLeft(v.CoverImg, "/")), + Link: strings.ReplaceAll(v.Link, "inner://yinseinner/", "yinseinner://"), + } + switch pos { + case 1: + data.DialogList = append(data.DialogList, info) + case 2: + data.NineGridList = append(data.NineGridList, info) + case 3: + data.VideoTopList = append(data.VideoTopList, info) + data.VideoDownList = append(data.VideoDownList, info) + case 12: + data.AppInfoAList = append(data.AppInfoAList, info) + case 13: + data.AppInfoBList = append(data.AppInfoBList, info) + } } CacheMgr.Lock.Lock() defer CacheMgr.Lock.Unlock() CacheMgr.Advitise = data } - -func mergeAdvitiseImages(list []proxy.AdvitiseInfo, cdn string) { - for i := range list { - if len(list[i].Images) > 0 { - list[i].Images = netutil.MergeURL(cdn, list[i].Images) - } - } -}