xiaomi.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546
  1. package addata
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. log "github.com/sirupsen/logrus"
  6. "html"
  7. "io/ioutil"
  8. "math/rand"
  9. "miads/adslib"
  10. "miads/adslib/device"
  11. "miads/adslib/redis_data"
  12. "miads/adslib/utils"
  13. "net/http"
  14. "net/url"
  15. "strconv"
  16. "strings"
  17. )
  18. type XiaomiAdData struct {
  19. DetailURL string `json:"detail_url"`
  20. DisplayType struct {
  21. Delay int64 `json:"delay"`
  22. Name string `json:"name"`
  23. RowCount int64 `json:"row_count"`
  24. } `json:"display_type"`
  25. Duration int64 `json:"duration"`
  26. EmcType string `json:"emc_type"`
  27. ID string `json:"id"`
  28. ImageURL string `json:"image_url"`
  29. Proportion string `json:"proportion"`
  30. Settings struct {
  31. ClickType string `json:"click_type"`
  32. HideCloseAt int64 `json:"hide_close_at"`
  33. LogTime string `json:"log_time"`
  34. ShowCloseAt int64 `json:"show_close_at"`
  35. } `json:"settings"`
  36. SkipTime int64 `json:"skip_time"`
  37. TagID string `json:"tag_id"`
  38. Target string `json:"target"`
  39. TargetAddition []string `json:"target_addition"`
  40. TargetAddition1 string `json:"target_addition1"`
  41. Title string `json:"title"`
  42. VideoURL string `json:"video_url"`
  43. }
  44. type XiaoMiAdInfoRsp struct {
  45. Data []XiaomiAdData `json:"data"`
  46. Msg string `json:"msg"`
  47. Result int64 `json:"result"`
  48. }
  49. type AdAction struct {
  50. Type string `json:"type"`
  51. Duration int `json:"duration"`
  52. Urls []string `json:"urls"`
  53. }
  54. type AdInfo struct {
  55. AdActions []AdAction `json:"ads_infos"`
  56. PercentBegin int `json:"percent_begin"`
  57. PercentEnd int `json:"percent_end"`
  58. }
  59. type AdData struct {
  60. TargetAddition []AdAction `json:"target_addition"`
  61. Target string `json:"target"`
  62. ImageUrl string `json:"image_url"`
  63. Duration int64 `json:"duration"`
  64. VideoUrl string `json:"video_url"`
  65. JsOrderId int64 `json:"js_order_id"`
  66. UserAgent string `json:"user_agent"`
  67. DpReport string `json:"dp_report,omitempty"`
  68. Dp string `json:"dp,omitempty"`
  69. OrderName string `json:"order_name,omitempty"`
  70. Result int `json:"result"`
  71. Msg string `json:"msg"`
  72. }
  73. func getAdsReqUrl(dsp *utils.DspParam) string {
  74. reqUrl := "https://m.video.xiaomi.com/api/a3/otv_emc?"
  75. ref := "yilin"
  76. sn := "05817a33d4210ad2c67f4b869b5eedde"
  77. // 组装 emcp
  78. reqUrl = reqUrl + "_emcp=pre_play"
  79. // 组装 devid
  80. reqUrl = reqUrl + "&_devid=" + dsp.RealMd5Imei
  81. // 组装 ref
  82. reqUrl = reqUrl + "&ref=" + ref
  83. // 组装 sn
  84. reqUrl = reqUrl + "&_sn=" + sn
  85. // 组装 ip
  86. reqUrl = reqUrl + "&__ip__=" + dsp.Ip
  87. rad := rand.Intn(100)
  88. if rad < 15 {
  89. dsp.SendPhoneType = 1
  90. } else {
  91. dsp.SendPhoneType = 0
  92. }
  93. reqUrl = reqUrl + fmt.Sprintf("&_phonetype=%d", dsp.SendPhoneType)
  94. return reqUrl
  95. }
  96. // 获取广告信息
  97. func GetAdsInfos(dsp *utils.DspParam, advertiser string) (*AdData, error) {
  98. reqUrl := getAdsReqUrl(dsp)
  99. if dsp.SendPhoneType == 0 {
  100. device.SetAdsTagLog("xiaomi", dsp.ReqSource, "ADS_XIAOMI", dsp.DspCityCode)
  101. } else if dsp.SendPhoneType == 1 {
  102. device.SetAdsTagLog("xiaomi", dsp.ReqSource, "ADS_XIAOMI_1", dsp.DspCityCode)
  103. }
  104. client := &http.Client{}
  105. fmt.Printf("req url: %s\n", reqUrl)
  106. req, err := http.NewRequest("GET", reqUrl, nil)
  107. if err != nil {
  108. return nil, err
  109. }
  110. req.Header.Set(",authority", "m.video.xiaomi.com")
  111. req.Header.Set(",method", "GET")
  112. req.Header.Set(",path", reqUrl[26:])
  113. req.Header.Set(",scheme", "https")
  114. req.Header.Set("content-type", "application/json")
  115. req.Header.Set("accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3")
  116. req.Header.Set("accept-encoding", "gzip, deflate, br")
  117. req.Header.Set("accept-language", "zh-CN,zh;q=0.9")
  118. req.Header.Set("cache-control", "max-age=0")
  119. req.Header.Set("upgrade-insecure-requests", "1")
  120. req.Header.Set("user-agent", dsp.Ua)
  121. resp, err := client.Do(req)
  122. if err != nil {
  123. return nil, err
  124. }
  125. defer resp.Body.Close()
  126. body, err := ioutil.ReadAll(resp.Body)
  127. fmt.Printf("rsp body: %s\n", body)
  128. if err != nil {
  129. return nil, err
  130. }
  131. rsp := XiaoMiAdInfoRsp{}
  132. err = json.Unmarshal(body, &rsp)
  133. if err != nil {
  134. return nil, err
  135. }
  136. adsDataNum := len(rsp.Data)
  137. xiaomiResponseAction := map[string]int{
  138. "xiaomi_view": 0, "xiaomi_click": 0, "xiaomi_close": 0, "xiaomi_video_finish": 0, "xiaomi_video_timer": 0,
  139. }
  140. if rsp.Result != 1 {
  141. return nil, nil
  142. }
  143. // bom渠道不处理
  144. if dsp.ReqSource != "bom" {
  145. rad := rand.Intn(100)
  146. if rad < 2 {
  147. dsp.ReqSource = "kk_h5"
  148. } else if rad < 4 {
  149. dsp.ReqSource = "day_09"
  150. } else if rad < 8 {
  151. dsp.ReqSource = "fu008"
  152. } else if rad < 9 {
  153. dsp.ReqSource = "yzh01"
  154. } else if rad < 11 {
  155. dsp.ReqSource = "qihuang88"
  156. }
  157. }
  158. if len(rsp.Data) == 0 {
  159. return nil, nil
  160. }
  161. ///last_infos = xiaomi_mix.xiaomi_fuse(data, dsp.req_source)
  162. dataInfo := rsp.Data[0]
  163. dsp.AllDuration = dataInfo.Duration
  164. dsp.VideoTimeDuration = 7
  165. showUrls := make([]string, 0, 100)
  166. clickUrls := make([]string, 0, 100)
  167. closeUrls := make([]string, 0, 100)
  168. videoFinishUrls := make([]string, 0, 100)
  169. videoTimerUrls := make([]string, 0, 100)
  170. for _, target := range rsp.Data[0].TargetAddition {
  171. target := html.UnescapeString(target)
  172. targetParseUrl, err := url.Parse(target)
  173. if err != nil {
  174. log.WithField("request_id", dsp.RequestId).Errorf("parse url failed, url: %s, err: %s", target, err)
  175. continue
  176. }
  177. targetParams := targetParseUrl.Query()
  178. targetUrl := targetParams.Get("url")
  179. event := targetParams.Get("event")
  180. if targetUrl != "" {
  181. switch event {
  182. case "VIEW":
  183. showUrls = append(showUrls, targetUrl)
  184. xiaomiResponseAction["xiaomi_view"] = 1
  185. case "CLICK":
  186. clickUrls = append(clickUrls, targetUrl)
  187. xiaomiResponseAction["xiaomi_click"] = 1
  188. case "CLOSE":
  189. closeUrls = append(closeUrls, targetUrl)
  190. xiaomiResponseAction["xiaomi_close"] = 1
  191. case "VIDEO_FINISH":
  192. videoFinishUrls = append(videoFinishUrls, targetUrl)
  193. xiaomiResponseAction["xiaomi_video_finish"] = 1
  194. case "VIDEO_TIMER":
  195. videoTimerUrls = append(videoTimerUrls, targetUrl)
  196. }
  197. }
  198. if event == "VIDEO_TIMER" {
  199. timeStr := targetParams.Get("time")
  200. if timeStr != "" {
  201. videoTimeDuration, _ := strconv.Atoi(timeStr)
  202. dsp.VideoTimeDuration = videoTimeDuration
  203. xiaomiResponseAction["xiaomi_video_timer"] = 1
  204. }
  205. }
  206. }
  207. gotoUrls := make([]string, 0, 100)
  208. target := rsp.Data[0].Target
  209. if target != "" {
  210. targetUrlObj, _ := url.Parse(target)
  211. targetParams := targetUrlObj.Query()
  212. targetUrl := targetParams.Get("link_url")
  213. if targetUrl == "" {
  214. targetUrl = strings.ReplaceAll(targetUrl, "mv:", "")
  215. }
  216. targetUrl = html.UnescapeString(targetUrl)
  217. if targetUrl != "" {
  218. gotoUrls = append(gotoUrls, targetUrl)
  219. }
  220. }
  221. if dsp.ReqSource == "mh" {
  222. closeUrls = videoFinishUrls
  223. }
  224. videoUrls := make([]string, 0, 100)
  225. videoUrl := rsp.Data[0].VideoURL
  226. if videoUrl != "" {
  227. videoUrls = append(videoUrls, videoUrl)
  228. }
  229. imageUrls := make([]string, 0, 100)
  230. imageUrl := rsp.Data[0].ImageURL
  231. if imageUrl != "" {
  232. imageUrls = append(imageUrls, imageUrl)
  233. }
  234. fmt.Printf("%+v, addata_num: %d\n", xiaomiResponseAction, adsDataNum)
  235. if len(gotoUrls) == 0 {
  236. return nil, nil
  237. }
  238. fmt.Printf("goto urls: %+v\nshow urls: %+v\nclick_urls: %+v\nclose urls: %+v\nvideo finish urls: %+v\nimagurls: %+v\nvideo urls:%+v\n",
  239. gotoUrls, showUrls, clickUrls, closeUrls, videoFinishUrls, imageUrls, videoUrls)
  240. adsData, err := CombineLastAdsInfos(dsp, advertiser, gotoUrls, showUrls, clickUrls, closeUrls, videoFinishUrls, videoTimerUrls, videoUrls)
  241. if err != nil {
  242. return nil, err
  243. }
  244. fmt.Printf("addata: %+v\n", adsData)
  245. if dsp.SendPhoneType == 0 {
  246. redis_data.SetAdsRequestNum(advertiser, 0)
  247. } else if dsp.SendPhoneType == 1 {
  248. redis_data.SetAdsRequestNum(advertiser, 1)
  249. }
  250. return adsData, nil
  251. }
  252. func getActionsConf(advertiser string) []string {
  253. defaultActions := []string{"VIEW", "CLICK", "CLOSE", "VIDEO_FINISH", "VIDEO_TIMER"}
  254. svrConf := adslib.GetConf()
  255. actions, ok := svrConf.AdsActions[advertiser]
  256. if !ok || len(actions) == 0 {
  257. return defaultActions
  258. }
  259. return actions
  260. }
  261. func getCombinationActionsConf(advertiser string) []adslib.CombinationActionsConf {
  262. svrConf := adslib.GetConf()
  263. combinationActions, ok := svrConf.CombinationActions[advertiser]
  264. if !ok {
  265. return []adslib.CombinationActionsConf{}
  266. }
  267. return combinationActions
  268. }
  269. // 组装最后的广告信息
  270. func CombineLastAdsInfos(dsp *utils.DspParam, advertiser string, gotoUrls []string, showUrls []string,
  271. clickUrls []string, closeUrls []string, videoFinishUrls []string,
  272. videoTimerUrls []string, videoUrls []string) (*AdData, error) {
  273. // 判断是不是关掉所有Action
  274. actions := getActionsConf(advertiser)
  275. if len(actions) == 0 {
  276. return nil, nil
  277. }
  278. clickChannelFlag, err := redis_data.GetChannelFlag(dsp.ReqSource, "ads_click")
  279. if err != nil {
  280. log.WithField("request_id", dsp.RequestId).Errorf("get ads_click channel flag failed: %s", err)
  281. return nil, err
  282. }
  283. log.WithField("request_id", dsp.RequestId).Tracef("click channel flag: %+v\n", clickChannelFlag)
  284. clickRandom := rand.Intn(100)
  285. needClick := false
  286. if clickChannelFlag.ChannelFlag == 1 && clickRandom <= clickChannelFlag.Weigth {
  287. needClick = true
  288. }
  289. lastInfos := make([]AdInfo, 0, 50)
  290. combinationActions := getCombinationActionsConf(advertiser)
  291. for _, combAction := range combinationActions {
  292. adActionDatas := make([]AdAction, 0, 20)
  293. percentBegin := combAction.PercentBegin
  294. percentEnd := combAction.PercentEnd
  295. actionGroup := combAction.GroupValue
  296. for _, action := range actionGroup {
  297. urls := make([]string, 0, 50)
  298. if dsp.ReqSource == "mh" {
  299. if action == "CLOSE" {
  300. action = "VIDEO_FINISH"
  301. }
  302. }
  303. switch action {
  304. case "VIEW":
  305. urls = showUrls
  306. case "CLICK":
  307. urls = clickUrls
  308. case "CLOSE":
  309. urls = closeUrls
  310. case "VIDEO_FINISH":
  311. urls = videoFinishUrls
  312. case "VIDEO_TIMER":
  313. urls = videoTimerUrls
  314. default:
  315. continue
  316. }
  317. // click的控制 控制bom
  318. if action == "CLICK" && dsp.RealReqSource == "bom" && dsp.SupClickFlag == 0 {
  319. continue
  320. }
  321. // show的控制 控制bom
  322. if action == "VIEW" && dsp.RealReqSource == "bom" && dsp.SupShowFlag == 0 {
  323. continue
  324. }
  325. if action == "CLICK" && !needClick {
  326. continue
  327. }
  328. if len(urls) == 0 && action == "VIDEO_TIMER" {
  329. continue
  330. }
  331. adActionData, err := genAdsAction(urls, advertiser, dsp, action, true)
  332. if err != nil {
  333. fmt.Printf("gen ads action data failed, err: %s\n", err)
  334. continue
  335. }
  336. // 没有url的就不要下发了
  337. if len(adActionData.Urls) == 0 {
  338. continue
  339. }
  340. adActionDatas = append(adActionDatas, *adActionData)
  341. }
  342. minValue := 2
  343. if len(videoTimerUrls) != 0 {
  344. minValue = 3
  345. }
  346. // 大于minValue才进行投放
  347. if len(adActionDatas) >= minValue {
  348. adInfo := AdInfo{
  349. AdActions: adActionDatas,
  350. PercentBegin: percentBegin,
  351. PercentEnd: percentEnd,
  352. }
  353. lastInfos = append(lastInfos, adInfo)
  354. }
  355. }
  356. targetUrl := ""
  357. if len(gotoUrls) != 0 {
  358. targetUrl = gotoUrls[0]
  359. }
  360. videoUrl := ""
  361. if len(videoUrls) != 0 {
  362. videoUrl = videoUrls[0]
  363. }
  364. randAdData := make([]AdAction, 0, 50)
  365. if len(lastInfos) != 0 {
  366. randIdx := rand.Intn(len(lastInfos))
  367. randAdData = lastInfos[randIdx].AdActions
  368. }
  369. hasClickAction := false
  370. for _, adData := range randAdData {
  371. if adData.Type == "CLICK" {
  372. hasClickAction = true
  373. break
  374. }
  375. }
  376. if !hasClickAction {
  377. targetUrl = ""
  378. }
  379. // 重新组装duration
  380. videoTimerUrls = make([]string, 0, 100)
  381. if len(randAdData) >= 3 {
  382. if randAdData[1].Type == "VIDEO_TIMER" {
  383. videoTimerUrls = randAdData[1].Urls
  384. }
  385. }
  386. rspAdDatas := make([]AdAction, 0, 20)
  387. for _, adData := range randAdData {
  388. if len(adData.Urls) == 0 || adData.Type == "" {
  389. continue
  390. }
  391. if adData.Type == "VIDEO_TIMER" && len(videoTimerUrls) > 0 {
  392. adData.Duration = int(dsp.AllDuration) - dsp.VideoTimeDuration
  393. } else if adData.Type == "VIDEO_TIMER" && len(videoTimerUrls) == 0 {
  394. adData.Duration = dsp.VideoTimeDuration
  395. } else if adData.Type == "VIEW" {
  396. if len(videoTimerUrls) > 0 {
  397. adData.Duration = dsp.VideoTimeDuration
  398. } else {
  399. adData.Duration = int(dsp.AllDuration)
  400. }
  401. }
  402. if adData.Type != "VIEW" {
  403. redis_data.SetDistributeActionNum(advertiser, adData.Type, dsp.SendPhoneType)
  404. }
  405. canReport, err := redis_data.GetDeviceIpReport(dsp.Imei, dsp.Ip)
  406. if err != nil {
  407. log.WithField("request_id", dsp.RequestId).Errorf("get device ip report failed: %s", err)
  408. return nil, err
  409. }
  410. md5Imei := ""
  411. // 返回的曝光, 如果没有返回过秒针的,那么加入
  412. if adData.Type == "VIEW" && canReport {
  413. if dsp.ReplaceFlag == 0 {
  414. md5Imei = utils.Md5(dsp.Imei)
  415. } else {
  416. md5Imei = dsp.Imei
  417. }
  418. conf := adslib.GetConf()
  419. adDataUrl := strings.ReplaceAll(conf.HostMiao, "__IMEI__", md5Imei)
  420. adData.Urls = append(adData.Urls, adDataUrl)
  421. redis_data.SetDeviceIpReport(dsp.Imei, dsp.Ip)
  422. }
  423. rspAdDatas = append(rspAdDatas, adData)
  424. }
  425. adData := AdData{
  426. TargetAddition: rspAdDatas,
  427. Target: targetUrl,
  428. Duration: dsp.AllDuration,
  429. VideoUrl: videoUrl,
  430. }
  431. return &adData, nil
  432. }
  433. // 组装展示
  434. func genAdsAction(urls []string, advertiser string,
  435. dsp *utils.DspParam, action string, needControl bool) (*AdAction, error) {
  436. // 获取advertiser上报的总量
  437. allSendNum, err := redis_data.GetAdsRequestNum(advertiser, dsp.SendPhoneType)
  438. if err != nil {
  439. return nil, err
  440. }
  441. allShowNum, err := redis_data.GetAdsFeedbackNum(advertiser, action, dsp.SendPhoneType)
  442. if err != nil {
  443. return nil, err
  444. }
  445. flowControlConf := redis_data.GetFlowPercentDuration(advertiser, action)
  446. flowPercent := 0
  447. duration := 0
  448. if flowControlConf != nil {
  449. flowPercent = flowControlConf.Percent
  450. duration = flowControlConf.Duration
  451. }
  452. lastUrls := make([]string, 0, 20)
  453. if !needControl || allSendNum == 0 || int(
  454. float32(allShowNum)/float32(allSendNum)*100) < flowPercent {
  455. reportUrl := getReportUrl(action, dsp)
  456. lastUrls = append(urls, reportUrl)
  457. }
  458. 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)
  459. if len(lastUrls) == 0 {
  460. duration = 0
  461. }
  462. if action == "VIDEO_TIMER" {
  463. duration = 7
  464. }
  465. adAction := AdAction{
  466. Type: action,
  467. Duration: duration,
  468. Urls: lastUrls,
  469. }
  470. return &adAction, nil
  471. }
  472. // 组装上报的url
  473. func getReportUrl(action string, dsp *utils.DspParam) string {
  474. urlHost := adslib.GetConf().HostIos
  475. reportUrl := fmt.Sprintf("%s?action=%s&advertiser=video&req_source=%s&brand=%s&city_code=%d&request_id=%s&spefial_flag=%d",
  476. urlHost, action, dsp.ReqSource, dsp.Brand, dsp.DspCityCode, dsp.RequestId, dsp.SendPhoneType)
  477. return reportUrl
  478. }