package addata import ( "encoding/json" "fmt" log "github.com/sirupsen/logrus" "html" "io/ioutil" "math/rand" "miads/adslib" "miads/adslib/device" "miads/adslib/redis_data" "miads/adslib/utils" "net/http" "net/url" "strconv" "strings" ) type XiaomiAdData struct { DetailURL string `json:"detail_url"` DisplayType struct { Delay int64 `json:"delay"` Name string `json:"name"` RowCount int64 `json:"row_count"` } `json:"display_type"` Duration int64 `json:"duration"` EmcType string `json:"emc_type"` ID string `json:"id"` ImageURL string `json:"image_url"` Proportion string `json:"proportion"` Settings struct { ClickType string `json:"click_type"` HideCloseAt int64 `json:"hide_close_at"` LogTime string `json:"log_time"` ShowCloseAt int64 `json:"show_close_at"` } `json:"settings"` SkipTime int64 `json:"skip_time"` TagID string `json:"tag_id"` Target string `json:"target"` TargetAddition []string `json:"target_addition"` TargetAddition1 string `json:"target_addition1"` Title string `json:"title"` VideoURL string `json:"video_url"` } type XiaoMiAdInfoRsp struct { Data []XiaomiAdData `json:"data"` Msg string `json:"msg"` Result int64 `json:"result"` } type AdAction struct { Type string `json:"type"` Duration int `json:"duration"` Urls []string `json:"urls"` } type AdInfo struct { AdActions []AdAction `json:"ads_infos"` PercentBegin int `json:"percent_begin"` PercentEnd int `json:"percent_end"` } type AdData struct { TargetAddition []AdAction `json:"target_addition,omitempty"` Target string `json:"target,omitempty"` ImageUrl string `json:"image_url,omitempty"` Duration int64 `json:"duration,omitempty"` VideoUrl string `json:"video_url,omitempty"` JsOrderId int64 `json:"js_order_id,omitempty"` UserAgent string `json:"user_agent,omitempty"` DpReport string `json:"dp_report,omitempty"` Dp string `json:"dp,omitempty"` OrderName string `json:"order_name,omitempty"` Result int `json:"result"` Msg string `json:"msg"` } func getAdsReqUrl(dsp *utils.DspParam) string { reqUrl := "https://m.video.xiaomi.com/api/a3/otv_emc?" ref := "yilin" sn := "05817a33d4210ad2c67f4b869b5eedde" // 组装 emcp reqUrl = reqUrl + "_emcp=pre_play" // 组装 devid reqUrl = reqUrl + "&_devid=" + dsp.RealMd5Imei // 组装 ref reqUrl = reqUrl + "&ref=" + ref // 组装 sn reqUrl = reqUrl + "&_sn=" + sn // 组装 ip reqUrl = reqUrl + "&__ip__=" + dsp.Ip rad := rand.Intn(100) if rad < 15 { dsp.SendPhoneType = 1 } else { dsp.SendPhoneType = 0 } reqUrl = reqUrl + fmt.Sprintf("&_phonetype=%d", dsp.SendPhoneType) return reqUrl } // 获取广告信息 func GetAdsInfos(dsp *utils.DspParam, advertiser string) (*AdData, error) { reqUrl := getAdsReqUrl(dsp) if dsp.SendPhoneType == 0 { device.SetAdsTagLog("xiaomi", dsp.ReqSource, "ADS_XIAOMI", dsp.DspCityCode) } else if dsp.SendPhoneType == 1 { device.SetAdsTagLog("xiaomi", dsp.ReqSource, "ADS_XIAOMI_1", dsp.DspCityCode) } client := &http.Client{} log.WithField("request_id", dsp.RequestId).Infof("req url: %s", reqUrl) req, err := http.NewRequest("GET", reqUrl, nil) if err != nil { return nil, err } req.Header.Set("authority", "m.video.xiaomi.com") req.Header.Set("method", "GET") req.Header.Set("path", reqUrl[26:]) req.Header.Set("scheme", "https") req.Header.Set("content-type", "application/json") req.Header.Set("accept-language", "zh-CN,zh;q=0.9") req.Header.Set("cache-control", "max-age=0") req.Header.Set("upgrade-insecure-requests", "1") req.Header.Set("user-agent", dsp.Ua) resp, err := client.Do(req) if err != nil { log.WithField("request_id", dsp.RequestId).Errorf("http failed: %s", err) return nil, err } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { log.WithField("request_id", dsp.RequestId).Errorf("read http body failed: %s", err) return nil, err } log.WithField("request_id", dsp.RequestId).Infof("rsp: %s", string(body)) rsp := XiaoMiAdInfoRsp{} err = json.Unmarshal([]byte(string(body)), &rsp) if err != nil { log.WithField("request_id", dsp.RequestId).Errorf("unmarshal http rsp failed: %s, rsp: %s", err, string(body)) return nil, err } adsDataNum := len(rsp.Data) xiaomiResponseAction := map[string]int{ "xiaomi_view": 0, "xiaomi_click": 0, "xiaomi_close": 0, "xiaomi_video_finish": 0, "xiaomi_video_timer": 0, } if rsp.Result != 1 { return nil, nil } // bom渠道不处理 if dsp.ReqSource != "bom" { rad := rand.Intn(100) if rad < 2 { dsp.ReqSource = "kk_h5" } else if rad < 4 { dsp.ReqSource = "day_09" } else if rad < 8 { dsp.ReqSource = "fu008" } else if rad < 9 { dsp.ReqSource = "yzh01" } else if rad < 11 { dsp.ReqSource = "qihuang88" } } if len(rsp.Data) == 0 { return nil, nil } ///last_infos = xiaomi_mix.xiaomi_fuse(data, dsp.req_source) dataInfo := rsp.Data[0] dsp.AllDuration = dataInfo.Duration dsp.VideoTimeDuration = 7 showUrls := make([]string, 0, 100) clickUrls := make([]string, 0, 100) closeUrls := make([]string, 0, 100) videoFinishUrls := make([]string, 0, 100) videoTimerUrls := make([]string, 0, 100) for _, target := range rsp.Data[0].TargetAddition { target := html.UnescapeString(target) targetParseUrl, err := url.Parse(target) if err != nil { log.WithField("request_id", dsp.RequestId).Errorf("parse url failed, url: %s, err: %s", target, err) continue } targetParams := targetParseUrl.Query() targetUrl := targetParams.Get("url") event := targetParams.Get("event") if targetUrl != "" { switch event { case "VIEW": showUrls = append(showUrls, targetUrl) xiaomiResponseAction["xiaomi_view"] = 1 case "CLICK": clickUrls = append(clickUrls, targetUrl) xiaomiResponseAction["xiaomi_click"] = 1 case "CLOSE": closeUrls = append(closeUrls, targetUrl) xiaomiResponseAction["xiaomi_close"] = 1 case "VIDEO_FINISH": videoFinishUrls = append(videoFinishUrls, targetUrl) xiaomiResponseAction["xiaomi_video_finish"] = 1 case "VIDEO_TIMER": videoTimerUrls = append(videoTimerUrls, targetUrl) } } if event == "VIDEO_TIMER" { timeStr := targetParams.Get("time") if timeStr != "" { videoTimeDuration, _ := strconv.Atoi(timeStr) dsp.VideoTimeDuration = videoTimeDuration xiaomiResponseAction["xiaomi_video_timer"] = 1 } } } gotoUrls := make([]string, 0, 100) target := rsp.Data[0].Target if target != "" { targetUrlObj, _ := url.Parse(target) targetParams := targetUrlObj.Query() targetUrl := targetParams.Get("link_url") if targetUrl == "" { targetUrl = strings.ReplaceAll(targetUrl, "mv:", "") } targetUrl = html.UnescapeString(targetUrl) if targetUrl != "" { gotoUrls = append(gotoUrls, targetUrl) } } if dsp.ReqSource == "mh" { closeUrls = videoFinishUrls } videoUrls := make([]string, 0, 100) videoUrl := rsp.Data[0].VideoURL if videoUrl != "" { videoUrls = append(videoUrls, videoUrl) } imageUrls := make([]string, 0, 100) imageUrl := rsp.Data[0].ImageURL if imageUrl != "" { imageUrls = append(imageUrls, imageUrl) } fmt.Printf("%+v, addata_num: %d\n", xiaomiResponseAction, adsDataNum) if len(gotoUrls) == 0 { return nil, nil } fmt.Printf("goto urls: %+v\nshow urls: %+v\nclick_urls: %+v\nclose urls: %+v\nvideo finish urls: %+v\nimagurls: %+v\nvideo urls:%+v\n", gotoUrls, showUrls, clickUrls, closeUrls, videoFinishUrls, imageUrls, videoUrls) adsData, err := CombineLastAdsInfos(dsp, advertiser, gotoUrls, showUrls, clickUrls, closeUrls, videoFinishUrls, videoTimerUrls, videoUrls) if err != nil { return nil, err } fmt.Printf("addata: %+v\n", adsData) if dsp.SendPhoneType == 0 { redis_data.SetAdsRequestNum(advertiser, 0) } else if dsp.SendPhoneType == 1 { redis_data.SetAdsRequestNum(advertiser, 1) } return adsData, nil } func getActionsConf(advertiser string) []string { defaultActions := []string{"VIEW", "CLICK", "CLOSE", "VIDEO_FINISH", "VIDEO_TIMER"} svrConf := adslib.GetConf() actions, ok := svrConf.AdsActions[advertiser] if !ok || len(actions) == 0 { return defaultActions } return actions } func getCombinationActionsConf(advertiser string) []adslib.CombinationActionsConf { svrConf := adslib.GetConf() combinationActions, ok := svrConf.CombinationActions[advertiser] if !ok { return []adslib.CombinationActionsConf{} } return combinationActions } // 组装最后的广告信息 func CombineLastAdsInfos(dsp *utils.DspParam, advertiser string, gotoUrls []string, showUrls []string, clickUrls []string, closeUrls []string, videoFinishUrls []string, videoTimerUrls []string, videoUrls []string) (*AdData, error) { // 判断是不是关掉所有Action actions := getActionsConf(advertiser) if len(actions) == 0 { return nil, nil } clickChannelFlag, err := redis_data.GetChannelFlag(dsp.ReqSource, "ads_click") if err != nil { log.WithField("request_id", dsp.RequestId).Errorf("get ads_click channel flag failed: %s", err) return nil, err } log.WithField("request_id", dsp.RequestId).Tracef("click channel flag: %+v\n", clickChannelFlag) clickRandom := rand.Intn(100) needClick := false if clickChannelFlag.ChannelFlag == 1 && clickRandom <= clickChannelFlag.Weigth { needClick = true } lastInfos := make([]AdInfo, 0, 50) combinationActions := getCombinationActionsConf(advertiser) for _, combAction := range combinationActions { adActionDatas := make([]AdAction, 0, 20) percentBegin := combAction.PercentBegin percentEnd := combAction.PercentEnd actionGroup := combAction.GroupValue for _, action := range actionGroup { urls := make([]string, 0, 50) if dsp.ReqSource == "mh" { if action == "CLOSE" { action = "VIDEO_FINISH" } } switch action { case "VIEW": urls = showUrls case "CLICK": urls = clickUrls case "CLOSE": urls = closeUrls case "VIDEO_FINISH": urls = videoFinishUrls case "VIDEO_TIMER": urls = videoTimerUrls default: continue } // click的控制 控制bom if action == "CLICK" && dsp.RealReqSource == "bom" && dsp.SupClickFlag == 0 { continue } // show的控制 控制bom if action == "VIEW" && dsp.RealReqSource == "bom" && dsp.SupShowFlag == 0 { continue } if action == "CLICK" && !needClick { continue } if len(urls) == 0 && action == "VIDEO_TIMER" { continue } adActionData, err := genAdsAction(urls, advertiser, dsp, action, true) if err != nil { fmt.Printf("gen ads action data failed, err: %s\n", err) continue } // 没有url的就不要下发了 if len(adActionData.Urls) == 0 { continue } adActionDatas = append(adActionDatas, *adActionData) } minValue := 2 if len(videoTimerUrls) != 0 { minValue = 3 } // 大于minValue才进行投放 if len(adActionDatas) >= minValue { adInfo := AdInfo{ AdActions: adActionDatas, PercentBegin: percentBegin, PercentEnd: percentEnd, } lastInfos = append(lastInfos, adInfo) } } targetUrl := "" if len(gotoUrls) != 0 { targetUrl = gotoUrls[0] } videoUrl := "" if len(videoUrls) != 0 { videoUrl = videoUrls[0] } randAdData := make([]AdAction, 0, 50) if len(lastInfos) != 0 { randIdx := rand.Intn(len(lastInfos)) randAdData = lastInfos[randIdx].AdActions } hasClickAction := false for _, adData := range randAdData { if adData.Type == "CLICK" { hasClickAction = true break } } if !hasClickAction { targetUrl = "" } // 重新组装duration videoTimerUrls = make([]string, 0, 100) if len(randAdData) >= 3 { if randAdData[1].Type == "VIDEO_TIMER" { videoTimerUrls = randAdData[1].Urls } } rspAdDatas := make([]AdAction, 0, 20) for _, adData := range randAdData { if len(adData.Urls) == 0 || adData.Type == "" { continue } if adData.Type == "VIDEO_TIMER" && len(videoTimerUrls) > 0 { adData.Duration = int(dsp.AllDuration) - dsp.VideoTimeDuration } else if adData.Type == "VIDEO_TIMER" && len(videoTimerUrls) == 0 { adData.Duration = dsp.VideoTimeDuration } else if adData.Type == "VIEW" { if len(videoTimerUrls) > 0 { adData.Duration = dsp.VideoTimeDuration } else { adData.Duration = int(dsp.AllDuration) } } if adData.Type != "VIEW" { redis_data.SetDistributeActionNum(advertiser, adData.Type, dsp.SendPhoneType) } canReport, err := redis_data.GetDeviceIpReport(dsp.Imei, dsp.Ip) if err != nil { log.WithField("request_id", dsp.RequestId).Errorf("get device ip report failed: %s", err) return nil, err } md5Imei := "" // 返回的曝光, 如果没有返回过秒针的,那么加入 if adData.Type == "VIEW" && canReport { if dsp.ReplaceFlag == 0 { md5Imei = utils.Md5(dsp.Imei) } else { md5Imei = dsp.Imei } conf := adslib.GetConf() adDataUrl := strings.ReplaceAll(conf.HostMiao, "__IMEI__", md5Imei) adData.Urls = append(adData.Urls, adDataUrl) redis_data.SetDeviceIpReport(dsp.Imei, dsp.Ip) } rspAdDatas = append(rspAdDatas, adData) } adData := AdData{ TargetAddition: rspAdDatas, Target: targetUrl, Duration: dsp.AllDuration, VideoUrl: videoUrl, } return &adData, nil } // 组装展示 func genAdsAction(urls []string, advertiser string, dsp *utils.DspParam, action string, needControl bool) (*AdAction, error) { // 获取advertiser上报的总量 allSendNum, err := redis_data.GetAdsRequestNum(advertiser, dsp.SendPhoneType) if err != nil { return nil, err } allShowNum, err := redis_data.GetAdsFeedbackNum(advertiser, action, dsp.SendPhoneType) if err != nil { return nil, err } flowControlConf := redis_data.GetFlowPercentDuration(advertiser, action) flowPercent := 0 duration := 0 if flowControlConf != nil { flowPercent = flowControlConf.Percent duration = flowControlConf.Duration } lastUrls := make([]string, 0, 20) if !needControl || allSendNum == 0 || int( float32(allShowNum)/float32(allSendNum)*100) < flowPercent { reportUrl := getReportUrl(action, dsp) lastUrls = append(urls, reportUrl) } log.WithField("request_id", dsp.RequestId).Tracef("action: %s, all send: %d, all show: %d, control: +%v %+v %t\n", action, allSendNum, allShowNum, flowControlConf, lastUrls, needControl) if len(lastUrls) == 0 { duration = 0 } if action == "VIDEO_TIMER" { duration = 7 } adAction := AdAction{ Type: action, Duration: duration, Urls: lastUrls, } return &adAction, nil } // 组装上报的url func getReportUrl(action string, dsp *utils.DspParam) string { urlHost := adslib.GetConf().HostIos reportUrl := fmt.Sprintf("%s?action=%s&advertiser=video&req_source=%s&brand=%s&city_code=%d&request_id=%s&spefial_flag=%d", urlHost, action, dsp.ReqSource, dsp.Brand, dsp.DspCityCode, dsp.RequestId, dsp.SendPhoneType) return reportUrl }