package addata import ( "fmt" log "github.com/sirupsen/logrus" "math/rand" "miads/adslib" "miads/adslib/redis_data" "miads/adslib/utils" "strconv" "strings" "time" ) func CombineOrderBy(adData *AdData, dsp *utils.DspParam) (*AdData, error) { customAdData, err := GetCustomAdsInfos(dsp, 1, 0, 0) if err != nil { return adData, err } if customAdData == nil { return adData, nil } if len(customAdData.TargetAddition) == 0 { return adData, nil } maxExchangeLen := len(customAdData.TargetAddition) // 最多替换数量, 不知道为啥是2 if maxExchangeLen > 2 { maxExchangeLen = 2 } hasClickTarget := false for i := 0; i < maxExchangeLen; i++ { adData.TargetAddition[i].Urls = customAdData.TargetAddition[i].Urls if customAdData.TargetAddition[i].Type == "CLICK" { adData.TargetAddition[i].Type = "CLICK" hasClickTarget = true } } if hasClickTarget { adData.Target = customAdData.Target adData.JsOrderId = customAdData.JsOrderId } return adData, nil } // 获取一个广告 func GetOneAds(dsp *utils.DspParam, orderType int, fixFlag int) (*redis_data.AdOrderInfo, error) { // 取出广告 orders, err := redis_data.GetOrderInfos(dsp, fixFlag) if err != nil { return nil, err } if len(orders) == 0 { return nil, nil } gotOrders := make([]redis_data.AdOrderInfo, 0, 1000) allKpi := int64(0) for _, order := range orders { if order.OrderType == int64(orderType) { gotOrders = append(gotOrders, order) allKpi += order.ShowKpi } } orderRange := make([]int, 0, 1000) curRateIdx := 0 // orderRange中记录的是当前同索引位置的gotOrders里的order的比例上限, 比如如果有两个order [ShowKpi:40, ShowKpi: 60] // 那么orderRange就是 [rate:400, rate: 1000], 所以要按比例取order, 只需要获得在比例上限里随机取一个数, 然后判断落在那个orderRange里, // 对应去gotOrders取同索引里的order即可, 因为算法里有对小于1的比例做补偿, 所以rate上限可能超出1000, rate乘1000而不是100主要是为了 // 如果showKpi差异过大, 对大量小于1的订单做补偿, 会影响整体流量分布, 这里放到1000倍, 降低补偿1的影响, 算法复杂度是O(n), 只有三个非嵌套循环 for _, order := range gotOrders { rate := int(float32(order.ShowKpi) / float32(allKpi) * 1000) // 防止比例过小, 取int后变为0 if rate < 1 { rate = 1 } curRateIdx = curRateIdx + rate orderRange = append(orderRange, curRateIdx) } randNum := rand.Intn(curRateIdx) for i, rateRange := range orderRange { if randNum <= rateRange { return &gotOrders[i], nil } } return nil, nil } // 获取投放数量 func getNeedDispatchCount(adData *redis_data.AdOrderInfo) (int, error) { beginTime := time.Unix(adData.BeginTime, 0) // 获取当前分钟值 beginMinute := beginTime.Minute() + (beginTime.Hour() * 60) // 获取起点分钟, 不知道原因, 不加会有bug beginMinute += 2 // 获取分钟数到24点还能跑多少值 key := "time_all_count_" + strconv.Itoa(beginMinute) // 0 默认曲线, 1 定制曲线 if adData.LineType == 1 { key = fmt.Sprintf("time_all_count_%d_%d", adData.OrderID, beginMinute) } // 起始剩余值 beginRemainDispatchCount, err := redis_data.GetRemainDispatchCount(key) if err != nil { return 0, err } // 计算最后分钟 endTime := time.Unix(adData.EndTime, 0) endMinute := endTime.Minute() + (endTime.Hour() * 60) if endMinute > 1439 { // 不懂这里逻辑 endMinute = endMinute - 1440 + 2 } endKey := "time_all_count_" + strconv.Itoa(endMinute) if adData.LineType == 1 { // 定制曲线 endKey = fmt.Sprintf("time_all_count_%d_%d", adData.OrderID, endMinute) } endRemainDispatchCnt, err := redis_data.GetRemainDispatchCount(endKey) if err != nil { return 0, err } log.Infof("begin need dispatch count key: %s, %d, end: %s, %d", key, beginRemainDispatchCount, endKey, endRemainDispatchCnt) // 结束的剩余值 - 起始剩余值 = 获取区间能跑的值 needDispatchCount := beginRemainDispatchCount - endRemainDispatchCnt return needDispatchCount, nil } func GetCustomAdsInfos(dsp *utils.DspParam, orderType int, fixFlag int, xiaomiHasFlag int) (*AdData, error) { order, err := GetOneAds(dsp, orderType, fixFlag) if err != nil { log.WithField("request_id", dsp.RequestId).Errorf("get one ads failed: %s", err) return nil, err } if order == nil { return nil, nil } log.WithField("request_id", dsp.RequestId).Infof("begin got custom ad info, xiaomi has flag: %d", xiaomiHasFlag) if xiaomiHasFlag == 1 { if strings.Index(order.Title, "_ios") > 0 { return nil, nil } } // 获取剩余时间内的值 needDispatchCnt, err := getNeedDispatchCount(order) if err != nil { return nil, err } if needDispatchCnt < 1 { return nil, nil } curTime := time.Now() // #已经投放的key finishShowCnt, err := redis_data.GetFinishedDispatchCount(order.OrderID, "show", curTime) if err != nil { return nil, err } redis_data.SetPlanDispatchCount(order.OrderID, "show", needDispatchCnt, curTime) log.WithField("request_id", dsp.RequestId).Infof("all count: %d, over kpi: %d, show kpi: %d", needDispatchCnt, finishShowCnt, order.ShowKpi) // 计算曲线比例 rate := float32(order.ShowKpi) / float32(needDispatchCnt) lineValue := 0 if order.LineType == 1 { // 订单定制曲线 lineValue, err = redis_data.GetOrderPerMinuteNeedDispatchCnt(order.OrderID, curTime) } else { lineValue, err = redis_data.GetPerMinuteNeedDispatchCnt(curTime) } if err != nil { return nil, err } log.WithField("request_id", dsp.RequestId).Infof("line value: %d, rate: %f", lineValue, rate) // 当前分钟需要放出去的数量 curMinuteNeedDispatchCnt := int(float32(lineValue) * rate) if curMinuteNeedDispatchCnt < 1 { curMinuteNeedDispatchCnt = 1 } redis_data.SetOrderPlanDispatchCount(order.OrderID, "show", curMinuteNeedDispatchCnt, curTime) log.WithField("request_id", dsp.RequestId).Infof("tt thirds: %+v", order) // 获取当前分钟已经完成的下发 curMinuteFinishedDispatchCnt, err := redis_data.GetPreMinuteFinishedDispatchCount(order.OrderID, "show", curTime) log.WithField("request_id", dsp.RequestId).Infof("o value: %d, w value: %d", curMinuteFinishedDispatchCnt, curMinuteNeedDispatchCnt) if curMinuteFinishedDispatchCnt < curMinuteNeedDispatchCnt { data := AdData{ Duration: 5, JsOrderId: order.JsOrderID, OrderName: order.Title, UserAgent: dsp.UaOrigin, } //放量 _, err := redis_data.IncrFinishedDispatchCount(order.OrderID, "show", 1, curTime) if err != nil { return nil, err } _, err = redis_data.IncrPreMinuteFinishedDispatchCount(order.OrderID, "show", 1, curTime) if err != nil { return nil, err } showUrl := order.ShowURL clickUrl := order.ClickURL targetUrl := order.TargetURL if strings.Index(order.Title, "_ios") != -1 { iosImei, iosUa, err := redis_data.GetIosUaImei(dsp.Ip) if err != nil { log.WithField("request_id", dsp.RequestId).Errorf("get ios ua imei failed: %s", err) return nil, err } if iosUa != "" { data.UserAgent = iosUa } if order.ImeiReplaceFlag == 1 && iosImei != "" { showUrl = strings.ReplaceAll(showUrl, "__IDFA__", iosImei) clickUrl = strings.ReplaceAll(clickUrl, "__IDFA__", iosImei) targetUrl = strings.ReplaceAll(targetUrl, "__IDFA__", iosImei) } if strings.Index(order.Title, "__OS__") != -1 { showUrl = strings.ReplaceAll(showUrl, "__OS__", "1") clickUrl = strings.ReplaceAll(clickUrl, "__OS__", "1") targetUrl = strings.ReplaceAll(targetUrl, "__OS__", "1") } } else if strings.Index(order.Title, "_android") != -1 { // 判断是否需要替换imei if order.ImeiReplaceFlag == 1 { showUrl = strings.ReplaceAll(showUrl, "__IMEI__", dsp.RealMd5Imei) clickUrl = strings.ReplaceAll(clickUrl, "__IMEI__", dsp.RealMd5Imei) targetUrl = strings.ReplaceAll(targetUrl, "__IMEI__", dsp.RealMd5Imei) } if strings.Index(order.Title, "__OS__") != -1 { showUrl = strings.ReplaceAll(showUrl, "__OS__", "0") clickUrl = strings.ReplaceAll(clickUrl, "__OS__", "0") targetUrl = strings.ReplaceAll(targetUrl, "__OS__", "0") } } else { r := rand.Intn(100) if r < 40 && xiaomiHasFlag == 0 { iosImei, iosUa, err := redis_data.GetIosUaImei(dsp.Ip) if err != nil { log.WithField("request_id", dsp.RequestId).Errorf("get ios ua imei failed: %s", err) return nil, err } if iosUa != "" { data.UserAgent = iosUa } if order.ImeiReplaceFlag == 1 && iosImei != "" { showUrl = strings.ReplaceAll(showUrl, "__IDFA__", iosImei) clickUrl = strings.ReplaceAll(clickUrl, "__IDFA__", iosImei) targetUrl = strings.ReplaceAll(targetUrl, "__IDFA__", iosImei) } if strings.Index(order.Title, "__OS__") != -1 { showUrl = strings.ReplaceAll(showUrl, "__OS__", "1") clickUrl = strings.ReplaceAll(clickUrl, "__OS__", "1") targetUrl = strings.ReplaceAll(targetUrl, "__OS__", "1") } } else { // 判断是否需要替换imei if order.ImeiReplaceFlag == 1 { showUrl = strings.ReplaceAll(showUrl, "__IMEI__", dsp.RealMd5Imei) clickUrl = strings.ReplaceAll(clickUrl, "__IMEI__", dsp.RealMd5Imei) targetUrl = strings.ReplaceAll(targetUrl, "__IMEI__", dsp.RealMd5Imei) } if strings.Index(order.Title, "__OS__") != -1 { showUrl = strings.ReplaceAll(showUrl, "__OS__", "0") clickUrl = strings.ReplaceAll(clickUrl, "__OS__", "0") targetUrl = strings.ReplaceAll(targetUrl, "__OS__", "0") } } } if strings.Index(order.Title, "__IP__") != -1 { showUrl = strings.ReplaceAll(showUrl, "__IP__", dsp.Ip) clickUrl = strings.ReplaceAll(clickUrl, "__IP__", dsp.Ip) targetUrl = strings.ReplaceAll(targetUrl, "__IP__", dsp.Ip) } var addi *AdAction if orderType == 0 { addi = genMonitorAction("VIEW", order.Title, dsp.ReqSource, showUrl) } else { addi = genMonitorAction("VIEW", order.Title, "follow", showUrl) } data.TargetAddition = append(data.TargetAddition, *addi) r := rand.Intn(1000) clickRate := int(float32(order.ClickKpi) / float32(order.ShowKpi) * 1000) log.WithField("request_id", dsp.RequestId).Infof("rand: %d, rate: %d", r, clickRate) if r < clickRate { //下发点击 addi = genMonitorAction("CLICK", order.Title, dsp.ReqSource, clickUrl) data.TargetAddition = append(data.TargetAddition, *addi) md5Skip := utils.Md5(targetUrl) _, err = redis_data.IncrFinishedDispatchCount(order.OrderID, "click", 1, curTime) if err != nil { log.WithField("request_id", dsp.RequestId).Errorf("incr finished dispatch count failed: %s", err) } realTarget := adslib.GetConf().Host + fmt.Sprintf("?action=LOADING&req_source=%s&advertiser=%s&skip=%s&skip_other=%s", dsp.ReqSource, order.Title, "", md5Skip) // 塞入缓存中 redis_data.SetSkipInfo(md5Skip, targetUrl) data.Target = realTarget } return &data, nil } return nil, nil } func genMonitorAction(action string, title string, reqSource string, url string) *AdAction { duration := 5 if action == "VIDEO_TIMER" { duration = 7 } urlArr := make([]string, 0, 50) if url != "" { urlArr = strings.Split(url, "||") } adAction := AdAction{ Type: action, Duration: duration, Urls: urlArr, } urlHost := adslib.GetConf().HostIos actionUrl := fmt.Sprintf("%s?action=%s&advertiser=%s&req_source=%s", urlHost, action, title, reqSource) adAction.Urls = append(adAction.Urls, actionUrl) return &adAction }