custom.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. package addata
  2. import (
  3. "fmt"
  4. log "github.com/sirupsen/logrus"
  5. "math/rand"
  6. "miads/adslib"
  7. "miads/adslib/redis_data"
  8. "miads/adslib/utils"
  9. "strconv"
  10. "strings"
  11. "time"
  12. )
  13. func CombineOrderBy(adData *AdData, dsp *utils.DspParam) (*AdData, error) {
  14. customAdData, err := GetCustomAdsInfos(dsp, 1, 0, 0)
  15. if err != nil {
  16. return adData, err
  17. }
  18. if customAdData == nil {
  19. return adData, nil
  20. }
  21. if len(customAdData.TargetAddition) == 0 {
  22. return adData, nil
  23. }
  24. maxExchangeLen := len(customAdData.TargetAddition)
  25. // 最多替换数量, 不知道为啥是2
  26. if maxExchangeLen > 2 {
  27. maxExchangeLen = 2
  28. }
  29. hasClickTarget := false
  30. for i := 0; i < maxExchangeLen; i++ {
  31. adData.TargetAddition[i].Urls = customAdData.TargetAddition[i].Urls
  32. if customAdData.TargetAddition[i].Type == "CLICK" {
  33. adData.TargetAddition[i].Type = "CLICK"
  34. hasClickTarget = true
  35. }
  36. }
  37. if hasClickTarget {
  38. adData.Target = customAdData.Target
  39. adData.JsOrderId = customAdData.JsOrderId
  40. }
  41. return adData, nil
  42. }
  43. // 获取一个广告
  44. func GetOneAds(dsp *utils.DspParam, orderType int, fixFlag int) (*redis_data.AdOrderInfo, error) {
  45. // 取出广告
  46. orders, err := redis_data.GetOrderInfos(dsp, fixFlag)
  47. if err != nil {
  48. return nil, err
  49. }
  50. log.WithField("request_id", dsp.RequestId).Infof("get one ads, order len: %d", len(orders))
  51. if len(orders) == 0 {
  52. return nil, nil
  53. }
  54. gotOrders := make([]redis_data.AdOrderInfo, 0, 1000)
  55. allKpi := int64(0)
  56. for _, order := range orders {
  57. if order.OrderType == int64(orderType) {
  58. gotOrders = append(gotOrders, order)
  59. allKpi += order.ShowKpi
  60. }
  61. }
  62. orderRange := make([]int, 0, 1000)
  63. curRateIdx := 0
  64. // orderRange中记录的是当前同索引位置的gotOrders里的order的比例上限, 比如如果有两个order [ShowKpi:40, ShowKpi: 60]
  65. // 那么orderRange就是 [rate:400, rate: 1000], 所以要按比例取order, 只需要获得在比例上限里随机取一个数, 然后判断落在那个orderRange里,
  66. // 对应去gotOrders取同索引里的order即可, 因为算法里有对小于1的比例做补偿, 所以rate上限可能超出1000, rate乘1000而不是100主要是为了
  67. // 如果showKpi差异过大, 对大量小于1的订单做补偿, 会影响整体流量分布, 这里放到1000倍, 降低补偿1的影响, 算法复杂度是O(n), 只有三个非嵌套循环
  68. for _, order := range gotOrders {
  69. rate := int(float32(order.ShowKpi) / float32(allKpi) * 1000)
  70. // 防止比例过小, 取int后变为0
  71. if rate < 1 {
  72. rate = 1
  73. }
  74. curRateIdx = curRateIdx + rate
  75. orderRange = append(orderRange, curRateIdx)
  76. }
  77. randNum := rand.Intn(curRateIdx)
  78. for i, rateRange := range orderRange {
  79. if randNum <= rateRange {
  80. return &gotOrders[i], nil
  81. }
  82. }
  83. return nil, nil
  84. }
  85. // 获取投放数量
  86. func getNeedDispatchCount(adData *redis_data.AdOrderInfo) (int, error) {
  87. beginTime := time.Unix(adData.BeginTime, 0)
  88. // 获取当前分钟值
  89. beginMinute := beginTime.Minute() + (beginTime.Hour() * 60)
  90. // 获取起点分钟, 不知道原因, 不加会有bug
  91. beginMinute += 2
  92. // 获取分钟数到24点还能跑多少值
  93. key := "time_all_count_" + strconv.Itoa(beginMinute)
  94. // 0 默认曲线, 1 定制曲线
  95. if adData.LineType == 1 {
  96. key = fmt.Sprintf("time_all_count_%d_%d", adData.OrderID, beginMinute)
  97. }
  98. // 起始剩余值
  99. beginRemainDispatchCount, err := redis_data.GetRemainDispatchCount(key)
  100. if err != nil {
  101. return 0, err
  102. }
  103. // 计算最后分钟
  104. endTime := time.Unix(adData.EndTime, 0)
  105. endMinute := endTime.Minute() + (endTime.Hour() * 60)
  106. if endMinute > 1439 {
  107. // 不懂这里逻辑
  108. endMinute = endMinute - 1440 + 2
  109. }
  110. endKey := "time_all_count_" + strconv.Itoa(endMinute)
  111. if adData.LineType == 1 {
  112. // 定制曲线
  113. endKey = fmt.Sprintf("time_all_count_%d_%d", adData.OrderID, endMinute)
  114. }
  115. endRemainDispatchCnt, err := redis_data.GetRemainDispatchCount(endKey)
  116. if err != nil {
  117. return 0, err
  118. }
  119. log.Infof("begin need dispatch count key: %s, %d, end: %s, %d", key, beginRemainDispatchCount, endKey, endRemainDispatchCnt)
  120. // 结束的剩余值 - 起始剩余值 = 获取区间能跑的值
  121. needDispatchCount := beginRemainDispatchCount - endRemainDispatchCnt
  122. return needDispatchCount, nil
  123. }
  124. func GetCustomAdsInfos(dsp *utils.DspParam, orderType int, fixFlag int, xiaomiHasFlag int) (*AdData, error) {
  125. order, err := GetOneAds(dsp, orderType, fixFlag)
  126. if err != nil {
  127. log.WithField("request_id", dsp.RequestId).Errorf("get one ads failed: %s", err)
  128. return nil, err
  129. }
  130. log.WithField("request_id", dsp.RequestId).Infof("lat get one ad info, order: %+v, xiaomi has flag: %d", order, xiaomiHasFlag)
  131. if order == nil {
  132. return nil, nil
  133. }
  134. if xiaomiHasFlag == 1 {
  135. if strings.Index(order.Title, "_ios") > 0 {
  136. return nil, nil
  137. }
  138. }
  139. // 获取剩余时间内的值
  140. needDispatchCnt, err := getNeedDispatchCount(order)
  141. if err != nil {
  142. return nil, err
  143. }
  144. if needDispatchCnt < 1 {
  145. return nil, nil
  146. }
  147. curTime := time.Now()
  148. // #已经投放的key
  149. finishShowCnt, err := redis_data.GetFinishedDispatchCount(order.OrderID, "show", curTime)
  150. if err != nil {
  151. return nil, err
  152. }
  153. redis_data.SetPlanDispatchCount(order.OrderID, "show", needDispatchCnt, curTime)
  154. log.WithField("request_id", dsp.RequestId).Infof("all count: %d, over kpi: %d, show kpi: %d", needDispatchCnt, finishShowCnt, order.ShowKpi)
  155. // 计算曲线比例
  156. rate := float32(order.ShowKpi) / float32(needDispatchCnt)
  157. lineValue := 0
  158. key := ""
  159. if order.LineType == 1 {
  160. // 订单定制曲线
  161. lineValue, key, err = redis_data.GetOrderPerMinuteNeedDispatchCnt(order.OrderID, curTime)
  162. } else {
  163. lineValue, key, err = redis_data.GetPerMinuteNeedDispatchCnt(curTime)
  164. }
  165. if err != nil {
  166. return nil, err
  167. }
  168. log.WithField("request_id", dsp.RequestId).Infof("key: %s, line value: %d, rate: %f", key, lineValue, rate)
  169. // 当前分钟需要放出去的数量
  170. curMinuteNeedDispatchCnt := int(float32(lineValue) * rate)
  171. if curMinuteNeedDispatchCnt < 1 {
  172. curMinuteNeedDispatchCnt = 1
  173. }
  174. redis_data.SetOrderPlanDispatchCount(order.OrderID, "show", curMinuteNeedDispatchCnt, curTime)
  175. // 获取当前分钟已经完成的下发
  176. curMinuteFinishedDispatchCnt, key, err := redis_data.GetPreMinuteFinishedDispatchCount(order.OrderID, "show", curTime)
  177. log.WithField("request_id", dsp.RequestId).Infof("o value: %s, %d, w value: %d", key, curMinuteFinishedDispatchCnt, curMinuteNeedDispatchCnt)
  178. if curMinuteFinishedDispatchCnt < curMinuteNeedDispatchCnt {
  179. data := AdData{
  180. Duration: 5,
  181. JsOrderId: order.JsOrderID,
  182. OrderName: order.Title,
  183. UserAgent: dsp.UaOrigin,
  184. }
  185. //放量
  186. _, err := redis_data.IncrFinishedDispatchCount(order.OrderID, "show", curTime)
  187. if err != nil {
  188. return nil, err
  189. }
  190. _, err = redis_data.IncrPreMinuteFinishedDispatchCount(order.OrderID, "show", curTime)
  191. if err != nil {
  192. return nil, err
  193. }
  194. showUrl := order.ShowURL
  195. clickUrl := order.ClickURL
  196. targetUrl := order.TargetURL
  197. if strings.Index(order.Title, "_ios") != -1 {
  198. iosImei, iosUa, err := redis_data.GetIosUaImei(dsp.Ip)
  199. if err != nil {
  200. log.WithField("request_id", dsp.RequestId).Errorf("get ios ua imei failed: %s", err)
  201. return nil, err
  202. }
  203. if iosUa != "" {
  204. data.UserAgent = iosUa
  205. }
  206. if order.ImeiReplaceFlag == 1 && iosImei != "" {
  207. showUrl = strings.ReplaceAll(showUrl, "__IDFA__", iosImei)
  208. clickUrl = strings.ReplaceAll(clickUrl, "__IDFA__", iosImei)
  209. targetUrl = strings.ReplaceAll(targetUrl, "__IDFA__", iosImei)
  210. }
  211. if strings.Index(order.Title, "__OS__") != -1 {
  212. showUrl = strings.ReplaceAll(showUrl, "__OS__", "1")
  213. clickUrl = strings.ReplaceAll(clickUrl, "__OS__", "1")
  214. targetUrl = strings.ReplaceAll(targetUrl, "__OS__", "1")
  215. }
  216. } else if strings.Index(order.Title, "_android") != -1 {
  217. // 判断是否需要替换imei
  218. if order.ImeiReplaceFlag == 1 {
  219. showUrl = strings.ReplaceAll(showUrl, "__IMEI__", dsp.RealMd5Imei)
  220. clickUrl = strings.ReplaceAll(clickUrl, "__IMEI__", dsp.RealMd5Imei)
  221. targetUrl = strings.ReplaceAll(targetUrl, "__IMEI__", dsp.RealMd5Imei)
  222. }
  223. if strings.Index(order.Title, "__OS__") != -1 {
  224. showUrl = strings.ReplaceAll(showUrl, "__OS__", "0")
  225. clickUrl = strings.ReplaceAll(clickUrl, "__OS__", "0")
  226. targetUrl = strings.ReplaceAll(targetUrl, "__OS__", "0")
  227. }
  228. } else {
  229. r := rand.Intn(100)
  230. if r < 40 && xiaomiHasFlag == 0 {
  231. iosImei, iosUa, err := redis_data.GetIosUaImei(dsp.Ip)
  232. if err != nil {
  233. log.WithField("request_id", dsp.RequestId).Errorf("get ios ua imei failed: %s", err)
  234. return nil, err
  235. }
  236. if iosUa != "" {
  237. data.UserAgent = iosUa
  238. }
  239. if order.ImeiReplaceFlag == 1 && iosImei != "" {
  240. showUrl = strings.ReplaceAll(showUrl, "__IDFA__", iosImei)
  241. clickUrl = strings.ReplaceAll(clickUrl, "__IDFA__", iosImei)
  242. targetUrl = strings.ReplaceAll(targetUrl, "__IDFA__", iosImei)
  243. }
  244. if strings.Index(order.Title, "__OS__") != -1 {
  245. showUrl = strings.ReplaceAll(showUrl, "__OS__", "1")
  246. clickUrl = strings.ReplaceAll(clickUrl, "__OS__", "1")
  247. targetUrl = strings.ReplaceAll(targetUrl, "__OS__", "1")
  248. }
  249. } else {
  250. // 判断是否需要替换imei
  251. if order.ImeiReplaceFlag == 1 {
  252. showUrl = strings.ReplaceAll(showUrl, "__IMEI__", dsp.RealMd5Imei)
  253. clickUrl = strings.ReplaceAll(clickUrl, "__IMEI__", dsp.RealMd5Imei)
  254. targetUrl = strings.ReplaceAll(targetUrl, "__IMEI__", dsp.RealMd5Imei)
  255. }
  256. if strings.Index(order.Title, "__OS__") != -1 {
  257. showUrl = strings.ReplaceAll(showUrl, "__OS__", "0")
  258. clickUrl = strings.ReplaceAll(clickUrl, "__OS__", "0")
  259. targetUrl = strings.ReplaceAll(targetUrl, "__OS__", "0")
  260. }
  261. }
  262. }
  263. if strings.Index(order.Title, "__IP__") != -1 {
  264. showUrl = strings.ReplaceAll(showUrl, "__IP__", dsp.Ip)
  265. clickUrl = strings.ReplaceAll(clickUrl, "__IP__", dsp.Ip)
  266. targetUrl = strings.ReplaceAll(targetUrl, "__IP__", dsp.Ip)
  267. }
  268. var addi *AdAction
  269. if orderType == 0 {
  270. addi = genMonitorAction("VIEW", order.Title, dsp.ReqSource, showUrl)
  271. } else {
  272. addi = genMonitorAction("VIEW", order.Title, "follow", showUrl)
  273. }
  274. data.TargetAddition = append(data.TargetAddition, *addi)
  275. r := rand.Intn(1000)
  276. clickRate := int(float32(order.ClickKpi) / float32(order.ShowKpi) * 1000)
  277. log.WithField("request_id", dsp.RequestId).Infof("rand: %d, rate: %d", r, clickRate)
  278. if r < clickRate {
  279. //下发点击
  280. addi = genMonitorAction("CLICK", order.Title, dsp.ReqSource, clickUrl)
  281. data.TargetAddition = append(data.TargetAddition, *addi)
  282. md5Skip := utils.Md5(targetUrl)
  283. _, err = redis_data.IncrFinishedDispatchCount(order.OrderID, "click", curTime)
  284. if err != nil {
  285. log.WithField("request_id", dsp.RequestId).Errorf("incr finished dispatch count failed: %s", err)
  286. }
  287. realTarget := adslib.GetConf().Host + fmt.Sprintf("?action=LOADING&req_source=%s&advertiser=%s&skip=%s&skip_other=%s",
  288. dsp.ReqSource, order.Title, "", md5Skip)
  289. // 塞入缓存中
  290. redis_data.SetSkipInfo(md5Skip, targetUrl)
  291. data.Target = realTarget
  292. }
  293. return &data, nil
  294. }
  295. return nil, nil
  296. }
  297. func genMonitorAction(action string, title string, reqSource string, url string) *AdAction {
  298. duration := 5
  299. if action == "VIDEO_TIMER" {
  300. duration = 7
  301. }
  302. urlArr := make([]string, 0, 50)
  303. if url != "" {
  304. urlArr = strings.Split(url, "||")
  305. }
  306. adAction := AdAction{
  307. Type: action,
  308. Duration: duration,
  309. Urls: urlArr,
  310. }
  311. urlHost := adslib.GetConf().HostIos
  312. actionUrl := fmt.Sprintf("%s?action=%s&advertiser=%s&req_source=%s",
  313. urlHost, action, title, reqSource)
  314. adAction.Urls = append(adAction.Urls, actionUrl)
  315. return &adAction
  316. }