123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369 |
- 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
- }
- log.WithField("request_id", dsp.RequestId).Infof("get one ads, order len: %d", len(orders))
- 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
- }
- log.WithField("request_id", dsp.RequestId).Infof("lat get one ad info, order: %+v, xiaomi has flag: %d", order, xiaomiHasFlag)
- if order == nil {
- return nil, nil
- }
- 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
- key := ""
- if order.LineType == 1 {
- // 订单定制曲线
- lineValue, key, err = redis_data.GetOrderPerMinuteNeedDispatchCnt(order.OrderID, curTime)
- } else {
- lineValue, key, err = redis_data.GetPerMinuteNeedDispatchCnt(curTime)
- }
- if err != nil {
- return nil, err
- }
- log.WithField("request_id", dsp.RequestId).Infof("key: %s, line value: %d, rate: %f", key, lineValue, rate)
- // 当前分钟需要放出去的数量
- curMinuteNeedDispatchCnt := int(float32(lineValue) * rate)
- if curMinuteNeedDispatchCnt < 1 {
- curMinuteNeedDispatchCnt = 1
- }
- redis_data.SetOrderPlanDispatchCount(order.OrderID, "show", curMinuteNeedDispatchCnt, curTime)
- // 获取当前分钟已经完成的下发
- curMinuteFinishedDispatchCnt, key, err := redis_data.GetPreMinuteFinishedDispatchCount(order.OrderID, "show", curTime)
- log.WithField("request_id", dsp.RequestId).Infof("o value: %s, %d, w value: %d", key, 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", curTime)
- if err != nil {
- return nil, err
- }
- _, err = redis_data.IncrPreMinuteFinishedDispatchCount(order.OrderID, "show", 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", 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
- }
|